Skip to content

feat(symbol): objectFit, viewbox and viewboxPreset for sizing custom symbols - #4707

Draft
raykholo wants to merge 3 commits into
tscircuit:mainfrom
raykholo:symbol-object-fit
Draft

raykholo wants to merge 3 commits into
tscircuit:mainfrom
raykholo:symbol-object-fit

Conversation

@raykholo

@raykholo raykholo commented Oct 16, 2026 •

Copy link
Copy Markdown

Implements #4698 with the opt-in props from tscircuit/props#944, following Seve's direction on the issue. Draft until props#944 is released: CI here runs against the published props, so the new tests can't pass yet.

Change (Symbol's user-coordinate transform)

  • objectFit="contain": both axes scale by one factor, the largest that fits width × height. With only width or only height, that dimension sets the factor. Unset or "fill" is today's per-axis scale.
  • viewbox={{ x, y, width, height }}: that region of the symbol's own coordinates is what gets fitted, instead of the measured bounds.
  • viewboxPreset="shape_bounds": the measured bounds leave out the ports, so port stubs no longer shrink the drawing. "shape_and_port_bounds" and unset keep today's measurement.
  • TSX typing: <symbol> is typed as React's SVG symbol element, so the three props join SVGAttributes, the same way <switch>'s do (lib/fiber/react-jsx-compat.ts).

Nothing changes for a symbol that sets none of the new props.

Tests (one behaviour per file; each fails against the commit before it)

  • symbol-object-fit-contain: a 1 × 1 square at width={2} height={1} comes out 2 × 1 with fill, 1 × 1 with contain, and 2 × 2 with contain and width only.
  • symbol-viewbox: with a port stub, the measured fit draws the square narrower than 2. With viewbox on the square it's exactly 2 wide and centred, with the port beyond it.
  • symbol-viewbox-preset: unset and "shape_and_port_bounds" give the same narrower square; "shape_bounds" gives exactly 2.

Checks (with props#944 built locally)

  • bun test: 1954 pass, 0 fail.
  • tsc --noEmit is clean.

Docs: tscircuit/docs#948.

🤖 Generated with Claude Code

With objectFit="contain" (CSS convention), a <symbol> with width / height is scaled by the largest single factor
that fits the given size, so the drawing keeps its proportions; with only width or only height given, that
dimension sets the factor. Unset or "fill" is unchanged: each axis scaled separately (tscircuit#4698).
<symbol> is typed as React's SVG symbol element, so objectFit joins SVGAttributes the way <switch> props do.
<symbol viewbox={{ x, y, width, height }}> (SVG viewBox convention, symbol coordinates in mm, y up, x / y the
minimum corner) names the region that is fitted into width / height. Without it the region stays the measured
bounds of the primitives and ports, which include the port stubs, so a drawing came out smaller than the size asked
for (tscircuit#4698). Typed on SVGAttributes with objectFit.
…ts ports

Without a viewbox, the fitted region is measured from the drawing and the ports, so port stubs count towards
width / height and the drawing comes out smaller than asked. viewboxPreset="shape_bounds" measures the drawing
alone; "shape_and_port_bounds" (and unset) keeps today's measurement (tscircuit#4698).
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
tscircuit-core-benchmarks Ready Ready Preview Oct 16, 2026 11:17pm UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 66712649 Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant