@kerfjs/ui 5.0.0-beta.64 → 5.0.0-beta.66

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.
Files changed (74) hide show
  1. package/ai/component-catalog.json +37 -15
  2. package/ai/component-composition.json +28 -4
  3. package/ai/components/content-item.md +4 -1
  4. package/ai/components/grid.md +3 -1
  5. package/ai/components/list.md +4 -2
  6. package/ai/components/nav-stack.md +4 -3
  7. package/ai/components/token-search-field.md +3 -0
  8. package/ai/components/workbench.md +5 -3
  9. package/ai/public-api-signatures-v1.md +66 -42
  10. package/ai/skill.md +4 -0
  11. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  12. package/dist/browser/catalog.js +2 -1
  13. package/dist/browser/workbench.js +4 -1
  14. package/dist/catalog-resources.d.ts +2 -0
  15. package/dist/catalog.d.ts +2 -0
  16. package/dist/catalog.js +3 -2
  17. package/dist/catalog.js.map +1 -1
  18. package/dist/{chunk-NSVLUDUW.js → chunk-5SYE4CCT.js} +25 -3
  19. package/dist/chunk-5SYE4CCT.js.map +1 -0
  20. package/dist/{chunk-2EKHENQK.js → chunk-B2ZMR7NY.js} +9 -1
  21. package/dist/chunk-B2ZMR7NY.js.map +1 -0
  22. package/dist/{chunk-IA4D5XNT.js → chunk-C74LITXE.js} +5 -5
  23. package/dist/{chunk-IA4D5XNT.js.map → chunk-C74LITXE.js.map} +1 -1
  24. package/dist/{chunk-SH6F3CCG.js → chunk-NCSWXG7G.js} +4 -4
  25. package/dist/{chunk-SH6F3CCG.js.map → chunk-NCSWXG7G.js.map} +1 -1
  26. package/dist/{chunk-M5H2RMCH.js → chunk-P4UXSYIJ.js} +16 -2
  27. package/dist/chunk-P4UXSYIJ.js.map +1 -0
  28. package/dist/{chunk-KFYANKJI.js → chunk-TGM424PI.js} +35 -5
  29. package/dist/chunk-TGM424PI.js.map +1 -0
  30. package/dist/chunk-VY6YRCTV.js +123 -0
  31. package/dist/chunk-VY6YRCTV.js.map +1 -0
  32. package/dist/{chunk-NAPE7WUS.js → chunk-W2AC6Q2A.js} +9 -1
  33. package/dist/chunk-W2AC6Q2A.js.map +1 -0
  34. package/dist/collapsible-panel.js +2 -2
  35. package/dist/content-item.d.ts +2 -2
  36. package/dist/content-item.js +1 -1
  37. package/dist/grid.d.ts +5 -1
  38. package/dist/grid.js +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +5 -5
  41. package/dist/list.d.ts +5 -1
  42. package/dist/list.js +1 -1
  43. package/dist/nav-stack.d.ts +5 -1
  44. package/dist/nav-stack.js +1 -1
  45. package/dist/split-view.js +1 -1
  46. package/dist/styles/nav-stack.css +4 -0
  47. package/dist/styles/token-search-field.css +36 -0
  48. package/dist/styles/toolbar.css +5 -1
  49. package/dist/{token-search-field-Xk8jiw8Y.d.ts → token-search-field-CVGwDn2y.d.ts} +14 -4
  50. package/dist/token-search-field.d.ts +1 -1
  51. package/dist/token-search-field.js +1 -1
  52. package/dist/token-search-model.d.ts +1 -1
  53. package/dist/wire-content-items.d.ts +3 -1
  54. package/dist/wire-content-items.js +49 -1
  55. package/dist/wire-content-items.js.map +1 -1
  56. package/dist/wire-token-search-fields.d.ts +1 -1
  57. package/dist/wire-token-search-fields.js +1 -1
  58. package/dist/workbench.d.ts +41 -33
  59. package/dist/workbench.js +4 -1
  60. package/docs/accessibility.md +26 -2
  61. package/docs/component-selection.md +16 -0
  62. package/docs/layout.md +24 -4
  63. package/docs/nav-stack.md +7 -0
  64. package/docs/webawesome-theme.md +6 -0
  65. package/docs/workbench.md +27 -3
  66. package/llms.txt +3 -0
  67. package/package.json +1 -1
  68. package/dist/chunk-2EKHENQK.js.map +0 -1
  69. package/dist/chunk-HXCMWANP.js +0 -116
  70. package/dist/chunk-HXCMWANP.js.map +0 -1
  71. package/dist/chunk-KFYANKJI.js.map +0 -1
  72. package/dist/chunk-M5H2RMCH.js.map +0 -1
  73. package/dist/chunk-NAPE7WUS.js.map +0 -1
  74. package/dist/chunk-NSVLUDUW.js.map +0 -1
@@ -1,3 +1,3 @@
1
1
  import 'kerfjs';
2
2
  import './semantic-content-BbzjvSu9.js';
3
- export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, j as TokenSearchToken, k as TokenSearchTrailingAction, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-field-Xk8jiw8Y.js';
3
+ export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, j as TokenSearchToken, k as TokenSearchTrailingAction, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-field-CVGwDn2y.js';
@@ -1,4 +1,4 @@
1
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-NSVLUDUW.js';
1
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-5SYE4CCT.js';
2
2
 
3
3
 
4
4
 
@@ -1,3 +1,3 @@
1
1
  import 'kerfjs';
2
- export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, l as createTokenSearchModel } from './token-search-field-Xk8jiw8Y.js';
2
+ export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, l as createTokenSearchModel } from './token-search-field-CVGwDn2y.js';
3
3
  import './semantic-content-BbzjvSu9.js';
@@ -1,3 +1,5 @@
1
+ /** True when a card action originated on the card rather than a nested control. */
2
+ declare function isContentItemActivation(event: Event, item: HTMLElement): boolean;
1
3
  /**
2
4
  * Give interactive ContentItems native-like Enter/Space activation with one
3
5
  * delegated listener pair. Space activates on release and is cancelled if
@@ -6,4 +8,4 @@
6
8
  */
7
9
  declare function wireContentItems(root: HTMLElement): () => void;
8
10
 
9
- export { wireContentItems };
11
+ export { isContentItemActivation, wireContentItems };
@@ -1,6 +1,26 @@
1
1
  // src/wire-content-items.ts
2
2
  var ITEM_SELECTOR = '[data-component="content-item"][data-interactive="true"]';
3
3
  var NESTED_CONTROL_SELECTOR = 'button, a[href], input, select, textarea, [contenteditable="true"], [role="button"]';
4
+ var MULTI_GRID_SELECTOR = '[role="grid"][aria-multiselectable="true"]';
5
+ function nextTile(item, rows, key) {
6
+ const current = item.getBoundingClientRect();
7
+ const centerX = current.left + current.width / 2;
8
+ const candidates = rows.filter((row) => row !== item).map((row) => {
9
+ const rect = row.getBoundingClientRect();
10
+ return { row, x: rect.left + rect.width / 2, y: rect.top };
11
+ });
12
+ const sameLine = (y) => Math.abs(y - current.top) < 1;
13
+ const directed = candidates.filter(
14
+ ({ x, y }) => key === "ArrowLeft" ? sameLine(y) && x < centerX : key === "ArrowRight" ? sameLine(y) && x > centerX : key === "ArrowUp" ? y < current.top - 1 : y > current.top + 1
15
+ );
16
+ directed.sort((a, b) => {
17
+ if (key === "ArrowLeft") return b.x - a.x;
18
+ if (key === "ArrowRight") return a.x - b.x;
19
+ const vertical = Math.abs(a.y - current.top) - Math.abs(b.y - current.top);
20
+ return vertical || Math.abs(a.x - centerX) - Math.abs(b.x - centerX);
21
+ });
22
+ return directed[0]?.row;
23
+ }
4
24
  function actionItem(root, event) {
5
25
  const target = event.target;
6
26
  if (!(target instanceof Element)) return null;
@@ -14,6 +34,9 @@ function actionItem(root, event) {
14
34
  }
15
35
  return item;
16
36
  }
37
+ function isContentItemActivation(event, item) {
38
+ return actionItem(item, event) === item;
39
+ }
17
40
  function wireContentItems(root) {
18
41
  let pendingSpace = null;
19
42
  const clearSpace = () => {
@@ -25,6 +48,31 @@ function wireContentItems(root) {
25
48
  return;
26
49
  const item = actionItem(root, event);
27
50
  if (!item) return;
51
+ if (item.dataset.selectionMode === "multiple" && [
52
+ "ArrowDown",
53
+ "ArrowUp",
54
+ "ArrowLeft",
55
+ "ArrowRight",
56
+ "Home",
57
+ "End"
58
+ ].includes(event.key)) {
59
+ const grid = item.closest(MULTI_GRID_SELECTOR);
60
+ if (!grid) return;
61
+ const rows = Array.from(
62
+ grid.querySelectorAll(
63
+ `${ITEM_SELECTOR}[data-selection-mode="multiple"]:not([data-disabled="true"])`
64
+ )
65
+ ).filter((row) => row.closest(MULTI_GRID_SELECTOR) === grid);
66
+ const index = rows.indexOf(item);
67
+ if (index < 0) return;
68
+ const isTileGrid = grid.getAttribute("data-component") === "grid";
69
+ const next = event.key === "Home" ? rows[0] : event.key === "End" ? rows[rows.length - 1] : isTileGrid ? nextTile(item, rows, event.key) : event.key === "ArrowDown" || event.key === "ArrowUp" ? rows[index + (event.key === "ArrowDown" ? 1 : -1)] : void 0;
70
+ if (next) {
71
+ event.preventDefault();
72
+ next.focus();
73
+ }
74
+ return;
75
+ }
28
76
  if (event.key === "Enter") {
29
77
  event.preventDefault();
30
78
  item.click();
@@ -61,6 +109,6 @@ function wireContentItems(root) {
61
109
  };
62
110
  }
63
111
 
64
- export { wireContentItems };
112
+ export { isContentItemActivation, wireContentItems };
65
113
 
66
114
  //# sourceMappingURL=wire-content-items.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/wire-content-items.ts"],"names":[],"mappings":"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","file":"wire-content-items.js","sourcesContent":["const ITEM_SELECTOR =\n '[data-component=\"content-item\"][data-interactive=\"true\"]';\nconst NESTED_CONTROL_SELECTOR =\n 'button, a[href], input, select, textarea, [contenteditable=\"true\"], [role=\"button\"]';\n\nfunction actionItem(root: HTMLElement, event: Event) {\n const target = event.target;\n if (!(target instanceof Element)) return null;\n const item = target.closest<HTMLElement>(ITEM_SELECTOR);\n if (!item || !root.contains(item) || item.dataset.disabled === 'true')\n return null;\n // Composed events expose native controls inside a custom element's shadow\n // root even though event.target is retargeted to its host here.\n for (const node of event.composedPath()) {\n if (node === item) break;\n if (node instanceof Element && node.matches(NESTED_CONTROL_SELECTOR))\n return null;\n }\n return item;\n}\n\n/**\n * Give interactive ContentItems native-like Enter/Space activation with one\n * delegated listener pair. Space activates on release and is cancelled if\n * focus moves; the item's own `data-action` still goes through the app's\n * ordinary delegated click handler. Returns a disposer.\n */\nexport function wireContentItems(root: HTMLElement): () => void {\n let pendingSpace: HTMLElement | null = null;\n\n const clearSpace = () => {\n pendingSpace?.removeAttribute('data-kui-pressed');\n pendingSpace = null;\n };\n\n const keydown = (event: KeyboardEvent) => {\n if (\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey\n )\n return;\n const item = actionItem(root, event);\n if (!item) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n item.click();\n return;\n }\n if (event.key === ' ' || event.key === 'Spacebar') {\n event.preventDefault();\n if (event.repeat) return;\n clearSpace();\n pendingSpace = item;\n item.setAttribute('data-kui-pressed', 'true');\n }\n };\n\n const keyup = (event: KeyboardEvent) => {\n if (event.key !== ' ' && event.key !== 'Spacebar') return;\n const item = pendingSpace;\n if (!item) return;\n event.preventDefault();\n clearSpace();\n if (\n item === actionItem(root, event) &&\n item.ownerDocument.activeElement === item\n )\n item.click();\n };\n\n const focusout = (event: FocusEvent) => {\n if (event.target === pendingSpace) clearSpace();\n };\n\n root.addEventListener('keydown', keydown);\n root.addEventListener('keyup', keyup);\n root.addEventListener('focusout', focusout);\n return () => {\n clearSpace();\n root.removeEventListener('keydown', keydown);\n root.removeEventListener('keyup', keyup);\n root.removeEventListener('focusout', focusout);\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/wire-content-items.ts"],"names":[],"mappings":"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","file":"wire-content-items.js","sourcesContent":["const ITEM_SELECTOR =\n '[data-component=\"content-item\"][data-interactive=\"true\"]';\nconst NESTED_CONTROL_SELECTOR =\n 'button, a[href], input, select, textarea, [contenteditable=\"true\"], [role=\"button\"]';\nconst MULTI_GRID_SELECTOR = '[role=\"grid\"][aria-multiselectable=\"true\"]';\n\nfunction nextTile(\n item: HTMLElement,\n rows: HTMLElement[],\n key: string,\n): HTMLElement | undefined {\n const current = item.getBoundingClientRect();\n const centerX = current.left + current.width / 2;\n const candidates = rows\n .filter((row) => row !== item)\n .map((row) => {\n const rect = row.getBoundingClientRect();\n return { row, x: rect.left + rect.width / 2, y: rect.top };\n });\n const sameLine = (y: number) => Math.abs(y - current.top) < 1;\n const directed = candidates.filter(({ x, y }) =>\n key === 'ArrowLeft'\n ? sameLine(y) && x < centerX\n : key === 'ArrowRight'\n ? sameLine(y) && x > centerX\n : key === 'ArrowUp'\n ? y < current.top - 1\n : y > current.top + 1,\n );\n directed.sort((a, b) => {\n if (key === 'ArrowLeft') return b.x - a.x;\n if (key === 'ArrowRight') return a.x - b.x;\n const vertical = Math.abs(a.y - current.top) - Math.abs(b.y - current.top);\n return vertical || Math.abs(a.x - centerX) - Math.abs(b.x - centerX);\n });\n return directed[0]?.row;\n}\n\nfunction actionItem(root: HTMLElement, event: Event) {\n const target = event.target;\n if (!(target instanceof Element)) return null;\n const item = target.closest<HTMLElement>(ITEM_SELECTOR);\n if (!item || !root.contains(item) || item.dataset.disabled === 'true')\n return null;\n // Composed events expose native controls inside a custom element's shadow\n // root even though event.target is retargeted to its host here.\n for (const node of event.composedPath()) {\n if (node === item) break;\n if (node instanceof Element && node.matches(NESTED_CONTROL_SELECTOR))\n return null;\n }\n return item;\n}\n\n/** True when a card action originated on the card rather than a nested control. */\nexport function isContentItemActivation(\n event: Event,\n item: HTMLElement,\n): boolean {\n return actionItem(item, event) === item;\n}\n\n/**\n * Give interactive ContentItems native-like Enter/Space activation with one\n * delegated listener pair. Space activates on release and is cancelled if\n * focus moves; the item's own `data-action` still goes through the app's\n * ordinary delegated click handler. Returns a disposer.\n */\nexport function wireContentItems(root: HTMLElement): () => void {\n let pendingSpace: HTMLElement | null = null;\n\n const clearSpace = () => {\n pendingSpace?.removeAttribute('data-kui-pressed');\n pendingSpace = null;\n };\n\n const keydown = (event: KeyboardEvent) => {\n if (\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey\n )\n return;\n const item = actionItem(root, event);\n if (!item) return;\n if (\n item.dataset.selectionMode === 'multiple' &&\n [\n 'ArrowDown',\n 'ArrowUp',\n 'ArrowLeft',\n 'ArrowRight',\n 'Home',\n 'End',\n ].includes(event.key)\n ) {\n const grid = item.closest(MULTI_GRID_SELECTOR);\n if (!grid) return;\n const rows = Array.from(\n grid.querySelectorAll<HTMLElement>(\n `${ITEM_SELECTOR}[data-selection-mode=\"multiple\"]:not([data-disabled=\"true\"])`,\n ),\n ).filter((row) => row.closest(MULTI_GRID_SELECTOR) === grid);\n const index = rows.indexOf(item);\n if (index < 0) return;\n const isTileGrid = grid.getAttribute('data-component') === 'grid';\n const next =\n event.key === 'Home'\n ? rows[0]\n : event.key === 'End'\n ? rows[rows.length - 1]\n : isTileGrid\n ? nextTile(item, rows, event.key)\n : event.key === 'ArrowDown' || event.key === 'ArrowUp'\n ? rows[index + (event.key === 'ArrowDown' ? 1 : -1)]\n : undefined;\n if (next) {\n event.preventDefault();\n next.focus();\n }\n return;\n }\n if (event.key === 'Enter') {\n event.preventDefault();\n item.click();\n return;\n }\n if (event.key === ' ' || event.key === 'Spacebar') {\n event.preventDefault();\n if (event.repeat) return;\n clearSpace();\n pendingSpace = item;\n item.setAttribute('data-kui-pressed', 'true');\n }\n };\n\n const keyup = (event: KeyboardEvent) => {\n if (event.key !== ' ' && event.key !== 'Spacebar') return;\n const item = pendingSpace;\n if (!item) return;\n event.preventDefault();\n clearSpace();\n if (\n item === actionItem(root, event) &&\n item.ownerDocument.activeElement === item\n )\n item.click();\n };\n\n const focusout = (event: FocusEvent) => {\n if (event.target === pendingSpace) clearSpace();\n };\n\n root.addEventListener('keydown', keydown);\n root.addEventListener('keyup', keyup);\n root.addEventListener('focusout', focusout);\n return () => {\n clearSpace();\n root.removeEventListener('keydown', keydown);\n root.removeEventListener('keyup', keyup);\n root.removeEventListener('focusout', focusout);\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Signal } from 'kerfjs';
2
- import { d as TokenSearchModel } from './token-search-field-Xk8jiw8Y.js';
2
+ import { d as TokenSearchModel } from './token-search-field-CVGwDn2y.js';
3
3
  import './semantic-content-BbzjvSu9.js';
4
4
 
5
5
  interface TokenSearchSubmit {
@@ -1,4 +1,4 @@
1
- export { wireTokenSearchFields } from './chunk-M5H2RMCH.js';
1
+ export { wireTokenSearchFields } from './chunk-P4UXSYIJ.js';
2
2
 
3
3
 
4
4
 
@@ -1,13 +1,15 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
  import { ListConfig } from './list.js';
3
+ import { NavStackProps } from './nav-stack.js';
3
4
  import { PaneConfig } from './pane.js';
4
5
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
5
6
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
6
- import { a as PanelToolbar, b as PanelChromePlacement, c as PanelBottomToolbar, d as PanelToggle } from './panel-toolbar-DDKh4uKd.js';
7
7
  import { ToolbarConfig } from './toolbar.js';
8
+ import { a as PanelToolbar, b as PanelChromePlacement, c as PanelBottomToolbar, d as PanelToggle } from './panel-toolbar-DDKh4uKd.js';
8
9
  import './css-values.js';
9
10
  import './flex-alignment-4ms8ZbV8.js';
10
11
  import './sides-BPSWde0A.js';
12
+ import './toolbar-text.js';
11
13
  import './lucide-icon.js';
12
14
  import 'lucide';
13
15
 
@@ -80,37 +82,8 @@ interface WorkbenchPanelResizable {
80
82
  /** Largest size (default 480). */
81
83
  max?: number;
82
84
  }
83
- /** A collapsible Workbench panel — a side rail or the bottom drawer. */
84
- interface WorkbenchPanel {
85
- content: KerfUiContent;
86
- /**
87
- * The panel's top toolbar, composed by the Workbench: its marked groups
88
- * and standard `toggle` move to the work area's toolbar while the panel is
89
- * collapsed. With it, `content` renders in a `Pane` below the toolbar.
90
- */
91
- toolbar?: WorkbenchPanelToolbar;
92
- /** Fixed content below a toolbar panel's top toolbar, above its scroll region. */
93
- header?: KerfUiContent;
94
- /** List layout for `header`; omitted fields keep List defaults. */
95
- headerList?: ListConfig;
96
- /** Placement of the top toolbar and header together; default `fixed`. */
97
- headerPlacement?: WorkbenchChromePlacement;
98
- /** Fixed content above a toolbar panel's bottom toolbar. */
99
- footer?: KerfUiContent;
100
- /** List layout for `footer`; omitted fields keep List defaults. */
101
- footerList?: ListConfig;
102
- /** Fixed toolbar below `footer`, using footer semantics. */
103
- bottomToolbar?: WorkbenchPanelBottomToolbar;
104
- /** Placement of the footer and bottom toolbar together; default `fixed`. */
105
- footerPlacement?: WorkbenchChromePlacement;
106
- /**
107
- * Configuration for a `toolbar` panel's `Pane` (`contentElement`,
108
- * `contentLabel`, `separators`, `safeAreaEdges`, `chromeDividers`) — for example
109
- * `{ contentElement: 'nav', contentLabel: 'Sections' }` for a navigation
110
- * rail. Omitted or `undefined` fields keep the `Pane` defaults. Ignored
111
- * without a `toolbar`, where `content` renders as given.
112
- */
113
- pane?: PaneConfig;
85
+ /** Region sizing, collapse, and presentation shared by either panel body. */
86
+ interface WorkbenchPanelBase {
114
87
  /** Whether the panel is currently collapsed (the app owns this). */
115
88
  collapsed?: boolean;
116
89
  /**
@@ -157,6 +130,41 @@ interface WorkbenchPanel {
157
130
  restoreControl?: SafeHtml;
158
131
  restorePosition?: ResizableRegionRestorePosition;
159
132
  }
133
+ /** The original static panel body: one toolbar, optional fixed chrome, and one Pane. */
134
+ interface WorkbenchStaticPanel extends WorkbenchPanelBase {
135
+ content: KerfUiContent;
136
+ /** The composed toolbar; marked groups and its toggle relocate on collapse. */
137
+ toolbar?: WorkbenchPanelToolbar;
138
+ header?: KerfUiContent;
139
+ headerList?: ListConfig;
140
+ headerPlacement?: WorkbenchChromePlacement;
141
+ footer?: KerfUiContent;
142
+ footerList?: ListConfig;
143
+ bottomToolbar?: WorkbenchPanelBottomToolbar;
144
+ footerPlacement?: WorkbenchChromePlacement;
145
+ /** Configuration for the static toolbar panel's Pane. */
146
+ pane?: PaneConfig;
147
+ navStack?: never;
148
+ }
149
+ /** A panel whose active NavStack view supplies the toolbar, fixed header, and scroll body. */
150
+ interface WorkbenchNavigationPanel extends WorkbenchPanelBase {
151
+ navStack: Omit<NavStackProps, 'panelToggle' | 'hideToolbar' | 'className' | 'slot'>;
152
+ /** Supplies the standard toggle; active view groups precede it and may relocate on collapse. */
153
+ toolbar: Omit<WorkbenchPanelToolbar, 'title' | 'leading' | 'center' | 'trailing'> & {
154
+ toggle: NonNullable<WorkbenchPanelToolbar['toggle']>;
155
+ };
156
+ content?: never;
157
+ header?: never;
158
+ headerList?: never;
159
+ headerPlacement?: never;
160
+ footer?: never;
161
+ footerList?: never;
162
+ bottomToolbar?: never;
163
+ footerPlacement?: never;
164
+ pane?: never;
165
+ }
166
+ /** A collapsible Workbench side rail or bottom drawer. */
167
+ type WorkbenchPanel = WorkbenchStaticPanel | WorkbenchNavigationPanel;
160
168
  interface WorkbenchProps {
161
169
  /**
162
170
  * The Workbench's `id`. Each panel's `id` derives from it —
@@ -257,4 +265,4 @@ interface WorkbenchProps {
257
265
  */
258
266
  declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainPane, mainHeaderList, mainFooterList, mainMinSize, mainMinHeight, overlayBackdrop, className, slot, }: WorkbenchProps): SafeHtml;
259
267
 
260
- export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchPanel, type WorkbenchPanelBottomToolbar, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt };
268
+ export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchNavigationPanel, type WorkbenchPanel, type WorkbenchPanelBottomToolbar, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt, type WorkbenchStaticPanel };
package/dist/workbench.js CHANGED
@@ -1,4 +1,7 @@
1
- export { Workbench } from './chunk-KFYANKJI.js';
1
+ export { Workbench } from './chunk-TGM424PI.js';
2
+
3
+
4
+
2
5
 
3
6
 
4
7
 
@@ -15,14 +15,27 @@ These are normative contracts for package components and consuming applications.
15
15
  - `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
16
16
  - A busy `ToolbarControlGroup` makes its controls inert and exposes `aria-busy`; its `busyLabel` is announced by an adjacent status node outside the inert group. Clear `busy` when the operation finishes so the controls rejoin the focus order.
17
17
  - A bottom `Toolbar` uses `position="footer"` for footer semantics; top toolbars keep the default `header`. Workbench's main bottom toolbar sets this automatically.
18
- - Interactive `ContentItem` cards have a keyboard reachable button or option role. Set `selectionMode="toggle"` for `aria-pressed`, or `selectionMode="single"` inside a listbox for `aria-selected`; wire `wireContentItems` once so Enter and Space activate their delegated action. Disabled cards expose `aria-disabled` and leave the Tab order.
18
+ - Interactive `ContentItem` cards are keyboard reachable. Set `selectionMode="toggle"` for a pressed button, `selectionMode="single"` for an option inside a listbox, or `selectionMode="multiple"` for a selectable row with a gridcell that can contain native controls. Place multiple-mode rows inside a labeled `List` or `Grid` with `selectionMode="multiple"`; the app owns the selection set and range gestures. Wire `wireContentItems` once so Enter and Space on the card activate its delegated action without activating it from a nested control. Disabled cards expose `aria-disabled` and leave the Tab order.
19
19
  - An unlabeled `Skeleton` (including the value slots a component's `placeholder` prop renders) is decorative loading chrome: it is `aria-hidden`, conveys no information a user needs to act on, and is deliberately exempt from the WCAG 3:1 non-text-contrast target. Inside a solid `Badge` it stays subtle (about 2.1–4.4:1 in light, 2.4–2.8:1 in dark, depending on tone); `tests/browser/tone-contrast.spec.ts` holds those values as a regression floor rather than an accessibility threshold. Announce loading through a labeled `Skeleton`, `LoadingSpinner`, or `aria-busy` on the owning region, never through the skeleton's visibility.
20
20
  - `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
21
21
  - A removable `Chip` has a native button with a specific `removeLabel`, such as "Remove Urgent tag". The button is absent for a plain chip and disabled for a disabled chip. Its leading `icon` is decorative; the visible label names the tag. `truncate` retains the full plain-text label as a native tooltip. Tag text remains visible because tone does not convey meaning alone.
22
22
  - `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
23
23
  - `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone. Its decorative option icons and value-dependent selected content remain present after controlled rerenders, so the visible choice does not silently lose its non-text cue.
24
24
  - `DisclosureArrow` is an 18px root-scaled decorative visual by default, not an interaction target. Its owning native control supplies a stable accessible name, pointer and keyboard interaction, and `aria-expanded`; multiple arrows keep independently controlled state. Consumers may resize it with the typed `size` prop without changing that ownership. Configured directions animate over the shortest path; a 180-degree closed-to-open tie uses counterclockwise rotation.
25
- - `List` is layout-only and adds no `list` role. Children own their native or ARIA semantics. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
25
+ - `List` is layout-only by default and adds no `list` role. With `selectionMode="multiple"` and a required `ariaLabel`, it becomes a multi-select grid for `ContentItem` rows. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
26
+ - `Grid` is layout-only by default. With `selectionMode="multiple"` and a required `ariaLabel`, it becomes a multi-select grid for `ContentItem` tiles; each tile remains a row with a gridcell, including when the CSS grid wraps.
27
+
28
+ In a multi-select ContentItem grid, `wireContentItems` moves focus between
29
+ enabled rows on Up/Down and to the first/last enabled row on Home/End. Native
30
+ controls inside a row retain those keys. In a `Grid`, Left/Right moves between
31
+ tiles on the same rendered line and Up/Down moves to the nearest tile in the
32
+ adjacent rendered line; Home/End reaches the first/last enabled tile. The
33
+ navigation reads live geometry, so responsive column changes need no rewiring.
34
+ The application changes selection
35
+ on card activation and handles its own Shift/Cmd range gestures.
36
+ With delegated click actions, use `isContentItemActivation(event, card)` in the
37
+ card's selection handler so a nested menu trigger or other control does not
38
+ also select the card.
26
39
 
27
40
  ## Select
28
41
 
@@ -397,6 +410,17 @@ need `tab`/`tabpanel` semantics.
397
410
 
398
411
  ## TokenSearchField
399
412
 
413
+ Use `presentation="form-field"` when a structured search query sits beside
414
+ Web Awesome form controls. It renders the visible `label` with the same inset
415
+ and typography, an optional `hint`, and an optional `required` marker. The
416
+ searchbox references the label and hint with `aria-labelledby` and
417
+ `aria-describedby`, and `required` sets `aria-required`; the application
418
+ validates the value before submission. `wireTokenSearchFields()` also moves
419
+ focus to the editor when the visible label is clicked. The field stands outside
420
+ a Toolbar and fills the same inline space as a neighboring `wa-input`.
421
+ Its control radius and required-marker color and offset follow Web Awesome's
422
+ form-control tokens, so the two controls read as one form.
423
+
400
424
  `TokenSearchField` exposes the editable surface as a named `searchbox`. Each
401
425
  chip is atomic (`contenteditable="false"`) and contains separately named edit
402
426
  and remove buttons; the clear action is also named. Disabled fields publish
@@ -329,6 +329,14 @@ still owns the controlled `value` and localized action labels.
329
329
  - `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
330
330
 
331
331
  For common structured search, use `createTokenSearchModel` from `@kerfjs/ui/token-search-model`, pass the model to `TokenSearchField`, and register it under the field id in `wireTokenSearchFields(root, { models })`. Rules parse `name:value` expressions and provide suggestions; the model owns chips, selection, removal, clear, and an optional evaluation result. Omit `model` and `models` for full manual control.
332
+
333
+ In a form, use `TokenSearchField presentation="form-field"` with its `label`,
334
+ optional `hint`, and `required` state. It renders a full-width control beside
335
+ `wa-input` without a Toolbar or extra inline inset. The app validates required
336
+ search content before saving.
337
+ The form-field corners and required marker use the same Web Awesome
338
+ form-control radius, marker color, and offset tokens as a neighboring
339
+ `wa-input`.
332
340
  `TokenSearchRule.suggest(input, state)` receives the unfinished value and current committed tokens, so a rule can omit values already selected. Existing one-argument suggestion callbacks continue to work.
333
341
  For a sibling date picker or other app helper, call `model.commit(value)` while a `name:` prefix is active. It parses and commits that value for the active rule even when it is absent from suggestions, replacing the unfinished prefix text. Invalid values and calls without an active prefix leave the query unchanged; `choose(value)` remains restricted to suggested tokens.
334
342
  To apply a saved search or restore a query programmatically, call `model.replace({ query, tokens })`. It publishes parsed state and rebuilds the DOM-owned editor text even when no chip changed. For a field without a model, change the field's `revision` prop when setting `query` yourself.
@@ -500,6 +508,14 @@ When that child is a selectable card, configure `ContentItem` with
500
508
  `wireContentItems` once for Enter and Space. The component owns selection,
501
509
  hover, focus, and disabled paint in its reserved 1px frame (see
502
510
  [Content items](./layout.md#content-items)).
511
+ For a multi-select card containing its own controls, place
512
+ `selectionMode="multiple"` ContentItems inside a labeled
513
+ `List selectionMode="multiple"`; the app supplies each card's `selected`
514
+ state and handles range gestures.
515
+ For wrapped document tiles, use `Grid minColumnWidth={px(160)}` with
516
+ `selectionMode="multiple"` and `ariaLabel`. The same ContentItem row/gridcell
517
+ contract applies, while `wireContentItems` follows the rendered tile layout
518
+ with all four arrow keys.
503
519
 
504
520
  Incorrect: duplicating component-like rows and compensating for nested padding
505
521
  forks the package anatomy and spacing contract.
package/docs/layout.md CHANGED
@@ -214,13 +214,25 @@ activates focused cards with Enter or Space and forwards the resulting click to
214
214
  the app's ordinary `data-action` handler. `selectionMode="toggle"` maps
215
215
  `selected` to a button's `aria-pressed`; `selectionMode="single"` maps it to an
216
216
  option's `aria-selected` and belongs in an app-owned `role="listbox"` container.
217
+ For a card with nested controls and multi-selection, place
218
+ `selectionMode="multiple"` cards inside a `List selectionMode="multiple"` with
219
+ `ariaLabel`. The List is a labeled `grid` with `aria-multiselectable`, each card
220
+ is a focusable `row` with `aria-selected`, and its content is a `gridcell` that
221
+ may contain native controls. The app owns the selection set, including
222
+ Shift/Cmd extension; the card renders the supplied `selected` state and
223
+ dispatches its `action`. Give nested controls their own actions.
224
+ When the app delegates clicks by `data-action`, guard the card's selection
225
+ handler with `isContentItemActivation(event, card)` from
226
+ `@kerfjs/ui/wire-content-items`; it ignores clicks from nested controls that
227
+ have no separate action, including a `PopupMenu` trigger.
228
+ `wireContentItems` moves focus among enabled rows with Up/Down and Home/End
229
+ when a row has focus; arrows from nested controls retain their native behavior.
217
230
  An interactive card is a Tab stop, and `disabled` removes its action and Tab
218
231
  stop while exposing `aria-disabled`. Hover uses the normal neutral fill so it
219
232
  remains visible on a lowered surface; hover, pressed, focus, and selected paints
220
233
  do not change geometry. A single-action card has no interactive descendants.
221
- For a rich card with a primary and secondary action, render a static
222
- `ContentItem` with sibling buttons inside it. Each button then has its own
223
- focus stop and action without nesting controls inside an interactive card.
234
+ For a rich card without card selection, render a static `ContentItem` with
235
+ sibling buttons inside it. Each button then has its own focus stop and action.
224
236
  `ListActionRow` already packages this pattern for a simple two-action row.
225
237
  `rootAttributes` still accepts safe app `data-*` metadata, while the card owns
226
238
  its interaction data.
@@ -277,6 +289,9 @@ item no taller than the band is centered in it by its own size, and a taller
277
289
  item (a wrapped title, a second trailing row) starts at the band's top and
278
290
  grows down, so it never moves the other controls. A wrapped `ToolbarText`
279
291
  centers its first line in the band.
292
+ The leading zone clips a shrinking title but reserves 4px of paint overflow
293
+ for the focus outline of a control at its edge. Focus rings in all three zones
294
+ remain visible at narrow and wide widths.
280
295
  Trailing icon actions share one axis: a toolbar control centers its glyph in
281
296
  that 44px slot at the 8px inline margin, and a `ListHeader` action centers its
282
297
  fitted 36px square in a slot of the same size (a transparent hit layer extends
@@ -436,7 +451,12 @@ their shared track width rather than expanding across an otherwise empty row.
436
451
  It requires `minColumnWidth`; the default `auto-fit` keeps the existing form
437
452
  behavior. When Grid is a direct child of a flex-owned `ListInsetControl`, pass
438
453
  `flex` so the Grid takes the available row width instead of shrink-wrapping.
439
- Grid is layout-only and adds no ARIA grid role. The application owns child
454
+ Grid is layout-only by default and adds no ARIA grid role. With
455
+ `selectionMode="multiple"` and a required `ariaLabel`, it supplies a labeled
456
+ multi-select grid for `ContentItem selectionMode="multiple"` tiles. Their
457
+ row/gridcell structure can contain independent controls, while
458
+ `wireContentItems` follows the rendered tile positions with arrow keys.
459
+ The application owns child
440
460
  semantics.
441
461
  Use application-owned CSS grid for intrinsic, asymmetric, spanning, or masonry
442
462
  tracks, and use `ResizableRegion` when people must adjust a boundary.
package/docs/nav-stack.md CHANGED
@@ -57,6 +57,9 @@ mounted (so their DOM state survives) but hidden. Each entry carries a
57
57
  `leading`, `center`, and trailing `toolbar` groups for the top toolbar, and an
58
58
  optional per-view `bottomToolbar`. The component-level
59
59
  `bottomToolbar` remains a persistent fallback for views that do not provide one.
60
+ Each view can also provide `header`, pinned below its top toolbar and above
61
+ that view's single scroll area. A fixed heading, notice, or section selector
62
+ can therefore change with the active screen without adding another scroller.
60
63
  The back control appears automatically once the stack has more than one entry;
61
64
  `wireNavStack`'s `onBack` is where the app pops its own signal.
62
65
 
@@ -103,6 +106,10 @@ Configure the toolbar with `toolbarConfig` instead of styling it:
103
106
  text beside it (for example, the previous view's title); visible text names
104
107
  the control, so `backLabel` applies only to the icon-only control.
105
108
 
109
+ `panelToggle` supplies a persistent final group in the trailing zone of every
110
+ view's top toolbar. `Workbench` uses it for a navigation panel's standard
111
+ collapse toggle; the active view still owns the preceding toolbar groups.
112
+
106
113
  The catalog's **Configured toolbar** example shows these together: a level-2
107
114
  heading title over a bottom divider, leading and center groups on the root
108
115
  view, a trailing group on the pushed view, and `backText` set to the previous
@@ -364,6 +364,12 @@ smallest subtree that needs a different identity:
364
364
  }
365
365
  ```
366
366
 
367
+ `TokenSearchField` with `presentation="form-field"` uses
368
+ `--wa-form-control-border-radius` for its control corners and
369
+ `--wa-form-control-required-content-color` and
370
+ `--wa-form-control-required-content-offset` for its required marker. These
371
+ tokens keep it aligned with a neighboring `wa-input` when a form theme changes.
372
+
367
373
  The shipped theme pairs every `--wa-color-{variant}-fill-loud` with an
368
374
  `--wa-color-{variant}-on-loud` that clears WCAG AA (4.5:1) in both color
369
375
  schemes. When you override a loud fill, set its `on-loud` alongside it, in the
package/docs/workbench.md CHANGED
@@ -14,8 +14,8 @@ import { Workbench } from "@kerfjs/ui/workbench";
14
14
  In a browser bundler that honors the `browser` export condition (Vite, esbuild, and
15
15
  webpack do by default), the import above also
16
16
  loads Workbench's stylesheet and those of the components it renders internally
17
- (`Pane`, `Toolbar`, `ToolbarControlGroup`, `FloatingToolbar`, `ResizableRegion`,
18
- `List`, `LucideIcon`). Without that condition, import the manual stylesheets instead:
17
+ (`Pane`, `NavStack`, `Toolbar`, `ToolbarControlGroup`, `FloatingToolbar`,
18
+ `ResizableRegion`, `List`, `LucideIcon`). Without that condition, import the manual stylesheets instead:
19
19
  `@kerfjs/ui/workbench.css` plus those components' CSS, or `@kerfjs/ui/styles.css`.
20
20
 
21
21
  A top-level `Workbench` needs a definite containing height. Import the opt-in
@@ -71,7 +71,7 @@ const navCollapsed = signal(false);
71
71
  />;
72
72
  ```
73
73
 
74
- Each `WorkbenchPanel` takes `content`, an optional `toolbar` and `footer` (see
74
+ Each static `WorkbenchPanel` takes `content`, an optional `toolbar` and `footer` (see
75
75
  [Panel toolbars](#panel-toolbars)), an optional `collapsed`, an optional
76
76
  `size` (rail width or drawer height in px, overriding the CSS default —
77
77
  `--kui-workbench-rail-width` 280px, `--kui-workbench-drawer-height` 220px), and
@@ -165,6 +165,30 @@ into `.kui-resizable-region__content`.
165
165
 
166
166
  ## Panel toolbars
167
167
 
168
+ A panel with `navStack` hosts a controlled `NavStack` in place of the static
169
+ `content` and `Pane` slots. Give it a `toolbar` with a `toggle`; each
170
+ `navStack.views` entry then supplies its own title, leading/center/trailing
171
+ groups, fixed `header`, and scrolling `content`. The active view's toolbar is
172
+ the panel's only toolbar row, with the standard toggle always last. On
173
+ collapse, marked groups from the active view and the toggle relocate to the
174
+ work-area toolbar; the view stack stays mounted. The app owns the view array
175
+ and calls `wireNavStack` for animated push/pop and focus movement. See
176
+ [Navigation stack](nav-stack.md) and the catalog's navigation ticket rail.
177
+
178
+ ```tsx
179
+ <Workbench
180
+ id="tickets"
181
+ label="Tickets workspace"
182
+ main={<Workspace />}
183
+ rightRail={{
184
+ label: "Tickets",
185
+ collapsed: ticketsCollapsed.value,
186
+ toolbar: { toggle: { action: "toggle-tickets", name: "tickets" } },
187
+ navStack: { id: "ticket-stack", label: "Tickets", views: ticketViews.value },
188
+ }}
189
+ />
190
+ ```
191
+
168
192
  A panel's controls follow it open and closed when the Workbench composes the
169
193
  toolbars. Give a panel a `toolbar` and the work area a `mainToolbar` (and, for
170
194
  a drawer, optionally a `mainBottomToolbar`); the Workbench renders each as a
package/llms.txt CHANGED
@@ -203,6 +203,9 @@ headingLevel`, `TokenSearchField clearIcon`, `ContentItem frame="framed"` for a
203
203
  The app owns the outer width and child semantics; use custom CSS grid for
204
204
  asymmetric/intrinsic tracks and `ResizableRegion` for adjustable boundaries.
205
205
  `Grid.gap` and `Grid.flex` use the same typed grammars as Row.
206
+ For selectable document tiles, use `selectionMode="multiple"` and a required
207
+ `ariaLabel`, with `ContentItem selectionMode="multiple"` children. Wire
208
+ `wireContentItems` for spatial arrow navigation and Enter/Space activation.
206
209
  - **Give a layout root `fill`, not a frame `Pane`.** A Row, List, or Grid that
207
210
  is the sole layout root of a definite-height parent takes its full height
208
211
  with `fill` and carries safe `data-*` markers through `rootAttributes`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.64",
3
+ "version": "5.0.0-beta.66",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/grid.tsx"],"names":[],"mappings":";;;;AAUA,IAAM,uBAAA,uBAA8B,GAAA,CAAI;AAAA,EACtC,gBAAA;AAAA,EACA,cAAA;AAAA,EACA,uBAAA;AAAA,EACA,gBAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAC,CAAA;AAaD,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;AAuCO,SAAS,IAAA,CAAK;AAAA,EACnB,QAAA;AAAA,EACA,OAAA;AAAA,EACA,cAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,GAAA,GAAM,IAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,SAAA,GAAY,EAAA;AAAA,EACZ,iBAAiB,EAAC;AAAA,EAClB;AACF,CAAA,EAAc;AACZ,EAAA,IAAI,cAAA,KAAmB,MAAA,IAAa,OAAA,KAAY,MAAA,EAAW;AACzD,IAAA,MAAM,IAAI,UAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,QAAA,IAAY,mBAAmB,MAAA,EAAW;AAC5C,IAAA,MAAM,IAAI,WAAW,uCAAuC,CAAA;AAAA,EAC9D;AACA,EAAA,IACE,cAAA,KAAmB,MAAA,KAClB,OAAA,KAAY,MAAA,IAAa,CAAC,OAAO,aAAA,CAAc,OAAO,CAAA,IAAK,OAAA,GAAU,CAAA,CAAA,EACtE;AACA,IAAA,MAAM,IAAI,WAAW,8CAA8C,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,kBAAA,GAAqB,oBAAA;AAAA,IACzB,cAAA;AAAA,IACA;AAAA,GACF;AACA,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,OAAA,KAAY,MAAA,GAAY,EAAA,GAAK,CAAA,oBAAA,EAAuB,OAAO,CAAA,CAAA;AAAA,IAC3D,cAAA,KAAmB,MAAA,GACf,EAAA,GACA,CAAA,6BAAA,EAAgC,cAAc,CAAA,CAAA;AAAA,IAClD,mBAAmB,QAAQ,CAAA,CAAA;AAAA,IAC3B,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,MACE,GAAG,kBAAA;AAAA,MACJ,KAAA,EAAO,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACpC,gBAAA,EAAe,MAAA;AAAA,MACf,cAAA,EAAc,OAAA,KAAY,MAAA,GAAY,MAAA,GAAY,OAAO,OAAO,CAAA;AAAA,MAChE,uBAAA,EAAuB,cAAA,KAAmB,MAAA,GAAY,MAAA,GAAY,MAAA;AAAA,MAClE,gBAAA,EAAgB,WAAW,MAAA,GAAS,MAAA;AAAA,MACpC,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,KAAA;AAAA,MACA,IAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-2EKHENQK.js","sourcesContent":["import {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from './css-values.js';\nimport { filterDataAttributes } from './extension-attributes.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\nconst gridProtectedAttributes = new Set([\n 'data-component',\n 'data-columns',\n 'data-min-column-width',\n 'data-auto-fill',\n 'data-flex',\n 'data-fill',\n]);\n\ntype GridRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-columns'?: never;\n 'data-min-column-width'?: never;\n 'data-auto-fill'?: never;\n 'data-flex'?: never;\n 'data-fill'?: never;\n }\n>;\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\ninterface GridCommonProps {\n children?: KerfUiContent;\n /** A named UI spacing token or typed CSS length. Defaults to xs. */\n gap?: UiSpaceName | CssLength;\n /** Allow this grid to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /**\n * Fill the height of a parent with a definite height, such as an app root or\n * a fixed-height frame, when this grid is that parent's layout root. Inside a\n * flex layout use `flex` instead. Defaults to false.\n */\n fill?: boolean;\n className?: string;\n /** Safe `data-*` metadata; Grid-owned structural attributes remain protected. */\n rootAttributes?: GridRootAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\nexport type GridProps = GridCommonProps &\n (\n | {\n /** Number of equal-width columns. Must be a positive safe integer. */\n columns: number;\n minColumnWidth?: never;\n autoFill?: never;\n }\n | {\n columns?: never;\n /** Fit equal columns of at least this width; collapse as the container narrows. */\n minColumnWidth: CssLength;\n /** Keep empty tracks instead of stretching a sparse row. Defaults to false. */\n autoFill?: boolean;\n }\n );\n\n/** Render equal tracks with a fixed count or a responsive minimum width. */\nexport function Grid({\n children,\n columns,\n minColumnWidth,\n autoFill = false,\n gap = 'xs',\n flex = false,\n fill = false,\n className = '',\n rootAttributes = {},\n slot,\n}: GridProps) {\n if (minColumnWidth !== undefined && columns !== undefined) {\n throw new RangeError(\n 'Grid columns and minColumnWidth are mutually exclusive',\n );\n }\n if (autoFill && minColumnWidth === undefined) {\n throw new RangeError('Grid autoFill requires minColumnWidth');\n }\n if (\n minColumnWidth === undefined &&\n (columns === undefined || !Number.isSafeInteger(columns) || columns < 1)\n ) {\n throw new RangeError('Grid columns must be a positive safe integer');\n }\n\n const safeRootAttributes = filterDataAttributes(\n rootAttributes,\n gridProtectedAttributes,\n );\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 columns === undefined ? '' : `--_kui-grid-columns:${columns}`,\n minColumnWidth === undefined\n ? ''\n : `--_kui-grid-min-column-width:${minColumnWidth}`,\n `--_kui-grid-gap:${gapValue}`,\n flexValue ? `--_kui-grid-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n {...safeRootAttributes}\n class={`kui-grid ${className}`.trim()}\n data-component=\"grid\"\n data-columns={columns === undefined ? undefined : String(columns)}\n data-min-column-width={minColumnWidth === undefined ? undefined : 'true'}\n data-auto-fill={autoFill ? 'true' : undefined}\n data-flex={String(Boolean(flex))}\n data-fill={fill ? 'true' : undefined}\n style={style}\n slot={slot}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from './css-values.js';\n"]}