@kerfjs/ui 5.0.0-beta.65 → 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 +14 -3
- package/ai/component-composition.json +13 -1
- package/ai/components/grid.md +3 -1
- package/ai/components/nav-stack.md +4 -3
- package/ai/components/workbench.md +5 -3
- package/ai/public-api-signatures-v1.md +56 -36
- 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 +2 -1
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-2EKHENQK.js → chunk-B2ZMR7NY.js} +9 -1
- package/dist/chunk-B2ZMR7NY.js.map +1 -0
- package/dist/{chunk-FJSZYEBM.js → chunk-C74LITXE.js} +4 -4
- package/dist/{chunk-FJSZYEBM.js.map → chunk-C74LITXE.js.map} +1 -1
- package/dist/{chunk-PWQAVNON.js → chunk-TGM424PI.js} +34 -4
- 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/collapsible-panel.js +2 -2
- package/dist/grid.d.ts +5 -1
- package/dist/grid.js +1 -1
- package/dist/index.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 +9 -2
- package/dist/styles/toolbar.css +5 -1
- package/dist/wire-content-items.js +32 -4
- package/dist/wire-content-items.js.map +1 -1
- package/dist/workbench.d.ts +41 -33
- package/dist/workbench.js +4 -1
- package/docs/accessibility.md +9 -2
- package/docs/component-selection.md +7 -0
- package/docs/layout.md +9 -1
- 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-PWQAVNON.js.map +0 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ToolbarText } from './chunk-V3DX3K65.js';
|
|
2
|
+
import { ToolbarControlGroup } from './chunk-NXWE5S57.js';
|
|
3
|
+
import { Toolbar } from './chunk-RGS4WX2M.js';
|
|
4
|
+
import { LucideIcon } from './chunk-MOSIE4Z2.js';
|
|
5
|
+
import { ChevronLeft } from 'lucide';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'kerfjs/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var DEFAULT_SAFE_AREA_EDGES = [
|
|
9
|
+
"block-start",
|
|
10
|
+
"inline-start",
|
|
11
|
+
"inline-end"
|
|
12
|
+
];
|
|
13
|
+
function NavStack({
|
|
14
|
+
id,
|
|
15
|
+
label,
|
|
16
|
+
views,
|
|
17
|
+
backLabel = "Back",
|
|
18
|
+
backIcon,
|
|
19
|
+
backText,
|
|
20
|
+
toolbarConfig = {},
|
|
21
|
+
panelToggle,
|
|
22
|
+
hideToolbar = false,
|
|
23
|
+
bottomToolbar,
|
|
24
|
+
chromeDividers = "scroll",
|
|
25
|
+
className = "",
|
|
26
|
+
slot
|
|
27
|
+
}) {
|
|
28
|
+
const topIndex = views.length - 1;
|
|
29
|
+
const top = views[topIndex];
|
|
30
|
+
const canPop = views.length > 1;
|
|
31
|
+
return /* @__PURE__ */ jsxs(
|
|
32
|
+
"section",
|
|
33
|
+
{
|
|
34
|
+
class: `kui-nav-stack ${className}`.trim(),
|
|
35
|
+
id,
|
|
36
|
+
"data-component": "nav-stack",
|
|
37
|
+
"data-nav-stack-id": id,
|
|
38
|
+
"data-depth": String(views.length),
|
|
39
|
+
"data-chrome-dividers": chromeDividers === "always" || chromeDividers === "none" ? chromeDividers : void 0,
|
|
40
|
+
"aria-label": label,
|
|
41
|
+
slot,
|
|
42
|
+
children: [
|
|
43
|
+
!hideToolbar && /* @__PURE__ */ jsxs("div", { class: "kui-nav-stack__chrome", "data-nav-stack-chrome": true, children: [
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
Toolbar,
|
|
46
|
+
{
|
|
47
|
+
label: toolbarConfig.label,
|
|
48
|
+
dividerSides: toolbarConfig.dividerSides,
|
|
49
|
+
centerAlign: toolbarConfig.centerAlign,
|
|
50
|
+
responsive: toolbarConfig.responsive,
|
|
51
|
+
responsiveAt: toolbarConfig.responsiveAt,
|
|
52
|
+
safeAreaEdges: toolbarConfig.safeAreaEdges ?? DEFAULT_SAFE_AREA_EDGES,
|
|
53
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
|
+
canPop && /* @__PURE__ */ jsx(
|
|
55
|
+
ToolbarControlGroup,
|
|
56
|
+
{
|
|
57
|
+
appearance: "borderless",
|
|
58
|
+
shape: "rounded",
|
|
59
|
+
content: backText ? "mixed" : "icon",
|
|
60
|
+
single: true,
|
|
61
|
+
children: /* @__PURE__ */ jsxs(
|
|
62
|
+
"button",
|
|
63
|
+
{
|
|
64
|
+
type: "button",
|
|
65
|
+
class: "kui-nav-stack__back",
|
|
66
|
+
"data-nav-back": true,
|
|
67
|
+
"aria-label": backText ? void 0 : backLabel,
|
|
68
|
+
children: [
|
|
69
|
+
backIcon ?? /* @__PURE__ */ jsx(
|
|
70
|
+
LucideIcon,
|
|
71
|
+
{
|
|
72
|
+
icon: ChevronLeft,
|
|
73
|
+
name: "chevron-left",
|
|
74
|
+
className: "kui-nav-stack__back-icon"
|
|
75
|
+
}
|
|
76
|
+
),
|
|
77
|
+
backText ? /* @__PURE__ */ jsx("span", { children: backText }) : null
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
top?.leading,
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
ToolbarText,
|
|
86
|
+
{
|
|
87
|
+
text: top?.title ?? "",
|
|
88
|
+
size: toolbarConfig.titleSize ?? "large",
|
|
89
|
+
headingLevel: toolbarConfig.headingLevel,
|
|
90
|
+
fill: true
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
] }),
|
|
94
|
+
center: top?.center,
|
|
95
|
+
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
96
|
+
top?.toolbar,
|
|
97
|
+
panelToggle
|
|
98
|
+
] })
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
top?.header && /* @__PURE__ */ jsx("div", { class: "kui-nav-stack__header", "data-nav-stack-header": true, children: top.header })
|
|
102
|
+
] }),
|
|
103
|
+
/* @__PURE__ */ jsx("div", { class: "kui-nav-stack__viewport", "data-nav-stack-viewport": true, children: views.map((view, index) => /* @__PURE__ */ jsx(
|
|
104
|
+
"article",
|
|
105
|
+
{
|
|
106
|
+
class: "kui-nav-stack__view",
|
|
107
|
+
"data-key": view.key,
|
|
108
|
+
"data-nav-key": view.key,
|
|
109
|
+
"data-nav-active": String(index === topIndex),
|
|
110
|
+
"data-appearance": view.appearance === "sunken" ? "sunken" : void 0,
|
|
111
|
+
"aria-hidden": String(index !== topIndex),
|
|
112
|
+
children: view.content
|
|
113
|
+
}
|
|
114
|
+
)) }),
|
|
115
|
+
(top?.bottomToolbar ?? bottomToolbar) && /* @__PURE__ */ jsx("footer", { class: "kui-nav-stack__bottom", "data-nav-stack-bottom": true, children: top?.bottomToolbar ?? bottomToolbar })
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { NavStack };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=chunk-VY6YRCTV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nav-stack.tsx"],"names":[],"mappings":"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","file":"chunk-VY6YRCTV.js","sourcesContent":["import { ChevronLeft } from 'lucide';\n\nimport { LucideIcon } from './lucide-icon.js';\nimport type { PaneAppearance } from './pane.js';\nimport type { KerfUiContent } from './semantic-content.js';\nimport { Toolbar, type ToolbarConfig } from './toolbar.js';\nimport { ToolbarControlGroup } from './toolbar-control-group.js';\nimport {\n type HeadingLevel,\n ToolbarText,\n type ToolbarTextSize,\n} from './toolbar-text.js';\n\n/**\n * One entry in a {@link NavStack}. The app owns the stack as an array (usually a\n * signal); `NavStack` renders it and `wireNavStack` animates the transitions.\n */\nexport interface NavStackView {\n /** Stable identity for keyed reconcile and transition direction. */\n key: string;\n content: KerfUiContent;\n /** Background of this view's scrolling work surface. */\n appearance?: PaneAppearance;\n /** Title shown in the top toolbar for this view. */\n title?: string;\n /** Leading groups for this view's top toolbar, after the back control and before the title. */\n leading?: KerfUiContent;\n /** Center content for this view's top toolbar (placed per `toolbarConfig.centerAlign`). */\n center?: KerfUiContent;\n /** Trailing actions for this view's top toolbar. */\n toolbar?: KerfUiContent;\n /** Fixed chrome below this view's toolbar, above its scrolling content. */\n header?: KerfUiContent;\n /** Bottom toolbar for this view. Cross-fades with the top chrome on navigation. */\n bottomToolbar?: KerfUiContent;\n}\n\n/**\n * The top toolbar's configuration. Its `ToolbarConfig` forwards to the real\n * `Toolbar` the stack renders; by default that toolbar draws no divider and\n * claims the top and side safe-area edges the stack still touches.\n */\nexport interface NavStackToolbarConfig extends ToolbarConfig {\n /** Accessible name of the top toolbar (default: none). */\n label?: string;\n /** Size of the view title's `ToolbarText` (default `large`). */\n titleSize?: ToolbarTextSize;\n /** Expose the view title as a heading at this level (default: a plain span). */\n headingLevel?: HeadingLevel;\n}\n\nexport interface NavStackProps {\n id: string;\n /** Accessible name for the stack region. */\n label: string;\n /** The stack, root first; the last entry is the active top view. */\n views: NavStackView[];\n /** Accessible label for the icon-only back control (default \"Back\"). */\n backLabel?: string;\n /** The back control's icon (default a chevron-left `LucideIcon`). */\n backIcon?: KerfUiContent;\n /**\n * Visible text beside the back icon, such as the previous view's title\n * (default: icon only). When set, the text names the control and\n * `backLabel` is not used.\n */\n backText?: string;\n /** The top toolbar's configuration, forwarded to its `Toolbar`. */\n toolbarConfig?: NavStackToolbarConfig;\n /** Persistent last group in every view's top toolbar, supplied by a hosting panel. */\n panelToggle?: KerfUiContent;\n /** Hide the top toolbar entirely (rare — a fully custom-chrome view). */\n hideToolbar?: boolean;\n /** Optional persistent bottom toolbar used when the active view does not provide one. */\n bottomToolbar?: KerfUiContent;\n /**\n * The line under the top chrome and over the bottom toolbar, where they\n * meet the active view. `scroll` (default) shows the chrome's line only\n * while the view's content is scrolled beneath it, and the bottom\n * toolbar's only while more content lies below — never when the content\n * fits — once `wireScrollDividers` (`@kerfjs/ui/wire-scroll-dividers`) is\n * wired above the stack; unwired, neither shows. `always` shows both\n * without the wiring; `none` neither, even when wired. The line is drawn\n * inside the chrome, so no state moves the chrome or the content. A\n * `toolbarConfig.dividerSides` edge is the Toolbar's own and is unaffected.\n */\n chromeDividers?: 'scroll' | 'always' | 'none';\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\nconst DEFAULT_SAFE_AREA_EDGES = [\n 'block-start',\n 'inline-start',\n 'inline-end',\n] as const;\n\n/**\n * A navigation stack (iOS-style push/pop). Renders every entry stacked, the last\n * one active; `@kerfjs/ui/wire-nav-stack`'s `wireNavStack` slides the content and\n * cross-fades the chrome across a change. Its top chrome is a real `Toolbar`: the\n * back control and title lead, the active view's `leading` / `center` / `toolbar`\n * content fills the zones, and `toolbarConfig` configures it. A single-pane\n * layout is a `NavStack` with one entry. See `docs/23-app-layouts.md` §3.1.\n */\nexport function NavStack({\n id,\n label,\n views,\n backLabel = 'Back',\n backIcon,\n backText,\n toolbarConfig = {},\n panelToggle,\n hideToolbar = false,\n bottomToolbar,\n chromeDividers = 'scroll',\n className = '',\n slot,\n}: NavStackProps) {\n const topIndex = views.length - 1;\n const top = views[topIndex];\n const canPop = views.length > 1;\n return (\n <section\n class={`kui-nav-stack ${className}`.trim()}\n id={id}\n data-component=\"nav-stack\"\n data-nav-stack-id={id}\n data-depth={String(views.length)}\n data-chrome-dividers={\n chromeDividers === 'always' || chromeDividers === 'none'\n ? chromeDividers\n : undefined\n }\n aria-label={label}\n slot={slot}\n >\n {!hideToolbar && (\n <div class=\"kui-nav-stack__chrome\" data-nav-stack-chrome>\n <Toolbar\n label={toolbarConfig.label}\n dividerSides={toolbarConfig.dividerSides}\n centerAlign={toolbarConfig.centerAlign}\n responsive={toolbarConfig.responsive}\n responsiveAt={toolbarConfig.responsiveAt}\n safeAreaEdges={\n toolbarConfig.safeAreaEdges ?? DEFAULT_SAFE_AREA_EDGES\n }\n leading={\n <>\n {canPop && (\n <ToolbarControlGroup\n appearance=\"borderless\"\n shape=\"rounded\"\n content={backText ? 'mixed' : 'icon'}\n single\n >\n <button\n type=\"button\"\n class=\"kui-nav-stack__back\"\n data-nav-back\n aria-label={backText ? undefined : backLabel}\n >\n {backIcon ?? (\n <LucideIcon\n icon={ChevronLeft}\n name=\"chevron-left\"\n className=\"kui-nav-stack__back-icon\"\n />\n )}\n {backText ? <span>{backText}</span> : null}\n </button>\n </ToolbarControlGroup>\n )}\n {top?.leading}\n <ToolbarText\n text={top?.title ?? ''}\n size={toolbarConfig.titleSize ?? 'large'}\n headingLevel={toolbarConfig.headingLevel}\n fill\n />\n </>\n }\n center={top?.center}\n trailing={\n <>\n {top?.toolbar}\n {panelToggle}\n </>\n }\n />\n {top?.header && (\n <div class=\"kui-nav-stack__header\" data-nav-stack-header>\n {top.header}\n </div>\n )}\n </div>\n )}\n <div class=\"kui-nav-stack__viewport\" data-nav-stack-viewport>\n {views.map((view, index) => (\n <article\n class=\"kui-nav-stack__view\"\n data-key={view.key}\n data-nav-key={view.key}\n data-nav-active={String(index === topIndex)}\n data-appearance={\n view.appearance === 'sunken' ? 'sunken' : undefined\n }\n aria-hidden={String(index !== topIndex)}\n >\n {view.content}\n </article>\n ))}\n </div>\n {(top?.bottomToolbar ?? bottomToolbar) && (\n <footer class=\"kui-nav-stack__bottom\" data-nav-stack-bottom>\n {top?.bottomToolbar ?? bottomToolbar}\n </footer>\n )}\n </section>\n );\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { collapsiblePanelToggleIcon, composedPanelBody, relocatedPanelGroups } from './chunk-
|
|
2
|
-
export { collapsiblePanelToggleIcon } from './chunk-
|
|
1
|
+
import { collapsiblePanelToggleIcon, composedPanelBody, relocatedPanelGroups } from './chunk-C74LITXE.js';
|
|
2
|
+
export { collapsiblePanelToggleIcon } from './chunk-C74LITXE.js';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
package/dist/grid.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ type GridRootAttributes = Readonly<Record<`data-${string}`, string | undefined>
|
|
|
12
12
|
}>;
|
|
13
13
|
interface GridCommonProps {
|
|
14
14
|
children?: KerfUiContent;
|
|
15
|
+
/** Make this a labeled multi-select grid for `ContentItem selectionMode="multiple"` tiles. */
|
|
16
|
+
selectionMode?: 'multiple';
|
|
17
|
+
/** Accessible name for the multi-select grid. */
|
|
18
|
+
ariaLabel?: string;
|
|
15
19
|
/** A named UI spacing token or typed CSS length. Defaults to xs. */
|
|
16
20
|
gap?: UiSpaceName | CssLength;
|
|
17
21
|
/** Allow this grid to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */
|
|
@@ -41,6 +45,6 @@ type GridProps = GridCommonProps & ({
|
|
|
41
45
|
autoFill?: boolean;
|
|
42
46
|
});
|
|
43
47
|
/** Render equal tracks with a fixed count or a responsive minimum width. */
|
|
44
|
-
declare function Grid({ children, columns, minColumnWidth, autoFill, gap, flex, fill, className, rootAttributes, slot, }: GridProps): kerfjs.SafeHtml;
|
|
48
|
+
declare function Grid({ children, selectionMode, ariaLabel, columns, minColumnWidth, autoFill, gap, flex, fill, className, rootAttributes, slot, }: GridProps): kerfjs.SafeHtml;
|
|
45
49
|
|
|
46
50
|
export { CssFlex, CssFlexKeyword, CssLength, Grid, type GridProps, UiSpaceName };
|
package/dist/grid.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { SegmentedControl } from './chunk-X5QSICJ3.js';
|
|
|
15
15
|
export { Select } from './chunk-F7OCOBTG.js';
|
|
16
16
|
export { ListInsetControl } from './chunk-5I7QGQVT.js';
|
|
17
17
|
export { EmptyState } from './chunk-E36EVCKG.js';
|
|
18
|
-
export { Grid } from './chunk-
|
|
18
|
+
export { Grid } from './chunk-B2ZMR7NY.js';
|
|
19
19
|
export { ListActionRow } from './chunk-IAOAZUOO.js';
|
|
20
20
|
export { AppTab } from './chunk-Q6YU3BT7.js';
|
|
21
21
|
export { PopupMenu, closePopupMenu, openPopupMenuAt } from './chunk-J3KGJ6P6.js';
|
package/dist/nav-stack.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ interface NavStackView {
|
|
|
23
23
|
center?: KerfUiContent;
|
|
24
24
|
/** Trailing actions for this view's top toolbar. */
|
|
25
25
|
toolbar?: KerfUiContent;
|
|
26
|
+
/** Fixed chrome below this view's toolbar, above its scrolling content. */
|
|
27
|
+
header?: KerfUiContent;
|
|
26
28
|
/** Bottom toolbar for this view. Cross-fades with the top chrome on navigation. */
|
|
27
29
|
bottomToolbar?: KerfUiContent;
|
|
28
30
|
}
|
|
@@ -57,6 +59,8 @@ interface NavStackProps {
|
|
|
57
59
|
backText?: string;
|
|
58
60
|
/** The top toolbar's configuration, forwarded to its `Toolbar`. */
|
|
59
61
|
toolbarConfig?: NavStackToolbarConfig;
|
|
62
|
+
/** Persistent last group in every view's top toolbar, supplied by a hosting panel. */
|
|
63
|
+
panelToggle?: KerfUiContent;
|
|
60
64
|
/** Hide the top toolbar entirely (rare — a fully custom-chrome view). */
|
|
61
65
|
hideToolbar?: boolean;
|
|
62
66
|
/** Optional persistent bottom toolbar used when the active view does not provide one. */
|
|
@@ -85,6 +89,6 @@ interface NavStackProps {
|
|
|
85
89
|
* content fills the zones, and `toolbarConfig` configures it. A single-pane
|
|
86
90
|
* layout is a `NavStack` with one entry. See `docs/23-app-layouts.md` §3.1.
|
|
87
91
|
*/
|
|
88
|
-
declare function NavStack({ id, label, views, backLabel, backIcon, backText, toolbarConfig, hideToolbar, bottomToolbar, chromeDividers, className, slot, }: NavStackProps): kerfjs.SafeHtml;
|
|
92
|
+
declare function NavStack({ id, label, views, backLabel, backIcon, backText, toolbarConfig, panelToggle, hideToolbar, bottomToolbar, chromeDividers, className, slot, }: NavStackProps): kerfjs.SafeHtml;
|
|
89
93
|
|
|
90
94
|
export { NavStack, type NavStackProps, type NavStackToolbarConfig, type NavStackView };
|
package/dist/nav-stack.js
CHANGED
package/dist/split-view.js
CHANGED
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
background: var(--kui-color-surface);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
.kui-nav-stack__header {
|
|
73
|
+
min-width: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
.kui-nav-stack__viewport {
|
|
73
77
|
--kui-edge-inset-block-start: var(--_kui-nav-stack-safe-block-start);
|
|
74
78
|
--kui-edge-inset-block-end: var(--_kui-nav-stack-safe-block-end);
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.kui-token-search__field-required {
|
|
19
|
-
margin-inline-start: 0.
|
|
20
|
-
color: var(--wa-
|
|
19
|
+
margin-inline-start: var(--wa-form-control-required-content-offset, 0.1em);
|
|
20
|
+
color: var(--wa-form-control-required-content-color, inherit);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.kui-token-search__field-hint {
|
|
@@ -56,6 +56,13 @@
|
|
|
56
56
|
color-mix(in srgb, var(--kui-color-text-link) 22%, transparent);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
.kui-token-search[data-presentation="form-field"] {
|
|
60
|
+
border-radius: var(
|
|
61
|
+
--wa-form-control-border-radius,
|
|
62
|
+
var(--wa-border-radius-m)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
.kui-token-search[data-disabled="true"] {
|
|
60
67
|
opacity: 0.55;
|
|
61
68
|
}
|
package/dist/styles/toolbar.css
CHANGED
|
@@ -191,7 +191,11 @@
|
|
|
191
191
|
var(--kui-toolbar-zone-padding-inline, 0px)
|
|
192
192
|
);
|
|
193
193
|
grid-column: 1;
|
|
194
|
-
|
|
194
|
+
/* Keep a shrinking title clipped while letting the 3px focus outline plus
|
|
195
|
+
its 1px offset paint past the zone edge. `hidden` cropped the ring of the
|
|
196
|
+
first control in this zone. */
|
|
197
|
+
overflow: clip;
|
|
198
|
+
overflow-clip-margin: 4px;
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
.kui-toolbar__center {
|
|
@@ -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;
|
|
@@ -28,17 +48,25 @@ function wireContentItems(root) {
|
|
|
28
48
|
return;
|
|
29
49
|
const item = actionItem(root, event);
|
|
30
50
|
if (!item) return;
|
|
31
|
-
if (item.dataset.selectionMode === "multiple" && [
|
|
32
|
-
|
|
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);
|
|
33
60
|
if (!grid) return;
|
|
34
61
|
const rows = Array.from(
|
|
35
62
|
grid.querySelectorAll(
|
|
36
63
|
`${ITEM_SELECTOR}[data-selection-mode="multiple"]:not([data-disabled="true"])`
|
|
37
64
|
)
|
|
38
|
-
);
|
|
65
|
+
).filter((row) => row.closest(MULTI_GRID_SELECTOR) === grid);
|
|
39
66
|
const index = rows.indexOf(item);
|
|
40
67
|
if (index < 0) return;
|
|
41
|
-
const
|
|
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;
|
|
42
70
|
if (next) {
|
|
43
71
|
event.preventDefault();
|
|
44
72
|
next.focus();
|
|
@@ -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/** 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 ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)\n ) {\n const grid = item.closest('[role=\"grid\"][aria-multiselectable=\"true\"]');\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 );\n const index = rows.indexOf(item);\n if (index < 0) return;\n const next =\n event.key === 'Home'\n ? rows[0]\n : event.key === 'End'\n ? rows[rows.length - 1]\n : rows[index + (event.key === 'ArrowDown' ? 1 : -1)];\n if (next) {\n event.preventDefault();\n next.focus();\n }\n return;\n }\n if (event.key === 'Enter') {\n event.preventDefault();\n item.click();\n return;\n }\n if (event.key === ' ' || event.key === 'Spacebar') {\n event.preventDefault();\n if (event.repeat) return;\n clearSpace();\n pendingSpace = item;\n item.setAttribute('data-kui-pressed', 'true');\n }\n };\n\n const keyup = (event: KeyboardEvent) => {\n if (event.key !== ' ' && event.key !== 'Spacebar') return;\n const item = pendingSpace;\n if (!item) return;\n event.preventDefault();\n clearSpace();\n if (\n item === actionItem(root, event) &&\n item.ownerDocument.activeElement === item\n )\n item.click();\n };\n\n const focusout = (event: FocusEvent) => {\n if (event.target === pendingSpace) clearSpace();\n };\n\n root.addEventListener('keydown', keydown);\n root.addEventListener('keyup', keyup);\n root.addEventListener('focusout', focusout);\n return () => {\n clearSpace();\n root.removeEventListener('keydown', keydown);\n root.removeEventListener('keyup', keyup);\n root.removeEventListener('focusout', focusout);\n };\n}\n"]}
|
|
1
|
+
{"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,7 +15,7 @@ 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 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 selectionMode="multiple"
|
|
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.
|
|
@@ -23,10 +23,15 @@ These are normative contracts for package components and consuming applications.
|
|
|
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
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.
|
|
26
27
|
|
|
27
28
|
In a multi-select ContentItem grid, `wireContentItems` moves focus between
|
|
28
29
|
enabled rows on Up/Down and to the first/last enabled row on Home/End. Native
|
|
29
|
-
controls inside a row retain those keys
|
|
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
|
|
30
35
|
on card activation and handles its own Shift/Cmd range gestures.
|
|
31
36
|
With delegated click actions, use `isContentItemActivation(event, card)` in the
|
|
32
37
|
card's selection handler so a nested menu trigger or other control does not
|
|
@@ -413,6 +418,8 @@ searchbox references the label and hint with `aria-labelledby` and
|
|
|
413
418
|
validates the value before submission. `wireTokenSearchFields()` also moves
|
|
414
419
|
focus to the editor when the visible label is clicked. The field stands outside
|
|
415
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.
|
|
416
423
|
|
|
417
424
|
`TokenSearchField` exposes the editable surface as a named `searchbox`. Each
|
|
418
425
|
chip is atomic (`contenteditable="false"`) and contains separately named edit
|
|
@@ -334,6 +334,9 @@ In a form, use `TokenSearchField presentation="form-field"` with its `label`,
|
|
|
334
334
|
optional `hint`, and `required` state. It renders a full-width control beside
|
|
335
335
|
`wa-input` without a Toolbar or extra inline inset. The app validates required
|
|
336
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`.
|
|
337
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.
|
|
338
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.
|
|
339
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.
|
|
@@ -509,6 +512,10 @@ For a multi-select card containing its own controls, place
|
|
|
509
512
|
`selectionMode="multiple"` ContentItems inside a labeled
|
|
510
513
|
`List selectionMode="multiple"`; the app supplies each card's `selected`
|
|
511
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.
|
|
512
519
|
|
|
513
520
|
Incorrect: duplicating component-like rows and compensating for nested padding
|
|
514
521
|
forks the package anatomy and spacing contract.
|
package/docs/layout.md
CHANGED
|
@@ -289,6 +289,9 @@ item no taller than the band is centered in it by its own size, and a taller
|
|
|
289
289
|
item (a wrapped title, a second trailing row) starts at the band's top and
|
|
290
290
|
grows down, so it never moves the other controls. A wrapped `ToolbarText`
|
|
291
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.
|
|
292
295
|
Trailing icon actions share one axis: a toolbar control centers its glyph in
|
|
293
296
|
that 44px slot at the 8px inline margin, and a `ListHeader` action centers its
|
|
294
297
|
fitted 36px square in a slot of the same size (a transparent hit layer extends
|
|
@@ -448,7 +451,12 @@ their shared track width rather than expanding across an otherwise empty row.
|
|
|
448
451
|
It requires `minColumnWidth`; the default `auto-fit` keeps the existing form
|
|
449
452
|
behavior. When Grid is a direct child of a flex-owned `ListInsetControl`, pass
|
|
450
453
|
`flex` so the Grid takes the available row width instead of shrink-wrapping.
|
|
451
|
-
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
|
|
452
460
|
semantics.
|
|
453
461
|
Use application-owned CSS grid for intrinsic, asymmetric, spanning, or masonry
|
|
454
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
|