@kerfjs/ui 5.0.0-beta.61 → 5.0.0-beta.63

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 (191) hide show
  1. package/README.md +4 -0
  2. package/ai/component-catalog-extension.schema.json +13 -0
  3. package/ai/component-catalog.json +20 -7
  4. package/ai/component-composition.json +17 -3
  5. package/ai/components/list-item.md +1 -1
  6. package/ai/components/pane.md +3 -2
  7. package/ai/components/select.md +1 -0
  8. package/ai/components/toolbar-text.md +2 -1
  9. package/ai/components/workbench.md +3 -2
  10. package/ai/public-api-signatures-v1.md +162 -48
  11. package/ai/skill.md +11 -5
  12. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  13. package/analyzer/index.mjs +3 -2
  14. package/catalog-projection-cli.mjs +96 -0
  15. package/catalog-projection.d.mts +55 -0
  16. package/catalog-projection.mjs +62 -0
  17. package/dist/browser/catalog.js +1 -1
  18. package/dist/browser/collapsible-panel.js +2 -0
  19. package/dist/browser/nav-stack.js +1 -0
  20. package/dist/browser/split-view.js +1 -0
  21. package/dist/browser/toolbar-control-group.js +1 -0
  22. package/dist/browser/workbench.js +1 -0
  23. package/dist/catalog-resources.d.ts +1 -1
  24. package/dist/catalog.d.ts +1 -1
  25. package/dist/catalog.js +11 -10
  26. package/dist/catalog.js.map +1 -1
  27. package/dist/chip.d.ts +9 -3
  28. package/dist/chip.js +1 -1
  29. package/dist/{chunk-EZVNC4J6.js → chunk-3BYFWTP3.js} +7 -3
  30. package/dist/chunk-3BYFWTP3.js.map +1 -0
  31. package/dist/{chunk-5Q3N6EV7.js → chunk-5IJ6UGNZ.js} +3 -3
  32. package/dist/{chunk-5Q3N6EV7.js.map → chunk-5IJ6UGNZ.js.map} +1 -1
  33. package/dist/chunk-7AKGPBCC.js +76 -0
  34. package/dist/chunk-7AKGPBCC.js.map +1 -0
  35. package/dist/{chunk-KROD7KZK.js → chunk-AUVG54KC.js} +2 -2
  36. package/dist/{chunk-KROD7KZK.js.map → chunk-AUVG54KC.js.map} +1 -1
  37. package/dist/{chunk-AUKT3KMG.js → chunk-CQHN45XR.js} +17 -2
  38. package/dist/chunk-CQHN45XR.js.map +1 -0
  39. package/dist/{chunk-SWVW56CV.js → chunk-EN4LDR5Y.js} +2 -2
  40. package/dist/{chunk-SWVW56CV.js.map → chunk-EN4LDR5Y.js.map} +1 -1
  41. package/dist/{chunk-TEYARRAK.js → chunk-F7OCOBTG.js} +5 -2
  42. package/dist/chunk-F7OCOBTG.js.map +1 -0
  43. package/dist/{chunk-5PESG2VP.js → chunk-HXCMWANP.js} +6 -5
  44. package/dist/chunk-HXCMWANP.js.map +1 -0
  45. package/dist/{chunk-NYL63PSD.js → chunk-IA4D5XNT.js} +61 -10
  46. package/dist/chunk-IA4D5XNT.js.map +1 -0
  47. package/dist/{chunk-F4QMLOXJ.js → chunk-IAOAZUOO.js} +2 -2
  48. package/dist/{chunk-F4QMLOXJ.js.map → chunk-IAOAZUOO.js.map} +1 -1
  49. package/dist/{chunk-Y74NWGQW.js → chunk-KFYANKJI.js} +66 -22
  50. package/dist/chunk-KFYANKJI.js.map +1 -0
  51. package/dist/chunk-MOSIE4Z2.js +51 -0
  52. package/dist/chunk-MOSIE4Z2.js.map +1 -0
  53. package/dist/{chunk-7JUBXOIV.js → chunk-NXWE5S57.js} +25 -6
  54. package/dist/chunk-NXWE5S57.js.map +1 -0
  55. package/dist/{chunk-NNMLEDMD.js → chunk-OBAFAH5B.js} +36 -8
  56. package/dist/chunk-OBAFAH5B.js.map +1 -0
  57. package/dist/{chunk-A3CKOW2R.js → chunk-R75GGNWY.js} +123 -20
  58. package/dist/chunk-R75GGNWY.js.map +1 -0
  59. package/dist/chunk-RGS4WX2M.js +44 -0
  60. package/dist/chunk-RGS4WX2M.js.map +1 -0
  61. package/dist/{chunk-N3GKMPDW.js → chunk-RKTTF3L4.js} +3 -1
  62. package/dist/chunk-RKTTF3L4.js.map +1 -0
  63. package/dist/{chunk-RRB26JRJ.js → chunk-RTJNJTG3.js} +2 -2
  64. package/dist/{chunk-RRB26JRJ.js.map → chunk-RTJNJTG3.js.map} +1 -1
  65. package/dist/{chunk-EAC4CDY7.js → chunk-SMZGAPCU.js} +15 -2
  66. package/dist/chunk-SMZGAPCU.js.map +1 -0
  67. package/dist/{chunk-C4OYOKWX.js → chunk-UA6JW52D.js} +36 -2
  68. package/dist/chunk-UA6JW52D.js.map +1 -0
  69. package/dist/{chunk-3SXMEK3B.js → chunk-V3DX3K65.js} +1 -1
  70. package/dist/chunk-V3DX3K65.js.map +1 -0
  71. package/dist/{chunk-6ZXOQEAN.js → chunk-WF75U3V4.js} +7 -1
  72. package/dist/chunk-WF75U3V4.js.map +1 -0
  73. package/dist/{chunk-VCBTX62E.js → chunk-ZRWXFDDS.js} +2 -2
  74. package/dist/chunk-ZRWXFDDS.js.map +1 -0
  75. package/dist/collapsible-panel.d.ts +18 -7
  76. package/dist/collapsible-panel.js +19 -3
  77. package/dist/collapsible-panel.js.map +1 -1
  78. package/dist/content-item.d.ts +36 -5
  79. package/dist/content-item.js +1 -1
  80. package/dist/disclosure-arrow.js +2 -1
  81. package/dist/index.d.ts +3 -3
  82. package/dist/index.js +22 -22
  83. package/dist/list-action-row.js +1 -1
  84. package/dist/list-header.js +1 -1
  85. package/dist/list-item.js +1 -1
  86. package/dist/lucide-icon.d.ts +5 -2
  87. package/dist/lucide-icon.js +2 -1
  88. package/dist/nav-stack.d.ts +3 -1
  89. package/dist/nav-stack.js +2 -1
  90. package/dist/pane.d.ts +10 -4
  91. package/dist/pane.js +1 -1
  92. package/dist/{panel-toolbar-DqwtBxQL.d.ts → panel-toolbar-DDKh4uKd.d.ts} +11 -2
  93. package/dist/popup-menu-register.js +38 -1
  94. package/dist/popup-menu-register.js.map +1 -1
  95. package/dist/popup-menu.d.ts +2 -2
  96. package/dist/popup-menu.js +1 -1
  97. package/dist/select.d.ts +2 -0
  98. package/dist/select.js +1 -1
  99. package/dist/split-view.d.ts +1 -1
  100. package/dist/split-view.js +2 -1
  101. package/dist/split-view.js.map +1 -1
  102. package/dist/styles/app-tab.css +84 -0
  103. package/dist/styles/chip.css +14 -0
  104. package/dist/styles/content-item.css +36 -0
  105. package/dist/styles/list-action-row.css +4 -3
  106. package/dist/styles/list-header.css +4 -3
  107. package/dist/styles/list-item.css +21 -11
  108. package/dist/styles/lucide-icon.css +5 -0
  109. package/dist/styles/nav-stack.css +13 -0
  110. package/dist/styles/pane.css +36 -2
  111. package/dist/styles/popup-menu.css +15 -10
  112. package/dist/styles/resizable-region.css +9 -0
  113. package/dist/styles/select.css +26 -0
  114. package/dist/styles/split-view.css +6 -0
  115. package/dist/styles/sunken-panel.css +7 -0
  116. package/dist/styles/tab-bar.css +3 -0
  117. package/dist/styles/tab-scaffold.css +13 -0
  118. package/dist/styles/text.css +29 -0
  119. package/dist/styles/token-search-field.css +29 -6
  120. package/dist/styles/toolbar-control-group.css +35 -0
  121. package/dist/styles/toolbar-text.css +15 -6
  122. package/dist/styles/toolbar.css +7 -0
  123. package/dist/styles/workbench.css +260 -90
  124. package/dist/sunken-panel.d.ts +6 -1
  125. package/dist/sunken-panel.js +1 -1
  126. package/dist/tab-bar.d.ts +6 -2
  127. package/dist/tab-bar.js +1 -1
  128. package/dist/tab-scaffold.d.ts +3 -0
  129. package/dist/tab-scaffold.js +1 -0
  130. package/dist/tab-scaffold.js.map +1 -1
  131. package/dist/text.d.ts +14 -3
  132. package/dist/text.js +1 -1
  133. package/dist/token-search-field.js +2 -1
  134. package/dist/toolbar-control-group.d.ts +5 -1
  135. package/dist/toolbar-control-group.js +2 -1
  136. package/dist/toolbar-text.d.ts +1 -1
  137. package/dist/toolbar-text.js +1 -1
  138. package/dist/toolbar.d.ts +7 -4
  139. package/dist/toolbar.js +1 -1
  140. package/dist/wire-catalog.js +1 -1
  141. package/dist/wire-content-items.d.ts +9 -0
  142. package/dist/wire-content-items.js +62 -0
  143. package/dist/wire-content-items.js.map +1 -0
  144. package/dist/wire-tab-bars.js +1 -1
  145. package/dist/wire-token-search-fields.js +2 -1
  146. package/dist/wire-workbench.d.ts +7 -0
  147. package/dist/wire-workbench.js +1 -1
  148. package/dist/workbench.d.ts +24 -8
  149. package/dist/workbench.js +2 -1
  150. package/docs/accessibility.md +21 -5
  151. package/docs/catalog.md +36 -0
  152. package/docs/collapsible-panel.md +12 -4
  153. package/docs/component-contract.md +10 -4
  154. package/docs/component-selection.md +65 -15
  155. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  156. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  157. package/docs/design/templates/toolbar-dark.svg +1 -1
  158. package/docs/design/templates/toolbar.svg +1 -1
  159. package/docs/examples/component-catalog-extension.json +6 -0
  160. package/docs/layout.md +61 -3
  161. package/docs/nav-stack.md +5 -0
  162. package/docs/split-view.md +5 -0
  163. package/docs/sunken-panel.md +30 -1
  164. package/docs/tab-scaffold.md +5 -0
  165. package/docs/ui-analyzer.md +4 -0
  166. package/docs/webawesome-theme.md +36 -6
  167. package/docs/workbench.md +60 -14
  168. package/llms.txt +12 -4
  169. package/package.json +16 -2
  170. package/scripts/component-catalog-extension-validation.mjs +28 -0
  171. package/dist/chunk-3SXMEK3B.js.map +0 -1
  172. package/dist/chunk-4R777DUZ.js +0 -34
  173. package/dist/chunk-4R777DUZ.js.map +0 -1
  174. package/dist/chunk-5PESG2VP.js.map +0 -1
  175. package/dist/chunk-6ZXOQEAN.js.map +0 -1
  176. package/dist/chunk-7JUBXOIV.js.map +0 -1
  177. package/dist/chunk-A3CKOW2R.js.map +0 -1
  178. package/dist/chunk-AUKT3KMG.js.map +0 -1
  179. package/dist/chunk-C4OYOKWX.js.map +0 -1
  180. package/dist/chunk-EAC4CDY7.js.map +0 -1
  181. package/dist/chunk-EZVNC4J6.js.map +0 -1
  182. package/dist/chunk-MA73TA6D.js +0 -42
  183. package/dist/chunk-MA73TA6D.js.map +0 -1
  184. package/dist/chunk-MERWRTBG.js +0 -45
  185. package/dist/chunk-MERWRTBG.js.map +0 -1
  186. package/dist/chunk-N3GKMPDW.js.map +0 -1
  187. package/dist/chunk-NNMLEDMD.js.map +0 -1
  188. package/dist/chunk-NYL63PSD.js.map +0 -1
  189. package/dist/chunk-TEYARRAK.js.map +0 -1
  190. package/dist/chunk-VCBTX62E.js.map +0 -1
  191. package/dist/chunk-Y74NWGQW.js.map +0 -1
@@ -1,4 +1,5 @@
1
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-RRB26JRJ.js';
1
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-RTJNJTG3.js';
2
+
2
3
 
3
4
 
4
5
  //# sourceMappingURL=token-search-field.js.map
@@ -60,6 +60,10 @@ interface ToolbarControlGroupProps {
60
60
  relocateOnCollapse?: boolean;
61
61
  /** Add contrast behind photo-backed avatar content. */
62
62
  scrim?: boolean;
63
+ /** Block the group's controls and show a spinner without changing its dimensions. */
64
+ busy?: boolean;
65
+ /** Announced status while busy; defaults to “Working”. */
66
+ busyLabel?: string;
63
67
  /**
64
68
  * Avatar image URL. A single-control group paints it on the group; a
65
69
  * multi-control group paints it only on the pressed selection highlight.
@@ -68,6 +72,6 @@ interface ToolbarControlGroupProps {
68
72
  /** Native named-slot assignment when composed inside a web component. */
69
73
  slot?: string;
70
74
  }
71
- declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, relocateOnCollapse, scrim, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
75
+ declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
72
76
 
73
77
  export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
@@ -1,3 +1,4 @@
1
- export { ToolbarActionLink, ToolbarControlGroup } from './chunk-7JUBXOIV.js';
1
+ export { ToolbarActionLink, ToolbarControlGroup } from './chunk-NXWE5S57.js';
2
+
2
3
 
3
4
  //# sourceMappingURL=toolbar-control-group.js.map
@@ -1,6 +1,6 @@
1
1
  import * as kerfjs from 'kerfjs';
2
2
 
3
- type ToolbarTextSize = 'xlarge' | 'large' | 'default' | 'small' | 'xsmall';
3
+ type ToolbarTextSize = 'xlarge' | 'xlarge-fixed' | 'large' | 'default' | 'small' | 'xsmall';
4
4
  /** ARIA heading level for a title exposed as a heading landmark. */
5
5
  type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
6
6
  interface ToolbarTextBaseProps {
@@ -1,4 +1,4 @@
1
- export { ToolbarText } from './chunk-3SXMEK3B.js';
1
+ export { ToolbarText } from './chunk-V3DX3K65.js';
2
2
 
3
3
 
4
4
 
package/dist/toolbar.d.ts CHANGED
@@ -3,11 +3,14 @@ import { PaneSeparatorSide } from './pane.js';
3
3
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
4
4
  import { S as Sides } from './sides-BPSWde0A.js';
5
5
 
6
+ type ToolbarPosition = 'header' | 'footer';
6
7
  interface ToolbarProps {
7
8
  leading?: KerfUiContent;
8
9
  center?: KerfUiContent;
9
10
  trailing?: KerfUiContent;
10
11
  label?: string;
12
+ /** Use footer semantics for a bottom toolbar; defaults to header. */
13
+ position?: ToolbarPosition;
11
14
  /**
12
15
  * Physical divider edges in canonical top/right/bottom/left order, drawn
13
16
  * always. Defaults to none: a toolbar pinned over or under scrolling content
@@ -17,8 +20,8 @@ interface ToolbarProps {
17
20
  * then draws its facing side the same way.
18
21
  */
19
22
  dividerSides?: Sides;
20
- /** Horizontal treatment of the center zone. Defaults to centered content. */
21
- centerAlign?: 'center' | 'stretch';
23
+ /** `balanced` gives both side zones equal tracks, centering the middle zone on the toolbar even if only one side has controls. */
24
+ centerAlign?: 'center' | 'stretch' | 'balanced';
22
25
  /**
23
26
  * Component-owned responsive layout; applications choose the policy rather
24
27
  * than restyling toolbar internals. Under every policy the trailing zone
@@ -57,6 +60,6 @@ interface ToolbarProps {
57
60
  * toolbar) forwards, so the app configures that toolbar instead of styling it.
58
61
  */
59
62
  type ToolbarConfig = Pick<ToolbarProps, 'dividerSides' | 'centerAlign' | 'responsive' | 'responsiveAt' | 'safeAreaEdges'>;
60
- declare function Toolbar({ leading, center, trailing, label, dividerSides, centerAlign, responsive, responsiveAt, safeAreaEdges, className, slot, }: ToolbarProps): kerfjs.SafeHtml;
63
+ declare function Toolbar({ leading, center, trailing, label, position, dividerSides, centerAlign, responsive, responsiveAt, safeAreaEdges, className, slot, }: ToolbarProps): kerfjs.SafeHtml;
61
64
 
62
- export { Sides, Toolbar, type ToolbarConfig, type ToolbarProps };
65
+ export { Sides, Toolbar, type ToolbarConfig, type ToolbarPosition, type ToolbarProps };
package/dist/toolbar.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Toolbar } from './chunk-MERWRTBG.js';
1
+ export { Toolbar } from './chunk-RGS4WX2M.js';
2
2
 
3
3
  //# sourceMappingURL=toolbar.js.map
@@ -1,4 +1,4 @@
1
- import { wireWorkbench } from './chunk-NNMLEDMD.js';
1
+ import { wireWorkbench } from './chunk-OBAFAH5B.js';
2
2
 
3
3
  import { wireScrollDividers } from './chunk-JCV4GXWB.js';
4
4
 
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Give interactive ContentItems native-like Enter/Space activation with one
3
+ * delegated listener pair. Space activates on release and is cancelled if
4
+ * focus moves; the item's own `data-action` still goes through the app's
5
+ * ordinary delegated click handler. Returns a disposer.
6
+ */
7
+ declare function wireContentItems(root: HTMLElement): () => void;
8
+
9
+ export { wireContentItems };
@@ -0,0 +1,62 @@
1
+ // src/wire-content-items.ts
2
+ var ITEM_SELECTOR = '[data-component="content-item"][data-interactive="true"]';
3
+ var NESTED_CONTROL_SELECTOR = 'button, a[href], input, select, textarea, [contenteditable="true"], [role="button"]';
4
+ function actionItem(root, target) {
5
+ if (!(target instanceof Element)) return null;
6
+ const item = target.closest(ITEM_SELECTOR);
7
+ if (!item || !root.contains(item) || item.dataset.disabled === "true")
8
+ return null;
9
+ const nested = target.closest(NESTED_CONTROL_SELECTOR);
10
+ if (nested && nested !== item && item.contains(nested)) return null;
11
+ return item;
12
+ }
13
+ function wireContentItems(root) {
14
+ let pendingSpace = null;
15
+ const clearSpace = () => {
16
+ pendingSpace?.removeAttribute("data-kui-pressed");
17
+ pendingSpace = null;
18
+ };
19
+ const keydown = (event) => {
20
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
21
+ return;
22
+ const item = actionItem(root, event.target);
23
+ if (!item) return;
24
+ if (event.key === "Enter") {
25
+ event.preventDefault();
26
+ item.click();
27
+ return;
28
+ }
29
+ if (event.key === " " || event.key === "Spacebar") {
30
+ event.preventDefault();
31
+ if (event.repeat) return;
32
+ clearSpace();
33
+ pendingSpace = item;
34
+ item.setAttribute("data-kui-pressed", "true");
35
+ }
36
+ };
37
+ const keyup = (event) => {
38
+ if (event.key !== " " && event.key !== "Spacebar") return;
39
+ const item = pendingSpace;
40
+ if (!item) return;
41
+ event.preventDefault();
42
+ clearSpace();
43
+ if (item === actionItem(root, event.target) && item.ownerDocument.activeElement === item)
44
+ item.click();
45
+ };
46
+ const focusout = (event) => {
47
+ if (event.target === pendingSpace) clearSpace();
48
+ };
49
+ root.addEventListener("keydown", keydown);
50
+ root.addEventListener("keyup", keyup);
51
+ root.addEventListener("focusout", focusout);
52
+ return () => {
53
+ clearSpace();
54
+ root.removeEventListener("keydown", keydown);
55
+ root.removeEventListener("keyup", keyup);
56
+ root.removeEventListener("focusout", focusout);
57
+ };
58
+ }
59
+
60
+ export { wireContentItems };
61
+
62
+ //# sourceMappingURL=wire-content-items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wire-content-items.ts"],"names":[],"mappings":";AAAA,IAAM,aAAA,GACJ,0DAAA;AACF,IAAM,uBAAA,GACJ,qFAAA;AAEF,SAAS,UAAA,CAAW,MAAmB,MAAA,EAA4B;AACjE,EAAA,IAAI,EAAE,MAAA,YAAkB,OAAA,CAAA,EAAU,OAAO,IAAA;AACzC,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,OAAA,CAAqB,aAAa,CAAA;AACtD,EAAA,IAAI,CAAC,QAAQ,CAAC,IAAA,CAAK,SAAS,IAAI,CAAA,IAAK,IAAA,CAAK,OAAA,CAAQ,QAAA,KAAa,MAAA;AAC7D,IAAA,OAAO,IAAA;AACT,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,OAAA,CAAQ,uBAAuB,CAAA;AACrD,EAAA,IAAI,UAAU,MAAA,KAAW,IAAA,IAAQ,KAAK,QAAA,CAAS,MAAM,GAAG,OAAO,IAAA;AAC/D,EAAA,OAAO,IAAA;AACT;AAQO,SAAS,iBAAiB,IAAA,EAA+B;AAC9D,EAAA,IAAI,YAAA,GAAmC,IAAA;AAEvC,EAAA,MAAM,aAAa,MAAM;AACvB,IAAA,YAAA,EAAc,gBAAgB,kBAAkB,CAAA;AAChD,IAAA,YAAA,GAAe,IAAA;AAAA,EACjB,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAAyB;AACxC,IAAA,IACE,MAAM,gBAAA,IACN,KAAA,CAAM,MAAA,IACN,KAAA,CAAM,WACN,KAAA,CAAM,OAAA;AAEN,MAAA;AACF,IAAA,MAAM,IAAA,GAAO,UAAA,CAAW,IAAA,EAAM,KAAA,CAAM,MAAM,CAAA;AAC1C,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,IAAI,KAAA,CAAM,QAAQ,OAAA,EAAS;AACzB,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,IAAA,CAAK,KAAA,EAAM;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,GAAA,IAAO,KAAA,CAAM,QAAQ,UAAA,EAAY;AACjD,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,IAAI,MAAM,MAAA,EAAQ;AAClB,MAAA,UAAA,EAAW;AACX,MAAA,YAAA,GAAe,IAAA;AACf,MAAA,IAAA,CAAK,YAAA,CAAa,oBAAoB,MAAM,CAAA;AAAA,IAC9C;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAC,KAAA,KAAyB;AACtC,IAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,GAAA,IAAO,KAAA,CAAM,QAAQ,UAAA,EAAY;AACnD,IAAA,MAAM,IAAA,GAAO,YAAA;AACb,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,UAAA,EAAW;AACX,IAAA,IACE,IAAA,KAAS,WAAW,IAAA,EAAM,KAAA,CAAM,MAAM,CAAA,IACtC,IAAA,CAAK,cAAc,aAAA,KAAkB,IAAA;AAErC,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAsB;AACtC,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,YAAA,EAAc,UAAA,EAAW;AAAA,EAChD,CAAA;AAEA,EAAA,IAAA,CAAK,gBAAA,CAAiB,WAAW,OAAO,CAAA;AACxC,EAAA,IAAA,CAAK,gBAAA,CAAiB,SAAS,KAAK,CAAA;AACpC,EAAA,IAAA,CAAK,gBAAA,CAAiB,YAAY,QAAQ,CAAA;AAC1C,EAAA,OAAO,MAAM;AACX,IAAA,UAAA,EAAW;AACX,IAAA,IAAA,CAAK,mBAAA,CAAoB,WAAW,OAAO,CAAA;AAC3C,IAAA,IAAA,CAAK,mBAAA,CAAoB,SAAS,KAAK,CAAA;AACvC,IAAA,IAAA,CAAK,mBAAA,CAAoB,YAAY,QAAQ,CAAA;AAAA,EAC/C,CAAA;AACF","file":"wire-content-items.js","sourcesContent":["const ITEM_SELECTOR =\n '[data-component=\"content-item\"][data-interactive=\"true\"]';\nconst NESTED_CONTROL_SELECTOR =\n 'button, a[href], input, select, textarea, [contenteditable=\"true\"], [role=\"button\"]';\n\nfunction actionItem(root: HTMLElement, 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,3 +1,3 @@
1
- export { reorderTabs, wireTabBars } from './chunk-C4OYOKWX.js';
1
+ export { reorderTabs, wireTabBars } from './chunk-UA6JW52D.js';
2
2
 
3
3
  //# sourceMappingURL=wire-tab-bars.js.map
@@ -1,4 +1,5 @@
1
- export { wireTokenSearchFields } from './chunk-A3CKOW2R.js';
1
+ export { wireTokenSearchFields } from './chunk-R75GGNWY.js';
2
+
2
3
 
3
4
 
4
5
 
@@ -32,6 +32,13 @@ interface WireWorkbenchPanel {
32
32
  * keeps Tab inside it, and it closes on Escape or a press outside it.
33
33
  */
34
34
  collapsed?: Signal<boolean>;
35
+ /**
36
+ * Treat presses in a portal surface launched by this panel (for example a
37
+ * body-level menu or dialog) as inside it. The predicate receives each Node
38
+ * in the press's composed path; return true for the portal root. Ordinary
39
+ * presses outside the panel and its allowed surfaces still dismiss it.
40
+ */
41
+ keepOpenOn?: (target: Node) => boolean;
35
42
  }
36
43
  /** A resize the user made, after `wireWorkbench` wrote it to the size signal. */
37
44
  interface WorkbenchResize {
@@ -1,4 +1,4 @@
1
- export { wireWorkbench } from './chunk-NNMLEDMD.js';
1
+ export { wireWorkbench } from './chunk-OBAFAH5B.js';
2
2
 
3
3
 
4
4
 
@@ -3,7 +3,7 @@ import { ListConfig } from './list.js';
3
3
  import { PaneConfig } from './pane.js';
4
4
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
5
5
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
6
- import { a as PanelToolbar, b as PanelToggle } from './panel-toolbar-DqwtBxQL.js';
6
+ import { a as PanelToolbar, b as PanelChromePlacement, c as PanelBottomToolbar, d as PanelToggle } from './panel-toolbar-DDKh4uKd.js';
7
7
  import { ToolbarConfig } from './toolbar.js';
8
8
  import './css-values.js';
9
9
  import './flex-alignment-4ms8ZbV8.js';
@@ -22,6 +22,7 @@ type WorkbenchPanelToggle = PanelToggle;
22
22
  * `FloatingToolbar` in the work area's bottom-end corner.
23
23
  */
24
24
  type WorkbenchPanelToolbar = PanelToolbar;
25
+ type WorkbenchPanelBottomToolbar = PanelBottomToolbar;
25
26
  /**
26
27
  * The work area's top toolbar; collapsed rails add their groups to it. Its
27
28
  * configuration forwards to its `Toolbar`. It draws no divider of its own by
@@ -57,7 +58,7 @@ interface WorkbenchMainBottomToolbar extends ToolbarConfig {
57
58
  * the Pane's `chromePlacement="auto"`; it applies to the pinned header and
58
59
  * footer together).
59
60
  */
60
- type WorkbenchChromePlacement = 'fixed' | 'scroll' | 'auto';
61
+ type WorkbenchChromePlacement = PanelChromePlacement;
61
62
 
62
63
  /**
63
64
  * The Workbench container breakpoint below which a panel presents as an
@@ -65,7 +66,7 @@ type WorkbenchChromePlacement = 'fixed' | 'scroll' | 'auto';
65
66
  * breakpoints as `ResizableRegion`'s `responsiveFillAt` — or `never` to keep
66
67
  * it inline at every width.
67
68
  */
68
- type WorkbenchResponsiveOverlayAt = 'compact' | 'narrow' | 'never';
69
+ type WorkbenchResponsiveOverlayAt = 'compact' | 'narrow' | 'never' | number;
69
70
  /**
70
71
  * How wide a rail's overlay is in a compact (448px or less) Workbench: `inset`
71
72
  * fills the Workbench less a dismiss margin on the side away from the rail's
@@ -88,8 +89,20 @@ interface WorkbenchPanel {
88
89
  * collapsed. With it, `content` renders in a `Pane` below the toolbar.
89
90
  */
90
91
  toolbar?: WorkbenchPanelToolbar;
91
- /** Optional bottom toolbar under a `toolbar` panel's content. */
92
+ /** Fixed content below a toolbar panel's top toolbar, above its scroll region. */
93
+ header?: KerfUiContent;
94
+ /** List layout for `header`; omitted fields keep List defaults. */
95
+ headerList?: ListConfig;
96
+ /** Placement of the top toolbar and header together; default `fixed`. */
97
+ headerPlacement?: WorkbenchChromePlacement;
98
+ /** Fixed content above a toolbar panel's bottom toolbar. */
92
99
  footer?: KerfUiContent;
100
+ /** List layout for `footer`; omitted fields keep List defaults. */
101
+ footerList?: ListConfig;
102
+ /** Fixed toolbar below `footer`, using footer semantics. */
103
+ bottomToolbar?: WorkbenchPanelBottomToolbar;
104
+ /** Placement of the footer and bottom toolbar together; default `fixed`. */
105
+ footerPlacement?: WorkbenchChromePlacement;
93
106
  /**
94
107
  * Configuration for a `toolbar` panel's `Pane` (`contentElement`,
95
108
  * `contentLabel`, `separators`, `safeAreaEdges`, `chromeDividers`) — for example
@@ -121,8 +134,9 @@ interface WorkbenchPanel {
121
134
  presentation?: ResizableRegionPresentation;
122
135
  /**
123
136
  * Present the panel as an overlay, without a separator, below a Workbench
124
- * container breakpoint, and inline above it — the CSS decides, so the app
125
- * needs no device-class check. A rail overlays from its side at full
137
+ * container breakpoint, and inline above it. `compact` and `narrow` use
138
+ * CSS container queries; a positive number is a pixel threshold measured
139
+ * by `wireWorkbench` and requires a `collapsed` signal. A rail overlays from its side at full
126
140
  * height, over the work area and an open drawer; the bottom drawer
127
141
  * overlays the bottom of the work-area column. Rails default to `narrow`
128
142
  * (pass `never` to keep one inline); the drawer defaults to inline.
@@ -222,6 +236,8 @@ interface WorkbenchProps {
222
236
  * shorter. Workbenches without a drawer ignore it.
223
237
  */
224
238
  mainMinHeight?: number;
239
+ /** Dim the work area below any open overlay and absorb outside presses; off by default. */
240
+ overlayBackdrop?: boolean;
225
241
  className?: string;
226
242
  /** Native named-slot assignment when composed inside a web component. */
227
243
  slot?: string;
@@ -239,6 +255,6 @@ interface WorkbenchProps {
239
255
  * lives outside it and stays reachable). A panel may opt in to drag and keyboard resizing with
240
256
  * `resizable`, which `wireWorkbench` drives. See `docs/23-app-layouts.md` §3.3.
241
257
  */
242
- declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainPane, mainHeaderList, mainFooterList, mainMinSize, mainMinHeight, className, slot, }: WorkbenchProps): SafeHtml;
258
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainPane, mainHeaderList, mainFooterList, mainMinSize, mainMinHeight, overlayBackdrop, className, slot, }: WorkbenchProps): SafeHtml;
243
259
 
244
- export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchPanel, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt };
260
+ export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchPanel, type WorkbenchPanelBottomToolbar, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt };
package/dist/workbench.js CHANGED
@@ -1,4 +1,5 @@
1
- export { Workbench } from './chunk-Y74NWGQW.js';
1
+ export { Workbench } from './chunk-KFYANKJI.js';
2
+
2
3
 
3
4
 
4
5
 
@@ -5,7 +5,7 @@ These are normative contracts for package components and consuming applications.
5
5
  ## Shared rules
6
6
 
7
7
  - All controls have accessible names and visible `:focus-visible` treatment.
8
- - Decorative `LucideIcon` output is `aria-hidden`; pass `label` only when the SVG itself conveys meaning.
8
+ - Decorative `LucideIcon` output is `aria-hidden`; pass `label` only when the SVG itself conveys meaning. `size="xs|s|m|l|xl"` selects the 12/16/20/24/32px root-scaled icon steps, and a positive numeric `size` converts pixels to rem; omitting `size` keeps 1em. Size changes the visual only, never the owning control's hit target or accessible name.
9
9
  - `ListItem` uses native button Enter/Space behavior. `selected` maps to `aria-current="page"`; `pressed` maps to `aria-pressed`. Selected rows keep the normal foreground over their brand-tinted fill so text retains WCAG AA contrast in light and dark themes. Its leading icon visual is 18px while the row retains its 44px minimum target; multiline icons align to the first text line. Its `trailing` content is dormant and must not contain controls. Its `rootAttributes` slot accepts only application `data-*` metadata and deliberately cannot emit `role="menuitem"` in isolation—a true ARIA menu must own arrow, Home/End, Escape, and focus behavior as one widget.
10
10
  - `ListItemLink` shares that row content and geometry but uses native anchor Enter behavior and requires `href`. It has no `action` or `pressed` state. `external` opens a new tab with `rel="noopener noreferrer"`; the default accessible name announces the new tab for a string label, so an explicit `accessibleLabel` should do the same. Disabled and loading links omit `href`, expose `aria-disabled`, and leave the tab order. Its trailing content remains dormant.
11
11
  - `ListHeader` owns action naming, disabled state, and disclosure `aria-expanded`. Toggle mode supplies one decorative 18px `DisclosureArrow` when `actionIcon` is omitted; it mirrors `expanded` while the native button's accessible name stays stable. The application must update that controlled state and reveal or hide real content. A valid `count` is visually rendered in an `aria-hidden` neutral pill while the required localized `countLabel` becomes part of the owning heading or disclosure button's accessible name; zero remains a real count. Its narrow `triggerAttributes` slot may describe a native popover or controlled-content relationship with `popoverTarget`, `popoverTargetAction`, `aria-controls`, and `aria-haspopup`; the application owns the target surface and its focus/dismissal behavior.
@@ -13,9 +13,12 @@ These are normative contracts for package components and consuming applications.
13
13
  - `wireNavStack` moves focus into every newly active top view and remembers the last focused descendant of each mounted view for pop restoration. Mark a preferred initial heading or control with `data-nav-focus` (and `tabindex="-1"` when it is not normally tabbable); otherwise the helper uses the first enabled focusable descendant, then a programmatically focusable view-container fallback.
14
14
  - In any `.kui-pane`, menu rows, `ListHeader` actions, and toolbar groups keep a minimum 44px target in both dimensions. A `ListHeader` fills the available inline width and keeps that action at the logical end, including in RTL. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square; a transparent hit layer extends the pointer target to at least 44 × 44 around that square without moving layout or enlarging the painted surface. Compact headers are 36px tall like the rest of the compact List family, so their action target is 44px wide and clamped to the 36px header height instead of reaching into a neighboring row. Do not reduce the target to the visible icon, and do not force the button itself to 44px.
15
15
  - `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
16
+ - A busy `ToolbarControlGroup` makes its controls inert and exposes `aria-busy`; its `busyLabel` is announced by an adjacent status node outside the inert group. Clear `busy` when the operation finishes so the controls rejoin the focus order.
17
+ - A bottom `Toolbar` uses `position="footer"` for footer semantics; top toolbars keep the default `header`. Workbench's main bottom toolbar sets this automatically.
18
+ - Interactive `ContentItem` cards have a keyboard reachable button or option role. Set `selectionMode="toggle"` for `aria-pressed`, or `selectionMode="single"` inside a listbox for `aria-selected`; wire `wireContentItems` once so Enter and Space activate their delegated action. Disabled cards expose `aria-disabled` and leave the Tab order.
16
19
  - An unlabeled `Skeleton` (including the value slots a component's `placeholder` prop renders) is decorative loading chrome: it is `aria-hidden`, conveys no information a user needs to act on, and is deliberately exempt from the WCAG 3:1 non-text-contrast target. Inside a solid `Badge` it stays subtle (about 2.1–4.4:1 in light, 2.4–2.8:1 in dark, depending on tone); `tests/browser/tone-contrast.spec.ts` holds those values as a regression floor rather than an accessibility threshold. Announce loading through a labeled `Skeleton`, `LoadingSpinner`, or `aria-busy` on the owning region, never through the skeleton's visibility.
17
20
  - `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
18
- - A removable `Chip` has a native button with a specific `removeLabel`, such as "Remove Urgent tag". The button is absent for a plain chip and disabled for a disabled chip. Tag text remains visible because tone does not convey meaning alone.
21
+ - A removable `Chip` has a native button with a specific `removeLabel`, such as "Remove Urgent tag". The button is absent for a plain chip and disabled for a disabled chip. Its leading `icon` is decorative; the visible label names the tag. `truncate` retains the full plain-text label as a native tooltip. Tag text remains visible because tone does not convey meaning alone.
19
22
  - `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
20
23
  - `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone. Its decorative option icons and value-dependent selected content remain present after controlled rerenders, so the visible choice does not silently lose its non-text cue.
21
24
  - `DisclosureArrow` is an 18px root-scaled decorative visual by default, not an interaction target. Its owning native control supplies a stable accessible name, pointer and keyboard interaction, and `aria-expanded`; multiple arrows keep independently controlled state. Consumers may resize it with the typed `size` prop without changing that ownership. Configured directions animate over the shortest path; a 180-degree closed-to-open tie uses counterclockwise rotation.
@@ -205,7 +208,9 @@ open popup. A disabled item stays in the menu but cannot be
205
208
  chosen; disable the whole trigger only when every command is unavailable.
206
209
  Nested items use Web Awesome's submenu pointer and Left/Right arrow routing;
207
210
  headings and dividers can separate nested groups without becoming keyboard
208
- choices. Checked items expose checkbox menu semantics. `details` supplies optional
211
+ 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
213
+ for a plain command. `details` supplies optional
209
214
  trailing visual content without changing a command's accessible label. A
210
215
  `context` menu's invisible anchor is outside the accessibility tree and normal
211
216
  Tab order; opening the menu moves focus into its commands and Escape dismisses
@@ -443,10 +448,21 @@ remove actions, and suggestions are managed. Arrow Down moves from the editor
443
448
  to suggestions, arrow keys move between suggestions, Enter selects one, and
444
449
  Escape returns focus to the editor. The application still announces results
445
450
  when needed and can supply an `evaluate` callback to compute them. Omitting the
446
- model leaves the manual behavior above intact.
451
+ model leaves the manual behavior above intact. After a chip commit, suggestion
452
+ choice, chip edit, or Backspace/Delete chip removal, focus restoration runs at
453
+ the controlled render checkpoint and keeps the query-relative caret offset;
454
+ it leaves focus on a different control if the user moved there in the meantime.
455
+ Suggestions use Floating UI positioning, following the same viewport-aware
456
+ popup behavior as the Web Awesome controls behind Select and PopupMenu. They
457
+ follow the field while scrolling, flip above it at the bottom edge, and shift
458
+ within narrow viewports without growing its toolbar row. One
459
+ suggestion keeps a pill outline; lists with multiple choices use the standard
460
+ rounded surface and scroll once they reach 240px. The clear button's hover and focus
461
+ highlight follows the field's pill shape or its toolbar group's rounded shape.
447
462
 
448
463
  Managed clear captures the action before application handlers run, keeps the adopted
449
- expanded signal open during editor replacement, and restores focus at the actual
464
+ expanded signal open for a collapsible field, and restores the editor focus for
465
+ both collapsible and plain model fields at the actual
450
466
  mutation checkpoint before the next input task. It also returns keyboard activation
451
467
  from the clear button to a surviving editor immediately. No animation frame owns
452
468
  clear focus, so fast typing cannot escape to page shortcuts or lose its first letter. The app still owns clearing query/tokens and emptying
package/docs/catalog.md CHANGED
@@ -215,6 +215,42 @@ border, and padding. Catalogs for downstream components should conform to the
215
215
  contract and can start from the checked
216
216
  [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
217
217
  example; provide those entries beside Kerf's shipped catalog to AI tools.
218
+ An extension entry may declare `uses` with direct Kerf or extension entry ids,
219
+ plus repository-relative `source` and `demoSource` paths. Keep the ids unique
220
+ and resolvable across both catalogs; paths use `/` separators and cannot
221
+ traverse outside the repository. These fields let consumer demos derive source
222
+ links and composition relationships from the catalog.
223
+
224
+ For a consumer UX demo, `@kerfjs/ui/catalog-projection` exports
225
+ `projectConsumerCatalog(extension, kerfCatalog, options)` and
226
+ `projectCatalogEntries(entries, options)`. The projection maps `purpose` to
227
+ `description`, `uses` to related-entry ids, `source` to `componentSource`, and
228
+ `demoSource` to the demo source link. It defaults the source identity to
229
+ `consumer` and category to `Application`. Explicit paths in an extension entry
230
+ take precedence over templates. Template placeholders are `{id}`, `{name}`,
231
+ `{kind}`, and `{package}`. Design templates are omitted unless configured.
232
+
233
+ The package also ships `kerf-catalog-demo` for a committed TypeScript module:
234
+
235
+ ```sh
236
+ kerf-catalog-demo --extension ai/component-catalog-extension.json \
237
+ --out ux-demo/catalog.generated.ts \
238
+ --repo-base https://github.com/acme/app/blob/main/ \
239
+ --source-template 'src/{id}.tsx' \
240
+ --demo-template 'ux-demo/demos/{id}.tsx' --write
241
+ kerf-catalog-demo --extension ai/component-catalog-extension.json \
242
+ --out ux-demo/catalog.generated.ts \
243
+ --repo-base https://github.com/acme/app/blob/main/ \
244
+ --source-template 'src/{id}.tsx' \
245
+ --demo-template 'ux-demo/demos/{id}.tsx' --check
246
+ ```
247
+
248
+ The command uses the Kerf catalog shipped with the package to resolve `uses`;
249
+ `--kerf-catalog` selects another catalog file. `--category`,
250
+ `--documentation-template`, and `--design-template` configure optional row
251
+ fields. `--check` fails if the generated file is missing or stale. The output
252
+ exports `generatedConsumerCatalog` and `catalogRepositoryBlobUrl`; the latter
253
+ is normalized to a trailing slash for source and documentation links.
218
254
 
219
255
  ### Automated conformance and reviewed exceptions
220
256
 
@@ -80,9 +80,10 @@ and that of the components it renders (`Pane`, `Toolbar`, `ToolbarControlGroup`,
80
80
  corners, with typed `restoreInset` for the corner distance. Set
81
81
  `restorePlacement="inline"` when the container should place the control in
82
82
  normal flow. An inline region's surface uses
83
- `--kui-resizable-region-background`; set `contentOverflow="visible"` while
84
- an anchored popup must escape the region's content box, then return to the
85
- normal overflow policy when it closes.
83
+ `--kui-resizable-region-background`; an open descendant `PopupMenu` or Web
84
+ Awesome dropdown releases clipping and raises the popup layer for its
85
+ lifecycle. Set `contentOverflow="visible"` for other anchored content that
86
+ must escape the region's content box.
86
87
  Overlay presentation also clamps fixed-size animated content to the configured
87
88
  responsive overlay maximum, so a remembered desktop size cannot escape a narrow
88
89
  viewport.
@@ -133,7 +134,7 @@ and that of the components it renders (`Pane`, `Toolbar`, `ToolbarControlGroup`,
133
134
 
134
135
  Give a panel a `toolbar` (the same roles as a
135
136
  [Workbench panel toolbar](workbench.md#panel-toolbars)) and it composes its own
136
- top toolbar over a `Pane`, with an optional `footer` below its content:
137
+ top toolbar over a `Pane`:
137
138
 
138
139
  - `title` precedes the `leading` zone; `leading`, `center`, and `trailing`
139
140
  place groups wherever the open panel needs them;
@@ -141,6 +142,13 @@ top toolbar over a `Pane`, with an optional `footer` below its content:
141
142
  work area when the panel closes; the standard `toggle: { action, name,
142
143
  showLabel?, hideLabel? }` is always the final group.
143
144
 
145
+ The panel can also take `header` under its toolbar, `footer` below its content,
146
+ `bottomToolbar` below that, `headerList` / `footerList` for those fixed content
147
+ stacks, and `headerPlacement` / `footerPlacement` (`fixed`, `scroll`, or `auto`).
148
+ These slots share the panel's one `Pane` scroll owner; the bottom toolbar uses
149
+ footer semantics. With no `toolbar`, the panel renders its children directly
150
+ and ignores the chrome slots.
151
+
144
152
  The toolbar also takes the `Toolbar`'s configuration (`dividerSides`,
145
153
  `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`); it draws no
146
154
  divider of its own unless `dividerSides` says so. The `Pane` under it draws
@@ -243,6 +243,9 @@ example. Keep app and Kerf entries as package-qualified inputs, then let people
243
243
  or AI tools search their combined entries and compare `geometry` before adding
244
244
  wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
245
245
  claim Kerf delivery paths for them.
246
+ Extension entries may add `uses` for direct composition dependencies and
247
+ repository-relative `source` and `demoSource` files. Resolve each dependency
248
+ against the combined Kerf and consumer catalogs; do not duplicate a Kerf id.
246
249
 
247
250
  - Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
248
251
  - Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
@@ -504,9 +507,10 @@ without application `::part(menu)` CSS; the default `standard` keeps each menu's
504
507
  own inset (8px for a raw `wa-dropdown`, the Select listbox's 4px for a
505
508
  `PopupMenu`), while `compact` and `list-zero` apply to every nested menu. A
506
509
  `PopupMenu` and a `Select` draw one popup: the same caret that turns to face the
507
- open popup, fade-in surface, surface inset, row height and padding, reserved
508
- check column, current-row fill, and group titles, all read from one private
509
- popup-choice contract in `foundation.css`. Compact mixed controls retain the standard
510
+ open popup, fade-in surface, surface inset, row height and padding, current-row
511
+ fill, and group titles, all read from one private popup-choice contract in
512
+ `foundation.css`. Checkbox items reserve a check column; plain commands start
513
+ at the checkmark's leading edge. Compact mixed controls retain the standard
510
514
  item padding, omit an internal separator, and let the raised selected item paint
511
515
  over the outer border instead of shrinking to an inset highlight. The Web
512
516
  Awesome trigger's shadow base owns max-content sizing, so icons, localized text,
@@ -578,7 +582,9 @@ by default it also owns the collapsible field's transient expand/collapse/focus
578
582
  state in a signal it exposes on the returned handle. An app reads that signal in
579
583
  render, hands in its own via `collapsible.signals`, drives it through
580
584
  `handle.open`/`handle.close`, or disables any individual behavior — so transient
581
- UI is consistent by default without every app reinventing it.
585
+ UI is consistent by default without every app reinventing it. A collapsed
586
+ field may reveal its editor after the current microtask; the helper keeps a
587
+ bounded render-frame focus handoff for that reveal and cancels it on disposal.
582
588
 
583
589
  A public subpath that exposes several visual components is a folder-backed
584
590
  surface. Put each component in its own source file and give each visual