@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.
- package/ai/component-catalog.json +37 -15
- package/ai/component-composition.json +28 -4
- package/ai/components/content-item.md +4 -1
- package/ai/components/grid.md +3 -1
- package/ai/components/list.md +4 -2
- package/ai/components/nav-stack.md +4 -3
- package/ai/components/token-search-field.md +3 -0
- package/ai/components/workbench.md +5 -3
- package/ai/public-api-signatures-v1.md +66 -42
- package/ai/skill.md +4 -0
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/browser/catalog.js +2 -1
- package/dist/browser/workbench.js +4 -1
- package/dist/catalog-resources.d.ts +2 -0
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +3 -2
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-NSVLUDUW.js → chunk-5SYE4CCT.js} +25 -3
- package/dist/chunk-5SYE4CCT.js.map +1 -0
- package/dist/{chunk-2EKHENQK.js → chunk-B2ZMR7NY.js} +9 -1
- package/dist/chunk-B2ZMR7NY.js.map +1 -0
- package/dist/{chunk-IA4D5XNT.js → chunk-C74LITXE.js} +5 -5
- package/dist/{chunk-IA4D5XNT.js.map → chunk-C74LITXE.js.map} +1 -1
- package/dist/{chunk-SH6F3CCG.js → chunk-NCSWXG7G.js} +4 -4
- package/dist/{chunk-SH6F3CCG.js.map → chunk-NCSWXG7G.js.map} +1 -1
- package/dist/{chunk-M5H2RMCH.js → chunk-P4UXSYIJ.js} +16 -2
- package/dist/chunk-P4UXSYIJ.js.map +1 -0
- package/dist/{chunk-KFYANKJI.js → chunk-TGM424PI.js} +35 -5
- package/dist/chunk-TGM424PI.js.map +1 -0
- package/dist/chunk-VY6YRCTV.js +123 -0
- package/dist/chunk-VY6YRCTV.js.map +1 -0
- package/dist/{chunk-NAPE7WUS.js → chunk-W2AC6Q2A.js} +9 -1
- package/dist/chunk-W2AC6Q2A.js.map +1 -0
- package/dist/collapsible-panel.js +2 -2
- package/dist/content-item.d.ts +2 -2
- package/dist/content-item.js +1 -1
- package/dist/grid.d.ts +5 -1
- package/dist/grid.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/list.d.ts +5 -1
- package/dist/list.js +1 -1
- package/dist/nav-stack.d.ts +5 -1
- package/dist/nav-stack.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/styles/nav-stack.css +4 -0
- package/dist/styles/token-search-field.css +36 -0
- package/dist/styles/toolbar.css +5 -1
- package/dist/{token-search-field-Xk8jiw8Y.d.ts → token-search-field-CVGwDn2y.d.ts} +14 -4
- package/dist/token-search-field.d.ts +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/token-search-model.d.ts +1 -1
- package/dist/wire-content-items.d.ts +3 -1
- package/dist/wire-content-items.js +49 -1
- package/dist/wire-content-items.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +41 -33
- package/dist/workbench.js +4 -1
- package/docs/accessibility.md +26 -2
- package/docs/component-selection.md +16 -0
- package/docs/layout.md +24 -4
- package/docs/nav-stack.md +7 -0
- package/docs/webawesome-theme.md +6 -0
- package/docs/workbench.md +27 -3
- package/llms.txt +3 -0
- package/package.json +1 -1
- package/dist/chunk-2EKHENQK.js.map +0 -1
- package/dist/chunk-HXCMWANP.js +0 -116
- package/dist/chunk-HXCMWANP.js.map +0 -1
- package/dist/chunk-KFYANKJI.js.map +0 -1
- package/dist/chunk-M5H2RMCH.js.map +0 -1
- package/dist/chunk-NAPE7WUS.js.map +0 -1
- 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-
|
|
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,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-
|
|
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"]}
|
package/dist/workbench.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
84
|
-
interface
|
|
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
package/docs/accessibility.md
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
222
|
-
|
|
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.
|
|
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
|
package/docs/webawesome-theme.md
CHANGED
|
@@ -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`,
|
|
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 +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"]}
|