@kerfjs/ui 5.0.0-beta.62 → 5.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/ai/component-catalog-extension.schema.json +13 -0
- package/ai/component-catalog.json +13 -2
- package/ai/component-composition.json +15 -2
- package/ai/components/list-item.md +1 -1
- package/ai/components/workbench.md +4 -2
- package/ai/public-api-signatures-v1.md +151 -44
- package/ai/skill.md +9 -5
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/index.mjs +3 -2
- package/catalog-projection-cli.mjs +96 -0
- package/catalog-projection.d.mts +55 -0
- package/catalog-projection.mjs +62 -0
- package/dist/browser/catalog.js +1 -1
- package/dist/browser/collapsible-panel.js +2 -0
- package/dist/browser/nav-stack.js +1 -0
- package/dist/browser/split-view.js +1 -0
- package/dist/browser/toolbar-control-group.js +1 -0
- package/dist/browser/workbench.js +1 -0
- package/dist/catalog-resources.d.ts +1 -1
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +11 -10
- package/dist/catalog.js.map +1 -1
- package/dist/chip.d.ts +9 -3
- package/dist/chip.js +1 -1
- package/dist/{chunk-UCKKT6KO.js → chunk-2EKHENQK.js} +7 -1
- package/dist/chunk-2EKHENQK.js.map +1 -0
- package/dist/{chunk-OD2VXAPL.js → chunk-3BYFWTP3.js} +4 -3
- package/dist/chunk-3BYFWTP3.js.map +1 -0
- package/dist/{chunk-5Q3N6EV7.js → chunk-5IJ6UGNZ.js} +3 -3
- package/dist/{chunk-5Q3N6EV7.js.map → chunk-5IJ6UGNZ.js.map} +1 -1
- package/dist/{chunk-KROD7KZK.js → chunk-AUVG54KC.js} +2 -2
- package/dist/{chunk-KROD7KZK.js.map → chunk-AUVG54KC.js.map} +1 -1
- package/dist/{chunk-AUKT3KMG.js → chunk-CQHN45XR.js} +17 -2
- package/dist/chunk-CQHN45XR.js.map +1 -0
- package/dist/{chunk-SWVW56CV.js → chunk-EN4LDR5Y.js} +2 -2
- package/dist/{chunk-SWVW56CV.js.map → chunk-EN4LDR5Y.js.map} +1 -1
- package/dist/{chunk-4MG3KXJM.js → chunk-F7OCOBTG.js} +2 -2
- package/dist/{chunk-4MG3KXJM.js.map → chunk-F7OCOBTG.js.map} +1 -1
- package/dist/{chunk-KYDDCJXU.js → chunk-HXCMWANP.js} +4 -4
- package/dist/{chunk-KYDDCJXU.js.map → chunk-HXCMWANP.js.map} +1 -1
- package/dist/{chunk-ENGVYQPG.js → chunk-IA4D5XNT.js} +60 -10
- package/dist/chunk-IA4D5XNT.js.map +1 -0
- package/dist/{chunk-F4QMLOXJ.js → chunk-IAOAZUOO.js} +2 -2
- package/dist/{chunk-F4QMLOXJ.js.map → chunk-IAOAZUOO.js.map} +1 -1
- package/dist/{chunk-VCBTX62E.js → chunk-J3KGJ6P6.js} +2 -2
- package/dist/chunk-J3KGJ6P6.js.map +1 -0
- package/dist/{chunk-X6ADHJNI.js → chunk-KFYANKJI.js} +25 -6
- package/dist/chunk-KFYANKJI.js.map +1 -0
- package/dist/{chunk-A3CKOW2R.js → chunk-M5H2RMCH.js} +165 -26
- package/dist/chunk-M5H2RMCH.js.map +1 -0
- package/dist/chunk-MOSIE4Z2.js +51 -0
- package/dist/chunk-MOSIE4Z2.js.map +1 -0
- package/dist/{chunk-RRB26JRJ.js → chunk-NSVLUDUW.js} +19 -4
- package/dist/chunk-NSVLUDUW.js.map +1 -0
- package/dist/{chunk-7JUBXOIV.js → chunk-NXWE5S57.js} +25 -6
- package/dist/chunk-NXWE5S57.js.map +1 -0
- package/dist/chunk-RGS4WX2M.js +44 -0
- package/dist/chunk-RGS4WX2M.js.map +1 -0
- package/dist/chunk-SH6F3CCG.js +80 -0
- package/dist/chunk-SH6F3CCG.js.map +1 -0
- package/dist/{chunk-EAC4CDY7.js → chunk-SMZGAPCU.js} +15 -2
- package/dist/chunk-SMZGAPCU.js.map +1 -0
- package/dist/{chunk-NNMLEDMD.js → chunk-TJK44RQC.js} +64 -11
- package/dist/chunk-TJK44RQC.js.map +1 -0
- package/dist/{chunk-6ZXOQEAN.js → chunk-WF75U3V4.js} +7 -1
- package/dist/chunk-WF75U3V4.js.map +1 -0
- package/dist/collapsible-panel.d.ts +18 -7
- package/dist/collapsible-panel.js +19 -3
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/content-item.d.ts +40 -5
- package/dist/content-item.js +1 -1
- package/dist/disclosure-arrow.js +2 -1
- package/dist/grid.d.ts +5 -1
- package/dist/grid.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +20 -20
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-item.js +1 -1
- package/dist/lucide-icon.d.ts +5 -2
- package/dist/lucide-icon.js +2 -1
- package/dist/nav-stack.js +2 -1
- package/dist/pane.d.ts +2 -1
- package/dist/pane.js +1 -1
- package/dist/{panel-toolbar-DqwtBxQL.d.ts → panel-toolbar-DDKh4uKd.d.ts} +11 -2
- package/dist/popup-menu-register.js +216 -3
- package/dist/popup-menu-register.js.map +1 -1
- package/dist/popup-menu.d.ts +4 -3
- package/dist/popup-menu.js +1 -1
- package/dist/select.js +1 -1
- package/dist/split-view.js +2 -1
- package/dist/split-view.js.map +1 -1
- package/dist/styles/catalog/components/catalog-example.css +4 -0
- package/dist/styles/chip.css +14 -0
- package/dist/styles/content-item.css +45 -0
- package/dist/styles/grid.css +7 -0
- package/dist/styles/list-action-row.css +4 -3
- package/dist/styles/list-header.css +4 -3
- package/dist/styles/list-item.css +21 -11
- package/dist/styles/lucide-icon.css +5 -0
- package/dist/styles/pane.css +3 -2
- package/dist/styles/popup-menu.css +24 -10
- package/dist/styles/segmented-control.css +3 -0
- package/dist/styles/select.css +11 -0
- package/dist/styles/sunken-panel.css +7 -0
- package/dist/styles/text.css +29 -0
- package/dist/styles/token-search-field.css +51 -11
- package/dist/styles/toolbar-control-group.css +49 -0
- package/dist/styles/workbench.css +130 -2
- package/dist/sunken-panel.d.ts +6 -1
- package/dist/sunken-panel.js +1 -1
- package/dist/text.d.ts +14 -3
- package/dist/text.js +1 -1
- package/dist/{token-search-field-DvgFLhLL.d.ts → token-search-field-Xk8jiw8Y.d.ts} +18 -4
- package/dist/token-search-field.d.ts +1 -1
- package/dist/token-search-field.js +2 -1
- package/dist/token-search-model.d.ts +1 -1
- package/dist/toolbar-control-group.d.ts +5 -1
- package/dist/toolbar-control-group.js +2 -1
- package/dist/toolbar.d.ts +5 -2
- package/dist/toolbar.js +1 -1
- package/dist/wire-catalog.js +1 -1
- package/dist/wire-content-items.d.ts +9 -0
- package/dist/wire-content-items.js +66 -0
- package/dist/wire-content-items.js.map +1 -0
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +2 -1
- package/dist/wire-workbench.d.ts +7 -0
- package/dist/wire-workbench.js +1 -1
- package/dist/workbench.d.ts +24 -8
- package/dist/workbench.js +2 -1
- package/docs/accessibility.md +30 -6
- package/docs/catalog.md +42 -2
- package/docs/collapsible-panel.md +8 -1
- package/docs/component-contract.md +43 -5
- package/docs/component-selection.md +46 -8
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/examples/component-catalog-extension.json +6 -0
- package/docs/layout.md +56 -9
- package/docs/sunken-panel.md +22 -0
- package/docs/surface-scaffold.md +6 -3
- package/docs/ui-analyzer.md +4 -0
- package/docs/ux-demo.md +1 -0
- package/docs/workbench.md +42 -4
- package/llms.txt +9 -5
- package/package.json +16 -2
- package/scripts/component-catalog-extension-validation.mjs +28 -0
- package/dist/chunk-4R777DUZ.js +0 -34
- package/dist/chunk-4R777DUZ.js.map +0 -1
- package/dist/chunk-6ZXOQEAN.js.map +0 -1
- package/dist/chunk-7JUBXOIV.js.map +0 -1
- package/dist/chunk-A3CKOW2R.js.map +0 -1
- package/dist/chunk-AUKT3KMG.js.map +0 -1
- package/dist/chunk-EAC4CDY7.js.map +0 -1
- package/dist/chunk-ENGVYQPG.js.map +0 -1
- package/dist/chunk-MA73TA6D.js +0 -42
- package/dist/chunk-MA73TA6D.js.map +0 -1
- package/dist/chunk-N2DRU35S.js +0 -45
- package/dist/chunk-N2DRU35S.js.map +0 -1
- package/dist/chunk-NNMLEDMD.js.map +0 -1
- package/dist/chunk-OD2VXAPL.js.map +0 -1
- package/dist/chunk-RRB26JRJ.js.map +0 -1
- package/dist/chunk-UCKKT6KO.js.map +0 -1
- package/dist/chunk-VCBTX62E.js.map +0 -1
- package/dist/chunk-X6ADHJNI.js.map +0 -1
package/dist/text.js
CHANGED
|
@@ -60,6 +60,12 @@ interface TokenSearchToken {
|
|
|
60
60
|
offset?: number;
|
|
61
61
|
accessibleLabel?: string;
|
|
62
62
|
}
|
|
63
|
+
interface TokenSearchTrailingAction {
|
|
64
|
+
icon: SafeHtml;
|
|
65
|
+
label: string;
|
|
66
|
+
action: string;
|
|
67
|
+
id?: string;
|
|
68
|
+
}
|
|
63
69
|
type TokenSearchEditorAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
|
|
64
70
|
'data-component'?: never;
|
|
65
71
|
'data-key'?: never;
|
|
@@ -88,8 +94,9 @@ interface TokenSearchFieldBaseProps {
|
|
|
88
94
|
tokenPlaceholder?: string;
|
|
89
95
|
disabled?: boolean;
|
|
90
96
|
autofocus?: boolean;
|
|
97
|
+
/** Let an expanded collapsible field consume the available inline width. */
|
|
98
|
+
fill?: boolean;
|
|
91
99
|
leading?: KerfUiContent;
|
|
92
|
-
trailing?: KerfUiContent;
|
|
93
100
|
/** Standalone field chrome or the inset visual layer of a configured toolbar group. */
|
|
94
101
|
presentation?: 'standalone' | 'toolbar-group';
|
|
95
102
|
editAction?: string;
|
|
@@ -103,6 +110,13 @@ interface TokenSearchFieldBaseProps {
|
|
|
103
110
|
/** Native named-slot assignment when composed inside a web component. */
|
|
104
111
|
slot?: string;
|
|
105
112
|
}
|
|
113
|
+
type TokenSearchTrailingProps = {
|
|
114
|
+
trailing?: KerfUiContent;
|
|
115
|
+
trailingAction?: never;
|
|
116
|
+
} | {
|
|
117
|
+
trailing?: never;
|
|
118
|
+
trailingAction: TokenSearchTrailingAction;
|
|
119
|
+
};
|
|
106
120
|
type TokenSearchCollapsibleProps = {
|
|
107
121
|
/** Allow an empty field to render as one iconic action. */
|
|
108
122
|
collapsible: true;
|
|
@@ -116,15 +130,15 @@ type TokenSearchCollapsibleProps = {
|
|
|
116
130
|
expandAction?: never;
|
|
117
131
|
expandLabel?: never;
|
|
118
132
|
};
|
|
119
|
-
type TokenSearchFieldProps = TokenSearchFieldBaseProps & TokenSearchCollapsibleProps;
|
|
133
|
+
type TokenSearchFieldProps = TokenSearchFieldBaseProps & TokenSearchCollapsibleProps & TokenSearchTrailingProps;
|
|
120
134
|
interface TokenSearchFieldValue {
|
|
121
135
|
query: string;
|
|
122
136
|
tokens: TokenSearchToken[];
|
|
123
137
|
}
|
|
124
|
-
declare function TokenSearchField({ id, label, query, tokens, model, revision, placeholder, tokenPlaceholder, disabled, autofocus, collapsible, expanded, expandAction, expandLabel, leading, trailing, presentation, editAction, removeAction, clearAction, clearLabel, clearIcon, className, editorAttributes, slot, }: TokenSearchFieldProps): SafeHtml;
|
|
138
|
+
declare function TokenSearchField({ id, label, query, tokens, model, revision, placeholder, tokenPlaceholder, disabled, autofocus, fill, collapsible, expanded, expandAction, expandLabel, leading, trailing, trailingAction, presentation, editAction, removeAction, clearAction, clearLabel, clearIcon, className, editorAttributes, slot, }: TokenSearchFieldProps): SafeHtml;
|
|
125
139
|
/** Read editable text and ordered token offsets from a rendered TokenSearchField editor. */
|
|
126
140
|
declare function readTokenSearchField(editor: HTMLElement, knownTokens?: readonly TokenSearchToken[]): TokenSearchFieldValue;
|
|
127
141
|
/** Focus an editor and place its caret at a text offset, skipping atomic token chips. */
|
|
128
142
|
declare function placeTokenSearchCaret(editor: HTMLElement, offset?: number): void;
|
|
129
143
|
|
|
130
|
-
export { type TokenSearchEditorAttributes as T, TokenSearchField as a, type TokenSearchFieldProps as b, type TokenSearchFieldValue as c, type TokenSearchModel as d, type TokenSearchModelOptions as e, type TokenSearchResolvedToken as f, type TokenSearchRule as g, type TokenSearchState as h, type TokenSearchSuggestion as i, type TokenSearchToken as j,
|
|
144
|
+
export { type TokenSearchEditorAttributes as T, TokenSearchField as a, type TokenSearchFieldProps as b, type TokenSearchFieldValue as c, type TokenSearchModel as d, type TokenSearchModelOptions as e, type TokenSearchResolvedToken as f, type TokenSearchRule as g, type TokenSearchState as h, type TokenSearchSuggestion as i, type TokenSearchToken as j, type TokenSearchTrailingAction as k, createTokenSearchModel as l, placeTokenSearchCaret as p, readTokenSearchField as r };
|
|
@@ -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, 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-Xk8jiw8Y.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,
|
|
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';
|
|
3
3
|
import './semantic-content-BbzjvSu9.js';
|
|
@@ -60,6 +60,10 @@ interface ToolbarControlGroupProps {
|
|
|
60
60
|
relocateOnCollapse?: boolean;
|
|
61
61
|
/** Add contrast behind photo-backed avatar content. */
|
|
62
62
|
scrim?: boolean;
|
|
63
|
+
/** Block the group's controls and show a spinner without changing its dimensions. */
|
|
64
|
+
busy?: boolean;
|
|
65
|
+
/** Announced status while busy; defaults to “Working”. */
|
|
66
|
+
busyLabel?: string;
|
|
63
67
|
/**
|
|
64
68
|
* Avatar image URL. A single-control group paints it on the group; a
|
|
65
69
|
* multi-control group paints it only on the pressed selection highlight.
|
|
@@ -68,6 +72,6 @@ interface ToolbarControlGroupProps {
|
|
|
68
72
|
/** Native named-slot assignment when composed inside a web component. */
|
|
69
73
|
slot?: string;
|
|
70
74
|
}
|
|
71
|
-
declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, relocateOnCollapse, scrim, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
|
|
75
|
+
declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
|
|
72
76
|
|
|
73
77
|
export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
|
package/dist/toolbar.d.ts
CHANGED
|
@@ -3,11 +3,14 @@ import { PaneSeparatorSide } from './pane.js';
|
|
|
3
3
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
4
4
|
import { S as Sides } from './sides-BPSWde0A.js';
|
|
5
5
|
|
|
6
|
+
type ToolbarPosition = 'header' | 'footer';
|
|
6
7
|
interface ToolbarProps {
|
|
7
8
|
leading?: KerfUiContent;
|
|
8
9
|
center?: KerfUiContent;
|
|
9
10
|
trailing?: KerfUiContent;
|
|
10
11
|
label?: string;
|
|
12
|
+
/** Use footer semantics for a bottom toolbar; defaults to header. */
|
|
13
|
+
position?: ToolbarPosition;
|
|
11
14
|
/**
|
|
12
15
|
* Physical divider edges in canonical top/right/bottom/left order, drawn
|
|
13
16
|
* always. Defaults to none: a toolbar pinned over or under scrolling content
|
|
@@ -57,6 +60,6 @@ interface ToolbarProps {
|
|
|
57
60
|
* toolbar) forwards, so the app configures that toolbar instead of styling it.
|
|
58
61
|
*/
|
|
59
62
|
type ToolbarConfig = Pick<ToolbarProps, 'dividerSides' | 'centerAlign' | 'responsive' | 'responsiveAt' | 'safeAreaEdges'>;
|
|
60
|
-
declare function Toolbar({ leading, center, trailing, label, dividerSides, centerAlign, responsive, responsiveAt, safeAreaEdges, className, slot, }: ToolbarProps): kerfjs.SafeHtml;
|
|
63
|
+
declare function Toolbar({ leading, center, trailing, label, position, dividerSides, centerAlign, responsive, responsiveAt, safeAreaEdges, className, slot, }: ToolbarProps): kerfjs.SafeHtml;
|
|
61
64
|
|
|
62
|
-
export { Sides, Toolbar, type ToolbarConfig, type ToolbarProps };
|
|
65
|
+
export { Sides, Toolbar, type ToolbarConfig, type ToolbarPosition, type ToolbarProps };
|
package/dist/toolbar.js
CHANGED
package/dist/wire-catalog.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Give interactive ContentItems native-like Enter/Space activation with one
|
|
3
|
+
* delegated listener pair. Space activates on release and is cancelled if
|
|
4
|
+
* focus moves; the item's own `data-action` still goes through the app's
|
|
5
|
+
* ordinary delegated click handler. Returns a disposer.
|
|
6
|
+
*/
|
|
7
|
+
declare function wireContentItems(root: HTMLElement): () => void;
|
|
8
|
+
|
|
9
|
+
export { wireContentItems };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/wire-content-items.ts
|
|
2
|
+
var ITEM_SELECTOR = '[data-component="content-item"][data-interactive="true"]';
|
|
3
|
+
var NESTED_CONTROL_SELECTOR = 'button, a[href], input, select, textarea, [contenteditable="true"], [role="button"]';
|
|
4
|
+
function actionItem(root, event) {
|
|
5
|
+
const target = event.target;
|
|
6
|
+
if (!(target instanceof Element)) return null;
|
|
7
|
+
const item = target.closest(ITEM_SELECTOR);
|
|
8
|
+
if (!item || !root.contains(item) || item.dataset.disabled === "true")
|
|
9
|
+
return null;
|
|
10
|
+
for (const node of event.composedPath()) {
|
|
11
|
+
if (node === item) break;
|
|
12
|
+
if (node instanceof Element && node.matches(NESTED_CONTROL_SELECTOR))
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return item;
|
|
16
|
+
}
|
|
17
|
+
function wireContentItems(root) {
|
|
18
|
+
let pendingSpace = null;
|
|
19
|
+
const clearSpace = () => {
|
|
20
|
+
pendingSpace?.removeAttribute("data-kui-pressed");
|
|
21
|
+
pendingSpace = null;
|
|
22
|
+
};
|
|
23
|
+
const keydown = (event) => {
|
|
24
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
|
|
25
|
+
return;
|
|
26
|
+
const item = actionItem(root, event);
|
|
27
|
+
if (!item) return;
|
|
28
|
+
if (event.key === "Enter") {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
item.click();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (event.key === " " || event.key === "Spacebar") {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
if (event.repeat) return;
|
|
36
|
+
clearSpace();
|
|
37
|
+
pendingSpace = item;
|
|
38
|
+
item.setAttribute("data-kui-pressed", "true");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const keyup = (event) => {
|
|
42
|
+
if (event.key !== " " && event.key !== "Spacebar") return;
|
|
43
|
+
const item = pendingSpace;
|
|
44
|
+
if (!item) return;
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
clearSpace();
|
|
47
|
+
if (item === actionItem(root, event) && item.ownerDocument.activeElement === item)
|
|
48
|
+
item.click();
|
|
49
|
+
};
|
|
50
|
+
const focusout = (event) => {
|
|
51
|
+
if (event.target === pendingSpace) clearSpace();
|
|
52
|
+
};
|
|
53
|
+
root.addEventListener("keydown", keydown);
|
|
54
|
+
root.addEventListener("keyup", keyup);
|
|
55
|
+
root.addEventListener("focusout", focusout);
|
|
56
|
+
return () => {
|
|
57
|
+
clearSpace();
|
|
58
|
+
root.removeEventListener("keydown", keydown);
|
|
59
|
+
root.removeEventListener("keyup", keyup);
|
|
60
|
+
root.removeEventListener("focusout", focusout);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { wireContentItems };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=wire-content-items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wire-content-items.ts"],"names":[],"mappings":";AAAA,IAAM,aAAA,GACJ,0DAAA;AACF,IAAM,uBAAA,GACJ,qFAAA;AAEF,SAAS,UAAA,CAAW,MAAmB,KAAA,EAAc;AACnD,EAAA,MAAM,SAAS,KAAA,CAAM,MAAA;AACrB,EAAA,IAAI,EAAE,MAAA,YAAkB,OAAA,CAAA,EAAU,OAAO,IAAA;AACzC,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,OAAA,CAAqB,aAAa,CAAA;AACtD,EAAA,IAAI,CAAC,QAAQ,CAAC,IAAA,CAAK,SAAS,IAAI,CAAA,IAAK,IAAA,CAAK,OAAA,CAAQ,QAAA,KAAa,MAAA;AAC7D,IAAA,OAAO,IAAA;AAGT,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,YAAA,EAAa,EAAG;AACvC,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,IAAI,IAAA,YAAgB,OAAA,IAAW,IAAA,CAAK,OAAA,CAAQ,uBAAuB,CAAA;AACjE,MAAA,OAAO,IAAA;AAAA,EACX;AACA,EAAA,OAAO,IAAA;AACT;AAQO,SAAS,iBAAiB,IAAA,EAA+B;AAC9D,EAAA,IAAI,YAAA,GAAmC,IAAA;AAEvC,EAAA,MAAM,aAAa,MAAM;AACvB,IAAA,YAAA,EAAc,gBAAgB,kBAAkB,CAAA;AAChD,IAAA,YAAA,GAAe,IAAA;AAAA,EACjB,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAAyB;AACxC,IAAA,IACE,MAAM,gBAAA,IACN,KAAA,CAAM,MAAA,IACN,KAAA,CAAM,WACN,KAAA,CAAM,OAAA;AAEN,MAAA;AACF,IAAA,MAAM,IAAA,GAAO,UAAA,CAAW,IAAA,EAAM,KAAK,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,IAAI,KAAA,CAAM,QAAQ,OAAA,EAAS;AACzB,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,IAAA,CAAK,KAAA,EAAM;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,GAAA,IAAO,KAAA,CAAM,QAAQ,UAAA,EAAY;AACjD,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,IAAI,MAAM,MAAA,EAAQ;AAClB,MAAA,UAAA,EAAW;AACX,MAAA,YAAA,GAAe,IAAA;AACf,MAAA,IAAA,CAAK,YAAA,CAAa,oBAAoB,MAAM,CAAA;AAAA,IAC9C;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAC,KAAA,KAAyB;AACtC,IAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,GAAA,IAAO,KAAA,CAAM,QAAQ,UAAA,EAAY;AACnD,IAAA,MAAM,IAAA,GAAO,YAAA;AACb,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,UAAA,EAAW;AACX,IAAA,IACE,SAAS,UAAA,CAAW,IAAA,EAAM,KAAK,CAAA,IAC/B,IAAA,CAAK,cAAc,aAAA,KAAkB,IAAA;AAErC,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAsB;AACtC,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,YAAA,EAAc,UAAA,EAAW;AAAA,EAChD,CAAA;AAEA,EAAA,IAAA,CAAK,gBAAA,CAAiB,WAAW,OAAO,CAAA;AACxC,EAAA,IAAA,CAAK,gBAAA,CAAiB,SAAS,KAAK,CAAA;AACpC,EAAA,IAAA,CAAK,gBAAA,CAAiB,YAAY,QAAQ,CAAA;AAC1C,EAAA,OAAO,MAAM;AACX,IAAA,UAAA,EAAW;AACX,IAAA,IAAA,CAAK,mBAAA,CAAoB,WAAW,OAAO,CAAA;AAC3C,IAAA,IAAA,CAAK,mBAAA,CAAoB,SAAS,KAAK,CAAA;AACvC,IAAA,IAAA,CAAK,mBAAA,CAAoB,YAAY,QAAQ,CAAA;AAAA,EAC/C,CAAA;AACF","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"]}
|
package/dist/wire-workbench.d.ts
CHANGED
|
@@ -32,6 +32,13 @@ interface WireWorkbenchPanel {
|
|
|
32
32
|
* keeps Tab inside it, and it closes on Escape or a press outside it.
|
|
33
33
|
*/
|
|
34
34
|
collapsed?: Signal<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Treat presses in a portal surface launched by this panel (for example a
|
|
37
|
+
* body-level menu or dialog) as inside it. The predicate receives each Node
|
|
38
|
+
* in the press's composed path; return true for the portal root. Ordinary
|
|
39
|
+
* presses outside the panel and its allowed surfaces still dismiss it.
|
|
40
|
+
*/
|
|
41
|
+
keepOpenOn?: (target: Node) => boolean;
|
|
35
42
|
}
|
|
36
43
|
/** A resize the user made, after `wireWorkbench` wrote it to the size signal. */
|
|
37
44
|
interface WorkbenchResize {
|
package/dist/wire-workbench.js
CHANGED
package/dist/workbench.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ListConfig } from './list.js';
|
|
|
3
3
|
import { PaneConfig } from './pane.js';
|
|
4
4
|
import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
|
|
5
5
|
import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
|
|
6
|
-
import { a as PanelToolbar, b as PanelToggle } from './panel-toolbar-
|
|
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
8
|
import './css-values.js';
|
|
9
9
|
import './flex-alignment-4ms8ZbV8.js';
|
|
@@ -22,6 +22,7 @@ type WorkbenchPanelToggle = PanelToggle;
|
|
|
22
22
|
* `FloatingToolbar` in the work area's bottom-end corner.
|
|
23
23
|
*/
|
|
24
24
|
type WorkbenchPanelToolbar = PanelToolbar;
|
|
25
|
+
type WorkbenchPanelBottomToolbar = PanelBottomToolbar;
|
|
25
26
|
/**
|
|
26
27
|
* The work area's top toolbar; collapsed rails add their groups to it. Its
|
|
27
28
|
* configuration forwards to its `Toolbar`. It draws no divider of its own by
|
|
@@ -57,7 +58,7 @@ interface WorkbenchMainBottomToolbar extends ToolbarConfig {
|
|
|
57
58
|
* the Pane's `chromePlacement="auto"`; it applies to the pinned header and
|
|
58
59
|
* footer together).
|
|
59
60
|
*/
|
|
60
|
-
type WorkbenchChromePlacement =
|
|
61
|
+
type WorkbenchChromePlacement = PanelChromePlacement;
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* The Workbench container breakpoint below which a panel presents as an
|
|
@@ -65,7 +66,7 @@ type WorkbenchChromePlacement = 'fixed' | 'scroll' | 'auto';
|
|
|
65
66
|
* breakpoints as `ResizableRegion`'s `responsiveFillAt` — or `never` to keep
|
|
66
67
|
* it inline at every width.
|
|
67
68
|
*/
|
|
68
|
-
type WorkbenchResponsiveOverlayAt = 'compact' | 'narrow' | 'never';
|
|
69
|
+
type WorkbenchResponsiveOverlayAt = 'compact' | 'narrow' | 'never' | number;
|
|
69
70
|
/**
|
|
70
71
|
* How wide a rail's overlay is in a compact (448px or less) Workbench: `inset`
|
|
71
72
|
* fills the Workbench less a dismiss margin on the side away from the rail's
|
|
@@ -88,8 +89,20 @@ interface WorkbenchPanel {
|
|
|
88
89
|
* collapsed. With it, `content` renders in a `Pane` below the toolbar.
|
|
89
90
|
*/
|
|
90
91
|
toolbar?: WorkbenchPanelToolbar;
|
|
91
|
-
/**
|
|
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. */
|
|
92
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;
|
|
93
106
|
/**
|
|
94
107
|
* Configuration for a `toolbar` panel's `Pane` (`contentElement`,
|
|
95
108
|
* `contentLabel`, `separators`, `safeAreaEdges`, `chromeDividers`) — for example
|
|
@@ -121,8 +134,9 @@ interface WorkbenchPanel {
|
|
|
121
134
|
presentation?: ResizableRegionPresentation;
|
|
122
135
|
/**
|
|
123
136
|
* Present the panel as an overlay, without a separator, below a Workbench
|
|
124
|
-
* container breakpoint, and inline above it
|
|
125
|
-
*
|
|
137
|
+
* container breakpoint, and inline above it. `compact` and `narrow` use
|
|
138
|
+
* CSS container queries; a positive number is a pixel threshold measured
|
|
139
|
+
* by `wireWorkbench` and requires a `collapsed` signal. A rail overlays from its side at full
|
|
126
140
|
* height, over the work area and an open drawer; the bottom drawer
|
|
127
141
|
* overlays the bottom of the work-area column. Rails default to `narrow`
|
|
128
142
|
* (pass `never` to keep one inline); the drawer defaults to inline.
|
|
@@ -222,6 +236,8 @@ interface WorkbenchProps {
|
|
|
222
236
|
* shorter. Workbenches without a drawer ignore it.
|
|
223
237
|
*/
|
|
224
238
|
mainMinHeight?: number;
|
|
239
|
+
/** Dim the work area below any open overlay and absorb outside presses; off by default. */
|
|
240
|
+
overlayBackdrop?: boolean;
|
|
225
241
|
className?: string;
|
|
226
242
|
/** Native named-slot assignment when composed inside a web component. */
|
|
227
243
|
slot?: string;
|
|
@@ -239,6 +255,6 @@ interface WorkbenchProps {
|
|
|
239
255
|
* lives outside it and stays reachable). A panel may opt in to drag and keyboard resizing with
|
|
240
256
|
* `resizable`, which `wireWorkbench` drives. See `docs/23-app-layouts.md` §3.3.
|
|
241
257
|
*/
|
|
242
|
-
declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainPane, mainHeaderList, mainFooterList, mainMinSize, mainMinHeight, className, slot, }: WorkbenchProps): SafeHtml;
|
|
258
|
+
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;
|
|
243
259
|
|
|
244
|
-
export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchPanel, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt };
|
|
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 };
|
package/dist/workbench.js
CHANGED
package/docs/accessibility.md
CHANGED
|
@@ -5,7 +5,7 @@ These are normative contracts for package components and consuming applications.
|
|
|
5
5
|
## Shared rules
|
|
6
6
|
|
|
7
7
|
- All controls have accessible names and visible `:focus-visible` treatment.
|
|
8
|
-
- Decorative `LucideIcon` output is `aria-hidden`; pass `label` only when the SVG itself conveys meaning.
|
|
8
|
+
- Decorative `LucideIcon` output is `aria-hidden`; pass `label` only when the SVG itself conveys meaning. `size="xs|s|m|l|xl"` selects the 12/16/20/24/32px root-scaled icon steps, and a positive numeric `size` converts pixels to rem; omitting `size` keeps 1em. Size changes the visual only, never the owning control's hit target or accessible name.
|
|
9
9
|
- `ListItem` uses native button Enter/Space behavior. `selected` maps to `aria-current="page"`; `pressed` maps to `aria-pressed`. Selected rows keep the normal foreground over their brand-tinted fill so text retains WCAG AA contrast in light and dark themes. Its leading icon visual is 18px while the row retains its 44px minimum target; multiline icons align to the first text line. Its `trailing` content is dormant and must not contain controls. Its `rootAttributes` slot accepts only application `data-*` metadata and deliberately cannot emit `role="menuitem"` in isolation—a true ARIA menu must own arrow, Home/End, Escape, and focus behavior as one widget.
|
|
10
10
|
- `ListItemLink` shares that row content and geometry but uses native anchor Enter behavior and requires `href`. It has no `action` or `pressed` state. `external` opens a new tab with `rel="noopener noreferrer"`; the default accessible name announces the new tab for a string label, so an explicit `accessibleLabel` should do the same. Disabled and loading links omit `href`, expose `aria-disabled`, and leave the tab order. Its trailing content remains dormant.
|
|
11
11
|
- `ListHeader` owns action naming, disabled state, and disclosure `aria-expanded`. Toggle mode supplies one decorative 18px `DisclosureArrow` when `actionIcon` is omitted; it mirrors `expanded` while the native button's accessible name stays stable. The application must update that controlled state and reveal or hide real content. A valid `count` is visually rendered in an `aria-hidden` neutral pill while the required localized `countLabel` becomes part of the owning heading or disclosure button's accessible name; zero remains a real count. Its narrow `triggerAttributes` slot may describe a native popover or controlled-content relationship with `popoverTarget`, `popoverTargetAction`, `aria-controls`, and `aria-haspopup`; the application owns the target surface and its focus/dismissal behavior.
|
|
@@ -13,9 +13,12 @@ These are normative contracts for package components and consuming applications.
|
|
|
13
13
|
- `wireNavStack` moves focus into every newly active top view and remembers the last focused descendant of each mounted view for pop restoration. Mark a preferred initial heading or control with `data-nav-focus` (and `tabindex="-1"` when it is not normally tabbable); otherwise the helper uses the first enabled focusable descendant, then a programmatically focusable view-container fallback.
|
|
14
14
|
- In any `.kui-pane`, menu rows, `ListHeader` actions, and toolbar groups keep a minimum 44px target in both dimensions. A `ListHeader` fills the available inline width and keeps that action at the logical end, including in RTL. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square; a transparent hit layer extends the pointer target to at least 44 × 44 around that square without moving layout or enlarging the painted surface. Compact headers are 36px tall like the rest of the compact List family, so their action target is 44px wide and clamped to the 36px header height instead of reaching into a neighboring row. Do not reduce the target to the visible icon, and do not force the button itself to 44px.
|
|
15
15
|
- `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
|
|
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
|
+
- 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.
|
|
16
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.
|
|
17
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.
|
|
18
|
-
- 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. Tag text remains visible because tone does not convey meaning alone.
|
|
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.
|
|
19
22
|
- `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
|
|
20
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.
|
|
21
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.
|
|
@@ -203,9 +206,15 @@ are presentational. A keyboard-focused item is the menu's current row, painted
|
|
|
203
206
|
with the Select's current-option fill rather than a second focus ring inside the
|
|
204
207
|
open popup. A disabled item stays in the menu but cannot be
|
|
205
208
|
chosen; disable the whole trigger only when every command is unavailable.
|
|
209
|
+
Clicking outside dismisses a context `PopupMenu`, including one reopened while
|
|
210
|
+
its previous hide animation is still finishing.
|
|
206
211
|
Nested items use Web Awesome's submenu pointer and Left/Right arrow routing;
|
|
207
212
|
headings and dividers can separate nested groups without becoming keyboard
|
|
208
|
-
choices.
|
|
213
|
+
choices. A rapid ArrowDown or ArrowUp after opening a submenu keeps the chosen
|
|
214
|
+
item focused when its show animation finishes. If a submenu is reopened during
|
|
215
|
+
its close animation, it remains visible; pointer and ArrowRight actions also
|
|
216
|
+
recover a submenu left hidden with an open state. Set `checked` to `true` or `false` for checkbox menu semantics; omit it
|
|
217
|
+
for a plain command. `details` supplies optional
|
|
209
218
|
trailing visual content without changing a command's accessible label. A
|
|
210
219
|
`context` menu's invisible anchor is outside the accessibility tree and normal
|
|
211
220
|
Tab order; opening the menu moves focus into its commands and Escape dismisses
|
|
@@ -427,7 +436,11 @@ it) with `data-token-search-keep-open`, or pass `collapsible.keepOpenOn(target)`
|
|
|
427
436
|
so focus or pointer activation moving into it does not collapse an empty field.
|
|
428
437
|
The helper preserves the pointer target when a browser omits
|
|
429
438
|
`focusout.relatedTarget`, preventing the field or target from disappearing
|
|
430
|
-
between pointerdown and click.
|
|
439
|
+
between pointerdown and click. For an empty field losing focus during a pointer
|
|
440
|
+
press, managed collapse waits until that press's click has dispatched (or the
|
|
441
|
+
press ends without a click), then rechecks focus and content before closing.
|
|
442
|
+
This keeps a shifting search row from moving the pressed control mid-click.
|
|
443
|
+
`wireTokenSearchFields`
|
|
431
444
|
can also, opt-in via `keyboard`, own atomic-chip editing keys: from a collapsed
|
|
432
445
|
caret with no selection, Backspace removes the token before the caret and Delete
|
|
433
446
|
the token after it (reported through `onRemoveToken` for the app to apply to its
|
|
@@ -443,10 +456,21 @@ remove actions, and suggestions are managed. Arrow Down moves from the editor
|
|
|
443
456
|
to suggestions, arrow keys move between suggestions, Enter selects one, and
|
|
444
457
|
Escape returns focus to the editor. The application still announces results
|
|
445
458
|
when needed and can supply an `evaluate` callback to compute them. Omitting the
|
|
446
|
-
model leaves the manual behavior above intact.
|
|
459
|
+
model leaves the manual behavior above intact. After a chip commit, suggestion
|
|
460
|
+
choice, chip edit, or Backspace/Delete chip removal, focus restoration runs at
|
|
461
|
+
the controlled render checkpoint and keeps the query-relative caret offset;
|
|
462
|
+
it leaves focus on a different control if the user moved there in the meantime.
|
|
463
|
+
Suggestions use Floating UI positioning, following the same viewport-aware
|
|
464
|
+
popup behavior as the Web Awesome controls behind Select and PopupMenu. They
|
|
465
|
+
follow the field while scrolling, flip above it at the bottom edge, and shift
|
|
466
|
+
within narrow viewports without growing its toolbar row. One
|
|
467
|
+
suggestion keeps a pill outline; lists with multiple choices use the standard
|
|
468
|
+
rounded surface and scroll once they reach 240px. The clear button's hover and focus
|
|
469
|
+
highlight follows the field's pill shape or its toolbar group's rounded shape.
|
|
447
470
|
|
|
448
471
|
Managed clear captures the action before application handlers run, keeps the adopted
|
|
449
|
-
expanded signal open
|
|
472
|
+
expanded signal open for a collapsible field, and restores the editor focus for
|
|
473
|
+
both collapsible and plain model fields at the actual
|
|
450
474
|
mutation checkpoint before the next input task. It also returns keyboard activation
|
|
451
475
|
from the clear button to a surviving editor immediately. No animation frame owns
|
|
452
476
|
clear focus, so fast typing cannot escape to page shortcuts or lose its first letter. The app still owns clearing query/tokens and emptying
|
package/docs/catalog.md
CHANGED
|
@@ -117,8 +117,12 @@ own overlay implementation.
|
|
|
117
117
|
spacing wrapper. A composition row may place the composition root there.
|
|
118
118
|
|
|
119
119
|
Use `align="glyph"` for a bare glyph/text specimen, `align="inline-control"`
|
|
120
|
-
for a control whose own inline padding contributes about 8px, and
|
|
121
|
-
|
|
120
|
+
for a control whose own inline padding contributes about 8px, and
|
|
121
|
+
`align="text-trigger"` for a Web Awesome dropdown's text trigger whose visible
|
|
122
|
+
label needs an 8px inset (such as a plain `PopupMenu` trigger). The dropdown host
|
|
123
|
+
has no layout box, so this inset applies to its button child. Use
|
|
124
|
+
`align="none"` (the default) for a content item or composition that owns its
|
|
125
|
+
geometry.
|
|
122
126
|
|
|
123
127
|
```tsx
|
|
124
128
|
import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
|
|
@@ -215,6 +219,42 @@ border, and padding. Catalogs for downstream components should conform to the
|
|
|
215
219
|
contract and can start from the checked
|
|
216
220
|
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
217
221
|
example; provide those entries beside Kerf's shipped catalog to AI tools.
|
|
222
|
+
An extension entry may declare `uses` with direct Kerf or extension entry ids,
|
|
223
|
+
plus repository-relative `source` and `demoSource` paths. Keep the ids unique
|
|
224
|
+
and resolvable across both catalogs; paths use `/` separators and cannot
|
|
225
|
+
traverse outside the repository. These fields let consumer demos derive source
|
|
226
|
+
links and composition relationships from the catalog.
|
|
227
|
+
|
|
228
|
+
For a consumer UX demo, `@kerfjs/ui/catalog-projection` exports
|
|
229
|
+
`projectConsumerCatalog(extension, kerfCatalog, options)` and
|
|
230
|
+
`projectCatalogEntries(entries, options)`. The projection maps `purpose` to
|
|
231
|
+
`description`, `uses` to related-entry ids, `source` to `componentSource`, and
|
|
232
|
+
`demoSource` to the demo source link. It defaults the source identity to
|
|
233
|
+
`consumer` and category to `Application`. Explicit paths in an extension entry
|
|
234
|
+
take precedence over templates. Template placeholders are `{id}`, `{name}`,
|
|
235
|
+
`{kind}`, and `{package}`. Design templates are omitted unless configured.
|
|
236
|
+
|
|
237
|
+
The package also ships `kerf-catalog-demo` for a committed TypeScript module:
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
kerf-catalog-demo --extension ai/component-catalog-extension.json \
|
|
241
|
+
--out ux-demo/catalog.generated.ts \
|
|
242
|
+
--repo-base https://github.com/acme/app/blob/main/ \
|
|
243
|
+
--source-template 'src/{id}.tsx' \
|
|
244
|
+
--demo-template 'ux-demo/demos/{id}.tsx' --write
|
|
245
|
+
kerf-catalog-demo --extension ai/component-catalog-extension.json \
|
|
246
|
+
--out ux-demo/catalog.generated.ts \
|
|
247
|
+
--repo-base https://github.com/acme/app/blob/main/ \
|
|
248
|
+
--source-template 'src/{id}.tsx' \
|
|
249
|
+
--demo-template 'ux-demo/demos/{id}.tsx' --check
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The command uses the Kerf catalog shipped with the package to resolve `uses`;
|
|
253
|
+
`--kerf-catalog` selects another catalog file. `--category`,
|
|
254
|
+
`--documentation-template`, and `--design-template` configure optional row
|
|
255
|
+
fields. `--check` fails if the generated file is missing or stale. The output
|
|
256
|
+
exports `generatedConsumerCatalog` and `catalogRepositoryBlobUrl`; the latter
|
|
257
|
+
is normalized to a trailing slash for source and documentation links.
|
|
218
258
|
|
|
219
259
|
### Automated conformance and reviewed exceptions
|
|
220
260
|
|
|
@@ -134,7 +134,7 @@ and that of the components it renders (`Pane`, `Toolbar`, `ToolbarControlGroup`,
|
|
|
134
134
|
|
|
135
135
|
Give a panel a `toolbar` (the same roles as a
|
|
136
136
|
[Workbench panel toolbar](workbench.md#panel-toolbars)) and it composes its own
|
|
137
|
-
top toolbar over a `Pane
|
|
137
|
+
top toolbar over a `Pane`:
|
|
138
138
|
|
|
139
139
|
- `title` precedes the `leading` zone; `leading`, `center`, and `trailing`
|
|
140
140
|
place groups wherever the open panel needs them;
|
|
@@ -142,6 +142,13 @@ top toolbar over a `Pane`, with an optional `footer` below its content:
|
|
|
142
142
|
work area when the panel closes; the standard `toggle: { action, name,
|
|
143
143
|
showLabel?, hideLabel? }` is always the final group.
|
|
144
144
|
|
|
145
|
+
The panel can also take `header` under its toolbar, `footer` below its content,
|
|
146
|
+
`bottomToolbar` below that, `headerList` / `footerList` for those fixed content
|
|
147
|
+
stacks, and `headerPlacement` / `footerPlacement` (`fixed`, `scroll`, or `auto`).
|
|
148
|
+
These slots share the panel's one `Pane` scroll owner; the bottom toolbar uses
|
|
149
|
+
footer semantics. With no `toolbar`, the panel renders its children directly
|
|
150
|
+
and ignores the chrome slots.
|
|
151
|
+
|
|
145
152
|
The toolbar also takes the `Toolbar`'s configuration (`dividerSides`,
|
|
146
153
|
`centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); it draws no
|
|
147
154
|
divider of its own unless `dividerSides` says so. The `Pane` under it draws
|
|
@@ -243,6 +243,9 @@ example. Keep app and Kerf entries as package-qualified inputs, then let people
|
|
|
243
243
|
or AI tools search their combined entries and compare `geometry` before adding
|
|
244
244
|
wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
|
|
245
245
|
claim Kerf delivery paths for them.
|
|
246
|
+
Extension entries may add `uses` for direct composition dependencies and
|
|
247
|
+
repository-relative `source` and `demoSource` files. Resolve each dependency
|
|
248
|
+
against the combined Kerf and consumer catalogs; do not duplicate a Kerf id.
|
|
246
249
|
|
|
247
250
|
- Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
|
|
248
251
|
- Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
|
|
@@ -425,7 +428,9 @@ tones can be rethemed globally with
|
|
|
425
428
|
segmented-control, app-tab, and tab-bar colors likewise use their public
|
|
426
429
|
`--kui-*-*` variables. `SegmentedControl` exposes surface, border, foreground,
|
|
427
430
|
hover, and selected-state variables, so rounded, pill, and toolbar presentations
|
|
428
|
-
remain opinionated but locally overridable.
|
|
431
|
+
remain opinionated but locally overridable. Its label color switches with the
|
|
432
|
+
theme without Web Awesome's native button color transition, so a dark filled
|
|
433
|
+
track never briefly pairs with an interpolated low-contrast label.
|
|
429
434
|
|
|
430
435
|
Inset hover and selection surfaces follow the outer control shape instead of
|
|
431
436
|
choosing an independent radius. `--kui-layout-highlight-inset` is the full
|
|
@@ -478,6 +483,15 @@ declarations.
|
|
|
478
483
|
one child 8px inline margin, a real 1px border, 8px padding, and 12px corners;
|
|
479
484
|
the border stays transparent unless `frame="framed"`, so framing never changes
|
|
480
485
|
geometry. `shape="pill"` selects the 22px radius.
|
|
486
|
+
`flush` removes block padding and borders while preserving inline geometry;
|
|
487
|
+
`title` passes through as the native tooltip. Interactive hover uses the normal
|
|
488
|
+
neutral fill so it remains visible on a lowered surface. For a card with
|
|
489
|
+
multiple actions, put sibling buttons in a static ContentItem (or use
|
|
490
|
+
`ListActionRow` for a simple two-action row).
|
|
491
|
+
For an interactive ContentItem, `wireContentItems` activates the card on Enter
|
|
492
|
+
or Space only when the key event originated from the card itself. A nested
|
|
493
|
+
native control, including one inside a custom element's shadow root, keeps its
|
|
494
|
+
own keyboard activation.
|
|
481
495
|
|
|
482
496
|
`ToolbarControlGroup` is the unit of toolbar organization, even for dormant
|
|
483
497
|
text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
|
|
@@ -504,13 +518,26 @@ without application `::part(menu)` CSS; the default `standard` keeps each menu's
|
|
|
504
518
|
own inset (8px for a raw `wa-dropdown`, the Select listbox's 4px for a
|
|
505
519
|
`PopupMenu`), while `compact` and `list-zero` apply to every nested menu. A
|
|
506
520
|
`PopupMenu` and a `Select` draw one popup: the same caret that turns to face the
|
|
507
|
-
open popup, fade-in surface, surface inset, row height and padding,
|
|
508
|
-
|
|
509
|
-
|
|
521
|
+
open popup, fade-in surface, surface inset, row height and padding, current-row
|
|
522
|
+
fill, and group titles, all read from one private popup-choice contract in
|
|
523
|
+
`foundation.css`. Checkbox items reserve a check column; plain commands start
|
|
524
|
+
at the checkmark's leading edge, beside the group title rather than the
|
|
525
|
+
farther-in Select option label. Checked submenu items keep their checkmark and
|
|
526
|
+
label inside the submenu surface. Compact mixed controls retain the standard
|
|
510
527
|
item padding, omit an internal separator, and let the raised selected item paint
|
|
511
528
|
over the outer border instead of shrinking to an inset highlight. The Web
|
|
512
529
|
Awesome trigger's shadow base owns max-content sizing, so icons, localized text,
|
|
513
530
|
and the shadow caret determine the width without a fixed host measurement.
|
|
531
|
+
With `popup-menu/register` loaded, PopupMenu and a raw dropdown inside
|
|
532
|
+
`PopupSurface` preserve item navigation made during the Web Awesome show
|
|
533
|
+
animation across its final initial-focus handoff.
|
|
534
|
+
At viewport widths of 480px or less, `PopupMenu` places an open submenu above
|
|
535
|
+
or below its parent menu, choosing the side with room and constraining the
|
|
536
|
+
submenu to the viewport. It never paints over the parent list. The submenu
|
|
537
|
+
remains a native Web Awesome `menu`: the reverse horizontal arrow (ArrowLeft in
|
|
538
|
+
LTR, ArrowRight in RTL) returns focus to its parent item, Escape dismisses the
|
|
539
|
+
menu, and choosing a child dispatches the normal
|
|
540
|
+
`data-action` and `wa-select` events. Wider viewports retain the side cascade.
|
|
514
541
|
The enclosing `Toolbar` owns zone alignment and responsive topology: use
|
|
515
542
|
`centerAlign="stretch"` when the center group should consume its track, and
|
|
516
543
|
choose `responsive="stack"` with `responsiveAt="compact" | "narrow"`,
|
|
@@ -578,7 +605,18 @@ by default it also owns the collapsible field's transient expand/collapse/focus
|
|
|
578
605
|
state in a signal it exposes on the returned handle. An app reads that signal in
|
|
579
606
|
render, hands in its own via `collapsible.signals`, drives it through
|
|
580
607
|
`handle.open`/`handle.close`, or disables any individual behavior — so transient
|
|
581
|
-
UI is consistent by default without every app reinventing it.
|
|
608
|
+
UI is consistent by default without every app reinventing it. A collapsed
|
|
609
|
+
field may reveal its editor after the current microtask; the helper keeps a
|
|
610
|
+
bounded render-frame focus handoff for that reveal and cancels it on disposal.
|
|
611
|
+
`TokenSearchField.trailingAction` supplies an icon, accessible `label`, delegated
|
|
612
|
+
`action`, and optional `id`; the field renders a 24px wide button that fills its
|
|
613
|
+
34px line slot, including the hit target, disabled state, hover, and focus ring.
|
|
614
|
+
Use `trailing` for passive content. The two props are mutually exclusive.
|
|
615
|
+
For a collapsible search in a stretched Toolbar center, `fill` lets the expanded
|
|
616
|
+
field and its enclosing search `ToolbarControlGroup` consume the available width;
|
|
617
|
+
the collapsed action retains its fixed icon width.
|
|
618
|
+
Grammar suggestion rows align their labels to one leading edge inside the
|
|
619
|
+
anchored popover, including when the labels have different lengths.
|
|
582
620
|
|
|
583
621
|
A public subpath that exposes several visual components is a folder-backed
|
|
584
622
|
surface. Put each component in its own source file and give each visual
|