@kerfjs/ui 5.0.0-beta.16 → 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.
- package/README.md +111 -81
- package/ai/component-catalog-extension.schema.json +79 -0
- package/ai/component-catalog.json +499 -0
- package/ai/component-catalog.schema.json +15 -0
- package/ai/public-api-signatures-v1.md +86 -32
- package/ai/skill.md +60 -51
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/dist/app-tab.d.ts +1 -1
- package/dist/app-tab.js +1 -1
- package/dist/catalog.d.ts +12 -3
- package/dist/catalog.js +283 -90
- package/dist/catalog.js.map +1 -1
- package/dist/chunk-43FUQH35.js +83 -0
- package/dist/chunk-43FUQH35.js.map +1 -0
- package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
- package/dist/chunk-4TRDV6RL.js.map +1 -0
- package/dist/chunk-6ETZKKAE.js +38 -0
- package/dist/chunk-6ETZKKAE.js.map +1 -0
- package/dist/chunk-6WTETUZ6.js +21 -0
- package/dist/chunk-6WTETUZ6.js.map +1 -0
- package/dist/chunk-7L6EHUMV.js +31 -0
- package/dist/chunk-7L6EHUMV.js.map +1 -0
- package/dist/chunk-BBRXX6PF.js +108 -0
- package/dist/chunk-BBRXX6PF.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-ELSJX4W7.js +68 -0
- package/dist/chunk-ELSJX4W7.js.map +1 -0
- package/dist/{chunk-LCOBDOCC.js → chunk-ENVTNLYC.js} +153 -67
- package/dist/chunk-ENVTNLYC.js.map +1 -0
- package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
- package/dist/chunk-FEY65TBV.js.map +1 -0
- package/dist/chunk-JTHUXDN2.js +58 -0
- package/dist/chunk-JTHUXDN2.js.map +1 -0
- package/dist/chunk-LPOMRC4C.js +25 -0
- package/dist/chunk-LPOMRC4C.js.map +1 -0
- package/dist/chunk-NENBUYX5.js +79 -0
- package/dist/chunk-NENBUYX5.js.map +1 -0
- package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
- package/dist/chunk-NVWXKU37.js.map +1 -0
- package/dist/chunk-PIDTEQ3U.js +105 -0
- package/dist/chunk-PIDTEQ3U.js.map +1 -0
- package/dist/chunk-PX3YSS5C.js +32 -0
- package/dist/chunk-PX3YSS5C.js.map +1 -0
- package/dist/chunk-PZQVSDLH.js +61 -0
- package/dist/chunk-PZQVSDLH.js.map +1 -0
- package/dist/chunk-QIRXYKCE.js +113 -0
- package/dist/chunk-QIRXYKCE.js.map +1 -0
- package/dist/chunk-QO55FN2Y.js +20 -0
- package/dist/chunk-QO55FN2Y.js.map +1 -0
- package/dist/chunk-QQFJMRIB.js +45 -0
- package/dist/chunk-QQFJMRIB.js.map +1 -0
- package/dist/chunk-S3HKPI7I.js +218 -0
- package/dist/chunk-S3HKPI7I.js.map +1 -0
- package/dist/chunk-S3PCZ7NV.js +38 -0
- package/dist/chunk-S3PCZ7NV.js.map +1 -0
- package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-U5IFGQCD.js +117 -0
- package/dist/chunk-U5IFGQCD.js.map +1 -0
- package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
- package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
- package/dist/chunk-ZK2G3VR5.js +35 -0
- package/dist/chunk-ZK2G3VR5.js.map +1 -0
- package/dist/chunk-ZVI5YST2.js +41 -0
- package/dist/chunk-ZVI5YST2.js.map +1 -0
- package/dist/collapsible-panel.d.ts +2 -2
- package/dist/collapsible-panel.js +47 -6
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/device-class.js +33 -5
- package/dist/device-class.js.map +1 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/empty-state.d.ts +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +1 -1
- package/dist/floating-toolbar.js +1 -1
- package/dist/index.js +25 -25
- package/dist/list-action-row.d.ts +1 -1
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.d.ts +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-inset-control.d.ts +1 -1
- package/dist/list-inset-control.js +1 -1
- package/dist/list-inset-text.d.ts +1 -1
- package/dist/list-inset-text.js +1 -1
- package/dist/list-item.d.ts +1 -1
- package/dist/list-item.js +1 -1
- package/dist/loading-spinner.js +1 -1
- package/dist/nav-stack.d.ts +1 -1
- package/dist/nav-stack.js +1 -1
- package/dist/panel-header.d.ts +1 -1
- package/dist/panel-header.js +1 -1
- package/dist/resizable-region.d.ts +1 -1
- package/dist/resizable-region.js +1 -1
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.d.ts +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/split-view.d.ts +1 -1
- package/dist/split-view.js +70 -10
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +1 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog.css +48 -1
- package/dist/styles/toolbar-text.css +54 -2
- package/dist/styles/webawesome.css +131 -0
- package/dist/tab-bar.d.ts +1 -1
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +1 -1
- package/dist/tab-scaffold.js +43 -8
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +1 -1
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +17 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +1 -1
- package/dist/toolbar.js +1 -1
- package/dist/value-table.d.ts +2 -2
- package/dist/value-table.js +1 -1
- package/dist/wire-catalog.d.ts +31 -2
- package/dist/wire-catalog.js +203 -8
- package/dist/wire-catalog.js.map +1 -1
- package/dist/wire-nav-stack.js +22 -7
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-resizable-regions.d.ts +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.js +52 -41
- package/dist/wire-sidebar.js.map +1 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.js +9 -4
- package/dist/wire-tab-scaffold.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.js +56 -9
- package/dist/workbench.js.map +1 -1
- package/docs/app-layouts.md +7 -7
- package/docs/catalog.md +110 -25
- package/docs/collapsible-panel.md +38 -11
- package/docs/component-contract.md +30 -2
- package/docs/component-selection.md +76 -65
- package/docs/design/templates.md +1 -1
- package/docs/design-philosophy.md +5 -5
- package/docs/device-class.md +8 -8
- package/docs/examples/command-palette-adapter.tsx +67 -17
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +19 -19
- package/docs/nav-stack.md +15 -6
- package/docs/recipes.md +4 -4
- package/docs/split-view.md +3 -3
- package/docs/tab-scaffold.md +16 -7
- package/docs/ux-demo.md +2 -2
- package/docs/webawesome-theme.md +73 -8
- package/docs/workbench.md +18 -5
- package/llms.txt +36 -15
- package/package.json +6 -4
- package/ux-demo/recipes/app-shell.tsx +206 -10
- package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
- package/ux-demo/recipes/compact-toolbar.tsx +114 -12
- package/ux-demo/recipes/composer-form.tsx +106 -18
- package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
- package/ux-demo/recipes/list-workspace-states.tsx +165 -8
- package/ux-demo/recipes/loaders.ts +5 -2
- package/ux-demo/recipes/loading-inspector.tsx +124 -41
- package/ux-demo/recipes/mount-recipe.ts +27 -6
- package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
- package/ux-demo/recipes/navigation-stack.tsx +61 -19
- package/ux-demo/recipes/types.ts +3 -1
- package/ux-demo/recipes/workspace-header.tsx +57 -2
- package/dist/chunk-2Z5TWHKQ.js +0 -34
- package/dist/chunk-2Z5TWHKQ.js.map +0 -1
- package/dist/chunk-37T3VNIZ.js +0 -19
- package/dist/chunk-37T3VNIZ.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-5ATCDSQS.js +0 -42
- package/dist/chunk-5ATCDSQS.js.map +0 -1
- package/dist/chunk-5E2HVJ33.js +0 -10
- package/dist/chunk-5E2HVJ33.js.map +0 -1
- package/dist/chunk-7BR3DO2A.js.map +0 -1
- package/dist/chunk-7JB7VPRI.js +0 -34
- package/dist/chunk-7JB7VPRI.js.map +0 -1
- package/dist/chunk-7LDCTU7D.js +0 -134
- package/dist/chunk-7LDCTU7D.js.map +0 -1
- package/dist/chunk-AHQI52QO.js.map +0 -1
- package/dist/chunk-AQNKJRFH.js +0 -79
- package/dist/chunk-AQNKJRFH.js.map +0 -1
- package/dist/chunk-AVZHJN6C.js +0 -14
- package/dist/chunk-AVZHJN6C.js.map +0 -1
- package/dist/chunk-B24C4A6T.js +0 -22
- package/dist/chunk-B24C4A6T.js.map +0 -1
- package/dist/chunk-BLK3UEEI.js +0 -23
- package/dist/chunk-BLK3UEEI.js.map +0 -1
- package/dist/chunk-CH4SQ6H5.js +0 -39
- package/dist/chunk-CH4SQ6H5.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-I7INRJO4.js +0 -17
- package/dist/chunk-I7INRJO4.js.map +0 -1
- package/dist/chunk-L4OKFDJY.js +0 -24
- package/dist/chunk-L4OKFDJY.js.map +0 -1
- package/dist/chunk-LCOBDOCC.js.map +0 -1
- package/dist/chunk-M5JRI6MN.js +0 -27
- package/dist/chunk-M5JRI6MN.js.map +0 -1
- package/dist/chunk-RNPGFQ33.js +0 -10
- package/dist/chunk-RNPGFQ33.js.map +0 -1
- package/dist/chunk-RUNWEPR4.js +0 -10
- package/dist/chunk-RUNWEPR4.js.map +0 -1
- package/dist/chunk-VPCTCUED.js +0 -10
- package/dist/chunk-VPCTCUED.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-XE5ZLPCC.js +0 -20
- package/dist/chunk-XE5ZLPCC.js.map +0 -1
- package/dist/chunk-XXMOARTH.js.map +0 -1
- package/dist/chunk-YGN7OMLT.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wire-tab-scaffold.ts"],"names":[],"mappings":";;;AAWO,SAAS,eAAA,
|
|
1
|
+
{"version":3,"sources":["../src/wire-tab-scaffold.ts"],"names":[],"mappings":";;;AAWO,SAAS,eAAA,CACd,MACA,OAAA,EACY;AACZ,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,iCAAiC,IAC1D,IAAA,GACA,IAAA,CAAK,cAAc,iCAAiC,CAAA;AACxD,EAAA,IAAI,EAAE,OAAA,YAAmB,WAAA,CAAA,EAAc,OAAO,MAAM;AAAA,EAAC,CAAA;AACrD,EAAA,OAAO,QAAA;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,yBAAA;AAAA,IACA,CAAC,QAAQ,MAAA,KAAW;AAClB,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,uBAAuB,CAAA;AACzD,MAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AAAA,IACnC;AAAA,GACF;AACF","file":"wire-tab-scaffold.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireTabScaffoldOptions {\n /** Invoked with the selected tab id when a bottom-bar tab is activated. */\n onSelect: (tabId: string) => void;\n}\n\n/**\n * Wire a `TabScaffold`'s bottom tab bar: clicking a tab calls `onSelect` with its\n * id (the app then updates its controlled `active`). Returns a disposer.\n */\nexport function wireTabScaffold(\n root: Element,\n options: WireTabScaffoldOptions,\n): () => void {\n const section = root.matches('[data-component=\"tab-scaffold\"]')\n ? root\n : root.querySelector('[data-component=\"tab-scaffold\"]');\n if (!(section instanceof HTMLElement)) return () => {};\n return delegate(\n section,\n 'click',\n '[data-tab-scaffold-tab]',\n (_event, target) => {\n const tabId = target.getAttribute('data-tab-scaffold-tab');\n if (tabId) options.onSelect(tabId);\n },\n );\n}\n"]}
|
|
@@ -101,6 +101,6 @@ interface TokenSearchFieldsHandle {
|
|
|
101
101
|
* expand/collapse/focus. Returns a {@link TokenSearchFieldsHandle} — a disposer that also
|
|
102
102
|
* exposes the managed `expanded` state per field id.
|
|
103
103
|
*/
|
|
104
|
-
declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
|
|
104
|
+
declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard, }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
|
|
105
105
|
|
|
106
106
|
export { type TokenSearchCollapsibleOptions, type TokenSearchEdit, type TokenSearchFieldsHandle, type TokenSearchKeyboardOptions, type TokenSearchSubmit, type TokenSearchTokenRemoval, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
|
package/dist/workbench.d.ts
CHANGED
|
@@ -28,6 +28,6 @@ interface WorkbenchProps {
|
|
|
28
28
|
* work area relayouts once, not per frame. The app owns each `collapsed` flag;
|
|
29
29
|
* the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.
|
|
30
30
|
*/
|
|
31
|
-
declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className }: WorkbenchProps): SafeHtml;
|
|
31
|
+
declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, }: WorkbenchProps): SafeHtml;
|
|
32
32
|
|
|
33
33
|
export { Workbench, type WorkbenchPanel, type WorkbenchProps };
|
package/dist/workbench.js
CHANGED
|
@@ -1,15 +1,62 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'kerfjs/jsx-runtime';
|
|
2
2
|
|
|
3
3
|
// src/workbench.tsx
|
|
4
|
-
function Workbench({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
function Workbench({
|
|
5
|
+
id,
|
|
6
|
+
label,
|
|
7
|
+
main,
|
|
8
|
+
leftRail,
|
|
9
|
+
rightRail,
|
|
10
|
+
bottomDrawer,
|
|
11
|
+
className = ""
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsxs(
|
|
14
|
+
"section",
|
|
15
|
+
{
|
|
16
|
+
class: `kui-workbench ${className}`.trim(),
|
|
17
|
+
id,
|
|
18
|
+
"data-component": "workbench",
|
|
19
|
+
"aria-label": label,
|
|
20
|
+
children: [
|
|
21
|
+
leftRail && /* @__PURE__ */ jsx(
|
|
22
|
+
"aside",
|
|
23
|
+
{
|
|
24
|
+
class: "kui-workbench__rail kui-workbench__rail--left",
|
|
25
|
+
"data-workbench-rail": "left",
|
|
26
|
+
"data-collapsed": String(leftRail.collapsed ?? false),
|
|
27
|
+
"aria-label": leftRail.label || void 0,
|
|
28
|
+
style: leftRail.size ? `--kui-workbench-rail-width: ${leftRail.size}px` : void 0,
|
|
29
|
+
children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: leftRail.content })
|
|
30
|
+
}
|
|
31
|
+
),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { class: "kui-workbench__center", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("div", { class: "kui-workbench__main", "data-workbench-main": true, children: main }),
|
|
34
|
+
bottomDrawer && /* @__PURE__ */ jsx(
|
|
35
|
+
"section",
|
|
36
|
+
{
|
|
37
|
+
class: "kui-workbench__drawer",
|
|
38
|
+
"data-workbench-drawer": true,
|
|
39
|
+
"data-collapsed": String(bottomDrawer.collapsed ?? false),
|
|
40
|
+
"aria-label": bottomDrawer.label || void 0,
|
|
41
|
+
style: bottomDrawer.size ? `--kui-workbench-drawer-height: ${bottomDrawer.size}px` : void 0,
|
|
42
|
+
children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: bottomDrawer.content })
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
] }),
|
|
46
|
+
rightRail && /* @__PURE__ */ jsx(
|
|
47
|
+
"aside",
|
|
48
|
+
{
|
|
49
|
+
class: "kui-workbench__rail kui-workbench__rail--right",
|
|
50
|
+
"data-workbench-rail": "right",
|
|
51
|
+
"data-collapsed": String(rightRail.collapsed ?? false),
|
|
52
|
+
"aria-label": rightRail.label || void 0,
|
|
53
|
+
style: rightRail.size ? `--kui-workbench-rail-width: ${rightRail.size}px` : void 0,
|
|
54
|
+
children: /* @__PURE__ */ jsx("div", { class: "kui-workbench__panel-content", children: rightRail.content })
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
);
|
|
13
60
|
}
|
|
14
61
|
|
|
15
62
|
export { Workbench };
|
package/dist/workbench.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workbench.tsx"],"names":[],"mappings":";;;AAgCO,SAAS,SAAA,CAAU,
|
|
1
|
+
{"version":3,"sources":["../src/workbench.tsx"],"names":[],"mappings":";;;AAgCO,SAAS,SAAA,CAAU;AAAA,EACxB,EAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA,GAAY;AACd,CAAA,EAAmB;AACjB,EAAA,uBACE,IAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,CAAA,cAAA,EAAiB,SAAS,CAAA,CAAA,CAAG,IAAA,EAAK;AAAA,MACzC,EAAA;AAAA,MACA,gBAAA,EAAe,WAAA;AAAA,MACf,YAAA,EAAY,KAAA;AAAA,MAEX,QAAA,EAAA;AAAA,QAAA,QAAA,oBACC,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,+CAAA;AAAA,YACN,qBAAA,EAAoB,MAAA;AAAA,YACpB,gBAAA,EAAgB,MAAA,CAAO,QAAA,CAAS,SAAA,IAAa,KAAK,CAAA;AAAA,YAClD,YAAA,EAAY,SAAS,KAAA,IAAS,MAAA;AAAA,YAC9B,OACE,QAAA,CAAS,IAAA,GACL,CAAA,4BAAA,EAA+B,QAAA,CAAS,IAAI,CAAA,EAAA,CAAA,GAC5C,MAAA;AAAA,YAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,8BAAA,EAAgC,mBAAS,OAAA,EAAQ;AAAA;AAAA,SAC9D;AAAA,wBAEF,IAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,uBAAA,EACT,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,qBAAA,EAAsB,qBAAA,EAAmB,MACjD,QAAA,EAAA,IAAA,EACH,CAAA;AAAA,UACC,YAAA,oBACC,GAAA;AAAA,YAAC,SAAA;AAAA,YAAA;AAAA,cACC,KAAA,EAAM,uBAAA;AAAA,cACN,uBAAA,EAAqB,IAAA;AAAA,cACrB,gBAAA,EAAgB,MAAA,CAAO,YAAA,CAAa,SAAA,IAAa,KAAK,CAAA;AAAA,cACtD,YAAA,EAAY,aAAa,KAAA,IAAS,MAAA;AAAA,cAClC,OACE,YAAA,CAAa,IAAA,GACT,CAAA,+BAAA,EAAkC,YAAA,CAAa,IAAI,CAAA,EAAA,CAAA,GACnD,MAAA;AAAA,cAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,8BAAA,EACR,uBAAa,OAAA,EAChB;AAAA;AAAA;AACF,SAAA,EAEJ,CAAA;AAAA,QACC,SAAA,oBACC,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,gDAAA;AAAA,YACN,qBAAA,EAAoB,OAAA;AAAA,YACpB,gBAAA,EAAgB,MAAA,CAAO,SAAA,CAAU,SAAA,IAAa,KAAK,CAAA;AAAA,YACnD,YAAA,EAAY,UAAU,KAAA,IAAS,MAAA;AAAA,YAC/B,OACE,SAAA,CAAU,IAAA,GACN,CAAA,4BAAA,EAA+B,SAAA,CAAU,IAAI,CAAA,EAAA,CAAA,GAC7C,MAAA;AAAA,YAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,8BAAA,EAAgC,oBAAU,OAAA,EAAQ;AAAA;AAAA;AAC/D;AAAA;AAAA,GAEJ;AAEJ","file":"workbench.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\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}\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 aria-label={leftRail.label || 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 <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 aria-label={bottomDrawer.label || undefined}\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 </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 aria-label={rightRail.label || 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 </section>\n );\n}\n"]}
|
package/docs/app-layouts.md
CHANGED
|
@@ -21,13 +21,13 @@ gate the roomier presentations.
|
|
|
21
21
|
|
|
22
22
|
## Decision matrix
|
|
23
23
|
|
|
24
|
-
| Situation
|
|
25
|
-
|
|
|
26
|
-
| Simple app, a few flat sections
|
|
27
|
-
| Drill-down browsing (list → item → sub-item)
|
|
28
|
-
| Two related panes, selecting on the left updates the right
|
|
29
|
-
| Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench`
|
|
30
|
-
| Mobile app with 2–5 top-level destinations, each its own drill-down
|
|
24
|
+
| Situation | Layout | Device threshold |
|
|
25
|
+
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| Simple app, a few flat sections | `NavStack` with one entry (single pane); add `TabScaffold` for 2–5 co-equal sections on handset | `TabScaffold` on `compact`; promote its tabs to a `Workbench` rail / sidebar `atLeast('desktop')` |
|
|
27
|
+
| Drill-down browsing (list → item → sub-item) | `NavStack`; upgrade to `SplitView` once list + detail fit together | `SplitView` two-pane `atLeast('tablet')` landscape / non-`compact`; `NavStack` form on `compact` |
|
|
28
|
+
| Two related panes, selecting on the left updates the right | `SplitView` | two panes when not `compact`; collapses to `NavStack` (list → detail) on `compact` |
|
|
29
|
+
| Complex tool / editor with peripheral panels (navigator, inspector, console) | `Workbench` | full three-panel `atLeast('desktop')`; on smaller classes present the rails via `NavStack` / overlay drawers, not a shrunken shell |
|
|
30
|
+
| Mobile app with 2–5 top-level destinations, each its own drill-down | `TabScaffold`, each tab a `NavStack` | bottom bar on `compact`; promote to a rail / sidebar `atLeast('desktop')` |
|
|
31
31
|
|
|
32
32
|
### Worked examples
|
|
33
33
|
|
package/docs/catalog.md
CHANGED
|
@@ -7,7 +7,7 @@ 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
9
|
```bash
|
|
10
|
-
npm install @kerfjs/ui
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
- `Catalog(props)` returns the shell as `SafeHtml` (a `<main class="kui-catalog">`).
|
|
@@ -17,19 +17,20 @@ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is neede
|
|
|
17
17
|
- `wireCatalog(root, options)` wires the interactions (sidebar selection, the
|
|
18
18
|
related-entry popup menu, and the collapse/theme toggles) with one delegated
|
|
19
19
|
listener set and returns a disposer; it can also mirror the active id into the
|
|
20
|
-
URL.
|
|
20
|
+
URL and reveal the active sidebar row after a controlled render.
|
|
21
21
|
|
|
22
22
|
## What you supply
|
|
23
23
|
|
|
24
24
|
- **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
|
|
25
|
-
|
|
26
|
-
under a `ListHeader` for its category.
|
|
25
|
+
description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
|
|
26
|
+
`ListItem` under a `ListHeader` for its category. Short `tags` render as quiet
|
|
27
|
+
trailing pills for decision metadata such as `Discouraged`.
|
|
27
28
|
- **`content`** — the rendered preview for the active entry. Keep a map of `id →
|
|
28
|
-
|
|
29
|
+
() => SafeHtml` in your app and call `renderers[active]()` in your render.
|
|
29
30
|
- **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
|
|
30
31
|
- **`secondarySections`** — an optional secondary "ecosystem" group shown below the
|
|
31
32
|
primary sections with a quieter treatment: `{ label, sections, collapsible?,
|
|
32
|
-
|
|
33
|
+
expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
|
|
33
34
|
`expanded` (the app owns it; wire it with `wireCatalog`'s `onToggleSecondary`).
|
|
34
35
|
- Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
|
|
35
36
|
sidebar content), and `status` (a footer status line).
|
|
@@ -46,7 +47,7 @@ instead of hand-rolled example markup, so labels, notes, and left-edge alignment
|
|
|
46
47
|
stay consistent:
|
|
47
48
|
|
|
48
49
|
```tsx
|
|
49
|
-
import { CatalogExample, CatalogExampleStack } from
|
|
50
|
+
import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
|
|
50
51
|
|
|
51
52
|
const buttonPreview = (
|
|
52
53
|
<CatalogExampleStack label="Button variants">
|
|
@@ -71,26 +72,99 @@ content-item/composition that already owns its geometry. The inset is published
|
|
|
71
72
|
the `--kui-catalog-example-align` custom property so a debug overlay can exclude it
|
|
72
73
|
from a specimen's measured margin.
|
|
73
74
|
|
|
75
|
+
## Geometry inspection
|
|
76
|
+
|
|
77
|
+
Pass `geometryOverlay` to `Catalog` when individual component previews should
|
|
78
|
+
show otherwise-invisible geometry. The controlled boolean draws a dashed outer
|
|
79
|
+
bound around specimens with transparent backgrounds and devtools-style orange
|
|
80
|
+
bands over positive margins. Keep the prop present while switching entries so
|
|
81
|
+
`wireCatalog` can reuse one overlay layer; set it to `true` for focused component
|
|
82
|
+
previews and `false` for full compositions whose child geometry should remain
|
|
83
|
+
unmarked.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Catalog
|
|
87
|
+
{...props}
|
|
88
|
+
geometryOverlay={activeEntry.kind === "component"}
|
|
89
|
+
content={renderers[active.value]()}
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Call `wireCatalogGeometryOverlay(root)` after the first render to synchronize the
|
|
94
|
+
opt-in layer across rerenders, theme changes, resizes, and scrolling, and retain
|
|
95
|
+
its disposer alongside `wireCatalog`'s. `CatalogExample` labels and notes are
|
|
96
|
+
excluded; its `align` inset is also subtracted so alignment scaffolding is not
|
|
97
|
+
reported as intrinsic component margin. Put
|
|
98
|
+
`data-catalog-geometry-overlay-skip` on a preview subtree that is intentionally
|
|
99
|
+
explanatory chrome rather than a specimen.
|
|
100
|
+
|
|
101
|
+
Use the overlay together with machine-readable geometry ownership metadata; the
|
|
102
|
+
overlay verifies what is rendered, while metadata tells people and AI tools
|
|
103
|
+
whether the component, its parent, or its children are responsible for margin,
|
|
104
|
+
border, and padding. Catalogs for downstream components should conform to the
|
|
105
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
106
|
+
contract and can start from the checked
|
|
107
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
108
|
+
example; provide those entries beside Kerf's shipped catalog to AI tools.
|
|
109
|
+
|
|
110
|
+
## Selection reveal
|
|
111
|
+
|
|
112
|
+
Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
|
|
113
|
+
`onSelect` updates controlled state, the helper waits one animation frame, finds
|
|
114
|
+
the exact matching `data-item-id`, and scrolls it into view without changing
|
|
115
|
+
focus. A newer selection or disposal cancels the pending reveal. The default
|
|
116
|
+
media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
|
|
117
|
+
layouts keep their existing scroll position.
|
|
118
|
+
|
|
119
|
+
Pass an options object instead of `true` to customize `block`, `inline`,
|
|
120
|
+
`behavior`, or `media`; `media: false` deliberately enables the behavior at all
|
|
121
|
+
sizes. For an initial deep link that did not come through `wireCatalog`, call
|
|
122
|
+
`revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
|
|
123
|
+
|
|
74
124
|
## Complete example
|
|
75
125
|
|
|
76
126
|
```tsx
|
|
77
|
-
import { mount, signal } from
|
|
78
|
-
import { Catalog, type CatalogSection } from
|
|
79
|
-
import {
|
|
80
|
-
|
|
127
|
+
import { mount, signal } from "kerfjs";
|
|
128
|
+
import { Catalog, type CatalogSection } from "@kerfjs/ui/catalog";
|
|
129
|
+
import {
|
|
130
|
+
revealCatalogEntry,
|
|
131
|
+
wireCatalog,
|
|
132
|
+
wireCatalogGeometryOverlay,
|
|
133
|
+
} from "@kerfjs/ui/wire-catalog";
|
|
134
|
+
import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
|
|
81
135
|
|
|
82
136
|
// 1. Describe your components once.
|
|
83
137
|
const sections: CatalogSection[] = [
|
|
84
138
|
{
|
|
85
|
-
category:
|
|
139
|
+
category: "Controls",
|
|
86
140
|
entries: [
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
141
|
+
{
|
|
142
|
+
id: "button",
|
|
143
|
+
name: "Button",
|
|
144
|
+
description: "A pressable control.",
|
|
145
|
+
resources: [
|
|
146
|
+
{
|
|
147
|
+
label: "Source",
|
|
148
|
+
href: "/src/button.tsx",
|
|
149
|
+
detail: "src/button.tsx",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "field",
|
|
155
|
+
name: "Field",
|
|
156
|
+
description: "A labeled input.",
|
|
157
|
+
tags: ["Discouraged"],
|
|
158
|
+
related: [{ id: "button", name: "Button", group: "Used with" }],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
category: "Feedback",
|
|
164
|
+
entries: [
|
|
165
|
+
{ id: "toast", name: "Toast", description: "A transient message." },
|
|
91
166
|
],
|
|
92
167
|
},
|
|
93
|
-
{ category: 'Feedback', entries: [{ id: 'toast', name: 'Toast', description: 'A transient message.' }] },
|
|
94
168
|
];
|
|
95
169
|
|
|
96
170
|
// 2. One preview render per entry id.
|
|
@@ -101,32 +175,43 @@ const renderers: Record<string, () => ReturnType<typeof Button>> = {
|
|
|
101
175
|
};
|
|
102
176
|
|
|
103
177
|
// 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
|
|
104
|
-
const initial =
|
|
178
|
+
const initial =
|
|
179
|
+
new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
|
|
105
180
|
const active = signal(initial);
|
|
106
181
|
const collapsed = signal(false);
|
|
107
|
-
const theme = signal<
|
|
182
|
+
const theme = signal<"light" | "dark">("light");
|
|
108
183
|
|
|
109
|
-
const app = document.getElementById(
|
|
184
|
+
const app = document.getElementById("app")!;
|
|
110
185
|
mount(app, () => (
|
|
111
186
|
<Catalog
|
|
112
|
-
brand={{ title:
|
|
187
|
+
brand={{ title: "Acme UI", subtitle: "Design system" }}
|
|
113
188
|
sections={sections}
|
|
114
189
|
active={active.value}
|
|
115
190
|
content={renderers[active.value]?.() ?? <></>}
|
|
116
191
|
collapsed={collapsed.value}
|
|
117
192
|
theme={theme.value}
|
|
193
|
+
geometryOverlay={true}
|
|
118
194
|
/>
|
|
119
195
|
));
|
|
120
196
|
|
|
121
197
|
wireCatalog(app, {
|
|
122
|
-
onSelect: (id) => {
|
|
123
|
-
|
|
198
|
+
onSelect: (id) => {
|
|
199
|
+
active.value = id;
|
|
200
|
+
},
|
|
201
|
+
onToggleSidebar: () => {
|
|
202
|
+
collapsed.value = !collapsed.value;
|
|
203
|
+
},
|
|
124
204
|
onToggleTheme: () => {
|
|
125
|
-
theme.value = theme.value ===
|
|
205
|
+
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
126
206
|
document.documentElement.dataset.theme = theme.value; // apply your theme however you like
|
|
127
207
|
},
|
|
128
|
-
urlParam:
|
|
208
|
+
urlParam: "c", // mirror the active id into ?c=<id>
|
|
209
|
+
revealSelection: true, // reveal long desktop sidebars without moving focus
|
|
129
210
|
});
|
|
211
|
+
wireCatalogGeometryOverlay(app);
|
|
212
|
+
|
|
213
|
+
// Optional for an initial deep link whose row may start outside the viewport.
|
|
214
|
+
revealCatalogEntry(app, initial, { block: "center" });
|
|
130
215
|
```
|
|
131
216
|
|
|
132
217
|
## Ownership boundary
|
|
@@ -7,7 +7,7 @@ a full shell so you can dock one panel wherever your layout needs it. They are
|
|
|
7
7
|
subpath-only, tree-shakeable modules that add nothing to the main barrel.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @kerfjs/ui
|
|
10
|
+
npm install @kerfjs/ui # kerfjs is a peer
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.css`.
|
|
@@ -46,33 +46,60 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
|
|
|
46
46
|
## Example
|
|
47
47
|
|
|
48
48
|
```tsx
|
|
49
|
-
import { signal, mount } from
|
|
50
|
-
import { deviceClass } from
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
import { signal, mount } from "kerfjs";
|
|
50
|
+
import { deviceClass } from "@kerfjs/ui/device-class";
|
|
51
|
+
import {
|
|
52
|
+
CollapsiblePanel,
|
|
53
|
+
CollapsiblePanelToggle,
|
|
54
|
+
} from "@kerfjs/ui/collapsible-panel";
|
|
55
|
+
import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
|
|
56
|
+
import "@kerfjs/ui/collapsible-panel.css";
|
|
54
57
|
|
|
55
58
|
const navCollapsed = signal(false);
|
|
56
59
|
const device = deviceClass();
|
|
57
60
|
|
|
58
|
-
const app = document.querySelector(
|
|
61
|
+
const app = document.querySelector("#app")!;
|
|
59
62
|
mount(app, () => (
|
|
60
63
|
<div class="layout">
|
|
61
|
-
<CollapsiblePanel
|
|
64
|
+
<CollapsiblePanel
|
|
65
|
+
id="nav"
|
|
66
|
+
side="left"
|
|
67
|
+
collapsed={navCollapsed.value}
|
|
68
|
+
label="Navigator"
|
|
69
|
+
>
|
|
62
70
|
<header>
|
|
63
|
-
<CollapsiblePanelToggle
|
|
71
|
+
<CollapsiblePanelToggle
|
|
72
|
+
side="left"
|
|
73
|
+
collapsed={navCollapsed.value}
|
|
74
|
+
action="toggle-nav"
|
|
75
|
+
panelId="nav"
|
|
76
|
+
/>
|
|
64
77
|
</header>
|
|
65
78
|
{/* nav items */}
|
|
66
79
|
</CollapsiblePanel>
|
|
67
80
|
<main>
|
|
68
|
-
{navCollapsed.value &&
|
|
81
|
+
{navCollapsed.value && (
|
|
82
|
+
<CollapsiblePanelToggle
|
|
83
|
+
side="left"
|
|
84
|
+
collapsed
|
|
85
|
+
action="toggle-nav"
|
|
86
|
+
label="Show navigator"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
69
89
|
{/* content */}
|
|
70
90
|
</main>
|
|
71
91
|
</div>
|
|
72
92
|
));
|
|
73
93
|
|
|
74
94
|
const stop = wireSidebar(app, {
|
|
75
|
-
panels: [
|
|
95
|
+
panels: [
|
|
96
|
+
{
|
|
97
|
+
id: "nav",
|
|
98
|
+
collapsed: navCollapsed,
|
|
99
|
+
toggleAction: "toggle-nav",
|
|
100
|
+
storageKey: "app.nav-collapsed",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
76
103
|
deviceClass: device,
|
|
77
104
|
});
|
|
78
105
|
```
|
|
@@ -20,6 +20,34 @@ remains authored rather than generated.
|
|
|
20
20
|
|
|
21
21
|
## Ownership boundaries
|
|
22
22
|
|
|
23
|
+
### Geometry ownership metadata
|
|
24
|
+
|
|
25
|
+
The canonical [`component-catalog.json`](../ai/component-catalog.json) records
|
|
26
|
+
the margin, border, and padding owner for every component and composition under
|
|
27
|
+
`geometry`. Read it before adding a wrapper or local spacing rule:
|
|
28
|
+
|
|
29
|
+
- `self` means the entry's public visual contract supplies that geometry.
|
|
30
|
+
- `parent` means the embedding layout must supply it.
|
|
31
|
+
- `child` means composed descendants supply it; the entry's outer shell does not.
|
|
32
|
+
- `none` means that dimension is intentionally absent from the standard contract.
|
|
33
|
+
- `conditional` means a variant, part, or configuration changes ownership; the
|
|
34
|
+
accompanying `notes` explain the boundary.
|
|
35
|
+
|
|
36
|
+
The values describe public ownership, including themed shadow parts, rather
|
|
37
|
+
than merely asking whether a CSS declaration appears on the host element.
|
|
38
|
+
Recipes are exempt because they arrange entries whose individual ownership is
|
|
39
|
+
already cataloged.
|
|
40
|
+
|
|
41
|
+
Downstream packages and applications should publish the same contract for their
|
|
42
|
+
reusable visual components. Use
|
|
43
|
+
[`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
|
|
44
|
+
and copy the structure of the checked
|
|
45
|
+
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
46
|
+
example. Keep app and Kerf entries as package-qualified inputs, then let people
|
|
47
|
+
or AI tools search their combined entries and compare `geometry` before adding
|
|
48
|
+
wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
|
|
49
|
+
claim Kerf delivery paths for them.
|
|
50
|
+
|
|
23
51
|
- Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
|
|
24
52
|
- Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
|
|
25
53
|
- Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
|
|
@@ -166,14 +194,14 @@ render, hands in its own via `collapsible.signals`, drives it through
|
|
|
166
194
|
UI is consistent by default without every app reinventing it.
|
|
167
195
|
|
|
168
196
|
`wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
|
|
169
|
-
helpers. Its collapse behavior was
|
|
197
|
+
helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
|
|
170
198
|
transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
|
|
171
199
|
apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
|
|
172
200
|
app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
|
|
173
201
|
`NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
|
|
174
202
|
and tab order are data, a `ResizableRegion`'s committed size and a
|
|
175
203
|
`Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
|
|
176
|
-
Each helper already owns only the
|
|
204
|
+
Each helper already owns only the _ephemeral mechanics_ around that state —
|
|
177
205
|
`wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
|
|
178
206
|
drag preview, `wireResizableRegions` the live drag preview — and reports committed
|
|
179
207
|
changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is
|