diff --git a/src/uix/eidos/components/box/box.css b/src/uix/eidos/components/box/box.css index 83d6b1466..2923cc3ae 100644 --- a/src/uix/eidos/components/box/box.css +++ b/src/uix/eidos/components/box/box.css @@ -80,4 +80,10 @@ order: var(--box-order, revert-layer); align-self: var(--box-align-self, revert-layer); justify-self: var(--box-justify-self, revert-layer); + place-self: var(--box-place-self, revert-layer); + + /* Grid item placement — Box used as a grid child declares its slot. */ + grid-column: var(--box-grid-column, revert-layer); + grid-row: var(--box-grid-row, revert-layer); + grid-area: var(--box-grid-area, revert-layer); } diff --git a/src/uix/eidos/components/box/box.svelte b/src/uix/eidos/components/box/box.svelte index a79e3a621..93b3d5f68 100644 --- a/src/uix/eidos/components/box/box.svelte +++ b/src/uix/eidos/components/box/box.svelte @@ -62,6 +62,10 @@ order, alignSelf, justifySelf, + placeSelf, + gridColumn, + gridRow, + gridArea, style, class: className, children, @@ -119,6 +123,12 @@ pushStyleVar(decls, '--box-order', formatLayoutRaw(eidos.resolve(order))); pushStyleVar(decls, '--box-align-self', eidos.resolve(alignSelf)); pushStyleVar(decls, '--box-justify-self', eidos.resolve(justifySelf)); + pushStyleVar(decls, '--box-place-self', eidos.resolve(placeSelf)); + + // Grid item placement (on the child, not the container). + pushStyleVar(decls, '--box-grid-column', formatLayoutRaw(eidos.resolve(gridColumn))); + pushStyleVar(decls, '--box-grid-row', formatLayoutRaw(eidos.resolve(gridRow))); + pushStyleVar(decls, '--box-grid-area', formatLayoutRaw(eidos.resolve(gridArea))); return composeStyle(decls, style); }); diff --git a/src/uix/eidos/components/box/types.ts b/src/uix/eidos/components/box/types.ts index b404a0307..332080c5b 100644 --- a/src/uix/eidos/components/box/types.ts +++ b/src/uix/eidos/components/box/types.ts @@ -78,6 +78,14 @@ export type BoxProps = Omit, 'style' | 'children'> & alignSelf?: ResponsiveProp; /** Grid item: `justify-self`. */ justifySelf?: ResponsiveProp; + /** Grid item: `place-self` shorthand (align-self + justify-self). */ + placeSelf?: ResponsiveProp; + /** Grid item: `grid-column`. */ + gridColumn?: ResponsiveProp; + /** Grid item: `grid-row`. */ + gridRow?: ResponsiveProp; + /** Grid item: `grid-area`. */ + gridArea?: ResponsiveProp; /** Extra inline style. Merged after the box's own CSS variable declarations. */ style?: string; /** Extra class names. */ diff --git a/src/uix/eidos/components/grid/grid.css b/src/uix/eidos/components/grid/grid.css index 99299e802..c3bfa2ce0 100644 --- a/src/uix/eidos/components/grid/grid.css +++ b/src/uix/eidos/components/grid/grid.css @@ -1,8 +1,11 @@ /* * Grid recipe — additional CSS variables layered on top of the Box - * recipe. Track and placement props (`place-items`, `place-content`, - * `grid-column`, `grid-row`, `grid-area`) fall through to `revert-layer` - * so they only apply when explicitly set. + * recipe. Container-only props: track templates, auto-flow, gap, and + * the container-side `place-items` / `place-content` shorthands. + * + * Item placement props (`grid-column`, `grid-row`, `grid-area`, + * `place-self`) live on the child `` — see `box.css`. A Box used + * as a Grid item declares its own slot. */ [data-box][data-grid] { @@ -15,9 +18,6 @@ justify-content: var(--grid-justify, start); place-items: var(--grid-place-items, revert-layer); place-content: var(--grid-place-content, revert-layer); - grid-column: var(--grid-column, revert-layer); - grid-row: var(--grid-row, revert-layer); - grid-area: var(--grid-area, revert-layer); row-gap: var(--grid-row-gap, var(--grid-gap, revert-layer)); column-gap: var(--grid-column-gap, var(--grid-gap, revert-layer)); } diff --git a/src/uix/eidos/components/grid/grid.svelte b/src/uix/eidos/components/grid/grid.svelte index 01c3399e5..8198e6113 100644 --- a/src/uix/eidos/components/grid/grid.svelte +++ b/src/uix/eidos/components/grid/grid.svelte @@ -26,9 +26,6 @@ justify, placeItems, placeContent, - gridColumn, - gridRow, - gridArea, style, class: className, children, @@ -48,9 +45,6 @@ pushStyleVar(decls, '--grid-justify', eidos.resolve(justify)); pushStyleVar(decls, '--grid-place-items', eidos.resolve(placeItems)); pushStyleVar(decls, '--grid-place-content', eidos.resolve(placeContent)); - pushStyleVar(decls, '--grid-column', eidos.resolve(gridColumn)); - pushStyleVar(decls, '--grid-row', eidos.resolve(gridRow)); - pushStyleVar(decls, '--grid-area', eidos.resolve(gridArea)); pushStyleVar(decls, '--grid-gap', formatLayoutSpace(eidos.resolve(gap))); pushStyleVar(decls, '--grid-column-gap', formatLayoutSpace(eidos.resolve(columnGap))); pushStyleVar(decls, '--grid-row-gap', formatLayoutSpace(eidos.resolve(rowGap))); diff --git a/src/uix/eidos/components/grid/types.ts b/src/uix/eidos/components/grid/types.ts index b1e4bad0f..39df95273 100644 --- a/src/uix/eidos/components/grid/types.ts +++ b/src/uix/eidos/components/grid/types.ts @@ -32,10 +32,8 @@ export type GridProps = Omit & { placeItems?: ResponsiveProp; /** `place-content` shorthand. */ placeContent?: ResponsiveProp; - /** Grid item: `grid-column`. */ - gridColumn?: ResponsiveProp; - /** Grid item: `grid-row`. */ - gridRow?: ResponsiveProp; - /** Grid item: `grid-area`. */ - gridArea?: ResponsiveProp; }; + +// Note: grid item placement props (`gridColumn`, `gridRow`, `gridArea`, +// `placeSelf`) belong on the child, not the container. They live on +// `` — a Box used as a grid item declares its own placement. diff --git a/src/uix/morfo/components/box.ts b/src/uix/morfo/components/box.ts new file mode 100644 index 000000000..a09389bdc --- /dev/null +++ b/src/uix/morfo/components/box.ts @@ -0,0 +1,32 @@ +import type { Morfo } from '../types'; + +/** + * Box — universal box-model utility (layout primitive). + * + * Eidos-native: the recipe is entirely CSS-variable driven. Every public + * prop maps to a `--box-{prop}` custom property and the consumer just + * sees a single `
` shell. No semantic events — Box is a + * pure visual / structural primitive, like the avatar / icon shells. + * + * Justification for 0-event surface: Box does not commit, emerge or + * react to anything. It is a passive container that styles its children + * via the cascade. Adding events would manufacture semantics the + * primitive doesn't carry. + */ +export const boxMorfo = { + name: 'Box', + kebab: 'box', + scope: ['eidos'], + parts: [ + { + name: 'Provider', + kebab: 'provider', + archetype: 'provider', + kind: 'public', + defaultElement: 'div', + optional: false, + data: [], + aria: [] + } + ] +} as const satisfies Morfo; diff --git a/web/routes/uix/components/box/+page.svelte b/web/routes/uix/components/box/+page.svelte index 2a2aa1aec..c69a376a5 100644 --- a/web/routes/uix/components/box/+page.svelte +++ b/web/routes/uix/components/box/+page.svelte @@ -1,5 +1,104 @@
@@ -7,127 +106,336 @@
Layout · Box

Box

- Universal box-model utility. Every prop maps to a CSS custom property; unset - props fall through to the normal cascade. + Universal box-model utility — size, padding/margin, position, overflow, plus flex/grid + item props (alignment, placement, order). Container-side flex/grid props (direction, + align, justify, wrap, templateColumns, …) live in + <Flex> and + <Grid> — UIX follows the Radix Themes split, not the + Chakra/MUI everything-on-Box model. Every prop maps to a --box-* CSS custom + property emitted inline; unset props fall through via revert-layer. + Eidos-native: no soma backing, no semantic events.

+
+ + parts{compiled.parts.order.length} + + + events0 + + + props40+ + + + scopeeidos + +
-
-

Live example

- - - Cell A + +
+
+ + Cell A + Cell B + Cell C - - Cell B - - - Cell C - - -
- -
-

Props

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropTypeNotes
displayLayoutDisplayblock · inline · flex · grid · …
width / minWidth / maxWidthnumber | stringnumber → px, string passes through
height / minHeight / maxHeightnumber | stringsame as above
padding / paddingX / paddingYnumber | stringnumber → var(--space-N)
- paddingTop / paddingRight / - paddingBottom / paddingLeft - number | stringper-side overrides
margin / marginX / marginY / sidesnumber | stringsame mapping as padding
gapnumber | stringfor flex/grid containers
- position / top / right / - bottom / left - variousposition + insets
inset / insetX / insetYnumber | stringshorthand insets
overflow / overflowX / overflowYLayoutOverflowvisible · hidden · clip · scroll · auto
- flex / grow / shrink / basis / - order / alignSelf / justifySelf - variousitem-side flex/grid props
stylestringextra inline style; merged after Box vars
-
- -
-

Reference

-
    -
  • - radix-themes Box — same idea: token-driven shorthand for the - box model; we mirror the responsive prop shape. -
  • -
  • - chakra-ui Box — origin of the per-side / shorthand prop split - (paddingX, paddingY, …). -
  • -
-
+
+
+ trace + {trace.length === 0 ? 'no semantic events' : `${trace.length} event(s)`} + · + display + {display} + + padding + {padding} · gap {gap} + +
+
+ +
+ + + + + + +
+ + {#if tab === 'live'} +
+

Controls

+

+ Box is eidos-native — no soma split. Each prop + maps to a --box-* custom property; numeric space values resolve to + var(--space-N), numeric length values to {`{N}px`}, strings pass + through. Unset props fall back to the cascade via revert-layer. +

+ +
+ eidos props · visual treatment +
+
+ + + + + + + +
+ + +
+
+ soma + n/a · box is eidos-native — equivalent markup shown + svelte +
+
{somaSnippet}
+
+ +
+
+ eidos + visual · every prop maps to a --box-* CSS variable + svelte +
+
{eidosSnippet}
+
+
+ {/if} + + {#if tab === 'api'} +
+

API reference

+

+ Numeric values for space props (padding*, margin*, + gap) resolve to var(--space-N). Numeric values for length props + (width, height, insets) resolve to {`{N}px`}. String + values pass through unchanged. revert-layer is the fallback for any unset prop. +

+ +
Sizing
+
+ + + + + + +
PropTypeNotes
width / minWidth / maxWidthnumber | stringNumber → px, string passes through.
height / minHeight / maxHeightnumber | stringSame as width.
+
+ +
Spacing
+
+ + + + + + + + + +
PropTypeNotes
padding / paddingX / paddingYnumber | stringNumber → var(--space-N).
paddingTop / paddingRight / paddingBottom / paddingLeftnumber | stringPer-side overrides; cascade paddingX/Y → padding.
margin / marginX / marginYnumber | stringSame as padding mapping.
marginTop / marginRight / marginBottom / marginLeftnumber | stringPer-side overrides.
gapnumber | stringUseful with display flex/grid.
+
+ +
Display + position
+
+ + + + + + + + + +
PropTypeNotes
displayblock | inline | inline-block | flex | inline-flex | grid | inline-grid | contents | none—
positionstatic | relative | absolute | fixed | sticky—
top / right / bottom / leftnumber | stringNumber → px.
inset / insetX / insetYnumber | stringShorthand insets.
overflow / overflowX / overflowYvisible | hidden | clip | scroll | auto—
+
+ +
Flex / grid item
+
+ + + + + + + + + + + + +
PropTypeNotes
flex / grow / shrinknumber | stringItem-side flex props.
basisnumber | stringNumber → px.
ordernumber—
alignSelfauto | start | end | center | stretch | baseline—
justifySelfauto | start | end | center | stretch—
placeSelfstringShorthand for align-self + justify-self.
gridColumn / gridRowstringGrid item placement. e.g. "1 / 3", "span 2".
gridAreastringNamed grid area, e.g. "header".
+
+ +
Escape hatch
+
+ + + + + +
PropTypeNotes
stylestringExtra inline CSS appended after Box's variable declarations. Use for one-offs the prop surface doesn't cover.
+
+ +
Reference comparison
+
+ + + + + + + +
LibraryClosest equivalentDifference
radix-themes<Box>Radix exposes spacing props as p/px shorthands; UIX uses full names. Both compile to CSS vars.
chakra-ui<Box>Chakra mixes the style-props system with theme aliases (bg, color); UIX Box is layout-only — color / typography live elsewhere.
mantine<Box>Mantine compiles styles to classnames; UIX writes inline CSS variables for transparent debugging.
+
+
+ {/if} + + {#if tab === 'morfo'} +
+

Morfo contract

+ +
+ + + + + + + + + +
FieldValue
name{boxMorfo.name}
kebab{boxMorfo.kebab}
scope{boxMorfo.scope.join(', ')}
parts{partsList.length}
events0
+
+ +
Parts
+
+ + + + {#each partsList as part} + + + + + + + + {/each} + +
kebabmarkerelementarchetypeoptional
{part.kebab}[{part.marker}]<{part.defaultElement}>{part.archetype}{part.optional ? 'yes' : 'no'}
+
+ +

+ The Provider part emits only the data-box marker — no states, no data + properties, no aria attributes, no keyboard. The recipe consumes the + --box-* CSS variables the component writes inline. +

+
+ {/if} + + {#if tab === 'sema'} +
+

+ sema · events +

+

+ Box declares no semantic events. As a passive layout primitive, it does not commit, emerge, + or react to anything — it just styles its children. Components that animate or change state + on appearance should compose Box with an interactive primitive (popover, drawer, + collapsible) that owns the relevant sema verbs. +

+
+ {/if} + + {#if tab === 'recipe'} +
+

Eidos recipe

+

+ Recipe lives in src/uix/eidos/components/box/box.css. Every property reads + its own --box-* custom property and falls back via + revert-layer so consumers can mix Box with any other styling. +

+
+ + + + + + + + + + + + + + +
SelectorOwnerPurpose
[data-box]morfoProvider marker. Single root element emitted by the component.
[data-box] {`{ display: var(--box-display, revert-layer); … }`}eidosRead every --box-* custom property, fall back to the cascade when unset.
+
+
+ {/if} + + {#if tab === 'a11y'} +
+

Accessibility

+
+ + + + + + + + +
ConcernContract
RoleNone implicit. Default element is <div> — semantically neutral. Use a semantic wrapper (<main>, <section>, <article>) around Box when content needs a landmark.
LabelNot applicable — Box has no content semantics. Labels belong to the interactive child the Box wraps.
KeyboardBox is not focusable. Tab order follows children.
Focus visibleBox does not paint a focus ring. Focus styling belongs to the interactive children.
+
+
+ {/if}