@kerfjs/ui 5.0.0-beta.36 → 5.0.0-beta.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,7 +126,7 @@ Do not concatenate a count into `label` or pass a numeric `badge`; the mutually
126
126
  exclusive legacy `badge` slot remains available for non-count `SafeHtml` such
127
127
  as a `New` marker.
128
128
 
129
- `Row.gap` and `List.gap` accept a direct spacing name such as
129
+ `Row.gap`, `List.gap`, and `Spacer` dimensions accept a direct spacing name such as
130
130
  `gap="xs"` or `gap="m"`, or a branded `CssLength`. Import `space`, `rem`, `em`,
131
131
  `px`, `pct`, `lengthVar`, `plus`, and `calc` from the CSS-free
132
132
  `@kerfjs/ui/css-values` subpath when a named step is not enough. `plus` returns
@@ -165,6 +165,7 @@ interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
165
165
  | `ToolbarText` | `@kerfjs/ui/toolbar-text` | `@kerfjs/ui/toolbar-text.css` |
166
166
  | `List` | `@kerfjs/ui/list` | `@kerfjs/ui/list.css` |
167
167
  | `Row` | `@kerfjs/ui/row` | `@kerfjs/ui/row.css` |
168
+ | `Spacer` | `@kerfjs/ui/spacer` | `@kerfjs/ui/spacer.css` |
168
169
  | Typed CSS dimension builders | `@kerfjs/ui/css-values` | — |
169
170
  | `ListActionRow` | `@kerfjs/ui/list-action-row` | `@kerfjs/ui/list-action-row.css` |
170
171
  | `ListItem` | `@kerfjs/ui/list-item` | `@kerfjs/ui/list-item.css` |
@@ -1486,6 +1486,92 @@
1486
1486
  "composition": "docs/layout.md#row-and-list-alignment"
1487
1487
  }
1488
1488
  },
1489
+ {
1490
+ "key": "@kerfjs/ui:spacer",
1491
+ "package": "@kerfjs/ui",
1492
+ "id": "spacer",
1493
+ "name": "Spacer",
1494
+ "kind": "component",
1495
+ "source": "kerf",
1496
+ "parents": { "mode": "any", "entries": [] },
1497
+ "contexts": ["application-ui"],
1498
+ "zones": [],
1499
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
1500
+ "state": [
1501
+ {
1502
+ "id": "whether the empty space carries structural meaning",
1503
+ "owner": "application",
1504
+ "required": false
1505
+ },
1506
+ {
1507
+ "id": "the named or typed dimensions",
1508
+ "owner": "application",
1509
+ "required": false
1510
+ },
1511
+ {
1512
+ "id": "whether the spacer fills the flex main axis",
1513
+ "owner": "application",
1514
+ "required": false
1515
+ }
1516
+ ],
1517
+ "wiring": { "required": false, "helpers": [], "obligations": [] },
1518
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
1519
+ "layout": {
1520
+ "roles": ["structure"],
1521
+ "geometry": {
1522
+ "margin": "none",
1523
+ "border": "none",
1524
+ "padding": "none",
1525
+ "notes": [
1526
+ "A fixed Spacer does not shrink. flex changes its flex shorthand from 0 0 auto to 1 1 auto; width and height remain physical dimensions."
1527
+ ]
1528
+ }
1529
+ },
1530
+ "accessibility": {
1531
+ "obligations": [
1532
+ "Spacer is always decorative and hidden from the accessibility tree.",
1533
+ "Spacer must not contain content or become an interaction target."
1534
+ ]
1535
+ },
1536
+ "cssValueProps": [
1537
+ {
1538
+ "path": "width",
1539
+ "grammar": "length",
1540
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1541
+ "nonStandaloneHelpers": ["plus"],
1542
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1543
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1544
+ "exceptionalShorthands": ["s", "xl"],
1545
+ "rawPolicy": "forbid",
1546
+ "examples": [
1547
+ "width=\"m\"",
1548
+ "width={space(\"m\")}",
1549
+ "width={calc(plus(rem(0.25), pct(10)))}"
1550
+ ]
1551
+ },
1552
+ {
1553
+ "path": "height",
1554
+ "grammar": "length",
1555
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1556
+ "nonStandaloneHelpers": ["plus"],
1557
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1558
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1559
+ "exceptionalShorthands": ["s", "xl"],
1560
+ "rawPolicy": "forbid",
1561
+ "examples": ["height=\"xs\"", "height={rem(1)}"]
1562
+ }
1563
+ ],
1564
+ "boundaries": {
1565
+ "rootClass": "kui-spacer",
1566
+ "publicClasses": ["kui-spacer"],
1567
+ "publicTokens": []
1568
+ },
1569
+ "diagnostics": [],
1570
+ "provenance": {
1571
+ "selection": "ai/component-catalog.json",
1572
+ "composition": "generated-permissive-default"
1573
+ }
1574
+ },
1489
1575
  {
1490
1576
  "key": "@kerfjs/ui:list",
1491
1577
  "package": "@kerfjs/ui",
@@ -1733,6 +1733,98 @@
1733
1733
  "recipe": "docs/layout.md#row-and-list-alignment"
1734
1734
  }
1735
1735
  },
1736
+ {
1737
+ "id": "spacer",
1738
+ "name": "Spacer",
1739
+ "category": "Structure",
1740
+ "kind": "component",
1741
+ "source": "kerf",
1742
+ "description": "A decorative, non-shrinking fixed gap or flexible main-axis spacer with typed physical width and height.",
1743
+ "uses": [],
1744
+ "purpose": "Express an intentional empty dimension or consume remaining flex space without application-owned spacer markup or raw CSS lengths.",
1745
+ "publicExports": ["Spacer"],
1746
+ "cssValueProps": [
1747
+ {
1748
+ "path": "width",
1749
+ "grammar": "length",
1750
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1751
+ "nonStandaloneHelpers": ["plus"],
1752
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1753
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1754
+ "exceptionalShorthands": ["s", "xl"],
1755
+ "rawPolicy": "forbid",
1756
+ "examples": ["width=\"m\"", "width={space(\"m\")}", "width={calc(plus(rem(0.25), pct(10)))}"]
1757
+ },
1758
+ {
1759
+ "path": "height",
1760
+ "grammar": "length",
1761
+ "helpers": ["px", "rem", "em", "pct", "space", "lengthVar", "calc"],
1762
+ "nonStandaloneHelpers": ["plus"],
1763
+ "shorthands": ["none", "2xs", "xs", "s", "m", "l", "xl"],
1764
+ "canonicalShorthands": ["none", "2xs", "xs", "m", "l"],
1765
+ "exceptionalShorthands": ["s", "xl"],
1766
+ "rawPolicy": "forbid",
1767
+ "examples": ["height=\"xs\"", "height={rem(1)}"]
1768
+ }
1769
+ ],
1770
+ "useWhen": [
1771
+ "Reserve a fixed empty width or height from the UI spacing scale inside a layout.",
1772
+ "Push siblings to opposite ends of a Row or other flex container by passing flex.",
1773
+ "Use a typed CssLength when the finite spacing scale cannot express the required dimension."
1774
+ ],
1775
+ "avoidWhen": [
1776
+ "Do not use Spacer for ordinary rhythm between siblings; prefer Row.gap or List.gap so the parent owns repeated spacing.",
1777
+ "Do not use Spacer to compensate for component-owned padding, margin, alignment, or responsive layout defects.",
1778
+ "Do not pass raw CSS strings to width or height."
1779
+ ],
1780
+ "alternatives": [
1781
+ {
1782
+ "id": "row",
1783
+ "relationship": "parent-spacing-alternative",
1784
+ "when": "Use Row.gap when every horizontal sibling needs the same repeated separation."
1785
+ },
1786
+ {
1787
+ "id": "list",
1788
+ "relationship": "parent-spacing-alternative",
1789
+ "when": "Use List.gap when every vertical sibling needs the same repeated separation."
1790
+ }
1791
+ ],
1792
+ "appOwns": [
1793
+ "whether the empty space carries structural meaning",
1794
+ "the named or typed dimensions",
1795
+ "whether the spacer fills the flex main axis"
1796
+ ],
1797
+ "variants": [
1798
+ "fixed named width and/or height",
1799
+ "fixed typed CssLength width and/or height",
1800
+ "flexible main-axis space"
1801
+ ],
1802
+ "accessibility": [
1803
+ "Spacer is always decorative and hidden from the accessibility tree.",
1804
+ "Spacer must not contain content or become an interaction target."
1805
+ ],
1806
+ "geometry": {
1807
+ "margin": "none",
1808
+ "border": "none",
1809
+ "padding": "none",
1810
+ "notes": [
1811
+ "A fixed Spacer does not shrink. flex changes its flex shorthand from 0 0 auto to 1 1 auto; width and height remain physical dimensions."
1812
+ ]
1813
+ },
1814
+ "delivery": {
1815
+ "browserImport": "@kerfjs/ui/spacer",
1816
+ "manualCssImport": "@kerfjs/ui/spacer.css",
1817
+ "sideEffects": ["browser-condition-css"]
1818
+ },
1819
+ "wiring": [],
1820
+ "publicClasses": ["kui-spacer"],
1821
+ "publicTokens": [],
1822
+ "links": {
1823
+ "catalogRoute": "?component=spacer",
1824
+ "documentation": "docs/layout.md",
1825
+ "recipe": "docs/layout.md#spacer"
1826
+ }
1827
+ },
1736
1828
  {
1737
1829
  "id": "list",
1738
1830
  "name": "List",
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.36` and `kerfjs@5.0.0-beta.36`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.37` and `kerfjs@5.0.0-beta.37`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
@@ -2036,3 +2036,24 @@ declare function Row({ children, hAlign, vAlign, gap, flex, wrap, textInsets, co
2036
2036
 
2037
2037
  export { CssFlex, CssFlexKeyword, CssLength, HorizontalAlignment, Row, type RowProps, Sides, UiSpaceName, VerticalAlignment };
2038
2038
  ```
2039
+
2040
+ ## `@kerfjs/ui/spacer`
2041
+
2042
+ ```ts
2043
+ import * as kerfjs from 'kerfjs';
2044
+ import { UiSpaceName, CssLength } from './css-values.js';
2045
+
2046
+ interface SpacerProps {
2047
+ /** Grow and shrink to fill the available space along a parent's flex axis. */
2048
+ flex?: boolean;
2049
+ /** A named UI spacing token or typed physical width. */
2050
+ width?: UiSpaceName | CssLength;
2051
+ /** A named UI spacing token or typed physical height. */
2052
+ height?: UiSpaceName | CssLength;
2053
+ className?: string;
2054
+ }
2055
+ /** A decorative fixed-size or flexible gap for Row, List, and other flex layouts. */
2056
+ declare function Spacer({ flex, width, height, className, }: SpacerProps): kerfjs.SafeHtml;
2057
+
2058
+ export { CssLength, Spacer, type SpacerProps, UiSpaceName };
2059
+ ```
package/ai/skill.md CHANGED
@@ -134,7 +134,7 @@ Hard rules:
134
134
  18. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, toolbar title/supporting-copy ids, shared 8px field/action gutters, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card or double-inset intrinsically bordered controls. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
135
135
  19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset.
136
136
  20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling.
137
- 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap` or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; when one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Skeleton geometry, `flex()` for List flex, and `uiColor()`/`colorVar()` for choice icons. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale".
137
+ 21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap` or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for List flex, and `uiColor()`/`colorVar()` for choice icons. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale".
138
138
  22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`. Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each ships a companion CSS import and stays out of the barrel. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). See `docs/app-layouts.md` and `docs/collapsible-panel.md`.
139
139
 
140
140
  Common mistakes:
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.36` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.37` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';
@@ -0,0 +1,3 @@
1
+ import '../styles/foundation.css';
2
+ import '../styles/spacer.css';
3
+ export * from '../spacer.js';
package/dist/catalog.js CHANGED
@@ -3,7 +3,7 @@ import { Toolbar } from './chunk-VXFGMDA7.js';
3
3
  import { Pane } from './chunk-I4V3UDSA.js';
4
4
  import { ListHeader } from './chunk-5FJ53EOF.js';
5
5
  import { ListItem } from './chunk-I45QO2IZ.js';
6
- import { List } from './chunk-TNXDXSGP.js';
6
+ import { List } from './chunk-C34NZOLJ.js';
7
7
 
8
8
  import { filterDataAttributes } from './chunk-SRSJO5QE.js';
9
9
 
@@ -40,8 +40,8 @@ function List({
40
40
  "data-v-align": verticalAlignment(vAlign),
41
41
  "data-scrollable": String(scrollable),
42
42
  "divider-sides": dividerSides || void 0,
43
- "text-insets": textInsets || void 0,
44
- "control-insets": controlInsets || void 0,
43
+ "data-text-insets": textInsets || void 0,
44
+ "data-control-insets": controlInsets || void 0,
45
45
  style: style || void 0,
46
46
  children
47
47
  }
@@ -50,4 +50,4 @@ function List({
50
50
 
51
51
  export { List };
52
52
 
53
- //# sourceMappingURL=chunk-TNXDXSGP.js.map
53
+ //# sourceMappingURL=chunk-C34NZOLJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/list.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAwBO,SAAS,IAAA,CAAK;AAAA,EACnB,QAAA;AAAA,EACA,GAAA,GAAM,KAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,KAAA;AAAA,EACT,UAAA,GAAa,KAAA;AAAA,EACb,YAAA,GAAe,EAAA;AAAA,EACf,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY;AACd,CAAA,EAAc;AACZ,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,IAAA,GACJ,qBAAA,GACA,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACpC,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA,IAAO,MAAA;AACf,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,QAAA,GAAW,CAAA,gBAAA,EAAmB,QAAQ,CAAA,CAAA,GAAK,EAAA;AAAA,IAC3C,SAAA,GAAY,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,GAAK;AAAA,GAChD,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACpC,gBAAA,EAAe,MAAA;AAAA,MACf,UAAA,EAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAC,CAAA;AAAA,MAC7B,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,iBAAA,EAAiB,OAAO,UAAU,CAAA;AAAA,MAClC,iBAAe,YAAA,IAAgB,MAAA;AAAA,MAC/B,oBAAkB,UAAA,IAAc,MAAA;AAAA,MAChC,uBAAqB,aAAA,IAAiB,MAAA;AAAA,MACtC,OAAO,KAAA,IAAS,MAAA;AAAA,MAEf;AAAA;AAAA,GACH;AAEJ","file":"chunk-C34NZOLJ.js","sourcesContent":["import {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from './css-values.js';\nimport type { DividerSides, Sides } from './divider-sides.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type VerticalAlignment,\n verticalAlignment,\n} from './flex-alignment.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface ListProps {\n children?: KerfUiContent;\n /** Use the standard item gap, a named UI spacing token, or a typed CSS length. Defaults to no gap. */\n gap?: boolean | UiSpaceName | CssLength;\n /** Allow this list to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /** Horizontal alignment. Defaults to full to preserve stretch-aligned list children. */\n hAlign?: HorizontalAlignment;\n /** Vertical distribution. Defaults to top. */\n vAlign?: VerticalAlignment;\n /** Own vertical scrolling and overscroll containment. */\n scrollable?: boolean;\n /** Physical divider edges in canonical top/right/bottom/left order. */\n dividerSides?: DividerSides;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n}\n\n/** A stretch-aligned vertical stack with optional gap, flex, scroll, and dividers. */\nexport function List({\n children,\n gap = false,\n flex = false,\n hAlign = 'full',\n vAlign = 'top',\n scrollable = false,\n dividerSides = '',\n textInsets = '',\n controlInsets = '',\n className = '',\n}: ListProps) {\n const gapValue =\n gap === true\n ? 'var(--kui-list-gap)'\n : spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap || undefined;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n gapValue ? `--_kui-list-gap:${gapValue}` : '',\n flexValue ? `--_kui-list-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n class={`kui-list ${className}`.trim()}\n data-component=\"list\"\n data-gap={String(Boolean(gap))}\n data-flex={String(Boolean(flex))}\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-scrollable={String(scrollable)}\n divider-sides={dividerSides || undefined}\n data-text-insets={textInsets || undefined}\n data-control-insets={controlInsets || undefined}\n style={style || undefined}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from './css-values.js';\nexport type { DividerSides, Sides } from './divider-sides.js';\nexport type {\n HorizontalAlignment,\n VerticalAlignment,\n} from './flex-alignment.js';\n"]}
@@ -0,0 +1,42 @@
1
+ import { space } from './chunk-OL4TC3U2.js';
2
+ import { jsx } from 'kerfjs/jsx-runtime';
3
+
4
+ var spaceNames = [
5
+ "none",
6
+ "2xs",
7
+ "xs",
8
+ "s",
9
+ "m",
10
+ "l",
11
+ "xl"
12
+ ];
13
+ function dimension(value) {
14
+ return spaceNames.includes(value) ? space(value) : value;
15
+ }
16
+ function Spacer({
17
+ flex = false,
18
+ width,
19
+ height,
20
+ className = ""
21
+ }) {
22
+ const widthValue = dimension(width);
23
+ const heightValue = dimension(height);
24
+ const style = [
25
+ widthValue ? `width:${widthValue}` : "",
26
+ heightValue ? `height:${heightValue}` : ""
27
+ ].filter(Boolean).join(";");
28
+ return /* @__PURE__ */ jsx(
29
+ "div",
30
+ {
31
+ class: `kui-spacer ${className}`.trim(),
32
+ "data-component": "spacer",
33
+ "data-flex": String(flex),
34
+ "aria-hidden": "true",
35
+ style: style || void 0
36
+ }
37
+ );
38
+ }
39
+
40
+ export { Spacer };
41
+
42
+ //# sourceMappingURL=chunk-OGTJNHRG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spacer.tsx"],"names":[],"mappings":";;;AAEA,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAYA,SAAS,UAAU,KAAA,EAAwD;AACzE,EAAA,OAAO,WAAW,QAAA,CAAS,KAAoB,CAAA,GAC3C,KAAA,CAAM,KAAoB,CAAA,GACzB,KAAA;AACP;AAGO,SAAS,MAAA,CAAO;AAAA,EACrB,IAAA,GAAO,KAAA;AAAA,EACP,KAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA,GAAY;AACd,CAAA,EAAgB;AACd,EAAA,MAAM,UAAA,GAAa,UAAU,KAAK,CAAA;AAClC,EAAA,MAAM,WAAA,GAAc,UAAU,MAAM,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,UAAA,GAAa,CAAA,MAAA,EAAS,UAAU,CAAA,CAAA,GAAK,EAAA;AAAA,IACrC,WAAA,GAAc,CAAA,OAAA,EAAU,WAAW,CAAA,CAAA,GAAK;AAAA,GAC1C,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,WAAA,EAAc,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACtC,gBAAA,EAAe,QAAA;AAAA,MACf,WAAA,EAAW,OAAO,IAAI,CAAA;AAAA,MACtB,aAAA,EAAY,MAAA;AAAA,MACZ,OAAO,KAAA,IAAS;AAAA;AAAA,GACjB;AAEL","file":"chunk-OGTJNHRG.js","sourcesContent":["import { type CssLength, space, type UiSpaceName } from './css-values.js';\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface SpacerProps {\n /** Grow and shrink to fill the available space along a parent's flex axis. */\n flex?: boolean;\n /** A named UI spacing token or typed physical width. */\n width?: UiSpaceName | CssLength;\n /** A named UI spacing token or typed physical height. */\n height?: UiSpaceName | CssLength;\n className?: string;\n}\n\nfunction dimension(value?: UiSpaceName | CssLength): CssLength | undefined {\n return spaceNames.includes(value as UiSpaceName)\n ? space(value as UiSpaceName)\n : (value as CssLength | undefined);\n}\n\n/** A decorative fixed-size or flexible gap for Row, List, and other flex layouts. */\nexport function Spacer({\n flex = false,\n width,\n height,\n className = '',\n}: SpacerProps) {\n const widthValue = dimension(width);\n const heightValue = dimension(height);\n const style = [\n widthValue ? `width:${widthValue}` : '',\n heightValue ? `height:${heightValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n class={`kui-spacer ${className}`.trim()}\n data-component=\"spacer\"\n data-flex={String(flex)}\n aria-hidden=\"true\"\n style={style || undefined}\n ></div>\n );\n}\n\nexport type { CssLength, UiSpaceName } from './css-values.js';\n"]}
@@ -37,8 +37,8 @@ function Row({
37
37
  "data-v-align": verticalAlignment(vAlign),
38
38
  "data-flex": String(Boolean(flex)),
39
39
  "data-wrap": String(wrap),
40
- "text-insets": textInsets || void 0,
41
- "control-insets": controlInsets || void 0,
40
+ "data-text-insets": textInsets || void 0,
41
+ "data-control-insets": controlInsets || void 0,
42
42
  style,
43
43
  children
44
44
  }
@@ -47,4 +47,4 @@ function Row({
47
47
 
48
48
  export { Row };
49
49
 
50
- //# sourceMappingURL=chunk-AVZ2IMWG.js.map
50
+ //# sourceMappingURL=chunk-X5GRDQ7V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/row.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAsBO,SAAS,GAAA,CAAI;AAAA,EAClB,QAAA;AAAA,EACA,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,MAAA;AAAA,EACT,GAAA,GAAM,IAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY;AACd,CAAA,EAAa;AACX,EAAA,MAAM,WAAW,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACnD,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA;AACJ,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,kBAAkB,QAAQ,CAAA,CAAA;AAAA,IAC1B,SAAA,GAAY,CAAA,gBAAA,EAAmB,SAAS,CAAA,CAAA,GAAK;AAAA,GAC/C,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,QAAA,EAAW,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACnC,gBAAA,EAAe,KAAA;AAAA,MACf,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,WAAA,EAAW,OAAO,IAAI,CAAA;AAAA,MACtB,oBAAkB,UAAA,IAAc,MAAA;AAAA,MAChC,uBAAqB,aAAA,IAAiB,MAAA;AAAA,MACtC,KAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-X5GRDQ7V.js","sourcesContent":["import {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from './css-values.js';\nimport type { Sides } from './divider-sides.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type VerticalAlignment,\n verticalAlignment,\n} from './flex-alignment.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface RowProps {\n children?: KerfUiContent;\n /** Horizontal distribution. Defaults to left. */\n hAlign?: HorizontalAlignment;\n /** Vertical alignment and wrapped-line distribution. Defaults to full. */\n vAlign?: VerticalAlignment;\n /** A named UI spacing token or typed CSS length. Defaults to xs. */\n gap?: UiSpaceName | CssLength;\n /** Allow this row to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /** Allow children to wrap onto additional lines. */\n wrap?: boolean;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n}\n\n/** A horizontal flex row with explicit physical-axis alignment and spacing. */\nexport function Row({\n children,\n hAlign = 'left',\n vAlign = 'full',\n gap = 'xs',\n flex = false,\n wrap = false,\n textInsets = '',\n controlInsets = '',\n className = '',\n}: RowProps) {\n const gapValue = spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n `--_kui-row-gap:${gapValue}`,\n flexValue ? `--_kui-row-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n class={`kui-row ${className}`.trim()}\n data-component=\"row\"\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-flex={String(Boolean(flex))}\n data-wrap={String(wrap)}\n data-text-insets={textInsets || undefined}\n data-control-insets={controlInsets || undefined}\n style={style}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from './css-values.js';\nexport type { Sides } from './divider-sides.js';\nexport type {\n HorizontalAlignment,\n VerticalAlignment,\n} from './flex-alignment.js';\n"]}
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { SegmentedControl, SegmentedControlAppearance, SegmentedControlChoice, S
18
18
  export { Select, SelectChoice, SelectFocusRingOwner, SelectPresentation, SelectProps, SelectSelectedPresentation, SelectSize } from './select.js';
19
19
  export { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
20
20
  export { Skeleton, SkeletonProps } from './skeleton.js';
21
+ export { Spacer, SpacerProps } from './spacer.js';
21
22
  export { StateBanner, StateBannerProps, StateBannerTone, StateBannerUrgency } from './state-banner.js';
22
23
  export { SunkenPanel, SunkenPanelProps, SunkenPanelShape } from './sunken-panel.js';
23
24
  export { DialogSurface, DialogSurfacePresentation, DialogSurfaceProps, DialogSurfaceSize, PopupSurface, PopupSurfaceInset, PopupSurfaceProps, SurfaceInset } from './surface-scaffold.js';
package/dist/index.js CHANGED
@@ -4,13 +4,14 @@ export { wireTokenSearchFields } from './chunk-VDUJZF5G.js';
4
4
  export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-TIGSG6BX.js';
5
5
  export { ValueTable, ValueTableRow } from './chunk-V6HCTCVD.js';
6
6
  export { Select } from './chunk-CK6P5AUK.js';
7
+ export { Spacer } from './chunk-OGTJNHRG.js';
7
8
  export { StateBanner } from './chunk-UDHHKZWO.js';
8
9
  export { SunkenPanel } from './chunk-SOK5EUF7.js';
9
10
  export { DialogSurface, PopupSurface } from './chunk-WSXUPSID.js';
10
11
  export { TabBar } from './chunk-ZPWZIVI6.js';
11
12
  export { ToolbarText } from './chunk-V6GQKOX2.js';
12
13
  export { ResizableRegion, clampRegionSize, resizeRegionFromPointer } from './chunk-CAB6NQ2T.js';
13
- export { Row } from './chunk-AVZ2IMWG.js';
14
+ export { Row } from './chunk-X5GRDQ7V.js';
14
15
  export { SegmentedControl } from './chunk-E34TE2ZI.js';
15
16
  export { FloatingToolbar } from './chunk-DYBPI5O2.js';
16
17
  export { ListActionRow } from './chunk-DAO2VFTT.js';
@@ -22,7 +23,7 @@ export { Toolbar } from './chunk-VXFGMDA7.js';
22
23
  export { Pane } from './chunk-I4V3UDSA.js';
23
24
  export { ListHeader } from './chunk-5FJ53EOF.js';
24
25
  export { ListItem } from './chunk-I45QO2IZ.js';
25
- export { List } from './chunk-TNXDXSGP.js';
26
+ export { List } from './chunk-C34NZOLJ.js';
26
27
 
27
28
 
28
29
  export { Skeleton } from './chunk-T7DOWS6T.js';
package/dist/list.js CHANGED
@@ -1,4 +1,4 @@
1
- export { List } from './chunk-TNXDXSGP.js';
1
+ export { List } from './chunk-C34NZOLJ.js';
2
2
 
3
3
 
4
4
 
package/dist/row.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Row } from './chunk-AVZ2IMWG.js';
1
+ export { Row } from './chunk-X5GRDQ7V.js';
2
2
 
3
3
 
4
4
 
@@ -0,0 +1,16 @@
1
+ import * as kerfjs from 'kerfjs';
2
+ import { UiSpaceName, CssLength } from './css-values.js';
3
+
4
+ interface SpacerProps {
5
+ /** Grow and shrink to fill the available space along a parent's flex axis. */
6
+ flex?: boolean;
7
+ /** A named UI spacing token or typed physical width. */
8
+ width?: UiSpaceName | CssLength;
9
+ /** A named UI spacing token or typed physical height. */
10
+ height?: UiSpaceName | CssLength;
11
+ className?: string;
12
+ }
13
+ /** A decorative fixed-size or flexible gap for Row, List, and other flex layouts. */
14
+ declare function Spacer({ flex, width, height, className, }: SpacerProps): kerfjs.SafeHtml;
15
+
16
+ export { CssLength, Spacer, type SpacerProps, UiSpaceName };
package/dist/spacer.js ADDED
@@ -0,0 +1,4 @@
1
+ export { Spacer } from './chunk-OGTJNHRG.js';
2
+
3
+
4
+ //# sourceMappingURL=spacer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"spacer.js"}
@@ -25,44 +25,44 @@
25
25
  inset 1px 0 var(--_kui-list-divider-left);
26
26
  }
27
27
 
28
- .kui-list[control-insets*="t"] {
28
+ .kui-list[data-control-insets*="t"] {
29
29
  --_kui-list-inset-top: var(--kui-layout-inline-margin, 0.5rem);
30
30
  }
31
31
 
32
- .kui-list[control-insets*="r"] {
32
+ .kui-list[data-control-insets*="r"] {
33
33
  --_kui-list-inset-right: var(--kui-layout-inline-margin, 0.5rem);
34
34
  }
35
35
 
36
- .kui-list[control-insets*="b"] {
36
+ .kui-list[data-control-insets*="b"] {
37
37
  --_kui-list-inset-bottom: var(--kui-layout-inline-margin, 0.5rem);
38
38
  }
39
39
 
40
- .kui-list[control-insets*="l"] {
40
+ .kui-list[data-control-insets*="l"] {
41
41
  --_kui-list-inset-left: var(--kui-layout-inline-margin, 0.5rem);
42
42
  }
43
43
 
44
- .kui-list[text-insets*="t"] {
44
+ .kui-list[data-text-insets*="t"] {
45
45
  --_kui-list-inset-top: calc(
46
46
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
47
47
  var(--kui-layout-item-padding, 0.5rem)
48
48
  );
49
49
  }
50
50
 
51
- .kui-list[text-insets*="r"] {
51
+ .kui-list[data-text-insets*="r"] {
52
52
  --_kui-list-inset-right: calc(
53
53
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
54
54
  var(--kui-layout-item-padding, 0.5rem)
55
55
  );
56
56
  }
57
57
 
58
- .kui-list[text-insets*="b"] {
58
+ .kui-list[data-text-insets*="b"] {
59
59
  --_kui-list-inset-bottom: calc(
60
60
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
61
61
  var(--kui-layout-item-padding, 0.5rem)
62
62
  );
63
63
  }
64
64
 
65
- .kui-list[text-insets*="l"] {
65
+ .kui-list[data-text-insets*="l"] {
66
66
  --_kui-list-inset-left: calc(
67
67
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
68
68
  var(--kui-layout-item-padding, 0.5rem)
@@ -16,44 +16,44 @@
16
16
  gap: var(--_kui-row-gap, var(--kui-row-gap));
17
17
  }
18
18
 
19
- .kui-row[control-insets*="t"] {
19
+ .kui-row[data-control-insets*="t"] {
20
20
  --_kui-row-inset-top: var(--kui-layout-inline-margin, 0.5rem);
21
21
  }
22
22
 
23
- .kui-row[control-insets*="r"] {
23
+ .kui-row[data-control-insets*="r"] {
24
24
  --_kui-row-inset-right: var(--kui-layout-inline-margin, 0.5rem);
25
25
  }
26
26
 
27
- .kui-row[control-insets*="b"] {
27
+ .kui-row[data-control-insets*="b"] {
28
28
  --_kui-row-inset-bottom: var(--kui-layout-inline-margin, 0.5rem);
29
29
  }
30
30
 
31
- .kui-row[control-insets*="l"] {
31
+ .kui-row[data-control-insets*="l"] {
32
32
  --_kui-row-inset-left: var(--kui-layout-inline-margin, 0.5rem);
33
33
  }
34
34
 
35
- .kui-row[text-insets*="t"] {
35
+ .kui-row[data-text-insets*="t"] {
36
36
  --_kui-row-inset-top: calc(
37
37
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
38
38
  var(--kui-layout-item-padding, 0.5rem)
39
39
  );
40
40
  }
41
41
 
42
- .kui-row[text-insets*="r"] {
42
+ .kui-row[data-text-insets*="r"] {
43
43
  --_kui-row-inset-right: calc(
44
44
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
45
45
  var(--kui-layout-item-padding, 0.5rem)
46
46
  );
47
47
  }
48
48
 
49
- .kui-row[text-insets*="b"] {
49
+ .kui-row[data-text-insets*="b"] {
50
50
  --_kui-row-inset-bottom: calc(
51
51
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
52
52
  var(--kui-layout-item-padding, 0.5rem)
53
53
  );
54
54
  }
55
55
 
56
- .kui-row[text-insets*="l"] {
56
+ .kui-row[data-text-insets*="l"] {
57
57
  --_kui-row-inset-left: calc(
58
58
  var(--kui-layout-inline-margin, 0.5rem) + 1px +
59
59
  var(--kui-layout-item-padding, 0.5rem)
@@ -0,0 +1,11 @@
1
+ .kui-spacer {
2
+ box-sizing: border-box;
3
+ flex: 0 0 auto;
4
+ min-width: 0;
5
+ min-height: 0;
6
+ pointer-events: none;
7
+ }
8
+
9
+ .kui-spacer[data-flex="true"] {
10
+ flex: 1 1 auto;
11
+ }
@@ -8,6 +8,7 @@
8
8
  @import "./toolbar-text.css";
9
9
  @import "./list.css";
10
10
  @import "./row.css";
11
+ @import "./spacer.css";
11
12
  @import "./list-action-row.css";
12
13
  @import "./list-item.css";
13
14
  @import "./list-header.css";
@@ -38,8 +38,10 @@ foreground/background contrast relationships and visible focus ring.
38
38
 
39
39
  ### Typed runtime dimensions
40
40
 
41
- Use `Row` or `List` spacing shorthands for the common case: `gap="xs"`, `gap="m"`, and
42
- the other `UiSpaceName` values resolve to their matching `--kui-space-*` token.
41
+ Use `Row` or `List` spacing shorthands for repeated sibling spacing: `gap="xs"`,
42
+ `gap="m"`, and the other `UiSpaceName` values resolve to their matching
43
+ `--kui-space-*` token. Use `Spacer` for one deliberate empty width or height, or
44
+ pass `flex` to consume the remaining space along a flex parent's main axis.
43
45
  The canonical rhythm is `none`, `2xs`, `xs`, `m`, and `l`; use `s` and `xl`
44
46
  only as deliberate exceptions.
45
47
 
@@ -65,7 +67,8 @@ Raw strings such as `"0.25rem"` no longer satisfy `Row.gap` or `List.gap`; this
65
67
  contract lands before the 5.0 stable release so misspelled tokens and incomplete
66
68
  expressions fail during typechecking rather than in the browser.
67
69
 
68
- Other CSS-valued props use distinct contracts: `flex()` creates `CssFlex` for
70
+ Other CSS-valued props use distinct contracts: `Spacer.width` and
71
+ `Spacer.height` take `UiSpaceName | CssLength`; `flex()` creates `CssFlex` for
69
72
  `Row.flex` and `List.flex`; `Skeleton` dimensions take typed lengths (plus finite intrinsic
70
73
  keywords for width/height); and `uiColor()`/`colorVar()` create `CssColor` for
71
74
  `SelectChoice.color`. Do not substitute one brand for another. Row components
package/docs/layout.md CHANGED
@@ -118,6 +118,31 @@ Use `rem`, `em`, `px`, `pct`, `lengthVar`, and `calc(plus(...))` only when a
118
118
  named spacing relationship does not express the requirement; do not pass raw
119
119
  CSS strings.
120
120
 
121
+ ## Spacer
122
+
123
+ Use `Spacer` from `@kerfjs/ui/spacer` for one intentional empty dimension that
124
+ is not the repeated relationship owned by a parent's `gap`. Its `width` and
125
+ `height` accept the same finite `UiSpaceName` vocabulary or a complete typed
126
+ `CssLength`. Fixed spacers use `flex: 0 0 auto`, so flex layouts do not compress
127
+ the requested dimension. Pass `flex` to use `1 1 auto` and consume the remaining
128
+ space along a `Row`, `List`, or other flex parent's main axis.
129
+
130
+ ```tsx
131
+ import { Row } from "@kerfjs/ui/row";
132
+ import { Spacer } from "@kerfjs/ui/spacer";
133
+
134
+ <Row gap="none">
135
+ <button>Back</button>
136
+ <Spacer flex />
137
+ <button>Save</button>
138
+ </Row>;
139
+ ```
140
+
141
+ Spacer is always decorative (`aria-hidden="true"`), accepts no children, and
142
+ uses physical width and height. Prefer `Row.gap` or `List.gap` for uniform
143
+ sibling rhythm, and never use Spacer to cancel or duplicate component-owned
144
+ insets.
145
+
121
146
  ## Row and List alignment
122
147
 
123
148
  Use `Row` for a horizontal flex layout and `List` for a vertical one. `Row`
package/llms.txt CHANGED
@@ -162,7 +162,8 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
162
162
  `--kui-space-none`, `4px` `--kui-space-2xs`, `8px` `--kui-space-xs` (inside a
163
163
  group), `16px` `--kui-space-m`, `24px` `--kui-space-l` (between major differing
164
164
  regions). 12px/32px are off-scale exceptions. Prefer direct `Row.gap`/`List.gap`
165
- shorthands; use `space`, `px`, `rem`, `em`, `pct`, `lengthVar`, and
165
+ shorthands for repeated rhythm; use `Spacer` for one typed empty width/height
166
+ or flexible main-axis gap. Use `space`, `px`, `rem`, `em`, `pct`, `lengthVar`, and
166
167
  `calc(plus(...))` for typed runtime composition. `remify()` is source-CSS only.
167
168
  - **Keep nested highlight corners concentric.** Inset hover and selection surfaces
168
169
  follow the owning control's pill or rounded shape; their radius is the outer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.36",
3
+ "version": "5.0.0-beta.37",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -93,6 +93,11 @@
93
93
  "browser": "./dist/browser/row.js",
94
94
  "import": "./dist/row.js"
95
95
  },
96
+ "./spacer": {
97
+ "types": "./dist/spacer.d.ts",
98
+ "browser": "./dist/browser/spacer.js",
99
+ "import": "./dist/spacer.js"
100
+ },
96
101
  "./list-action-row": {
97
102
  "types": "./dist/list-action-row.d.ts",
98
103
  "browser": "./dist/browser/list-action-row.js",
@@ -284,6 +289,7 @@
284
289
  "./toolbar-text.css": "./dist/styles/toolbar-text.css",
285
290
  "./list.css": "./dist/styles/list.css",
286
291
  "./row.css": "./dist/styles/row.css",
292
+ "./spacer.css": "./dist/styles/spacer.css",
287
293
  "./list-action-row.css": "./dist/styles/list-action-row.css",
288
294
  "./list-item.css": "./dist/styles/list-item.css",
289
295
  "./list-header.css": "./dist/styles/list-header.css",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/row.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAsBO,SAAS,GAAA,CAAI;AAAA,EAClB,QAAA;AAAA,EACA,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,MAAA;AAAA,EACT,GAAA,GAAM,IAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY;AACd,CAAA,EAAa;AACX,EAAA,MAAM,WAAW,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACnD,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA;AACJ,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,kBAAkB,QAAQ,CAAA,CAAA;AAAA,IAC1B,SAAA,GAAY,CAAA,gBAAA,EAAmB,SAAS,CAAA,CAAA,GAAK;AAAA,GAC/C,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,QAAA,EAAW,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACnC,gBAAA,EAAe,KAAA;AAAA,MACf,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,WAAA,EAAW,OAAO,IAAI,CAAA;AAAA,MACtB,eAAa,UAAA,IAAc,MAAA;AAAA,MAC3B,kBAAgB,aAAA,IAAiB,MAAA;AAAA,MACjC,KAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-AVZ2IMWG.js","sourcesContent":["import {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from './css-values.js';\nimport type { Sides } from './divider-sides.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type VerticalAlignment,\n verticalAlignment,\n} from './flex-alignment.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface RowProps {\n children?: KerfUiContent;\n /** Horizontal distribution. Defaults to left. */\n hAlign?: HorizontalAlignment;\n /** Vertical alignment and wrapped-line distribution. Defaults to full. */\n vAlign?: VerticalAlignment;\n /** A named UI spacing token or typed CSS length. Defaults to xs. */\n gap?: UiSpaceName | CssLength;\n /** Allow this row to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /** Allow children to wrap onto additional lines. */\n wrap?: boolean;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n}\n\n/** A horizontal flex row with explicit physical-axis alignment and spacing. */\nexport function Row({\n children,\n hAlign = 'left',\n vAlign = 'full',\n gap = 'xs',\n flex = false,\n wrap = false,\n textInsets = '',\n controlInsets = '',\n className = '',\n}: RowProps) {\n const gapValue = spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n `--_kui-row-gap:${gapValue}`,\n flexValue ? `--_kui-row-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n class={`kui-row ${className}`.trim()}\n data-component=\"row\"\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-flex={String(Boolean(flex))}\n data-wrap={String(wrap)}\n text-insets={textInsets || undefined}\n control-insets={controlInsets || undefined}\n style={style}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from './css-values.js';\nexport type { Sides } from './divider-sides.js';\nexport type {\n HorizontalAlignment,\n VerticalAlignment,\n} from './flex-alignment.js';\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/list.tsx"],"names":[],"mappings":";;;;AAgBA,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AAwBO,SAAS,IAAA,CAAK;AAAA,EACnB,QAAA;AAAA,EACA,GAAA,GAAM,KAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,KAAA;AAAA,EACT,UAAA,GAAa,KAAA;AAAA,EACb,YAAA,GAAe,EAAA;AAAA,EACf,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY;AACd,CAAA,EAAc;AACZ,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,IAAA,GACJ,qBAAA,GACA,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACpC,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA,IAAO,MAAA;AACf,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,QAAA,GAAW,CAAA,gBAAA,EAAmB,QAAQ,CAAA,CAAA,GAAK,EAAA;AAAA,IAC3C,SAAA,GAAY,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,GAAK;AAAA,GAChD,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACpC,gBAAA,EAAe,MAAA;AAAA,MACf,UAAA,EAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAC,CAAA;AAAA,MAC7B,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,iBAAA,EAAiB,OAAO,UAAU,CAAA;AAAA,MAClC,iBAAe,YAAA,IAAgB,MAAA;AAAA,MAC/B,eAAa,UAAA,IAAc,MAAA;AAAA,MAC3B,kBAAgB,aAAA,IAAiB,MAAA;AAAA,MACjC,OAAO,KAAA,IAAS,MAAA;AAAA,MAEf;AAAA;AAAA,GACH;AAEJ","file":"chunk-TNXDXSGP.js","sourcesContent":["import {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from './css-values.js';\nimport type { DividerSides, Sides } from './divider-sides.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type VerticalAlignment,\n verticalAlignment,\n} from './flex-alignment.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface ListProps {\n children?: KerfUiContent;\n /** Use the standard item gap, a named UI spacing token, or a typed CSS length. Defaults to no gap. */\n gap?: boolean | UiSpaceName | CssLength;\n /** Allow this list to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /** Horizontal alignment. Defaults to full to preserve stretch-aligned list children. */\n hAlign?: HorizontalAlignment;\n /** Vertical distribution. Defaults to top. */\n vAlign?: VerticalAlignment;\n /** Own vertical scrolling and overscroll containment. */\n scrollable?: boolean;\n /** Physical divider edges in canonical top/right/bottom/left order. */\n dividerSides?: DividerSides;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n}\n\n/** A stretch-aligned vertical stack with optional gap, flex, scroll, and dividers. */\nexport function List({\n children,\n gap = false,\n flex = false,\n hAlign = 'full',\n vAlign = 'top',\n scrollable = false,\n dividerSides = '',\n textInsets = '',\n controlInsets = '',\n className = '',\n}: ListProps) {\n const gapValue =\n gap === true\n ? 'var(--kui-list-gap)'\n : spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap || undefined;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n gapValue ? `--_kui-list-gap:${gapValue}` : '',\n flexValue ? `--_kui-list-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n class={`kui-list ${className}`.trim()}\n data-component=\"list\"\n data-gap={String(Boolean(gap))}\n data-flex={String(Boolean(flex))}\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-scrollable={String(scrollable)}\n divider-sides={dividerSides || undefined}\n text-insets={textInsets || undefined}\n control-insets={controlInsets || undefined}\n style={style || undefined}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from './css-values.js';\nexport type { DividerSides, Sides } from './divider-sides.js';\nexport type {\n HorizontalAlignment,\n VerticalAlignment,\n} from './flex-alignment.js';\n"]}