@kerfjs/ui 5.0.0-beta.63 → 5.0.0-beta.65
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 +28 -12
- package/ai/component-composition.json +21 -3
- package/ai/components/content-item.md +4 -1
- package/ai/components/list.md +4 -2
- package/ai/components/token-search-field.md +3 -0
- package/ai/components/workbench.md +1 -0
- package/ai/public-api-signatures-v1.md +24 -11
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.js +3 -3
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-UCKKT6KO.js → chunk-2EKHENQK.js} +7 -1
- package/dist/chunk-2EKHENQK.js.map +1 -0
- package/dist/{chunk-RTJNJTG3.js → chunk-5SYE4CCT.js} +42 -5
- package/dist/chunk-5SYE4CCT.js.map +1 -0
- package/dist/{chunk-IA4D5XNT.js → chunk-FJSZYEBM.js} +2 -2
- package/dist/{chunk-IA4D5XNT.js.map → chunk-FJSZYEBM.js.map} +1 -1
- package/dist/{chunk-ZRWXFDDS.js → chunk-J3KGJ6P6.js} +1 -1
- package/dist/{chunk-ZRWXFDDS.js.map → chunk-J3KGJ6P6.js.map} +1 -1
- package/dist/{chunk-7AKGPBCC.js → chunk-NCSWXG7G.js} +8 -4
- package/dist/chunk-NCSWXG7G.js.map +1 -0
- package/dist/{chunk-R75GGNWY.js → chunk-P4UXSYIJ.js} +58 -8
- package/dist/chunk-P4UXSYIJ.js.map +1 -0
- package/dist/{chunk-KFYANKJI.js → chunk-PWQAVNON.js} +3 -3
- package/dist/{chunk-KFYANKJI.js.map → chunk-PWQAVNON.js.map} +1 -1
- package/dist/{chunk-OBAFAH5B.js → chunk-TJK44RQC.js} +33 -8
- package/dist/chunk-TJK44RQC.js.map +1 -0
- package/dist/{chunk-NAPE7WUS.js → chunk-W2AC6Q2A.js} +9 -1
- package/dist/chunk-W2AC6Q2A.js.map +1 -0
- package/dist/collapsible-panel.js +2 -2
- package/dist/content-item.d.ts +7 -3
- package/dist/content-item.js +1 -1
- package/dist/grid.d.ts +5 -1
- package/dist/grid.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/list.d.ts +5 -1
- package/dist/list.js +1 -1
- package/dist/popup-menu-register.js +181 -5
- package/dist/popup-menu-register.js.map +1 -1
- package/dist/popup-menu.d.ts +2 -1
- package/dist/popup-menu.js +1 -1
- package/dist/styles/catalog/components/catalog-example.css +4 -0
- package/dist/styles/content-item.css +11 -2
- package/dist/styles/grid.css +7 -0
- package/dist/styles/popup-menu.css +9 -0
- package/dist/styles/segmented-control.css +3 -0
- package/dist/styles/token-search-field.css +51 -5
- package/dist/styles/toolbar-control-group.css +14 -0
- package/dist/styles/workbench.css +7 -25
- package/dist/{token-search-field-DvgFLhLL.d.ts → token-search-field-CVGwDn2y.d.ts} +30 -6
- package/dist/token-search-field.d.ts +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/token-search-model.d.ts +1 -1
- package/dist/wire-catalog.js +1 -1
- package/dist/wire-content-items.d.ts +3 -1
- package/dist/wire-content-items.js +30 -6
- package/dist/wire-content-items.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/wire-workbench.js +1 -1
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +29 -4
- package/docs/catalog.md +6 -2
- package/docs/component-contract.md +34 -2
- package/docs/component-selection.md +16 -0
- package/docs/layout.md +42 -10
- package/docs/surface-scaffold.md +6 -3
- package/docs/ux-demo.md +1 -0
- package/docs/workbench.md +5 -1
- package/package.json +1 -1
- package/dist/chunk-7AKGPBCC.js.map +0 -1
- package/dist/chunk-NAPE7WUS.js.map +0 -1
- package/dist/chunk-OBAFAH5B.js.map +0 -1
- package/dist/chunk-R75GGNWY.js.map +0 -1
- package/dist/chunk-RTJNJTG3.js.map +0 -1
- package/dist/chunk-UCKKT6KO.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'kerfjs';
|
|
2
2
|
import './semantic-content-BbzjvSu9.js';
|
|
3
|
-
export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, j as TokenSearchToken, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-field-
|
|
3
|
+
export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, j as TokenSearchToken, k as TokenSearchTrailingAction, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-field-CVGwDn2y.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'kerfjs';
|
|
2
|
-
export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion,
|
|
2
|
+
export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, l as createTokenSearchModel } from './token-search-field-CVGwDn2y.js';
|
|
3
3
|
import './semantic-content-BbzjvSu9.js';
|
package/dist/wire-catalog.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** True when a card action originated on the card rather than a nested control. */
|
|
2
|
+
declare function isContentItemActivation(event: Event, item: HTMLElement): boolean;
|
|
1
3
|
/**
|
|
2
4
|
* Give interactive ContentItems native-like Enter/Space activation with one
|
|
3
5
|
* delegated listener pair. Space activates on release and is cancelled if
|
|
@@ -6,4 +8,4 @@
|
|
|
6
8
|
*/
|
|
7
9
|
declare function wireContentItems(root: HTMLElement): () => void;
|
|
8
10
|
|
|
9
|
-
export { wireContentItems };
|
|
11
|
+
export { isContentItemActivation, wireContentItems };
|
|
@@ -1,15 +1,22 @@
|
|
|
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
|
-
function actionItem(root,
|
|
4
|
+
function actionItem(root, event) {
|
|
5
|
+
const target = event.target;
|
|
5
6
|
if (!(target instanceof Element)) return null;
|
|
6
7
|
const item = target.closest(ITEM_SELECTOR);
|
|
7
8
|
if (!item || !root.contains(item) || item.dataset.disabled === "true")
|
|
8
9
|
return null;
|
|
9
|
-
const
|
|
10
|
-
|
|
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
|
+
}
|
|
11
15
|
return item;
|
|
12
16
|
}
|
|
17
|
+
function isContentItemActivation(event, item) {
|
|
18
|
+
return actionItem(item, event) === item;
|
|
19
|
+
}
|
|
13
20
|
function wireContentItems(root) {
|
|
14
21
|
let pendingSpace = null;
|
|
15
22
|
const clearSpace = () => {
|
|
@@ -19,8 +26,25 @@ function wireContentItems(root) {
|
|
|
19
26
|
const keydown = (event) => {
|
|
20
27
|
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
|
|
21
28
|
return;
|
|
22
|
-
const item = actionItem(root, event
|
|
29
|
+
const item = actionItem(root, event);
|
|
23
30
|
if (!item) return;
|
|
31
|
+
if (item.dataset.selectionMode === "multiple" && ["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
32
|
+
const grid = item.closest('[role="grid"][aria-multiselectable="true"]');
|
|
33
|
+
if (!grid) return;
|
|
34
|
+
const rows = Array.from(
|
|
35
|
+
grid.querySelectorAll(
|
|
36
|
+
`${ITEM_SELECTOR}[data-selection-mode="multiple"]:not([data-disabled="true"])`
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
const index = rows.indexOf(item);
|
|
40
|
+
if (index < 0) return;
|
|
41
|
+
const next = event.key === "Home" ? rows[0] : event.key === "End" ? rows[rows.length - 1] : rows[index + (event.key === "ArrowDown" ? 1 : -1)];
|
|
42
|
+
if (next) {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
next.focus();
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
24
48
|
if (event.key === "Enter") {
|
|
25
49
|
event.preventDefault();
|
|
26
50
|
item.click();
|
|
@@ -40,7 +64,7 @@ function wireContentItems(root) {
|
|
|
40
64
|
if (!item) return;
|
|
41
65
|
event.preventDefault();
|
|
42
66
|
clearSpace();
|
|
43
|
-
if (item === actionItem(root, event
|
|
67
|
+
if (item === actionItem(root, event) && item.ownerDocument.activeElement === item)
|
|
44
68
|
item.click();
|
|
45
69
|
};
|
|
46
70
|
const focusout = (event) => {
|
|
@@ -57,6 +81,6 @@ function wireContentItems(root) {
|
|
|
57
81
|
};
|
|
58
82
|
}
|
|
59
83
|
|
|
60
|
-
export { wireContentItems };
|
|
84
|
+
export { isContentItemActivation, wireContentItems };
|
|
61
85
|
|
|
62
86
|
//# sourceMappingURL=wire-content-items.js.map
|
|
@@ -1 +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,
|
|
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"]}
|
package/dist/wire-workbench.js
CHANGED
package/dist/workbench.js
CHANGED
package/docs/accessibility.md
CHANGED
|
@@ -15,14 +15,22 @@ These are normative contracts for package components and consuming applications.
|
|
|
15
15
|
- `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
|
|
16
16
|
- A busy `ToolbarControlGroup` makes its controls inert and exposes `aria-busy`; its `busyLabel` is announced by an adjacent status node outside the inert group. Clear `busy` when the operation finishes so the controls rejoin the focus order.
|
|
17
17
|
- A bottom `Toolbar` uses `position="footer"` for footer semantics; top toolbars keep the default `header`. Workbench's main bottom toolbar sets this automatically.
|
|
18
|
-
- Interactive `ContentItem` cards
|
|
18
|
+
- Interactive `ContentItem` cards are keyboard reachable. Set `selectionMode="toggle"` for a pressed button, `selectionMode="single"` for an option inside a listbox, or `selectionMode="multiple"` for a selectable row with a gridcell that can contain native controls. Place multiple-mode rows inside a labeled `List selectionMode="multiple"` grid; the app owns the selection set and range gestures. Wire `wireContentItems` once so Enter and Space on the card activate its delegated action without activating it from a nested control. Disabled cards expose `aria-disabled` and leave the Tab order.
|
|
19
19
|
- An unlabeled `Skeleton` (including the value slots a component's `placeholder` prop renders) is decorative loading chrome: it is `aria-hidden`, conveys no information a user needs to act on, and is deliberately exempt from the WCAG 3:1 non-text-contrast target. Inside a solid `Badge` it stays subtle (about 2.1–4.4:1 in light, 2.4–2.8:1 in dark, depending on tone); `tests/browser/tone-contrast.spec.ts` holds those values as a regression floor rather than an accessibility threshold. Announce loading through a labeled `Skeleton`, `LoadingSpinner`, or `aria-busy` on the owning region, never through the skeleton's visibility.
|
|
20
20
|
- `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
|
|
21
21
|
- A removable `Chip` has a native button with a specific `removeLabel`, such as "Remove Urgent tag". The button is absent for a plain chip and disabled for a disabled chip. Its leading `icon` is decorative; the visible label names the tag. `truncate` retains the full plain-text label as a native tooltip. Tag text remains visible because tone does not convey meaning alone.
|
|
22
22
|
- `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
|
|
23
23
|
- `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone. Its decorative option icons and value-dependent selected content remain present after controlled rerenders, so the visible choice does not silently lose its non-text cue.
|
|
24
24
|
- `DisclosureArrow` is an 18px root-scaled decorative visual by default, not an interaction target. Its owning native control supplies a stable accessible name, pointer and keyboard interaction, and `aria-expanded`; multiple arrows keep independently controlled state. Consumers may resize it with the typed `size` prop without changing that ownership. Configured directions animate over the shortest path; a 180-degree closed-to-open tie uses counterclockwise rotation.
|
|
25
|
-
- `List` is layout-only and adds no `list` role.
|
|
25
|
+
- `List` is layout-only by default and adds no `list` role. With `selectionMode="multiple"` and a required `ariaLabel`, it becomes a multi-select grid for `ContentItem` rows. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
|
|
26
|
+
|
|
27
|
+
In a multi-select ContentItem grid, `wireContentItems` moves focus between
|
|
28
|
+
enabled rows on Up/Down and to the first/last enabled row on Home/End. Native
|
|
29
|
+
controls inside a row retain those keys; the application changes selection
|
|
30
|
+
on card activation and handles its own Shift/Cmd range gestures.
|
|
31
|
+
With delegated click actions, use `isContentItemActivation(event, card)` in the
|
|
32
|
+
card's selection handler so a nested menu trigger or other control does not
|
|
33
|
+
also select the card.
|
|
26
34
|
|
|
27
35
|
## Select
|
|
28
36
|
|
|
@@ -206,10 +214,14 @@ are presentational. A keyboard-focused item is the menu's current row, painted
|
|
|
206
214
|
with the Select's current-option fill rather than a second focus ring inside the
|
|
207
215
|
open popup. A disabled item stays in the menu but cannot be
|
|
208
216
|
chosen; disable the whole trigger only when every command is unavailable.
|
|
217
|
+
Clicking outside dismisses a context `PopupMenu`, including one reopened while
|
|
218
|
+
its previous hide animation is still finishing.
|
|
209
219
|
Nested items use Web Awesome's submenu pointer and Left/Right arrow routing;
|
|
210
220
|
headings and dividers can separate nested groups without becoming keyboard
|
|
211
221
|
choices. A rapid ArrowDown or ArrowUp after opening a submenu keeps the chosen
|
|
212
|
-
item focused when its show animation finishes.
|
|
222
|
+
item focused when its show animation finishes. If a submenu is reopened during
|
|
223
|
+
its close animation, it remains visible; pointer and ArrowRight actions also
|
|
224
|
+
recover a submenu left hidden with an open state. Set `checked` to `true` or `false` for checkbox menu semantics; omit it
|
|
213
225
|
for a plain command. `details` supplies optional
|
|
214
226
|
trailing visual content without changing a command's accessible label. A
|
|
215
227
|
`context` menu's invisible anchor is outside the accessibility tree and normal
|
|
@@ -393,6 +405,15 @@ need `tab`/`tabpanel` semantics.
|
|
|
393
405
|
|
|
394
406
|
## TokenSearchField
|
|
395
407
|
|
|
408
|
+
Use `presentation="form-field"` when a structured search query sits beside
|
|
409
|
+
Web Awesome form controls. It renders the visible `label` with the same inset
|
|
410
|
+
and typography, an optional `hint`, and an optional `required` marker. The
|
|
411
|
+
searchbox references the label and hint with `aria-labelledby` and
|
|
412
|
+
`aria-describedby`, and `required` sets `aria-required`; the application
|
|
413
|
+
validates the value before submission. `wireTokenSearchFields()` also moves
|
|
414
|
+
focus to the editor when the visible label is clicked. The field stands outside
|
|
415
|
+
a Toolbar and fills the same inline space as a neighboring `wa-input`.
|
|
416
|
+
|
|
396
417
|
`TokenSearchField` exposes the editable surface as a named `searchbox`. Each
|
|
397
418
|
chip is atomic (`contenteditable="false"`) and contains separately named edit
|
|
398
419
|
and remove buttons; the clear action is also named. Disabled fields publish
|
|
@@ -432,7 +453,11 @@ it) with `data-token-search-keep-open`, or pass `collapsible.keepOpenOn(target)`
|
|
|
432
453
|
so focus or pointer activation moving into it does not collapse an empty field.
|
|
433
454
|
The helper preserves the pointer target when a browser omits
|
|
434
455
|
`focusout.relatedTarget`, preventing the field or target from disappearing
|
|
435
|
-
between pointerdown and click.
|
|
456
|
+
between pointerdown and click. For an empty field losing focus during a pointer
|
|
457
|
+
press, managed collapse waits until that press's click has dispatched (or the
|
|
458
|
+
press ends without a click), then rechecks focus and content before closing.
|
|
459
|
+
This keeps a shifting search row from moving the pressed control mid-click.
|
|
460
|
+
`wireTokenSearchFields`
|
|
436
461
|
can also, opt-in via `keyboard`, own atomic-chip editing keys: from a collapsed
|
|
437
462
|
caret with no selection, Backspace removes the token before the caret and Delete
|
|
438
463
|
the token after it (reported through `onRemoveToken` for the app to apply to its
|
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";
|
|
@@ -428,7 +428,9 @@ tones can be rethemed globally with
|
|
|
428
428
|
segmented-control, app-tab, and tab-bar colors likewise use their public
|
|
429
429
|
`--kui-*-*` variables. `SegmentedControl` exposes surface, border, foreground,
|
|
430
430
|
hover, and selected-state variables, so rounded, pill, and toolbar presentations
|
|
431
|
-
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.
|
|
432
434
|
|
|
433
435
|
Inset hover and selection surfaces follow the outer control shape instead of
|
|
434
436
|
choosing an independent radius. `--kui-layout-highlight-inset` is the full
|
|
@@ -481,6 +483,15 @@ declarations.
|
|
|
481
483
|
one child 8px inline margin, a real 1px border, 8px padding, and 12px corners;
|
|
482
484
|
the border stays transparent unless `frame="framed"`, so framing never changes
|
|
483
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.
|
|
484
495
|
|
|
485
496
|
`ToolbarControlGroup` is the unit of toolbar organization, even for dormant
|
|
486
497
|
text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
|
|
@@ -510,11 +521,23 @@ own inset (8px for a raw `wa-dropdown`, the Select listbox's 4px for a
|
|
|
510
521
|
open popup, fade-in surface, surface inset, row height and padding, current-row
|
|
511
522
|
fill, and group titles, all read from one private popup-choice contract in
|
|
512
523
|
`foundation.css`. Checkbox items reserve a check column; plain commands start
|
|
513
|
-
at the checkmark's leading edge
|
|
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
|
|
514
527
|
item padding, omit an internal separator, and let the raised selected item paint
|
|
515
528
|
over the outer border instead of shrinking to an inset highlight. The Web
|
|
516
529
|
Awesome trigger's shadow base owns max-content sizing, so icons, localized text,
|
|
517
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.
|
|
518
541
|
The enclosing `Toolbar` owns zone alignment and responsive topology: use
|
|
519
542
|
`centerAlign="stretch"` when the center group should consume its track, and
|
|
520
543
|
choose `responsive="stack"` with `responsiveAt="compact" | "narrow"`,
|
|
@@ -585,6 +608,15 @@ render, hands in its own via `collapsible.signals`, drives it through
|
|
|
585
608
|
UI is consistent by default without every app reinventing it. A collapsed
|
|
586
609
|
field may reveal its editor after the current microtask; the helper keeps a
|
|
587
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.
|
|
588
620
|
|
|
589
621
|
A public subpath that exposes several visual components is a folder-backed
|
|
590
622
|
surface. Put each component in its own source file and give each visual
|
|
@@ -329,10 +329,22 @@ still owns the controlled `value` and localized action labels.
|
|
|
329
329
|
- `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
|
|
330
330
|
|
|
331
331
|
For common structured search, use `createTokenSearchModel` from `@kerfjs/ui/token-search-model`, pass the model to `TokenSearchField`, and register it under the field id in `wireTokenSearchFields(root, { models })`. Rules parse `name:value` expressions and provide suggestions; the model owns chips, selection, removal, clear, and an optional evaluation result. Omit `model` and `models` for full manual control.
|
|
332
|
+
|
|
333
|
+
In a form, use `TokenSearchField presentation="form-field"` with its `label`,
|
|
334
|
+
optional `hint`, and `required` state. It renders a full-width control beside
|
|
335
|
+
`wa-input` without a Toolbar or extra inline inset. The app validates required
|
|
336
|
+
search content before saving.
|
|
332
337
|
`TokenSearchRule.suggest(input, state)` receives the unfinished value and current committed tokens, so a rule can omit values already selected. Existing one-argument suggestion callbacks continue to work.
|
|
333
338
|
For a sibling date picker or other app helper, call `model.commit(value)` while a `name:` prefix is active. It parses and commits that value for the active rule even when it is absent from suggestions, replacing the unfinished prefix text. Invalid values and calls without an active prefix leave the query unchanged; `choose(value)` remains restricted to suggested tokens.
|
|
334
339
|
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.
|
|
335
340
|
When one root contains both kinds of field, a registered model enables chip keyboard behavior only for its own field. Other fields keep native editing unless the root opts into `keyboard` with an application removal callback.
|
|
341
|
+
For an icon action at the end of the field, pass `trailingAction` with `icon`,
|
|
342
|
+
accessible `label`, and delegated `action` (plus optional `id`). The component
|
|
343
|
+
renders a button that fills the trailing hit target. Reserve `trailing` for
|
|
344
|
+
passive content such as a shortcut hint.
|
|
345
|
+
Set `fill` on a collapsible `TokenSearchField` in a stretched Toolbar center to
|
|
346
|
+
use the available width when expanded; the field and its search group retain
|
|
347
|
+
their compact widths when collapsed.
|
|
336
348
|
|
|
337
349
|
## Toolbar composition
|
|
338
350
|
|
|
@@ -493,6 +505,10 @@ When that child is a selectable card, configure `ContentItem` with
|
|
|
493
505
|
`wireContentItems` once for Enter and Space. The component owns selection,
|
|
494
506
|
hover, focus, and disabled paint in its reserved 1px frame (see
|
|
495
507
|
[Content items](./layout.md#content-items)).
|
|
508
|
+
For a multi-select card containing its own controls, place
|
|
509
|
+
`selectionMode="multiple"` ContentItems inside a labeled
|
|
510
|
+
`List selectionMode="multiple"`; the app supplies each card's `selected`
|
|
511
|
+
state and handles range gestures.
|
|
496
512
|
|
|
497
513
|
Incorrect: duplicating component-like rows and compensating for nested padding
|
|
498
514
|
forks the package anatomy and spacing contract.
|
package/docs/layout.md
CHANGED
|
@@ -188,6 +188,7 @@ import { ContentItem } from "@kerfjs/ui/content-item";
|
|
|
188
188
|
<Pane label="Inspector">
|
|
189
189
|
<ContentItem>{details}</ContentItem>
|
|
190
190
|
<ContentItem frame="framed">{pendingChanges}</ContentItem>
|
|
191
|
+
<ContentItem flush>{markdownPreview}</ContentItem>
|
|
191
192
|
<ContentItem shape="pill">{summary}</ContentItem>
|
|
192
193
|
</Pane>;
|
|
193
194
|
```
|
|
@@ -202,7 +203,10 @@ needs to align with neighboring items is `ListInsetText`. Safe `data-*`
|
|
|
202
203
|
metadata goes in `rootAttributes`. Pass `ariaLabel` only for a distinct named
|
|
203
204
|
region (`role="region"`), and `focusTarget` to make the item a programmatic
|
|
204
205
|
focus target (`tabindex="-1"`), for example a NavStack view's `data-nav-focus`
|
|
205
|
-
target.
|
|
206
|
+
target. `flush` removes block padding and both block borders while retaining
|
|
207
|
+
the inline margin, padding, and border. Use it for content such as a markdown
|
|
208
|
+
preview that must reach the item's top and bottom edges. `title` passes through
|
|
209
|
+
to the root element as a native tooltip.
|
|
206
210
|
|
|
207
211
|
For a selectable card, set `interactive`, a delegated `action`, and an optional
|
|
208
212
|
`itemId`. Wire `wireContentItems(root)` once on the containing app root; it
|
|
@@ -210,14 +214,32 @@ activates focused cards with Enter or Space and forwards the resulting click to
|
|
|
210
214
|
the app's ordinary `data-action` handler. `selectionMode="toggle"` maps
|
|
211
215
|
`selected` to a button's `aria-pressed`; `selectionMode="single"` maps it to an
|
|
212
216
|
option's `aria-selected` and belongs in an app-owned `role="listbox"` container.
|
|
217
|
+
For a card with nested controls and multi-selection, place
|
|
218
|
+
`selectionMode="multiple"` cards inside a `List selectionMode="multiple"` with
|
|
219
|
+
`ariaLabel`. The List is a labeled `grid` with `aria-multiselectable`, each card
|
|
220
|
+
is a focusable `row` with `aria-selected`, and its content is a `gridcell` that
|
|
221
|
+
may contain native controls. The app owns the selection set, including
|
|
222
|
+
Shift/Cmd extension; the card renders the supplied `selected` state and
|
|
223
|
+
dispatches its `action`. Give nested controls their own actions.
|
|
224
|
+
When the app delegates clicks by `data-action`, guard the card's selection
|
|
225
|
+
handler with `isContentItemActivation(event, card)` from
|
|
226
|
+
`@kerfjs/ui/wire-content-items`; it ignores clicks from nested controls that
|
|
227
|
+
have no separate action, including a `PopupMenu` trigger.
|
|
228
|
+
`wireContentItems` moves focus among enabled rows with Up/Down and Home/End
|
|
229
|
+
when a row has focus; arrows from nested controls retain their native behavior.
|
|
213
230
|
An interactive card is a Tab stop, and `disabled` removes its action and Tab
|
|
214
|
-
stop while exposing `aria-disabled`.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
stop while exposing `aria-disabled`. Hover uses the normal neutral fill so it
|
|
232
|
+
remains visible on a lowered surface; hover, pressed, focus, and selected paints
|
|
233
|
+
do not change geometry. A single-action card has no interactive descendants.
|
|
234
|
+
For a rich card without card selection, render a static `ContentItem` with
|
|
235
|
+
sibling buttons inside it. Each button then has its own focus stop and action.
|
|
236
|
+
`ListActionRow` already packages this pattern for a simple two-action row.
|
|
237
|
+
`rootAttributes` still accepts safe app `data-*` metadata, while the card owns
|
|
238
|
+
its interaction data.
|
|
239
|
+
|
|
240
|
+
The rendered classes — `.kui-content-item`, `.kui-content-item--framed`,
|
|
241
|
+
`.kui-content-item--pill`, and `.kui-content-item--flush` — stay public for the
|
|
242
|
+
rare element that must carry the
|
|
221
243
|
geometry itself (for example a `<ul>` list or a `Text` paragraph in an
|
|
222
244
|
application-owned adapter). Prefer `ContentItem` everywhere else so the framing
|
|
223
245
|
choice is typed. A plain `<div class="kui-content-item">` is exactly what
|
|
@@ -410,14 +432,24 @@ import { px } from "@kerfjs/ui/css-values";
|
|
|
410
432
|
<label>Provider <input /></label>
|
|
411
433
|
<label>Endpoint <input /></label>
|
|
412
434
|
</Grid>;
|
|
435
|
+
|
|
436
|
+
<Grid minColumnWidth={px(160)} autoFill gap="m">
|
|
437
|
+
<DocumentTile />
|
|
438
|
+
</Grid>;
|
|
413
439
|
```
|
|
414
440
|
|
|
415
441
|
`columns` must be a positive safe integer; invalid counts throw instead of
|
|
416
442
|
silently producing invalid CSS. Import `px` from `@kerfjs/ui/css-values` for a
|
|
417
443
|
pixel minimum. `columns` and `minColumnWidth` are mutually exclusive. In the
|
|
418
444
|
responsive mode, the container width decides how many equal tracks fit; constrain
|
|
419
|
-
the Grid's outer width when the form should have a maximum count.
|
|
420
|
-
|
|
445
|
+
the Grid's outer width when the form should have a maximum count.
|
|
446
|
+
`autoFill` keeps the unoccupied responsive tracks so one or two tiles retain
|
|
447
|
+
their shared track width rather than expanding across an otherwise empty row.
|
|
448
|
+
It requires `minColumnWidth`; the default `auto-fit` keeps the existing form
|
|
449
|
+
behavior. When Grid is a direct child of a flex-owned `ListInsetControl`, pass
|
|
450
|
+
`flex` so the Grid takes the available row width instead of shrink-wrapping.
|
|
451
|
+
Grid is layout-only and adds no ARIA grid role. The application owns child
|
|
452
|
+
semantics.
|
|
421
453
|
Use application-owned CSS grid for intrinsic, asymmetric, spanning, or masonry
|
|
422
454
|
tracks, and use `ResizableRegion` when people must adjust a boundary.
|
|
423
455
|
|
package/docs/surface-scaffold.md
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
`DialogSurface` and `PopupSurface` configure recurring Web Awesome surface
|
|
4
4
|
geometry without application `::part()` overrides. They do not replace Web
|
|
5
|
-
Awesome behavior: the application still owns open state,
|
|
6
|
-
|
|
5
|
+
Awesome behavior: the application still owns open state, dismissal policy, ids,
|
|
6
|
+
labels, and content. With `@kerfjs/ui/popup-menu/register` imported, a dropdown
|
|
7
|
+
inside `PopupSurface` also keeps keyboard item navigation made during its show
|
|
8
|
+
animation when Web Awesome completes its initial focus handoff.
|
|
7
9
|
|
|
8
10
|
Wrap one `wa-dialog` with `DialogSurface`. Choose `size` (`small`, `medium`, or
|
|
9
11
|
`large`), `presentation` (`modal`, `side-sheet`, or `fullscreen`), and independent
|
|
@@ -22,4 +24,5 @@ Wrap one `PopupMenu` (or another single `wa-dropdown`) with `PopupSurface`. `ins
|
|
|
22
24
|
padding for a child that already owns row insets, `compact` uses 4px, and
|
|
23
25
|
`standard` uses the shared 8px surface inset. Keep the dropdown trigger named and
|
|
24
26
|
preserve `data-morph-skip-children` when its upgraded light-DOM items must retain
|
|
25
|
-
identity across Kerf rerenders.
|
|
27
|
+
identity across Kerf rerenders. Import `@kerfjs/ui/popup-menu/register` once for
|
|
28
|
+
the focus handoff fix when wrapping a raw `wa-dropdown`.
|
package/docs/ux-demo.md
CHANGED
|
@@ -22,6 +22,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
|
|
|
22
22
|
|
|
23
23
|
- It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
|
|
24
24
|
- The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
|
|
25
|
+
- The `PopupMenu` route's context example opens one app-owned menu either at the row's right-click point or below its adjacent, keyboard-operable Demand actions button. Its interactive row/button composition opts out of the catalog geometry overlay so the overlay's border and margin highlights do not masquerade as a row inset during activation. The Nested decisions example shows the automatic phone-width submenu placement above or below its parent list, with native ArrowLeft return, Escape dismissal, and child selection.
|
|
25
26
|
- The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding (a new tab opens `pending` with a spinner and stays selectable while its panel shows a `Skeleton` heading until a simulated load completes), closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
|
|
26
27
|
- Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
|
|
27
28
|
- `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
|
package/docs/workbench.md
CHANGED
|
@@ -379,7 +379,11 @@ const stop = wireWorkbench(root, {
|
|
|
379
379
|
full-size overlay over the work area rather than squeezing beside it (see
|
|
380
380
|
the catalog's resizable and responsive-drawer Workbench examples).
|
|
381
381
|
A numeric value measures the Workbench container, not the viewport. If the
|
|
382
|
-
|
|
382
|
+
app has no numeric Workbench breakpoint, the overlay wiring does not measure
|
|
383
|
+
the Workbench on ordinary panel updates. For numeric breakpoints the wiring
|
|
384
|
+
mirrors the active panel state onto the Workbench root, keeping work-area
|
|
385
|
+
styles local to actual overlay transitions. If the rest of an app switches
|
|
386
|
+
by viewport width and its Workbench can be narrower
|
|
383
387
|
than the viewport, keep the app's viewport decision in
|
|
384
388
|
`presentation: "overlay"` and pass its `collapsed` signals to
|
|
385
389
|
`wireWorkbench`. On entering that mode, collapse the panels; after leaving,
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/content-item.tsx"],"names":[],"mappings":";;;AAWA,IAAM,8BAAA,mBAAiC,IAAI,GAAA,CAAI,CAAC,gBAAgB,CAAC,CAAA;AACjE,IAAM,8BAAA,uBAAqC,GAAA,CAAI;AAAA,EAC7C,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,cAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAC,CAAA;AA+EM,SAAS,WAAA,CAAY;AAAA,EAC1B,QAAA;AAAA,EACA,KAAA,GAAQ,MAAA;AAAA,EACR,KAAA,GAAQ,SAAA;AAAA,EACR,SAAA;AAAA,EACA,WAAA,GAAc,KAAA;AAAA,EACd,WAAA,GAAc,KAAA;AAAA,EACd,MAAA;AAAA,EACA,MAAA;AAAA,EACA,aAAA,GAAgB,MAAA;AAAA,EAChB,QAAA,GAAW,KAAA;AAAA,EACX,QAAA,GAAW,KAAA;AAAA,EACX,SAAA,GAAY,EAAA;AAAA,EACZ,iBAAiB,EAAC;AAAA,EAClB;AACF,CAAA,EAAqB;AACnB,EAAA,MAAM,kBAAA,GAAqB,oBAAA;AAAA,IACzB,cAAA;AAAA,IACA,cACI,8BAAA,GACA;AAAA,GACN;AACA,EAAA,IAAI,WAAA,IAAe,CAAC,MAAA,EAAQ;AAC1B,IAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,EAC9D;AACA,EAAA,IAAI,WAAA,IAAe,QAAA,IAAY,aAAA,KAAkB,MAAA,EAAQ;AACvD,IAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,EACjE;AACA,EAAA,MAAM,GAAA,GAAM;AAAA,IACV,kBAAA;AAAA,IACA,KAAA,KAAU,SAAS,wBAAA,GAA2B,EAAA;AAAA,IAC9C,KAAA,KAAU,WAAW,0BAAA,GAA6B,EAAA;AAAA,IAClD;AAAA,GACF,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AACX,EAAA,MAAM,wBAAwB,WAAA,GAC1B;AAAA,IACE,kBAAA,EAAoB,MAAA;AAAA,IACpB,aAAA,EAAe,WAAW,MAAA,GAAY,MAAA;AAAA,IACtC,cAAA,EAAgB,MAAA;AAAA,IAChB,qBAAA,EAAuB,aAAA;AAAA,IACvB,eAAA,EACE,aAAA,KAAkB,MAAA,GAAS,MAAA,GAAY,OAAO,QAAQ,CAAA;AAAA,IACxD,eAAA,EAAiB,WAAW,MAAA,GAAS;AAAA,MAEvC,EAAC;AACL,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACE,GAAG,kBAAA;AAAA,MACH,GAAG,qBAAA;AAAA,MACJ,KAAA,EAAO,GAAA;AAAA,MACP,gBAAA,EAAe,cAAA;AAAA,MACf,MACE,WAAA,GACI,aAAA,KAAkB,WAChB,QAAA,GACA,QAAA,GACF,YACE,QAAA,GACA,MAAA;AAAA,MAER,YAAA,EAAY,SAAA;AAAA,MACZ,iBACE,WAAA,IAAe,aAAA,KAAkB,QAAA,GAAW,MAAA,CAAO,QAAQ,CAAA,GAAI,MAAA;AAAA,MAEjE,gBACE,WAAA,IAAe,aAAA,KAAkB,QAAA,GAAW,MAAA,CAAO,QAAQ,CAAA,GAAI,MAAA;AAAA,MAEjE,eAAA,EAAe,WAAA,IAAe,QAAA,GAAW,MAAA,GAAS,MAAA;AAAA,MAClD,UACE,WAAA,GAAe,QAAA,GAAW,IAAA,GAAO,GAAA,GAAO,cAAc,IAAA,GAAO,MAAA;AAAA,MAE/D,IAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-7AKGPBCC.js","sourcesContent":["import { filterDataAttributes } from './extension-attributes.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\n/** Whether the item's always-reserved 1px border is transparent or visible. */\nexport type ContentItemFrame = 'none' | 'framed';\n\n/** Corner shape: the 12px rounded rectangle or the 22px pill. */\nexport type ContentItemShape = 'rounded' | 'pill';\n/** `single` uses option selection; `toggle` uses a pressed button. */\nexport type ContentItemSelectionMode = 'none' | 'single' | 'toggle';\n\nconst contentItemProtectedAttributes = new Set(['data-component']);\nconst interactiveProtectedAttributes = new Set([\n 'data-component',\n 'data-action',\n 'data-item-id',\n 'data-interactive',\n 'data-selected',\n 'data-disabled',\n 'data-selection-mode',\n 'data-kui-pressed',\n]);\n\ntype ContentItemRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n }\n>;\n\ntype ContentItemInteractiveRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-action'?: never;\n 'data-item-id'?: never;\n 'data-interactive'?: never;\n 'data-selected'?: never;\n 'data-disabled'?: never;\n 'data-selection-mode'?: never;\n 'data-kui-pressed'?: never;\n }\n>;\n\ninterface ContentItemBaseProps {\n /** Item content; a plain string is allowed for bare copy. */\n children?: KerfUiContent | string;\n /**\n * `framed` paints the standard neutral border in the 1px the item always\n * reserves, so framing never changes geometry. Frame only an item that\n * marks a real distinction. Defaults to `none` (transparent border).\n */\n frame?: ContentItemFrame;\n /** Corner shape. Defaults to `rounded`. */\n shape?: ContentItemShape;\n /**\n * Names the item as a distinct region (`role=\"region\"`). Omit it for an\n * ordinary item, which stays a non-landmark grouping.\n */\n ariaLabel?: string;\n /**\n * Makes the item a programmatic focus target (`tabindex=\"-1\"`, never a tab\n * stop), for example the preferred initial focus of a NavStack view when\n * combined with `data-nav-focus` in `rootAttributes`.\n */\n focusTarget?: boolean;\n className?: string;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\nexport type ContentItemProps = ContentItemBaseProps &\n (\n | {\n interactive?: false;\n action?: never;\n itemId?: never;\n selectionMode?: never;\n selected?: never;\n disabled?: never;\n /** Existing static `data-*` metadata remains available. */\n rootAttributes?: ContentItemRootAttributes;\n }\n | {\n /** Enable a keyboard reachable card; wire `wireContentItems` once on its containing root. */\n interactive: true;\n /** Delegated action dispatched by pointer, Enter, or Space. */\n action: string;\n itemId?: string;\n selectionMode?: ContentItemSelectionMode;\n selected?: boolean;\n disabled?: boolean;\n /** Safe app metadata; interaction attributes are component-owned. */\n rootAttributes?: ContentItemInteractiveRootAttributes;\n }\n );\n\n/**\n * One self-contained `.kui-content` child: an 8px inline margin, a real 1px\n * border (transparent unless `framed`), 8px padding, and a rounded or pill\n * radius. It owns that whole geometry, so wrappers must not add more.\n */\nexport function ContentItem({\n children,\n frame = 'none',\n shape = 'rounded',\n ariaLabel,\n focusTarget = false,\n interactive = false,\n action,\n itemId,\n selectionMode = 'none',\n selected = false,\n disabled = false,\n className = '',\n rootAttributes = {},\n slot,\n}: ContentItemProps) {\n const safeRootAttributes = filterDataAttributes(\n rootAttributes,\n interactive\n ? interactiveProtectedAttributes\n : contentItemProtectedAttributes,\n );\n if (interactive && !action) {\n throw new Error('Interactive ContentItem requires an action');\n }\n if (interactive && selected && selectionMode === 'none') {\n throw new Error('Selected ContentItem requires a selectionMode');\n }\n const cls = [\n 'kui-content-item',\n shape === 'pill' ? 'kui-content-item--pill' : '',\n frame === 'framed' ? 'kui-content-item--framed' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n const interactiveAttributes = interactive\n ? {\n 'data-interactive': 'true',\n 'data-action': disabled ? undefined : action,\n 'data-item-id': itemId,\n 'data-selection-mode': selectionMode,\n 'data-selected':\n selectionMode === 'none' ? undefined : String(selected),\n 'data-disabled': disabled ? 'true' : undefined,\n }\n : {};\n return (\n <div\n {...safeRootAttributes}\n {...interactiveAttributes}\n class={cls}\n data-component=\"content-item\"\n role={\n interactive\n ? selectionMode === 'single'\n ? 'option'\n : 'button'\n : ariaLabel\n ? 'region'\n : undefined\n }\n aria-label={ariaLabel}\n aria-selected={\n interactive && selectionMode === 'single' ? String(selected) : undefined\n }\n aria-pressed={\n interactive && selectionMode === 'toggle' ? String(selected) : undefined\n }\n aria-disabled={interactive && disabled ? 'true' : undefined}\n tabindex={\n interactive ? (disabled ? '-1' : '0') : focusTarget ? '-1' : undefined\n }\n slot={slot}\n >\n {children}\n </div>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/list.tsx"],"names":[],"mappings":";;;;;AAiBA,IAAM,uBAAA,uBAA8B,GAAA,CAAI;AAAA,EACtC,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,iBAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAgBD,IAAM,UAAA,GAAqC;AAAA,EACzC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA;AACF,CAAA;AA6CO,SAAS,IAAA,CAAK;AAAA,EACnB,QAAA;AAAA,EACA,GAAA,GAAM,KAAA;AAAA,EACN,IAAA,GAAO,KAAA;AAAA,EACP,IAAA,GAAO,KAAA;AAAA,EACP,MAAA,GAAS,MAAA;AAAA,EACT,MAAA,GAAS,KAAA;AAAA,EACT,UAAA,GAAa,KAAA;AAAA,EACb,YAAA,GAAe,EAAA;AAAA,EACf,UAAA,GAAa,EAAA;AAAA,EACb,aAAA,GAAgB,EAAA;AAAA,EAChB,SAAA,GAAY,EAAA;AAAA,EACZ,iBAAiB,EAAC;AAAA,EAClB;AACF,CAAA,EAAc;AACZ,EAAA,MAAM,kBAAA,GAAqB,oBAAA;AAAA,IACzB,cAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,IAAA,GACJ,qBAAA,GACA,UAAA,CAAW,QAAA,CAAS,GAAkB,CAAA,GACpC,KAAA,CAAM,GAAkB,CAAA,GACxB,GAAA,IAAO,MAAA;AACf,EAAA,MAAM,SAAA,GAAY,IAAA,KAAS,IAAA,GAAO,UAAA,GAAa,IAAA,IAAQ,MAAA;AACvD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,QAAA,GAAW,CAAA,gBAAA,EAAmB,QAAQ,CAAA,CAAA,GAAK,EAAA;AAAA,IAC3C,SAAA,GAAY,CAAA,iBAAA,EAAoB,SAAS,CAAA,CAAA,GAAK;AAAA,GAChD,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAEX,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACE,GAAG,kBAAA;AAAA,MACJ,KAAA,EAAO,CAAA,SAAA,EAAY,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACpC,gBAAA,EAAe,MAAA;AAAA,MACf,UAAA,EAAU,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAC,CAAA;AAAA,MAC7B,WAAA,EAAW,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,MAC/B,WAAA,EAAW,OAAO,MAAA,GAAS,MAAA;AAAA,MAC3B,cAAA,EAAc,oBAAoB,MAAM,CAAA;AAAA,MACxC,cAAA,EAAc,kBAAkB,MAAM,CAAA;AAAA,MACtC,iBAAA,EAAiB,OAAO,UAAU,CAAA;AAAA,MAClC,iBAAe,YAAA,IAAgB,MAAA;AAAA,MAC/B,oBAAkB,UAAA,IAAc,MAAA;AAAA,MAChC,uBAAqB,aAAA,IAAiB,MAAA;AAAA,MACtC,OAAO,KAAA,IAAS,MAAA;AAAA,MAChB,IAAA;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"chunk-NAPE7WUS.js","sourcesContent":["import {\n type CssFlex,\n type CssFlexKeyword,\n type CssLength,\n space,\n type UiSpaceName,\n} from './css-values.js';\nimport { filterDataAttributes } from './extension-attributes.js';\nimport {\n type HorizontalAlignment,\n horizontalAlignment,\n type ListVerticalAlignment,\n verticalAlignment,\n} from './flex-alignment.js';\nimport type { KerfUiContent } from './semantic-content.js';\nimport type { Sides } from './sides.js';\n\nconst listProtectedAttributes = new Set([\n 'data-component',\n 'data-gap',\n 'data-flex',\n 'data-fill',\n 'data-h-align',\n 'data-v-align',\n 'data-scrollable',\n 'data-text-insets',\n 'data-control-insets',\n]);\n\ntype ListRootAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-gap'?: never;\n 'data-flex'?: never;\n 'data-fill'?: never;\n 'data-h-align'?: never;\n 'data-v-align'?: never;\n 'data-scrollable'?: never;\n 'data-text-insets'?: never;\n 'data-control-insets'?: never;\n }\n>;\n\nconst spaceNames: readonly UiSpaceName[] = [\n 'none',\n '2xs',\n 'xs',\n 's',\n 'm',\n 'l',\n 'xl',\n];\n\nexport interface ListProps {\n children?: KerfUiContent;\n /** Use the standard item gap, a named UI spacing token, or a typed CSS length. Defaults to no gap. */\n gap?: boolean | UiSpaceName | CssLength;\n /** Allow this list to grow/shrink, use a keyword, or supply a typed CSS flex shorthand. */\n flex?: boolean | CssFlexKeyword | CssFlex;\n /**\n * Fill the height of a parent with a definite height, such as an app root or\n * a fixed-height frame, when this list is that parent's layout root. Inside a\n * flex layout use `flex` instead. Defaults to false.\n */\n fill?: boolean;\n /** Horizontal alignment. Defaults to full to preserve stretch-aligned list children. */\n hAlign?: HorizontalAlignment;\n /** Vertical distribution. Defaults to top. */\n vAlign?: ListVerticalAlignment;\n /** Own vertical scrolling and overscroll containment. */\n scrollable?: boolean;\n /** Physical divider edges in canonical top/right/bottom/left order. */\n dividerSides?: Sides;\n /** Physical sides that receive the standard 17px text inset. */\n textInsets?: Sides;\n /** Physical sides that receive the standard 8px control inset. Text insets win on overlap. */\n controlInsets?: Sides;\n className?: string;\n /** Safe `data-*` metadata; List-owned structural attributes remain protected. */\n rootAttributes?: ListRootAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\n/**\n * A list's configuration, apart from its content and styling hooks: the props a\n * composite that renders a `List` for the app (the Workbench's `mainHeader` /\n * `mainFooter` chrome) forwards, so the app configures that list instead of\n * styling it. An omitted or `undefined` field keeps the composite's default.\n */\nexport type ListConfig = Pick<\n ListProps,\n 'gap' | 'hAlign' | 'vAlign' | 'dividerSides' | 'textInsets' | 'controlInsets'\n>;\n\n/** A stretch-aligned vertical stack with optional gap, flex, fill, scroll, and dividers. */\nexport function List({\n children,\n gap = false,\n flex = false,\n fill = false,\n hAlign = 'full',\n vAlign = 'top',\n scrollable = false,\n dividerSides = '',\n textInsets = '',\n controlInsets = '',\n className = '',\n rootAttributes = {},\n slot,\n}: ListProps) {\n const safeRootAttributes = filterDataAttributes(\n rootAttributes,\n listProtectedAttributes,\n );\n const gapValue =\n gap === true\n ? 'var(--kui-list-gap)'\n : spaceNames.includes(gap as UiSpaceName)\n ? space(gap as UiSpaceName)\n : gap || undefined;\n const flexValue = flex === true ? '1 1 auto' : flex || undefined;\n const style = [\n gapValue ? `--_kui-list-gap:${gapValue}` : '',\n flexValue ? `--_kui-list-flex:${flexValue}` : '',\n ]\n .filter(Boolean)\n .join(';');\n\n return (\n <div\n {...safeRootAttributes}\n class={`kui-list ${className}`.trim()}\n data-component=\"list\"\n data-gap={String(Boolean(gap))}\n data-flex={String(Boolean(flex))}\n data-fill={fill ? 'true' : undefined}\n data-h-align={horizontalAlignment(hAlign)}\n data-v-align={verticalAlignment(vAlign)}\n data-scrollable={String(scrollable)}\n divider-sides={dividerSides || undefined}\n data-text-insets={textInsets || undefined}\n data-control-insets={controlInsets || undefined}\n style={style || undefined}\n slot={slot}\n >\n {children}\n </div>\n );\n}\n\nexport type {\n CssFlex,\n CssFlexKeyword,\n CssLength,\n UiSpaceName,\n} from './css-values.js';\nexport type {\n HorizontalAlignment,\n ListVerticalAlignment,\n VerticalAlignment,\n} from './flex-alignment.js';\nexport type { Sides } from './sides.js';\n"]}
|