@kerfjs/ui 5.0.0-beta.17 → 5.0.0-beta.21
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 +221 -81
- package/ai/application-ui-diagnostic-ids-v1.json +92 -0
- package/ai/application-ui-profile-sync.cjs +529 -0
- package/ai/application-ui-profile.d.mts +1 -0
- package/ai/application-ui-profile.d.ts +102 -0
- package/ai/application-ui-profile.defaults.json +41 -0
- package/ai/application-ui-profile.mjs +626 -0
- package/ai/application-ui-profile.schema.json +130 -0
- package/ai/catalog-authoring.json +25 -0
- package/ai/catalog-authoring.schema.json +61 -0
- package/ai/compile-time-contracts-v1.json +101 -0
- package/ai/compile-time-contracts-v1.schema.json +49 -0
- package/ai/component-catalog-extension-v2.d.ts +7 -0
- package/ai/component-catalog-extension-v2.schema.json +7 -0
- package/ai/component-catalog-extension.schema.json +79 -0
- package/ai/component-catalog-v2-overrides.json +326 -0
- package/ai/component-catalog-v2.d.ts +67 -0
- package/ai/component-catalog-v2.json +4441 -0
- package/ai/component-catalog-v2.schema.json +246 -0
- package/ai/component-catalog.json +649 -6
- package/ai/component-catalog.schema.json +15 -0
- package/ai/public-api-signatures-v1.md +357 -91
- package/ai/skill.md +95 -50
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/cli.mjs +46 -0
- package/analyzer/index.d.mts +45 -0
- package/analyzer/index.mjs +743 -0
- package/analyzer/report.schema.json +66 -0
- package/dist/app-tab.d.ts +1 -1
- package/dist/app-tab.js +1 -1
- package/dist/browser/pane.js +3 -0
- package/dist/browser/sunken-panel.js +3 -0
- package/dist/catalog-resources.d.ts +34 -0
- package/dist/catalog-resources.js +22 -0
- package/dist/catalog-resources.js.map +1 -0
- package/dist/catalog.d.ts +29 -4
- package/dist/catalog.js +302 -84
- package/dist/catalog.js.map +1 -1
- package/dist/chunk-5VTB2BKP.js +35 -0
- package/dist/chunk-5VTB2BKP.js.map +1 -0
- package/dist/chunk-6WTETUZ6.js +21 -0
- package/dist/chunk-6WTETUZ6.js.map +1 -0
- package/dist/chunk-7KEUJIIC.js +218 -0
- package/dist/chunk-7KEUJIIC.js.map +1 -0
- package/dist/chunk-7L6EHUMV.js +31 -0
- package/dist/chunk-7L6EHUMV.js.map +1 -0
- package/dist/{chunk-XXMOARTH.js → chunk-AASLYRGE.js} +57 -16
- package/dist/chunk-AASLYRGE.js.map +1 -0
- package/dist/chunk-BBRXX6PF.js +108 -0
- package/dist/chunk-BBRXX6PF.js.map +1 -0
- package/dist/{chunk-6NLRBSVX.js → chunk-CN2XNTAM.js} +129 -72
- package/dist/chunk-CN2XNTAM.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-DSNIYOZ3.js +114 -0
- package/dist/chunk-DSNIYOZ3.js.map +1 -0
- package/dist/chunk-ELSJX4W7.js +68 -0
- package/dist/chunk-ELSJX4W7.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-JRKSK2HX.js +79 -0
- package/dist/chunk-JRKSK2HX.js.map +1 -0
- package/dist/chunk-K57KRAOV.js +117 -0
- package/dist/chunk-K57KRAOV.js.map +1 -0
- package/dist/chunk-LPOMRC4C.js +25 -0
- package/dist/chunk-LPOMRC4C.js.map +1 -0
- package/dist/chunk-M7JBER5U.js +41 -0
- package/dist/chunk-M7JBER5U.js.map +1 -0
- package/dist/chunk-NENBUYX5.js +79 -0
- package/dist/chunk-NENBUYX5.js.map +1 -0
- package/dist/chunk-OHU6CI22.js +83 -0
- package/dist/chunk-OHU6CI22.js.map +1 -0
- package/dist/{chunk-YGN7OMLT.js → chunk-OZWU5ZPZ.js} +2 -2
- package/dist/chunk-OZWU5ZPZ.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-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-RYRIFZCT.js +58 -0
- package/dist/chunk-RYRIFZCT.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-VLYV65LK.js +38 -0
- package/dist/chunk-VLYV65LK.js.map +1 -0
- package/dist/chunk-W4CXQYSG.js +23 -0
- package/dist/chunk-W4CXQYSG.js.map +1 -0
- package/dist/{chunk-WXMGNX2Y.js → chunk-W7YD6E4N.js} +2 -2
- package/dist/chunk-W7YD6E4N.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/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.d.ts +6 -4
- package/dist/index.js +27 -25
- package/dist/list-action-row.d.ts +1 -1
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.d.ts +30 -9
- 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/pane.d.ts +49 -0
- package/dist/pane.js +4 -0
- package/dist/pane.js.map +1 -0
- package/dist/panel-header.d.ts +10 -4
- 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.d.ts +6 -6
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +11 -5
- 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 +4 -3
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog.css +32 -6
- package/dist/styles/layout.css +2 -20
- package/dist/styles/pane.css +62 -0
- package/dist/styles/segmented-control.css +3 -7
- package/dist/styles/sunken-panel.css +18 -0
- package/dist/sunken-panel.d.ts +16 -0
- package/dist/sunken-panel.js +3 -0
- package/dist/sunken-panel.js.map +1 -0
- package/dist/tab-bar.d.ts +4 -3
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +6 -6
- package/dist/tab-scaffold.js +43 -8
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/token-search-field.d.ts +25 -9
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +10 -6
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +10 -11
- 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.d.ts +5 -2
- 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 +12 -7
- 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 +249 -46
- package/docs/collapsible-panel.md +38 -11
- package/docs/component-contract.md +132 -7
- package/docs/component-selection.md +79 -67
- package/docs/design/templates.md +1 -1
- package/docs/design-philosophy.md +7 -9
- package/docs/device-class.md +8 -8
- package/docs/examples/application-ui-profile.json +60 -0
- package/docs/examples/command-palette-adapter.tsx +67 -17
- package/docs/examples/component-catalog-extension-v2.json +81 -0
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +51 -32
- package/docs/nav-stack.md +15 -6
- package/docs/recipes.md +4 -4
- package/docs/split-view.md +3 -3
- package/docs/sunken-panel.md +43 -0
- package/docs/tab-scaffold.md +16 -7
- package/docs/type-contracts.md +99 -0
- package/docs/ui-analyzer.md +81 -0
- package/docs/ui-doctor.md +77 -0
- package/docs/ui-evaluator.md +122 -0
- package/docs/webawesome-theme.md +8 -8
- package/docs/workbench.md +18 -5
- package/doctor/cli.mjs +115 -0
- package/doctor/config.schema.json +68 -0
- package/doctor/index.d.mts +116 -0
- package/doctor/index.mjs +1256 -0
- package/doctor/report.schema.json +118 -0
- package/evaluator/cli.mjs +89 -0
- package/evaluator/index.d.mts +90 -0
- package/evaluator/index.mjs +1138 -0
- package/evaluator/report.schema.json +152 -0
- package/llms.txt +58 -15
- package/package.json +70 -10
- 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-6NLRBSVX.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-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-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-VPCTCUED.js +0 -10
- package/dist/chunk-VPCTCUED.js.map +0 -1
- package/dist/chunk-WRTAQ7BZ.js +0 -23
- package/dist/chunk-WRTAQ7BZ.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-WXMGNX2Y.js.map +0 -1
- package/dist/chunk-XXMOARTH.js.map +0 -1
- package/dist/chunk-YGN7OMLT.js.map +0 -1
- package/dist/chunk-ZQQ6P4SV.js +0 -20
- package/dist/chunk-ZQQ6P4SV.js.map +0 -1
package/dist/wire-nav-stack.js
CHANGED
|
@@ -3,10 +3,14 @@ import { delegate } from 'kerfjs';
|
|
|
3
3
|
// src/wire-nav-stack.ts
|
|
4
4
|
var DEFAULT_DURATION = 200;
|
|
5
5
|
function viewsOf(viewport) {
|
|
6
|
-
return Array.from(
|
|
6
|
+
return Array.from(
|
|
7
|
+
viewport.querySelectorAll(":scope > .kui-nav-stack__view")
|
|
8
|
+
);
|
|
7
9
|
}
|
|
8
10
|
function topView(viewport) {
|
|
9
|
-
const live = viewsOf(viewport).filter(
|
|
11
|
+
const live = viewsOf(viewport).filter(
|
|
12
|
+
(view) => view.dataset.navExiting !== "true"
|
|
13
|
+
);
|
|
10
14
|
return live[live.length - 1];
|
|
11
15
|
}
|
|
12
16
|
function reducedMotion(view) {
|
|
@@ -19,7 +23,12 @@ function wireNavStack(root, options = {}) {
|
|
|
19
23
|
};
|
|
20
24
|
const view = section.ownerDocument.defaultView ?? window;
|
|
21
25
|
const duration = options.duration ?? DEFAULT_DURATION;
|
|
22
|
-
const disposeBack = delegate(
|
|
26
|
+
const disposeBack = delegate(
|
|
27
|
+
section,
|
|
28
|
+
"click",
|
|
29
|
+
"[data-nav-back]",
|
|
30
|
+
() => options.onBack?.()
|
|
31
|
+
);
|
|
23
32
|
let activeKey = topView(viewport)?.dataset.navKey;
|
|
24
33
|
const timers = /* @__PURE__ */ new Set();
|
|
25
34
|
const settle = (fn) => {
|
|
@@ -39,12 +48,16 @@ function wireNavStack(root, options = {}) {
|
|
|
39
48
|
el.classList.add("kui-nav-stack__view--entering");
|
|
40
49
|
if (animated) {
|
|
41
50
|
void el.offsetWidth;
|
|
42
|
-
view.requestAnimationFrame(
|
|
51
|
+
view.requestAnimationFrame(
|
|
52
|
+
() => el.classList.remove("kui-nav-stack__view--entering")
|
|
53
|
+
);
|
|
43
54
|
} else {
|
|
44
55
|
el.classList.remove("kui-nav-stack__view--entering");
|
|
45
56
|
}
|
|
46
57
|
} else if (animated) {
|
|
47
|
-
view.requestAnimationFrame(
|
|
58
|
+
view.requestAnimationFrame(
|
|
59
|
+
() => el.classList.add("kui-nav-stack__view--exiting")
|
|
60
|
+
);
|
|
48
61
|
} else {
|
|
49
62
|
el.classList.add("kui-nav-stack__view--exiting");
|
|
50
63
|
}
|
|
@@ -54,10 +67,12 @@ function wireNavStack(root, options = {}) {
|
|
|
54
67
|
let added = false;
|
|
55
68
|
for (const record of records) {
|
|
56
69
|
record.removedNodes.forEach((node) => {
|
|
57
|
-
if (node instanceof HTMLElement && node.classList.contains("kui-nav-stack__view") && node.dataset.navExiting !== "true")
|
|
70
|
+
if (node instanceof HTMLElement && node.classList.contains("kui-nav-stack__view") && node.dataset.navExiting !== "true")
|
|
71
|
+
removed.push(node);
|
|
58
72
|
});
|
|
59
73
|
record.addedNodes.forEach((node) => {
|
|
60
|
-
if (node instanceof HTMLElement && node.classList.contains("kui-nav-stack__view"))
|
|
74
|
+
if (node instanceof HTMLElement && node.classList.contains("kui-nav-stack__view"))
|
|
75
|
+
added = true;
|
|
61
76
|
});
|
|
62
77
|
}
|
|
63
78
|
const top = topView(viewport);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wire-nav-stack.ts"],"names":[],"mappings":";;;AASA,IAAM,gBAAA,GAAmB,GAAA;AAEzB,SAAS,QAAQ,QAAA,EAAkC;AACjD,EAAA,OAAO,KAAA,CAAM,IAAA,
|
|
1
|
+
{"version":3,"sources":["../src/wire-nav-stack.ts"],"names":[],"mappings":"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","file":"wire-nav-stack.js","sourcesContent":["import { delegate } from 'kerfjs';\n\nexport interface WireNavStackOptions {\n /** Invoked when the back control is activated. The app pops its own stack. */\n onBack?: () => void;\n /** Transition duration in ms (default 200). Set 0 to disable animation. */\n duration?: number;\n}\n\nconst DEFAULT_DURATION = 200;\n\nfunction viewsOf(viewport: Element): HTMLElement[] {\n return Array.from(\n viewport.querySelectorAll<HTMLElement>(':scope > .kui-nav-stack__view'),\n );\n}\n\nfunction topView(viewport: Element): HTMLElement | undefined {\n const live = viewsOf(viewport).filter(\n (view) => view.dataset.navExiting !== 'true',\n );\n return live[live.length - 1];\n}\n\nfunction reducedMotion(view: Window): boolean {\n return (\n typeof view.matchMedia === 'function' &&\n view.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/**\n * Animate a `NavStack`'s push/pop transitions and wire its back control. The app\n * owns the stack (a signal of `NavStackView[]`) and re-renders `NavStack` when it\n * changes; this helper slides the content and settles the chrome across each\n * change, and calls `onBack` when the back control is used. Returns a disposer.\n */\nexport function wireNavStack(\n root: Element,\n options: WireNavStackOptions = {},\n): () => void {\n const section = root.matches('[data-component=\"nav-stack\"]')\n ? root\n : root.querySelector('[data-component=\"nav-stack\"]');\n const viewport = section?.querySelector('[data-nav-stack-viewport]');\n if (!(section instanceof HTMLElement) || !viewport) return () => {};\n\n const view = section.ownerDocument.defaultView ?? window;\n const duration = options.duration ?? DEFAULT_DURATION;\n const disposeBack = delegate(section, 'click', '[data-nav-back]', () =>\n options.onBack?.(),\n );\n\n let activeKey = topView(viewport)?.dataset.navKey;\n const timers = new Set<number>();\n\n const settle = (fn: () => void): void => {\n if (duration <= 0 || reducedMotion(view)) {\n fn();\n return;\n }\n const timer = view.setTimeout(() => {\n timers.delete(timer);\n fn();\n }, duration);\n timers.add(timer);\n };\n\n const play = (el: HTMLElement, kind: 'entering' | 'exiting'): void => {\n const animated = duration > 0 && !reducedMotion(view);\n if (kind === 'entering') {\n // Push: start the incoming view off the trailing edge, then release it so\n // it slides to rest (translateX(100%) → 0).\n el.classList.add('kui-nav-stack__view--entering');\n if (animated) {\n // Force a reflow so the starting transform applies before we clear it.\n void el.offsetWidth;\n view.requestAnimationFrame(() =>\n el.classList.remove('kui-nav-stack__view--entering'),\n );\n } else {\n el.classList.remove('kui-nav-stack__view--entering');\n }\n } else if (animated) {\n // Pop: the outgoing view is at rest; add the off-edge class on the next\n // frame so it slides OUT (translateX(0) → 100%), not in.\n view.requestAnimationFrame(() =>\n el.classList.add('kui-nav-stack__view--exiting'),\n );\n } else {\n el.classList.add('kui-nav-stack__view--exiting');\n }\n };\n\n const observer = new MutationObserver((records) => {\n const removed: HTMLElement[] = [];\n let added = false;\n for (const record of records) {\n record.removedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view') &&\n node.dataset.navExiting !== 'true'\n )\n removed.push(node);\n });\n record.addedNodes.forEach((node) => {\n if (\n node instanceof HTMLElement &&\n node.classList.contains('kui-nav-stack__view')\n )\n added = true;\n });\n }\n\n const top = topView(viewport);\n const currentKey = top?.dataset.navKey;\n const poppedTop = removed.find((node) => node.dataset.navKey === activeKey);\n\n if (poppedTop && currentKey !== activeKey) {\n // Pop: bring the removed node back briefly to slide it out over the revealed view.\n poppedTop.dataset.navExiting = 'true';\n poppedTop.setAttribute('aria-hidden', 'true');\n viewport.append(poppedTop);\n play(poppedTop, 'exiting');\n settle(() => poppedTop.remove());\n } else if (added && currentKey !== activeKey && top) {\n // Push: slide the new top in.\n play(top, 'entering');\n }\n\n activeKey = currentKey;\n });\n observer.observe(viewport, { childList: true });\n\n return () => {\n disposeBack();\n observer.disconnect();\n for (const timer of timers) view.clearTimeout(timer);\n timers.clear();\n };\n}\n"]}
|
|
@@ -10,6 +10,6 @@ interface WireResizableRegionsOptions {
|
|
|
10
10
|
onCommit: (change: ResizeCommit) => void;
|
|
11
11
|
}
|
|
12
12
|
/** Wire pointer and separator-keyboard behavior for every ResizableRegion below root. */
|
|
13
|
-
declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPreview, onCommit }: WireResizableRegionsOptions): () => void;
|
|
13
|
+
declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPreview, onCommit, }: WireResizableRegionsOptions): () => void;
|
|
14
14
|
|
|
15
15
|
export { type ResizeCommit, type WireResizableRegionsOptions, wireResizableRegions };
|
package/dist/wire-sidebar.js
CHANGED
|
@@ -20,36 +20,43 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
|
|
|
20
20
|
for (const panel of panels) {
|
|
21
21
|
if (!panel.storageKey || !storage) continue;
|
|
22
22
|
const stored = storage.getItem(panel.storageKey);
|
|
23
|
-
if (stored === "true" || stored === "false")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (stored === "true" || stored === "false")
|
|
24
|
+
panel.collapsed.value = stored === "true";
|
|
25
|
+
disposers.push(
|
|
26
|
+
effect(() => {
|
|
27
|
+
storage.setItem(panel.storageKey, String(panel.collapsed.value));
|
|
28
|
+
})
|
|
29
|
+
);
|
|
27
30
|
}
|
|
28
31
|
const panelElement = (id) => root.querySelector(`[data-collapsible-panel="${id}"]`);
|
|
29
32
|
const collapse = (panel) => {
|
|
30
33
|
if (panel.collapsed.value) return;
|
|
31
34
|
panel.collapsed.value = true;
|
|
32
35
|
};
|
|
33
|
-
disposers.push(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
disposers.push(
|
|
37
|
+
delegate(root, "click", "[data-action]", (_event, element) => {
|
|
38
|
+
const trigger = element;
|
|
39
|
+
const panel = byAction.get(trigger.dataset.action ?? "");
|
|
40
|
+
if (!panel) return;
|
|
41
|
+
returnFocus.set(panel.id, trigger);
|
|
42
|
+
panel.collapsed.value = !panel.collapsed.value;
|
|
43
|
+
})
|
|
44
|
+
);
|
|
40
45
|
for (const panel of panels) {
|
|
41
46
|
let previous = panel.collapsed.value;
|
|
42
|
-
disposers.push(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
disposers.push(
|
|
48
|
+
effect(() => {
|
|
49
|
+
const collapsed = panel.collapsed.value;
|
|
50
|
+
if (collapsed === previous) return;
|
|
51
|
+
previous = collapsed;
|
|
52
|
+
const element = panelElement(panel.id);
|
|
53
|
+
if (!collapsed && element) {
|
|
54
|
+
(focusables(element)[0] ?? element).focus();
|
|
55
|
+
} else if (collapsed) {
|
|
56
|
+
returnFocus.get(panel.id)?.focus();
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
);
|
|
53
60
|
}
|
|
54
61
|
if (deviceClass) {
|
|
55
62
|
let backdrop;
|
|
@@ -58,25 +65,27 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
|
|
|
58
65
|
backdrop?.remove();
|
|
59
66
|
backdrop = void 0;
|
|
60
67
|
};
|
|
61
|
-
disposers.push(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
disposers.push(
|
|
69
|
+
effect(() => {
|
|
70
|
+
const compact = deviceClass.value.compact;
|
|
71
|
+
const open = compact ? openPanel() : void 0;
|
|
72
|
+
root.dataset.collapsibleOverlay = String(compact);
|
|
73
|
+
if (compact && open) {
|
|
74
|
+
if (!backdrop) {
|
|
75
|
+
backdrop = ownerDocument.createElement("button");
|
|
76
|
+
backdrop.type = "button";
|
|
77
|
+
backdrop.className = "kui-collapsible-panel__backdrop";
|
|
78
|
+
backdrop.setAttribute("aria-label", "Close");
|
|
79
|
+
backdrop.addEventListener("click", () => collapse(open));
|
|
80
|
+
const element = panelElement(open.id);
|
|
81
|
+
element?.parentElement?.insertBefore(backdrop, element);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
removeBackdrop();
|
|
85
|
+
if (!compact) delete root.dataset.collapsibleOverlay;
|
|
74
86
|
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!compact) delete root.dataset.collapsibleOverlay;
|
|
78
|
-
}
|
|
79
|
-
}));
|
|
87
|
+
})
|
|
88
|
+
);
|
|
80
89
|
const onKeydown = (event) => {
|
|
81
90
|
if (!deviceClass.value.compact) return;
|
|
82
91
|
const open = openPanel();
|
|
@@ -103,7 +112,9 @@ function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() })
|
|
|
103
112
|
}
|
|
104
113
|
};
|
|
105
114
|
ownerDocument.addEventListener("keydown", onKeydown);
|
|
106
|
-
disposers.push(
|
|
115
|
+
disposers.push(
|
|
116
|
+
() => ownerDocument.removeEventListener("keydown", onKeydown)
|
|
117
|
+
);
|
|
107
118
|
disposers.push(removeBackdrop);
|
|
108
119
|
}
|
|
109
120
|
return () => {
|
package/dist/wire-sidebar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the collapsed state is loaded from and saved to `storage` under\n * this key (a persistence hook), so the panel remembers its state. */\n storageKey?: string;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.\n */\nexport function wireSidebar(root: HTMLElement, { panels, deviceClass, storage = defaultStorage() }: WireSidebarOptions): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n\n // Persistence: seed from storage, then mirror on change.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n const stored = storage.getItem(panel.storageKey);\n if (stored === 'true' || stored === 'false') panel.collapsed.value = stored === 'true';\n disposers.push(effect(() => {\n storage.setItem(panel.storageKey!, String(panel.collapsed.value));\n }));\n }\n\n const panelElement = (id: string): HTMLElement | null => root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n panel.collapsed.value = !panel.collapsed.value;\n }));\n\n // Focus: move into the panel when it opens; restore to the trigger when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n if (!collapsed && element) {\n (focusables(element)[0] ?? element).focus();\n } else if (collapsed) {\n returnFocus.get(panel.id)?.focus();\n }\n }));\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n const openPanel = (): WireSidebarPanel | undefined => panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n };\n disposers.push(effect(() => {\n const compact = deviceClass.value.compact;\n const open = compact ? openPanel() : undefined;\n root.dataset.collapsibleOverlay = String(compact);\n if (compact && open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n backdrop.addEventListener('click', () => collapse(open));\n const element = panelElement(open.id);\n element?.parentElement?.insertBefore(backdrop, element);\n }\n } else {\n removeBackdrop();\n if (!compact) delete root.dataset.collapsibleOverlay;\n }\n }));\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact) return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() => ownerDocument.removeEventListener('keydown', onKeydown));\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n delete root.dataset.collapsibleOverlay;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/wire-sidebar.ts"],"names":[],"mappings":"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","file":"wire-sidebar.js","sourcesContent":["import { delegate, effect, type ReadonlySignal, type Signal } from 'kerfjs';\n\nimport type { DeviceClass } from './device-class.js';\n\n/** Minimal `localStorage`-shaped store, so the persistence hook is testable. */\nexport interface SidebarStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\nexport interface WireSidebarPanel {\n /** The panel id — matches `CollapsiblePanel`'s `id` and a toggle's `panelId`. */\n id: string;\n /** The app-owned collapsed signal. `wireSidebar` reads it (focus, overlay) and\n * writes it (toggle, Escape, backdrop, persistence). */\n collapsed: Signal<boolean>;\n /** `data-action` value the panel's toggle button(s) carry. */\n toggleAction: string;\n /** When set, the collapsed state is loaded from and saved to `storage` under\n * this key (a persistence hook), so the panel remembers its state. */\n storageKey?: string;\n}\n\nexport interface WireSidebarOptions {\n panels: readonly WireSidebarPanel[];\n /**\n * When provided, the sidebar adopts a compact **overlay** presentation while\n * `deviceClass.compact` is true: an open panel floats over the content with a\n * dismissable backdrop, Escape and backdrop-click collapse it, and focus is\n * trapped within the open panel (the ARIA dialog pattern). Without it the panel\n * is always inline.\n */\n deviceClass?: ReadonlySignal<DeviceClass>;\n /** Persistence store (default `globalThis.localStorage`, if present). */\n storage?: SidebarStorage;\n}\n\nconst FOCUSABLE =\n 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nfunction focusables(panel: HTMLElement): HTMLElement[] {\n // Focus only ever targets an OPEN panel, so the selector (which already drops\n // disabled controls and tabindex=\"-1\") is sufficient without a layout-based\n // visibility check that a headless DOM can't answer.\n return [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];\n}\n\nfunction defaultStorage(): SidebarStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The reusable sidebar-semantics layer for {@link CollapsiblePanel}s: toggle\n * delegation with focus restore, focus-into on open, an optional compact overlay\n * (backdrop + Escape + focus trap) driven by {@link deviceClass}, and an optional\n * persistence hook. The app owns each `collapsed` signal and the layout; this wire\n * owns the interaction. Returns a disposer. See `docs/24-collapsible-panel.md`.\n */\nexport function wireSidebar(\n root: HTMLElement,\n { panels, deviceClass, storage = defaultStorage() }: WireSidebarOptions,\n): () => void {\n const ownerDocument = root.ownerDocument;\n const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));\n const returnFocus = new Map<string, HTMLElement>();\n const disposers: Array<() => void> = [];\n\n // Persistence: seed from storage, then mirror on change.\n for (const panel of panels) {\n if (!panel.storageKey || !storage) continue;\n const stored = storage.getItem(panel.storageKey);\n if (stored === 'true' || stored === 'false')\n panel.collapsed.value = stored === 'true';\n disposers.push(\n effect(() => {\n storage.setItem(panel.storageKey!, String(panel.collapsed.value));\n }),\n );\n }\n\n const panelElement = (id: string): HTMLElement | null =>\n root.querySelector<HTMLElement>(`[data-collapsible-panel=\"${id}\"]`);\n\n const collapse = (panel: WireSidebarPanel): void => {\n if (panel.collapsed.value) return;\n panel.collapsed.value = true;\n };\n\n // Toggle delegation. The trigger is remembered so focus restores to it on close.\n disposers.push(\n delegate(root, 'click', '[data-action]', (_event, element) => {\n const trigger = element as HTMLElement;\n const panel = byAction.get(trigger.dataset.action ?? '');\n if (!panel) return;\n returnFocus.set(panel.id, trigger);\n panel.collapsed.value = !panel.collapsed.value;\n }),\n );\n\n // Focus: move into the panel when it opens; restore to the trigger when it closes.\n for (const panel of panels) {\n let previous = panel.collapsed.value;\n disposers.push(\n effect(() => {\n const collapsed = panel.collapsed.value;\n if (collapsed === previous) return;\n previous = collapsed;\n const element = panelElement(panel.id);\n if (!collapsed && element) {\n (focusables(element)[0] ?? element).focus();\n } else if (collapsed) {\n returnFocus.get(panel.id)?.focus();\n }\n }),\n );\n }\n\n // Compact overlay: a dismissable backdrop + Escape + focus trap while compact\n // and a panel is open.\n if (deviceClass) {\n let backdrop: HTMLButtonElement | undefined;\n const openPanel = (): WireSidebarPanel | undefined =>\n panels.find((panel) => !panel.collapsed.value);\n const removeBackdrop = (): void => {\n backdrop?.remove();\n backdrop = undefined;\n };\n disposers.push(\n effect(() => {\n const compact = deviceClass.value.compact;\n const open = compact ? openPanel() : undefined;\n root.dataset.collapsibleOverlay = String(compact);\n if (compact && open) {\n if (!backdrop) {\n backdrop = ownerDocument.createElement('button');\n backdrop.type = 'button';\n backdrop.className = 'kui-collapsible-panel__backdrop';\n backdrop.setAttribute('aria-label', 'Close');\n backdrop.addEventListener('click', () => collapse(open));\n const element = panelElement(open.id);\n element?.parentElement?.insertBefore(backdrop, element);\n }\n } else {\n removeBackdrop();\n if (!compact) delete root.dataset.collapsibleOverlay;\n }\n }),\n );\n\n const onKeydown = (event: KeyboardEvent): void => {\n if (!deviceClass.value.compact) return;\n const open = openPanel();\n if (!open) return;\n if (event.key === 'Escape') {\n event.preventDefault();\n collapse(open);\n return;\n }\n if (event.key !== 'Tab') return;\n const element = panelElement(open.id);\n if (!element) return;\n const items = focusables(element);\n if (items.length === 0) return;\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const active = ownerDocument.activeElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n ownerDocument.addEventListener('keydown', onKeydown);\n disposers.push(() =>\n ownerDocument.removeEventListener('keydown', onKeydown),\n );\n disposers.push(removeBackdrop);\n }\n\n return () => {\n for (const dispose of disposers.splice(0)) dispose();\n delete root.dataset.collapsibleOverlay;\n };\n}\n"]}
|
package/dist/wire-tab-bars.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { TabActivation } from './tab-bar.js';
|
|
2
|
+
import 'kerfjs';
|
|
3
|
+
|
|
1
4
|
type TabReorderSource = 'pointer' | 'keyboard';
|
|
2
5
|
type TabDropPosition = 'before' | 'after';
|
|
3
6
|
interface TabReorder {
|
|
@@ -7,7 +10,7 @@ interface TabReorder {
|
|
|
7
10
|
position: TabDropPosition;
|
|
8
11
|
source: TabReorderSource;
|
|
9
12
|
}
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
interface WireTabBarsOptions {
|
|
12
15
|
onReorder: (change: TabReorder) => void;
|
|
13
16
|
/**
|
|
@@ -27,4 +30,4 @@ declare function reorderTabs<T>(items: readonly T[], getId: (item: T) => string,
|
|
|
27
30
|
/** Wire reordering and keyboard navigation while leaving controlled state in the application. */
|
|
28
31
|
declare function wireTabBars(root: HTMLElement | Document, { onReorder, activation }: WireTabBarsOptions): () => void;
|
|
29
32
|
|
|
30
|
-
export {
|
|
33
|
+
export { TabActivation, type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
|
package/dist/wire-tab-bars.js
CHANGED
|
@@ -5,10 +5,15 @@ function wireTabScaffold(root, options) {
|
|
|
5
5
|
const section = root.matches('[data-component="tab-scaffold"]') ? root : root.querySelector('[data-component="tab-scaffold"]');
|
|
6
6
|
if (!(section instanceof HTMLElement)) return () => {
|
|
7
7
|
};
|
|
8
|
-
return delegate(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
return delegate(
|
|
9
|
+
section,
|
|
10
|
+
"click",
|
|
11
|
+
"[data-tab-scaffold-tab]",
|
|
12
|
+
(_event, target) => {
|
|
13
|
+
const tabId = target.getAttribute("data-tab-scaffold-tab");
|
|
14
|
+
if (tabId) options.onSelect(tabId);
|
|
15
|
+
}
|
|
16
|
+
);
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
export { wireTabScaffold };
|
|
@@ -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"]}
|
|
@@ -29,21 +29,26 @@ interface TokenSearchTokenRemoval {
|
|
|
29
29
|
* for the caller to apply, while caret movement past a chip is a pure ephemeral
|
|
30
30
|
* mechanic the helper performs itself.
|
|
31
31
|
*/
|
|
32
|
-
interface
|
|
32
|
+
interface TokenSearchKeyboardBaseOptions {
|
|
33
33
|
/**
|
|
34
34
|
* From a collapsed caret with no selection, Backspace removes the token
|
|
35
35
|
* immediately before it and Delete the token immediately after — reported via
|
|
36
36
|
* `onRemoveToken` — instead of deleting a character. Default: true.
|
|
37
37
|
*/
|
|
38
|
-
removeAdjacentToken?: boolean;
|
|
39
38
|
/**
|
|
40
39
|
* ArrowRight moves the caret past a trailing atomic token so text typed next
|
|
41
40
|
* lands after the chip. Default: true.
|
|
42
41
|
*/
|
|
43
42
|
moveCaretPastToken?: boolean;
|
|
44
|
-
/** Apply the reported removal to your controlled state, then re-render. */
|
|
45
|
-
onRemoveToken?: (removal: TokenSearchTokenRemoval) => void;
|
|
46
43
|
}
|
|
44
|
+
type TokenSearchKeyboardOptions = TokenSearchKeyboardBaseOptions & ({
|
|
45
|
+
removeAdjacentToken?: true;
|
|
46
|
+
/** Apply the reported removal to your controlled state, then re-render. */
|
|
47
|
+
onRemoveToken: (removal: TokenSearchTokenRemoval) => void;
|
|
48
|
+
} | {
|
|
49
|
+
removeAdjacentToken: false;
|
|
50
|
+
onRemoveToken?: never;
|
|
51
|
+
});
|
|
47
52
|
/**
|
|
48
53
|
* Managed collapsible behavior for the iconic TokenSearchField. Every piece is on
|
|
49
54
|
* by default; disable a specific one to own it in the app. Provide `signals` to
|
|
@@ -76,8 +81,8 @@ interface WireTokenSearchFieldsOptions {
|
|
|
76
81
|
onEdit?: (edit: TokenSearchEdit) => void;
|
|
77
82
|
/** Managed collapsible transient behavior. `true`/omitted = on with defaults; `false` = fully off. */
|
|
78
83
|
collapsible?: boolean | TokenSearchCollapsibleOptions;
|
|
79
|
-
/** Opt-in atomic-chip keyboard behavior (off by default).
|
|
80
|
-
keyboard?:
|
|
84
|
+
/** Opt-in atomic-chip keyboard behavior (off by default). */
|
|
85
|
+
keyboard?: false | TokenSearchKeyboardOptions;
|
|
81
86
|
}
|
|
82
87
|
/**
|
|
83
88
|
* The value returned from {@link wireTokenSearchFields}: call it (or `dispose()`) to
|
|
@@ -101,6 +106,6 @@ interface TokenSearchFieldsHandle {
|
|
|
101
106
|
* expand/collapse/focus. Returns a {@link TokenSearchFieldsHandle} — a disposer that also
|
|
102
107
|
* exposes the managed `expanded` state per field id.
|
|
103
108
|
*/
|
|
104
|
-
declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
|
|
109
|
+
declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard, }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
|
|
105
110
|
|
|
106
111
|
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
|
|