@kerfjs/ui 5.0.0-beta.29 → 5.0.0-beta.30

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 (224) hide show
  1. package/README.md +43 -8
  2. package/ai/application-ui-diagnostic-ids-v1.json +5 -0
  3. package/ai/compile-time-contracts-v1.json +37 -0
  4. package/ai/compile-time-contracts-v1.schema.json +3 -1
  5. package/ai/component-catalog-v2-overrides.json +198 -1
  6. package/ai/component-catalog-v2.d.ts +22 -0
  7. package/ai/component-catalog-v2.json +488 -32
  8. package/ai/component-catalog-v2.schema.json +51 -0
  9. package/ai/component-catalog.json +251 -36
  10. package/ai/component-catalog.schema.json +23 -0
  11. package/ai/public-api-signatures-v1.md +231 -84
  12. package/ai/skill.md +32 -10
  13. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  14. package/analyzer/index.mjs +247 -5
  15. package/dist/app-tab.d.ts +3 -2
  16. package/dist/app-tab.js +2 -1
  17. package/dist/catalog-resources.d.ts +1 -0
  18. package/dist/catalog.d.ts +7 -6
  19. package/dist/catalog.js +8 -7
  20. package/dist/catalog.js.map +1 -1
  21. package/dist/{chunk-PX3YSS5C.js → chunk-6NDBE2OK.js} +1 -1
  22. package/dist/chunk-6NDBE2OK.js.map +1 -0
  23. package/dist/{chunk-B3A3IVO5.js → chunk-AQFWBG23.js} +3 -3
  24. package/dist/chunk-AQFWBG23.js.map +1 -0
  25. package/dist/{chunk-WNWD54GR.js → chunk-B6JYRX3P.js} +4 -2
  26. package/dist/chunk-B6JYRX3P.js.map +1 -0
  27. package/dist/{chunk-HP2B2L26.js → chunk-CAB6NQ2T.js} +1 -1
  28. package/dist/chunk-CAB6NQ2T.js.map +1 -0
  29. package/dist/{chunk-2NMQW7FX.js → chunk-CK6P5AUK.js} +4 -3
  30. package/dist/chunk-CK6P5AUK.js.map +1 -0
  31. package/dist/{chunk-VCE4MPVE.js → chunk-DAO2VFTT.js} +5 -6
  32. package/dist/chunk-DAO2VFTT.js.map +1 -0
  33. package/dist/{chunk-LPOMRC4C.js → chunk-DYBPI5O2.js} +1 -1
  34. package/dist/chunk-DYBPI5O2.js.map +1 -0
  35. package/dist/{chunk-IJBSZ4NX.js → chunk-E34TE2ZI.js} +4 -3
  36. package/dist/chunk-E34TE2ZI.js.map +1 -0
  37. package/dist/{chunk-4EF2CMYS.js → chunk-I45QO2IZ.js} +8 -7
  38. package/dist/chunk-I45QO2IZ.js.map +1 -0
  39. package/dist/{chunk-JRKSK2HX.js → chunk-I4V3UDSA.js} +1 -1
  40. package/dist/chunk-I4V3UDSA.js.map +1 -0
  41. package/dist/{chunk-FEY65TBV.js → chunk-KFUUFNTS.js} +1 -1
  42. package/dist/chunk-KFUUFNTS.js.map +1 -0
  43. package/dist/{chunk-QO55FN2Y.js → chunk-MFR2IIFG.js} +1 -1
  44. package/dist/chunk-MFR2IIFG.js.map +1 -0
  45. package/dist/{chunk-GWEAPEZI.js → chunk-OC275HHS.js} +4 -3
  46. package/dist/chunk-OC275HHS.js.map +1 -0
  47. package/dist/chunk-OL4TC3U2.js +156 -0
  48. package/dist/chunk-OL4TC3U2.js.map +1 -0
  49. package/dist/{chunk-OT6RPYAP.js → chunk-OMSRSL3B.js} +4 -3
  50. package/dist/chunk-OMSRSL3B.js.map +1 -0
  51. package/dist/{chunk-J5BFYY7Q.js → chunk-RL6COPKF.js} +12 -3
  52. package/dist/chunk-RL6COPKF.js.map +1 -0
  53. package/dist/{chunk-TB6DY7H5.js → chunk-SOK5EUF7.js} +1 -1
  54. package/dist/chunk-SOK5EUF7.js.map +1 -0
  55. package/dist/{chunk-RKVQEH4J.js → chunk-T7DOWS6T.js} +3 -3
  56. package/dist/chunk-T7DOWS6T.js.map +1 -0
  57. package/dist/{chunk-7KEUJIIC.js → chunk-TIGSG6BX.js} +1 -1
  58. package/dist/chunk-TIGSG6BX.js.map +1 -0
  59. package/dist/{chunk-W5L2JSBY.js → chunk-UDHHKZWO.js} +6 -5
  60. package/dist/chunk-UDHHKZWO.js.map +1 -0
  61. package/dist/{chunk-R7ZWUN64.js → chunk-V6GQKOX2.js} +4 -3
  62. package/dist/chunk-V6GQKOX2.js.map +1 -0
  63. package/dist/{chunk-KWWR5VMS.js → chunk-V6HCTCVD.js} +4 -3
  64. package/dist/chunk-V6HCTCVD.js.map +1 -0
  65. package/dist/{chunk-JFGXQBIP.js → chunk-VDUJZF5G.js} +76 -13
  66. package/dist/chunk-VDUJZF5G.js.map +1 -0
  67. package/dist/{chunk-SYDMYBPG.js → chunk-VXFGMDA7.js} +1 -1
  68. package/dist/chunk-VXFGMDA7.js.map +1 -0
  69. package/dist/{chunk-YMKH5XTF.js → chunk-WN52USHV.js} +2 -2
  70. package/dist/{chunk-YMKH5XTF.js.map → chunk-WN52USHV.js.map} +1 -1
  71. package/dist/{chunk-OBLTAKSX.js → chunk-WSXUPSID.js} +1 -1
  72. package/dist/chunk-WSXUPSID.js.map +1 -0
  73. package/dist/{chunk-X2U3QJCJ.js → chunk-ZPWZIVI6.js} +1 -1
  74. package/dist/chunk-ZPWZIVI6.js.map +1 -0
  75. package/dist/collapsible-panel.d.ts +5 -2
  76. package/dist/collapsible-panel.js.map +1 -1
  77. package/dist/css-values.d.ts +75 -0
  78. package/dist/css-values.js +3 -0
  79. package/dist/css-values.js.map +1 -0
  80. package/dist/empty-state.d.ts +2 -1
  81. package/dist/empty-state.js +1 -1
  82. package/dist/floating-toolbar.d.ts +4 -3
  83. package/dist/floating-toolbar.js +1 -1
  84. package/dist/index.d.ts +2 -0
  85. package/dist/index.js +26 -25
  86. package/dist/list-action-row.d.ts +1 -2
  87. package/dist/list-action-row.js +2 -1
  88. package/dist/list-header.d.ts +1 -1
  89. package/dist/list-header.js +2 -1
  90. package/dist/list-inset-control.d.ts +4 -3
  91. package/dist/list-inset-control.js +1 -1
  92. package/dist/list-inset-text.d.ts +4 -3
  93. package/dist/list-inset-text.js +1 -1
  94. package/dist/list-item.d.ts +4 -3
  95. package/dist/list-item.js +2 -1
  96. package/dist/list.d.ts +10 -8
  97. package/dist/list.js +2 -1
  98. package/dist/nav-stack.d.ts +9 -6
  99. package/dist/nav-stack.js +2 -1
  100. package/dist/pane.d.ts +6 -5
  101. package/dist/pane.js +1 -1
  102. package/dist/resizable-region.d.ts +2 -1
  103. package/dist/resizable-region.js +1 -1
  104. package/dist/segmented-control.d.ts +4 -3
  105. package/dist/segmented-control.js +2 -1
  106. package/dist/select.d.ts +3 -1
  107. package/dist/select.js +2 -1
  108. package/dist/semantic-content-BbzjvSu9.d.ts +14 -0
  109. package/dist/skeleton.d.ts +7 -6
  110. package/dist/skeleton.js +2 -1
  111. package/dist/split-view.d.ts +5 -4
  112. package/dist/split-view.js +3 -2
  113. package/dist/split-view.js.map +1 -1
  114. package/dist/state-banner.d.ts +3 -2
  115. package/dist/state-banner.js +2 -1
  116. package/dist/styles/catalog.css +8 -1
  117. package/dist/styles/collapsible-panel.css +6 -2
  118. package/dist/styles/foundation.css +44 -0
  119. package/dist/styles/list-header.css +6 -0
  120. package/dist/styles/list-item.css +9 -0
  121. package/dist/styles/nav-stack.css +32 -2
  122. package/dist/styles/segmented-control.css +8 -1
  123. package/dist/styles/select.css +2 -1
  124. package/dist/styles/state-banner.css +14 -0
  125. package/dist/styles/tab-scaffold.css +1 -0
  126. package/dist/styles/token-search-field.css +4 -0
  127. package/dist/styles/toolbar-control-group.css +98 -16
  128. package/dist/styles/webawesome.css +22 -0
  129. package/dist/styles/workbench.css +4 -0
  130. package/dist/sunken-panel.d.ts +4 -3
  131. package/dist/sunken-panel.js +1 -1
  132. package/dist/surface-scaffold.d.ts +6 -5
  133. package/dist/surface-scaffold.js +1 -1
  134. package/dist/tab-bar.d.ts +6 -5
  135. package/dist/tab-bar.js +1 -1
  136. package/dist/tab-scaffold.d.ts +2 -1
  137. package/dist/tab-scaffold.js.map +1 -1
  138. package/dist/token-search-field.d.ts +5 -4
  139. package/dist/token-search-field.js +1 -1
  140. package/dist/toolbar-control-group.d.ts +10 -4
  141. package/dist/toolbar-control-group.js +1 -1
  142. package/dist/toolbar-text.js +2 -1
  143. package/dist/toolbar.d.ts +6 -5
  144. package/dist/toolbar.js +1 -1
  145. package/dist/value-table.d.ts +4 -2
  146. package/dist/value-table.js +2 -1
  147. package/dist/wire-nav-stack.d.ts +4 -1
  148. package/dist/wire-nav-stack.js +125 -0
  149. package/dist/wire-nav-stack.js.map +1 -1
  150. package/dist/wire-resizable-regions.js +1 -1
  151. package/dist/wire-tab-bars.d.ts +1 -0
  152. package/dist/wire-token-search-fields.js +1 -1
  153. package/dist/workbench.d.ts +7 -4
  154. package/dist/workbench.js.map +1 -1
  155. package/docs/accessibility.md +9 -3
  156. package/docs/collapsible-panel.md +4 -1
  157. package/docs/component-contract.md +56 -8
  158. package/docs/component-selection.md +45 -8
  159. package/docs/design/templates/list/compact-dark.svg +1 -1
  160. package/docs/design/templates/list/compact.svg +1 -1
  161. package/docs/design/templates/list/stack-dark.svg +1 -1
  162. package/docs/design/templates/list/stack.svg +1 -1
  163. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  164. package/docs/design/templates/list-action-row/default.svg +1 -1
  165. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  166. package/docs/design/templates/list-action-row/selected.svg +1 -1
  167. package/docs/design/templates/list-action-row-dark.svg +1 -1
  168. package/docs/design/templates/list-action-row.svg +1 -1
  169. package/docs/design/templates/list-dark.svg +1 -1
  170. package/docs/design/templates/list-item/default-dark.svg +1 -1
  171. package/docs/design/templates/list-item/default.svg +1 -1
  172. package/docs/design/templates/list-item/multiline-dark.svg +1 -1
  173. package/docs/design/templates/list-item/multiline.svg +1 -1
  174. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  175. package/docs/design/templates/list-item/selected.svg +1 -1
  176. package/docs/design/templates/list-item/trailing-dark.svg +1 -1
  177. package/docs/design/templates/list-item/trailing.svg +1 -1
  178. package/docs/design/templates/list-item-dark.svg +1 -1
  179. package/docs/design/templates/list-item.svg +1 -1
  180. package/docs/design/templates/list.svg +1 -1
  181. package/docs/design-philosophy.md +4 -2
  182. package/docs/examples/component-catalog-extension-v2.json +2 -0
  183. package/docs/layout.md +12 -4
  184. package/docs/nav-stack.md +29 -8
  185. package/docs/recipes.md +8 -4
  186. package/docs/split-view.md +6 -0
  187. package/docs/surface-scaffold.md +8 -0
  188. package/docs/tab-scaffold.md +3 -1
  189. package/docs/type-contracts.md +39 -12
  190. package/docs/ui-analyzer.md +17 -3
  191. package/docs/ui-doctor.md +7 -0
  192. package/docs/ux-demo.md +10 -4
  193. package/docs/webawesome-theme.md +6 -0
  194. package/docs/workbench.md +6 -4
  195. package/llms.txt +46 -6
  196. package/package.json +5 -1
  197. package/ux-demo/recipes/compact-toolbar.tsx +10 -9
  198. package/ux-demo/recipes/composer-form.tsx +6 -3
  199. package/ux-demo/recipes/loading-inspector.tsx +2 -1
  200. package/dist/chunk-2NMQW7FX.js.map +0 -1
  201. package/dist/chunk-4EF2CMYS.js.map +0 -1
  202. package/dist/chunk-7KEUJIIC.js.map +0 -1
  203. package/dist/chunk-B3A3IVO5.js.map +0 -1
  204. package/dist/chunk-FEY65TBV.js.map +0 -1
  205. package/dist/chunk-GWEAPEZI.js.map +0 -1
  206. package/dist/chunk-HP2B2L26.js.map +0 -1
  207. package/dist/chunk-IJBSZ4NX.js.map +0 -1
  208. package/dist/chunk-J5BFYY7Q.js.map +0 -1
  209. package/dist/chunk-JFGXQBIP.js.map +0 -1
  210. package/dist/chunk-JRKSK2HX.js.map +0 -1
  211. package/dist/chunk-KWWR5VMS.js.map +0 -1
  212. package/dist/chunk-LPOMRC4C.js.map +0 -1
  213. package/dist/chunk-OBLTAKSX.js.map +0 -1
  214. package/dist/chunk-OT6RPYAP.js.map +0 -1
  215. package/dist/chunk-PX3YSS5C.js.map +0 -1
  216. package/dist/chunk-QO55FN2Y.js.map +0 -1
  217. package/dist/chunk-R7ZWUN64.js.map +0 -1
  218. package/dist/chunk-RKVQEH4J.js.map +0 -1
  219. package/dist/chunk-SYDMYBPG.js.map +0 -1
  220. package/dist/chunk-TB6DY7H5.js.map +0 -1
  221. package/dist/chunk-VCE4MPVE.js.map +0 -1
  222. package/dist/chunk-W5L2JSBY.js.map +0 -1
  223. package/dist/chunk-WNWD54GR.js.map +0 -1
  224. package/dist/chunk-X2U3QJCJ.js.map +0 -1
@@ -1,4 +1,5 @@
1
- import { SafeHtml } from 'kerfjs';
1
+ import * as kerfjs from 'kerfjs';
2
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
2
3
 
3
4
  type ToolbarControlGroupAppearance = 'contained' | 'borderless';
4
5
  type ToolbarControlGroupTone = 'default' | 'dark';
@@ -8,9 +9,9 @@ type ToolbarControlGroupSize = 'default' | 'compact';
8
9
  type ToolbarControlGroupDensity = 'comfortable' | 'tight';
9
10
  type ToolbarControlGroupContent = 'icon' | 'text' | 'mixed' | 'avatar';
10
11
  type ToolbarControlGroupSelectedChrome = 'raised' | 'filled' | 'outline';
11
- type ToolbarControlGroupSelectedTone = 'brand' | 'neutral';
12
+ type ToolbarControlGroupSelectedTone = 'brand' | 'neutral' | 'pop';
12
13
  interface ToolbarControlGroupProps {
13
- children: SafeHtml | SafeHtml[];
14
+ children: KerfUiContent;
14
15
  label?: string;
15
16
  className?: string;
16
17
  expanded?: boolean;
@@ -29,7 +30,12 @@ interface ToolbarControlGroupProps {
29
30
  nestedDropdown?: boolean;
30
31
  /** Add contrast behind photo-backed avatar content. */
31
32
  scrim?: boolean;
33
+ /**
34
+ * Avatar image URL. A single-control group paints it on the group; a
35
+ * multi-control group paints it only on the pressed selection highlight.
36
+ */
37
+ avatarImage?: string;
32
38
  }
33
- declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, selectedChrome, selectedTone, nestedDropdown, scrim, }: ToolbarControlGroupProps): SafeHtml;
39
+ declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, selectedChrome, selectedTone, nestedDropdown, scrim, avatarImage, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
34
40
 
35
41
  export { ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone };
@@ -1,3 +1,3 @@
1
- export { ToolbarControlGroup } from './chunk-WNWD54GR.js';
1
+ export { ToolbarControlGroup } from './chunk-B6JYRX3P.js';
2
2
 
3
3
  //# sourceMappingURL=toolbar-control-group.js.map
@@ -1,4 +1,5 @@
1
- export { ToolbarText } from './chunk-R7ZWUN64.js';
1
+ export { ToolbarText } from './chunk-V6GQKOX2.js';
2
+
2
3
 
3
4
 
4
5
  //# sourceMappingURL=toolbar-text.js.map
package/dist/toolbar.d.ts CHANGED
@@ -1,15 +1,16 @@
1
- import { SafeHtml } from 'kerfjs';
1
+ import * as kerfjs from 'kerfjs';
2
2
  import { D as DividerSides } from './divider-sides-267FA7sY.js';
3
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
4
 
4
5
  interface ToolbarProps {
5
- leading?: SafeHtml;
6
- center?: SafeHtml;
7
- trailing?: SafeHtml;
6
+ leading?: KerfUiContent;
7
+ center?: KerfUiContent;
8
+ trailing?: KerfUiContent;
8
9
  label?: string;
9
10
  /** Physical divider edges in canonical top/right/bottom/left order. Defaults to bottom. */
10
11
  dividerSides?: DividerSides;
11
12
  className?: string;
12
13
  }
13
- declare function Toolbar({ leading, center, trailing, label, dividerSides, className, }: ToolbarProps): SafeHtml;
14
+ declare function Toolbar({ leading, center, trailing, label, dividerSides, className, }: ToolbarProps): kerfjs.SafeHtml;
14
15
 
15
16
  export { DividerSides, Toolbar, type ToolbarProps };
package/dist/toolbar.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Toolbar } from './chunk-SYDMYBPG.js';
1
+ export { Toolbar } from './chunk-VXFGMDA7.js';
2
2
 
3
3
  //# sourceMappingURL=toolbar.js.map
@@ -1,4 +1,6 @@
1
+ import * as kerfjs from 'kerfjs';
1
2
  import { SafeHtml } from 'kerfjs';
3
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
2
4
 
3
5
  interface ValueTableRowProps {
4
6
  label: string | SafeHtml;
@@ -13,8 +15,8 @@ declare function ValueTableRow({ label, value, icon, className, placeholder, }:
13
15
  interface ValueTableProps {
14
16
  label: string;
15
17
  className?: string;
16
- children: SafeHtml | readonly SafeHtml[];
18
+ children: KerfUiContent;
17
19
  }
18
- declare function ValueTable({ label, className, children, }: ValueTableProps): SafeHtml;
20
+ declare function ValueTable({ label, className, children, }: ValueTableProps): kerfjs.SafeHtml;
19
21
 
20
22
  export { ValueTable, type ValueTableProps, ValueTableRow, type ValueTableRowProps };
@@ -1,4 +1,5 @@
1
- export { ValueTable, ValueTableRow } from './chunk-KWWR5VMS.js';
1
+ export { ValueTable, ValueTableRow } from './chunk-V6HCTCVD.js';
2
+
2
3
 
3
4
 
4
5
  //# sourceMappingURL=value-table.js.map
@@ -8,7 +8,10 @@ interface WireNavStackOptions {
8
8
  * Animate a `NavStack`'s push/pop transitions and wire its back control. The app
9
9
  * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it
10
10
  * changes; this helper slides the content and settles the chrome across each
11
- * change, and calls `onBack` when the back control is used. Returns a disposer.
11
+ * change, moves focus into every new top view, restores that view's last focused
12
+ * descendant on pop, and calls `onBack` when the back control is used. Mark a
13
+ * preferred initial target with `data-nav-focus`; otherwise the first focusable
14
+ * descendant (or the view itself) receives focus. Returns a disposer.
12
15
  */
13
16
  declare function wireNavStack(root: Element, options?: WireNavStackOptions): () => void;
14
17
 
@@ -2,6 +2,14 @@ import { delegate } from 'kerfjs';
2
2
 
3
3
  // src/wire-nav-stack.ts
4
4
  var DEFAULT_DURATION = 200;
5
+ var FOCUSABLE_SELECTOR = [
6
+ "a[href]",
7
+ "button:not([disabled])",
8
+ "input:not([disabled])",
9
+ "select:not([disabled])",
10
+ "textarea:not([disabled])",
11
+ '[tabindex]:not([tabindex="-1"])'
12
+ ].join(",");
5
13
  function viewsOf(viewport) {
6
14
  return Array.from(
7
15
  viewport.querySelectorAll(":scope > .kui-nav-stack__view")
@@ -16,6 +24,16 @@ function topView(viewport) {
16
24
  function reducedMotion(view) {
17
25
  return typeof view.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
18
26
  }
27
+ function chromeOf(section) {
28
+ return Array.from(
29
+ section.querySelectorAll(
30
+ ":scope > [data-nav-stack-chrome], :scope > [data-nav-stack-bottom]"
31
+ )
32
+ );
33
+ }
34
+ function availableFocusTarget(element) {
35
+ return element instanceof HTMLElement && element.isConnected && !element.matches('[disabled], [aria-disabled="true"]') && !element.closest('[inert], [hidden], [aria-hidden="true"]');
36
+ }
19
37
  function wireNavStack(root, options = {}) {
20
38
  const section = root.matches('[data-component="nav-stack"]') ? root : root.querySelector('[data-component="nav-stack"]');
21
39
  const viewport = section?.querySelector("[data-nav-stack-viewport]");
@@ -23,6 +41,13 @@ function wireNavStack(root, options = {}) {
23
41
  };
24
42
  const view = section.ownerDocument.defaultView ?? window;
25
43
  const duration = options.duration ?? DEFAULT_DURATION;
44
+ const previousDuration = section.style.getPropertyValue(
45
+ "--kui-nav-stack-transition-duration"
46
+ );
47
+ section.style.setProperty(
48
+ "--kui-nav-stack-transition-duration",
49
+ `${Math.max(0, duration)}ms`
50
+ );
26
51
  const disposeBack = delegate(
27
52
  section,
28
53
  "click",
@@ -31,6 +56,11 @@ function wireNavStack(root, options = {}) {
31
56
  );
32
57
  let activeKey = topView(viewport)?.dataset.navKey;
33
58
  const timers = /* @__PURE__ */ new Set();
59
+ const rememberedFocus = /* @__PURE__ */ new Map();
60
+ const generatedFallbacks = /* @__PURE__ */ new Map();
61
+ let chromeSnapshots = chromeOf(section).map(
62
+ (element) => element.cloneNode(true)
63
+ );
34
64
  const settle = (fn) => {
35
65
  if (duration <= 0 || reducedMotion(view)) {
36
66
  fn();
@@ -62,6 +92,80 @@ function wireNavStack(root, options = {}) {
62
92
  el.classList.add("kui-nav-stack__view--exiting");
63
93
  }
64
94
  };
95
+ const rememberFocusedView = (target) => {
96
+ if (!(target instanceof HTMLElement)) return;
97
+ const owner = target.closest(".kui-nav-stack__view");
98
+ const key = owner?.dataset.navKey;
99
+ if (key && owner !== target && viewport.contains(owner))
100
+ rememberedFocus.set(key, target);
101
+ };
102
+ const focusInto = (targetView) => {
103
+ const key = targetView.dataset.navKey;
104
+ const remembered = key ? rememberedFocus.get(key) : void 0;
105
+ const preferred = Array.from(
106
+ targetView.querySelectorAll("[data-nav-focus]")
107
+ ).find(availableFocusTarget);
108
+ const first = Array.from(
109
+ targetView.querySelectorAll(FOCUSABLE_SELECTOR)
110
+ ).find(availableFocusTarget);
111
+ const target = (availableFocusTarget(remembered) && targetView.contains(remembered) ? remembered : void 0) ?? preferred ?? first;
112
+ if (target) {
113
+ target.focus({ preventScroll: true });
114
+ return;
115
+ }
116
+ if (!generatedFallbacks.has(targetView)) {
117
+ generatedFallbacks.set(
118
+ targetView,
119
+ targetView.hasAttribute("tabindex") ? targetView.getAttribute("tabindex") : null
120
+ );
121
+ targetView.tabIndex = -1;
122
+ }
123
+ targetView.focus({ preventScroll: true });
124
+ };
125
+ const onFocusIn = (event) => rememberFocusedView(event.target);
126
+ section.addEventListener("focusin", onFocusIn);
127
+ rememberFocusedView(section.ownerDocument.activeElement);
128
+ const crossFadeChrome = () => {
129
+ const current = chromeOf(section);
130
+ const animated = duration > 0 && !reducedMotion(view);
131
+ if (!animated) {
132
+ chromeSnapshots = current.map(
133
+ (element) => element.cloneNode(true)
134
+ );
135
+ return;
136
+ }
137
+ section.dataset.navChromeTransition = "true";
138
+ const copies = chromeSnapshots.map((snapshot) => {
139
+ const copy = snapshot.cloneNode(true);
140
+ copy.classList.add("kui-nav-stack__chrome-copy");
141
+ copy.dataset.navChromeCopy = "";
142
+ copy.setAttribute("aria-hidden", "true");
143
+ copy.setAttribute("inert", "");
144
+ if (copy.hasAttribute("data-nav-stack-bottom"))
145
+ copy.classList.add("kui-nav-stack__chrome-copy--bottom");
146
+ section.append(copy);
147
+ return copy;
148
+ });
149
+ current.forEach(
150
+ (element) => element.classList.add("kui-nav-stack__chrome--entering")
151
+ );
152
+ void section.offsetWidth;
153
+ view.requestAnimationFrame(() => {
154
+ current.forEach(
155
+ (element) => element.classList.remove("kui-nav-stack__chrome--entering")
156
+ );
157
+ copies.forEach(
158
+ (copy) => copy.classList.add("kui-nav-stack__chrome-copy--exiting")
159
+ );
160
+ });
161
+ settle(() => {
162
+ copies.forEach((copy) => copy.remove());
163
+ delete section.dataset.navChromeTransition;
164
+ });
165
+ chromeSnapshots = current.map(
166
+ (element) => element.cloneNode(true)
167
+ );
168
+ };
65
169
  const observer = new MutationObserver((records) => {
66
170
  const removed = [];
67
171
  let added = false;
@@ -83,18 +187,39 @@ function wireNavStack(root, options = {}) {
83
187
  poppedTop.setAttribute("aria-hidden", "true");
84
188
  viewport.append(poppedTop);
85
189
  play(poppedTop, "exiting");
190
+ crossFadeChrome();
191
+ if (top) focusInto(top);
86
192
  settle(() => poppedTop.remove());
87
193
  } else if (added && currentKey !== activeKey && top) {
194
+ rememberFocusedView(section.ownerDocument.activeElement);
88
195
  play(top, "entering");
196
+ crossFadeChrome();
197
+ focusInto(top);
89
198
  }
90
199
  activeKey = currentKey;
91
200
  });
92
201
  observer.observe(viewport, { childList: true });
93
202
  return () => {
94
203
  disposeBack();
204
+ section.removeEventListener("focusin", onFocusIn);
95
205
  observer.disconnect();
96
206
  for (const timer of timers) view.clearTimeout(timer);
97
207
  timers.clear();
208
+ section.querySelectorAll("[data-nav-chrome-copy]").forEach((copy) => copy.remove());
209
+ delete section.dataset.navChromeTransition;
210
+ for (const [element, previousTabIndex] of generatedFallbacks) {
211
+ if (!element.isConnected || element.tabIndex !== -1) continue;
212
+ if (previousTabIndex === null) element.removeAttribute("tabindex");
213
+ else element.setAttribute("tabindex", previousTabIndex);
214
+ }
215
+ generatedFallbacks.clear();
216
+ rememberedFocus.clear();
217
+ if (previousDuration)
218
+ section.style.setProperty(
219
+ "--kui-nav-stack-transition-duration",
220
+ previousDuration
221
+ );
222
+ else section.style.removeProperty("--kui-nav-stack-transition-duration");
98
223
  };
99
224
  }
100
225
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/wire-nav-stack.ts"],"names":[],"mappings":";;;AASA,IAAM,gBAAA,GAAmB,GAAA;AAEzB,SAAS,QAAQ,QAAA,EAAkC;AACjD,EAAA,OAAO,KAAA,CAAM,IAAA;AAAA,IACX,QAAA,CAAS,iBAA8B,+BAA+B;AAAA,GACxE;AACF;AAEA,SAAS,QAAQ,QAAA,EAA4C;AAC3D,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,QAAQ,CAAA,CAAE,MAAA;AAAA,IAC7B,CAAC,IAAA,KAAS,IAAA,CAAK,OAAA,CAAQ,UAAA,KAAe;AAAA,GACxC;AACA,EAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AAC7B;AAEA,SAAS,cAAc,IAAA,EAAuB;AAC5C,EAAA,OACE,OAAO,IAAA,CAAK,UAAA,KAAe,cAC3B,IAAA,CAAK,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA;AAExD;AAQO,SAAS,YAAA,CACd,IAAA,EACA,OAAA,GAA+B,EAAC,EACpB;AACZ,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,8BAA8B,IACvD,IAAA,GACA,IAAA,CAAK,cAAc,8BAA8B,CAAA;AACrD,EAAA,MAAM,QAAA,GAAW,OAAA,EAAS,aAAA,CAAc,2BAA2B,CAAA;AACnE,EAAA,IAAI,EAAE,OAAA,YAAmB,WAAA,CAAA,IAAgB,CAAC,QAAA,SAAiB,MAAM;AAAA,EAAC,CAAA;AAElE,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,aAAA,CAAc,WAAA,IAAe,MAAA;AAClD,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,gBAAA;AACrC,EAAA,MAAM,WAAA,GAAc,QAAA;AAAA,IAAS,OAAA;AAAA,IAAS,OAAA;AAAA,IAAS,iBAAA;AAAA,IAAmB,MAChE,QAAQ,MAAA;AAAS,GACnB;AAEA,EAAA,IAAI,SAAA,GAAY,OAAA,CAAQ,QAAQ,CAAA,EAAG,OAAA,CAAQ,MAAA;AAC3C,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAY;AAE/B,EAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAyB;AACvC,IAAA,IAAI,QAAA,IAAY,CAAA,IAAK,aAAA,CAAc,IAAI,CAAA,EAAG;AACxC,MAAA,EAAA,EAAG;AACH,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,UAAA,CAAW,MAAM;AAClC,MAAA,MAAA,CAAO,OAAO,KAAK,CAAA;AACnB,MAAA,EAAA,EAAG;AAAA,IACL,GAAG,QAAQ,CAAA;AACX,IAAA,MAAA,CAAO,IAAI,KAAK,CAAA;AAAA,EAClB,CAAA;AAEA,EAAA,MAAM,IAAA,GAAO,CAAC,EAAA,EAAiB,IAAA,KAAuC;AACpE,IAAA,MAAM,QAAA,GAAW,QAAA,GAAW,CAAA,IAAK,CAAC,cAAc,IAAI,CAAA;AACpD,IAAA,IAAI,SAAS,UAAA,EAAY;AAGvB,MAAA,EAAA,CAAG,SAAA,CAAU,IAAI,+BAA+B,CAAA;AAChD,MAAA,IAAI,QAAA,EAAU;AAEZ,QAAA,KAAK,EAAA,CAAG,WAAA;AACR,QAAA,IAAA,CAAK,qBAAA;AAAA,UAAsB,MACzB,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,+BAA+B;AAAA,SACrD;AAAA,MACF,CAAA,MAAO;AACL,QAAA,EAAA,CAAG,SAAA,CAAU,OAAO,+BAA+B,CAAA;AAAA,MACrD;AAAA,IACF,WAAW,QAAA,EAAU;AAGnB,MAAA,IAAA,CAAK,qBAAA;AAAA,QAAsB,MACzB,EAAA,CAAG,SAAA,CAAU,GAAA,CAAI,8BAA8B;AAAA,OACjD;AAAA,IACF,CAAA,MAAO;AACL,MAAA,EAAA,CAAG,SAAA,CAAU,IAAI,8BAA8B,CAAA;AAAA,IACjD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,IAAI,gBAAA,CAAiB,CAAC,OAAA,KAAY;AACjD,IAAA,MAAM,UAAyB,EAAC;AAChC,IAAA,IAAI,KAAA,GAAQ,KAAA;AACZ,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,CAAC,IAAA,KAAS;AACpC,QAAA,IACE,IAAA,YAAgB,eAChB,IAAA,CAAK,SAAA,CAAU,SAAS,qBAAqB,CAAA,IAC7C,IAAA,CAAK,OAAA,CAAQ,UAAA,KAAe,MAAA;AAE5B,UAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,MACrB,CAAC,CAAA;AACD,MAAA,MAAA,CAAO,UAAA,CAAW,OAAA,CAAQ,CAAC,IAAA,KAAS;AAClC,QAAA,IACE,IAAA,YAAgB,WAAA,IAChB,IAAA,CAAK,SAAA,CAAU,SAAS,qBAAqB,CAAA;AAE7C,UAAA,KAAA,GAAQ,IAAA;AAAA,MACZ,CAAC,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,GAAA,GAAM,QAAQ,QAAQ,CAAA;AAC5B,IAAA,MAAM,UAAA,GAAa,KAAK,OAAA,CAAQ,MAAA;AAChC,IAAA,MAAM,SAAA,GAAY,QAAQ,IAAA,CAAK,CAAC,SAAS,IAAA,CAAK,OAAA,CAAQ,WAAW,SAAS,CAAA;AAE1E,IAAA,IAAI,SAAA,IAAa,eAAe,SAAA,EAAW;AAEzC,MAAA,SAAA,CAAU,QAAQ,UAAA,GAAa,MAAA;AAC/B,MAAA,SAAA,CAAU,YAAA,CAAa,eAAe,MAAM,CAAA;AAC5C,MAAA,QAAA,CAAS,OAAO,SAAS,CAAA;AACzB,MAAA,IAAA,CAAK,WAAW,SAAS,CAAA;AACzB,MAAA,MAAA,CAAO,MAAM,SAAA,CAAU,MAAA,EAAQ,CAAA;AAAA,IACjC,CAAA,MAAA,IAAW,KAAA,IAAS,UAAA,KAAe,SAAA,IAAa,GAAA,EAAK;AAEnD,MAAA,IAAA,CAAK,KAAK,UAAU,CAAA;AAAA,IACtB;AAEA,IAAA,SAAA,GAAY,UAAA;AAAA,EACd,CAAC,CAAA;AACD,EAAA,QAAA,CAAS,OAAA,CAAQ,QAAA,EAAU,EAAE,SAAA,EAAW,MAAM,CAAA;AAE9C,EAAA,OAAO,MAAM;AACX,IAAA,WAAA,EAAY;AACZ,IAAA,QAAA,CAAS,UAAA,EAAW;AACpB,IAAA,KAAA,MAAW,KAAA,IAAS,MAAA,EAAQ,IAAA,CAAK,YAAA,CAAa,KAAK,CAAA;AACnD,IAAA,MAAA,CAAO,KAAA,EAAM;AAAA,EACf,CAAA;AACF","file":"wire-nav-stack.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireNavStackOptions {\n /** Invoked when the back control is activated. The app pops its own stack. */\n onBack?: () => void;\n /** Transition duration in ms (default 200). Set 0 to disable animation. */\n duration?: number;\n}\n\nconst DEFAULT_DURATION = 200;\n\nfunction viewsOf(viewport: Element): HTMLElement[] {\n return Array.from(\n viewport.querySelectorAll<HTMLElement>(':scope > .kui-nav-stack__view'),\n );\n}\n\nfunction topView(viewport: Element): HTMLElement | undefined {\n const live = viewsOf(viewport).filter(\n (view) => view.dataset.navExiting !== 'true',\n );\n return live[live.length - 1];\n}\n\nfunction reducedMotion(view: Window): boolean {\n return (\n typeof view.matchMedia === 'function' &&\n view.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/**\n * Animate a `NavStack`'s push/pop transitions and wire its back control. The app\n * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it\n * changes; this helper slides the content and settles the chrome across each\n * change, and calls `onBack` when the back control is used. Returns a disposer.\n */\nexport function wireNavStack(\n root: Element,\n options: WireNavStackOptions = {},\n): () => void {\n const section = root.matches('[data-component=\"nav-stack\"]')\n ? root\n : root.querySelector('[data-component=\"nav-stack\"]');\n const viewport = section?.querySelector('[data-nav-stack-viewport]');\n if (!(section instanceof HTMLElement) || !viewport) return () => {};\n\n const view = section.ownerDocument.defaultView ?? window;\n const duration = options.duration ?? DEFAULT_DURATION;\n const disposeBack = delegate(section, 'click', '[data-nav-back]', () =>\n options.onBack?.(),\n );\n\n let activeKey = topView(viewport)?.dataset.navKey;\n const timers = new Set<number>();\n\n const settle = (fn: () => void): void => {\n if (duration <= 0 || reducedMotion(view)) {\n fn();\n return;\n }\n const timer = view.setTimeout(() => {\n timers.delete(timer);\n fn();\n }, duration);\n timers.add(timer);\n };\n\n const play = (el: HTMLElement, kind: 'entering' | 'exiting'): void => {\n const animated = duration > 0 && !reducedMotion(view);\n if (kind === 'entering') {\n // Push: start the incoming view off the trailing edge, then release it so\n // it slides to rest (translateX(100%) → 0).\n el.classList.add('kui-nav-stack__view--entering');\n if (animated) {\n // Force a reflow so the starting transform applies before we clear it.\n void el.offsetWidth;\n view.requestAnimationFrame(() =>\n el.classList.remove('kui-nav-stack__view--entering'),\n );\n } else {\n el.classList.remove('kui-nav-stack__view--entering');\n }\n } else if (animated) {\n // Pop: the outgoing view is at rest; add the off-edge class on the next\n // frame so it slides OUT (translateX(0) → 100%), not in.\n view.requestAnimationFrame(() =>\n el.classList.add('kui-nav-stack__view--exiting'),\n );\n } else {\n el.classList.add('kui-nav-stack__view--exiting');\n }\n };\n\n const observer = new MutationObserver((records) => {\n const removed: HTMLElement[] = [];\n let added = false;\n for (const record of records) {\n record.removedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view') &&\n node.dataset.navExiting !== 'true'\n )\n removed.push(node);\n });\n record.addedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view')\n )\n added = true;\n });\n }\n\n const top = topView(viewport);\n const currentKey = top?.dataset.navKey;\n const poppedTop = removed.find((node) => node.dataset.navKey === activeKey);\n\n if (poppedTop && currentKey !== activeKey) {\n // Pop: bring the removed node back briefly to slide it out over the revealed view.\n poppedTop.dataset.navExiting = 'true';\n poppedTop.setAttribute('aria-hidden', 'true');\n viewport.append(poppedTop);\n play(poppedTop, 'exiting');\n settle(() => poppedTop.remove());\n } else if (added && currentKey !== activeKey && top) {\n // Push: slide the new top in.\n play(top, 'entering');\n }\n\n activeKey = currentKey;\n });\n observer.observe(viewport, { childList: true });\n\n return () => {\n disposeBack();\n observer.disconnect();\n for (const timer of timers) view.clearTimeout(timer);\n timers.clear();\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/wire-nav-stack.ts"],"names":[],"mappings":"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","file":"wire-nav-stack.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireNavStackOptions {\n /** Invoked when the back control is activated. The app pops its own stack. */\n onBack?: () => void;\n /** Transition duration in ms (default 200). Set 0 to disable animation. */\n duration?: number;\n}\n\nconst DEFAULT_DURATION = 200;\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nfunction viewsOf(viewport: Element): HTMLElement[] {\n return Array.from(\n viewport.querySelectorAll<HTMLElement>(':scope > .kui-nav-stack__view'),\n );\n}\n\nfunction topView(viewport: Element): HTMLElement | undefined {\n const live = viewsOf(viewport).filter(\n (view) => view.dataset.navExiting !== 'true',\n );\n return live[live.length - 1];\n}\n\nfunction reducedMotion(view: Window): boolean {\n return (\n typeof view.matchMedia === 'function' &&\n view.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\nfunction chromeOf(section: HTMLElement): HTMLElement[] {\n return Array.from(\n section.querySelectorAll<HTMLElement>(\n ':scope > [data-nav-stack-chrome], :scope > [data-nav-stack-bottom]',\n ),\n );\n}\n\nfunction availableFocusTarget(\n element: Element | null | undefined,\n): element is HTMLElement {\n return (\n element instanceof HTMLElement &&\n element.isConnected &&\n !element.matches('[disabled], [aria-disabled=\"true\"]') &&\n !element.closest('[inert], [hidden], [aria-hidden=\"true\"]')\n );\n}\n\n/**\n * Animate a `NavStack`'s push/pop transitions and wire its back control. The app\n * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it\n * changes; this helper slides the content and settles the chrome across each\n * change, moves focus into every new top view, restores that view's last focused\n * descendant on pop, and calls `onBack` when the back control is used. Mark a\n * preferred initial target with `data-nav-focus`; otherwise the first focusable\n * descendant (or the view itself) receives focus. Returns a disposer.\n */\nexport function wireNavStack(\n root: Element,\n options: WireNavStackOptions = {},\n): () => void {\n const section = root.matches('[data-component=\"nav-stack\"]')\n ? root\n : root.querySelector('[data-component=\"nav-stack\"]');\n const viewport = section?.querySelector('[data-nav-stack-viewport]');\n if (!(section instanceof HTMLElement) || !viewport) return () => {};\n\n const view = section.ownerDocument.defaultView ?? window;\n const duration = options.duration ?? DEFAULT_DURATION;\n const previousDuration = section.style.getPropertyValue(\n '--kui-nav-stack-transition-duration',\n );\n section.style.setProperty(\n '--kui-nav-stack-transition-duration',\n `${Math.max(0, duration)}ms`,\n );\n const disposeBack = delegate(section, 'click', '[data-nav-back]', () =>\n options.onBack?.(),\n );\n\n let activeKey = topView(viewport)?.dataset.navKey;\n const timers = new Set<number>();\n const rememberedFocus = new Map<string, HTMLElement>();\n const generatedFallbacks = new Map<HTMLElement, string | null>();\n let chromeSnapshots = chromeOf(section).map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n\n const settle = (fn: () => void): void => {\n if (duration <= 0 || reducedMotion(view)) {\n fn();\n return;\n }\n const timer = view.setTimeout(() => {\n timers.delete(timer);\n fn();\n }, duration);\n timers.add(timer);\n };\n\n const play = (el: HTMLElement, kind: 'entering' | 'exiting'): void => {\n const animated = duration > 0 && !reducedMotion(view);\n if (kind === 'entering') {\n // Push: start the incoming view off the trailing edge, then release it so\n // it slides to rest (translateX(100%) → 0).\n el.classList.add('kui-nav-stack__view--entering');\n if (animated) {\n // Force a reflow so the starting transform applies before we clear it.\n void el.offsetWidth;\n view.requestAnimationFrame(() =>\n el.classList.remove('kui-nav-stack__view--entering'),\n );\n } else {\n el.classList.remove('kui-nav-stack__view--entering');\n }\n } else if (animated) {\n // Pop: the outgoing view is at rest; add the off-edge class on the next\n // frame so it slides OUT (translateX(0) → 100%), not in.\n view.requestAnimationFrame(() =>\n el.classList.add('kui-nav-stack__view--exiting'),\n );\n } else {\n el.classList.add('kui-nav-stack__view--exiting');\n }\n };\n\n const rememberFocusedView = (target: Element | null): void => {\n if (!(target instanceof HTMLElement)) return;\n const owner = target.closest<HTMLElement>('.kui-nav-stack__view');\n const key = owner?.dataset.navKey;\n if (key && owner !== target && viewport.contains(owner))\n rememberedFocus.set(key, target);\n };\n\n const focusInto = (targetView: HTMLElement): void => {\n const key = targetView.dataset.navKey;\n const remembered = key ? rememberedFocus.get(key) : undefined;\n const preferred = Array.from(\n targetView.querySelectorAll<HTMLElement>('[data-nav-focus]'),\n ).find(availableFocusTarget);\n const first = Array.from(\n targetView.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),\n ).find(availableFocusTarget);\n const target =\n (availableFocusTarget(remembered) && targetView.contains(remembered)\n ? remembered\n : undefined) ??\n preferred ??\n first;\n if (target) {\n target.focus({ preventScroll: true });\n return;\n }\n\n if (!generatedFallbacks.has(targetView)) {\n generatedFallbacks.set(\n targetView,\n targetView.hasAttribute('tabindex')\n ? targetView.getAttribute('tabindex')\n : null,\n );\n targetView.tabIndex = -1;\n }\n targetView.focus({ preventScroll: true });\n };\n\n const onFocusIn = (event: Event): void =>\n rememberFocusedView(event.target as Element | null);\n section.addEventListener('focusin', onFocusIn);\n rememberFocusedView(section.ownerDocument.activeElement);\n\n const crossFadeChrome = (): void => {\n const current = chromeOf(section);\n const animated = duration > 0 && !reducedMotion(view);\n if (!animated) {\n chromeSnapshots = current.map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n return;\n }\n\n section.dataset.navChromeTransition = 'true';\n const copies = chromeSnapshots.map((snapshot) => {\n const copy = snapshot.cloneNode(true) as HTMLElement;\n copy.classList.add('kui-nav-stack__chrome-copy');\n copy.dataset.navChromeCopy = '';\n copy.setAttribute('aria-hidden', 'true');\n copy.setAttribute('inert', '');\n if (copy.hasAttribute('data-nav-stack-bottom'))\n copy.classList.add('kui-nav-stack__chrome-copy--bottom');\n section.append(copy);\n return copy;\n });\n\n current.forEach((element) =>\n element.classList.add('kui-nav-stack__chrome--entering'),\n );\n void section.offsetWidth;\n view.requestAnimationFrame(() => {\n current.forEach((element) =>\n element.classList.remove('kui-nav-stack__chrome--entering'),\n );\n copies.forEach((copy) =>\n copy.classList.add('kui-nav-stack__chrome-copy--exiting'),\n );\n });\n settle(() => {\n copies.forEach((copy) => copy.remove());\n delete section.dataset.navChromeTransition;\n });\n chromeSnapshots = current.map((element) =>\n element.cloneNode(true),\n ) as HTMLElement[];\n };\n\n const observer = new MutationObserver((records) => {\n const removed: HTMLElement[] = [];\n let added = false;\n for (const record of records) {\n record.removedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view') &&\n node.dataset.navExiting !== 'true'\n )\n removed.push(node);\n });\n record.addedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view')\n )\n added = true;\n });\n }\n\n const top = topView(viewport);\n const currentKey = top?.dataset.navKey;\n const poppedTop = removed.find((node) => node.dataset.navKey === activeKey);\n\n if (poppedTop && currentKey !== activeKey) {\n // Pop: bring the removed node back briefly to slide it out over the revealed view.\n poppedTop.dataset.navExiting = 'true';\n poppedTop.setAttribute('aria-hidden', 'true');\n viewport.append(poppedTop);\n play(poppedTop, 'exiting');\n crossFadeChrome();\n if (top) focusInto(top);\n settle(() => poppedTop.remove());\n } else if (added && currentKey !== activeKey && top) {\n // Push: slide the new top in.\n rememberFocusedView(section.ownerDocument.activeElement);\n play(top, 'entering');\n crossFadeChrome();\n focusInto(top);\n }\n\n activeKey = currentKey;\n });\n observer.observe(viewport, { childList: true });\n\n return () => {\n disposeBack();\n section.removeEventListener('focusin', onFocusIn);\n observer.disconnect();\n for (const timer of timers) view.clearTimeout(timer);\n timers.clear();\n section\n .querySelectorAll<HTMLElement>('[data-nav-chrome-copy]')\n .forEach((copy) => copy.remove());\n delete section.dataset.navChromeTransition;\n for (const [element, previousTabIndex] of generatedFallbacks) {\n if (!element.isConnected || element.tabIndex !== -1) continue;\n if (previousTabIndex === null) element.removeAttribute('tabindex');\n else element.setAttribute('tabindex', previousTabIndex);\n }\n generatedFallbacks.clear();\n rememberedFocus.clear();\n if (previousDuration)\n section.style.setProperty(\n '--kui-nav-stack-transition-duration',\n previousDuration,\n );\n else section.style.removeProperty('--kui-nav-stack-transition-duration');\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { wireResizableRegions } from './chunk-YMKH5XTF.js';
1
+ export { wireResizableRegions } from './chunk-WN52USHV.js';
2
2
 
3
3
 
4
4
  //# sourceMappingURL=wire-resizable-regions.js.map
@@ -1,5 +1,6 @@
1
1
  import { TabActivation } from './tab-bar.js';
2
2
  import 'kerfjs';
3
+ import './semantic-content-BbzjvSu9.js';
3
4
 
4
5
  type TabReorderSource = 'pointer' | 'keyboard';
5
6
  type TabDropPosition = 'before' | 'after';
@@ -1,4 +1,4 @@
1
- export { wireTokenSearchFields } from './chunk-JFGXQBIP.js';
1
+ export { wireTokenSearchFields } from './chunk-VDUJZF5G.js';
2
2
 
3
3
 
4
4
 
@@ -1,9 +1,10 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
3
+ import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
4
 
4
5
  /** A collapsible Workbench panel — a side rail or the bottom drawer. */
5
6
  interface WorkbenchPanel {
6
- content: SafeHtml;
7
+ content: KerfUiContent;
7
8
  /** Whether the panel is currently collapsed (the app owns this). */
8
9
  collapsed?: boolean;
9
10
  /** Rail width, or drawer height, in px. Overrides the CSS default. */
@@ -22,7 +23,7 @@ interface WorkbenchProps {
22
23
  id: string;
23
24
  label: string;
24
25
  /** The central work area. */
25
- main: SafeHtml;
26
+ main: KerfUiContent;
26
27
  leftRail?: WorkbenchPanel;
27
28
  rightRail?: WorkbenchPanel;
28
29
  bottomDrawer?: WorkbenchPanel;
@@ -33,8 +34,10 @@ interface WorkbenchProps {
33
34
  * bottom drawer around a central work area (any absent). Collapsing snaps the
34
35
  * panel's track to zero in one reflow while its fixed-size content slides out via
35
36
  * a composited transform — the instant-width / sliding-content technique, so the
36
- * work area relayouts once, not per frame. The app owns each `collapsed` flag;
37
- * the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.
37
+ * work area relayouts once, not per frame. Bottom-drawer content stays anchored
38
+ * to the shell's stable bottom edge throughout that transition. The app owns
39
+ * each `collapsed` flag; the collapse is pure CSS (no wire). See
40
+ * `docs/23-app-layouts.md` §3.3.
38
41
  */
39
42
  declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, }: WorkbenchProps): SafeHtml;
40
43
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/workbench.tsx"],"names":[],"mappings":"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","file":"workbench.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type {\n ResizableRegionCollapseMotion,\n ResizableRegionContentOverflow,\n ResizableRegionPresentation,\n ResizableRegionRestorePosition,\n ResizableRegionSeparator,\n} from './resizable-region.js';\n\n/** A collapsible Workbench panel — a side rail or the bottom drawer. */\nexport interface WorkbenchPanel {\n content: SafeHtml;\n /** Whether the panel is currently collapsed (the app owns this). */\n collapsed?: boolean;\n /** Rail width, or drawer height, in px. Overrides the CSS default. */\n size?: number;\n /** Accessible name for the panel region. */\n label?: string;\n separator?: ResizableRegionSeparator;\n collapseMotion?: ResizableRegionCollapseMotion;\n contentOverflow?: ResizableRegionContentOverflow;\n presentation?: ResizableRegionPresentation;\n /** Control shown in a safe-area-aware viewport corner while collapsed. */\n restoreControl?: SafeHtml;\n restorePosition?: ResizableRegionRestorePosition;\n}\n\nexport interface WorkbenchProps {\n id: string;\n label: string;\n /** The central work area. */\n main: SafeHtml;\n leftRail?: WorkbenchPanel;\n rightRail?: WorkbenchPanel;\n bottomDrawer?: WorkbenchPanel;\n className?: string;\n}\n\n/**\n * The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and\n * bottom drawer around a central work area (any absent). Collapsing snaps the\n * panel's track to zero in one reflow while its fixed-size content slides out via\n * a composited transform — the instant-width / sliding-content technique, so the\n * work area relayouts once, not per frame. The app owns each `collapsed` flag;\n * the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.\n */\nexport function Workbench({\n id,\n label,\n main,\n leftRail,\n rightRail,\n bottomDrawer,\n className = '',\n}: WorkbenchProps) {\n return (\n <section\n class={`kui-workbench ${className}`.trim()}\n id={id}\n data-component=\"workbench\"\n aria-label={label}\n >\n {leftRail && (\n <aside\n class=\"kui-workbench__rail kui-workbench__rail--left\"\n data-workbench-rail=\"left\"\n data-collapsed={String(leftRail.collapsed ?? false)}\n data-separator={leftRail.separator ?? 'auto'}\n data-collapse-motion={leftRail.collapseMotion ?? 'slide'}\n data-content-overflow={leftRail.contentOverflow ?? 'clip'}\n data-presentation={leftRail.presentation ?? 'inline'}\n aria-label={leftRail.label || undefined}\n aria-hidden={leftRail.presentation === 'hidden' ? 'true' : undefined}\n style={\n leftRail.size\n ? `--kui-workbench-rail-width: ${leftRail.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">{leftRail.content}</div>\n </aside>\n )}\n {leftRail?.collapsed &&\n leftRail.restoreControl &&\n leftRail.presentation !== 'hidden' && (\n <div\n class=\"kui-workbench__restore\"\n data-panel=\"left\"\n data-position={leftRail.restorePosition ?? 'bottom-start'}\n >\n {leftRail.restoreControl}\n </div>\n )}\n <div class=\"kui-workbench__center\">\n <div class=\"kui-workbench__main\" data-workbench-main>\n {main}\n </div>\n {bottomDrawer && (\n <section\n class=\"kui-workbench__drawer\"\n data-workbench-drawer\n data-collapsed={String(bottomDrawer.collapsed ?? false)}\n data-separator={bottomDrawer.separator ?? 'auto'}\n data-collapse-motion={bottomDrawer.collapseMotion ?? 'slide'}\n data-content-overflow={bottomDrawer.contentOverflow ?? 'clip'}\n data-presentation={bottomDrawer.presentation ?? 'inline'}\n aria-label={bottomDrawer.label || undefined}\n aria-hidden={\n bottomDrawer.presentation === 'hidden' ? 'true' : undefined\n }\n style={\n bottomDrawer.size\n ? `--kui-workbench-drawer-height: ${bottomDrawer.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">\n {bottomDrawer.content}\n </div>\n </section>\n )}\n {bottomDrawer?.collapsed &&\n bottomDrawer.restoreControl &&\n bottomDrawer.presentation !== 'hidden' && (\n <div\n class=\"kui-workbench__restore\"\n data-panel=\"bottom\"\n data-position={bottomDrawer.restorePosition ?? 'bottom-end'}\n >\n {bottomDrawer.restoreControl}\n </div>\n )}\n </div>\n {rightRail && (\n <aside\n class=\"kui-workbench__rail kui-workbench__rail--right\"\n data-workbench-rail=\"right\"\n data-collapsed={String(rightRail.collapsed ?? false)}\n data-separator={rightRail.separator ?? 'auto'}\n data-collapse-motion={rightRail.collapseMotion ?? 'slide'}\n data-content-overflow={rightRail.contentOverflow ?? 'clip'}\n data-presentation={rightRail.presentation ?? 'inline'}\n aria-label={rightRail.label || undefined}\n aria-hidden={rightRail.presentation === 'hidden' ? 'true' : undefined}\n style={\n rightRail.size\n ? `--kui-workbench-rail-width: ${rightRail.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">{rightRail.content}</div>\n </aside>\n )}\n {rightRail?.collapsed &&\n rightRail.restoreControl &&\n rightRail.presentation !== 'hidden' && (\n <div\n class=\"kui-workbench__restore\"\n data-panel=\"right\"\n data-position={rightRail.restorePosition ?? 'bottom-end'}\n >\n {rightRail.restoreControl}\n </div>\n )}\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/workbench.tsx"],"names":[],"mappings":"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","file":"workbench.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\n\nimport type {\n ResizableRegionCollapseMotion,\n ResizableRegionContentOverflow,\n ResizableRegionPresentation,\n ResizableRegionRestorePosition,\n ResizableRegionSeparator,\n} from './resizable-region.js';\nimport type { KerfUiContent } from './semantic-content.js';\n\n/** A collapsible Workbench panel — a side rail or the bottom drawer. */\nexport interface WorkbenchPanel {\n content: KerfUiContent;\n /** Whether the panel is currently collapsed (the app owns this). */\n collapsed?: boolean;\n /** Rail width, or drawer height, in px. Overrides the CSS default. */\n size?: number;\n /** Accessible name for the panel region. */\n label?: string;\n separator?: ResizableRegionSeparator;\n collapseMotion?: ResizableRegionCollapseMotion;\n contentOverflow?: ResizableRegionContentOverflow;\n presentation?: ResizableRegionPresentation;\n /** Control shown in a safe-area-aware viewport corner while collapsed. */\n restoreControl?: SafeHtml;\n restorePosition?: ResizableRegionRestorePosition;\n}\n\nexport interface WorkbenchProps {\n id: string;\n label: string;\n /** The central work area. */\n main: KerfUiContent;\n leftRail?: WorkbenchPanel;\n rightRail?: WorkbenchPanel;\n bottomDrawer?: WorkbenchPanel;\n className?: string;\n}\n\n/**\n * The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and\n * bottom drawer around a central work area (any absent). Collapsing snaps the\n * panel's track to zero in one reflow while its fixed-size content slides out via\n * a composited transform — the instant-width / sliding-content technique, so the\n * work area relayouts once, not per frame. Bottom-drawer content stays anchored\n * to the shell's stable bottom edge throughout that transition. The app owns\n * each `collapsed` flag; the collapse is pure CSS (no wire). See\n * `docs/23-app-layouts.md` §3.3.\n */\nexport function Workbench({\n id,\n label,\n main,\n leftRail,\n rightRail,\n bottomDrawer,\n className = '',\n}: WorkbenchProps) {\n return (\n <section\n class={`kui-workbench ${className}`.trim()}\n id={id}\n data-component=\"workbench\"\n aria-label={label}\n >\n {leftRail && (\n <aside\n class=\"kui-workbench__rail kui-workbench__rail--left\"\n data-workbench-rail=\"left\"\n data-collapsed={String(leftRail.collapsed ?? false)}\n data-separator={leftRail.separator ?? 'auto'}\n data-collapse-motion={leftRail.collapseMotion ?? 'slide'}\n data-content-overflow={leftRail.contentOverflow ?? 'clip'}\n data-presentation={leftRail.presentation ?? 'inline'}\n aria-label={leftRail.label || undefined}\n aria-hidden={leftRail.presentation === 'hidden' ? 'true' : undefined}\n style={\n leftRail.size\n ? `--kui-workbench-rail-width: ${leftRail.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">{leftRail.content}</div>\n </aside>\n )}\n {leftRail?.collapsed &&\n leftRail.restoreControl &&\n leftRail.presentation !== 'hidden' && (\n <div\n class=\"kui-workbench__restore\"\n data-panel=\"left\"\n data-position={leftRail.restorePosition ?? 'bottom-start'}\n >\n {leftRail.restoreControl}\n </div>\n )}\n <div class=\"kui-workbench__center\">\n <div class=\"kui-workbench__main\" data-workbench-main>\n {main}\n </div>\n {bottomDrawer && (\n <section\n class=\"kui-workbench__drawer\"\n data-workbench-drawer\n data-collapsed={String(bottomDrawer.collapsed ?? false)}\n data-separator={bottomDrawer.separator ?? 'auto'}\n data-collapse-motion={bottomDrawer.collapseMotion ?? 'slide'}\n data-content-overflow={bottomDrawer.contentOverflow ?? 'clip'}\n data-presentation={bottomDrawer.presentation ?? 'inline'}\n aria-label={bottomDrawer.label || undefined}\n aria-hidden={\n bottomDrawer.presentation === 'hidden' ? 'true' : undefined\n }\n style={\n bottomDrawer.size\n ? `--kui-workbench-drawer-height: ${bottomDrawer.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">\n {bottomDrawer.content}\n </div>\n </section>\n )}\n {bottomDrawer?.collapsed &&\n bottomDrawer.restoreControl &&\n bottomDrawer.presentation !== 'hidden' && (\n <div\n class=\"kui-workbench__restore\"\n data-panel=\"bottom\"\n data-position={bottomDrawer.restorePosition ?? 'bottom-end'}\n >\n {bottomDrawer.restoreControl}\n </div>\n )}\n </div>\n {rightRail && (\n <aside\n class=\"kui-workbench__rail kui-workbench__rail--right\"\n data-workbench-rail=\"right\"\n data-collapsed={String(rightRail.collapsed ?? false)}\n data-separator={rightRail.separator ?? 'auto'}\n data-collapse-motion={rightRail.collapseMotion ?? 'slide'}\n data-content-overflow={rightRail.contentOverflow ?? 'clip'}\n data-presentation={rightRail.presentation ?? 'inline'}\n aria-label={rightRail.label || undefined}\n aria-hidden={rightRail.presentation === 'hidden' ? 'true' : undefined}\n style={\n rightRail.size\n ? `--kui-workbench-rail-width: ${rightRail.size}px`\n : undefined\n }\n >\n <div class=\"kui-workbench__panel-content\">{rightRail.content}</div>\n </aside>\n )}\n {rightRail?.collapsed &&\n rightRail.restoreControl &&\n rightRail.presentation !== 'hidden' && (\n <div\n class=\"kui-workbench__restore\"\n data-panel=\"right\"\n data-position={rightRail.restorePosition ?? 'bottom-end'}\n >\n {rightRail.restoreControl}\n </div>\n )}\n </section>\n );\n}\n"]}
@@ -9,6 +9,7 @@ These are normative contracts for package components and consuming applications.
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
  - `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.
11
11
  - Dispose `wireTabBars` and `wireTokenSearchFields` with their owning view. Disposal cancels queued controlled-render focus restoration, so a torn-down view cannot reclaim focus from its successor.
12
+ - `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.
12
13
  - 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`; do not reduce the target to the visible icon.
13
14
  - `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
14
15
  - `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.
@@ -188,7 +189,10 @@ transient state is false. Width animation is disabled under reduced motion.
188
189
  When the field is composed with a caller-owned surface rendered outside it — a
189
190
  suggestions listbox, date picker, or help popover — mark that surface (or wrap
190
191
  it) with `data-token-search-keep-open`, or pass `collapsible.keepOpenOn(target)`,
191
- so focus moving into it does not collapse an empty field. `wireTokenSearchFields`
192
+ so focus or pointer activation moving into it does not collapse an empty field.
193
+ The helper preserves the pointer target when a browser omits
194
+ `focusout.relatedTarget`, preventing the field or target from disappearing
195
+ between pointerdown and click. `wireTokenSearchFields`
192
196
  can also, opt-in via `keyboard`, own atomic-chip editing keys: from a collapsed
193
197
  caret with no selection, Backspace removes the token before the caret and Delete
194
198
  the token after it (reported through `onRemoveToken` for the app to apply to its
@@ -199,8 +203,10 @@ originating `InputEvent` so a caller can gate on `inputType`/`data` — fires on
199
203
  owns query parsing and result-count/loading announcements.
200
204
 
201
205
  Managed clear captures the action before application handlers run, keeps the adopted
202
- expanded signal open during editor replacement, and focuses the current editor after
203
- rendering so typing can continue. The app still owns clearing query/tokens and emptying
206
+ expanded signal open during editor replacement, and restores focus at the actual
207
+ mutation checkpoint before the next input task. It also returns keyboard activation
208
+ from the clear button to a surviving editor immediately. No animation frame owns
209
+ clear focus, so fast typing cannot escape to page shortcuts or lose its first letter. The app still owns clearing query/tokens and emptying
204
210
  DOM-owned text. This focus step respects `manageFocus: false`, disposal, removed fields,
205
211
  and focus deliberately moved to another control; no app-level reopen callback is needed.
206
212
 
@@ -18,7 +18,10 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
18
18
  docked panel: a `'left'` / `'right'` rail or a `'bottom'` drawer. It owns only
19
19
  presentation. Collapsing snaps the panel's own size to zero in one reflow while
20
20
  the fixed-size content slides out via `transform` (composited, clipped) — never
21
- a per-frame width/height animation. The app owns the `collapsed` signal; `size`
21
+ a per-frame width/height animation. A bottom drawer anchors that fixed-size
22
+ content to its stable bottom edge, so opening and closing move monotonically
23
+ through the transform alone instead of transitioning from a changing layout
24
+ origin and snapping at the end. The app owns the `collapsed` signal; `size`
22
25
  overrides the CSS default width/height.
23
26
  Reusable shell policies are typed props: `separator`, `collapseMotion`,
24
27
  `contentOverflow`, and `presentation`. A collapsed panel may also receive a