@kerfjs/ui 5.0.0-beta.57 → 5.0.0-beta.59

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 (79) hide show
  1. package/README.md +29 -6
  2. package/ai/component-catalog.json +51 -16
  3. package/ai/component-catalog.schema.json +16 -0
  4. package/ai/component-composition.json +16 -2
  5. package/ai/components/README.md +2 -2
  6. package/ai/components/popup-menu.md +96 -7
  7. package/ai/components/text.md +3 -3
  8. package/ai/components/token-search-field.md +6 -4
  9. package/ai/components/toolbar-control-group.md +2 -0
  10. package/ai/public-api-signatures-v1.md +67 -97
  11. package/ai/skill.md +6 -2
  12. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  13. package/dist/catalog-resources.d.ts +1 -1
  14. package/dist/catalog.d.ts +1 -1
  15. package/dist/catalog.js +5 -5
  16. package/dist/{chunk-G2RKFVRB.js → chunk-2CCMJTVY.js} +2 -2
  17. package/dist/chunk-2CCMJTVY.js.map +1 -0
  18. package/dist/{chunk-ZIYOJFZD.js → chunk-7JUBXOIV.js} +17 -4
  19. package/dist/chunk-7JUBXOIV.js.map +1 -0
  20. package/dist/chunk-F7F7KRO2.js +187 -0
  21. package/dist/chunk-F7F7KRO2.js.map +1 -0
  22. package/dist/{chunk-XCO6YCYM.js → chunk-QPUPLNNB.js} +16 -5
  23. package/dist/chunk-QPUPLNNB.js.map +1 -0
  24. package/dist/{chunk-63ZWBGE3.js → chunk-RRB26JRJ.js} +25 -3
  25. package/dist/chunk-RRB26JRJ.js.map +1 -0
  26. package/dist/{chunk-NBYTIVHS.js → chunk-UFYZHYWU.js} +118 -12
  27. package/dist/chunk-UFYZHYWU.js.map +1 -0
  28. package/dist/{chunk-WLG6SSNT.js → chunk-UWONG4QE.js} +41 -8
  29. package/dist/chunk-UWONG4QE.js.map +1 -0
  30. package/dist/{chunk-553ASUK6.js → chunk-UX2VVXZ3.js} +2 -2
  31. package/dist/{chunk-553ASUK6.js.map → chunk-UX2VVXZ3.js.map} +1 -1
  32. package/dist/{chunk-24PHKUR3.js → chunk-UXT7TE6B.js} +1 -1
  33. package/dist/chunk-UXT7TE6B.js.map +1 -0
  34. package/dist/{chunk-PWXNJS2B.js → chunk-W4XKTST6.js} +2 -2
  35. package/dist/{chunk-PWXNJS2B.js.map → chunk-W4XKTST6.js.map} +1 -1
  36. package/dist/collapsible-panel.d.ts +8 -8
  37. package/dist/collapsible-panel.js +2 -2
  38. package/dist/collapsible-panel.js.map +1 -1
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.js +7 -6
  41. package/dist/list-header.js +1 -1
  42. package/dist/nav-stack.js +1 -1
  43. package/dist/{panel-toolbar-BN4x0rth.d.ts → panel-toolbar-DqwtBxQL.d.ts} +10 -11
  44. package/dist/popup-menu.d.ts +31 -3
  45. package/dist/popup-menu.js +1 -1
  46. package/dist/split-view.js +1 -1
  47. package/dist/styles/popup-menu.css +33 -4
  48. package/dist/styles/text.css +8 -0
  49. package/dist/styles/token-search-field.css +29 -0
  50. package/dist/text.d.ts +1 -1
  51. package/dist/text.js +1 -1
  52. package/dist/token-search-field-CCke1yvQ.d.ts +126 -0
  53. package/dist/token-search-field.d.ts +3 -75
  54. package/dist/token-search-field.js +1 -1
  55. package/dist/token-search-model.d.ts +3 -0
  56. package/dist/token-search-model.js +3 -0
  57. package/dist/token-search-model.js.map +1 -0
  58. package/dist/toolbar-control-group.d.ts +3 -1
  59. package/dist/toolbar-control-group.js +1 -1
  60. package/dist/wire-token-search-fields.d.ts +5 -1
  61. package/dist/wire-token-search-fields.js +1 -1
  62. package/dist/workbench.d.ts +5 -5
  63. package/dist/workbench.js +1 -1
  64. package/docs/accessibility.md +14 -0
  65. package/docs/collapsible-panel.md +12 -7
  66. package/docs/component-contract.md +2 -1
  67. package/docs/component-integration.md +8 -1
  68. package/docs/component-selection.md +43 -2
  69. package/docs/layout.md +4 -1
  70. package/docs/workbench.md +12 -11
  71. package/llms.txt +6 -3
  72. package/package.json +5 -1
  73. package/dist/chunk-24PHKUR3.js.map +0 -1
  74. package/dist/chunk-63ZWBGE3.js.map +0 -1
  75. package/dist/chunk-G2RKFVRB.js.map +0 -1
  76. package/dist/chunk-NBYTIVHS.js.map +0 -1
  77. package/dist/chunk-WLG6SSNT.js.map +0 -1
  78. package/dist/chunk-XCO6YCYM.js.map +0 -1
  79. package/dist/chunk-ZIYOJFZD.js.map +0 -1
package/README.md CHANGED
@@ -39,13 +39,15 @@ npm install kerfjs @kerfjs/ui
39
39
  ```
40
40
 
41
41
  ```tsx
42
+ import { mount } from "kerfjs";
42
43
  import { ListItem } from "@kerfjs/ui/list-item";
43
44
  import { ListHeader } from "@kerfjs/ui/list-header";
44
45
  import { ListActionRow } from "@kerfjs/ui/list-action-row";
45
46
  import { Toolbar } from "@kerfjs/ui/toolbar";
46
- import { ToolbarControlGroup } from "@kerfjs/ui/toolbar-control-group";
47
47
  import { ToolbarText } from "@kerfjs/ui/toolbar-text";
48
48
 
49
+ const root = document.getElementById("app")!;
50
+
49
51
  mount(root, () => (
50
52
  <>
51
53
  <Toolbar
@@ -80,7 +82,7 @@ mount(root, () => (
80
82
  label="src/main.ts"
81
83
  trailingAction="open-file-actions"
82
84
  trailingActionLabel="Actions for src/main.ts"
83
- trailingActionIcon={moreIcon}
85
+ trailingActionIcon={<span aria-hidden="true">⋯</span>}
84
86
  />
85
87
  <div id="workspace-actions" popover="auto">
86
88
  Application-owned actions
@@ -222,17 +224,29 @@ interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
222
224
  | `SegmentedControl` | `@kerfjs/ui/segmented-control` | `@kerfjs/ui/segmented-control.css` |
223
225
  | `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret` | `@kerfjs/ui/token-search-field` | `@kerfjs/ui/token-search-field.css` |
224
226
  | `wireTokenSearchFields` | `@kerfjs/ui/wire-token-search-fields` | — |
227
+ | `createTokenSearchModel` | `@kerfjs/ui/token-search-model` | — |
225
228
  | `StateBanner` | `@kerfjs/ui/state-banner` | `@kerfjs/ui/state-banner.css` |
226
229
  | `EmptyState` | `@kerfjs/ui/empty-state` | `@kerfjs/ui/empty-state.css` |
227
230
  | `ValueTable`, `ValueTableRow` | `@kerfjs/ui/value-table` | `@kerfjs/ui/value-table.css` |
228
231
 
229
- Opt-in application layouts keep JavaScript and CSS explicit. Import each
230
- layout from its component subpath, load the matching manual CSS export, and
231
- add its wire subpath when the layout has interactive behavior. The catalog
232
+ Application layouts are separate component subpaths. Browser-condition imports
233
+ include their reachable CSS; use the matching manual CSS export when your
234
+ bundler does not select the browser condition. Add a wire subpath when the
235
+ layout has interactive behavior. The catalog
232
236
  entries for `nav-stack`, `split-view`, `tab-scaffold`, `workbench`, and
233
237
  `collapsible-panel` document their selection rules, ownership boundaries,
234
238
  public classes, tokens, and focused demos. See
235
239
  [`docs/app-layouts.md`](./docs/app-layouts.md) for the layout decision matrix.
240
+ `NavStack` and `TabScaffold` expose `chromeDividers="scroll" | "always" |
241
+ "none"` for their chrome lines; `SplitView` forwards the setting to its
242
+ compact stack. A sole `Pane` in a view or scene fills it, keeping the Pane's
243
+ header in place while its own content scrolls.
244
+
245
+ For a `Workbench` or `CollapsiblePanel`, place panel toolbar groups in its
246
+ `leading`, `center`, and `trailing` zones. Add `relocateOnCollapse` to each
247
+ `ToolbarControlGroup` that should remain available in the work-area toolbar
248
+ while the panel is closed. The panel collects marked groups in zone order;
249
+ its toggle follows them. See [Workbench panel toolbars](./docs/workbench.md#panel-toolbars).
236
250
 
237
251
  Compose panel, dialog, and page headings directly with `Toolbar`: put an optional
238
252
  icon in a `ToolbarControlGroup`, use a direct extra-large `ToolbarText` for the
@@ -625,7 +639,14 @@ value through `data-segment-value`. Override an instance through
625
639
  free text and ordered filter chips in one field. Its editable text stays
626
640
  DOM-owned between token changes so typing does not replace the caret. Tokens
627
641
  expose stable edit/remove actions and remain atomic during editing; the app owns
628
- parsing, suggestions, query execution, and state. Use `readTokenSearchField()`
642
+ parsing, suggestions, query execution, and state in manual mode. For automatic
643
+ handling, create a model with `createTokenSearchModel({ rules, evaluate })`,
644
+ pass it as the field's `model`, and register it under the field id in
645
+ `wireTokenSearchFields(root, { models })`. Rules parse `name:value` expressions;
646
+ their optional `suggest` function supplies selectable completions. The model
647
+ commits valid expressions on whitespace or Enter, owns chips and clear/edit/
648
+ remove actions, and exposes `state`, `suggestions`, and the latest `result` as
649
+ signals. Omit the model to retain full manual control. Use `readTokenSearchField()`
629
650
  after browser input to recover text plus token offsets, and
630
651
  `placeTokenSearchCaret()` when restoring focus after a controlled update.
631
652
  Call `wireTokenSearchFields()` once at a stable root to make Enter submit through
@@ -680,6 +701,8 @@ import "@kerfjs/ui/select/register";
680
701
  ```
681
702
 
682
703
  That boundary keeps Web Awesome and its custom-element side effects out of bundles that use unrelated components. Automated consumer-bundle tests enforce it. `PopupMenu` follows the same rule: import `@kerfjs/ui/popup-menu/register` once to register the dropdown, item, button, and divider elements it renders. Either registration also installs help tags: an icon-only Select, PopupMenu, or `aria-label`-named `<button>` in a `ToolbarControlGroup` shows its name in an `aria-hidden` tooltip on hover and keyboard focus. A toolbar with icon-only buttons but neither control imports `@kerfjs/ui/help-tags/register`, which registers only the tooltip.
704
+
705
+ `PopupMenu` items may include `submenu` children, `checked` choices, a leading `icon`, trailing `details`, `disabledReason`, and a `danger` tone. The app owns choice state and dispatches each item's `action`. For a right-click menu, set `context: true` and a `label`, then call `openPopupMenuAt(menu, event.clientX, event.clientY)` from the app's `contextmenu` handler; `closePopupMenu(menu)` closes it programmatically. See [component selection](docs/component-selection.md#popup-menu-migration) for examples.
683
706
  Use `label` for a visible label or `ariaLabel` for a visually hidden name. A
684
707
  nonempty visible label takes precedence when both are supplied. Kerf names the
685
708
  actual shadow combobox without adding visible label spacing; this also works
@@ -1498,7 +1498,8 @@
1498
1498
  "Group related controls inside a toolbar with a supported visual treatment.",
1499
1499
  "Use ToolbarActionLink for navigation or external resources that must retain native anchor semantics inside a group.",
1500
1500
  "Fill it with plain <button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Nothing wraps inside a group: an icon sits beside its label on one row and no trigger gets a fixed width.",
1501
- "For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight."
1501
+ "For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight.",
1502
+ "In a Workbench or CollapsiblePanel toolbar, mark a group relocateOnCollapse to keep it available in the work-area toolbar when the panel closes; place it directly or in an array in any toolbar zone."
1502
1503
  ],
1503
1504
  "avoidWhen": [
1504
1505
  "Do not use merely to align unrelated buttons or to imitate an exclusive choice.",
@@ -1535,6 +1536,7 @@
1535
1536
  "standard, compact, or list-zero dropdown menu inset",
1536
1537
  "visible or horizontally scrollable overflow",
1537
1538
  "always visible or compact-only responsive visibility",
1539
+ "relocate on panel collapse",
1538
1540
  "avatar scrim",
1539
1541
  "contained avatar background on a single group or selected multi-button highlight"
1540
1542
  ],
@@ -1838,7 +1840,7 @@
1838
1840
  "source": "kerf",
1839
1841
  "description": "Semantic native paragraph text by default, with heading and inline-span variants plus composable semantic presentation roles.",
1840
1842
  "uses": [],
1841
- "purpose": "Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact size, or monospace font.",
1843
+ "purpose": "Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact through extra-large size, or monospace font.",
1842
1844
  "publicExports": [
1843
1845
  "Text"
1844
1846
  ],
@@ -1870,7 +1872,7 @@
1870
1872
  "variants": [
1871
1873
  "p, h1 through h6, or inline span semantic variant",
1872
1874
  "default, quiet, or danger tone",
1873
- "default or compact size",
1875
+ "compact, default, large, or xlarge size",
1874
1876
  "default or monospace font"
1875
1877
  ],
1876
1878
  "accessibility": [
@@ -1900,6 +1902,8 @@
1900
1902
  "publicTokens": [
1901
1903
  "--kui-color-danger-on-quiet",
1902
1904
  "--kui-color-text-quiet",
1905
+ "--kui-font-2xl",
1906
+ "--kui-font-l",
1903
1907
  "--kui-font-mono",
1904
1908
  "--kui-font-xs",
1905
1909
  "--kui-layout-item-padding"
@@ -3639,8 +3643,13 @@
3639
3643
  "TokenSearchField",
3640
3644
  "readTokenSearchField",
3641
3645
  "placeTokenSearchCaret",
3642
- "wireTokenSearchFields"
3646
+ "wireTokenSearchFields",
3647
+ "createTokenSearchModel"
3643
3648
  ],
3649
+ "publicExportImports": {
3650
+ "wireTokenSearchFields": "@kerfjs/ui/wire-token-search-fields",
3651
+ "createTokenSearchModel": "@kerfjs/ui/token-search-model"
3652
+ },
3644
3653
  "useWhen": [
3645
3654
  "Combine DOM-owned free text with ordered editable/removable filter tokens in one searchbox.",
3646
3655
  "Enable collapsible mode when an empty, unfocused search should reduce to one iconic action, standalone or inside a toolbar group."
@@ -3655,10 +3664,7 @@
3655
3664
  }
3656
3665
  ],
3657
3666
  "appOwns": [
3658
- "parsing",
3659
- "suggestions",
3660
- "tokens",
3661
- "query execution",
3667
+ "the grammar and evaluation callback when using the optional managed model, or parsing, suggestions, tokens, and query execution in manual mode",
3662
3668
  "results",
3663
3669
  "the revision that rebuilds the DOM-owned editor for a programmatic text replacement",
3664
3670
  "optional override of the collapsible expand/collapse/focus, which wireTokenSearchFields manages by default (adopt an app expanded signal, opt out per behavior, or drive it via the returned handle)"
@@ -3725,6 +3731,8 @@
3725
3731
  "kui-token-search__token",
3726
3732
  "kui-token-search__token-edit",
3727
3733
  "kui-token-search__token-remove",
3734
+ "kui-token-search__suggestions",
3735
+ "kui-token-search__suggestion",
3728
3736
  "kui-token-search__trailing"
3729
3737
  ],
3730
3738
  "publicTokens": [
@@ -4016,20 +4024,23 @@
4016
4024
  "category": "Controls",
4017
4025
  "kind": "component",
4018
4026
  "source": "kerf",
4019
- "description": "A trigger button that opens a typed list of commands, built on the Web Awesome dropdown.",
4027
+ "description": "A typed action menu with nested choices, selected state, and optional pointer-anchored context opening, built on the Web Awesome dropdown.",
4020
4028
  "uses": [
4021
4029
  "wa-dropdown",
4022
4030
  "wa-dropdown-item",
4023
4031
  "wa-button",
4024
4032
  "wa-divider"
4025
4033
  ],
4026
- "purpose": "A trigger button that opens a typed list of commands, built on the Web Awesome dropdown.",
4034
+ "purpose": "A typed action menu with nested choices, selected state, and optional pointer-anchored context opening, built on the Web Awesome dropdown.",
4027
4035
  "publicExports": [
4028
- "PopupMenu"
4036
+ "PopupMenu",
4037
+ "openPopupMenuAt",
4038
+ "closePopupMenu"
4029
4039
  ],
4030
4040
  "useWhen": [
4031
4041
  "Offer a short list of commands behind one trigger, such as a toolbar sort or More menu.",
4032
- "Group related commands under headings and dividers in one popup."
4042
+ "Group related commands under headings and dividers in one popup.",
4043
+ "Nest decisions under a parent command, show checked or trailing choice state, or open a context menu at pointer coordinates."
4033
4044
  ],
4034
4045
  "avoidWhen": [
4035
4046
  "Do not use it to choose a persistent value (use Select) or for navigation rows (use ListItem).",
@@ -4048,7 +4059,8 @@
4048
4059
  "appOwns": [
4049
4060
  "commands and their delegated actions",
4050
4061
  "which items are enabled",
4051
- "reactions to the menu opening or closing"
4062
+ "reactions to the menu opening or closing",
4063
+ "contextmenu event handling and identification of the menu root when using a pointer-anchored context menu"
4052
4064
  ],
4053
4065
  "variants": [
4054
4066
  "icon trigger",
@@ -4057,10 +4069,31 @@
4057
4069
  "headings and dividers",
4058
4070
  "item icons",
4059
4071
  "disabled items",
4072
+ "nested actions",
4073
+ "checked choices and trailing details",
4074
+ "danger tone and disabled reasons",
4075
+ "context trigger",
4060
4076
  "placement"
4061
4077
  ],
4078
+ "usageExamples": [
4079
+ {
4080
+ "title": "Nested decisions",
4081
+ "description": "The app owns the current decision, batch disabled state, and delegated actions. Each child keeps its own record metadata.",
4082
+ "code": "<PopupMenu\n label=\"Line actions\"\n items={[\n { label: 'Decide', submenu: [\n { label: 'Approve', action: 'decide', checked: current === 'approve',\n attributes: { 'data-line-id': lineId, 'data-decision': 'approve' } },\n { label: 'Reject', action: 'decide', tone: 'danger',\n disabled: needsPrice, disabledReason: needsPrice ? 'Add a price first' : undefined,\n attributes: { 'data-line-id': lineId, 'data-decision': 'reject' } },\n ] },\n { kind: 'divider' },\n { label: 'Remove', action: 'remove', tone: 'danger' },\n ]}\n/>"
4083
+ },
4084
+ {
4085
+ "title": "Flat selected choices",
4086
+ "description": "Headings separate sort dimensions; trailing safe content marks the active command without changing its accessible label.",
4087
+ "code": "<PopupMenu\n label=\"Sort chat\"\n items={[\n { kind: 'heading', label: 'Sort by' },\n { label: 'Updated', action: 'sort-updated', details: sort === 'updated' ? tick : undefined },\n { label: 'Priority', action: 'sort-priority', details: sort === 'priority' ? tick : undefined },\n { kind: 'heading', label: 'Order' },\n { label: 'Newest first', action: 'order-newest', details: order === 'newest' ? tick : undefined },\n ]}\n/>"
4088
+ },
4089
+ {
4090
+ "title": "Pointer anchored context menu",
4091
+ "description": "Keep the row's visible disabled action separate from the context menu anchor. Find the menu root with a stable data attribute.",
4092
+ "code": "<PopupMenu\n context\n label=\"Demand actions\"\n rootAttributes={{ 'data-demand-menu': '' }}\n items={[{ label: 'Open', action: 'open-demand' }]}\n/>\n// In the app's row wiring:\nrow.addEventListener('contextmenu', (event) => {\n event.preventDefault();\n const menu = row.querySelector('[data-demand-menu]') as PopupMenuElement;\n openPopupMenuAt(menu, event.clientX, event.clientY);\n});\n// Call closePopupMenu(menu) when the app needs to dismiss it."
4093
+ }
4094
+ ],
4062
4095
  "accessibility": [
4063
- "An icon-only trigger requires label, rendered as visually hidden slotted text because a Web Awesome button takes its accessible name from its content; a trigger with visible text is named by that text. Web Awesome provides menu roles, arrow-key navigation, typeahead, and Escape.",
4096
+ "An icon-only trigger requires label, rendered as visually hidden slotted text because a Web Awesome button takes its accessible name from its content; a trigger with visible text is named by that text. The context anchor stays out of the accessibility tree. Web Awesome provides menu roles, nested arrow-key navigation, typeahead, and Escape.",
4064
4097
  "Once registered, an icon-only trigger shows its label as an aria-hidden Web Awesome help tag after a 500ms hover and at once on keyboard focus; it hides while the menu is open, on a press, and on Escape, and never joins aria-labelledby. Do not add a title or another tooltip."
4065
4098
  ],
4066
4099
  "geometry": {
@@ -4081,13 +4114,15 @@
4081
4114
  "wiring": [],
4082
4115
  "publicClasses": [
4083
4116
  "kui-popup-menu",
4084
- "kui-popup-menu__label"
4117
+ "kui-popup-menu__label",
4118
+ "kui-popup-menu__details",
4119
+ "kui-popup-menu__context-anchor"
4085
4120
  ],
4086
4121
  "publicTokens": [],
4087
4122
  "links": {
4088
4123
  "catalogRoute": "?component=popup-menu",
4089
4124
  "documentation": "docs/component-selection.md",
4090
- "recipe": "docs/component-selection.md#web-awesome-overlap-policy"
4125
+ "recipe": "docs/component-selection.md#popup-menu-migration"
4091
4126
  }
4092
4127
  },
4093
4128
  {
@@ -29,6 +29,11 @@
29
29
  "purpose": { "type": "string", "minLength": 1 },
30
30
  "customElement": { "type": "string" },
31
31
  "publicExports": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
32
+ "publicExportImports": {
33
+ "description": "Subpath overrides for public exports that do not live in the component delivery import.",
34
+ "type": "object",
35
+ "additionalProperties": { "type": "string", "minLength": 1 }
36
+ },
32
37
  "useWhen": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
33
38
  "avoidWhen": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
34
39
  "recommendation": { "enum": ["supported", "conditional", "exceptional", "underlying", "avoid"] },
@@ -37,6 +42,7 @@
37
42
  "wiring": { "type": "array", "items": { "$ref": "#/$defs/wiring" } },
38
43
  "appOwns": { "type": "array", "items": { "type": "string" } },
39
44
  "variants": { "type": "array", "items": { "type": "string" } },
45
+ "usageExamples": { "type": "array", "items": { "$ref": "#/$defs/usageExample" } },
40
46
  "accessibility": { "type": "array", "items": { "type": "string" } },
41
47
  "publicClasses": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
42
48
  "placeableClasses": { "description": "Public classes an application may place on its own elements (layout utilities, a document root, or item geometry on a non-div carrier). Every other public class is component anatomy: render the component instead.", "type": "array", "items": { "type": "string" }, "uniqueItems": true },
@@ -49,6 +55,16 @@
49
55
  },
50
56
  "additionalProperties": false
51
57
  },
58
+ "usageExample": {
59
+ "type": "object",
60
+ "required": ["title", "code"],
61
+ "properties": {
62
+ "title": { "type": "string", "minLength": 1 },
63
+ "description": { "type": "string" },
64
+ "code": { "type": "string", "minLength": 1 }
65
+ },
66
+ "additionalProperties": false
67
+ },
52
68
  "geometry": {
53
69
  "type": "object",
54
70
  "required": ["margin", "border", "padding"],
@@ -1733,6 +1733,8 @@
1733
1733
  "publicTokens": [
1734
1734
  "--kui-color-danger-on-quiet",
1735
1735
  "--kui-color-text-quiet",
1736
+ "--kui-font-2xl",
1737
+ "--kui-font-l",
1736
1738
  "--kui-font-mono",
1737
1739
  "--kui-font-xs",
1738
1740
  "--kui-layout-item-padding"
@@ -3084,6 +3086,8 @@
3084
3086
  "kui-token-search__token",
3085
3087
  "kui-token-search__token-edit",
3086
3088
  "kui-token-search__token-remove",
3089
+ "kui-token-search__suggestions",
3090
+ "kui-token-search__suggestion",
3087
3091
  "kui-token-search__trailing"
3088
3092
  ],
3089
3093
  "publicTokens": [
@@ -3321,6 +3325,11 @@
3321
3325
  "id": "reactions to the menu opening or closing",
3322
3326
  "owner": "application",
3323
3327
  "required": false
3328
+ },
3329
+ {
3330
+ "id": "contextmenu event handling and identification of the menu root when using a pointer-anchored context menu",
3331
+ "owner": "application",
3332
+ "required": false
3324
3333
  }
3325
3334
  ],
3326
3335
  "wiring": {
@@ -3336,14 +3345,19 @@
3336
3345
  },
3337
3346
  "accessibility": {
3338
3347
  "obligations": [
3339
- "An icon-only trigger requires label, rendered as visually hidden slotted text because a Web Awesome button takes its accessible name from its content; a trigger with visible text is named by that text. Web Awesome provides menu roles, arrow-key navigation, typeahead, and Escape.",
3348
+ "An icon-only trigger requires label, rendered as visually hidden slotted text because a Web Awesome button takes its accessible name from its content; a trigger with visible text is named by that text. The context anchor stays out of the accessibility tree. Web Awesome provides menu roles, nested arrow-key navigation, typeahead, and Escape.",
3340
3349
  "Once registered, an icon-only trigger shows its label as an aria-hidden Web Awesome help tag after a 500ms hover and at once on keyboard focus; it hides while the menu is open, on a press, and on Escape, and never joins aria-labelledby. Do not add a title or another tooltip."
3341
3350
  ]
3342
3351
  },
3343
3352
  "cssValueProps": [],
3344
3353
  "boundaries": {
3345
3354
  "rootClass": "kui-popup-menu",
3346
- "publicClasses": ["kui-popup-menu", "kui-popup-menu__label"],
3355
+ "publicClasses": [
3356
+ "kui-popup-menu",
3357
+ "kui-popup-menu__label",
3358
+ "kui-popup-menu__details",
3359
+ "kui-popup-menu__context-anchor"
3360
+ ],
3347
3361
  "publicTokens": []
3348
3362
  },
3349
3363
  "diagnostics": [],
@@ -29,7 +29,7 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
29
29
  - [Surface scaffolds](./surface-scaffold.md) — Configure recurring dialog and popup geometry without consumer ::part() overrides.
30
30
  - [FloatingToolbar](./floating-toolbar.md) — A transparent, forced-dark toolbar that floats over its container's content.
31
31
  - [ToolbarText](./toolbar-text.md) — Large, default, and compact toolbar identity text.
32
- - [Text](./text.md) — Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact size, or monospace font.
32
+ - [Text](./text.md) — Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact through extra-large size, or monospace font.
33
33
  - [Headers](./headers.md) — Page, dialog, and definition-list hierarchy.
34
34
  - [ValueTable](./value-table.md) — Key/value rows with optional icons and aligned separators.
35
35
  - [Row](./row.md) — Arrange related content horizontally with consistent spacing, optional physical-side text/control insets, and explicit left/center/right/full plus top/middle/bottom/full/baseline alignment instead of application-owned flex wrappers.
@@ -55,7 +55,7 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
55
55
  - [TokenSearchField](./token-search-field.md) — Token-controlled, optionally collapsible search editor with ordered, editable, removable filter chips.
56
56
  - [ResizableRegion](./resize.md) — Pointer and keyboard-operable split region with a customizable handle icon.
57
57
  - [Select](./select.md) — Controlled Web Awesome select with typed form, toolbar, navigation, compact, icon-only, truncating, hint, and grouped-choice presentations.
58
- - [PopupMenu](./popup-menu.md) — A trigger button that opens a typed list of commands, built on the Web Awesome dropdown.
58
+ - [PopupMenu](./popup-menu.md) — A typed action menu with nested choices, selected state, and optional pointer-anchored context opening, built on the Web Awesome dropdown.
59
59
 
60
60
  ## Kerf UI — Feedback
61
61
 
@@ -6,10 +6,11 @@
6
6
 
7
7
  ## When to use
8
8
 
9
- A trigger button that opens a typed list of commands, built on the Web Awesome dropdown.
9
+ A typed action menu with nested choices, selected state, and optional pointer-anchored context opening, built on the Web Awesome dropdown.
10
10
 
11
11
  - Offer a short list of commands behind one trigger, such as a toolbar sort or More menu.
12
12
  - Group related commands under headings and dividers in one popup.
13
+ - Nest decisions under a parent command, show checked or trailing choice state, or open a context menu at pointer coordinates.
13
14
 
14
15
  **Not when:**
15
16
 
@@ -23,7 +24,7 @@ A trigger button that opens a typed list of commands, built on the Web Awesome d
23
24
 
24
25
  ## Imports
25
26
 
26
- - JavaScript: `@kerfjs/ui/popup-menu` — exports `PopupMenu`.
27
+ - JavaScript: `@kerfjs/ui/popup-menu` — exports `PopupMenu`, `openPopupMenuAt`, `closePopupMenu`.
27
28
  - CSS: the browser build of `@kerfjs/ui/popup-menu` pulls its CSS automatically; import `@kerfjs/ui/popup-menu.css` manually only without the `browser` export condition.
28
29
  - Theme CSS: import `@kerfjs/ui/webawesome.css` once.
29
30
  - Registration: import `@kerfjs/ui/popup-menu/register` once (a side effect).
@@ -41,6 +42,10 @@ Exact prop names and types: [`@kerfjs/ui/popup-menu`](../public-api-signatures-v
41
42
  - headings and dividers
42
43
  - item icons
43
44
  - disabled items
45
+ - nested actions
46
+ - checked choices and trailing details
47
+ - danger tone and disabled reasons
48
+ - context trigger
44
49
  - placement
45
50
 
46
51
  ## Composition
@@ -52,7 +57,7 @@ Exact prop names and types: [`@kerfjs/ui/popup-menu`](../public-api-signatures-v
52
57
 
53
58
  ## State and wiring
54
59
 
55
- **The app owns:** commands and their delegated actions; which items are enabled; reactions to the menu opening or closing.
60
+ **The app owns:** commands and their delegated actions; which items are enabled; reactions to the menu opening or closing; contextmenu event handling and identification of the menu root when using a pointer-anchored context menu.
56
61
 
57
62
  No wiring helper.
58
63
 
@@ -62,21 +67,105 @@ Margin: none · border: child · padding: child (layout role: controls). `self`
62
67
 
63
68
  ## Accessibility
64
69
 
65
- - An icon-only trigger requires label, rendered as visually hidden slotted text because a Web Awesome button takes its accessible name from its content; a trigger with visible text is named by that text. Web Awesome provides menu roles, arrow-key navigation, typeahead, and Escape.
70
+ - An icon-only trigger requires label, rendered as visually hidden slotted text because a Web Awesome button takes its accessible name from its content; a trigger with visible text is named by that text. The context anchor stays out of the accessibility tree. Web Awesome provides menu roles, nested arrow-key navigation, typeahead, and Escape.
66
71
  - Once registered, an icon-only trigger shows its label as an aria-hidden Web Awesome help tag after a 500ms hover and at once on keyboard focus; it hides while the menu is open, on a press, and on Escape, and never joins aria-labelledby. Do not add a title or another tooltip.
67
72
 
73
+ ## Usage examples
74
+
75
+ ### Nested decisions
76
+
77
+ The app owns the current decision, batch disabled state, and delegated actions. Each child keeps its own record metadata.
78
+
79
+ ```tsx
80
+ <PopupMenu
81
+ label="Line actions"
82
+ items={[
83
+ {
84
+ label: 'Decide',
85
+ submenu: [
86
+ {
87
+ label: 'Approve',
88
+ action: 'decide',
89
+ checked: current === 'approve',
90
+ attributes: { 'data-line-id': lineId, 'data-decision': 'approve' },
91
+ },
92
+ {
93
+ label: 'Reject',
94
+ action: 'decide',
95
+ tone: 'danger',
96
+ disabled: needsPrice,
97
+ disabledReason: needsPrice ? 'Add a price first' : undefined,
98
+ attributes: { 'data-line-id': lineId, 'data-decision': 'reject' },
99
+ },
100
+ ],
101
+ },
102
+ { kind: 'divider' },
103
+ { label: 'Remove', action: 'remove', tone: 'danger' },
104
+ ]}
105
+ />
106
+ ```
107
+
108
+ ### Flat selected choices
109
+
110
+ Headings separate sort dimensions; trailing safe content marks the active command without changing its accessible label.
111
+
112
+ ```tsx
113
+ <PopupMenu
114
+ label="Sort chat"
115
+ items={[
116
+ { kind: 'heading', label: 'Sort by' },
117
+ {
118
+ label: 'Updated',
119
+ action: 'sort-updated',
120
+ details: sort === 'updated' ? tick : undefined,
121
+ },
122
+ {
123
+ label: 'Priority',
124
+ action: 'sort-priority',
125
+ details: sort === 'priority' ? tick : undefined,
126
+ },
127
+ { kind: 'heading', label: 'Order' },
128
+ {
129
+ label: 'Newest first',
130
+ action: 'order-newest',
131
+ details: order === 'newest' ? tick : undefined,
132
+ },
133
+ ]}
134
+ />
135
+ ```
136
+
137
+ ### Pointer anchored context menu
138
+
139
+ Keep the row's visible disabled action separate from the context menu anchor. Find the menu root with a stable data attribute.
140
+
141
+ ```tsx
142
+ <PopupMenu
143
+ context
144
+ label="Demand actions"
145
+ rootAttributes={{ 'data-demand-menu': '' }}
146
+ items={[{ label: 'Open', action: 'open-demand' }]}
147
+ />;
148
+ // In the app's row wiring:
149
+ row.addEventListener('contextmenu', (event) => {
150
+ event.preventDefault();
151
+ const menu = row.querySelector('[data-demand-menu]') as PopupMenuElement;
152
+ openPopupMenuAt(menu, event.clientX, event.clientY);
153
+ });
154
+ // Call closePopupMenu(menu) when the app needs to dismiss it.
155
+ ```
156
+
68
157
  ## Styling boundary
69
158
 
70
159
  The component owns its own styles. Configure it through its props and variants; do not override its internals.
71
160
 
72
- Public class hooks (select for layout placement only, never to change the component's look): `kui-popup-menu`, `kui-popup-menu__label`.
161
+ Public class hooks (select for layout placement only, never to change the component's look): `kui-popup-menu`, `kui-popup-menu__label`, `kui-popup-menu__details`, `kui-popup-menu__context-anchor`.
73
162
 
74
- Never put `kui-popup-menu`, `kui-popup-menu__label` on an element you write; render `PopupMenu` instead (`KUI-L103`).
163
+ Never put `kui-popup-menu`, `kui-popup-menu__label`, `kui-popup-menu__details`, `kui-popup-menu__context-anchor` on an element you write; render `PopupMenu` instead (`KUI-L103`).
75
164
 
76
165
  Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
77
166
 
78
167
  ## Related
79
168
 
80
169
  - Components: [Dropdown](./wa-dropdown.md), [Dropdown Item](./wa-dropdown-item.md), [Button](./wa-button.md), [Divider](./wa-divider.md), [Select](./select.md).
81
- - Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/component-selection.md#web-awesome-overlap-policy`](../../docs/component-selection.md#web-awesome-overlap-policy).
170
+ - Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/component-selection.md#popup-menu-migration`](../../docs/component-selection.md#popup-menu-migration).
82
171
  - UX catalog route: `?component=popup-menu`.
@@ -8,7 +8,7 @@ Semantic native paragraph text by default, with heading and inline-span variants
8
8
 
9
9
  ## When to use
10
10
 
11
- Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact size, or monospace font.
11
+ Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact through extra-large size, or monospace font.
12
12
 
13
13
  - Render ordinary application headings or paragraphs with native document semantics, or inline secondary text inside rows, labels, and table cells.
14
14
 
@@ -36,7 +36,7 @@ Exact prop names and types: [`@kerfjs/ui/text`](../public-api-signatures-v1.md#k
36
36
 
37
37
  - p, h1 through h6, or inline span semantic variant
38
38
  - default, quiet, or danger tone
39
- - default or compact size
39
+ - compact, default, large, or xlarge size
40
40
  - default or monospace font
41
41
 
42
42
  ## Composition
@@ -71,7 +71,7 @@ Public class hooks (select for layout placement only, never to change the compon
71
71
 
72
72
  Never put `kui-text` on an element you write; render `Text` instead (`KUI-L103`).
73
73
 
74
- Public tokens it reads: `--kui-color-danger-on-quiet`, `--kui-color-text-quiet`, `--kui-font-mono`, `--kui-font-xs`, `--kui-layout-item-padding`. Set a token only where its public contract allows; prefer a prop.
74
+ Public tokens it reads: `--kui-color-danger-on-quiet`, `--kui-color-text-quiet`, `--kui-font-2xl`, `--kui-font-l`, `--kui-font-mono`, `--kui-font-xs`, `--kui-layout-item-padding`. Set a token only where its public contract allows; prefer a prop.
75
75
 
76
76
  Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
77
77
 
@@ -21,7 +21,9 @@ Token-controlled, optionally collapsible search editor with ordered, editable, r
21
21
 
22
22
  ## Imports
23
23
 
24
- - JavaScript: `@kerfjs/ui/token-search-field` — exports `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields`.
24
+ - JavaScript: `@kerfjs/ui/token-search-field` — exports `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`.
25
+ - JavaScript: `@kerfjs/ui/wire-token-search-fields` — exports `wireTokenSearchFields`.
26
+ - JavaScript: `@kerfjs/ui/token-search-model` — exports `createTokenSearchModel`.
25
27
  - CSS: the browser build of `@kerfjs/ui/token-search-field` pulls its CSS automatically; import `@kerfjs/ui/token-search-field.css` manually only without the `browser` export condition.
26
28
  - Wiring: `readTokenSearchField` from `@kerfjs/ui/token-search-field` (required).
27
29
  - Wiring: `placeTokenSearchCaret` from `@kerfjs/ui/token-search-field` (optional).
@@ -30,7 +32,7 @@ Token-controlled, optionally collapsible search editor with ordered, editable, r
30
32
 
31
33
  ## Props
32
34
 
33
- Exact prop names and types: [`@kerfjs/ui/token-search-field`](../public-api-signatures-v1.md#kerfjsuitoken-search-field), [`@kerfjs/ui/wire-token-search-fields`](../public-api-signatures-v1.md#kerfjsuiwire-token-search-fields) in `public-api-signatures-v1.md`. Do not infer props from examples.
35
+ Exact prop names and types: [`@kerfjs/ui/token-search-field`](../public-api-signatures-v1.md#kerfjsuitoken-search-field), [`@kerfjs/ui/wire-token-search-fields`](../public-api-signatures-v1.md#kerfjsuiwire-token-search-fields), [`@kerfjs/ui/token-search-model`](../public-api-signatures-v1.md#kerfjsuitoken-search-model) in `public-api-signatures-v1.md`. Do not infer props from examples.
34
36
 
35
37
  **Variants (configure, do not restyle):**
36
38
 
@@ -82,9 +84,9 @@ Margin: none · border: self · padding: self (layout role: controls). `self` me
82
84
 
83
85
  The component owns its own styles. Configure it through its props and variants; do not override its internals.
84
86
 
85
- Public class hooks (select for layout placement only, never to change the component's look): `kui-token-search`, `kui-token-search__clear`, `kui-token-search__editor`, `kui-token-search__expand`, `kui-token-search__leading`, `kui-token-search__token`, `kui-token-search__token-edit`, `kui-token-search__token-remove`, `kui-token-search__trailing`.
87
+ Public class hooks (select for layout placement only, never to change the component's look): `kui-token-search`, `kui-token-search__clear`, `kui-token-search__editor`, `kui-token-search__expand`, `kui-token-search__leading`, `kui-token-search__token`, `kui-token-search__token-edit`, `kui-token-search__token-remove`, `kui-token-search__suggestions`, `kui-token-search__suggestion`, `kui-token-search__trailing`.
86
88
 
87
- Never put `kui-token-search`, `kui-token-search__clear`, `kui-token-search__editor`, `kui-token-search__expand`, `kui-token-search__leading`, `kui-token-search__token`, `kui-token-search__token-edit`, `kui-token-search__token-remove`, `kui-token-search__trailing` on an element you write; render `TokenSearchField` instead (`KUI-L103`).
89
+ Never put `kui-token-search`, `kui-token-search__clear`, `kui-token-search__editor`, `kui-token-search__expand`, `kui-token-search__leading`, `kui-token-search__token`, `kui-token-search__token-edit`, `kui-token-search__token-remove`, `kui-token-search__suggestions`, `kui-token-search__suggestion`, `kui-token-search__trailing` on an element you write; render `TokenSearchField` instead (`KUI-L103`).
88
90
 
89
91
  Public tokens it reads: `--kui-color-brand-fill-normal`, `--kui-color-brand-on-quiet`, `--kui-color-neutral-border-normal`, `--kui-color-text-link`, `--kui-color-surface`, `--kui-color-text`, `--kui-color-text-quiet`, `--kui-focus-ring`, `--kui-font-s`, `--kui-font-xs`, `--kui-radius-pill`, `--kui-token-search-background`, `--kui-token-search-border`, `--kui-token-search-line-size`, `--kui-token-search-token-background`, `--kui-token-search-token-foreground`, `--kui-token-search-expanded-width`. Set a token only where its public contract allows; prefer a prop.
90
92
 
@@ -14,6 +14,7 @@ Provide complete toolbar action geometry through configuration, including semant
14
14
  - Use ToolbarActionLink for navigation or external resources that must retain native anchor semantics inside a group.
15
15
  - Fill it with plain \<button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Nothing wraps inside a group: an icon sits beside its label on one row and no trigger gets a fixed width.
16
16
  - For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight.
17
+ - In a Workbench or CollapsiblePanel toolbar, mark a group relocateOnCollapse to keep it available in the work-area toolbar when the panel closes; place it directly or in an array in any toolbar zone.
17
18
 
18
19
  **Not when:**
19
20
 
@@ -50,6 +51,7 @@ Exact prop names and types: [`@kerfjs/ui/toolbar-control-group`](../public-api-s
50
51
  - standard, compact, or list-zero dropdown menu inset
51
52
  - visible or horizontally scrollable overflow
52
53
  - always visible or compact-only responsive visibility
54
+ - relocate on panel collapse
53
55
  - avatar scrim
54
56
  - contained avatar background on a single group or selected multi-button highlight
55
57