@kerfjs/ui 4.4.1 → 4.5.0-beta.1
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 +425 -42
- 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 +102 -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 +319 -0
- package/ai/component-catalog-v2.d.ts +67 -0
- package/ai/component-catalog-v2.json +4675 -0
- package/ai/component-catalog-v2.schema.json +246 -0
- package/ai/component-catalog.json +5826 -0
- package/ai/component-catalog.schema.json +95 -0
- package/ai/public-api-signatures-v1.md +1719 -0
- package/ai/skill.md +129 -21
- package/ai/webawesome-jsx-signatures-v1.md +106 -0
- package/analyzer/cli.mjs +46 -0
- package/analyzer/index.d.mts +45 -0
- package/analyzer/index.mjs +737 -0
- package/analyzer/report.schema.json +66 -0
- package/dist/app-tab.d.ts +14 -2
- package/dist/app-tab.js +3 -1
- package/dist/browser/app-tab.js +3 -2
- package/dist/browser/disclosure-arrow.js +4 -0
- package/dist/browser/empty-state.js +3 -3
- package/dist/browser/floating-toolbar.js +3 -0
- package/dist/browser/list-action-row.js +4 -0
- package/dist/browser/list-header.js +6 -0
- package/dist/browser/list-inset-control.js +3 -0
- package/dist/browser/list-inset-text.js +3 -0
- package/dist/browser/list-item.js +4 -0
- package/dist/browser/list.js +3 -0
- package/dist/browser/loading-spinner.js +2 -2
- package/dist/browser/lucide-icon.js +2 -2
- package/dist/browser/pane.js +3 -0
- package/dist/browser/panel-header.js +7 -0
- package/dist/browser/resizable-region.js +2 -2
- package/dist/browser/segmented-control.js +3 -2
- package/dist/browser/select.js +4 -3
- package/dist/browser/skeleton.js +3 -0
- package/dist/browser/state-banner.js +3 -2
- package/dist/browser/sunken-panel.js +3 -0
- package/dist/browser/tab-bar.js +2 -2
- package/dist/browser/token-search-field.js +4 -0
- package/dist/browser/toolbar-control-group.js +2 -2
- package/dist/browser/toolbar-text.js +3 -2
- package/dist/browser/toolbar.js +2 -2
- package/dist/browser/value-table.js +3 -2
- 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 +150 -0
- package/dist/catalog.js +377 -0
- package/dist/catalog.js.map +1 -0
- 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-7XRDUG4Y.js +58 -0
- package/dist/chunk-7XRDUG4Y.js.map +1 -0
- package/dist/chunk-B3A3IVO5.js +83 -0
- package/dist/chunk-B3A3IVO5.js.map +1 -0
- package/dist/chunk-DPDBVKNA.js +113 -0
- package/dist/chunk-DPDBVKNA.js.map +1 -0
- package/dist/chunk-FEAMR7S4.js +68 -0
- package/dist/chunk-FEAMR7S4.js.map +1 -0
- package/dist/chunk-FEY65TBV.js +15 -0
- package/dist/chunk-FEY65TBV.js.map +1 -0
- package/dist/{chunk-TRNBHPNW.js → chunk-IJBSZ4NX.js} +11 -8
- package/dist/chunk-IJBSZ4NX.js.map +1 -0
- package/dist/chunk-J5BFYY7Q.js +35 -0
- package/dist/chunk-J5BFYY7Q.js.map +1 -0
- package/dist/chunk-JRKSK2HX.js +79 -0
- package/dist/chunk-JRKSK2HX.js.map +1 -0
- package/dist/chunk-KWWR5VMS.js +45 -0
- package/dist/chunk-KWWR5VMS.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-NWSEP5RT.js +117 -0
- package/dist/chunk-NWSEP5RT.js.map +1 -0
- package/dist/chunk-OJWWHFH3.js +426 -0
- package/dist/chunk-OJWWHFH3.js.map +1 -0
- package/dist/chunk-PSKN344N.js +114 -0
- package/dist/chunk-PSKN344N.js.map +1 -0
- package/dist/chunk-PX3YSS5C.js +32 -0
- package/dist/chunk-PX3YSS5C.js.map +1 -0
- package/dist/chunk-QO55FN2Y.js +20 -0
- package/dist/chunk-QO55FN2Y.js.map +1 -0
- package/dist/chunk-R7ZWUN64.js +38 -0
- package/dist/chunk-R7ZWUN64.js.map +1 -0
- package/dist/chunk-RKVQEH4J.js +61 -0
- package/dist/chunk-RKVQEH4J.js.map +1 -0
- package/dist/chunk-SRSJO5QE.js +40 -0
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-SYDMYBPG.js +31 -0
- package/dist/chunk-SYDMYBPG.js.map +1 -0
- package/dist/chunk-TB6DY7H5.js +25 -0
- package/dist/chunk-TB6DY7H5.js.map +1 -0
- package/dist/chunk-UFZKLBCF.js +108 -0
- package/dist/chunk-UFZKLBCF.js.map +1 -0
- package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
- package/dist/chunk-UKKSMZYT.js.map +1 -0
- package/dist/chunk-W5L2JSBY.js +40 -0
- package/dist/chunk-W5L2JSBY.js.map +1 -0
- package/dist/chunk-Y45PPBDV.js +105 -0
- package/dist/chunk-Y45PPBDV.js.map +1 -0
- package/dist/chunk-Y6LYKFXY.js +43 -0
- package/dist/chunk-Y6LYKFXY.js.map +1 -0
- package/dist/collapsible-panel.d.ts +66 -0
- package/dist/collapsible-panel.js +63 -0
- package/dist/collapsible-panel.js.map +1 -0
- package/dist/device-class.d.ts +62 -0
- package/dist/device-class.js +103 -0
- package/dist/device-class.js.map +1 -0
- package/dist/disclosure-arrow.d.ts +14 -0
- package/dist/disclosure-arrow.js +4 -0
- package/dist/disclosure-arrow.js.map +1 -0
- package/dist/divider-sides-267FA7sY.d.ts +4 -0
- package/dist/empty-state.d.ts +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/floating-toolbar.d.ts +28 -0
- package/dist/floating-toolbar.js +3 -0
- package/dist/floating-toolbar.js.map +1 -0
- package/dist/index.d.ts +20 -9
- package/dist/index.js +29 -18
- package/dist/list-action-row.d.ts +52 -0
- package/dist/list-action-row.js +5 -0
- package/dist/list-action-row.js.map +1 -0
- package/dist/list-header.d.ts +64 -0
- package/dist/list-header.js +7 -0
- package/dist/list-header.js.map +1 -0
- package/dist/list-inset-control.d.ts +16 -0
- package/dist/list-inset-control.js +3 -0
- package/dist/list-inset-control.js.map +1 -0
- package/dist/list-inset-text.d.ts +25 -0
- package/dist/list-inset-text.js +3 -0
- package/dist/list-inset-text.js.map +1 -0
- package/dist/list-item.d.ts +33 -0
- package/dist/list-item.js +5 -0
- package/dist/list-item.js.map +1 -0
- package/dist/list.d.ts +19 -0
- package/dist/list.js +3 -0
- package/dist/list.js.map +1 -0
- package/dist/loading-spinner.js +1 -1
- package/dist/nav-stack.d.ts +38 -0
- package/dist/nav-stack.js +5 -0
- package/dist/nav-stack.js.map +1 -0
- 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 +42 -0
- package/dist/panel-header.js +7 -0
- package/dist/panel-header.js.map +1 -0
- package/dist/resizable-region.d.ts +3 -1
- package/dist/resizable-region.js +1 -1
- package/dist/segmented-control.d.ts +8 -6
- package/dist/segmented-control.js +2 -1
- package/dist/select-register.d.ts +5 -1
- package/dist/select-register.js +3 -0
- package/dist/select-register.js.map +1 -1
- package/dist/select.d.ts +15 -6
- package/dist/select.js +2 -1
- package/dist/skeleton.d.ts +24 -0
- package/dist/skeleton.js +3 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/split-view.d.ts +42 -0
- package/dist/split-view.js +81 -0
- package/dist/split-view.js.map +1 -0
- package/dist/state-banner.d.ts +8 -3
- package/dist/state-banner.js +2 -1
- package/dist/styles/app-tab.css +152 -0
- package/dist/styles/catalog.css +575 -0
- package/dist/styles/collapsible-panel.css +146 -0
- package/dist/styles/disclosure-arrow.css +23 -0
- package/dist/styles/empty-state.css +47 -0
- package/dist/styles/floating-toolbar.css +38 -0
- package/dist/styles/foundation.css +362 -0
- package/dist/styles/layout.css +55 -0
- package/dist/styles/list-action-row.css +160 -0
- package/dist/styles/list-header.css +160 -0
- package/dist/styles/list-inset-control.css +7 -0
- package/dist/styles/list-inset-text.css +14 -0
- package/dist/styles/list-item.css +123 -0
- package/dist/styles/list.css +43 -0
- package/dist/styles/loading-spinner.css +21 -0
- package/dist/styles/lucide-icon.css +6 -0
- package/dist/styles/nav-stack.css +111 -0
- package/dist/styles/pane.css +62 -0
- package/dist/styles/panel-header.css +64 -0
- package/dist/styles/resizable-region.css +111 -0
- package/{src → dist/styles}/segmented-control.css +58 -31
- package/dist/styles/select.css +140 -0
- package/dist/styles/skeleton.css +31 -0
- package/dist/styles/split-view.css +35 -0
- package/dist/styles/state-banner.css +198 -0
- package/dist/styles/styles.css +24 -0
- package/dist/styles/sunken-panel.css +22 -0
- package/dist/styles/tab-bar.css +45 -0
- package/dist/styles/tab-scaffold.css +84 -0
- package/dist/styles/token-search-field.css +289 -0
- package/dist/styles/toolbar-control-group.css +225 -0
- package/dist/styles/toolbar-text.css +92 -0
- package/dist/styles/toolbar.css +97 -0
- package/dist/styles/value-table.css +75 -0
- package/dist/styles/webawesome.css +399 -0
- package/dist/styles/workbench.css +94 -0
- package/dist/sunken-panel.d.ts +19 -0
- package/dist/sunken-panel.js +3 -0
- package/dist/sunken-panel.js.map +1 -0
- package/dist/tab-bar.d.ts +10 -2
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +30 -0
- package/dist/tab-scaffold.js +51 -0
- package/dist/tab-scaffold.js.map +1 -0
- package/dist/token-search-field.d.ts +59 -0
- package/dist/token-search-field.js +4 -0
- package/dist/token-search-field.js.map +1 -0
- package/dist/toolbar-control-group.d.ts +11 -5
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +31 -4
- package/dist/toolbar-text.js +2 -1
- package/dist/toolbar.d.ts +5 -3
- package/dist/toolbar.js +1 -1
- package/dist/value-table.d.ts +13 -3
- package/dist/value-table.js +2 -1
- package/dist/webawesome.d.ts +100 -0
- package/dist/webawesome.js +3 -0
- package/dist/webawesome.js.map +1 -0
- package/dist/wire-catalog.d.ts +57 -0
- package/dist/wire-catalog.js +302 -0
- package/dist/wire-catalog.js.map +1 -0
- package/dist/wire-nav-stack.d.ts +15 -0
- package/dist/wire-nav-stack.js +103 -0
- package/dist/wire-nav-stack.js.map +1 -0
- package/dist/wire-resizable-regions.d.ts +1 -1
- package/dist/wire-resizable-regions.js +1 -1
- package/dist/wire-sidebar.d.ts +43 -0
- package/dist/wire-sidebar.js +128 -0
- package/dist/wire-sidebar.js.map +1 -0
- package/dist/wire-tab-bars.d.ts +18 -2
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.d.ts +11 -0
- package/dist/wire-tab-scaffold.js +21 -0
- package/dist/wire-tab-scaffold.js.map +1 -0
- package/dist/wire-token-search-fields.d.ts +111 -0
- package/dist/wire-token-search-fields.js +5 -0
- package/dist/wire-token-search-fields.js.map +1 -0
- package/dist/workbench.d.ts +33 -0
- package/dist/workbench.js +64 -0
- package/dist/workbench.js.map +1 -0
- package/docs/accessibility.md +124 -4
- package/docs/app-layouts.md +61 -0
- package/docs/catalog.md +364 -0
- package/docs/collapsible-panel.md +129 -0
- package/docs/component-contract.md +312 -5
- package/docs/component-selection.md +219 -0
- package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
- package/docs/design/templates/empty-state/filtered.svg +1 -0
- package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
- package/docs/design/templates/empty-state/with-action.svg +1 -0
- package/docs/design/templates/empty-state-dark.svg +1 -0
- package/docs/design/templates/empty-state.svg +1 -0
- package/docs/design/templates/list/compact-dark.svg +1 -0
- package/docs/design/templates/list/compact.svg +1 -0
- package/docs/design/templates/list/stack-dark.svg +1 -0
- package/docs/design/templates/list/stack.svg +1 -0
- package/docs/design/templates/list-action-row/default-dark.svg +1 -0
- package/docs/design/templates/list-action-row/default.svg +1 -0
- package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
- package/docs/design/templates/list-action-row/selected.svg +1 -0
- package/docs/design/templates/list-action-row-dark.svg +1 -0
- package/docs/design/templates/list-action-row.svg +1 -0
- package/docs/design/templates/list-dark.svg +1 -0
- package/docs/design/templates/list-header/action-dark.svg +1 -0
- package/docs/design/templates/list-header/action.svg +1 -0
- package/docs/design/templates/list-header/count-dark.svg +1 -0
- package/docs/design/templates/list-header/count.svg +1 -0
- package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
- package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
- package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
- package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
- package/docs/design/templates/list-header-dark.svg +1 -0
- package/docs/design/templates/list-header.svg +1 -0
- package/docs/design/templates/list-item/default-dark.svg +1 -0
- package/docs/design/templates/list-item/default.svg +1 -0
- package/docs/design/templates/list-item/multiline-dark.svg +1 -0
- package/docs/design/templates/list-item/multiline.svg +1 -0
- package/docs/design/templates/list-item/selected-dark.svg +1 -0
- package/docs/design/templates/list-item/selected.svg +1 -0
- package/docs/design/templates/list-item/trailing-dark.svg +1 -0
- package/docs/design/templates/list-item/trailing.svg +1 -0
- package/docs/design/templates/list-item-dark.svg +1 -0
- package/docs/design/templates/list-item.svg +1 -0
- package/docs/design/templates/list.svg +1 -0
- package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
- package/docs/design/templates/panel-header/icon-actions.svg +1 -0
- package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
- package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
- package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
- package/docs/design/templates/panel-header/no-icon.svg +1 -0
- package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
- package/docs/design/templates/panel-header/page-heading.svg +1 -0
- package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
- package/docs/design/templates/panel-header/title-only.svg +1 -0
- package/docs/design/templates/panel-header-dark.svg +1 -0
- package/docs/design/templates/panel-header.svg +1 -0
- package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
- package/docs/design/templates/segmented-control/equal.svg +1 -0
- package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
- package/docs/design/templates/segmented-control/pill-small.svg +1 -0
- package/docs/design/templates/segmented-control-dark.svg +1 -0
- package/docs/design/templates/segmented-control.svg +1 -0
- package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
- package/docs/design/templates/skeleton/avatar.svg +1 -0
- package/docs/design/templates/skeleton/block-dark.svg +1 -0
- package/docs/design/templates/skeleton/block.svg +1 -0
- package/docs/design/templates/skeleton/lines-dark.svg +1 -0
- package/docs/design/templates/skeleton/lines.svg +1 -0
- package/docs/design/templates/skeleton-dark.svg +1 -0
- package/docs/design/templates/skeleton.svg +1 -0
- package/docs/design/templates/state-banner/danger-dark.svg +1 -0
- package/docs/design/templates/state-banner/danger.svg +1 -0
- package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
- package/docs/design/templates/state-banner/info-badge.svg +1 -0
- package/docs/design/templates/state-banner/info-dark.svg +1 -0
- package/docs/design/templates/state-banner/info.svg +1 -0
- package/docs/design/templates/state-banner/success-dark.svg +1 -0
- package/docs/design/templates/state-banner/success.svg +1 -0
- package/docs/design/templates/state-banner/warning-dark.svg +1 -0
- package/docs/design/templates/state-banner/warning.svg +1 -0
- package/docs/design/templates/state-banner-dark.svg +1 -0
- package/docs/design/templates/state-banner.svg +1 -0
- package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
- package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
- package/docs/design/templates/tab-bar-dark.svg +1 -0
- package/docs/design/templates/tab-bar.svg +1 -0
- package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
- package/docs/design/templates/token-search-field/empty.svg +1 -0
- package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
- package/docs/design/templates/token-search-field/tokens.svg +1 -0
- package/docs/design/templates/token-search-field-dark.svg +1 -0
- package/docs/design/templates/token-search-field.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
- package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
- package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
- package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group.svg +1 -0
- package/docs/design/templates/toolbar-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/default.svg +1 -0
- package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/large.svg +1 -0
- package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/small.svg +1 -0
- package/docs/design/templates/toolbar-text-dark.svg +1 -0
- package/docs/design/templates/toolbar-text.svg +1 -0
- package/docs/design/templates/toolbar.svg +1 -0
- package/docs/design/templates/value-table/details-dark.svg +1 -0
- package/docs/design/templates/value-table/details.svg +1 -0
- package/docs/design/templates/value-table-dark.svg +1 -0
- package/docs/design/templates/value-table.svg +1 -0
- package/docs/design/templates.md +130 -0
- package/docs/design-philosophy.md +59 -1
- package/docs/device-class.md +54 -0
- package/docs/examples/application-ui-profile.json +60 -0
- package/docs/examples/command-palette-adapter.tsx +120 -0
- package/docs/examples/component-catalog-extension-v2.json +81 -0
- package/docs/examples/component-catalog-extension.json +45 -0
- package/docs/layout.md +150 -0
- package/docs/nav-stack.md +56 -0
- package/docs/recipes.md +167 -0
- package/docs/split-view.md +49 -0
- package/docs/sunken-panel.md +53 -0
- package/docs/tab-scaffold.md +50 -0
- package/docs/type-contracts.md +102 -0
- package/docs/ui-analyzer.md +86 -0
- package/docs/ui-doctor.md +84 -0
- package/docs/ui-evaluator.md +122 -0
- package/docs/ux-demo.md +23 -9
- package/docs/webawesome-theme.md +87 -7
- package/docs/workbench.md +73 -0
- package/doctor/cli.mjs +115 -0
- package/doctor/config.schema.json +68 -0
- package/doctor/eslint-diagnostics.d.mts +8 -0
- package/doctor/eslint-diagnostics.mjs +7 -0
- package/doctor/index.d.mts +116 -0
- package/doctor/index.mjs +1257 -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 +197 -14
- package/package.json +228 -46
- package/traversal-exclusions.mjs +27 -0
- package/ux-demo/recipes/app-shell.tsx +228 -0
- package/ux-demo/recipes/collapsible-sidebar.tsx +223 -0
- package/ux-demo/recipes/compact-toolbar.tsx +145 -0
- package/ux-demo/recipes/composer-form.tsx +151 -0
- package/ux-demo/recipes/list-detail-dialog.tsx +181 -0
- package/ux-demo/recipes/list-workspace-states.tsx +183 -0
- package/ux-demo/recipes/loaders.ts +36 -0
- package/ux-demo/recipes/loading-inspector.tsx +164 -0
- package/ux-demo/recipes/mount-recipe.ts +64 -0
- package/ux-demo/recipes/navigation-sidebar.tsx +137 -0
- package/ux-demo/recipes/navigation-stack.tsx +118 -0
- package/ux-demo/recipes/recipes.css +448 -0
- package/ux-demo/recipes/types.ts +19 -0
- package/ux-demo/recipes/workspace-header.tsx +67 -0
- package/dist/browser/dialog-header.js +0 -3
- package/dist/browser/menu-header.js +0 -3
- package/dist/browser/menu-item.js +0 -3
- package/dist/browser/page-header.js +0 -3
- package/dist/chunk-2PES33HS.js +0 -13
- package/dist/chunk-2PES33HS.js.map +0 -1
- package/dist/chunk-3O7ZEUW2.js +0 -27
- package/dist/chunk-3O7ZEUW2.js.map +0 -1
- package/dist/chunk-4HHCYSGT.js +0 -79
- package/dist/chunk-4HHCYSGT.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-723YNJBH.js +0 -17
- package/dist/chunk-723YNJBH.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-GY5WH7TO.js +0 -10
- package/dist/chunk-GY5WH7TO.js.map +0 -1
- package/dist/chunk-MBR3TYNL.js +0 -14
- package/dist/chunk-MBR3TYNL.js.map +0 -1
- package/dist/chunk-MFKMHK5W.js +0 -10
- package/dist/chunk-MFKMHK5W.js.map +0 -1
- package/dist/chunk-MQIZAD4A.js.map +0 -1
- package/dist/chunk-NHXX4JI5.js +0 -28
- package/dist/chunk-NHXX4JI5.js.map +0 -1
- package/dist/chunk-OPCJRCYO.js +0 -19
- package/dist/chunk-OPCJRCYO.js.map +0 -1
- package/dist/chunk-T5XNMVOI.js +0 -24
- package/dist/chunk-T5XNMVOI.js.map +0 -1
- package/dist/chunk-TRNBHPNW.js.map +0 -1
- package/dist/chunk-VHW5YNIB.js +0 -17
- package/dist/chunk-VHW5YNIB.js.map +0 -1
- package/dist/chunk-VKH2JLDZ.js +0 -10
- package/dist/chunk-VKH2JLDZ.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-WY4CAEPG.js +0 -14
- package/dist/chunk-WY4CAEPG.js.map +0 -1
- package/dist/dialog-header.d.ts +0 -14
- package/dist/dialog-header.js +0 -3
- package/dist/dialog-header.js.map +0 -1
- package/dist/menu-header.d.ts +0 -15
- package/dist/menu-header.js +0 -3
- package/dist/menu-header.js.map +0 -1
- package/dist/menu-item.d.ts +0 -22
- package/dist/menu-item.js +0 -3
- package/dist/menu-item.js.map +0 -1
- package/dist/page-header.d.ts +0 -9
- package/dist/page-header.js +0 -3
- package/dist/page-header.js.map +0 -1
- package/src/app-tab.css +0 -20
- package/src/dialog-header.css +0 -9
- package/src/empty-state.css +0 -6
- package/src/foundation.css +0 -106
- package/src/loading-spinner.css +0 -4
- package/src/lucide-icon.css +0 -1
- package/src/menu-header.css +0 -13
- package/src/menu-item.css +0 -12
- package/src/page-header.css +0 -3
- package/src/resizable-region.css +0 -19
- package/src/select.css +0 -18
- package/src/state-banner.css +0 -31
- package/src/styles.css +0 -18
- package/src/tab-bar.css +0 -4
- package/src/toolbar-control-group.css +0 -25
- package/src/toolbar-text.css +0 -4
- package/src/toolbar.css +0 -9
- package/src/value-table.css +0 -5
- package/src/webawesome.css +0 -178
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { effect, delegate } from 'kerfjs';
|
|
2
|
+
|
|
3
|
+
// src/wire-sidebar.ts
|
|
4
|
+
var FOCUSABLE = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
5
|
+
function focusables(panel) {
|
|
6
|
+
return [...panel.querySelectorAll(FOCUSABLE)];
|
|
7
|
+
}
|
|
8
|
+
function defaultStorage() {
|
|
9
|
+
try {
|
|
10
|
+
return globalThis.localStorage ?? void 0;
|
|
11
|
+
} catch {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function wireSidebar(root, { panels, deviceClass, storage = defaultStorage() }) {
|
|
16
|
+
const ownerDocument = root.ownerDocument;
|
|
17
|
+
const byAction = new Map(panels.map((panel) => [panel.toggleAction, panel]));
|
|
18
|
+
const returnFocus = /* @__PURE__ */ new Map();
|
|
19
|
+
const disposers = [];
|
|
20
|
+
for (const panel of panels) {
|
|
21
|
+
if (!panel.storageKey || !storage) continue;
|
|
22
|
+
const stored = storage.getItem(panel.storageKey);
|
|
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
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const panelElement = (id) => root.querySelector(`[data-collapsible-panel="${id}"]`);
|
|
32
|
+
const collapse = (panel) => {
|
|
33
|
+
if (panel.collapsed.value) return;
|
|
34
|
+
panel.collapsed.value = true;
|
|
35
|
+
};
|
|
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
|
+
);
|
|
45
|
+
for (const panel of panels) {
|
|
46
|
+
let previous = panel.collapsed.value;
|
|
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
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (deviceClass) {
|
|
62
|
+
let backdrop;
|
|
63
|
+
const openPanel = () => panels.find((panel) => !panel.collapsed.value);
|
|
64
|
+
const removeBackdrop = () => {
|
|
65
|
+
backdrop?.remove();
|
|
66
|
+
backdrop = void 0;
|
|
67
|
+
};
|
|
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;
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
const onKeydown = (event) => {
|
|
90
|
+
if (!deviceClass.value.compact) return;
|
|
91
|
+
const open = openPanel();
|
|
92
|
+
if (!open) return;
|
|
93
|
+
if (event.key === "Escape") {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
collapse(open);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (event.key !== "Tab") return;
|
|
99
|
+
const element = panelElement(open.id);
|
|
100
|
+
if (!element) return;
|
|
101
|
+
const items = focusables(element);
|
|
102
|
+
if (items.length === 0) return;
|
|
103
|
+
const first = items[0];
|
|
104
|
+
const last = items[items.length - 1];
|
|
105
|
+
const active = ownerDocument.activeElement;
|
|
106
|
+
if (event.shiftKey && active === first) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
last.focus();
|
|
109
|
+
} else if (!event.shiftKey && active === last) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
first.focus();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
ownerDocument.addEventListener("keydown", onKeydown);
|
|
115
|
+
disposers.push(
|
|
116
|
+
() => ownerDocument.removeEventListener("keydown", onKeydown)
|
|
117
|
+
);
|
|
118
|
+
disposers.push(removeBackdrop);
|
|
119
|
+
}
|
|
120
|
+
return () => {
|
|
121
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
122
|
+
delete root.dataset.collapsibleOverlay;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { wireSidebar };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=wire-sidebar.js.map
|
|
@@ -0,0 +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 =\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,11 +10,24 @@ interface TabReorder {
|
|
|
7
10
|
position: TabDropPosition;
|
|
8
11
|
source: TabReorderSource;
|
|
9
12
|
}
|
|
13
|
+
|
|
10
14
|
interface WireTabBarsOptions {
|
|
11
15
|
onReorder: (change: TabReorder) => void;
|
|
16
|
+
/**
|
|
17
|
+
* How arrow / Home / End keys activate tabs (default `'automatic'`):
|
|
18
|
+
* - `'automatic'` moves roving focus **and** selects the focused tab (clicks it).
|
|
19
|
+
* - `'manual'` moves roving focus only; the user selects with Enter / Space / click
|
|
20
|
+
* (the ARIA Tabs manual-activation pattern). Use this when activation is a heavy or
|
|
21
|
+
* side-effecting action (e.g. a tab that loads a project) so arrowing through the
|
|
22
|
+
* strip doesn't trigger it on every tab.
|
|
23
|
+
*
|
|
24
|
+
* A per-bar `data-tab-activation="manual" | "automatic"` attribute (see the `TabBar`
|
|
25
|
+
* `activation` prop) overrides this option for that strip.
|
|
26
|
+
*/
|
|
27
|
+
activation?: TabActivation;
|
|
12
28
|
}
|
|
13
29
|
declare function reorderTabs<T>(items: readonly T[], getId: (item: T) => string, sourceId: string, targetId: string, position: TabDropPosition): T[];
|
|
14
30
|
/** Wire reordering and keyboard navigation while leaving controlled state in the application. */
|
|
15
|
-
declare function wireTabBars(root: HTMLElement | Document, { onReorder }: WireTabBarsOptions): () => void;
|
|
31
|
+
declare function wireTabBars(root: HTMLElement | Document, { onReorder, activation }: WireTabBarsOptions): () => void;
|
|
16
32
|
|
|
17
|
-
export { type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
|
|
33
|
+
export { TabActivation, type TabDropPosition, type TabReorder, type TabReorderSource, type WireTabBarsOptions, reorderTabs, wireTabBars };
|
package/dist/wire-tab-bars.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface WireTabScaffoldOptions {
|
|
2
|
+
/** Invoked with the selected tab id when a bottom-bar tab is activated. */
|
|
3
|
+
onSelect: (tabId: string) => void;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wire a `TabScaffold`'s bottom tab bar: clicking a tab calls `onSelect` with its
|
|
7
|
+
* id (the app then updates its controlled `active`). Returns a disposer.
|
|
8
|
+
*/
|
|
9
|
+
declare function wireTabScaffold(root: Element, options: WireTabScaffoldOptions): () => void;
|
|
10
|
+
|
|
11
|
+
export { type WireTabScaffoldOptions, wireTabScaffold };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { delegate } from 'kerfjs';
|
|
2
|
+
|
|
3
|
+
// src/wire-tab-scaffold.ts
|
|
4
|
+
function wireTabScaffold(root, options) {
|
|
5
|
+
const section = root.matches('[data-component="tab-scaffold"]') ? root : root.querySelector('[data-component="tab-scaffold"]');
|
|
6
|
+
if (!(section instanceof HTMLElement)) return () => {
|
|
7
|
+
};
|
|
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
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { wireTabScaffold };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=wire-tab-scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Signal } from 'kerfjs';
|
|
2
|
+
|
|
3
|
+
interface TokenSearchSubmit {
|
|
4
|
+
id: string;
|
|
5
|
+
editor: HTMLElement;
|
|
6
|
+
}
|
|
7
|
+
/** Reported to {@link WireTokenSearchFieldsOptions.onEdit} on every editor input. */
|
|
8
|
+
interface TokenSearchEdit extends TokenSearchSubmit {
|
|
9
|
+
/**
|
|
10
|
+
* The `InputEvent` that mutated the editor — read `event.inputType` / `event.data`
|
|
11
|
+
* to gate commit behavior (e.g. only parse a chip on whitespace-terminated input)
|
|
12
|
+
* without keeping a separate `input` listener.
|
|
13
|
+
*/
|
|
14
|
+
event: InputEvent;
|
|
15
|
+
}
|
|
16
|
+
/** Reported when adjacent-token keyboard deletion asks the app to drop a chip. */
|
|
17
|
+
interface TokenSearchTokenRemoval {
|
|
18
|
+
id: string;
|
|
19
|
+
/** The `data-token-value` of the token the app should remove from its state. */
|
|
20
|
+
value: string;
|
|
21
|
+
editor: HTMLElement;
|
|
22
|
+
/** `'backward'` = the token before the caret (Backspace); `'forward'` = after (Delete). */
|
|
23
|
+
direction: 'backward' | 'forward';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Opt-in keyboard behavior for the atomic token chips. Off unless `keyboard` is
|
|
27
|
+
* set; each piece defaults on once opted in. The helper never mutates app state:
|
|
28
|
+
* a removal is reported through {@link TokenSearchKeyboardOptions.onRemoveToken}
|
|
29
|
+
* for the caller to apply, while caret movement past a chip is a pure ephemeral
|
|
30
|
+
* mechanic the helper performs itself.
|
|
31
|
+
*/
|
|
32
|
+
interface TokenSearchKeyboardBaseOptions {
|
|
33
|
+
/**
|
|
34
|
+
* From a collapsed caret with no selection, Backspace removes the token
|
|
35
|
+
* immediately before it and Delete the token immediately after — reported via
|
|
36
|
+
* `onRemoveToken` — instead of deleting a character. Default: true.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* ArrowRight moves the caret past a trailing atomic token so text typed next
|
|
40
|
+
* lands after the chip. Default: true.
|
|
41
|
+
*/
|
|
42
|
+
moveCaretPastToken?: boolean;
|
|
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
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Managed collapsible behavior for the iconic TokenSearchField. Every piece is on
|
|
54
|
+
* by default; disable a specific one to own it in the app. Provide `signals` to
|
|
55
|
+
* drive app-owned `expanded` signals per field id instead of helper-created ones.
|
|
56
|
+
*/
|
|
57
|
+
interface TokenSearchCollapsibleOptions {
|
|
58
|
+
/** Expand the field and focus its editor when the iconic trigger is activated. Default: true. */
|
|
59
|
+
expandOnActivate?: boolean;
|
|
60
|
+
/** Collapse the field when focus leaves it while it is empty. Default: true. */
|
|
61
|
+
collapseOnEmptyBlur?: boolean;
|
|
62
|
+
/** Collapse an empty field on Escape and restore focus to its trigger. Default: true. */
|
|
63
|
+
collapseOnEscape?: boolean;
|
|
64
|
+
/** Focus the editor on expand/controlled clear and the trigger on Escape-collapse. Default: true. */
|
|
65
|
+
manageFocus?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Keep an empty field expanded when focus moves to a caller-owned surface
|
|
68
|
+
* rendered outside the field — a suggestions dropdown, date picker, or help
|
|
69
|
+
* popover shown beside it. Return true for any focus target that must NOT
|
|
70
|
+
* trigger collapse-on-empty-blur. An element carrying `data-token-search-keep-open`
|
|
71
|
+
* (or any node inside one) is always exempt, so this predicate is only needed
|
|
72
|
+
* for surfaces you cannot mark declaratively.
|
|
73
|
+
*/
|
|
74
|
+
keepOpenOn?: (target: Node | null) => boolean;
|
|
75
|
+
/** App-owned `expanded` signals keyed by field id; adopted instead of helper-created. */
|
|
76
|
+
signals?: Readonly<Record<string, Signal<boolean>>>;
|
|
77
|
+
}
|
|
78
|
+
interface WireTokenSearchFieldsOptions {
|
|
79
|
+
onSubmit?: (submission: TokenSearchSubmit) => void;
|
|
80
|
+
/** Fired on every editor `input`, after the browser mutates it, so a caller can drop its own `input` listener. */
|
|
81
|
+
onEdit?: (edit: TokenSearchEdit) => void;
|
|
82
|
+
/** Managed collapsible transient behavior. `true`/omitted = on with defaults; `false` = fully off. */
|
|
83
|
+
collapsible?: boolean | TokenSearchCollapsibleOptions;
|
|
84
|
+
/** Opt-in atomic-chip keyboard behavior (off by default). */
|
|
85
|
+
keyboard?: false | TokenSearchKeyboardOptions;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The value returned from {@link wireTokenSearchFields}: call it (or `dispose()`) to
|
|
89
|
+
* tear down. When collapsible behavior is managed, it also exposes the transient
|
|
90
|
+
* `expanded` state per field id so the app can read it in render, hand in its own
|
|
91
|
+
* signal, or drive it imperatively.
|
|
92
|
+
*/
|
|
93
|
+
interface TokenSearchFieldsHandle {
|
|
94
|
+
(): void;
|
|
95
|
+
dispose(): void;
|
|
96
|
+
/** The managed `expanded` signal for a field id (adopted or helper-created); undefined when unmanaged. */
|
|
97
|
+
expanded(id: string): Signal<boolean> | undefined;
|
|
98
|
+
/** Expand the field (and, when focus is managed, focus its editor). */
|
|
99
|
+
open(id: string): void;
|
|
100
|
+
/** Collapse the field (and, when focus is managed, restore focus to its trigger). */
|
|
101
|
+
close(id: string): void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Wire every TokenSearchField under `root`: submit on Enter, preserve the caret across
|
|
105
|
+
* controlled token deletion, and (by default) manage the collapsible field's transient
|
|
106
|
+
* expand/collapse/focus. Returns a {@link TokenSearchFieldsHandle} — a disposer that also
|
|
107
|
+
* exposes the managed `expanded` state per field id.
|
|
108
|
+
*/
|
|
109
|
+
declare function wireTokenSearchFields(root: HTMLElement, { onSubmit, onEdit, collapsible, keyboard, }?: WireTokenSearchFieldsOptions): TokenSearchFieldsHandle;
|
|
110
|
+
|
|
111
|
+
export { type TokenSearchCollapsibleOptions, type TokenSearchEdit, type TokenSearchFieldsHandle, type TokenSearchKeyboardOptions, type TokenSearchSubmit, type TokenSearchTokenRemoval, type WireTokenSearchFieldsOptions, wireTokenSearchFields };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"wire-token-search-fields.js"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SafeHtml } from 'kerfjs';
|
|
2
|
+
|
|
3
|
+
/** A collapsible Workbench panel — a side rail or the bottom drawer. */
|
|
4
|
+
interface WorkbenchPanel {
|
|
5
|
+
content: SafeHtml;
|
|
6
|
+
/** Whether the panel is currently collapsed (the app owns this). */
|
|
7
|
+
collapsed?: boolean;
|
|
8
|
+
/** Rail width, or drawer height, in px. Overrides the CSS default. */
|
|
9
|
+
size?: number;
|
|
10
|
+
/** Accessible name for the panel region. */
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
interface WorkbenchProps {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
/** The central work area. */
|
|
17
|
+
main: SafeHtml;
|
|
18
|
+
leftRail?: WorkbenchPanel;
|
|
19
|
+
rightRail?: WorkbenchPanel;
|
|
20
|
+
bottomDrawer?: WorkbenchPanel;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The Xcode-like multi-panel workspace: a collapsible left rail, right rail, and
|
|
25
|
+
* bottom drawer around a central work area (any absent). Collapsing snaps the
|
|
26
|
+
* panel's track to zero in one reflow while its fixed-size content slides out via
|
|
27
|
+
* a composited transform — the instant-width / sliding-content technique, so the
|
|
28
|
+
* work area relayouts once, not per frame. The app owns each `collapsed` flag;
|
|
29
|
+
* the collapse is pure CSS (no wire). See `docs/23-app-layouts.md` §3.3.
|
|
30
|
+
*/
|
|
31
|
+
declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, }: WorkbenchProps): SafeHtml;
|
|
32
|
+
|
|
33
|
+
export { Workbench, type WorkbenchPanel, type WorkbenchProps };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'kerfjs/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/workbench.tsx
|
|
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
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { Workbench };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=workbench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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/accessibility.md
CHANGED
|
@@ -6,11 +6,32 @@ These are normative contracts for package components and consuming applications.
|
|
|
6
6
|
|
|
7
7
|
- All controls have accessible names and visible `:focus-visible` treatment.
|
|
8
8
|
- Decorative `LucideIcon` output is `aria-hidden`; pass `label` only when the SVG itself conveys meaning.
|
|
9
|
-
- `
|
|
9
|
+
- `ListItem` uses native button Enter/Space behavior. `selected` maps to `aria-current="page"`; `pressed` maps to `aria-pressed`. Selected rows keep the normal foreground over their brand-tinted fill so text retains WCAG AA contrast in light and dark themes. Its leading icon visual is 18px while the row retains its 44px minimum target; multiline icons align to the first text line. Its `trailing` content is dormant and must not contain controls. Its `rootAttributes` slot accepts only application `data-*` metadata and deliberately cannot emit `role="menuitem"` in isolation—a true ARIA menu must own arrow, Home/End, Escape, and focus behavior as one widget.
|
|
10
|
+
- `ListHeader` owns action naming, disabled state, and disclosure `aria-expanded`. Toggle mode supplies one decorative 18px `DisclosureArrow` when `actionIcon` is omitted; it mirrors `expanded` while the native button's accessible name stays stable. The application must update that controlled state and reveal or hide real content. A valid `count` is visually rendered in an `aria-hidden` neutral pill while the required localized `countLabel` becomes part of the owning heading or disclosure button's accessible name; zero remains a real count. Its narrow `triggerAttributes` slot may describe a native popover or controlled-content relationship with `popoverTarget`, `popoverTargetAction`, `aria-controls`, and `aria-haspopup`; the application owns the target surface and its focus/dismissal behavior.
|
|
11
|
+
- Dispose `wireTabBars` and `wireTokenSearchFields` with their owning view. Disposal cancels queued controlled-render focus restoration, so a torn-down view cannot reclaim focus from its successor.
|
|
12
|
+
- In any `.kui-pane`, menu rows, `ListHeader` actions, and toolbar groups keep a minimum 44px target in both dimensions. A `ListHeader` fills the available inline width and keeps that action at the logical end, including in RTL. Its action visual defaults to 18px through `--kui-list-header-action-icon-size`; do not reduce the target to the visible icon.
|
|
10
13
|
- `LoadingSpinner` is either labeled (`role="img"`) or hidden. Its rotation stops for reduced motion.
|
|
11
|
-
- `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure.
|
|
14
|
+
- `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
|
|
12
15
|
- `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
|
|
13
|
-
- `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone.
|
|
16
|
+
- `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone. Its decorative option icons and value-dependent selected content remain present after controlled rerenders, so the visible choice does not silently lose its non-text cue.
|
|
17
|
+
- `DisclosureArrow` is an 18px root-scaled decorative visual by default, not an interaction target. Its owning native control supplies a stable accessible name, pointer and keyboard interaction, and `aria-expanded`; multiple arrows keep independently controlled state. Consumers may override `--kui-disclosure-arrow-size` without changing that ownership. Configured directions animate over the shortest path; a 180-degree closed-to-open tie uses counterclockwise rotation.
|
|
18
|
+
- `List` is layout-only and adds no `list` role. Children own their native or ARIA semantics. Give a scrollable List a bounded block size, avoid nested scroll owners, and keep focused children visible while scrolling.
|
|
19
|
+
|
|
20
|
+
## ListActionRow
|
|
21
|
+
|
|
22
|
+
`ListActionRow` renders a noninteractive visual root containing primary and
|
|
23
|
+
trailing native buttons as siblings. The primary comes first in DOM and Tab
|
|
24
|
+
order and alone receives `aria-current` or `aria-pressed`; the trailing button
|
|
25
|
+
has its own required accessible name and disabled state. Both controls carry
|
|
26
|
+
the row's item id for application delegation, while the root deliberately has
|
|
27
|
+
no action or role. `label`, `icon`, and `trailingActionIcon` are dormant visual
|
|
28
|
+
content and must not contain buttons, links, interactive roles, or other
|
|
29
|
+
controls. A trailing click, double click, context menu, Enter, or Space
|
|
30
|
+
must never activate the primary control. Selected rows keep the normal
|
|
31
|
+
foreground over their brand-tinted fill so text retains WCAG AA contrast in
|
|
32
|
+
light and dark themes. The application owns controlled
|
|
33
|
+
selection and the lifecycle, focus, and dismissal policy of any related
|
|
34
|
+
popover or context menu.
|
|
14
35
|
|
|
15
36
|
## ResizableRegion
|
|
16
37
|
|
|
@@ -22,12 +43,43 @@ The handle exposes separator role, orientation, name, minimum, maximum, and curr
|
|
|
22
43
|
- primary-pointer drag with clamping;
|
|
23
44
|
- preview callbacks while dragging and one commit callback on release.
|
|
24
45
|
|
|
25
|
-
The application owns persistence and collapsed/expanded policy. Keep the last expanded size outside the component and restore it when reopening.
|
|
46
|
+
The application owns persistence and collapsed/expanded policy. Keep the last expanded size outside the component and restore it when reopening. An optional `handleIcon` replaces only decorative dormant content; it must not contain controls or interactive roles because the separator remains the sole focus and interaction owner.
|
|
47
|
+
|
|
48
|
+
## PanelHeader
|
|
49
|
+
|
|
50
|
+
`PanelHeader` is a plain `Toolbar` heading. Its leading zone holds an optional
|
|
51
|
+
icon (a normal bordered `ToolbarControlGroup`) and the title as extra-large
|
|
52
|
+
`ToolbarText`, and the app's trailing controls go straight into the trailing
|
|
53
|
+
zone. By default the title carries no native heading role, so for a **dialog or
|
|
54
|
+
panel** the application connects `titleId` and an optional `summaryId` to the
|
|
55
|
+
owning host through `aria-labelledby` and `aria-describedby`. For a **page or
|
|
56
|
+
view** title, pass `headingLevel` (usually `1`): the title then exposes
|
|
57
|
+
`role="heading"` with a matching `aria-level`, giving the view a heading landmark
|
|
58
|
+
so screen-reader heading navigation and "main heading" semantics work — the same
|
|
59
|
+
role/level pair `ToolbarText` exposes when it is given `headingLevel` directly.
|
|
60
|
+
Keep the levels meaningful and non-skipping within a view. Pass the trailing
|
|
61
|
+
controls as a labeled `ToolbarControlGroup` when that group needs an accessible
|
|
62
|
+
name.
|
|
63
|
+
|
|
64
|
+
## FloatingToolbar
|
|
65
|
+
|
|
66
|
+
`FloatingToolbar` is a `role="toolbar"` region with a **required** accessible
|
|
67
|
+
`label`; it floats over the content of its nearest positioned ancestor and is
|
|
68
|
+
forced to a dark color scheme, but it is **not** in the top layer, so it never
|
|
69
|
+
covers dialogs, popovers, or other overlays. Its children are the app's controls
|
|
70
|
+
(normally `ToolbarControlGroup`s), which keep their own names, focus, and
|
|
71
|
+
keyboard behavior; the application owns their actions, the toolbar's visibility,
|
|
72
|
+
and — via `position` and `--kui-floating-toolbar-inset` — where it sits.
|
|
26
73
|
|
|
27
74
|
## Tabs
|
|
28
75
|
|
|
29
76
|
`AppTab` renders one controlled tab. `TabBar` supplies the containing tab list,
|
|
30
77
|
fixed leading/trailing regions, and a horizontally scrollable strip.
|
|
78
|
+
`AppTab.rootAttributes` accepts application `data-*` metadata only; runtime
|
|
79
|
+
filtering rejects roles plus case variants of component- or wiring-owned
|
|
80
|
+
action, identity, selection, drag, drop, and component attributes. An optional
|
|
81
|
+
`closeIcon` is decorative dormant content inside the already named close button
|
|
82
|
+
and must not contain interactive descendants.
|
|
31
83
|
`wireTabBars()` adds Left/Right wrapping, Home/End, Delete/Backspace close
|
|
32
84
|
activation, same-bar pointer reordering, `Alt+Shift+ArrowLeft/ArrowRight`
|
|
33
85
|
reordering, focus restoration, scroll-into-view, and pointer-proximity
|
|
@@ -35,6 +87,16 @@ autoscroll at either horizontal edge. Edge autoscroll is direct manipulation,
|
|
|
35
87
|
stops on drop/drag end/disposal, and does not change keyboard behavior. It
|
|
36
88
|
returns a disposer.
|
|
37
89
|
|
|
90
|
+
Arrow / Home / End follow the ARIA Tabs **automatic-activation** pattern by
|
|
91
|
+
default: they move roving focus and select the focused tab. If selection synchronously
|
|
92
|
+
replaces the strip, focus returns to the replacement with the same bar and tab IDs. Pass
|
|
93
|
+
`activation: 'manual'` (or set `data-tab-activation="manual"` on a strip via the
|
|
94
|
+
`TabBar` `activation` prop, which overrides the option per bar) for
|
|
95
|
+
**manual activation**: arrow keys move roving focus only and the user selects
|
|
96
|
+
with Enter / Space (native on the tab button) or click. Use manual activation
|
|
97
|
+
when selecting a tab is a heavy or side-effecting action — e.g. a tab that loads
|
|
98
|
+
a project — so arrowing through the strip does not trigger it on every tab.
|
|
99
|
+
|
|
38
100
|
The application owns the ordered tab array, selection, panels, close policy,
|
|
39
101
|
routing, and persistence. On `onReorder`, synchronously render the reported
|
|
40
102
|
order so the helper can restore focus to the moved tab. Pair tabs with
|
|
@@ -51,6 +113,64 @@ re-renders. Keep labels unique and meaningful even when `content` shows only an
|
|
|
51
113
|
icon. Use tabs—not a segmented control—when choices switch page regions that
|
|
52
114
|
need `tab`/`tabpanel` semantics.
|
|
53
115
|
|
|
116
|
+
## TokenSearchField
|
|
117
|
+
|
|
118
|
+
`TokenSearchField` exposes the editable surface as a named `searchbox`. Each
|
|
119
|
+
chip is atomic (`contenteditable="false"`) and contains separately named edit
|
|
120
|
+
and remove buttons; the clear action is also named. Disabled fields publish
|
|
121
|
+
`aria-disabled` and stop editing without hiding the current expression. The
|
|
122
|
+
application owns query parsing and must announce result-count or loading
|
|
123
|
+
changes separately when that feedback is useful. Use `readTokenSearchField()`
|
|
124
|
+
to ignore the chip buttons' visible text when reading browser-edited content,
|
|
125
|
+
and `placeTokenSearchCaret()` to restore a text caret without landing inside a
|
|
126
|
+
chip. Call `wireTokenSearchFields()` once at a stable root so Enter submits
|
|
127
|
+
without inserting a contenteditable line break and keyboard chip deletion
|
|
128
|
+
restores focus plus the text-relative caret after controlled rendering replaces
|
|
129
|
+
the editor; the editor still wraps text visually at its inline edge. Editable text is DOM-owned between token changes; a clear handler empties
|
|
130
|
+
the editor's `textContent` before updating application state. Leading and
|
|
131
|
+
trailing controls share the first text line's fixed vertical center and remain
|
|
132
|
+
there as the editor wraps. In `collapsible` mode, the closed state is one named
|
|
133
|
+
iconic search button and the open state is the same named searchbox, whether
|
|
134
|
+
the field stands alone or is composed inside `ToolbarControlGroup`.
|
|
135
|
+
Applications set `expanded` while the field is focused, move focus from the
|
|
136
|
+
trigger into the revealed editor, and clear `expanded` only after focus leaves
|
|
137
|
+
the complete component. Text or tokens keep the field expanded even when that
|
|
138
|
+
transient state is false. Width animation is disabled under reduced motion.
|
|
139
|
+
|
|
140
|
+
When the field is composed with a caller-owned surface rendered outside it — a
|
|
141
|
+
suggestions listbox, date picker, or help popover — mark that surface (or wrap
|
|
142
|
+
it) with `data-token-search-keep-open`, or pass `collapsible.keepOpenOn(target)`,
|
|
143
|
+
so focus moving into it does not collapse an empty field. `wireTokenSearchFields`
|
|
144
|
+
can also, opt-in via `keyboard`, own atomic-chip editing keys: from a collapsed
|
|
145
|
+
caret with no selection, Backspace removes the token before the caret and Delete
|
|
146
|
+
the token after it (reported through `onRemoveToken` for the app to apply to its
|
|
147
|
+
controlled state), and ArrowRight moves the caret past a trailing chip so typed
|
|
148
|
+
text lands after it. An optional `onEdit({ id, editor, event })` — with the
|
|
149
|
+
originating `InputEvent` so a caller can gate on `inputType`/`data` — fires on every editor
|
|
150
|
+
`input`, letting a caller drop its own `input` listener; the application still
|
|
151
|
+
owns query parsing and result-count/loading announcements.
|
|
152
|
+
|
|
153
|
+
Managed clear captures the action before application handlers run, keeps the adopted
|
|
154
|
+
expanded signal open during editor replacement, and focuses the current editor after
|
|
155
|
+
rendering so typing can continue. The app still owns clearing query/tokens and emptying
|
|
156
|
+
DOM-owned text. This focus step respects `manageFocus: false`, disposal, removed fields,
|
|
157
|
+
and focus deliberately moved to another control; no app-level reopen callback is needed.
|
|
158
|
+
|
|
159
|
+
## CollapsiblePanel / sidebar
|
|
160
|
+
|
|
161
|
+
`CollapsiblePanel` is a labeled `aside` region that is `aria-hidden` while
|
|
162
|
+
collapsed; its content slides via transform (disabled under reduced motion).
|
|
163
|
+
`CollapsiblePanelToggle` is a named button carrying `aria-expanded` and the
|
|
164
|
+
standard per-side collapse/expand glyph (`PanelLeft*` / `PanelRight*` /
|
|
165
|
+
`PanelBottom*`); place a collapse toggle inside the panel and an expand toggle in
|
|
166
|
+
an always-visible location so it is reachable while collapsed. `wireSidebar()`
|
|
167
|
+
moves focus into the panel when it opens and restores it to the trigger when it
|
|
168
|
+
closes. When a `deviceClass()` reports `compact`, an open panel becomes an overlay
|
|
169
|
+
with a dismissable backdrop, Escape and backdrop-click collapse it, and Tab is
|
|
170
|
+
trapped within it (the ARIA dialog pattern). The application owns each `collapsed`
|
|
171
|
+
signal, the panels, their sizes, and content; the wire may persist the collapsed
|
|
172
|
+
state per panel.
|
|
173
|
+
|
|
54
174
|
## Verification matrix
|
|
55
175
|
|
|
56
176
|
For each changed component, inspect default, hover, focus, disabled, selected/pressed, busy/error, long-content, wide, narrow, light, dark, increased-contrast, reduced-motion, keyboard-only, and 200%-zoom states where applicable. DOM order must match reading and focus order, with no clipping or unreachable action.
|