@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.
Files changed (77) hide show
  1. package/ai/component-catalog.json +28 -12
  2. package/ai/component-composition.json +21 -3
  3. package/ai/components/content-item.md +4 -1
  4. package/ai/components/list.md +4 -2
  5. package/ai/components/token-search-field.md +3 -0
  6. package/ai/components/workbench.md +1 -0
  7. package/ai/public-api-signatures-v1.md +24 -11
  8. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  9. package/dist/catalog.d.ts +1 -1
  10. package/dist/catalog.js +3 -3
  11. package/dist/catalog.js.map +1 -1
  12. package/dist/{chunk-UCKKT6KO.js → chunk-2EKHENQK.js} +7 -1
  13. package/dist/chunk-2EKHENQK.js.map +1 -0
  14. package/dist/{chunk-RTJNJTG3.js → chunk-5SYE4CCT.js} +42 -5
  15. package/dist/chunk-5SYE4CCT.js.map +1 -0
  16. package/dist/{chunk-IA4D5XNT.js → chunk-FJSZYEBM.js} +2 -2
  17. package/dist/{chunk-IA4D5XNT.js.map → chunk-FJSZYEBM.js.map} +1 -1
  18. package/dist/{chunk-ZRWXFDDS.js → chunk-J3KGJ6P6.js} +1 -1
  19. package/dist/{chunk-ZRWXFDDS.js.map → chunk-J3KGJ6P6.js.map} +1 -1
  20. package/dist/{chunk-7AKGPBCC.js → chunk-NCSWXG7G.js} +8 -4
  21. package/dist/chunk-NCSWXG7G.js.map +1 -0
  22. package/dist/{chunk-R75GGNWY.js → chunk-P4UXSYIJ.js} +58 -8
  23. package/dist/chunk-P4UXSYIJ.js.map +1 -0
  24. package/dist/{chunk-KFYANKJI.js → chunk-PWQAVNON.js} +3 -3
  25. package/dist/{chunk-KFYANKJI.js.map → chunk-PWQAVNON.js.map} +1 -1
  26. package/dist/{chunk-OBAFAH5B.js → chunk-TJK44RQC.js} +33 -8
  27. package/dist/chunk-TJK44RQC.js.map +1 -0
  28. package/dist/{chunk-NAPE7WUS.js → chunk-W2AC6Q2A.js} +9 -1
  29. package/dist/chunk-W2AC6Q2A.js.map +1 -0
  30. package/dist/collapsible-panel.js +2 -2
  31. package/dist/content-item.d.ts +7 -3
  32. package/dist/content-item.js +1 -1
  33. package/dist/grid.d.ts +5 -1
  34. package/dist/grid.js +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +6 -6
  37. package/dist/list.d.ts +5 -1
  38. package/dist/list.js +1 -1
  39. package/dist/popup-menu-register.js +181 -5
  40. package/dist/popup-menu-register.js.map +1 -1
  41. package/dist/popup-menu.d.ts +2 -1
  42. package/dist/popup-menu.js +1 -1
  43. package/dist/styles/catalog/components/catalog-example.css +4 -0
  44. package/dist/styles/content-item.css +11 -2
  45. package/dist/styles/grid.css +7 -0
  46. package/dist/styles/popup-menu.css +9 -0
  47. package/dist/styles/segmented-control.css +3 -0
  48. package/dist/styles/token-search-field.css +51 -5
  49. package/dist/styles/toolbar-control-group.css +14 -0
  50. package/dist/styles/workbench.css +7 -25
  51. package/dist/{token-search-field-DvgFLhLL.d.ts → token-search-field-CVGwDn2y.d.ts} +30 -6
  52. package/dist/token-search-field.d.ts +1 -1
  53. package/dist/token-search-field.js +1 -1
  54. package/dist/token-search-model.d.ts +1 -1
  55. package/dist/wire-catalog.js +1 -1
  56. package/dist/wire-content-items.d.ts +3 -1
  57. package/dist/wire-content-items.js +30 -6
  58. package/dist/wire-content-items.js.map +1 -1
  59. package/dist/wire-token-search-fields.d.ts +1 -1
  60. package/dist/wire-token-search-fields.js +1 -1
  61. package/dist/wire-workbench.js +1 -1
  62. package/dist/workbench.js +1 -1
  63. package/docs/accessibility.md +29 -4
  64. package/docs/catalog.md +6 -2
  65. package/docs/component-contract.md +34 -2
  66. package/docs/component-selection.md +16 -0
  67. package/docs/layout.md +42 -10
  68. package/docs/surface-scaffold.md +6 -3
  69. package/docs/ux-demo.md +1 -0
  70. package/docs/workbench.md +5 -1
  71. package/package.json +1 -1
  72. package/dist/chunk-7AKGPBCC.js.map +0 -1
  73. package/dist/chunk-NAPE7WUS.js.map +0 -1
  74. package/dist/chunk-OBAFAH5B.js.map +0 -1
  75. package/dist/chunk-R75GGNWY.js.map +0 -1
  76. package/dist/chunk-RTJNJTG3.js.map +0 -1
  77. 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-DvgFLhLL.js';
3
+ export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, j as TokenSearchToken, k as TokenSearchTrailingAction, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-field-CVGwDn2y.js';
@@ -1,4 +1,4 @@
1
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-RTJNJTG3.js';
1
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-5SYE4CCT.js';
2
2
 
3
3
 
4
4
 
@@ -1,3 +1,3 @@
1
1
  import 'kerfjs';
2
- export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, k as createTokenSearchModel } from './token-search-field-DvgFLhLL.js';
2
+ export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, l as createTokenSearchModel } from './token-search-field-CVGwDn2y.js';
3
3
  import './semantic-content-BbzjvSu9.js';
@@ -1,4 +1,4 @@
1
- import { wireWorkbench } from './chunk-OBAFAH5B.js';
1
+ import { wireWorkbench } from './chunk-TJK44RQC.js';
2
2
 
3
3
  import { wireScrollDividers } from './chunk-JCV4GXWB.js';
4
4
 
@@ -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, target) {
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 nested = target.closest(NESTED_CONTROL_SELECTOR);
10
- if (nested && nested !== item && item.contains(nested)) return null;
10
+ for (const node of event.composedPath()) {
11
+ if (node === item) break;
12
+ if (node instanceof Element && node.matches(NESTED_CONTROL_SELECTOR))
13
+ return null;
14
+ }
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.target);
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.target) && item.ownerDocument.activeElement === item)
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":"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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, target: EventTarget | null) {\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 const nested = target.closest(NESTED_CONTROL_SELECTOR);\n if (nested && nested !== item && item.contains(nested)) return null;\n return item;\n}\n\n/**\n * Give interactive ContentItems native-like Enter/Space activation with one\n * delegated listener pair. Space activates on release and is cancelled if\n * focus moves; the item's own `data-action` still goes through the app's\n * ordinary delegated click handler. Returns a disposer.\n */\nexport function wireContentItems(root: HTMLElement): () => void {\n let pendingSpace: HTMLElement | null = null;\n\n const clearSpace = () => {\n pendingSpace?.removeAttribute('data-kui-pressed');\n pendingSpace = null;\n };\n\n const keydown = (event: KeyboardEvent) => {\n if (\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey\n )\n return;\n const item = actionItem(root, event.target);\n if (!item) return;\n if (event.key === 'Enter') {\n event.preventDefault();\n item.click();\n return;\n }\n if (event.key === ' ' || event.key === 'Spacebar') {\n event.preventDefault();\n if (event.repeat) return;\n clearSpace();\n pendingSpace = item;\n item.setAttribute('data-kui-pressed', 'true');\n }\n };\n\n const keyup = (event: KeyboardEvent) => {\n if (event.key !== ' ' && event.key !== 'Spacebar') return;\n const item = pendingSpace;\n if (!item) return;\n event.preventDefault();\n clearSpace();\n if (\n item === actionItem(root, event.target) &&\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\"]';\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,5 +1,5 @@
1
1
  import { Signal } from 'kerfjs';
2
- import { d as TokenSearchModel } from './token-search-field-DvgFLhLL.js';
2
+ import { d as TokenSearchModel } from './token-search-field-CVGwDn2y.js';
3
3
  import './semantic-content-BbzjvSu9.js';
4
4
 
5
5
  interface TokenSearchSubmit {
@@ -1,4 +1,4 @@
1
- export { wireTokenSearchFields } from './chunk-R75GGNWY.js';
1
+ export { wireTokenSearchFields } from './chunk-P4UXSYIJ.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- export { wireWorkbench } from './chunk-OBAFAH5B.js';
1
+ export { wireWorkbench } from './chunk-TJK44RQC.js';
2
2
 
3
3
 
4
4
 
package/dist/workbench.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Workbench } from './chunk-KFYANKJI.js';
1
+ export { Workbench } from './chunk-PWQAVNON.js';
2
2
 
3
3
 
4
4
 
@@ -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 have a keyboard reachable button or option role. Set `selectionMode="toggle"` for `aria-pressed`, or `selectionMode="single"` inside a listbox for `aria-selected`; wire `wireContentItems` once so Enter and Space activate their delegated action. Disabled cards expose `aria-disabled` and leave the Tab order.
18
+ - Interactive `ContentItem` cards are keyboard reachable. Set `selectionMode="toggle"` for a pressed button, `selectionMode="single"` for an option inside a listbox, or `selectionMode="multiple"` for a selectable row with a gridcell that can contain native controls. Place multiple-mode rows inside a labeled `List 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. Children own their native or ARIA semantics. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
25
+ - `List` is layout-only by default and adds no `list` role. With `selectionMode="multiple"` and a required `ariaLabel`, it becomes a multi-select grid for `ContentItem` rows. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
26
+
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. Set `checked` to `true` or `false` for checkbox menu semantics; omit it
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. `wireTokenSearchFields`
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 `align="none"`
121
- (the default) for a content item or composition that owns its geometry.
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. Compact mixed controls retain the standard
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`. Keep interactive descendants outside a
215
- card; a card is one action target. Hover, pressed, focus, and selected paints
216
- reuse the reserved border without changing geometry. `rootAttributes` still
217
- accepts safe app `data-*` metadata, while the card owns its interaction data.
218
-
219
- The rendered classes — `.kui-content-item`, `.kui-content-item--framed`, and
220
- `.kui-content-item--pill` — stay public for the rare element that must carry the
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. Grid is
420
- layout-only and adds no ARIA grid role. The application owns child semantics.
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
 
@@ -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, focus lifecycle,
6
- dismissal policy, ids, labels, and content.
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
- rest of an app switches by viewport width and its Workbench can be narrower
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.63",
3
+ "version": "5.0.0-beta.65",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/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"]}