@kerfjs/ui 5.0.0-beta.97 → 5.0.0-beta.99
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 +42 -1
- package/ai/application-ui-diagnostic-ids-v1.json +1 -0
- package/ai/component-catalog.json +487 -66
- package/ai/component-catalog.schema.json +1 -1
- package/ai/component-composition-rules.json +22 -2
- package/ai/component-composition.json +360 -33
- package/ai/component-composition.schema.json +1 -1
- package/ai/components/README.md +18 -17
- package/ai/components/anchored-surface.md +13 -0
- package/ai/components/application-tabs.md +1 -1
- package/ai/components/chip.md +9 -5
- package/ai/components/choice-chips.md +15 -8
- package/ai/components/collapsible-panel.md +3 -2
- package/ai/components/composer.md +83 -0
- package/ai/components/content-item.md +2 -2
- package/ai/components/corner-status.md +1 -1
- package/ai/components/document-baseline.md +3 -3
- package/ai/components/foundation.md +2 -2
- package/ai/components/layout.md +1 -1
- package/ai/components/list-action-row.md +5 -1
- package/ai/components/list-item.md +5 -1
- package/ai/components/list.md +19 -3
- package/ai/components/nav-stack.md +11 -2
- package/ai/components/pane.md +13 -4
- package/ai/components/popup-menu.md +4 -4
- package/ai/components/recipe-app-shell.md +3 -3
- package/ai/components/recipe-collapsible-sidebar.md +3 -3
- package/ai/components/recipe-compact-toolbar.md +1 -1
- package/ai/components/recipe-composer-form.md +3 -3
- package/ai/components/recipe-list-detail-dialog.md +2 -2
- package/ai/components/recipe-list-workspace-states.md +3 -3
- package/ai/components/recipe-loading-inspector.md +1 -1
- package/ai/components/recipe-navigation-sidebar.md +3 -3
- package/ai/components/recipe-navigation-stack.md +3 -3
- package/ai/components/recipe-workspace-header.md +3 -3
- package/ai/components/resize.md +2 -2
- package/ai/components/split-view.md +4 -2
- package/ai/components/sunken-panel.md +2 -2
- package/ai/components/surface-scaffold.md +15 -3
- package/ai/components/tab-bar.md +1 -0
- package/ai/components/tab-navigator.md +4 -0
- package/ai/components/toolbar-control-group.md +2 -0
- package/ai/components/toolbar-text.md +1 -0
- package/ai/components/toolbar.md +1 -0
- package/ai/components/workbench.md +1 -0
- package/ai/public-api-signatures-v1.md +504 -80
- package/ai/skill.md +41 -14
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/component-ownership.mjs +1 -1
- package/analyzer/index.mjs +41 -14
- package/dist/anchored-surface.d.ts +30 -2
- package/dist/anchored-surface.js +1 -1
- package/dist/app-tab.js +1 -1
- package/dist/browser/composer.js +4 -0
- package/dist/catalog-resources.d.ts +3 -2
- package/dist/catalog-resources.js +4 -4
- package/dist/catalog-resources.js.map +1 -1
- package/dist/catalog.d.ts +39 -8
- package/dist/catalog.js +55 -32
- package/dist/catalog.js.map +1 -1
- package/dist/chip.d.ts +41 -9
- package/dist/chip.js +1 -1
- package/dist/choice-chips.d.ts +40 -16
- package/dist/choice-chips.js +1 -1
- package/dist/chunk-34U4ANDJ.js +119 -0
- package/dist/chunk-34U4ANDJ.js.map +1 -0
- package/dist/{chunk-36TL6BCR.js → chunk-3GLYIIFR.js} +34 -6
- package/dist/chunk-3GLYIIFR.js.map +1 -0
- package/dist/{chunk-HWY4BSLS.js → chunk-3VXDSIBJ.js} +2 -2
- package/dist/{chunk-HWY4BSLS.js.map → chunk-3VXDSIBJ.js.map} +1 -1
- package/dist/chunk-3WYREXW4.js +120 -0
- package/dist/chunk-3WYREXW4.js.map +1 -0
- package/dist/chunk-AV7CPAPT.js +82 -0
- package/dist/chunk-AV7CPAPT.js.map +1 -0
- package/dist/{chunk-XK4E225T.js → chunk-B53PS2LE.js} +2 -2
- package/dist/{chunk-XK4E225T.js.map → chunk-B53PS2LE.js.map} +1 -1
- package/dist/{chunk-R42D2BAO.js → chunk-CA7LNX3J.js} +2 -2
- package/dist/{chunk-R42D2BAO.js.map → chunk-CA7LNX3J.js.map} +1 -1
- package/dist/{chunk-AYHC62OZ.js → chunk-CMETHPKR.js} +26 -29
- package/dist/chunk-CMETHPKR.js.map +1 -0
- package/dist/{chunk-Y43B6DS3.js → chunk-CVD5IZGB.js} +2 -2
- package/dist/{chunk-Y43B6DS3.js.map → chunk-CVD5IZGB.js.map} +1 -1
- package/dist/{chunk-6STQXRT4.js → chunk-CXZXHZTR.js} +121 -12
- package/dist/chunk-CXZXHZTR.js.map +1 -0
- package/dist/{chunk-GZE2XVKC.js → chunk-DZJ4GBSP.js} +3 -3
- package/dist/{chunk-GZE2XVKC.js.map → chunk-DZJ4GBSP.js.map} +1 -1
- package/dist/chunk-ELCMSQNJ.js +49 -0
- package/dist/chunk-ELCMSQNJ.js.map +1 -0
- package/dist/{chunk-P74VXMB6.js → chunk-EOO6PYGF.js} +7 -14
- package/dist/chunk-EOO6PYGF.js.map +1 -0
- package/dist/{chunk-PRG4CL3Q.js → chunk-FR76ZJQC.js} +21 -19
- package/dist/chunk-FR76ZJQC.js.map +1 -0
- package/dist/{chunk-QQ3TTWFH.js → chunk-G3J7JDP6.js} +1 -1
- package/dist/{chunk-QQ3TTWFH.js.map → chunk-G3J7JDP6.js.map} +1 -1
- package/dist/{chunk-IGTWXPTN.js → chunk-HGV7J4T6.js} +3 -3
- package/dist/{chunk-IGTWXPTN.js.map → chunk-HGV7J4T6.js.map} +1 -1
- package/dist/{chunk-YWQGSUF2.js → chunk-HLDRH23D.js} +3 -3
- package/dist/{chunk-YWQGSUF2.js.map → chunk-HLDRH23D.js.map} +1 -1
- package/dist/{chunk-PBT3CPPY.js → chunk-HQZYKJ56.js} +24 -4
- package/dist/chunk-HQZYKJ56.js.map +1 -0
- package/dist/{chunk-V6W56V3N.js → chunk-HZPHCSCS.js} +16 -3
- package/dist/chunk-HZPHCSCS.js.map +1 -0
- package/dist/{chunk-GJYQ27C2.js → chunk-INI4WDWC.js} +8 -1
- package/dist/chunk-INI4WDWC.js.map +1 -0
- package/dist/{chunk-U657IHFB.js → chunk-IQSRHXZR.js} +10 -4
- package/dist/chunk-IQSRHXZR.js.map +1 -0
- package/dist/{chunk-KQDU7Z7P.js → chunk-K4AT3V3J.js} +2 -1
- package/dist/chunk-K4AT3V3J.js.map +1 -0
- package/dist/{chunk-QIAPOWHH.js → chunk-KCS6NJ3O.js} +2 -2
- package/dist/{chunk-QIAPOWHH.js.map → chunk-KCS6NJ3O.js.map} +1 -1
- package/dist/{chunk-UKKRPP56.js → chunk-LOSRXZAH.js} +2 -2
- package/dist/{chunk-UKKRPP56.js.map → chunk-LOSRXZAH.js.map} +1 -1
- package/dist/{chunk-ROZLJULF.js → chunk-MF6ZUUJZ.js} +8 -3
- package/dist/chunk-MF6ZUUJZ.js.map +1 -0
- package/dist/{chunk-6IVNMC6R.js → chunk-MHMWQ7RB.js} +2 -2
- package/dist/{chunk-6IVNMC6R.js.map → chunk-MHMWQ7RB.js.map} +1 -1
- package/dist/{chunk-JYD7G4RG.js → chunk-PIVY32AE.js} +15 -5
- package/dist/chunk-PIVY32AE.js.map +1 -0
- package/dist/{chunk-BU23Z5HZ.js → chunk-PYBBYSZ3.js} +13 -18
- package/dist/chunk-PYBBYSZ3.js.map +1 -0
- package/dist/chunk-QDSZTQXL.js +28 -0
- package/dist/chunk-QDSZTQXL.js.map +1 -0
- package/dist/{chunk-YXOMI7ES.js → chunk-QIG4IGUP.js} +2 -2
- package/dist/{chunk-YXOMI7ES.js.map → chunk-QIG4IGUP.js.map} +1 -1
- package/dist/{chunk-6BRRUPJY.js → chunk-QROYYGWU.js} +3 -3
- package/dist/chunk-QROYYGWU.js.map +1 -0
- package/dist/{chunk-FOQK4F3L.js → chunk-RHD7RKGI.js} +28 -7
- package/dist/chunk-RHD7RKGI.js.map +1 -0
- package/dist/{chunk-BBU2SPRE.js → chunk-T4WJ5VGP.js} +4 -4
- package/dist/{chunk-BBU2SPRE.js.map → chunk-T4WJ5VGP.js.map} +1 -1
- package/dist/{chunk-6NOLL5KG.js → chunk-TGLUDQP2.js} +3 -3
- package/dist/{chunk-6NOLL5KG.js.map → chunk-TGLUDQP2.js.map} +1 -1
- package/dist/{chunk-HO6FU3G7.js → chunk-TLMMFQWM.js} +4 -4
- package/dist/{chunk-HO6FU3G7.js.map → chunk-TLMMFQWM.js.map} +1 -1
- package/dist/chunk-UMKV7YVQ.js +20 -0
- package/dist/chunk-UMKV7YVQ.js.map +1 -0
- package/dist/{chunk-M2NRCFRO.js → chunk-VFYO3UGZ.js} +2 -2
- package/dist/{chunk-M2NRCFRO.js.map → chunk-VFYO3UGZ.js.map} +1 -1
- package/dist/{chunk-YOKEL75M.js → chunk-WVS6CUCT.js} +2 -2
- package/dist/{chunk-YOKEL75M.js.map → chunk-WVS6CUCT.js.map} +1 -1
- package/dist/{chunk-MN3POJFN.js → chunk-YMUIQRWK.js} +2 -2
- package/dist/{chunk-MN3POJFN.js.map → chunk-YMUIQRWK.js.map} +1 -1
- package/dist/{chunk-Y3LC7JQO.js → chunk-YOA5W5ZH.js} +2 -2
- package/dist/{chunk-Y3LC7JQO.js.map → chunk-YOA5W5ZH.js.map} +1 -1
- package/dist/{chunk-44A6LP54.js → chunk-YQ2AC4E2.js} +2 -2
- package/dist/{chunk-44A6LP54.js.map → chunk-YQ2AC4E2.js.map} +1 -1
- package/dist/chunk-ZNROKDSZ.js +54 -0
- package/dist/chunk-ZNROKDSZ.js.map +1 -0
- package/dist/collapsible-panel.d.ts +3 -2
- package/dist/collapsible-panel.js +4 -3
- package/dist/collapsible-panel.js.map +1 -1
- package/dist/composer.d.ts +62 -0
- package/dist/composer.js +5 -0
- package/dist/composer.js.map +1 -0
- package/dist/corner-status.d.ts +1 -1
- package/dist/corner-status.js +1 -1
- package/dist/css-values.d.ts +9 -1
- package/dist/css-values.js +1 -1
- package/dist/disclosure-arrow.js +1 -1
- package/dist/empty-state.js +1 -1
- package/dist/grid.js +1 -1
- package/dist/icon-button.js +1 -1
- package/dist/icon-toggle-button.js +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +40 -37
- package/dist/list-action-row.d.ts +10 -2
- package/dist/list-action-row.js +1 -1
- package/dist/list-header.js +1 -1
- package/dist/list-icon-tile-B8dr97OW.d.ts +18 -0
- package/dist/list-item.d.ts +10 -3
- package/dist/list-item.js +1 -1
- package/dist/list.d.ts +36 -4
- package/dist/list.js +1 -1
- package/dist/loading-spinner.js +1 -1
- package/dist/lucide-icon.js +1 -1
- package/dist/{nav-stack-BvSKY448.d.ts → nav-stack-VXTumzKa.d.ts} +53 -4
- package/dist/nav-stack.d.ts +3 -2
- package/dist/nav-stack.js +2 -1
- package/dist/pane.d.ts +40 -3
- package/dist/pane.js +1 -1
- package/dist/{panel-toolbar-CL9tYOkh.d.ts → panel-toolbar-vg6Ir5Dy.d.ts} +6 -1
- package/dist/popup-menu-register.d.ts +2 -1
- package/dist/popup-menu-register.js +97 -22
- package/dist/popup-menu-register.js.map +1 -1
- package/dist/popup-menu.js +1 -1
- package/dist/row.js +1 -1
- package/dist/scroll-clearance-BikHljTc.d.ts +14 -0
- package/dist/select-register.js +11 -0
- package/dist/select-register.js.map +1 -1
- package/dist/select.js +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/spacer.js +1 -1
- package/dist/split-button.js +1 -1
- package/dist/split-view.d.ts +7 -5
- package/dist/split-view.js +6 -3
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +6 -1
- package/dist/state-banner.js +1 -1
- package/dist/styles/anchored-surface.css +27 -1
- package/dist/styles/catalog/components/catalog-example-stack.css +5 -0
- package/dist/styles/catalog/components/catalog-example.css +33 -0
- package/dist/styles/catalog/components/catalog-stage.css +7 -0
- package/dist/styles/chip.css +70 -0
- package/dist/styles/components/actions/popup-menu/popup-menu.css +21 -4
- package/dist/styles/components/actions/toolbar/toolbar.css +19 -0
- package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +75 -1
- package/dist/styles/components/actions/toolbar-text/toolbar-text.css +75 -0
- package/dist/styles/components/collections/list/list.css +77 -7
- package/dist/styles/components/collections/list-action-row/list-action-row.css +177 -22
- package/dist/styles/components/collections/list-item/list-item.css +158 -11
- package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +27 -1
- package/dist/styles/components/feedback/chip/chip.css +70 -0
- package/dist/styles/components/feedback/state-banner/state-banner.css +24 -13
- package/dist/styles/components/forms/composer/composer.css +100 -0
- package/dist/styles/components/forms/select/select.css +1 -0
- package/dist/styles/components/forms/token-search-field/token-search-field.css +13 -2
- package/dist/styles/components/layout/pane/pane.css +81 -2
- package/dist/styles/components/navigation/nav-stack/nav-stack.css +84 -0
- package/dist/styles/components/navigation/tab-bar/tab-bar.css +15 -4
- package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +79 -4
- package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +195 -0
- package/dist/styles/composer.css +100 -0
- package/dist/styles/foundation.css +66 -0
- package/dist/styles/list-action-row.css +177 -22
- package/dist/styles/list-item.css +158 -11
- package/dist/styles/list.css +77 -7
- package/dist/styles/nav-stack.css +84 -0
- package/dist/styles/pane.css +81 -2
- package/dist/styles/popup-menu.css +21 -4
- package/dist/styles/select.css +1 -0
- package/dist/styles/state-banner.css +24 -13
- package/dist/styles/styles.css +1 -0
- package/dist/styles/surface-scaffold.css +195 -0
- package/dist/styles/tab-bar.css +15 -4
- package/dist/styles/tab-navigator.css +79 -4
- package/dist/styles/token-search-field.css +13 -2
- package/dist/styles/toolbar-control-group.css +75 -1
- package/dist/styles/toolbar-text.css +75 -0
- package/dist/styles/toolbar.css +19 -0
- package/dist/styles/webawesome.css +18 -0
- package/dist/surface-scaffold-register.d.ts +6 -1
- package/dist/surface-scaffold-register.js +89 -3
- package/dist/surface-scaffold-register.js.map +1 -1
- package/dist/surface-scaffold.d.ts +66 -7
- package/dist/surface-scaffold.js +1 -1
- package/dist/tab-navigator.d.ts +23 -2
- package/dist/tab-navigator.js +2 -0
- package/dist/tab-navigator.js.map +1 -1
- package/dist/token-search-field.d.ts +1 -1
- package/dist/token-search-field.js +1 -1
- package/dist/{token-search-model-DTcTXWMS.d.ts → token-search-model-aYR0YAei.d.ts} +32 -2
- package/dist/token-search-model.d.ts +1 -1
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.js +1 -1
- package/dist/toolbar.d.ts +1 -0
- package/dist/value-table.js +1 -1
- package/dist/wire-catalog.d.ts +3 -2
- package/dist/wire-catalog.js +2 -2
- package/dist/wire-composers.d.ts +16 -0
- package/dist/wire-composers.js +3 -0
- package/dist/wire-composers.js.map +1 -0
- package/dist/wire-list-focus-scroll.d.ts +16 -0
- package/dist/wire-list-focus-scroll.js +3 -0
- package/dist/wire-list-focus-scroll.js.map +1 -0
- package/dist/wire-nav-stack.js +135 -0
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-scroll-dividers.d.ts +9 -0
- package/dist/wire-scroll-dividers.js +1 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-navigator.d.ts +3 -1
- package/dist/wire-tab-navigator.js +133 -1
- package/dist/wire-tab-navigator.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +1 -1
- package/dist/wire-token-search-fields.js +1 -1
- package/dist/workbench.d.ts +8 -3
- package/dist/workbench.js +2 -1
- package/docs/accessibility.md +132 -3
- package/docs/anchored-surface.md +46 -4
- package/docs/app-layouts.md +23 -2
- package/docs/apple-hig.md +1 -0
- package/docs/catalog.md +61 -9
- package/docs/collapsible-panel.md +9 -6
- package/docs/component-contract.md +46 -13
- package/docs/component-selection.md +147 -20
- package/docs/composer.md +143 -0
- package/docs/design/templates/choice-chips/multiple-dark.svg +1 -0
- package/docs/design/templates/choice-chips/multiple.svg +1 -0
- package/docs/design/templates/choice-chips-dark.svg +1 -1
- package/docs/design/templates/choice-chips.svg +1 -1
- package/docs/design/templates/composer/chat-dark.svg +1 -0
- package/docs/design/templates/composer/chat.svg +1 -0
- package/docs/design/templates/composer/prompt-dark.svg +1 -0
- package/docs/design/templates/composer/prompt.svg +1 -0
- package/docs/design/templates/composer-dark.svg +1 -0
- package/docs/design/templates/composer.svg +1 -0
- package/docs/design/templates/empty-state/filtered-dark.svg +1 -1
- package/docs/design/templates/empty-state/filtered.svg +1 -1
- package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
- package/docs/design/templates/empty-state/with-action.svg +1 -1
- package/docs/design/templates/empty-state-dark.svg +1 -1
- package/docs/design/templates/empty-state.svg +1 -1
- package/docs/design/templates/list/compact-dark.svg +1 -1
- package/docs/design/templates/list/compact.svg +1 -1
- package/docs/design/templates/list-action-row/default-dark.svg +1 -1
- package/docs/design/templates/list-action-row/default.svg +1 -1
- package/docs/design/templates/list-action-row/icon-tile-dark.svg +1 -0
- package/docs/design/templates/list-action-row/icon-tile.svg +1 -0
- package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
- package/docs/design/templates/list-action-row/selected.svg +1 -1
- package/docs/design/templates/list-action-row/trailing-menu-dark.svg +1 -1
- package/docs/design/templates/list-action-row/trailing-menu.svg +1 -1
- package/docs/design/templates/list-action-row-dark.svg +1 -1
- package/docs/design/templates/list-action-row.svg +1 -1
- package/docs/design/templates/list-dark.svg +1 -1
- package/docs/design/templates/list-header/size-l-dark.svg +1 -1
- package/docs/design/templates/list-header/size-l.svg +1 -1
- package/docs/design/templates/list-header/size-m-dark.svg +1 -1
- package/docs/design/templates/list-header/size-m.svg +1 -1
- package/docs/design/templates/list-header/size-xl-dark.svg +1 -1
- package/docs/design/templates/list-header/size-xl.svg +1 -1
- package/docs/design/templates/list-header-dark.svg +1 -1
- package/docs/design/templates/list-header.svg +1 -1
- package/docs/design/templates/list-item/icon-tile-dark.svg +1 -0
- package/docs/design/templates/list-item/icon-tile.svg +1 -0
- package/docs/design/templates/list-item/selected-dark.svg +1 -1
- package/docs/design/templates/list-item/selected.svg +1 -1
- package/docs/design/templates/list-item-dark.svg +1 -1
- package/docs/design/templates/list-item.svg +1 -1
- package/docs/design/templates/list.svg +1 -1
- package/docs/design/templates/segmented-control-dark.svg +1 -1
- package/docs/design/templates/segmented-control.svg +1 -1
- package/docs/design/templates/skeleton-dark.svg +1 -1
- package/docs/design/templates/skeleton.svg +1 -1
- package/docs/design/templates/state-banner/danger-dark.svg +1 -1
- package/docs/design/templates/state-banner/danger.svg +1 -1
- package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
- package/docs/design/templates/state-banner/info-badge.svg +1 -1
- package/docs/design/templates/state-banner/info-dark.svg +1 -1
- package/docs/design/templates/state-banner/info.svg +1 -1
- package/docs/design/templates/state-banner/strip-dark.svg +1 -1
- package/docs/design/templates/state-banner/strip.svg +1 -1
- package/docs/design/templates/state-banner/success-dark.svg +1 -1
- package/docs/design/templates/state-banner/success.svg +1 -1
- package/docs/design/templates/state-banner/warning-dark.svg +1 -1
- package/docs/design/templates/state-banner/warning.svg +1 -1
- package/docs/design/templates/state-banner-dark.svg +1 -1
- package/docs/design/templates/state-banner.svg +1 -1
- package/docs/design/templates/tab-bar-dark.svg +1 -1
- package/docs/design/templates/tab-bar.svg +1 -1
- package/docs/design/templates/token-search-field-dark.svg +1 -1
- package/docs/design/templates/token-search-field.svg +1 -1
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
- package/docs/design/templates/toolbar-control-group.svg +1 -1
- package/docs/design/templates/toolbar-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/default.svg +1 -1
- package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
- package/docs/design/templates/toolbar-text/large.svg +1 -1
- package/docs/design/templates/toolbar-text-dark.svg +1 -1
- package/docs/design/templates/toolbar-text.svg +1 -1
- package/docs/design/templates/toolbar.svg +1 -1
- package/docs/design/templates/value-table/details-dark.svg +1 -1
- package/docs/design/templates/value-table/details.svg +1 -1
- package/docs/design/templates/value-table-dark.svg +1 -1
- package/docs/design/templates/value-table.svg +1 -1
- package/docs/design-philosophy.md +9 -1
- package/docs/examples/command-palette-adapter.tsx +3 -3
- package/docs/layout.md +147 -14
- package/docs/nav-stack.md +132 -8
- package/docs/recipes.md +9 -9
- package/docs/split-view.md +3 -3
- package/docs/sunken-panel.md +2 -2
- package/docs/surface-scaffold.md +138 -7
- package/docs/tab-navigator.md +38 -1
- package/docs/type-contracts.md +16 -16
- package/docs/ui-analyzer.md +3 -1
- package/docs/ui-doctor.md +71 -0
- package/docs/ux-demo.md +6 -6
- package/docs/webawesome-theme.md +7 -0
- package/docs/workbench.md +10 -6
- package/docs/writing.md +152 -0
- package/doctor/copy-capitalization-checks.mjs +591 -0
- package/doctor/index.mjs +17 -8
- package/llms.txt +105 -11
- package/package.json +15 -1
- package/ux-demo/recipes/app-shell.tsx +11 -11
- package/ux-demo/recipes/collapsible-sidebar.tsx +12 -11
- package/ux-demo/recipes/compact-toolbar.tsx +10 -10
- package/ux-demo/recipes/composer-form.tsx +8 -8
- package/ux-demo/recipes/list-detail-dialog.tsx +13 -13
- package/ux-demo/recipes/list-workspace-states.tsx +13 -13
- package/ux-demo/recipes/loading-inspector.tsx +10 -10
- package/ux-demo/recipes/navigation-sidebar.tsx +7 -7
- package/ux-demo/recipes/navigation-stack.tsx +5 -5
- package/ux-demo/recipes/workspace-header.tsx +5 -5
- package/dist/chunk-36TL6BCR.js.map +0 -1
- package/dist/chunk-4LOK4634.js +0 -21
- package/dist/chunk-4LOK4634.js.map +0 -1
- package/dist/chunk-6BRRUPJY.js.map +0 -1
- package/dist/chunk-6STQXRT4.js.map +0 -1
- package/dist/chunk-76PWEN5Y.js +0 -64
- package/dist/chunk-76PWEN5Y.js.map +0 -1
- package/dist/chunk-AYHC62OZ.js.map +0 -1
- package/dist/chunk-BU23Z5HZ.js.map +0 -1
- package/dist/chunk-DQDTZE4W.js +0 -70
- package/dist/chunk-DQDTZE4W.js.map +0 -1
- package/dist/chunk-FOQK4F3L.js.map +0 -1
- package/dist/chunk-GJYQ27C2.js.map +0 -1
- package/dist/chunk-JYD7G4RG.js.map +0 -1
- package/dist/chunk-KQDU7Z7P.js.map +0 -1
- package/dist/chunk-P74VXMB6.js.map +0 -1
- package/dist/chunk-PBT3CPPY.js.map +0 -1
- package/dist/chunk-PRG4CL3Q.js.map +0 -1
- package/dist/chunk-ROZLJULF.js.map +0 -1
- package/dist/chunk-U657IHFB.js.map +0 -1
- package/dist/chunk-V6W56V3N.js.map +0 -1
|
@@ -61,6 +61,13 @@ mockup or another application:
|
|
|
61
61
|
The package's defaults are the starting design, not an empty canvas. A custom
|
|
62
62
|
component or rule should explain which missing behavior or structure it owns.
|
|
63
63
|
|
|
64
|
+
Write the copy by the package rule as you choose components: a component's
|
|
65
|
+
naming props (button and menu-item labels, segment and `Select` choices,
|
|
66
|
+
`ToolbarText` titles, `ListHeader` labels, field labels) take title style, and
|
|
67
|
+
its describing props (`description`, `detail`, hints, placeholders, tooltips,
|
|
68
|
+
option text, status) take sentence style. See
|
|
69
|
+
[Writing interface copy](./writing.md#capitalization).
|
|
70
|
+
|
|
64
71
|
**Don't fight the components.** The package is built to look right without an
|
|
65
72
|
application customization layer. Before adding `padding`, `margin`, `width`,
|
|
66
73
|
`height`, `border`, `background`, a wrapper card, or a decoration, check whether
|
|
@@ -187,8 +194,14 @@ selection entries.
|
|
|
187
194
|
For arbitrary interactive help or popup content beside a control, use
|
|
188
195
|
[`openAnchoredSurface`](anchored-surface.md) with an accessible label; use
|
|
189
196
|
`openAnchoredSurfaceAt` for pointer coordinates. It owns the Kerf UI surface,
|
|
190
|
-
placement
|
|
191
|
-
|
|
197
|
+
placement (an 8px viewport gutter, and a height fitted to the room beside the
|
|
198
|
+
anchor with scrolling content), focus, dismissal, and modal-dialog hosting while
|
|
199
|
+
the app owns its content and action policy. Never add media queries or
|
|
200
|
+
`max-height` overrides to make a surface fit a phone. For a popup anchored to
|
|
201
|
+
dark media chrome (a `ToolbarControlGroup tone="dark"` over a preview), pass
|
|
202
|
+
`tone: 'dark'` rather than building an app-owned dark panel: the surface takes
|
|
203
|
+
the group's inverse tone and its content inherits the matching colors. Use
|
|
204
|
+
`PopupMenu` for a typed command list.
|
|
192
205
|
|
|
193
206
|
Kerf UI does not export a command-palette component. Do not invent a package
|
|
194
207
|
command-palette import.
|
|
@@ -229,6 +242,7 @@ beside a `ContentItem` value, and connect its id to the preview group's
|
|
|
229
242
|
| Self-contained content child — `ContentItem` | An ordinary surface-like child of a `Pane` or `.kui-content` stack needs the shared 8px margin / 1px border / 8px padding geometry; pass `frame="framed"` only when it marks a real distinction, `appearance="surface"` or a semantic appearance for coordinated surface colors, and `shape="pill"` for the 22px radius. | Do not wrap a component that already owns content-item geometry (`ListItem`, `ListHeader`, `StateBanner`, `Toolbar`, `ValueTable`) or pad around it — that double-insets. Do not frame an item only to make it look contained. Use `SunkenPanel` for a lowered inset stack and `ListInsetText` for bare text that only needs to align. | Pass the item content as children; choose `appearance` (`transparent` default, `surface`, or a semantic status), `frame`, and `shape` instead of writing CSS. Put safe `data-*` metadata in `rootAttributes`; `ariaLabel` names a distinct region and `focusTarget` makes it a programmatic focus target. | The content, surface or status appearance, and whether it marks a distinction worth a visible frame. | `@kerfjs/ui/content-item` | [Content items](./layout.md#content-items) |
|
|
230
243
|
| Application column — `Pane` | A sidebar, main area, inspector, or dialog needs shared vertical header/content/footer organization, one scroll owner, and optional edge separators. | Do not pad the pane shell, wrap child-owned geometry in competing insets, invent unrelated centered measures, or leave an icon-only rail for a hidden pane. | Import `Pane`, use `children` for its homogeneous primary content and the named `SafeHtml` prop slots `header`/`footer` for optional fixed chrome, then opt into only the needed logical-edge separators; add `ContentItem` children as needed. A visible pane owns collapse in its toolbar; move a hidden inline-start pane's restore control to the main toolbar leading edge and an inline-end pane's restore control to its trailing edge. | Layout hierarchy, reading width, responsive relocation, pane visibility state, slot semantics, labels, and separator placement. | `@kerfjs/ui/pane` | [Pane anatomy](./layout.md#anatomy) |
|
|
231
244
|
| Scroll dividers — `wireScrollDividers` | Pinned chrome (a `Pane` header or footer, a `NavStack` top chrome or bottom toolbar, a `TabNavigator` bar, the sides of a `TabBar` strip, or app-owned chrome around a scroller) needs a divider only while content is scrolled away beneath it. | Do not keep a static toolbar divider to fake it or write a border onto another component; each component draws its own line from the wiring's state. | Call `wireScrollDividers` once at the app root and retain the disposer; it covers every `Pane`, `NavStack`, `TabNavigator`, and `TabBar` below it. Name app-owned arrangements in `targets` by id; a `Toolbar` or `List` named as chrome draws its facing side. Configure a pane with `chromeDividers` (`scroll` default, `always`, `none`). | Which panes follow the scroll, app-owned targets, and the disposer. | `@kerfjs/ui/wire-scroll-dividers` | [Scroll dividers](./layout.md#scroll-dividers) |
|
|
245
|
+
| Keyboard focus scroll in scrollable Lists — `wireListFocusScroll` | Rows of a `scrollable` List are reached by keyboard (Tab, or the app's own arrow-key focus moves) and must come fully into view, focus ring included, in Firefox and Safari too: their `focus()` leaves a partly hidden row clipped. | Do not call `scrollIntoView` from app focus handlers or style the List; do not wire it for pointer-only lists. | Call `wireListFocusScroll(root)` once at the app root and retain the disposer. On keyboard (`:focus-visible`) focus inside a `scrollable` List it scrolls the focused element into view only when its ring crosses the clip; pointer focus is left alone. | The disposer. | `@kerfjs/ui/wire-list-focus-scroll` | [Framed result pickers](./layout.md#framed-result-pickers) |
|
|
232
246
|
| One-pane navigation — `NavStack` | A compact flow pushes into details while keeping earlier views mounted, or a single-pane screen needs the same stable layout contract. | Do not use it when list and detail should remain visible together; use `SplitView`. Do not use it alone for co-equal top-level destinations; use one stack per `TabNavigator` tab. | Keep the ordered view array controlled, put view-specific bottom chrome on each view (or use the component fallback), import its CSS explicitly, and call `wireNavStack` for back delegation, content-slide/chrome-cross-fade transitions, and focus move/restore. Mark a preferred initial target with `data-nav-focus` when DOM order is insufficient. | View keys/content/titles, push and pop actions, preferred initial focus targets, and per-view or persistent bottom toolbar content. | `@kerfjs/ui/nav-stack` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
|
|
233
247
|
| Responsive list-detail — `SplitView` | A primary list and related detail belong together on roomy devices but become a one-pane drill-down on compact devices. | Do not use it for a single linear flow or a multi-panel editor shell. | Provide controlled selection/detail state and device class; opt into resize wiring only when the separator is user adjustable. | Selection, detail content, compact back action, device policy, and optional persisted width. | `@kerfjs/ui/split-view` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
|
|
234
248
|
| Compact top-level destinations — `TabNavigator` | A handset or portrait-tablet app has two to five co-equal destinations whose scenes and nested navigation must remain mounted across switches. | Do not use it for document tabs, reorderable work items, or a desktop navigation rail. | Keep the active id controlled, import its CSS explicitly, and call `wireTabNavigator`; commonly place one `NavStack` in each tab scene. | Destination definitions, active id, routing, persistence, and roomy-device replacement. | `@kerfjs/ui/tab-navigator` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
|
|
@@ -242,7 +256,7 @@ beside a `ContentItem` value, and connect its id to the preview group's
|
|
|
242
256
|
| Panel, dialog, or page heading — `Toolbar` + `ToolbarText` | A panel, dialog, or page needs an extra-large title, optional icon, grouped trailing actions, and optional supporting copy. | Do not add a private heading wrapper or bespoke layout. The toolbar supplies structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. In a narrow peripheral rail or drawer, where xlarge would truncate at the panel's minimum size, use the default `ToolbarText` size as pane identity. | Connect title/supporting-copy ids to the host, group controls, and delegate actions. **For a page or view title set `headingLevel` (usually `1`)** on `ToolbarText`; omit it for a dialog title referenced by `aria-labelledby`. | Open state, focus lifecycle, dismissal, controls, ids, copy, and heading level. | `@kerfjs/ui/toolbar` + `@kerfjs/ui/toolbar-text` | [Toolbar headings](./accessibility.md#toolbar-headings) |
|
|
243
257
|
| Dialog or popup geometry — `DialogSurface`, `PopoverDialogSurface`, `PopupSurface` | Recurring Web Awesome dialogs need standard sizes/presentations/body/footer insets, typed modal viewport bounds, a definite height, or a body one child fills; a dialog must stay non-modal and light-dismiss; or dropdown menus need list-compatible inset ownership. | Do not use these wrappers as generic layout or to replace native dialog/dropdown behavior. Avoid consumer `::part()` overrides and do not stack a dialog body inset around list-owned child geometry. | Wrap one `wa-dialog` or `wa-dropdown`; choose the typed geometry while retaining native labeling, open state, focus, dismissal, and menu semantics. Compose a dialog body as a `List` by default, use `bodyInset="none"` when its children own list geometry, and wrap bare prose in `ListInsetText`. Set `height` with `bodyLayout="fill"` when one `SplitView` or `Pane` should fill the body. For a non-modal, light-dismiss dialog render `PopoverDialogSurface` (native `popover="auto"`, opened by a `popoverTarget` control or `showPopover()`), never a hand-built popover with app chrome CSS; `presentation="embedded"` shows it in flow. | State, focus lifecycle, dismissal, labels, content, and outer placement. | `@kerfjs/ui/surface-scaffold` | [Surface scaffolds](./surface-scaffold.md) |
|
|
244
258
|
| Key/value facts — `ValueTable`, `ValueTableRow` | Read-only labels and values form a semantic definition list, optionally with a leading icon. | Do not use it for editable form fields or a row/column data grid; use native form or table semantics. | Compose typed `ValueTableRow` entries; pass `icon` when a 24px leading icon adds useful context. Set `labelWidth` for a minimum label column, `presentation="stacked"` to put each value under its label, or `stackBelow` (`compact` 512px, `narrow` 576px) to stack only while the table is that narrow; never override `--kui-value-table-row-columns` for these. | Values, formatting, icon meaning, and empty/loading policy. | `@kerfjs/ui/value-table` | [Component ownership](./component-contract.md#ownership-boundaries) |
|
|
245
|
-
| Framed rows of arbitrary content — `FramedList`, `FramedListRow` | Settings rows, account or source cards, or a shortcut list group several rows of copy and controls in one rounded frame with dividers between them. | Do not use it for read-only label/value facts; use `ValueTable`. Do not use it for selectable navigation rows; use `List` with `ListItem
|
|
259
|
+
| Framed rows of arbitrary content — `FramedList`, `FramedListRow` | Settings rows, account or source cards, or a shortcut list group several rows of copy and controls in one rounded frame with dividers between them. | Do not use it for read-only label/value facts; use `ValueTable`. Do not use it for selectable navigation rows or a bounded result picker; use `List` with `ListItem` (a picker adds `frame`, `scrollable`, and `maxHeight`). Do not pad or border rows from application CSS. | Compose any row content (a `Row` of copy, a keycap, `IconButton`s) in each `FramedListRow`; mark the row being edited `highlighted`. Name the list with `label` when no heading does. | Row content, controls, which row is highlighted, and the list's name. | `@kerfjs/ui/framed-list` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
246
260
|
| Selectable item summaries — `SummaryRow` | A list or board shows many items as dense, selectable summaries: an identifier and title, a timestamp, and a metadata line (tickets, tasks). | Do not use it for navigation rows; use `ListItem`. Do not use it for free-form selectable tiles; use `ContentItem`. Do not hand-roll row chrome. | Pass `title`, optional `leading`, `trailingMeta`, and `metadata`; choose `presentation` (`row` or `card`), `rail`, `selected`, `dimmed`, `listEdge`, and `titleLines`. Put rows in a labeled `role="listbox"` and own selection and arrow-key focus in the app. | The listbox, selection, keyboard movement, content, rail tone, and dimming. | `@kerfjs/ui/summary-row` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
247
261
|
| Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | Pass a label when the spinner conveys status; use size for a named icon step or positive pixel size. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
|
|
248
262
|
| Loading placeholder — `Skeleton` + a component's `placeholder` prop | A value or a whole component is still loading and should hold its space as a subtle, unanimated block, keeping the layout stable — an inspector or detail view rendering its real chrome with per-record values absent. | Do not use it for known progress (use `LoadingSpinner`), do not animate it, and do not hand-rebuild a component's empty state — set `placeholder` on the component instead. Prefer it over `wa-skeleton`, which the pure-Kerf primitives avoid to stay Web-Awesome-free. | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render skeletons in its value slots with interactivity disabled; use the standalone `Skeleton` for a custom slot. | Loading lifecycle, which slots are unknown, and announcing the loading region. | `@kerfjs/ui/skeleton` | [Accessibility](./accessibility.md#shared-rules) |
|
|
@@ -254,10 +268,11 @@ beside a `ContentItem` value, and connect its id to the preview group's
|
|
|
254
268
|
| Value selection — `Select` | A controlled form, toolbar, or compact navigation value comes from a moderate or long choice list, possibly grouped, icon-bearing, icon-only when closed, or truncating. Set `multiple` with an array `value` when the person may choose any number of them; add `selectedPresentation="icon-only"` and a fixed `triggerIcon` for a toolbar filter menu. | Do not use it for commands; use `PopupMenu`. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once for registration and current-request animation ownership; listen for standard input/change events. Do not add consumer popup timing or positioning repairs. Use typed presentation props rather than consumer `::part()` overrides; let the parent own placement. | Controlled value, validation, choices, domain mapping, and outer placement. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
|
|
255
269
|
| Pick or create a value by typing — `Combobox`, `wireComboboxes` | People type to find an existing value among many or create a new one (tags, labels, recipients). | Do not use it when only existing values are allowed; use `Select`. Do not use it for search text with removable filter tokens; use `TokenSearchField`. Do not style a native input with a datalist. | Pass `id`, `label`, and `suggestions` (strings or `{ value, label }`), plus `placeholder`, `hint`, and `createLabel` (or `false` for suggestions only). Call `wireComboboxes(root, { onCommit })` once; it filters, moves the active option with the arrows, and reports `{ id, value, created, input }` on Enter, a comma, or a click. | Suggestions, committed values, normalization, and clearing the field. | `@kerfjs/ui/combobox`, `@kerfjs/ui/wire-comboboxes` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
256
270
|
| Small exclusive choice — `SegmentedControl` | A few visible choices switch a compact view or setting, with toolbar, rounded, or pill presentation. | Do not use it for tabpanel semantics; use `TabBar`. Do not use it for many choices; use `ChoiceChips` when they stay visible and wrap, or `Select` for a long list. Prefer it over `wa-button-group` when the controls select one value. | Delegate its action, read `data-segment-value`, update `value`, and rerender. | Controlled value, labels, action, and persistence. | `@kerfjs/ui/segmented-control` | [SegmentedControl contract](./accessibility.md#segmentedcontrol) |
|
|
257
|
-
| Wrapping
|
|
271
|
+
| Wrapping visible choice — `ChoiceChips` | Several visible options, too many for one segmented track, must wrap onto more rows — a labeled color palette with swatches, a tag color, a category. Set `multiple` with an array `value` when any number may be on, such as label filters or annotation intents. | Do not use it for a few compact choices on one track; use `SegmentedControl`. Do not use it for a long list or tight space; use `Select`. Do not use it for removable tags (`Chip`). Prefer it over `wa-radio-group` with `appearance="button"` radios, which wrap as a ragged joined group. | Pass a form `name`, a `label` or `labelledBy`, and the controlled `value`; delegate the `action` on `change`, read the radio `value` (or, with `multiple`, the checked boxes under `name`), and rerender. Place it in a dark appearance scope for inverse chrome rather than restyling it. Lead choices with a `swatch` (`CssColor` or `'transparent'`) or an `icon`. | Controlled value, choices, swatch colors, change handling, and form submission. | `@kerfjs/ui/choice-chips` | [ChoiceChips contract](./accessibility.md#choicechips) |
|
|
272
|
+
| Chat or prompt composer — `Composer`, `wireComposers` | A chat message, assistant prompt, or reply is written in a raised frame and sent with a trailing action, often in a `DialogSurface` floating footer over a transcript. | Do not use it for an ordinary form field submitted with the rest of a form; use `wa-textarea` with a `FieldLabel`. Do not wrap it in another framed `ContentItem` or card; it owns its frame, padding, and shadow. Prefer it over a `wa-textarea` and a button in app-styled chrome. | Pass a `name`, a title-style `label`, an `id` when it has a `hint`, and the trailing `actions` (a brand Send `wa-button`). Bound the growing field with `minRows` and `maxRows`. Read and clear the textarea from the send action. For Enter-to-send, set `submitOnEnter` and call `wireComposers(root)` once; Enter clicks the first trailing action, Shift+Enter is a newline, and IME commits never send. | Value, sending, shortcuts beyond the opt-in Enter-to-send, and the disabled or busy state of its actions. | `@kerfjs/ui/composer`, `@kerfjs/ui/wire-composers` | [Composer](./composer.md) |
|
|
258
273
|
| Structured search editor — `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields` | Free text and ordered, editable, removable filter tokens share one searchbox; enable `collapsible` when an empty, unfocused field should reduce to one iconic action, standalone or in a toolbar group. | Do not use it for ordinary text entry; use a native input or Web Awesome `wa-input`. Do not use it when filters belong in separate form controls. | Read DOM-owned text on input, empty `textContent` on clear, and use `placeTokenSearchCaret` after explicit controlled focus changes. Call `wireTokenSearchFields` from `@kerfjs/ui/wire-token-search-fields` once so Enter submits without adding a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. In `collapsible` mode it also manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse); bind the field's `expanded` to the signal on the returned handle (`handle.expanded(id)`) or adopt your own via `collapsible.signals`, and opt out per behavior only when the app must own it. | Parsing, suggestions, tokens, query execution, results, announcements, and — only if overriding the default — the collapsible `expanded` signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
|
|
259
274
|
| Persistent inline status — `StateBanner` | A neutral, info, pop, success, warning, or danger message belongs next to the affected work; pop highlights featured or novel content without implying status. | Do not use pop as a success, warning, or danger substitute. Do not use StateBanner for a no-content screen; use `EmptyState`. Do not use it for transient confirmation; use a toast. Web Awesome `wa-callout` is the ecosystem alternative for Web Awesome-owned content. | Delegate an optional action; choose alert urgency only for attention-requiring failure. Use the optional tone-tinted `badge` for a terse count or status beside the title, not as the only expression of meaning. | State mapping, message lifetime, retry/action behavior, badge content, and copy. | `@kerfjs/ui/state-banner` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
260
|
-
| App-level progress in a corner — `CornerStatus`, `wireCornerStatus` | Ongoing app-wide work, such as "Loading…" or "Updating
|
|
275
|
+
| App-level progress in a corner — `CornerStatus`, `wireCornerStatus` | Ongoing app-wide work, such as "Loading…" or "Updating Tickets… 3 of 12", needs a non-dismissible status that has no better home beside the affected work. | Do not use it for transient, dismissible confirmation; use a toast. Do not use it for status that belongs beside the affected work; place a `StateBanner` there. Do not position a `StateBanner` with fixed-position application CSS. | Compose one or more `StateBanner`s with `urgency="status"`; they stack. Call `wireCornerStatus` once so each overlay host opens in the top layer as renders insert it. It never takes focus and has no close button, light dismiss, or Escape. | Status lifetime (render while the work runs, remove when it ends), banner copy and progress, and corner placement. | `@kerfjs/ui/corner-status`, `@kerfjs/ui/wire-corner-status` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
261
276
|
| Ambient edge activity — `EdgeActivityIndicator` | App-wide background activity (server work, sync) should be visible along the top edge without a modal, a banner, or a layout shift. | Do not use it for progress the user waits on or must read; use `CornerStatus`. Do not use it for known progress; use a progress bar. Do not make it the only record of important state. | Drive `busy`; pass an optional `message` for a polite status pill and a `tone`. It sits below the top safe area (or flush with `inset="none"`), takes no pointer, and only fades under reduced motion. | The busy state, message, and tone. | `@kerfjs/ui/edge-activity-indicator` | [Feedback accessibility](./accessibility.md#shared-rules) |
|
|
262
277
|
| Empty or busy content area — `EmptyState` | A content region has no items, cannot proceed, or is loading and needs explanation plus an optional action. | Do not use it for an inline status update; use `StateBanner`. Do not use it for transient success; use `wa-toast`/`wa-toast-item` or the application's toast system. | Delegate its optional action; it composes `LoadingSpinner` when busy. | Empty/busy policy, recovery action, illustration, and copy. | `@kerfjs/ui/empty-state` | [Feedback ownership](./component-contract.md#extracted-versus-application-specific) |
|
|
263
278
|
|
|
@@ -304,6 +319,18 @@ title minimum height, and border-width tokens for section density.
|
|
|
304
319
|
Set `--kui-list-group-divider-color` on a containing `List` to give
|
|
305
320
|
`ListHeader`, `ListItem`, and `ListActionRow` the same before/after divider
|
|
306
321
|
color; each row otherwise uses the quiet neutral border tone.
|
|
322
|
+
Set `--kui-list-row-padding-inline` on a containing `List` to give `ListItem`
|
|
323
|
+
and `ListActionRow` rows one leading inline inset: both start their leading
|
|
324
|
+
icons on the same edge at any value, and the token defaults to
|
|
325
|
+
`--kui-layout-item-padding`. `--kui-list-item-padding-inline` still overrides
|
|
326
|
+
it for `ListItem` rows alone, so use the shared token whenever the two row kinds
|
|
327
|
+
share a list.
|
|
328
|
+
Both row kinds also draw their untiled leading icon at one shared size,
|
|
329
|
+
`--kui-list-row-icon-size` (default 18px), so a list that mixes them starts
|
|
330
|
+
their labels on one edge too. Set it on the containing `List` to resize both
|
|
331
|
+
row kinds together; `--kui-list-item-icon-size` still overrides it for
|
|
332
|
+
`ListItem` rows alone. An `iconTile` keeps its 32px tile and 18px glyph at any
|
|
333
|
+
value.
|
|
307
334
|
For a filled command list, set `--kui-list-item-background` and
|
|
308
335
|
`--kui-list-item-border` on the list or row. Scope `--kui-list-item-color`,
|
|
309
336
|
`--kui-list-item-hover-background`, `--kui-list-item-selected-color`, and
|
|
@@ -324,7 +351,10 @@ trailing zone that should take a full second row when expanded. `responsiveAt`
|
|
|
324
351
|
chooses the compact or narrow container breakpoint. The leading identity stays
|
|
325
352
|
in the first row. Set `centerAlign="balanced"` when the center zone must sit on
|
|
326
353
|
the toolbar's midpoint despite unequal or empty side content; it gives the
|
|
327
|
-
leading and trailing zones equal tracks.
|
|
354
|
+
leading and trailing zones equal tracks. A lone `ToolbarText` in a balanced
|
|
355
|
+
center is a navigation-bar title: the side zones keep their content width and
|
|
356
|
+
the title truncates first, centered while the sides leave room (NavStack uses
|
|
357
|
+
this once it shows a back control). `--kui-toolbar-leading-min-width`,
|
|
328
358
|
`--kui-toolbar-trailing-gap`, `--kui-toolbar-trailing-justify`, and the
|
|
329
359
|
`--kui-toolbar-*-padding-inline` zone tokens adjust local geometry without
|
|
330
360
|
selecting toolbar anatomy from application CSS.
|
|
@@ -356,7 +386,7 @@ group; set `--kui-token-search-editor-min-width` only when a wider editor is
|
|
|
356
386
|
required.
|
|
357
387
|
|
|
358
388
|
- `Toolbar` serves persistent app chrome and headings; the zone contents and accessible naming distinguish the purpose.
|
|
359
|
-
- `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `ChoiceChips` keeps a larger exclusive set visible as wrapping pills; `Select` handles a longer value list.
|
|
389
|
+
- `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `ChoiceChips` keeps a larger exclusive set (or, with `multiple`, any-number set) visible as wrapping pills; `Select` handles a longer value list.
|
|
360
390
|
|
|
361
391
|
Use `AppTab presentation="icon-only"` with its `name` kept as the accessible
|
|
362
392
|
tab name. For segmented tabs that should show names in a wide reader and icons
|
|
@@ -384,7 +414,9 @@ beside that pinned tab. `wireTabBars` measures the pinned inset and provides
|
|
|
384
414
|
enough end scroll room for the last peer to align there; a partial peer can
|
|
385
415
|
still appear at the trailing edge as an overflow cue. This mode also aligns a
|
|
386
416
|
newly selected tab at the pinned edge in LTR and RTL. Set the prop on the bar,
|
|
387
|
-
without styling its strip or tab internals.
|
|
417
|
+
without styling its strip or tab internals. With `snapTabs`, keep the trailing
|
|
418
|
+
zone non-shrinking (the default `--kui-tab-bar-trailing-flex`): the reserved
|
|
419
|
+
end room counts toward the strip's share when both zones shrink.
|
|
388
420
|
Set the public
|
|
389
421
|
`--kui-app-tab-pinned-background` token if the surrounding surface differs from
|
|
390
422
|
the default; the pinned tab must cover peers as they scroll beneath it. Its
|
|
@@ -398,8 +430,13 @@ scroll correction.
|
|
|
398
430
|
- `StateBanner` persists beside affected work; `EmptyState` replaces absent content; `wa-callout` is contextual ecosystem content; `wa-toast` and `wa-toast-item` are transient and must not carry the only copy of important state.
|
|
399
431
|
|
|
400
432
|
For a banner with multi-line supporting copy, use `copyLayout="stacked"` to put
|
|
401
|
-
the detail below the title and badge.
|
|
402
|
-
action
|
|
433
|
+
the detail below the title and badge. The default `actionPlacement="trailing"`
|
|
434
|
+
keeps the action beside the copy while the copy has room and moves it to its
|
|
435
|
+
own end-aligned trailing row when the banner is narrow (a phone, a sidebar),
|
|
436
|
+
so a group of buttons never squeezes or covers the title; no breakpoint or
|
|
437
|
+
`actionPlacement` switch is needed for narrow layouts. Use
|
|
438
|
+
`actionPlacement="below"` when the action should always take a separate
|
|
439
|
+
trailing row. The default copy layout keeps the detail inline;
|
|
403
440
|
`--kui-state-banner-copy-gap` and `--kui-state-banner-copy-row-gap` adjust the
|
|
404
441
|
stacked copy's spacing at an instance boundary.
|
|
405
442
|
|
|
@@ -494,8 +531,13 @@ To apply a saved search or restore a query programmatically, call `model.replace
|
|
|
494
531
|
When one root contains both kinds of field, a registered model enables chip keyboard behavior only for its own field. Other fields keep native editing unless the root opts into `keyboard` with an application removal callback.
|
|
495
532
|
For an icon action at the end of the field, pass `trailingAction` with `icon`,
|
|
496
533
|
accessible `label`, and delegated `action` (plus optional `id`). The component
|
|
497
|
-
renders a button
|
|
498
|
-
|
|
534
|
+
renders a compact button beside the clear button, with the same size, hover,
|
|
535
|
+
focus ring, and cursor, in every presentation. For a help toggle that opens a
|
|
536
|
+
surface, set `expanded` (and `haspopup`, plus `controls` while it is open); for
|
|
537
|
+
an on/off toggle, set `pressed`. The open or pressed state keeps the hover fill.
|
|
538
|
+
Put domain `data-*` metadata in `attributes`. Never style your own button into
|
|
539
|
+
the `trailing` slot; reserve `trailing` for passive content such as a shortcut
|
|
540
|
+
hint.
|
|
499
541
|
Set `fill` on a collapsible `TokenSearchField` in a stretched Toolbar center to
|
|
500
542
|
use the available width when expanded; the field and its search group retain
|
|
501
543
|
their compact widths when collapsed.
|
|
@@ -505,6 +547,8 @@ unset.
|
|
|
505
547
|
|
|
506
548
|
`ToolbarControlGroup.tileTone` selects `neutral`, `brand`, `success`, `warning`, or `danger` quiet fill, matching quiet border, and on-quiet foreground for a non-interactive icon tile. Compose a contained `single` group whose only child is a direct decorative LucideIcon (`aria-hidden="true"`). Omission preserves the normal group palette. The prop does not recolor interactive, mixed, multi-control, or borderless groups; keep real actions in controls with their existing focus/hover/pressed treatments.
|
|
507
549
|
|
|
550
|
+
`ListItem`, `ListItemLink`, and `ListActionRow` take `iconTile` to present their leading `icon` in a filled 32px rounded tile holding an 18px glyph, for settings or command rows that identify each entry by color. Pass a tone (`neutral`, `brand`, `success`, `warning`, or `danger`) for the same quiet fill, matching quiet border, and on-quiet foreground as `ToolbarControlGroup.tileTone`, or `{ color, foreground? }` for an application color: `color` is a `CssColor` (`uiColor()`, `colorVar()`, or `fillColor('#e5484d')`) and `foreground` a `CssForegroundColor`; without one, the glyph is black or white, whichever contrasts with the fill. The tile sits 6px clear of a standard 44px row (a compact row is at least 42px tall), moves the label to the tile's edge, and keeps its colors when the row is hovered or selected. Never wrap the icon in your own tile or resize the row's icon slot.
|
|
551
|
+
|
|
508
552
|
## Toolbar composition
|
|
509
553
|
|
|
510
554
|
Use `ToolbarText tone="dark"` on a loud or photo-backed toolbar surface to
|
|
@@ -573,7 +617,7 @@ the control's dimensions, shows `LoadingSpinner` in its icon slot, sets
|
|
|
573
617
|
| ------------------------------ | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
574
618
|
| Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title that fits) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
|
|
575
619
|
| One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
|
|
576
|
-
| One primary application action | `trailing={<wa-button variant="brand">New
|
|
620
|
+
| One primary application action | `trailing={<wa-button variant="brand">New Ticket…</wa-button>}` | The standalone button keeps its Web Awesome chrome; use `variant="danger"` when the action is destructive |
|
|
577
621
|
| An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
|
|
578
622
|
| A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once; use `focusRingOwner="group"` so focus follows the group's geometry |
|
|
579
623
|
| A command menu | `<ToolbarControlGroup single nestedDropdown><PopupMenu … /></ToolbarControlGroup>` | Register `@kerfjs/ui/popup-menu/register` once; set `menuInset` on the group |
|
|
@@ -634,7 +678,7 @@ menu.
|
|
|
634
678
|
label="Sort"
|
|
635
679
|
icon={<LucideIcon icon={ArrowDownAZ} name="arrow-down-a-z" />}
|
|
636
680
|
items={[
|
|
637
|
-
{ label: "Recently
|
|
681
|
+
{ label: "Recently Updated", action: "sort-recent" },
|
|
638
682
|
{ label: "Priority", action: "sort-priority" },
|
|
639
683
|
]}
|
|
640
684
|
/>
|
|
@@ -653,7 +697,7 @@ items shape covers single-line and batch actions; compute each batch child's
|
|
|
653
697
|
<PopupMenu
|
|
654
698
|
label="Line actions"
|
|
655
699
|
items={[
|
|
656
|
-
{ label: 'Copy
|
|
700
|
+
{ label: 'Copy Reference', action: 'copy-reference', attributes: { 'data-item-id': lineId } },
|
|
657
701
|
{ label: 'Decide', submenu: [
|
|
658
702
|
{ kind: 'heading', label: 'Review' },
|
|
659
703
|
{ label: 'Approve', action: 'decide', checked: current === 'approve', attributes: { 'data-item-id': lineId, 'data-decision': 'approve' } },
|
|
@@ -670,8 +714,11 @@ items shape covers single-line and batch actions; compute each batch child's
|
|
|
670
714
|
For a flat command menu with persistent sort choices, group commands under
|
|
671
715
|
`heading` entries and set `checked` explicitly on each choice. Checked rows
|
|
672
716
|
show a checkmark; the background marks the current keyboard row, as in `Select`.
|
|
673
|
-
|
|
674
|
-
|
|
717
|
+
Items with `checked: true` or `checked: false` reserve checkmark space, and so
|
|
718
|
+
does every plain command in a menu level (the top menu or one submenu) that
|
|
719
|
+
holds such an item, so icons and labels line up across the level; a level
|
|
720
|
+
without a choice keeps the compact start. Omit `checked` for a plain command,
|
|
721
|
+
including one beside checkbox items.
|
|
675
722
|
Commands still dispatch their own `data-action` once on pointer and keyboard
|
|
676
723
|
selection.
|
|
677
724
|
|
|
@@ -715,6 +762,9 @@ remain siblings. `trailingActionVisibility="interaction"` keeps the action
|
|
|
715
762
|
available on hover and keyboard focus and automatically leaves it visible on
|
|
716
763
|
non-hover devices. These props own presentation only—the application still
|
|
717
764
|
owns labels, status meaning, action policy, selection, and domain metadata.
|
|
765
|
+
`density="compact"` makes a single-line row 36px tall (42px with `iconTile`),
|
|
766
|
+
the same height as a compact `ListItem`, so the two row kinds stay level in a
|
|
767
|
+
dense mixed list; a standard row is 44px.
|
|
718
768
|
|
|
719
769
|
When the trailing control is an overflow menu ("…" with Edit, Delete, …),
|
|
720
770
|
pass `trailingMenu: { label, icon, items, placement?, disabled? }` instead of
|
|
@@ -832,6 +882,40 @@ requires a string label so the title cannot lose text hidden inside markup.
|
|
|
832
882
|
</Chip>
|
|
833
883
|
```
|
|
834
884
|
|
|
885
|
+
## File and link chips
|
|
886
|
+
|
|
887
|
+
Give `Chip` an `href` when a short chip should open or download something,
|
|
888
|
+
such as the files a chat message references. The chip becomes a native `<a>`
|
|
889
|
+
with the same geometry, icon slot, truncation, and focus ring, plus hover
|
|
890
|
+
feedback. `download` is `true` or the suggested file name; `external` opens a
|
|
891
|
+
new tab with `rel="noopener noreferrer"` and announces it, like
|
|
892
|
+
`ListItemLink`. A link chip cannot also carry a remove button. Use
|
|
893
|
+
`ListItemLink` for a navigation row and `ToolbarActionLink` in a toolbar.
|
|
894
|
+
|
|
895
|
+
Inside a `brand-loud` or `neutral` `ContentItem` bubble, set
|
|
896
|
+
`tone="inherit"`: the chip follows the bubble's foreground and draws its shape
|
|
897
|
+
with a currentColor border (soft for `quiet`, full strength for `outline` and
|
|
898
|
+
`solid`) instead of a fill, so its label keeps the bubble's text contrast.
|
|
899
|
+
Its hover strengthens the border and underlines the label, and its focus ring
|
|
900
|
+
follows the foreground. Semantic tones keep their fills and add a currentColor
|
|
901
|
+
wash on hover. File names are data: keep their spelling.
|
|
902
|
+
|
|
903
|
+
```tsx
|
|
904
|
+
<Row gap="xs" wrap>
|
|
905
|
+
{files.map((file) => (
|
|
906
|
+
<Chip
|
|
907
|
+
tone="inherit"
|
|
908
|
+
href={file.url}
|
|
909
|
+
download={file.name}
|
|
910
|
+
icon={<LucideIcon icon={FileText} name="file-text" />}
|
|
911
|
+
truncate
|
|
912
|
+
>
|
|
913
|
+
{file.name}
|
|
914
|
+
</Chip>
|
|
915
|
+
))}
|
|
916
|
+
</Row>
|
|
917
|
+
```
|
|
918
|
+
|
|
835
919
|
## Wrapping exclusive choices
|
|
836
920
|
|
|
837
921
|
Use `ChoiceChips` when one value is chosen from a visible set that does not
|
|
@@ -844,7 +928,8 @@ The pills wrap with the standard 8px control gap; never wrap a
|
|
|
844
928
|
borders turn ragged across rows.
|
|
845
929
|
|
|
846
930
|
Lead a choice with `swatch` for a color (a `CssColor` from `uiColor()`,
|
|
847
|
-
`colorVar()`, or `
|
|
931
|
+
`colorVar()`, or the `fillColor` helper — `fillColor('#3b82f6')` — for an
|
|
932
|
+
application-chosen CSS color; never `foregroundColor()` for a fill), `swatch: 'transparent'` for the
|
|
848
933
|
no-color option (an outlined dot struck through diagonally), or a decorative
|
|
849
934
|
`icon`. Keep every label visible: the swatch and the selected tint are
|
|
850
935
|
supplementary. Name the group with `label`, or with `labelledBy` pointing at a
|
|
@@ -859,12 +944,54 @@ visible `FieldLabel`.
|
|
|
859
944
|
action="select-command-color"
|
|
860
945
|
choices={[
|
|
861
946
|
{ value: 'transparent', label: 'Transparent', swatch: 'transparent' },
|
|
862
|
-
{ value: 'blue', label: 'Blue', swatch:
|
|
863
|
-
{ value: 'green', label: 'Green', swatch:
|
|
947
|
+
{ value: 'blue', label: 'Blue', swatch: fillColor('#3b82f6') },
|
|
948
|
+
{ value: 'green', label: 'Green', swatch: fillColor('#22c55e') },
|
|
864
949
|
]}
|
|
865
950
|
/>
|
|
866
951
|
```
|
|
867
952
|
|
|
953
|
+
### Wrapping multiple choices
|
|
954
|
+
|
|
955
|
+
Set `multiple` when the person may turn any number of the visible options on
|
|
956
|
+
or off — label filters, or the intents an annotation carries. The same pills
|
|
957
|
+
are then native checkboxes in a `role="group"`: each pill is a Tab stop, Space
|
|
958
|
+
toggles it, and every checked value submits under `name`. `value` becomes a
|
|
959
|
+
`readonly Value[]` (the type rejects a single value, and a single group
|
|
960
|
+
rejects an array). Delegate the `action` on `change` and read the checked
|
|
961
|
+
boxes by `name`, which lists them in choice order. A multiple group takes no `required`. Prefer a
|
|
962
|
+
multiple `Select` when the option list is long or space is tight.
|
|
963
|
+
|
|
964
|
+
On dark inverse chrome, such as a media viewer, put the chips inside a dark
|
|
965
|
+
appearance scope (`.wa-dark`, or any `color-scheme: dark` ancestor) rather
|
|
966
|
+
than restyling them: every chip color is a theme token that follows the scope.
|
|
967
|
+
|
|
968
|
+
```tsx
|
|
969
|
+
<ChoiceChips
|
|
970
|
+
name="intents"
|
|
971
|
+
label="Annotation Intents"
|
|
972
|
+
multiple
|
|
973
|
+
value={annotation.intents}
|
|
974
|
+
action="toggle-annotation-intent"
|
|
975
|
+
choices={[
|
|
976
|
+
{ value: 'comment', label: 'Comment' },
|
|
977
|
+
{ value: 'bug', label: 'Bug' },
|
|
978
|
+
{ value: 'question', label: 'Question' },
|
|
979
|
+
]}
|
|
980
|
+
/>
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
```ts
|
|
984
|
+
delegateActions(root, 'change', {
|
|
985
|
+
'toggle-annotation-intent': () => {
|
|
986
|
+
annotation.intents = [
|
|
987
|
+
...root.querySelectorAll<HTMLInputElement>(
|
|
988
|
+
'input[name="intents"]:checked',
|
|
989
|
+
),
|
|
990
|
+
].map((box) => box.value);
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
```
|
|
994
|
+
|
|
868
995
|
## Web Awesome overlap policy
|
|
869
996
|
|
|
870
997
|
Web Awesome catalog coverage means supported and themed, not preferred. Import
|
package/docs/composer.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Composer
|
|
2
|
+
|
|
3
|
+
`Composer` (`@kerfjs/ui/composer`, CSS `@kerfjs/ui/composer.css`) is the chat
|
|
4
|
+
or prompt composer: the place a person writes a message, assistant prompt, or
|
|
5
|
+
reply and sends it with a trailing action. It replaces app-owned composer
|
|
6
|
+
chrome, such as a `wa-textarea` and a button wrapped in application CSS that
|
|
7
|
+
draws a frame, a focus border, and a shadow.
|
|
8
|
+
|
|
9
|
+
## Contract
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Composer
|
|
13
|
+
id="assistant-message"
|
|
14
|
+
name="message"
|
|
15
|
+
label="Message"
|
|
16
|
+
placeholder="Ask about this build"
|
|
17
|
+
hint="Answers use the latest nightly build."
|
|
18
|
+
minRows={1}
|
|
19
|
+
maxRows={8}
|
|
20
|
+
actions={
|
|
21
|
+
<wa-button variant="brand" data-action="send-message">
|
|
22
|
+
Send
|
|
23
|
+
</wa-button>
|
|
24
|
+
}
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **Frame.** The root is a raised surface (`--kui-color-surface-raised`) with
|
|
29
|
+
the standard 1px neutral border, the large radius (`--kui-radius-l`), and
|
|
30
|
+
`--kui-shadow-l`. While focus is anywhere within it, including on a trailing
|
|
31
|
+
action, the border turns `--kui-color-brand-border-loud`. The frame owns
|
|
32
|
+
8px padding, so a composer placed 8px from a container edge puts its text on
|
|
33
|
+
the same 17px column as a `List` of content items beside it.
|
|
34
|
+
- **Field.** A native, borderless `<textarea>` named by `label`. It sizes to
|
|
35
|
+
its content with `field-sizing: content`, between `minRows` (default 1) and
|
|
36
|
+
`maxRows` (default 8) lines, and scrolls past the maximum. Both bounds are
|
|
37
|
+
positive integers, and `maxRows` is at least `minRows`; otherwise `Composer`
|
|
38
|
+
throws a `RangeError`. In an engine without `field-sizing`, the field keeps
|
|
39
|
+
a fixed height of three lines, clamped to the bounds, and scrolls.
|
|
40
|
+
- **Action row.** Below the field: an optional quiet `hint` at the text start
|
|
41
|
+
and the trailing `actions`, normally one brand Send button. With an `id`,
|
|
42
|
+
the hint is the field's accessible description (`<id>-hint`).
|
|
43
|
+
- **Value.** `value` seeds the field (a restored draft, or `''`). The app
|
|
44
|
+
reads the textarea in its send action, clears it, and re-renders.
|
|
45
|
+
- **Disabled.** `disabled` disables the field; the app disables or marks busy
|
|
46
|
+
its own actions.
|
|
47
|
+
- **Placement.** `slot` assigns it to a web component slot, such as a
|
|
48
|
+
`wa-dialog`'s `footer`. `rootAttributes` takes safe `data-*` identity.
|
|
49
|
+
- **Enter to send.** `submitOnEnter` opts in to the keyboard contract below;
|
|
50
|
+
it marks the root `data-submit-on-enter="true"` (protected from
|
|
51
|
+
`rootAttributes`) and gives the field `aria-keyshortcuts="Enter"`.
|
|
52
|
+
|
|
53
|
+
The app owns sending, any other keyboard shortcut, what a sent message looks
|
|
54
|
+
like, and where the composer sits.
|
|
55
|
+
|
|
56
|
+
## Enter to send
|
|
57
|
+
|
|
58
|
+
Set `submitOnEnter` and call `wireComposers` once over an ancestor of every
|
|
59
|
+
such composer; it returns a disposer:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { wireComposers } from '@kerfjs/ui/wire-composers';
|
|
63
|
+
|
|
64
|
+
const stopComposers = wireComposers(app);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In an opted-in composer's field:
|
|
68
|
+
|
|
69
|
+
| Key | Result |
|
|
70
|
+
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| Enter, Cmd+Enter, Ctrl+Enter | Clicks the composer's **first trailing action**; the newline is suppressed. |
|
|
72
|
+
| Shift+Enter, Alt+Enter | Inserts a newline, as in any textarea. |
|
|
73
|
+
| Enter committing an IME composition | Left to the input method: `isComposing`, keyCode 229, or between `compositionstart` and `compositionend`. |
|
|
74
|
+
| Enter on an empty or whitespace-only message | Nothing: no send and no newline. |
|
|
75
|
+
| Auto-repeating Enter (key held down) | Nothing. |
|
|
76
|
+
|
|
77
|
+
The first trailing action is the first `wa-button`, `button`, or
|
|
78
|
+
`[data-action]` element inside `actions`. Clicking it means the keyboard send
|
|
79
|
+
runs the same delegated `data-action` handler as a pointer send, so the app
|
|
80
|
+
writes one send handler that reads the field, clears it, and re-renders. The
|
|
81
|
+
click does not move focus, so the person keeps typing. Nothing is sent while
|
|
82
|
+
the field is `disabled`, or while that action has a `disabled` attribute or
|
|
83
|
+
property, a `loading` attribute (a busy `wa-button`), or
|
|
84
|
+
`aria-disabled="true"`; the Enter is still suppressed, so a message waiting on
|
|
85
|
+
a busy Send does not gain a newline.
|
|
86
|
+
|
|
87
|
+
Leave `submitOnEnter` off for a long-form prompt where Enter should be a
|
|
88
|
+
newline, and send with the action alone. A visible `hint` may also state the
|
|
89
|
+
shortcut ("Enter to send, Shift+Enter for a new line") when the audience may
|
|
90
|
+
not expect it; `aria-keyshortcuts` already announces it to assistive
|
|
91
|
+
technology.
|
|
92
|
+
|
|
93
|
+
## In a chat dialog
|
|
94
|
+
|
|
95
|
+
A chat dialog floats the composer over its transcript. Use a `DialogSurface`
|
|
96
|
+
with `bodyLayout="fill-last"` and `footerPresentation="floating"` (and
|
|
97
|
+
`footerInset="none"`, since the composer draws its own frame), and slot the
|
|
98
|
+
composer into the footer:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<DialogSurface height={remify(560)} bodyInset="none" bodyLayout="fill-last" footerPresentation="floating" footerOffset={remify(8)} footerInset="none">
|
|
102
|
+
<wa-dialog id="assistant" label="Assistant">
|
|
103
|
+
<StateBanner title="Answers use the latest nightly build." tone="neutral" />
|
|
104
|
+
<List gap="xs" controlInsets="tb" scrollable scrollClearance="block-end">{messages}</List>
|
|
105
|
+
<Composer submitOnEnter slot="footer" id="assistant-message" name="message" label="Message" placeholder="Ask about this build" actions={send} />
|
|
106
|
+
</wa-dialog>
|
|
107
|
+
</DialogSurface>;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The floating footer is transparent, so the composer's own frame and shadow
|
|
111
|
+
lift it over the transcript. The transcript's `scrollClearance="block-end"`
|
|
112
|
+
lets messages scroll visibly beneath the composer while the last one still
|
|
113
|
+
comes to rest above it, following the composer's measured height as it grows.
|
|
114
|
+
Without the opt-in, the dialog body reserves that height below the transcript
|
|
115
|
+
instead.
|
|
116
|
+
|
|
117
|
+
## Do not
|
|
118
|
+
|
|
119
|
+
- Wrap it in a framed `ContentItem`, card, or bordered container; it owns its
|
|
120
|
+
frame, padding, and shadow.
|
|
121
|
+
- Style its field, frame, or action row from application CSS. A variation it
|
|
122
|
+
does not offer is a component gap.
|
|
123
|
+
- Use it for an ordinary form field submitted with the rest of a form; use
|
|
124
|
+
`wa-textarea` with a `FieldLabel`.
|
|
125
|
+
|
|
126
|
+
## Verification
|
|
127
|
+
|
|
128
|
+
`tests/unit/composer.test.tsx` covers serialization, the hint description,
|
|
129
|
+
row bounds, and protected attributes; `tests/unit/wire-composers.test.tsx`
|
|
130
|
+
covers the Enter-to-send matrix (modifiers, IME, empty, repeat, disabled
|
|
131
|
+
field and actions, first action only, disposal). `tests/browser/composer.spec.ts` covers
|
|
132
|
+
the focus-within border, growth up to `maxRows` and then scrolling, the action
|
|
133
|
+
row geometry, and the send flow at wide and phone widths, and, in Chromium,
|
|
134
|
+
Firefox, and WebKit, Enter, Shift+Enter, Cmd/Ctrl+Enter, empty messages, and
|
|
135
|
+
IME composition (dispatched composition events and keyCode 229). In
|
|
136
|
+
Chromium it also drives a real IME composition through the DevTools protocol
|
|
137
|
+
(`Input.imeSetComposition`, then `Input.insertText` to commit). That test
|
|
138
|
+
asserts the engine's own event order: an Enter keydown flagged `isComposing`
|
|
139
|
+
while composing, the committing `Process` keydown with keyCode 229, then
|
|
140
|
+
`compositionend`. None of those sends, the committed text lands in the field,
|
|
141
|
+
and the next Enter sends it;
|
|
142
|
+
`tests/browser/surface-scaffold.spec.ts` covers the composer in the floating
|
|
143
|
+
footer of the chat dialog, including Enter to send.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 88" width="600" height="88"><defs><clipPath id="ishc0"><rect x="21" y="21" width="68.8" height="30" rx="15"/></clipPath><clipPath id="ishc1"><rect x="33" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp2"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ct3"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ishc4"><rect x="111.8" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp5"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ct6"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ishc7"><rect x="204.1" y="21" width="76.4" height="30" rx="15"/></clipPath><clipPath id="ishc8"><rect x="216.1" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp9"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ct10"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ishc11"><rect x="302.5" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp12"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ct13"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ishc14"><rect x="401.7" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp15"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath><clipPath id="ct16"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath></defs><rect x="20" y="20" width="70.8" height="32" rx="16" fill="rgb(40,57,66)"/><g clip-path="url(#ishc0)"><path d="M17 17h76.8v38H17zm19 5h38.8a14 14 0 0 1 14 14 14 14 0 0 1-14 14H36a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(100,210,255)" fill-rule="evenodd"/></g><rect x="20.5" y="20.5" width="70" height="31" rx="15.5" fill="none" stroke="rgb(100,210,255)" stroke-width="1"/><rect x="33" y="30" width="12" height="12" rx="6" fill="rgb(239,68,68)"/><g clip-path="url(#ishc1)"><path d="M29 26h20v20H29zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="51" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(138,203,255)">Bug</text></g></g><rect x="98.8" y="20" width="96.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="99.5" y="20.5" width="95" height="31" rx="15.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><rect x="111.8" y="30" width="12" height="12" rx="6" fill="rgb(59,130,246)"/><g clip-path="url(#ishc4)"><path d="M107.8 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="129.81" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(245,245,247)">Feature</text></g></g><rect x="203.1" y="20" width="78.4" height="32" rx="16" fill="rgb(40,57,66)"/><g clip-path="url(#ishc7)"><path d="M200.1 17h84.4v38h-84.4zm19 5h46.4a14 14 0 0 1 14 14 14 14 0 0 1-14 14h-46.4a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(100,210,255)" fill-rule="evenodd"/></g><rect x="203.5" y="20.5" width="77" height="31" rx="15.5" fill="none" stroke="rgb(100,210,255)" stroke-width="1"/><rect x="216.1" y="30" width="12" height="12" rx="6" fill="rgb(34,197,94)"/><g clip-path="url(#ishc8)"><path d="M212.1 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp9)"><g clip-path="url(#ct10)"><text x="234.06" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(138,203,255)">Docs</text></g></g><rect x="289.5" y="20" width="91.2" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="289.5" y="20.5" width="91" height="31" rx="15.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><rect x="302.5" y="30" width="12" height="12" rx="6" fill="rgb(168,85,247)"/><g clip-path="url(#ishc11)"><path d="M298.5 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp12)"><g clip-path="url(#ct13)"><text x="320.47" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(245,245,247)">Design</text></g></g><rect x="388.7" y="20" width="85.1" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="389.5" y="20.5" width="84" height="31" rx="15.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><rect x="401.7" y="30" width="12" height="12" rx="6" fill="rgb(107,114,128)"/><g clip-path="url(#ishc14)"><path d="M397.7 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp15)"><g clip-path="url(#ct16)"><text x="419.7" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(245,245,247)">Chore</text></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 88" width="600" height="88"><defs><clipPath id="ishc0"><rect x="21" y="21" width="68.8" height="30" rx="15"/></clipPath><clipPath id="ishc1"><rect x="33" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp2"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ct3"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ishc4"><rect x="111.8" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp5"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ct6"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ishc7"><rect x="204.1" y="21" width="76.4" height="30" rx="15"/></clipPath><clipPath id="ishc8"><rect x="216.1" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp9"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ct10"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ishc11"><rect x="302.5" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp12"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ct13"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ishc14"><rect x="401.7" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp15"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath><clipPath id="ct16"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath></defs><rect x="20" y="20" width="70.8" height="32" rx="16" fill="rgb(214,233,249)"/><g clip-path="url(#ishc0)"><path d="M17 17h76.8v38H17zm19 5h38.8a14 14 0 0 1 14 14 14 14 0 0 1-14 14H36a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(0,117,219)" fill-rule="evenodd"/></g><rect x="20.5" y="20.5" width="70" height="31" rx="15.5" fill="none" stroke="rgb(0,117,219)" stroke-width="1"/><rect x="33" y="30" width="12" height="12" rx="6" fill="rgb(239,68,68)"/><g clip-path="url(#ishc1)"><path d="M29 26h20v20H29zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="51" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(26,93,207)">Bug</text></g></g><rect x="98.8" y="20" width="96.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="99.5" y="20.5" width="95" height="31" rx="15.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><rect x="111.8" y="30" width="12" height="12" rx="6" fill="rgb(59,130,246)"/><g clip-path="url(#ishc4)"><path d="M107.8 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="129.81" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(29,29,31)">Feature</text></g></g><rect x="203.1" y="20" width="78.4" height="32" rx="16" fill="rgb(214,233,249)"/><g clip-path="url(#ishc7)"><path d="M200.1 17h84.4v38h-84.4zm19 5h46.4a14 14 0 0 1 14 14 14 14 0 0 1-14 14h-46.4a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(0,117,219)" fill-rule="evenodd"/></g><rect x="203.5" y="20.5" width="77" height="31" rx="15.5" fill="none" stroke="rgb(0,117,219)" stroke-width="1"/><rect x="216.1" y="30" width="12" height="12" rx="6" fill="rgb(34,197,94)"/><g clip-path="url(#ishc8)"><path d="M212.1 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp9)"><g clip-path="url(#ct10)"><text x="234.06" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(26,93,207)">Docs</text></g></g><rect x="289.5" y="20" width="91.2" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="289.5" y="20.5" width="91" height="31" rx="15.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><rect x="302.5" y="30" width="12" height="12" rx="6" fill="rgb(168,85,247)"/><g clip-path="url(#ishc11)"><path d="M298.5 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp12)"><g clip-path="url(#ct13)"><text x="320.47" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(29,29,31)">Design</text></g></g><rect x="388.7" y="20" width="85.1" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="389.5" y="20.5" width="84" height="31" rx="15.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><rect x="401.7" y="30" width="12" height="12" rx="6" fill="rgb(107,114,128)"/><g clip-path="url(#ishc14)"><path d="M397.7 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp15)"><g clip-path="url(#ct16)"><text x="419.7" y="41" font-family="system-ui, "-apple-system", sans-serif" font-size="14" font-weight="650" fill="rgb(29,29,31)">Chore</text></g></g></svg>
|