feat(surface): <Surface> primitive — paint / layer / hit-test / elevation / backdrop (A23)#90
Conversation
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
…attrs (A23) Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
…A23) Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
…dary, composition (A23) Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
… CLAUDE.md, AGENTS.md (A23) Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c95a30ab99
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| style={{ | ||
| ...rest, | ||
| position, | ||
| zIndex: resolvedZ, |
There was a problem hiding this comment.
Restore Box flex defaults in Surface
When Surface builds the underlying ink-box style here, it omits the defaults that Box always supplies (flexDirection='row', flexWrap='nowrap', flexGrow=0, flexShrink=1). A default Surface therefore falls back to the Yoga defaults (column and flexShrink=0), so any migrated Draggable/Resizable or new Surface with multiple children and no explicit flex props will stack children vertically and stop shrinking where the equivalent Box used to lay them out in a row. Add the same defaults before spreading the caller's style props to preserve Box parity.
Useful? React with 👍 / 👎.
| // `components/Surface/shadow.ts` for the cell math. | ||
| const elevation = node.attributes.surfaceElevation | ||
| if (typeof elevation === 'number' && elevation > 0 && node.style.position === 'absolute') { | ||
| paintShadowCells( |
There was a problem hiding this comment.
Clear shadow damage outside the surface rect
Painting elevation shadows writes cells outside the surface's own cached layout rect, but the dirty/moved clear path still only clears {x,y,width,height} for the node. When an elevated absolute Surface moves, shrinks, or lowers/removes elevation, the old shadow band outside the previous rect is never cleared and can remain on screen until some other node happens to repaint those cells. The shadow extent needs to be included in damage/clears or tracked separately.
Useful? React with 👍 / 👎.
…#90) Co-Authored-By: Mark <psyhik17@gmail.com> Co-Authored-By: Claude <noreply@anthropic.com>
Closes #57 (A23).
The foundational rectangle primitive every desktop UI element in yokai will be built from. Replaces ad-hoc
<Box>+position+zIndexpatterns that every higher-level primitive (<Window>, modal panels, popovers, tooltips, dropdowns, drag ghosts, backdrops) would otherwise rediscover with subtly incompatible numbers.Surface in one sentence
A
<Surface>is a paintable, layered (named z-band), optionally hit-test-bounded region with optional drop-shadow elevation and optional auto-rendered backdrop scrim. Not a window — no titlebar, no drag, no resize, no focus ownership; just the paint substrate.What's in this PR (12 commits, no v1/v2 split)
feat(surface): SurfaceProps types + resolveZIndex layer helper (A23)feat(surface): shadowCells pure cell math for elevation 0-5 (A23)feat(jsx): declare surfaceHitTestBoundary + surfaceElevation ink-box attrs (A23)feat(hit-test): honor surfaceHitTestBoundary attribute (A23)feat(render): paint Surface elevation shadow before ink-box content (A23)feat(warn): dev warnings for non-absolute Surface flags (A23)ifXWithoutAbsolutehelpers inwarn.tsfeat(surface): Surface React shell + tests + barrel (A23)feat(api): export Surface from package index (A23)refactor(Draggable): paint outer Box through Surface (A23)refactor(Resizable): paint outer Box through Surface clip='hidden' (A23)feat(examples): surface demo — layer stack, backdrop, elevation, boundary, composition (A23)pnpm demo:surfacedocs(surface): component reference, internals, cross-refs, changelog, CLAUDE.md, AGENTS.md (A23)Layer band table
'base''docked''overlay''dropdown''modal''popover''tooltip''drag-ghost'Gaps of 100 / 1000 leave room for numeric
zIndexnudges within a band.Design questions — committed answers
<ink-box>plus two new host attributes for the truly host-level concerns.hitTestBoundarydefault?false. Explicit opt-in for modals; tooltips / drag ghosts don't need it.layerabstraction? Yes — named enum + numeric escape hatch. ExplicitzIndexwins.clipdefault?'visible'(Box parity). Resizable's migration explicitly setsclip="hidden"to preserve its prior behavior.Defaults preserve Box parity
A default
<Surface>(nolayer, nozIndex, nohitTestBoundary/elevation/backdrop) renders anink-boxwith no surface-specific attributes and no extra siblings — byte-identical to a<Box>with the same props. Tested explicitly inSurface.test.tsx > default Surface (no layer, no extras) → ink-box with no zIndex and no surface attrs.Test plan
pnpm typecheckcleanpnpm lintcleanpnpm buildcleanpnpm test— 859 passed (was 770 onmainpre-PR; +89 across 4 new test files: layer, shadow, hit-test boundary, render elevation, Surface component)pnpm demo:drag,pnpm demo:constrained-drag,pnpm demo:drag-and-drop,pnpm demo:scratchpad,pnpm demo:resizable— Draggable/Resizable demos work unchanged after migrationpnpm demo:surface— visual showcase of every Surface featureOut of scope (explicit follow-ups)
<Window>primitive — composes Surface + Draggable + Resizable + title chrome)dimfield onShadowCellis captured for future use; v1 paints all shadow cells with a single reserved color)pendingClearsor parent-tracked dirty regions)