@kerfjs/ui 5.0.0-beta.53 → 5.0.0-beta.55

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 (152) hide show
  1. package/README.md +4 -2
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/component-catalog-v2.json +138 -5
  4. package/ai/component-catalog-v2.schema.json +2 -1
  5. package/ai/component-catalog.json +208 -16
  6. package/ai/public-api-signatures-v1.md +462 -39
  7. package/ai/skill.md +28 -20
  8. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  9. package/analyzer/index.mjs +48 -0
  10. package/analyzer/loud-pairs.d.mts +49 -0
  11. package/analyzer/loud-pairs.mjs +357 -0
  12. package/dist/app-tab.d.ts +7 -5
  13. package/dist/app-tab.js +1 -1
  14. package/dist/browser/popup-menu.js +3 -0
  15. package/dist/catalog-resources.d.ts +1 -1
  16. package/dist/catalog.d.ts +10 -4
  17. package/dist/catalog.js +243 -300
  18. package/dist/catalog.js.map +1 -1
  19. package/dist/{chunk-7IUSV4XG.js → chunk-24PHKUR3.js} +2 -2
  20. package/dist/chunk-24PHKUR3.js.map +1 -0
  21. package/dist/{chunk-EKYZCRCX.js → chunk-3CGQ7ZNO.js} +54 -22
  22. package/dist/chunk-3CGQ7ZNO.js.map +1 -0
  23. package/dist/{chunk-UKKSMZYT.js → chunk-46263QLK.js} +51 -1
  24. package/dist/chunk-46263QLK.js.map +1 -0
  25. package/dist/chunk-4BIMLITI.js +92 -0
  26. package/dist/chunk-4BIMLITI.js.map +1 -0
  27. package/dist/{chunk-NDUGJ4TU.js → chunk-5NDQABWK.js} +1 -1
  28. package/dist/{chunk-NDUGJ4TU.js.map → chunk-5NDQABWK.js.map} +1 -1
  29. package/dist/{chunk-ZK5EGVFP.js → chunk-5OLVUEND.js} +12 -2
  30. package/dist/chunk-5OLVUEND.js.map +1 -0
  31. package/dist/{chunk-CMUIXJZM.js → chunk-BSKEH3UC.js} +2 -2
  32. package/dist/{chunk-CMUIXJZM.js.map → chunk-BSKEH3UC.js.map} +1 -1
  33. package/dist/chunk-EAX2GSS4.js +384 -0
  34. package/dist/chunk-EAX2GSS4.js.map +1 -0
  35. package/dist/chunk-KH6VUGHU.js +399 -0
  36. package/dist/chunk-KH6VUGHU.js.map +1 -0
  37. package/dist/{chunk-BJJKFVWW.js → chunk-LZXQVUGN.js} +2 -2
  38. package/dist/chunk-LZXQVUGN.js.map +1 -0
  39. package/dist/chunk-N7FHFNT6.js +55 -0
  40. package/dist/chunk-N7FHFNT6.js.map +1 -0
  41. package/dist/{chunk-CCMKJWLX.js → chunk-NE5JAOQS.js} +7 -7
  42. package/dist/chunk-NE5JAOQS.js.map +1 -0
  43. package/dist/chunk-PSHUSWQH.js +18 -0
  44. package/dist/chunk-PSHUSWQH.js.map +1 -0
  45. package/dist/{chunk-KPNTVB7A.js → chunk-R754MHXX.js} +3 -3
  46. package/dist/{chunk-KPNTVB7A.js.map → chunk-R754MHXX.js.map} +1 -1
  47. package/dist/{chunk-HJKRCKDR.js → chunk-RNRUVPJ3.js} +4 -2
  48. package/dist/chunk-RNRUVPJ3.js.map +1 -0
  49. package/dist/{chunk-VOCPXGQN.js → chunk-VHADGP3B.js} +9 -8
  50. package/dist/chunk-VHADGP3B.js.map +1 -0
  51. package/dist/chunk-XQV2B4IL.js +14 -0
  52. package/dist/chunk-XQV2B4IL.js.map +1 -0
  53. package/dist/collapsible-panel.d.ts +46 -15
  54. package/dist/collapsible-panel.js +42 -10
  55. package/dist/collapsible-panel.js.map +1 -1
  56. package/dist/floating-toolbar.d.ts +5 -2
  57. package/dist/floating-toolbar.js +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.js +20 -18
  60. package/dist/list-header.js +1 -1
  61. package/dist/list-inset-text.d.ts +7 -1
  62. package/dist/list-inset-text.js +2 -1
  63. package/dist/panel-toolbar-BFQxFITu.d.ts +52 -0
  64. package/dist/popup-menu-register.d.ts +9 -0
  65. package/dist/popup-menu-register.js +30 -0
  66. package/dist/popup-menu-register.js.map +1 -0
  67. package/dist/popup-menu.d.ts +71 -0
  68. package/dist/popup-menu.js +3 -0
  69. package/dist/popup-menu.js.map +1 -0
  70. package/dist/resizable-region.d.ts +4 -1
  71. package/dist/resizable-region.js +1 -1
  72. package/dist/split-view.js +1 -1
  73. package/dist/state-banner.js +1 -1
  74. package/dist/styles/catalog/components/catalog-example.css +0 -16
  75. package/dist/styles/catalog/components/catalog-stage.css +8 -13
  76. package/dist/styles/catalog/components/catalog.css +8 -18
  77. package/dist/styles/catalog.css +0 -5
  78. package/dist/styles/collapsible-panel.css +88 -10
  79. package/dist/styles/floating-toolbar.css +11 -6
  80. package/dist/styles/popup-menu.css +12 -0
  81. package/dist/styles/resizable-region.css +81 -13
  82. package/dist/styles/state-banner.css +13 -4
  83. package/dist/styles/styles.css +1 -0
  84. package/dist/styles/tab-bar.css +31 -3
  85. package/dist/styles/toolbar-control-group.css +13 -0
  86. package/dist/styles/toolbar.css +43 -2
  87. package/dist/styles/workbench.css +503 -10
  88. package/dist/text.js +1 -1
  89. package/dist/token-search-field.d.ts +10 -1
  90. package/dist/token-search-field.js +1 -1
  91. package/dist/{types-CEsieBZi.d.ts → types-D6vzj-VT.d.ts} +15 -0
  92. package/dist/wire-catalog.d.ts +13 -1
  93. package/dist/wire-catalog.js +18 -3
  94. package/dist/wire-catalog.js.map +1 -1
  95. package/dist/wire-resizable-regions.d.ts +2 -1
  96. package/dist/wire-resizable-regions.js +2 -1
  97. package/dist/wire-sidebar.js +6 -3
  98. package/dist/wire-sidebar.js.map +1 -1
  99. package/dist/wire-tab-bars.js +1 -1
  100. package/dist/wire-token-search-fields.js +1 -1
  101. package/dist/wire-workbench.d.ts +109 -0
  102. package/dist/wire-workbench.js +6 -0
  103. package/dist/wire-workbench.js.map +1 -0
  104. package/dist/workbench.d.ts +166 -6
  105. package/dist/workbench.js +11 -104
  106. package/dist/workbench.js.map +1 -1
  107. package/docs/accessibility.md +66 -10
  108. package/docs/app-layouts.md +12 -1
  109. package/docs/catalog.md +27 -9
  110. package/docs/collapsible-panel.md +114 -1
  111. package/docs/component-contract.md +2 -2
  112. package/docs/component-selection.md +41 -23
  113. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  114. package/docs/design/templates/state-banner/danger.svg +1 -1
  115. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  116. package/docs/design/templates/state-banner/info.svg +1 -1
  117. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  118. package/docs/design/templates/state-banner/success.svg +1 -1
  119. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  120. package/docs/design/templates/state-banner/warning.svg +1 -1
  121. package/docs/design/templates/state-banner-dark.svg +1 -1
  122. package/docs/design/templates/state-banner.svg +1 -1
  123. package/docs/document-baseline.md +4 -0
  124. package/docs/layout.md +10 -1
  125. package/docs/recipes.md +48 -39
  126. package/docs/surface-scaffold.md +1 -1
  127. package/docs/type-contracts.md +2 -1
  128. package/docs/ui-analyzer.md +94 -19
  129. package/docs/ui-doctor.md +6 -0
  130. package/docs/ux-demo.md +7 -3
  131. package/docs/webawesome-theme.md +11 -2
  132. package/docs/workbench.md +388 -8
  133. package/llms.txt +13 -8
  134. package/package.json +17 -2
  135. package/ux-demo/recipes/app-shell.tsx +141 -223
  136. package/ux-demo/recipes/collapsible-sidebar.tsx +48 -78
  137. package/ux-demo/recipes/list-detail-dialog.tsx +150 -54
  138. package/ux-demo/recipes/mount-recipe.ts +0 -5
  139. package/ux-demo/recipes/types.ts +0 -1
  140. package/dist/chunk-7IUSV4XG.js.map +0 -1
  141. package/dist/chunk-BJJKFVWW.js.map +0 -1
  142. package/dist/chunk-CCMKJWLX.js.map +0 -1
  143. package/dist/chunk-EKYZCRCX.js.map +0 -1
  144. package/dist/chunk-HJKRCKDR.js.map +0 -1
  145. package/dist/chunk-UKKSMZYT.js.map +0 -1
  146. package/dist/chunk-VOCPXGQN.js.map +0 -1
  147. package/dist/chunk-ZK5EGVFP.js.map +0 -1
  148. package/dist/styles/catalog/components/catalog-detail.css +0 -35
  149. package/dist/styles/catalog/components/catalog-resource-footer.css +0 -37
  150. package/dist/styles/catalog/components/catalog-secondary-sections.css +0 -38
  151. package/dist/styles/catalog/components/catalog-section-list.css +0 -22
  152. package/dist/styles/catalog/components/catalog-sidebar.css +0 -82
@@ -1,32 +1,189 @@
1
1
  import { SafeHtml } from 'kerfjs';
2
2
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
3
3
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
4
+ import { a as PanelToolbar, b as PanelToggle } from './panel-toolbar-BFQxFITu.js';
5
+ import { ToolbarProps } from './toolbar.js';
6
+ import './lucide-icon.js';
7
+ import 'lucide';
8
+ import './divider-sides-BzB6rphT.js';
9
+ import './pane.js';
4
10
 
11
+ /** The standard toggle a Workbench renders for a panel (see {@link PanelToggle}). */
12
+ type WorkbenchPanelToggle = PanelToggle;
13
+ /**
14
+ * A Workbench panel's top toolbar, composed by the Workbench so its groups can
15
+ * follow the panel's open state (see {@link PanelToolbar} for the roles).
16
+ * A collapsed rail's `constant` groups and toggle go to the leading edge of
17
+ * `mainToolbar` (left rail) or its trailing edge (right rail); a collapsed
18
+ * drawer's go to the trailing edge of `mainBottomToolbar`, else to a
19
+ * `FloatingToolbar` in the work area's bottom-end corner.
20
+ */
21
+ type WorkbenchPanelToolbar = PanelToolbar;
22
+ /** The work area's top toolbar; collapsed rails add their groups to it. */
23
+ interface WorkbenchMainToolbar {
24
+ label: string;
25
+ /** The work area's title, usually an extra-large `ToolbarText`. */
26
+ title?: KerfUiContent;
27
+ /** Groups after the title. */
28
+ leading?: KerfUiContent;
29
+ center?: KerfUiContent;
30
+ /** Groups at the trailing edge, before a collapsed right rail's groups. */
31
+ trailing?: KerfUiContent;
32
+ /** The toolbar's narrow-width policy (see `Toolbar.responsive`). */
33
+ responsive?: ToolbarProps['responsive'];
34
+ responsiveAt?: ToolbarProps['responsiveAt'];
35
+ }
36
+ /** The work area's bottom toolbar; a collapsed drawer adds its groups to it. */
37
+ interface WorkbenchMainBottomToolbar {
38
+ label: string;
39
+ leading?: KerfUiContent;
40
+ trailing?: KerfUiContent;
41
+ }
42
+ /**
43
+ * The work area: the app's `main` under `mainToolbar` (and above
44
+ * `mainBottomToolbar`), with each collapsed panel's groups added to the
45
+ * toolbar on its side. Without either toolbar, `main` renders as given.
46
+ */
47
+ /**
48
+ * Whether the work area's header or footer chrome stays pinned (`fixed`) or
49
+ * scrolls away with the content (`scroll`).
50
+ */
51
+ type WorkbenchChromePlacement = 'fixed' | 'scroll';
52
+
53
+ /**
54
+ * The Workbench container breakpoint below which a panel presents as an
55
+ * overlay: `narrow` (704px or less) or `compact` (448px or less) — the same
56
+ * breakpoints as `ResizableRegion`'s `responsiveFillAt` — or `never` to keep
57
+ * it inline at every width.
58
+ */
59
+ type WorkbenchResponsiveOverlayAt = 'compact' | 'narrow' | 'never';
60
+ /**
61
+ * How wide a rail's overlay is in a compact (448px or less) Workbench: `inset`
62
+ * fills the Workbench less a dismiss margin on the side away from the rail's
63
+ * edge, so a press beside it closes it; `full` fills the Workbench.
64
+ */
65
+ type WorkbenchCompactOverlay = 'inset' | 'full';
66
+ /** Drag-resize limits for a resizable Workbench panel, in px. */
67
+ interface WorkbenchPanelResizable {
68
+ /** Smallest size (default 180 for a rail, 120 for the drawer). */
69
+ min?: number;
70
+ /** Largest size (default 480). */
71
+ max?: number;
72
+ }
5
73
  /** A collapsible Workbench panel — a side rail or the bottom drawer. */
6
74
  interface WorkbenchPanel {
7
75
  content: KerfUiContent;
76
+ /**
77
+ * The panel's top toolbar, composed by the Workbench: its `constant` groups
78
+ * and standard `toggle` move to the work area's toolbar while the panel is
79
+ * collapsed. With it, `content` renders in a `Pane` below the toolbar.
80
+ */
81
+ toolbar?: WorkbenchPanelToolbar;
82
+ /** Optional bottom toolbar under a `toolbar` panel's content. */
83
+ footer?: KerfUiContent;
8
84
  /** Whether the panel is currently collapsed (the app owns this). */
9
85
  collapsed?: boolean;
10
- /** Rail width, or drawer height, in px. Overrides the CSS default. */
86
+ /**
87
+ * Rail width, or drawer height, in px. Overrides the CSS default. For a
88
+ * resizable panel it is the current size (default 280 for a rail, 220 for
89
+ * the drawer), clamped to the limits; the app owns it and `wireWorkbench`
90
+ * reports each resize.
91
+ */
11
92
  size?: number;
93
+ /**
94
+ * Opt in to drag and keyboard resizing on the panel's inner separator:
95
+ * `true` for the default limits, or `{ min, max }`. Off by default. Pair it
96
+ * with `wireWorkbench` from `@kerfjs/ui/wire-workbench`.
97
+ */
98
+ resizable?: boolean | WorkbenchPanelResizable;
12
99
  /** Accessible name for the panel region. */
13
100
  label?: string;
14
101
  separator?: ResizableRegionSeparator;
15
102
  collapseMotion?: ResizableRegionCollapseMotion;
16
103
  contentOverflow?: ResizableRegionContentOverflow;
17
104
  presentation?: ResizableRegionPresentation;
18
- /** Control shown in a safe-area-aware viewport corner while collapsed. */
105
+ /**
106
+ * Present the panel as an overlay, without a separator, below a Workbench
107
+ * container breakpoint, and inline above it — the CSS decides, so the app
108
+ * needs no device-class check. A rail overlays from its side at full
109
+ * height, over the work area and an open drawer; the bottom drawer
110
+ * overlays the bottom of the work-area column. Rails default to `narrow`
111
+ * (pass `never` to keep one inline); the drawer defaults to inline.
112
+ */
113
+ responsiveOverlayAt?: WorkbenchResponsiveOverlayAt;
114
+ /**
115
+ * A rail's overlay width in a compact Workbench (default `inset`: the
116
+ * Workbench less `--kui-workbench-overlay-dismiss-margin`, 44px). Ignored
117
+ * by the drawer.
118
+ */
119
+ compactOverlay?: WorkbenchCompactOverlay;
120
+ /**
121
+ * Control shown while collapsed, in a safe-area-aware corner of the
122
+ * Workbench (not the viewport); the bottom drawer's sits in the work-area
123
+ * column. Prefer `toolbar.toggle`, which the Workbench relocates into the
124
+ * work area's toolbar (or this corner when that toolbar is absent).
125
+ */
19
126
  restoreControl?: SafeHtml;
20
127
  restorePosition?: ResizableRegionRestorePosition;
21
128
  }
22
129
  interface WorkbenchProps {
130
+ /**
131
+ * The Workbench's `id`. Each panel's `id` derives from it —
132
+ * `<id>-left-rail`, `<id>-right-rail`, `<id>-bottom-drawer` — so a toggle
133
+ * can name the panel it shows and hides with `aria-controls`.
134
+ */
23
135
  id: string;
24
136
  label: string;
25
137
  /** The central work area. */
26
138
  main: KerfUiContent;
139
+ /**
140
+ * The work area's top toolbar. A collapsed left rail's `constant` groups
141
+ * and toggle lead it; a collapsed right rail's trail it. With it, `main`
142
+ * renders in a `Pane` below the toolbar.
143
+ */
144
+ mainToolbar?: WorkbenchMainToolbar;
145
+ /**
146
+ * Fixed content under `mainToolbar` — supporting copy such as a
147
+ * description — divided from the scrolling `main` below it.
148
+ */
149
+ mainHeader?: KerfUiContent;
150
+ /**
151
+ * Fixed content over `mainBottomToolbar` — a status line or a resource
152
+ * toolbar — divided from the scrolling `main` above it.
153
+ */
154
+ mainFooter?: KerfUiContent;
155
+ /**
156
+ * The work area's bottom toolbar. A collapsed drawer's `constant` groups and
157
+ * toggle trail it; without it they float in the work area's corner.
158
+ */
159
+ mainBottomToolbar?: WorkbenchMainBottomToolbar;
160
+ /**
161
+ * Whether `mainToolbar` and `mainHeader` stay pinned (`fixed`, default) or
162
+ * scroll away with `main` (`scroll`) — useful where large text would leave
163
+ * pinned chrome little room.
164
+ */
165
+ mainHeaderPlacement?: WorkbenchChromePlacement;
166
+ /** The same for `mainFooter` and `mainBottomToolbar` (default `fixed`). */
167
+ mainFooterPlacement?: WorkbenchChromePlacement;
27
168
  leftRail?: WorkbenchPanel;
28
169
  rightRail?: WorkbenchPanel;
29
170
  bottomDrawer?: WorkbenchPanel;
171
+ /**
172
+ * Minimum width, in px, the work area keeps beside the inline rails, fixed
173
+ * or `resizable` (default 320; `0` turns it off). Resizing a rail stops
174
+ * where the work area would drop below it, and inline rails shrink
175
+ * proportionally when the Workbench gets narrower. Workbenches without a
176
+ * rail ignore it.
177
+ */
178
+ mainMinSize?: number;
179
+ /**
180
+ * Minimum height, in px, the work area keeps above an inline bottom drawer,
181
+ * fixed or `resizable` (default 120; `0` turns it off) — the drawer's
182
+ * counterpart of `mainMinSize`. Resizing the drawer stops where the work
183
+ * area would drop below it, and the drawer shrinks when the Workbench gets
184
+ * shorter. Workbenches without a drawer ignore it.
185
+ */
186
+ mainMinHeight?: number;
30
187
  className?: string;
31
188
  /** Native named-slot assignment when composed inside a web component. */
32
189
  slot?: string;
@@ -38,9 +195,12 @@ interface WorkbenchProps {
38
195
  * a composited transform — the instant-width / sliding-content technique, so the
39
196
  * work area relayouts once, not per frame. Bottom-drawer content stays anchored
40
197
  * to the shell's stable bottom edge throughout that transition. The app owns
41
- * each `collapsed` flag; the collapse is pure CSS (no wire). See
42
- * `docs/23-app-layouts.md` §3.3.
198
+ * each `collapsed` flag; the collapse is pure CSS. A collapsed panel renders
199
+ * `inert` and `aria-hidden`, so neither Tab nor assistive technology reaches
200
+ * controls that have slid out of view or an empty landmark (its restore control
201
+ * lives outside it and stays reachable). A panel may opt in to drag and keyboard resizing with
202
+ * `resizable`, which `wireWorkbench` drives. See `docs/23-app-layouts.md` §3.3.
43
203
  */
44
- declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, slot, }: WorkbenchProps): SafeHtml;
204
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainMinSize, mainMinHeight, className, slot, }: WorkbenchProps): SafeHtml;
45
205
 
46
- export { Workbench, type WorkbenchPanel, type WorkbenchProps };
206
+ export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchPanel, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt };
package/dist/workbench.js CHANGED
@@ -1,108 +1,15 @@
1
- import { jsxs, jsx } from 'kerfjs/jsx-runtime';
1
+ export { Workbench } from './chunk-EAX2GSS4.js';
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
2
12
 
3
- // src/workbench.tsx
4
- function Workbench({
5
- id,
6
- label,
7
- main,
8
- leftRail,
9
- rightRail,
10
- bottomDrawer,
11
- className = "",
12
- slot
13
- }) {
14
- return /* @__PURE__ */ jsxs(
15
- "section",
16
- {
17
- class: `kui-workbench ${className}`.trim(),
18
- id,
19
- "data-component": "workbench",
20
- "aria-label": label,
21
- slot,
22
- children: [
23
- leftRail && /* @__PURE__ */ jsx(
24
- "aside",
25
- {
26
- class: "kui-workbench__rail kui-workbench__rail--left",
27
- "data-workbench-rail": "left",
28
- "data-collapsed": String(leftRail.collapsed ?? false),
29
- "data-separator": leftRail.separator ?? "auto",
30
- "data-collapse-motion": leftRail.collapseMotion ?? "slide",
31
- "data-content-overflow": leftRail.contentOverflow ?? "clip",
32
- "data-presentation": leftRail.presentation ?? "inline",
33
- "aria-label": leftRail.label || void 0,
34
- "aria-hidden": leftRail.presentation === "hidden" ? "true" : void 0,
35
- style: leftRail.size ? `--kui-workbench-rail-width: ${leftRail.size}px` : void 0,
36
- children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: leftRail.content })
37
- }
38
- ),
39
- leftRail?.collapsed && leftRail.restoreControl && leftRail.presentation !== "hidden" && /* @__PURE__ */ jsx(
40
- "div",
41
- {
42
- class: "kui-workbench__restore",
43
- "data-panel": "left",
44
- "data-position": leftRail.restorePosition ?? "bottom-start",
45
- children: leftRail.restoreControl
46
- }
47
- ),
48
- /* @__PURE__ */ jsxs("div", { class: "kui-workbench__center", children: [
49
- /* @__PURE__ */ jsx("div", { class: "kui-workbench__main", "data-workbench-main": true, children: main }),
50
- bottomDrawer && /* @__PURE__ */ jsx(
51
- "section",
52
- {
53
- class: "kui-workbench__drawer",
54
- "data-workbench-drawer": true,
55
- "data-collapsed": String(bottomDrawer.collapsed ?? false),
56
- "data-separator": bottomDrawer.separator ?? "auto",
57
- "data-collapse-motion": bottomDrawer.collapseMotion ?? "slide",
58
- "data-content-overflow": bottomDrawer.contentOverflow ?? "clip",
59
- "data-presentation": bottomDrawer.presentation ?? "inline",
60
- "aria-label": bottomDrawer.label || void 0,
61
- "aria-hidden": bottomDrawer.presentation === "hidden" ? "true" : void 0,
62
- style: bottomDrawer.size ? `--kui-workbench-drawer-height: ${bottomDrawer.size}px` : void 0,
63
- children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: bottomDrawer.content })
64
- }
65
- ),
66
- bottomDrawer?.collapsed && bottomDrawer.restoreControl && bottomDrawer.presentation !== "hidden" && /* @__PURE__ */ jsx(
67
- "div",
68
- {
69
- class: "kui-workbench__restore",
70
- "data-panel": "bottom",
71
- "data-position": bottomDrawer.restorePosition ?? "bottom-end",
72
- children: bottomDrawer.restoreControl
73
- }
74
- )
75
- ] }),
76
- rightRail && /* @__PURE__ */ jsx(
77
- "aside",
78
- {
79
- class: "kui-workbench__rail kui-workbench__rail--right",
80
- "data-workbench-rail": "right",
81
- "data-collapsed": String(rightRail.collapsed ?? false),
82
- "data-separator": rightRail.separator ?? "auto",
83
- "data-collapse-motion": rightRail.collapseMotion ?? "slide",
84
- "data-content-overflow": rightRail.contentOverflow ?? "clip",
85
- "data-presentation": rightRail.presentation ?? "inline",
86
- "aria-label": rightRail.label || void 0,
87
- "aria-hidden": rightRail.presentation === "hidden" ? "true" : void 0,
88
- style: rightRail.size ? `--kui-workbench-rail-width: ${rightRail.size}px` : void 0,
89
- children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: rightRail.content })
90
- }
91
- ),
92
- rightRail?.collapsed && rightRail.restoreControl && rightRail.presentation !== "hidden" && /* @__PURE__ */ jsx(
93
- "div",
94
- {
95
- class: "kui-workbench__restore",
96
- "data-panel": "right",
97
- "data-position": rightRail.restorePosition ?? "bottom-end",
98
- children: rightRail.restoreControl
99
- }
100
- )
101
- ]
102
- }
103
- );
104
- }
105
13
 
106
- export { Workbench };
107
14
 
108
15
  //# sourceMappingURL=workbench.js.map
@@ -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';\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 /** Native named-slot assignment when composed inside a web component. */\n slot?: 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 slot,\n}: WorkbenchProps) {\n return (\n <section\n class={`kui-workbench ${className}`.trim()}\n id={id}\n data-component=\"workbench\"\n aria-label={label}\n slot={slot}\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":[],"names":[],"mappings":"","file":"workbench.js"}
@@ -12,6 +12,7 @@ These are normative contracts for package components and consuming applications.
12
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.
13
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` 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.
14
14
  - `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
15
+ - 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.
15
16
  - `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.
16
17
  - `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
17
18
  - `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.
@@ -34,7 +35,7 @@ Use `presentation="toolbar-borderless"` inside a `ToolbarControlGroup`, with
34
35
  parent paints the composed focus ring. `selectedPresentation="icon-only"` hides
35
36
  only the visible selected label—the required `label` or `ariaLabel` still names
36
37
  the combobox. An icon-only trigger keeps its disclosure caret: the selected icon
37
- and caret form one pill with the same geometry as a popup-menu `wa-dropdown`
38
+ and caret form one pill with the same geometry as a `PopupMenu`
38
39
  trigger (8px inline padding on both sides, 12px from the icon to the caret, a
39
40
  40px default or 32px compact control height), so the caret still signals a
40
41
  choice list. Inside a group the trigger is inset evenly on every side (2px in a
@@ -80,6 +81,17 @@ repeated and out-of-order completions, cancellation, instance isolation, and
80
81
  removal. `tests/browser/select-lifecycle.spec.ts` repeats animation/resize/reopen
81
82
  sequences at wide and narrow widths in Chromium, Firefox, and WebKit.
82
83
 
84
+ ## PopupMenu
85
+
86
+ `PopupMenu` renders a Web Awesome dropdown, so the menu roles, arrow-key and
87
+ typeahead navigation, Escape, and focus return to the trigger come from
88
+ `wa-dropdown`. Kerf's part is the trigger's name. A Web Awesome button takes its
89
+ name from its content and ignores an `aria-label` on the host, so an icon-only
90
+ trigger requires `label`, which renders as visually hidden text inside the
91
+ button; a trigger with visible `text` is named by that text. Group headings render as the dropdown's native menu labels, and
92
+ dividers are presentational. A disabled item stays in the menu but cannot be
93
+ chosen; disable the whole trigger only when every command is unavailable.
94
+
83
95
  ## ListActionRow
84
96
 
85
97
  `ListActionRow` renders a noninteractive visual root containing primary and
@@ -120,6 +132,15 @@ The handle exposes separator role, orientation, name, minimum, maximum, and curr
120
132
  them after a re-render writes the rendered props back, so assistive
121
133
  technology never reads an unclamped size between interactions (the
122
134
  disposer disconnects both observers);
135
+ - a truthful value below the minimum: when the container shows the track at
136
+ less than `min` (a narrowing `Workbench` squeezes its resizable rails), the
137
+ handle reports that shown size in `aria-valuenow` and pins `aria-valuemin`
138
+ and `aria-valuemax` to it — WAI-ARIA requires the value to lie inside the
139
+ range, and the separator cannot move a track its container holds. A pinned
140
+ separator — squeezed, or with a reachable range collapsed to one size —
141
+ commits nothing: arrow keys, Home/End, and drags leave the app's size (and
142
+ its storage) as it was, so the track returns to it, with the rendered range,
143
+ once it has room;
123
144
  - a whole hit target and focus ring at a clamped edge: when the separator has
124
145
  less room past it than the handle's 10px overhang (a parent clamps the track
125
146
  to its own edge), the region carries `data-handle-inset` and the handle sits
@@ -162,7 +183,10 @@ forced to a dark color scheme, but it is **not** in the top layer, so it never
162
183
  covers dialogs, popovers, or other overlays. Its children are the app's controls
163
184
  (normally `ToolbarControlGroup`s), which keep their own names, focus, and
164
185
  keyboard behavior; the application owns their actions, the toolbar's visibility,
165
- and — via `position` and `--kui-floating-toolbar-inset` — where it sits.
186
+ and — via `position` and `--kui-floating-toolbar-inset`, set on the toolbar
187
+ or an ancestor — where it sits. Inside a `Workbench`, `CollapsiblePanel`, or
188
+ `ResizableRegion` `restoreControl` the restore corner owns the inset, so the
189
+ toolbar floats from that corner.
166
190
 
167
191
  ## Tabs
168
192
 
@@ -180,8 +204,11 @@ that geometry; applications must not override its anatomy to create the split.
180
204
  presentations. Icon-only tabs keep the required `name` as the tab button's
181
205
  accessible name while visually hiding the duplicate label. A `pending` tab (known,
182
206
  still opening) keeps its visible name as the tab button's accessible name, so a
183
- trailing spinner's own label is not folded into it; like `placeholder`, it is
184
- `aria-busy`, disabled, out of the tab order, and not draggable. These props own
207
+ trailing spinner's own label is not folded into it. It is `aria-busy` and stays
208
+ selectable (in the roving tab order like a live tab), so the application can
209
+ show placeholder content in its panel until loading completes; its close
210
+ button is disabled and hidden and it is not draggable. A `placeholder` tab
211
+ (unknown) is instead disabled, out of the tab order, and not draggable. These props own
185
212
  component appearance only; the application still owns the bar's outer placement.
186
213
  `AppTab.rootAttributes` accepts application `data-*` metadata only; runtime
187
214
  filtering rejects roles plus case variants of component- or wiring-owned
@@ -192,8 +219,11 @@ and must not contain interactive descendants.
192
219
  activation, same-bar pointer reordering, `Alt+Shift+ArrowLeft/ArrowRight`
193
220
  reordering, focus restoration, scroll-into-view, and pointer-proximity
194
221
  autoscroll at either horizontal edge. Edge autoscroll is direct manipulation,
195
- stops on drop/drag end/disposal, and does not change keyboard behavior. It
196
- returns a disposer.
222
+ stops on drop/drag end/disposal, and does not change keyboard behavior. When
223
+ the application changes a bar's selected tab without focus (adding and
224
+ selecting a tab), the wiring scrolls that tab's strip — never the page — so
225
+ the new selection is fully visible; a re-render that keeps the same selection
226
+ leaves a strip the user scrolled where it is. It returns a disposer.
197
227
 
198
228
  Arrow / Home / End follow the ARIA Tabs **automatic-activation** pattern by
199
229
  default: they move roving focus and select the focused tab. If selection synchronously
@@ -276,15 +306,26 @@ and focus deliberately moved to another control; no app-level reopen callback is
276
306
 
277
307
  ## CollapsiblePanel / sidebar
278
308
 
279
- `CollapsiblePanel` is a labeled `aside` region that is `aria-hidden` while
280
- collapsed; its content slides via transform (disabled under reduced motion).
309
+ `CollapsiblePanel` is a labeled `aside` region that renders `inert` and
310
+ `aria-hidden` while collapsed, straight from the app's `collapsed` flag, so its
311
+ controls leave the Tab order and the accessibility tree even while the content
312
+ is still sliding out, and focusing one can never scroll clipped content back into
313
+ view (`aria-hidden` alone left them focusable inside a hidden subtree). Its
314
+ content slides via transform (disabled under reduced motion); `inert` does not
315
+ affect rendering, so the slide still plays. A `restoreControl` renders outside
316
+ the panel and stays reachable. A collapsed `ResizableRegion` renders `inert` and
317
+ `aria-hidden` on the region itself the same way (its content wrapper is `inert`
318
+ too, and its separator is already hidden and not displayed), so no empty
319
+ labeled region landmark stays behind; its `restoreControl` stays outside the
320
+ region.
281
321
  `CollapsiblePanelToggle` is a named button carrying `aria-expanded` and the
282
322
  standard per-side collapse/expand glyph (`PanelLeft*` / `PanelRight*` /
283
323
  `PanelBottom*`); place a collapse toggle inside the panel and an expand toggle in
284
324
  an always-visible location so it is reachable while collapsed. `wireSidebar()`
285
325
  moves focus into the panel when it opens and restores it to the trigger when it
286
- closes; a trigger inside the now-hidden panel hands focus to the panel's toggle
287
- outside it instead, so an app may render the expand toggle only while the panel
326
+ closes; a trigger inside the now-inert panel hands focus to the panel's toggle
327
+ outside it instead (and focus stranded there by a wide → compact crossing is
328
+ rescued the same way), so an app may render the expand toggle only while the panel
288
329
  is collapsed. When a `deviceClass()` reports `compact`, an open panel becomes an overlay
289
330
  with a dismissable backdrop, Escape and backdrop-click collapse it, and Tab is
290
331
  trapped within it (the ARIA dialog pattern). That modal state only ever begins
@@ -293,6 +334,21 @@ crossing, every panel starts collapsed, so no page load or resize traps focus. T
293
334
  signal, the panels, their sizes, and content; the wire may persist the collapsed
294
335
  state per panel.
295
336
 
337
+ ## Workbench
338
+
339
+ Each `Workbench` rail and drawer is a labeled region (`aside` for a rail, `section`
340
+ for the drawer). While a panel is collapsed the region itself renders `inert` and
341
+ `aria-hidden="true"`, and so does not linger as an empty landmark, and its content
342
+ wrapper is `inert`, all rendered from the app's `collapsed` flag, so its controls
343
+ leave the Tab order and the accessibility tree even while the content is still
344
+ sliding out, and focusing one can never scroll clipped content back into view. The panel's `restoreControl` renders
345
+ outside that content and stays reachable; a resizable panel's separator also leaves
346
+ the Tab order while collapsed or not inline. `wireWorkbench`, given a panel's
347
+ `collapsed` signal, returns focus stranded in a closing panel to the control that
348
+ opened it, else the restore control, else a control whose `aria-controls` names the
349
+ panel; an open overlay panel takes focus and keeps Tab inside it (the ARIA dialog
350
+ pattern). See [`workbench.md`](workbench.md).
351
+
296
352
  ## Verification matrix
297
353
 
298
354
  For each changed component, inspect default, hover, focus, disabled, selected/pressed, busy/error, long-content, wide, narrow, light, dark, increased-contrast, reduced-motion, keyboard-only, and 200%-zoom states where applicable. DOM order must match reading and focus order, with no clipping or unreachable action.
@@ -113,7 +113,18 @@ device in tests.
113
113
  selection).
114
114
  - **IDE (complex tool):** `Workbench` with a left navigator rail, a right
115
115
  inspector rail, and a bottom console drawer, each `collapsed` bound to a
116
- signal. Only offer this `atLeast('desktop')`.
116
+ signal. Only offer this `atLeast('desktop')`. To let people size the
117
+ navigator or console, opt those panels in with `resizable` and call
118
+ `wireWorkbench` with their size signals and `deviceClass`; resizing is
119
+ suspended on `compact`, where the rails become overlay drawers — or give each
120
+ rail `responsiveOverlayAt: "narrow"` and let the Workbench's own width decide
121
+ when it overlays, with no device check; pass each rail's `collapsed` signal
122
+ to `wireWorkbench` so the overlay starts collapsed, takes focus and keeps Tab
123
+ inside it while open, and closes on Escape or an outside press. Inline
124
+ rails, fixed or resizable, leave the editor its `mainMinSize` (320px by
125
+ default) and shrink in proportion when the window narrows; the bottom drawer
126
+ leaves it `mainMinHeight` (120px by default) and shrinks when the window gets
127
+ shorter.
117
128
  - **Social app (tabbed):** `TabScaffold` with Home / Search / Profile tabs, each
118
129
  `content` a `NavStack`. On a tablet/desktop, render the same sections as a
119
130
  `Workbench` left rail instead of a bottom bar.
package/docs/catalog.md CHANGED
@@ -6,6 +6,15 @@ footer that the kerf UI catalog itself uses. Point it at your own components and
6
6
  you get the same shell without rebuilding it. Like the app layouts, it is a
7
7
  subpath-only, tree-shakeable module that adds nothing to the main barrel.
8
8
 
9
+ The shell is a [`Workbench`](workbench.md) built entirely from `@kerfjs/ui`
10
+ components: the sidebar is its left rail and the active entry its work area. So
11
+ the sidebar behaves like any Workbench rail — its standard toggle moves into the
12
+ entry toolbar while it is collapsed, and on a small screen (a Workbench narrower
13
+ than 704px) it overlays the stage, one tap from the entry toolbar, filling the
14
+ width less a 44px strip that closes it, instead of squeezing the stage. Pass the
15
+ app's `collapsed` signal to `wireCatalog` so that overlay closes on Escape, on a
16
+ press outside, and when an entry is chosen from it.
17
+
9
18
  ```bash
10
19
  npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
11
20
  ```
@@ -36,9 +45,9 @@ expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
36
45
  sidebar content), and `status` (a footer status line).
37
46
 
38
47
  Per-entry `resources` render as "open in new tab" links in the footer, and
39
- `related` renders a "Related entries" popup menu — a `single` `ToolbarControlGroup`
40
- around a `wa-dropdown` (grouped by each entry's `group`), so register its elements
41
- with `@kerfjs/ui/select/register` when you use it.
48
+ `related` renders a "Related entries" `PopupMenu` in a `single`
49
+ `ToolbarControlGroup` (grouped by each entry's `group`), so register its elements
50
+ with `@kerfjs/ui/popup-menu/register` when you use it.
42
51
 
43
52
  ## Catalog demo authoring contract
44
53
 
@@ -249,8 +258,8 @@ Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
249
258
  `onSelect` updates controlled state, the helper waits one animation frame, finds
250
259
  the exact matching `data-item-id`, and scrolls it into view without changing
251
260
  focus. A newer selection or disposal cancels the pending reveal. The default
252
- media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
253
- layouts keep their existing scroll position.
261
+ media guard follows the Workbench's inline sidebar (`min-width: 44.01rem`), so a
262
+ closed phone overlay is never scrolled behind the user's back.
254
263
 
255
264
  Pass an options object instead of `true` to customize `block`, `inline`,
256
265
  `behavior`, or `media`; `media: false` deliberately enables the behavior at all
@@ -384,6 +393,12 @@ revealCatalogEntry(app, initial, { block: "center" });
384
393
  - **`active`** is domain state (which entry is shown) — the app's signal, updated in
385
394
  `onSelect`, read to compute `content`.
386
395
  - **`collapsed`** is transient UI — the app's signal, flipped in `onToggleSidebar`.
396
+ Pass the same signal as `wireCatalog`'s `collapsed` option so the sidebar is
397
+ wired as a Workbench rail (transient on small screens, focus handed to the
398
+ relocated toggle).
399
+ - **`headerPlacement` / `footerPlacement`** choose whether the entry toolbar and the
400
+ status + resource footer stay pinned (`fixed`, default) or scroll with the preview
401
+ (`scroll`) — useful where large text would leave pinned chrome little room.
387
402
  - **`theme`** is a global preference — the app's signal; `wireCatalog` only reports
388
403
  the toggle, the app applies the theme (the shell reads `theme` to show the toggle's
389
404
  opposite-state label). Omit `theme` to hide the toggle entirely.
@@ -397,7 +412,10 @@ matching options on `wireCatalog`) if they collide with your own action table.
397
412
 
398
413
  ## CSS
399
414
 
400
- `Catalog` composes public primitives (`Toolbar`, `ListHeader`, `ListItem`, `Select`,
401
- …). Import `@kerfjs/ui/styles.css` for the whole layer, or `@kerfjs/ui/catalog.css`
402
- plus each composed primitive's CSS. The shell is theme-aware and responsive: it
403
- stacks the sidebar above the detail below ~832px and hides it when collapsed.
415
+ `Catalog` composes public components (`Workbench`, `Pane`, `Toolbar`, `List`,
416
+ `ListHeader`, `ListItem`, `ListInsetText`, `Text`, …) and owns only the preview stage
417
+ (the checkerboard and centered canvas), the geometry overlay, the example viewport
418
+ options, and the brand mark's size. Import `@kerfjs/ui/styles.css` for the whole
419
+ layer, or `@kerfjs/ui/catalog.css` plus `@kerfjs/ui/workbench.css` and each composed
420
+ primitive's CSS. Like any Workbench it needs a definite containing height — the
421
+ `@kerfjs/ui/document.css` baseline's `.kui-app-root`.