@kerfjs/ui 5.0.0-beta.17 → 5.0.0-beta.18

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 (213) hide show
  1. package/README.md +108 -79
  2. package/ai/component-catalog-extension.schema.json +79 -0
  3. package/ai/component-catalog.json +499 -0
  4. package/ai/component-catalog.schema.json +15 -0
  5. package/ai/public-api-signatures-v1.md +67 -31
  6. package/ai/skill.md +58 -49
  7. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  8. package/dist/app-tab.d.ts +1 -1
  9. package/dist/app-tab.js +1 -1
  10. package/dist/catalog.d.ts +10 -3
  11. package/dist/catalog.js +279 -90
  12. package/dist/catalog.js.map +1 -1
  13. package/dist/chunk-43FUQH35.js +83 -0
  14. package/dist/chunk-43FUQH35.js.map +1 -0
  15. package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
  16. package/dist/chunk-4TRDV6RL.js.map +1 -0
  17. package/dist/chunk-6ETZKKAE.js +38 -0
  18. package/dist/chunk-6ETZKKAE.js.map +1 -0
  19. package/dist/chunk-6WTETUZ6.js +21 -0
  20. package/dist/chunk-6WTETUZ6.js.map +1 -0
  21. package/dist/chunk-7L6EHUMV.js +31 -0
  22. package/dist/chunk-7L6EHUMV.js.map +1 -0
  23. package/dist/chunk-BBRXX6PF.js +108 -0
  24. package/dist/chunk-BBRXX6PF.js.map +1 -0
  25. package/dist/chunk-DPDBVKNA.js +113 -0
  26. package/dist/chunk-DPDBVKNA.js.map +1 -0
  27. package/dist/chunk-ELSJX4W7.js +68 -0
  28. package/dist/chunk-ELSJX4W7.js.map +1 -0
  29. package/dist/{chunk-6NLRBSVX.js → chunk-ENVTNLYC.js} +125 -68
  30. package/dist/chunk-ENVTNLYC.js.map +1 -0
  31. package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
  32. package/dist/chunk-FEY65TBV.js.map +1 -0
  33. package/dist/chunk-JTHUXDN2.js +58 -0
  34. package/dist/chunk-JTHUXDN2.js.map +1 -0
  35. package/dist/chunk-LPOMRC4C.js +25 -0
  36. package/dist/chunk-LPOMRC4C.js.map +1 -0
  37. package/dist/chunk-NENBUYX5.js +79 -0
  38. package/dist/chunk-NENBUYX5.js.map +1 -0
  39. package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
  40. package/dist/chunk-NVWXKU37.js.map +1 -0
  41. package/dist/chunk-PIDTEQ3U.js +105 -0
  42. package/dist/chunk-PIDTEQ3U.js.map +1 -0
  43. package/dist/chunk-PX3YSS5C.js +32 -0
  44. package/dist/chunk-PX3YSS5C.js.map +1 -0
  45. package/dist/chunk-PZQVSDLH.js +61 -0
  46. package/dist/chunk-PZQVSDLH.js.map +1 -0
  47. package/dist/chunk-QIRXYKCE.js +113 -0
  48. package/dist/chunk-QIRXYKCE.js.map +1 -0
  49. package/dist/chunk-QO55FN2Y.js +20 -0
  50. package/dist/chunk-QO55FN2Y.js.map +1 -0
  51. package/dist/chunk-QQFJMRIB.js +45 -0
  52. package/dist/chunk-QQFJMRIB.js.map +1 -0
  53. package/dist/chunk-S3HKPI7I.js +218 -0
  54. package/dist/chunk-S3HKPI7I.js.map +1 -0
  55. package/dist/{chunk-WXMGNX2Y.js → chunk-S3PCZ7NV.js} +2 -2
  56. package/dist/chunk-S3PCZ7NV.js.map +1 -0
  57. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  58. package/dist/chunk-SRSJO5QE.js.map +1 -0
  59. package/dist/chunk-U5IFGQCD.js +117 -0
  60. package/dist/chunk-U5IFGQCD.js.map +1 -0
  61. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  62. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  63. package/dist/chunk-ZK2G3VR5.js +35 -0
  64. package/dist/chunk-ZK2G3VR5.js.map +1 -0
  65. package/dist/chunk-ZVI5YST2.js +41 -0
  66. package/dist/chunk-ZVI5YST2.js.map +1 -0
  67. package/dist/collapsible-panel.d.ts +2 -2
  68. package/dist/collapsible-panel.js +47 -6
  69. package/dist/collapsible-panel.js.map +1 -1
  70. package/dist/device-class.js +33 -5
  71. package/dist/device-class.js.map +1 -1
  72. package/dist/disclosure-arrow.js +1 -1
  73. package/dist/empty-state.d.ts +1 -1
  74. package/dist/empty-state.js +1 -1
  75. package/dist/floating-toolbar.d.ts +1 -1
  76. package/dist/floating-toolbar.js +1 -1
  77. package/dist/index.js +25 -25
  78. package/dist/list-action-row.d.ts +1 -1
  79. package/dist/list-action-row.js +1 -1
  80. package/dist/list-header.d.ts +1 -1
  81. package/dist/list-header.js +1 -1
  82. package/dist/list-inset-control.d.ts +1 -1
  83. package/dist/list-inset-control.js +1 -1
  84. package/dist/list-inset-text.d.ts +1 -1
  85. package/dist/list-inset-text.js +1 -1
  86. package/dist/list-item.d.ts +1 -1
  87. package/dist/list-item.js +1 -1
  88. package/dist/loading-spinner.js +1 -1
  89. package/dist/nav-stack.d.ts +1 -1
  90. package/dist/nav-stack.js +1 -1
  91. package/dist/panel-header.d.ts +1 -1
  92. package/dist/panel-header.js +1 -1
  93. package/dist/resizable-region.d.ts +1 -1
  94. package/dist/resizable-region.js +1 -1
  95. package/dist/segmented-control.js +1 -1
  96. package/dist/select.d.ts +1 -1
  97. package/dist/select.js +1 -1
  98. package/dist/skeleton.d.ts +1 -1
  99. package/dist/skeleton.js +1 -1
  100. package/dist/split-view.d.ts +1 -1
  101. package/dist/split-view.js +70 -10
  102. package/dist/split-view.js.map +1 -1
  103. package/dist/state-banner.d.ts +1 -1
  104. package/dist/state-banner.js +1 -1
  105. package/dist/styles/catalog.css +26 -0
  106. package/dist/tab-bar.d.ts +1 -1
  107. package/dist/tab-bar.js +1 -1
  108. package/dist/tab-scaffold.d.ts +1 -1
  109. package/dist/tab-scaffold.js +43 -8
  110. package/dist/tab-scaffold.js.map +1 -1
  111. package/dist/token-search-field.js +1 -1
  112. package/dist/toolbar-control-group.d.ts +1 -1
  113. package/dist/toolbar-control-group.js +1 -1
  114. package/dist/toolbar-text.js +1 -1
  115. package/dist/toolbar.d.ts +1 -1
  116. package/dist/toolbar.js +1 -1
  117. package/dist/value-table.d.ts +2 -2
  118. package/dist/value-table.js +1 -1
  119. package/dist/wire-catalog.d.ts +31 -2
  120. package/dist/wire-catalog.js +203 -8
  121. package/dist/wire-catalog.js.map +1 -1
  122. package/dist/wire-nav-stack.js +22 -7
  123. package/dist/wire-nav-stack.js.map +1 -1
  124. package/dist/wire-resizable-regions.d.ts +1 -1
  125. package/dist/wire-resizable-regions.js +1 -1
  126. package/dist/wire-sidebar.js +52 -41
  127. package/dist/wire-sidebar.js.map +1 -1
  128. package/dist/wire-tab-bars.js +1 -1
  129. package/dist/wire-tab-scaffold.js +9 -4
  130. package/dist/wire-tab-scaffold.js.map +1 -1
  131. package/dist/wire-token-search-fields.d.ts +1 -1
  132. package/dist/wire-token-search-fields.js +1 -1
  133. package/dist/workbench.d.ts +1 -1
  134. package/dist/workbench.js +56 -9
  135. package/dist/workbench.js.map +1 -1
  136. package/docs/app-layouts.md +7 -7
  137. package/docs/catalog.md +108 -24
  138. package/docs/collapsible-panel.md +38 -11
  139. package/docs/component-contract.md +30 -2
  140. package/docs/component-selection.md +71 -63
  141. package/docs/design/templates.md +1 -1
  142. package/docs/design-philosophy.md +5 -5
  143. package/docs/device-class.md +8 -8
  144. package/docs/examples/command-palette-adapter.tsx +67 -17
  145. package/docs/examples/component-catalog-extension.json +45 -0
  146. package/docs/layout.md +19 -19
  147. package/docs/nav-stack.md +15 -6
  148. package/docs/recipes.md +4 -4
  149. package/docs/split-view.md +3 -3
  150. package/docs/tab-scaffold.md +16 -7
  151. package/docs/webawesome-theme.md +8 -8
  152. package/docs/workbench.md +18 -5
  153. package/llms.txt +20 -14
  154. package/package.json +6 -4
  155. package/ux-demo/recipes/app-shell.tsx +206 -10
  156. package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
  157. package/ux-demo/recipes/compact-toolbar.tsx +114 -12
  158. package/ux-demo/recipes/composer-form.tsx +106 -18
  159. package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
  160. package/ux-demo/recipes/list-workspace-states.tsx +165 -8
  161. package/ux-demo/recipes/loaders.ts +5 -2
  162. package/ux-demo/recipes/loading-inspector.tsx +124 -41
  163. package/ux-demo/recipes/mount-recipe.ts +27 -6
  164. package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
  165. package/ux-demo/recipes/navigation-stack.tsx +61 -19
  166. package/ux-demo/recipes/types.ts +3 -1
  167. package/ux-demo/recipes/workspace-header.tsx +57 -2
  168. package/dist/chunk-2Z5TWHKQ.js +0 -34
  169. package/dist/chunk-2Z5TWHKQ.js.map +0 -1
  170. package/dist/chunk-37T3VNIZ.js +0 -19
  171. package/dist/chunk-37T3VNIZ.js.map +0 -1
  172. package/dist/chunk-55DATV3A.js +0 -10
  173. package/dist/chunk-55DATV3A.js.map +0 -1
  174. package/dist/chunk-5ATCDSQS.js +0 -42
  175. package/dist/chunk-5ATCDSQS.js.map +0 -1
  176. package/dist/chunk-5E2HVJ33.js +0 -10
  177. package/dist/chunk-5E2HVJ33.js.map +0 -1
  178. package/dist/chunk-6NLRBSVX.js.map +0 -1
  179. package/dist/chunk-7BR3DO2A.js.map +0 -1
  180. package/dist/chunk-7JB7VPRI.js +0 -34
  181. package/dist/chunk-7JB7VPRI.js.map +0 -1
  182. package/dist/chunk-7LDCTU7D.js +0 -134
  183. package/dist/chunk-7LDCTU7D.js.map +0 -1
  184. package/dist/chunk-AHQI52QO.js.map +0 -1
  185. package/dist/chunk-AQNKJRFH.js +0 -79
  186. package/dist/chunk-AQNKJRFH.js.map +0 -1
  187. package/dist/chunk-AVZHJN6C.js +0 -14
  188. package/dist/chunk-AVZHJN6C.js.map +0 -1
  189. package/dist/chunk-B24C4A6T.js +0 -22
  190. package/dist/chunk-B24C4A6T.js.map +0 -1
  191. package/dist/chunk-CH4SQ6H5.js +0 -39
  192. package/dist/chunk-CH4SQ6H5.js.map +0 -1
  193. package/dist/chunk-DAFFDO33.js +0 -14
  194. package/dist/chunk-DAFFDO33.js.map +0 -1
  195. package/dist/chunk-I7INRJO4.js +0 -17
  196. package/dist/chunk-I7INRJO4.js.map +0 -1
  197. package/dist/chunk-L4OKFDJY.js +0 -24
  198. package/dist/chunk-L4OKFDJY.js.map +0 -1
  199. package/dist/chunk-M5JRI6MN.js +0 -27
  200. package/dist/chunk-M5JRI6MN.js.map +0 -1
  201. package/dist/chunk-RNPGFQ33.js +0 -10
  202. package/dist/chunk-RNPGFQ33.js.map +0 -1
  203. package/dist/chunk-VPCTCUED.js +0 -10
  204. package/dist/chunk-VPCTCUED.js.map +0 -1
  205. package/dist/chunk-WRTAQ7BZ.js +0 -23
  206. package/dist/chunk-WRTAQ7BZ.js.map +0 -1
  207. package/dist/chunk-WVPYVDXY.js +0 -15
  208. package/dist/chunk-WVPYVDXY.js.map +0 -1
  209. package/dist/chunk-WXMGNX2Y.js.map +0 -1
  210. package/dist/chunk-XXMOARTH.js.map +0 -1
  211. package/dist/chunk-YGN7OMLT.js.map +0 -1
  212. package/dist/chunk-ZQQ6P4SV.js +0 -20
  213. package/dist/chunk-ZQQ6P4SV.js.map +0 -1
package/docs/nav-stack.md CHANGED
@@ -8,9 +8,9 @@ a `NavStack` with one entry**. It is one of the opt-in app layouts (see
8
8
  Import the component and its companion stylesheet:
9
9
 
10
10
  ```ts
11
- import { NavStack, type NavStackView } from '@kerfjs/ui/nav-stack';
12
- import { wireNavStack } from '@kerfjs/ui/wire-nav-stack';
13
- import '@kerfjs/ui/nav-stack.css';
11
+ import { NavStack, type NavStackView } from "@kerfjs/ui/nav-stack";
12
+ import { wireNavStack } from "@kerfjs/ui/wire-nav-stack";
13
+ import "@kerfjs/ui/nav-stack.css";
14
14
  ```
15
15
 
16
16
  ## State lives in the app
@@ -20,16 +20,25 @@ stack as a `signal<NavStackView[]>`, `NavStack({ views })` renders it, and
20
20
  `wireNavStack` animates the transitions.
21
21
 
22
22
  ```tsx
23
- const views = signal<NavStackView[]>([{ key: 'inbox', title: 'Inbox', content: <InboxView /> }]);
23
+ const views = signal<NavStackView[]>([
24
+ { key: "inbox", title: "Inbox", content: <InboxView /> },
25
+ ]);
24
26
 
25
27
  // render inside mount():
26
28
  <NavStack id="mail" label="Mail" views={views.value} />;
27
29
 
28
30
  // once, after first render:
29
- const dispose = wireNavStack(root, { onBack: () => { views.value = views.value.slice(0, -1); } });
31
+ const dispose = wireNavStack(root, {
32
+ onBack: () => {
33
+ views.value = views.value.slice(0, -1);
34
+ },
35
+ });
30
36
 
31
37
  // push / pop by editing the signal:
32
- views.value = [...views.value, { key: id, title: 'Message', content: <MessageView id={id} /> }];
38
+ views.value = [
39
+ ...views.value,
40
+ { key: id, title: "Message", content: <MessageView id={id} /> },
41
+ ];
33
42
  ```
34
43
 
35
44
  `NavStack` renders every entry stacked, the last one active and the rest kept
package/docs/recipes.md CHANGED
@@ -14,12 +14,12 @@ the public `onCommit` callback, retains every disposer, and returns one
14
14
  idempotent disposer:
15
15
 
16
16
  ```ts
17
- import { createRecipe } from './navigation-sidebar.js';
18
- import { mountRecipe } from './mount-recipe.js';
17
+ import { createRecipe } from "./navigation-sidebar.js";
18
+ import { mountRecipe } from "./mount-recipe.js";
19
19
 
20
- const root = document.querySelector<HTMLElement>('#navigation')!;
20
+ const root = document.querySelector<HTMLElement>("#navigation")!;
21
21
  const stopRecipe = mountRecipe(root, createRecipe(announce));
22
- window.addEventListener('pagehide', stopRecipe, { once: true });
22
+ window.addEventListener("pagehide", stopRecipe, { once: true });
23
23
  ```
24
24
 
25
25
  The adapter is delivered as reference source, not a new package runtime export.
@@ -6,9 +6,9 @@ on compact ones. One of the opt-in app layouts (see
6
6
  [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
7
 
8
8
  ```ts
9
- import { SplitView } from '@kerfjs/ui/split-view';
10
- import { deviceClass } from '@kerfjs/ui/device-class';
11
- import '@kerfjs/ui/split-view.css';
9
+ import { SplitView } from "@kerfjs/ui/split-view";
10
+ import { deviceClass } from "@kerfjs/ui/device-class";
11
+ import "@kerfjs/ui/split-view.css";
12
12
  // plus nav-stack.css when the compact path is reachable, and
13
13
  // wireResizableRegions / wireNavStack for the interactive behavior.
14
14
  ```
@@ -7,9 +7,9 @@ opt-in app layouts (see [`../../docs/23-app-layouts.md`](../../docs/23-app-layou
7
7
  It is distinct from `TabBar` (document-oriented, reorderable strips).
8
8
 
9
9
  ```ts
10
- import { TabScaffold } from '@kerfjs/ui/tab-scaffold';
11
- import { wireTabScaffold } from '@kerfjs/ui/wire-tab-scaffold';
12
- import '@kerfjs/ui/tab-scaffold.css';
10
+ import { TabScaffold } from "@kerfjs/ui/tab-scaffold";
11
+ import { wireTabScaffold } from "@kerfjs/ui/wire-tab-scaffold";
12
+ import "@kerfjs/ui/tab-scaffold.css";
13
13
  ```
14
14
 
15
15
  ## Controlled selection
@@ -19,20 +19,29 @@ The app owns the active tab (a signal); `TabScaffold` renders every tab's scene
19
19
  clicks.
20
20
 
21
21
  ```tsx
22
- const active = signal('home');
22
+ const active = signal("home");
23
23
 
24
24
  <TabScaffold
25
25
  id="app"
26
26
  label="Sections"
27
27
  active={active.value}
28
28
  tabs={[
29
- { id: 'home', label: 'Home', icon: <HomeIcon />, content: <HomeStack /> },
30
- { id: 'search', label: 'Search', icon: <SearchIcon />, content: <SearchStack /> },
29
+ { id: "home", label: "Home", icon: <HomeIcon />, content: <HomeStack /> },
30
+ {
31
+ id: "search",
32
+ label: "Search",
33
+ icon: <SearchIcon />,
34
+ content: <SearchStack />,
35
+ },
31
36
  ]}
32
37
  />;
33
38
 
34
39
  // once, after first render:
35
- const dispose = wireTabScaffold(root, { onSelect: (id) => { active.value = id; } });
40
+ const dispose = wireTabScaffold(root, {
41
+ onSelect: (id) => {
42
+ active.value = id;
43
+ },
44
+ });
36
45
  ```
37
46
 
38
47
  Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
@@ -4,10 +4,10 @@
4
4
  free components and the Kerf/Hot Sheet 2 design system.
5
5
 
6
6
  ```ts
7
- import type {} from '@kerfjs/ui/webawesome';
8
- import '@kerfjs/ui/webawesome.css';
9
- import '@awesome.me/webawesome/dist/components/button/button.js';
10
- import '@awesome.me/webawesome/dist/components/input/input.js';
7
+ import type {} from "@kerfjs/ui/webawesome";
8
+ import "@kerfjs/ui/webawesome.css";
9
+ import "@awesome.me/webawesome/dist/components/button/button.js";
10
+ import "@awesome.me/webawesome/dist/components/input/input.js";
11
11
  ```
12
12
 
13
13
  The type-only package import augments `kerfjs/jsx-runtime` with the 70
@@ -138,12 +138,12 @@ subtree, or for one instance after the theme import:
138
138
 
139
139
  ```css
140
140
  :root {
141
- --wa-tooltip-arrow-size: .375rem;
142
- --kui-wa-popover-arrow-size: .375rem;
141
+ --wa-tooltip-arrow-size: 0.375rem;
142
+ --kui-wa-popover-arrow-size: 0.375rem;
143
143
  }
144
144
 
145
145
  .pointed-popover {
146
- --arrow-size: .5rem;
146
+ --arrow-size: 0.5rem;
147
147
  }
148
148
  ```
149
149
 
@@ -244,7 +244,7 @@ smallest subtree that needs a different identity:
244
244
  :root {
245
245
  --wa-color-brand-fill-loud: #7540a8;
246
246
  --wa-color-focus: #7540a8;
247
- --wa-form-control-border-radius: .5rem;
247
+ --wa-form-control-border-radius: 0.5rem;
248
248
  }
249
249
 
250
250
  .billing-workspace {
package/docs/workbench.md CHANGED
@@ -8,8 +8,8 @@ sidebar. One of the opt-in app layouts (see
8
8
  desktop-class devices.
9
9
 
10
10
  ```ts
11
- import { Workbench } from '@kerfjs/ui/workbench';
12
- import '@kerfjs/ui/workbench.css';
11
+ import { Workbench } from "@kerfjs/ui/workbench";
12
+ import "@kerfjs/ui/workbench.css";
13
13
  ```
14
14
 
15
15
  ## State lives in the app
@@ -25,9 +25,22 @@ const navCollapsed = signal(false);
25
25
  id="studio"
26
26
  label="Studio"
27
27
  main={<Editor />}
28
- leftRail={{ content: <Navigator />, label: 'Navigator', collapsed: navCollapsed.value, size: 280 }}
29
- rightRail={{ content: <Inspector />, label: 'Inspector', collapsed: inspectorCollapsed.value }}
30
- bottomDrawer={{ content: <Console />, label: 'Console', collapsed: consoleCollapsed.value }}
28
+ leftRail={{
29
+ content: <Navigator />,
30
+ label: "Navigator",
31
+ collapsed: navCollapsed.value,
32
+ size: 280,
33
+ }}
34
+ rightRail={{
35
+ content: <Inspector />,
36
+ label: "Inspector",
37
+ collapsed: inspectorCollapsed.value,
38
+ }}
39
+ bottomDrawer={{
40
+ content: <Console />,
41
+ label: "Console",
42
+ collapsed: consoleCollapsed.value,
43
+ }}
31
44
  />;
32
45
  ```
33
46
 
package/llms.txt CHANGED
@@ -6,7 +6,7 @@
6
6
  > generate correct, good-looking kerf UI — it is not a catalog to browse.
7
7
 
8
8
  ```bash
9
- npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional peer
9
+ npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional peer
10
10
  ```
11
11
 
12
12
  Import each component from its own subpath (`@kerfjs/ui/list-item`, `@kerfjs/ui/toolbar`,
@@ -22,15 +22,18 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
22
22
  [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) (real prop names,
23
23
  callback shapes, disposer return types).
24
24
  3. **For exhaustive, structured facts** (ids, exports, variants, `publicClasses`,
25
- relationships, side effects) load the
25
+ relationships, side effects, and geometry ownership) load the
26
26
  [Machine-readable component catalog](./ai/component-catalog.json) — the
27
- canonical checked-in source; its schema is alongside it.
27
+ canonical checked-in source; its schema is alongside it. Also load the
28
+ consuming application's catalog extension when present; generate one from the
29
+ [Consumer catalog extension schema](./ai/component-catalog-extension.schema.json)
30
+ when app-owned components must participate in selection or alignment.
28
31
  4. **Apply the rules below and in** [skill.md](./ai/skill.md).
29
32
 
30
33
  ## Core rules (do the right thing)
31
34
 
32
35
  - **Reach for the primitive, not for CSS.** The package is built to look right
33
- *unstyled*. Before adding `padding`, `margin`, `width`, `height`, `border`,
36
+ _unstyled_. Before adding `padding`, `margin`, `width`, `height`, `border`,
34
37
  `background`, a wrapper card, or a decoration, check whether the component, the
35
38
  pane, or the content-item already owns it — it almost always does. Prefer a
36
39
  component prop or a `--kui-*` token; write CSS only for genuinely new structure,
@@ -69,7 +72,7 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
69
72
  popup menu. A toolbar popup menu is a `single` `ToolbarControlGroup` around a
70
73
  `wa-dropdown` (its `slot="trigger"` `wa-button` is the toolbar button, kept under
71
74
  `data-morph-skip-children`).
72
- - **`PanelHeader` is *the* panel/dialog/page heading.** A plain toolbar with an
75
+ - **`PanelHeader` is _the_ panel/dialog/page heading.** A plain toolbar with an
73
76
  optional bordered icon group, an extra-large `ToolbarText` title, the trailing
74
77
  controls you pass, and an optional subtitle below — do not hand-build a heading
75
78
  row or restyle a toolbar into one.
@@ -90,7 +93,7 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
90
93
  - **App state is domain/persisted; helpers own only ephemeral mechanics.** Keep
91
94
  navigation stacks, tab selection and order, pane sizes, and `collapsed`/`expanded`
92
95
  disclosure flags in the app — they are data or persisted layout, and the app reads
93
- them to render. The `wire…` helpers own the *ephemeral* parts: `wireNavStack` the
96
+ them to render. The `wire…` helpers own the _ephemeral_ parts: `wireNavStack` the
94
97
  push/pop animation, `wireTabBars` the overflow autoscroll and drag preview,
95
98
  `wireResizableRegions` the live drag preview; they report committed changes via
96
99
  callbacks. The one richer case is `wireTokenSearchFields`, which manages the
@@ -128,13 +131,16 @@ tree-shakeable, declarative with `wire…` helpers, each with a companion CSS im
128
131
  `NavStack`, `SplitView`, `Workbench`, `TabScaffold`, driven by `deviceClass()` from
129
132
  `@kerfjs/ui/device-class`. For a standalone collapsible rail/drawer outside a full
130
133
  shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelToggle`)
131
- + `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence).
132
- `@kerfjs/ui/catalog` is another opt-in shell a
133
- `Catalog` component + `wireCatalog` helper for building a component-gallery catalog
134
- (collapsible category sidebar + preview stage + resources footer) from your own
135
- entries; controlled/stateless like the layouts. `@kerfjs/ui/webawesome` is a
136
- type-only boundary for `wa-*` JSX; `@kerfjs/ui/select/register` and the CSS are the
137
- declared side effects.
134
+
135
+ - `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence).
136
+ `@kerfjs/ui/catalog` is another opt-in shell a
137
+ `Catalog` component + `wireCatalog` helper for building a component-gallery catalog
138
+ (collapsible category sidebar + preview stage + resources footer, with an opt-in
139
+ transparent-bound and margin geometry overlay plus focus-preserving active-row
140
+ reveal for long desktop sidebars) from your own
141
+ entries; controlled/stateless like the layouts. `@kerfjs/ui/webawesome` is a
142
+ type-only boundary for `wa-*` JSX; `@kerfjs/ui/select/register` and the CSS are the
143
+ declared side effects.
138
144
 
139
145
  ## Reference
140
146
 
@@ -146,5 +152,5 @@ declared side effects.
146
152
  - [recipes.md](./docs/recipes.md): production compositions (app shell, list-detail dialog, composer form, …) as copyable references.
147
153
  - [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation.
148
154
  - [webawesome-theme.md](./docs/webawesome-theme.md): the optional Web Awesome theme; catalog coverage means themed-and-supported, not preferred (prefer the Kerf primitive when one exists). The UX sidebar visibly tags superseded and exceptional Web Awesome choices as `Discouraged`; Popup remains an available conditional positioning primitive.
149
- - [component-catalog.json](./ai/component-catalog.json) + [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) + [webawesome-jsx-signatures-v1.md](./ai/webawesome-jsx-signatures-v1.md): machine-readable facts and exact signatures.
155
+ - [component-catalog.json](./ai/component-catalog.json) + [component-catalog-extension.schema.json](./ai/component-catalog-extension.schema.json) + [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) + [webawesome-jsx-signatures-v1.md](./ai/webawesome-jsx-signatures-v1.md): machine-readable facts, a reusable app-component extension contract, and exact signatures.
150
156
  - [README.md](./README.md): package overview and the component subpath table.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.17",
3
+ "version": "5.0.0-beta.18",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -272,8 +272,9 @@
272
272
  "dev": "npm run build && vite --config ux-demo/vite.config.ts",
273
273
  "demo:build": "npm run build && vite build --config ux-demo/vite.config.ts && node scripts/check-demo-bundle.mjs",
274
274
  "demo:serve": "vite preview --config ux-demo/vite.config.ts",
275
+ "format": "prettier --write . --ignore-unknown",
275
276
  "format:css": "prettier --write \"src/**/*.css\" \"ux-demo/**/*.css\"",
276
- "check:format": "prettier --check \"src/**/*.css\" \"ux-demo/**/*.css\"",
277
+ "check:format": "prettier --check . --ignore-unknown",
277
278
  "catalog:sync": "node scripts/sync-component-catalog.mjs",
278
279
  "check:catalog": "node scripts/sync-component-catalog.mjs --check && node scripts/check-component-catalog.mjs",
279
280
  "check:recipes": "node scripts/check-recipes.mjs",
@@ -285,14 +286,15 @@
285
286
  "ai:regressions:record": "node scripts/record-ai-regression-run.mjs",
286
287
  "ai:regressions:score": "node scripts/score-ai-regression.mjs",
287
288
  "check:guidance": "node scripts/check-decision-guidance.mjs",
288
- "lint": "eslint src tests ux-demo docs/examples --max-warnings 0",
289
+ "lint:eslint": "eslint src tests ux-demo docs/examples --max-warnings 0",
290
+ "lint": "npm run lint:eslint && npm run check:format",
289
291
  "typecheck": "node node_modules/typescript7/bin/tsc --noEmit",
290
292
  "test": "npm run test:unit && npm run test:bundle",
291
293
  "test:unit": "vitest run tests/unit --coverage",
292
294
  "test:bundle": "npm run build && vitest run tests/bundle",
293
295
  "test:consumer-types": "npm run build && node node_modules/typescript7/bin/tsc -p tests/consumer-types/webawesome/tsconfig.json",
294
296
  "test:e2e": "npm run demo:build && playwright test",
295
- "check": "npm run check:format && npm run check:catalog && npm run check:recipes && npm run check:guidance && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm run check:design-templates && npm pack --dry-run",
297
+ "check": "npm run check:catalog && npm run check:recipes && npm run check:guidance && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm run check:design-templates && npm pack --dry-run",
296
298
  "prepublishOnly": "npm run check",
297
299
  "check:design-templates": "node scripts/check-design-templates.mjs",
298
300
  "check:design-templates:drift": "node scripts/check-design-templates-drift.mjs",
@@ -19,14 +19,210 @@ export const createRecipe: RecipeFactory = (announce) => {
19
19
  const selected = signal('inbox');
20
20
  const navigationSize = signal(224);
21
21
  const inspectorSize = signal(240);
22
- const responsivePane = signal<'content' | 'navigation' | 'inspector'>('content');
23
- const render = () => <section class="kui-recipe recipe-shell kui-recipe__surface kui-layout" data-recipe="recipe-app-shell" data-responsive-pane={responsivePane.value}>
24
- <Toolbar label="Atlas workspace" leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Atlas" /></ToolbarControlGroup>} trailing={<div class="recipe-shell__toolbar-actions"><ToolbarControlGroup className="recipe-shell__responsive-controls" appearance="borderless" label="Workspace panes"><button type="button" aria-label="Show navigation" aria-controls="recipe-shell-navigation" aria-pressed={String(responsivePane.value === 'navigation')} data-action="recipe-action" data-recipe-command="show-navigation"><LucideIcon icon={Folder} name="folder" /></button><button type="button" aria-label="Show content" aria-controls="recipe-shell-content" aria-pressed={String(responsivePane.value === 'content')} data-action="recipe-action" data-recipe-command="show-content"><LucideIcon icon={Inbox} name="inbox" /></button><button type="button" aria-label="Show inspector" aria-controls="recipe-shell-inspector" aria-pressed={String(responsivePane.value === 'inspector')} data-action="recipe-action" data-recipe-command="show-inspector"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup><ToolbarControlGroup appearance="borderless" label="Workspace controls"><button type="button" aria-label="Notifications" data-action="recipe-action" data-recipe-command="notify"><LucideIcon icon={Bell} name="bell" /></button><button type="button" aria-label="Settings" data-action="recipe-action" data-recipe-command="settings"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup></div>} />
25
- <div class="recipe-shell__body">
26
- <div id="recipe-shell-navigation" class="recipe-shell__nav-region"><ResizableRegion id="recipe-navigation" label="Navigation" size={navigationSize.value} min={180} max={320}><aside class="recipe-shell__nav kui-pane"><ListHeader label="Workspace" /><nav class="recipe-shell__nav-list kui-pane__content kui-content" aria-label="Workspace"><section><ListItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} selected={selected.value === 'inbox'} /><ListItem action="recipe-action" itemId="projects" label="Projects with a deliberately wrapping title" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} multiline /></section></nav></aside></ResizableRegion></div>
27
- <main id="recipe-shell-content" class="recipe-shell__main kui-pane"><PanelHeader title={selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'} titleId="recipe-shell-main-title" actions={<button class="kui-recipe__button" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="new">New task</button>} /><div class="recipe-shell__main-body kui-pane__content kui-content"><p class="kui-recipe__ownership kui-content-item">Recipe owns pane geometry and one scroll owner per pane. The app owns routing, data, pane visibility, sizes, and persistence.</p><div class="recipe-shell__cards">{['Release accessibility audit', 'Prepare tablet navigation', 'Review stale-data states', 'Confirm package boundaries'].map((title) => <article class="recipe-shell__card kui-content-item"><strong>{title}</strong><p class="kui-recipe__muted">Assigned to the interface systems team · due this week</p></article>)}</div></div></main>
28
- <div id="recipe-shell-inspector" class="recipe-shell__inspector-region"><ResizableRegion id="recipe-inspector" label="Inspector" size={inspectorSize.value} min={200} max={360} edge="start"><aside class="recipe-shell__inspector kui-pane"><PanelHeader title="Inspector" titleId="recipe-shell-inspector-title" /><div class="recipe-shell__inspector-body kui-pane__content kui-content"><ValueTable label="Selected task"><ValueTableRow label="Status" value="In review" /><ValueTableRow label="Owner" value="Mara Chen" /><ValueTableRow label="Priority" value="High" /></ValueTable></div></aside></ResizableRegion></div>
29
- </div>
30
- </section>;
31
- return { render, action(command, element) { const pane = command.match(/^show-(navigation|content|inspector)$/)?.[1] as typeof responsivePane.value | undefined; if (pane) responsivePane.value = pane; const id = element.dataset.itemId; if (id) selected.value = id; announce(id ? `Opened ${id}` : pane ? `Showing ${pane}` : command === 'new' ? 'New task requested' : `${command} requested`); }, resize(id, size) { if (id === 'recipe-navigation') navigationSize.value = size; if (id === 'recipe-inspector') inspectorSize.value = size; announce(`${id} resized to ${size}px`); } };
22
+ const responsivePane = signal<'content' | 'navigation' | 'inspector'>(
23
+ 'content',
24
+ );
25
+ const render = () => (
26
+ <section
27
+ class="kui-recipe recipe-shell kui-recipe__surface kui-layout"
28
+ data-recipe="recipe-app-shell"
29
+ data-responsive-pane={responsivePane.value}
30
+ >
31
+ <Toolbar
32
+ label="Atlas workspace"
33
+ leading={
34
+ <ToolbarControlGroup appearance="borderless" single>
35
+ <ToolbarText text="Atlas" />
36
+ </ToolbarControlGroup>
37
+ }
38
+ trailing={
39
+ <div class="recipe-shell__toolbar-actions">
40
+ <ToolbarControlGroup
41
+ className="recipe-shell__responsive-controls"
42
+ appearance="borderless"
43
+ label="Workspace panes"
44
+ >
45
+ <button
46
+ type="button"
47
+ aria-label="Show navigation"
48
+ aria-controls="recipe-shell-navigation"
49
+ aria-pressed={String(responsivePane.value === 'navigation')}
50
+ data-action="recipe-action"
51
+ data-recipe-command="show-navigation"
52
+ >
53
+ <LucideIcon icon={Folder} name="folder" />
54
+ </button>
55
+ <button
56
+ type="button"
57
+ aria-label="Show content"
58
+ aria-controls="recipe-shell-content"
59
+ aria-pressed={String(responsivePane.value === 'content')}
60
+ data-action="recipe-action"
61
+ data-recipe-command="show-content"
62
+ >
63
+ <LucideIcon icon={Inbox} name="inbox" />
64
+ </button>
65
+ <button
66
+ type="button"
67
+ aria-label="Show inspector"
68
+ aria-controls="recipe-shell-inspector"
69
+ aria-pressed={String(responsivePane.value === 'inspector')}
70
+ data-action="recipe-action"
71
+ data-recipe-command="show-inspector"
72
+ >
73
+ <LucideIcon icon={Settings} name="settings" />
74
+ </button>
75
+ </ToolbarControlGroup>
76
+ <ToolbarControlGroup
77
+ appearance="borderless"
78
+ label="Workspace controls"
79
+ >
80
+ <button
81
+ type="button"
82
+ aria-label="Notifications"
83
+ data-action="recipe-action"
84
+ data-recipe-command="notify"
85
+ >
86
+ <LucideIcon icon={Bell} name="bell" />
87
+ </button>
88
+ <button
89
+ type="button"
90
+ aria-label="Settings"
91
+ data-action="recipe-action"
92
+ data-recipe-command="settings"
93
+ >
94
+ <LucideIcon icon={Settings} name="settings" />
95
+ </button>
96
+ </ToolbarControlGroup>
97
+ </div>
98
+ }
99
+ />
100
+ <div class="recipe-shell__body">
101
+ <div id="recipe-shell-navigation" class="recipe-shell__nav-region">
102
+ <ResizableRegion
103
+ id="recipe-navigation"
104
+ label="Navigation"
105
+ size={navigationSize.value}
106
+ min={180}
107
+ max={320}
108
+ >
109
+ <aside class="recipe-shell__nav kui-pane">
110
+ <ListHeader label="Workspace" />
111
+ <nav
112
+ class="recipe-shell__nav-list kui-pane__content kui-content"
113
+ aria-label="Workspace"
114
+ >
115
+ <section>
116
+ <ListItem
117
+ action="recipe-action"
118
+ itemId="inbox"
119
+ label="Inbox"
120
+ icon={<LucideIcon icon={Inbox} name="inbox" />}
121
+ selected={selected.value === 'inbox'}
122
+ />
123
+ <ListItem
124
+ action="recipe-action"
125
+ itemId="projects"
126
+ label="Projects with a deliberately wrapping title"
127
+ icon={<LucideIcon icon={Folder} name="folder" />}
128
+ selected={selected.value === 'projects'}
129
+ multiline
130
+ />
131
+ </section>
132
+ </nav>
133
+ </aside>
134
+ </ResizableRegion>
135
+ </div>
136
+ <main id="recipe-shell-content" class="recipe-shell__main kui-pane">
137
+ <PanelHeader
138
+ title={
139
+ selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'
140
+ }
141
+ titleId="recipe-shell-main-title"
142
+ actions={
143
+ <button
144
+ class="kui-recipe__button"
145
+ data-primary="true"
146
+ type="button"
147
+ data-action="recipe-action"
148
+ data-recipe-command="new"
149
+ >
150
+ New task
151
+ </button>
152
+ }
153
+ />
154
+ <div class="recipe-shell__main-body kui-pane__content kui-content">
155
+ <p class="kui-recipe__ownership kui-content-item">
156
+ Recipe owns pane geometry and one scroll owner per pane. The app
157
+ owns routing, data, pane visibility, sizes, and persistence.
158
+ </p>
159
+ <div class="recipe-shell__cards">
160
+ {[
161
+ 'Release accessibility audit',
162
+ 'Prepare tablet navigation',
163
+ 'Review stale-data states',
164
+ 'Confirm package boundaries',
165
+ ].map((title) => (
166
+ <article class="recipe-shell__card kui-content-item">
167
+ <strong>{title}</strong>
168
+ <p class="kui-recipe__muted">
169
+ Assigned to the interface systems team · due this week
170
+ </p>
171
+ </article>
172
+ ))}
173
+ </div>
174
+ </div>
175
+ </main>
176
+ <div id="recipe-shell-inspector" class="recipe-shell__inspector-region">
177
+ <ResizableRegion
178
+ id="recipe-inspector"
179
+ label="Inspector"
180
+ size={inspectorSize.value}
181
+ min={200}
182
+ max={360}
183
+ edge="start"
184
+ >
185
+ <aside class="recipe-shell__inspector kui-pane">
186
+ <PanelHeader
187
+ title="Inspector"
188
+ titleId="recipe-shell-inspector-title"
189
+ />
190
+ <div class="recipe-shell__inspector-body kui-pane__content kui-content">
191
+ <ValueTable label="Selected task">
192
+ <ValueTableRow label="Status" value="In review" />
193
+ <ValueTableRow label="Owner" value="Mara Chen" />
194
+ <ValueTableRow label="Priority" value="High" />
195
+ </ValueTable>
196
+ </div>
197
+ </aside>
198
+ </ResizableRegion>
199
+ </div>
200
+ </div>
201
+ </section>
202
+ );
203
+ return {
204
+ render,
205
+ action(command, element) {
206
+ const pane = command.match(
207
+ /^show-(navigation|content|inspector)$/,
208
+ )?.[1] as typeof responsivePane.value | undefined;
209
+ if (pane) responsivePane.value = pane;
210
+ const id = element.dataset.itemId;
211
+ if (id) selected.value = id;
212
+ announce(
213
+ id
214
+ ? `Opened ${id}`
215
+ : pane
216
+ ? `Showing ${pane}`
217
+ : command === 'new'
218
+ ? 'New task requested'
219
+ : `${command} requested`,
220
+ );
221
+ },
222
+ resize(id, size) {
223
+ if (id === 'recipe-navigation') navigationSize.value = size;
224
+ if (id === 'recipe-inspector') inspectorSize.value = size;
225
+ announce(`${id} resized to ${size}px`);
226
+ },
227
+ };
32
228
  };