@next-buildr/editor 0.0.0-stage → 2.0.0
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/LICENSE +21 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/app/builder-editor.d.ts +8 -0
- package/dist/app/builder-editor.d.ts.map +1 -0
- package/dist/app/builder-editor.js +14 -0
- package/dist/app/builder-editor.test.d.ts +2 -0
- package/dist/app/builder-editor.test.d.ts.map +1 -0
- package/dist/app/builder-editor.test.js +199 -0
- package/dist/app/config.d.ts +59 -0
- package/dist/app/config.d.ts.map +1 -0
- package/dist/app/config.js +18 -0
- package/dist/app/content-language.test.d.ts +2 -0
- package/dist/app/content-language.test.d.ts.map +1 -0
- package/dist/app/content-language.test.js +157 -0
- package/dist/app/editor-app.d.ts +17 -0
- package/dist/app/editor-app.d.ts.map +1 -0
- package/dist/app/editor-app.js +206 -0
- package/dist/app/editor-app.test.d.ts +2 -0
- package/dist/app/editor-app.test.d.ts.map +1 -0
- package/dist/app/editor-app.test.js +135 -0
- package/dist/app/layout.d.ts +32 -0
- package/dist/app/layout.d.ts.map +1 -0
- package/dist/app/layout.js +82 -0
- package/dist/app/manifest.d.ts +12 -0
- package/dist/app/manifest.d.ts.map +1 -0
- package/dist/app/manifest.js +17 -0
- package/dist/app/shell-panels.d.ts +15 -0
- package/dist/app/shell-panels.d.ts.map +1 -0
- package/dist/app/shell-panels.js +5 -0
- package/dist/app/small-screen.d.ts +12 -0
- package/dist/app/small-screen.d.ts.map +1 -0
- package/dist/app/small-screen.js +33 -0
- package/dist/app/small-screen.test.d.ts +2 -0
- package/dist/app/small-screen.test.d.ts.map +1 -0
- package/dist/app/small-screen.test.js +108 -0
- package/dist/app/status-info.d.ts +14 -0
- package/dist/app/status-info.d.ts.map +1 -0
- package/dist/app/status-info.js +34 -0
- package/dist/app/use-resizable.d.ts +34 -0
- package/dist/app/use-resizable.d.ts.map +1 -0
- package/dist/app/use-resizable.js +84 -0
- package/dist/canvas-host/canvas-frame.d.ts +53 -0
- package/dist/canvas-host/canvas-frame.d.ts.map +1 -0
- package/dist/canvas-host/canvas-frame.js +146 -0
- package/dist/canvas-host/canvas-frame.test.d.ts +2 -0
- package/dist/canvas-host/canvas-frame.test.d.ts.map +1 -0
- package/dist/canvas-host/canvas-frame.test.js +91 -0
- package/dist/canvas-host/host.d.ts +109 -0
- package/dist/canvas-host/host.d.ts.map +1 -0
- package/dist/canvas-host/host.js +293 -0
- package/dist/canvas-host/host.test.d.ts +2 -0
- package/dist/canvas-host/host.test.d.ts.map +1 -0
- package/dist/canvas-host/host.test.js +398 -0
- package/dist/canvas-host/index.d.ts +5 -0
- package/dist/canvas-host/index.d.ts.map +1 -0
- package/dist/canvas-host/index.js +2 -0
- package/dist/clipboard/clipboard.d.ts +38 -0
- package/dist/clipboard/clipboard.d.ts.map +1 -0
- package/dist/clipboard/clipboard.js +160 -0
- package/dist/clipboard/clipboard.test.d.ts +2 -0
- package/dist/clipboard/clipboard.test.d.ts.map +1 -0
- package/dist/clipboard/clipboard.test.js +457 -0
- package/dist/clipboard/format.d.ts +27 -0
- package/dist/clipboard/format.d.ts.map +1 -0
- package/dist/clipboard/format.js +37 -0
- package/dist/clipboard/index.d.ts +6 -0
- package/dist/clipboard/index.d.ts.map +1 -0
- package/dist/clipboard/index.js +3 -0
- package/dist/clipboard/react.d.ts +20 -0
- package/dist/clipboard/react.d.ts.map +1 -0
- package/dist/clipboard/react.js +71 -0
- package/dist/dialogs/index.d.ts +2 -0
- package/dist/dialogs/index.d.ts.map +1 -0
- package/dist/dialogs/index.js +1 -0
- package/dist/dialogs/media-picker/index.d.ts +7 -0
- package/dist/dialogs/media-picker/index.d.ts.map +1 -0
- package/dist/dialogs/media-picker/index.js +3 -0
- package/dist/dialogs/media-picker/library.d.ts +17 -0
- package/dist/dialogs/media-picker/library.d.ts.map +1 -0
- package/dist/dialogs/media-picker/library.js +9 -0
- package/dist/dialogs/media-picker/media-picker.d.ts +18 -0
- package/dist/dialogs/media-picker/media-picker.d.ts.map +1 -0
- package/dist/dialogs/media-picker/media-picker.js +99 -0
- package/dist/dialogs/media-picker/media-picker.test.d.ts +2 -0
- package/dist/dialogs/media-picker/media-picker.test.d.ts.map +1 -0
- package/dist/dialogs/media-picker/media-picker.test.js +161 -0
- package/dist/dialogs/media-picker/media-ref.d.ts +28 -0
- package/dist/dialogs/media-picker/media-ref.d.ts.map +1 -0
- package/dist/dialogs/media-picker/media-ref.js +52 -0
- package/dist/dnd/check.d.ts +8 -0
- package/dist/dnd/check.d.ts.map +1 -0
- package/dist/dnd/check.js +12 -0
- package/dist/dnd/destinations.d.ts +15 -0
- package/dist/dnd/destinations.d.ts.map +1 -0
- package/dist/dnd/destinations.js +43 -0
- package/dist/dnd/dnd-react.test.d.ts +2 -0
- package/dist/dnd/dnd-react.test.d.ts.map +1 -0
- package/dist/dnd/dnd-react.test.js +93 -0
- package/dist/dnd/dnd.test.d.ts +2 -0
- package/dist/dnd/dnd.test.d.ts.map +1 -0
- package/dist/dnd/dnd.test.js +327 -0
- package/dist/dnd/engine.d.ts +85 -0
- package/dist/dnd/engine.d.ts.map +1 -0
- package/dist/dnd/engine.js +185 -0
- package/dist/dnd/fragment.d.ts +6 -0
- package/dist/dnd/fragment.d.ts.map +1 -0
- package/dist/dnd/fragment.js +18 -0
- package/dist/dnd/geometry.d.ts +30 -0
- package/dist/dnd/geometry.d.ts.map +1 -0
- package/dist/dnd/geometry.js +31 -0
- package/dist/dnd/index.d.ts +14 -0
- package/dist/dnd/index.d.ts.map +1 -0
- package/dist/dnd/index.js +7 -0
- package/dist/dnd/move-to-dialog.d.ts +13 -0
- package/dist/dnd/move-to-dialog.d.ts.map +1 -0
- package/dist/dnd/move-to-dialog.js +30 -0
- package/dist/dnd/react.d.ts +43 -0
- package/dist/dnd/react.d.ts.map +1 -0
- package/dist/dnd/react.js +185 -0
- package/dist/dnd/tree-target.d.ts +29 -0
- package/dist/dnd/tree-target.d.ts.map +1 -0
- package/dist/dnd/tree-target.js +97 -0
- package/dist/fonts/LICENSE-inter.txt +93 -0
- package/dist/fonts/inter-latin-400-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-500-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-600-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-ext-400-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-ext-500-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-ext-600-normal.woff2 +0 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/messages/en.d.ts +335 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +332 -0
- package/dist/messages/index.d.ts +16 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +22 -0
- package/dist/messages/pl.d.ts +3 -0
- package/dist/messages/pl.d.ts.map +1 -0
- package/dist/messages/pl.js +327 -0
- package/dist/panels/breadcrumbs/breadcrumbs.d.ts +6 -0
- package/dist/panels/breadcrumbs/breadcrumbs.d.ts.map +1 -0
- package/dist/panels/breadcrumbs/breadcrumbs.js +24 -0
- package/dist/panels/breadcrumbs/breadcrumbs.test.d.ts +2 -0
- package/dist/panels/breadcrumbs/breadcrumbs.test.d.ts.map +1 -0
- package/dist/panels/breadcrumbs/breadcrumbs.test.js +56 -0
- package/dist/panels/index.d.ts +9 -0
- package/dist/panels/index.d.ts.map +1 -0
- package/dist/panels/index.js +6 -0
- package/dist/panels/insert/catalog.d.ts +30 -0
- package/dist/panels/insert/catalog.d.ts.map +1 -0
- package/dist/panels/insert/catalog.js +76 -0
- package/dist/panels/insert/catalog.order.test.d.ts +2 -0
- package/dist/panels/insert/catalog.order.test.d.ts.map +1 -0
- package/dist/panels/insert/catalog.order.test.js +17 -0
- package/dist/panels/insert/category-name.d.ts +4 -0
- package/dist/panels/insert/category-name.d.ts.map +1 -0
- package/dist/panels/insert/category-name.js +8 -0
- package/dist/panels/insert/index.d.ts +3 -0
- package/dist/panels/insert/index.d.ts.map +1 -0
- package/dist/panels/insert/index.js +2 -0
- package/dist/panels/insert/insert-panel.d.ts +7 -0
- package/dist/panels/insert/insert-panel.d.ts.map +1 -0
- package/dist/panels/insert/insert-panel.js +155 -0
- package/dist/panels/insert/insert-panel.test.d.ts +2 -0
- package/dist/panels/insert/insert-panel.test.d.ts.map +1 -0
- package/dist/panels/insert/insert-panel.test.js +323 -0
- package/dist/panels/insert/target.d.ts +17 -0
- package/dist/panels/insert/target.d.ts.map +1 -0
- package/dist/panels/insert/target.js +48 -0
- package/dist/panels/inspector/controls/boolean.d.ts +4 -0
- package/dist/panels/inspector/controls/boolean.d.ts.map +1 -0
- package/dist/panels/inspector/controls/boolean.js +4 -0
- package/dist/panels/inspector/controls/compound.d.ts +15 -0
- package/dist/panels/inspector/controls/compound.d.ts.map +1 -0
- package/dist/panels/inspector/controls/compound.js +99 -0
- package/dist/panels/inspector/controls/compound.test.d.ts +2 -0
- package/dist/panels/inspector/controls/compound.test.d.ts.map +1 -0
- package/dist/panels/inspector/controls/compound.test.js +195 -0
- package/dist/panels/inspector/controls/icon.d.ts +5 -0
- package/dist/panels/inspector/controls/icon.d.ts.map +1 -0
- package/dist/panels/inspector/controls/icon.js +6 -0
- package/dist/panels/inspector/controls/index.d.ts +13 -0
- package/dist/panels/inspector/controls/index.d.ts.map +1 -0
- package/dist/panels/inspector/controls/index.js +11 -0
- package/dist/panels/inspector/controls/label.d.ts +4 -0
- package/dist/panels/inspector/controls/label.d.ts.map +1 -0
- package/dist/panels/inspector/controls/label.js +8 -0
- package/dist/panels/inspector/controls/link.d.ts +5 -0
- package/dist/panels/inspector/controls/link.d.ts.map +1 -0
- package/dist/panels/inspector/controls/link.js +6 -0
- package/dist/panels/inspector/controls/media.d.ts +9 -0
- package/dist/panels/inspector/controls/media.d.ts.map +1 -0
- package/dist/panels/inspector/controls/media.js +21 -0
- package/dist/panels/inspector/controls/number.d.ts +10 -0
- package/dist/panels/inspector/controls/number.d.ts.map +1 -0
- package/dist/panels/inspector/controls/number.js +33 -0
- package/dist/panels/inspector/controls/rich-text-model.d.ts +20 -0
- package/dist/panels/inspector/controls/rich-text-model.d.ts.map +1 -0
- package/dist/panels/inspector/controls/rich-text-model.js +91 -0
- package/dist/panels/inspector/controls/rich-text.d.ts +10 -0
- package/dist/panels/inspector/controls/rich-text.d.ts.map +1 -0
- package/dist/panels/inspector/controls/rich-text.js +159 -0
- package/dist/panels/inspector/controls/select.d.ts +5 -0
- package/dist/panels/inspector/controls/select.d.ts.map +1 -0
- package/dist/panels/inspector/controls/select.js +16 -0
- package/dist/panels/inspector/controls/select.test.d.ts +2 -0
- package/dist/panels/inspector/controls/select.test.d.ts.map +1 -0
- package/dist/panels/inspector/controls/select.test.js +43 -0
- package/dist/panels/inspector/controls/text.d.ts +4 -0
- package/dist/panels/inspector/controls/text.d.ts.map +1 -0
- package/dist/panels/inspector/controls/text.js +5 -0
- package/dist/panels/inspector/controls/textarea.d.ts +4 -0
- package/dist/panels/inspector/controls/textarea.d.ts.map +1 -0
- package/dist/panels/inspector/controls/textarea.js +4 -0
- package/dist/panels/inspector/controls/types.d.ts +16 -0
- package/dist/panels/inspector/controls/types.d.ts.map +1 -0
- package/dist/panels/inspector/controls/types.js +1 -0
- package/dist/panels/inspector/index.d.ts +17 -0
- package/dist/panels/inspector/index.d.ts.map +1 -0
- package/dist/panels/inspector/index.js +9 -0
- package/dist/panels/inspector/inspector.d.ts +16 -0
- package/dist/panels/inspector/inspector.d.ts.map +1 -0
- package/dist/panels/inspector/inspector.js +116 -0
- package/dist/panels/inspector/inspector.test.d.ts +2 -0
- package/dist/panels/inspector/inspector.test.d.ts.map +1 -0
- package/dist/panels/inspector/inspector.test.js +328 -0
- package/dist/panels/inspector/props-panel.d.ts +26 -0
- package/dist/panels/inspector/props-panel.d.ts.map +1 -0
- package/dist/panels/inspector/props-panel.js +117 -0
- package/dist/panels/inspector/styles/box-model.d.ts +20 -0
- package/dist/panels/inspector/styles/box-model.d.ts.map +1 -0
- package/dist/panels/inspector/styles/box-model.js +31 -0
- package/dist/panels/inspector/styles/index.d.ts +5 -0
- package/dist/panels/inspector/styles/index.d.ts.map +1 -0
- package/dist/panels/inspector/styles/index.js +2 -0
- package/dist/panels/inspector/styles/model.d.ts +55 -0
- package/dist/panels/inspector/styles/model.d.ts.map +1 -0
- package/dist/panels/inspector/styles/model.js +111 -0
- package/dist/panels/inspector/styles/segments.d.ts +18 -0
- package/dist/panels/inspector/styles/segments.d.ts.map +1 -0
- package/dist/panels/inspector/styles/segments.js +75 -0
- package/dist/panels/inspector/styles/sides-editor.d.ts +34 -0
- package/dist/panels/inspector/styles/sides-editor.d.ts.map +1 -0
- package/dist/panels/inspector/styles/sides-editor.js +86 -0
- package/dist/panels/inspector/styles/style-inspector.d.ts +15 -0
- package/dist/panels/inspector/styles/style-inspector.d.ts.map +1 -0
- package/dist/panels/inspector/styles/style-inspector.js +219 -0
- package/dist/panels/inspector/styles/style-inspector.test.d.ts +2 -0
- package/dist/panels/inspector/styles/style-inspector.test.d.ts.map +1 -0
- package/dist/panels/inspector/styles/style-inspector.test.js +265 -0
- package/dist/panels/inspector/styles/token-picker.d.ts +15 -0
- package/dist/panels/inspector/styles/token-picker.d.ts.map +1 -0
- package/dist/panels/inspector/styles/token-picker.js +23 -0
- package/dist/panels/inspector/styles/use-style-value.d.ts +28 -0
- package/dist/panels/inspector/styles/use-style-value.d.ts.map +1 -0
- package/dist/panels/inspector/styles/use-style-value.js +51 -0
- package/dist/panels/inspector/value.d.ts +20 -0
- package/dist/panels/inspector/value.d.ts.map +1 -0
- package/dist/panels/inspector/value.js +32 -0
- package/dist/panels/inspector/values/analyze.d.ts +49 -0
- package/dist/panels/inspector/values/analyze.d.ts.map +1 -0
- package/dist/panels/inspector/values/analyze.js +115 -0
- package/dist/panels/inspector/values/data.d.ts +18 -0
- package/dist/panels/inspector/values/data.d.ts.map +1 -0
- package/dist/panels/inspector/values/data.js +10 -0
- package/dist/panels/inspector/values/translation.d.ts +20 -0
- package/dist/panels/inspector/values/translation.d.ts.map +1 -0
- package/dist/panels/inspector/values/translation.js +25 -0
- package/dist/panels/inspector/values/value-editor.d.ts +30 -0
- package/dist/panels/inspector/values/value-editor.d.ts.map +1 -0
- package/dist/panels/inspector/values/value-editor.js +191 -0
- package/dist/panels/inspector/values/values.test.d.ts +2 -0
- package/dist/panels/inspector/values/values.test.d.ts.map +1 -0
- package/dist/panels/inspector/values/values.test.js +173 -0
- package/dist/panels/issues/collect.d.ts +70 -0
- package/dist/panels/issues/collect.d.ts.map +1 -0
- package/dist/panels/issues/collect.js +96 -0
- package/dist/panels/issues/index.d.ts +5 -0
- package/dist/panels/issues/index.d.ts.map +1 -0
- package/dist/panels/issues/index.js +2 -0
- package/dist/panels/issues/issues-panel.d.ts +17 -0
- package/dist/panels/issues/issues-panel.d.ts.map +1 -0
- package/dist/panels/issues/issues-panel.js +77 -0
- package/dist/panels/layers/flatten.d.ts +28 -0
- package/dist/panels/layers/flatten.d.ts.map +1 -0
- package/dist/panels/layers/flatten.js +61 -0
- package/dist/panels/layers/layers-panel.d.ts +14 -0
- package/dist/panels/layers/layers-panel.d.ts.map +1 -0
- package/dist/panels/layers/layers-panel.js +428 -0
- package/dist/panels/layers/layers-panel.test.d.ts +2 -0
- package/dist/panels/layers/layers-panel.test.d.ts.map +1 -0
- package/dist/panels/layers/layers-panel.test.js +315 -0
- package/dist/persistence/controller.d.ts +55 -0
- package/dist/persistence/controller.d.ts.map +1 -0
- package/dist/persistence/controller.js +341 -0
- package/dist/persistence/index.d.ts +6 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/load.d.ts +35 -0
- package/dist/persistence/load.d.ts.map +1 -0
- package/dist/persistence/load.js +67 -0
- package/dist/persistence/persistence.test.d.ts +2 -0
- package/dist/persistence/persistence.test.d.ts.map +1 -0
- package/dist/persistence/persistence.test.js +483 -0
- package/dist/persistence/react.d.ts +18 -0
- package/dist/persistence/react.d.ts.map +1 -0
- package/dist/persistence/react.js +105 -0
- package/dist/persistence/types.d.ts +182 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +1 -0
- package/dist/preview/index.d.ts +3 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +2 -0
- package/dist/preview/prepare.d.ts +16 -0
- package/dist/preview/prepare.d.ts.map +1 -0
- package/dist/preview/prepare.js +21 -0
- package/dist/preview/preview.d.ts +27 -0
- package/dist/preview/preview.d.ts.map +1 -0
- package/dist/preview/preview.js +51 -0
- package/dist/preview/preview.test.d.ts +2 -0
- package/dist/preview/preview.test.d.ts.map +1 -0
- package/dist/preview/preview.test.js +129 -0
- package/dist/shortcuts/actions.d.ts +17 -0
- package/dist/shortcuts/actions.d.ts.map +1 -0
- package/dist/shortcuts/actions.js +85 -0
- package/dist/shortcuts/index.d.ts +11 -0
- package/dist/shortcuts/index.d.ts.map +1 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/keys.d.ts +29 -0
- package/dist/shortcuts/keys.d.ts.map +1 -0
- package/dist/shortcuts/keys.js +100 -0
- package/dist/shortcuts/map.d.ts +17 -0
- package/dist/shortcuts/map.d.ts.map +1 -0
- package/dist/shortcuts/map.js +31 -0
- package/dist/shortcuts/registry.d.ts +32 -0
- package/dist/shortcuts/registry.d.ts.map +1 -0
- package/dist/shortcuts/registry.js +53 -0
- package/dist/shortcuts/shortcuts.d.ts +32 -0
- package/dist/shortcuts/shortcuts.d.ts.map +1 -0
- package/dist/shortcuts/shortcuts.js +95 -0
- package/dist/shortcuts/shortcuts.test.d.ts +2 -0
- package/dist/shortcuts/shortcuts.test.d.ts.map +1 -0
- package/dist/shortcuts/shortcuts.test.js +263 -0
- package/dist/store/create-store.d.ts +13 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/create-store.js +248 -0
- package/dist/store/index.d.ts +9 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +5 -0
- package/dist/store/locale.d.ts +23 -0
- package/dist/store/locale.d.ts.map +1 -0
- package/dist/store/locale.js +31 -0
- package/dist/store/react.d.ts +17 -0
- package/dist/store/react.d.ts.map +1 -0
- package/dist/store/react.js +32 -0
- package/dist/store/selection.d.ts +32 -0
- package/dist/store/selection.d.ts.map +1 -0
- package/dist/store/selection.js +84 -0
- package/dist/store/selection.test.d.ts +2 -0
- package/dist/store/selection.test.d.ts.map +1 -0
- package/dist/store/selection.test.js +95 -0
- package/dist/store/selectors.d.ts +18 -0
- package/dist/store/selectors.d.ts.map +1 -0
- package/dist/store/selectors.js +39 -0
- package/dist/store/store.test.d.ts +2 -0
- package/dist/store/store.test.d.ts.map +1 -0
- package/dist/store/store.test.js +331 -0
- package/dist/store/types.d.ts +114 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +1 -0
- package/dist/styles/tokens.test.d.ts +2 -0
- package/dist/styles/tokens.test.d.ts.map +1 -0
- package/dist/styles/tokens.test.js +199 -0
- package/dist/styles.css +2885 -0
- package/dist/toolbar/brand.d.ts +7 -0
- package/dist/toolbar/brand.d.ts.map +1 -0
- package/dist/toolbar/brand.js +11 -0
- package/dist/toolbar/breakpoint-control.d.ts +9 -0
- package/dist/toolbar/breakpoint-control.d.ts.map +1 -0
- package/dist/toolbar/breakpoint-control.js +23 -0
- package/dist/toolbar/editable-title.d.ts +14 -0
- package/dist/toolbar/editable-title.d.ts.map +1 -0
- package/dist/toolbar/editable-title.js +46 -0
- package/dist/toolbar/editable-title.test.d.ts +2 -0
- package/dist/toolbar/editable-title.test.d.ts.map +1 -0
- package/dist/toolbar/editable-title.test.js +53 -0
- package/dist/toolbar/index.d.ts +12 -0
- package/dist/toolbar/index.d.ts.map +1 -0
- package/dist/toolbar/index.js +5 -0
- package/dist/toolbar/locale-switcher.d.ts +10 -0
- package/dist/toolbar/locale-switcher.d.ts.map +1 -0
- package/dist/toolbar/locale-switcher.js +20 -0
- package/dist/toolbar/more-menu.d.ts +12 -0
- package/dist/toolbar/more-menu.d.ts.map +1 -0
- package/dist/toolbar/more-menu.js +47 -0
- package/dist/toolbar/panel-toggles.d.ts +9 -0
- package/dist/toolbar/panel-toggles.d.ts.map +1 -0
- package/dist/toolbar/panel-toggles.js +19 -0
- package/dist/toolbar/publish-dialog.d.ts +18 -0
- package/dist/toolbar/publish-dialog.d.ts.map +1 -0
- package/dist/toolbar/publish-dialog.js +49 -0
- package/dist/toolbar/publish.test.d.ts +2 -0
- package/dist/toolbar/publish.test.d.ts.map +1 -0
- package/dist/toolbar/publish.test.js +258 -0
- package/dist/toolbar/sample-picker.d.ts +18 -0
- package/dist/toolbar/sample-picker.d.ts.map +1 -0
- package/dist/toolbar/sample-picker.js +72 -0
- package/dist/toolbar/sample-picker.test.d.ts +2 -0
- package/dist/toolbar/sample-picker.test.d.ts.map +1 -0
- package/dist/toolbar/sample-picker.test.js +78 -0
- package/dist/toolbar/save-indicator.d.ts +8 -0
- package/dist/toolbar/save-indicator.d.ts.map +1 -0
- package/dist/toolbar/save-indicator.js +30 -0
- package/dist/toolbar/theme.d.ts +22 -0
- package/dist/toolbar/theme.d.ts.map +1 -0
- package/dist/toolbar/theme.js +46 -0
- package/dist/toolbar/theme.test.d.ts +2 -0
- package/dist/toolbar/theme.test.d.ts.map +1 -0
- package/dist/toolbar/theme.test.js +49 -0
- package/dist/toolbar/toolbar-centre.d.ts +13 -0
- package/dist/toolbar/toolbar-centre.d.ts.map +1 -0
- package/dist/toolbar/toolbar-centre.js +7 -0
- package/dist/toolbar/toolbar-left.d.ts +12 -0
- package/dist/toolbar/toolbar-left.d.ts.map +1 -0
- package/dist/toolbar/toolbar-left.js +16 -0
- package/dist/toolbar/toolbar-right.d.ts +19 -0
- package/dist/toolbar/toolbar-right.d.ts.map +1 -0
- package/dist/toolbar/toolbar-right.js +21 -0
- package/dist/toolbar/toolbar.d.ts +38 -0
- package/dist/toolbar/toolbar.d.ts.map +1 -0
- package/dist/toolbar/toolbar.js +27 -0
- package/dist/toolbar/toolbar.test.d.ts +2 -0
- package/dist/toolbar/toolbar.test.d.ts.map +1 -0
- package/dist/toolbar/toolbar.test.js +289 -0
- package/dist/toolbar/use-overflow-level.d.ts +9 -0
- package/dist/toolbar/use-overflow-level.d.ts.map +1 -0
- package/dist/toolbar/use-overflow-level.js +40 -0
- package/dist/toolbar/zoom-menu.d.ts +10 -0
- package/dist/toolbar/zoom-menu.d.ts.map +1 -0
- package/dist/toolbar/zoom-menu.js +17 -0
- package/dist/ui/icon.d.ts +102 -0
- package/dist/ui/icon.d.ts.map +1 -0
- package/dist/ui/icon.js +182 -0
- package/dist/ui/icon.test.d.ts +2 -0
- package/dist/ui/icon.test.d.ts.map +1 -0
- package/dist/ui/icon.test.js +115 -0
- package/dist/ui/index.d.ts +11 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +5 -0
- package/dist/ui/panel.d.ts +18 -0
- package/dist/ui/panel.d.ts.map +1 -0
- package/dist/ui/panel.js +21 -0
- package/dist/ui/portal.d.ts +14 -0
- package/dist/ui/portal.d.ts.map +1 -0
- package/dist/ui/portal.js +14 -0
- package/dist/ui/primitives.d.ts +190 -0
- package/dist/ui/primitives.d.ts.map +1 -0
- package/dist/ui/primitives.js +164 -0
- package/dist/ui/primitives.test.d.ts +2 -0
- package/dist/ui/primitives.test.d.ts.map +1 -0
- package/dist/ui/primitives.test.js +215 -0
- package/dist/ui/toast.d.ts +39 -0
- package/dist/ui/toast.d.ts.map +1 -0
- package/dist/ui/toast.js +76 -0
- package/dist/ui/toast.test.d.ts +2 -0
- package/dist/ui/toast.test.d.ts.map +1 -0
- package/dist/ui/toast.test.js +95 -0
- package/package.json +66 -3
- package/README.md +0 -3
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
|
|
3
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
4
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
5
|
+
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
6
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
7
|
+
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
|
8
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
9
|
+
import { useRef, useState } from 'react';
|
|
10
|
+
import { useT } from '../messages/index.js';
|
|
11
|
+
import { Icon } from './icon.js';
|
|
12
|
+
import { usePortalContainer } from './portal.js';
|
|
13
|
+
const cx = (...parts) => parts.filter(Boolean).join(' ');
|
|
14
|
+
export function Button({ variant = 'default', className, type = 'button', ...rest }) {
|
|
15
|
+
return (_jsx("button", { ...rest, type: type, "data-variant": variant, className: cx('bd-button', className) }));
|
|
16
|
+
}
|
|
17
|
+
/** A button with an icon and a required accessible name; the name is also its tooltip. */
|
|
18
|
+
export function IconButton({ label, icon, hint, className, ...rest }) {
|
|
19
|
+
return (_jsx(Tooltip, { content: hint === undefined ? label : `${label} (${hint})`, children: _jsx(Button, { ...rest, "aria-label": label, className: cx('bd-icon-button', className), children: _jsx(Icon, { name: icon }) }) }));
|
|
20
|
+
}
|
|
21
|
+
export function Input({ className, ...rest }) {
|
|
22
|
+
return _jsx("input", { ...rest, className: cx('bd-input', className) });
|
|
23
|
+
}
|
|
24
|
+
export function Select({ value, onValueChange, options, label, disabled }) {
|
|
25
|
+
const portal = usePortalContainer();
|
|
26
|
+
return (_jsxs(SelectPrimitive.Root, { value: value, onValueChange: onValueChange, ...(disabled !== undefined ? { disabled } : {}), children: [_jsxs(SelectPrimitive.Trigger, { className: "bd-select-trigger", "aria-label": label, children: [_jsx(SelectPrimitive.Value, {}), _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(Icon, { name: "chevron-down" }) })] }), _jsx(SelectPrimitive.Portal, { container: portal, children: _jsx(SelectPrimitive.Content, { className: "bd-select-content", position: "popper", sideOffset: 4, children: _jsx(SelectPrimitive.Viewport, { children: options.map((option) => (_jsx(SelectPrimitive.Item, { value: option.value, className: "bd-select-item", children: _jsx(SelectPrimitive.ItemText, { children: option.label }) }, option.value))) }) }) })] }));
|
|
27
|
+
}
|
|
28
|
+
export function Tabs({ items, label, value, defaultValue, onValueChange }) {
|
|
29
|
+
const first = items[0]?.value;
|
|
30
|
+
return (_jsxs(TabsPrimitive.Root, { ...(value !== undefined ? { value } : { defaultValue: defaultValue ?? first ?? '' }), ...(onValueChange !== undefined ? { onValueChange } : {}), children: [_jsx(TabsPrimitive.List, { className: "bd-tabs-list", "aria-label": label, children: items.map((item) => (_jsx(TabsPrimitive.Trigger, { value: item.value, className: "bd-tab", children: item.label }, item.value))) }), items.map((item) => (_jsx(TabsPrimitive.Content, { value: item.value, children: item.content }, item.value)))] }));
|
|
31
|
+
}
|
|
32
|
+
export function Popover({ trigger, children, open, onOpenChange, contentClassName, align, label, }) {
|
|
33
|
+
const portal = usePortalContainer();
|
|
34
|
+
return (_jsxs(PopoverPrimitive.Root, { ...(open !== undefined ? { open } : {}), ...(onOpenChange !== undefined ? { onOpenChange } : {}), children: [_jsx(PopoverPrimitive.Trigger, { asChild: true, children: trigger }), _jsx(PopoverPrimitive.Portal, { container: portal, children: _jsx(PopoverPrimitive.Content, { className: cx('bd-popover', contentClassName), sideOffset: 6, ...(align !== undefined ? { align } : {}), ...(label !== undefined ? { 'aria-label': label } : {}), children: children }) })] }));
|
|
35
|
+
}
|
|
36
|
+
/** A modal: header (title, description, close icon button), scrollable body, right-aligned footer. */
|
|
37
|
+
export function Dialog({ open, onOpenChange, title, description, children, footer, hideClose, }) {
|
|
38
|
+
const t = useT();
|
|
39
|
+
const portal = usePortalContainer();
|
|
40
|
+
// The dialogs are controlled (opened from a toolbar button, a menu item or a shortcut), so Radix has
|
|
41
|
+
// no trigger to return focus to: remember the element that had focus when the dialog opened.
|
|
42
|
+
const opener = useRef(null);
|
|
43
|
+
return (_jsx(DialogPrimitive.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogPrimitive.Portal, { container: portal, children: [_jsx(DialogPrimitive.Overlay, { className: "bd-dialog-overlay" }), _jsxs(DialogPrimitive.Content, { className: "bd-dialog", onOpenAutoFocus: () => {
|
|
44
|
+
const active = document.activeElement;
|
|
45
|
+
opener.current = active instanceof HTMLElement ? active : null;
|
|
46
|
+
}, onCloseAutoFocus: (event) => {
|
|
47
|
+
const target = opener.current;
|
|
48
|
+
opener.current = null;
|
|
49
|
+
if (target?.isConnected === true) {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
target.focus();
|
|
52
|
+
}
|
|
53
|
+
}, ...(description === undefined ? { 'aria-describedby': undefined } : {}), children: [_jsxs("header", { className: "bd-dialog-header", children: [_jsxs("div", { className: "bd-dialog-heading", children: [_jsx(DialogPrimitive.Title, { className: "bd-dialog-title", children: title }), description !== undefined && (_jsx(DialogPrimitive.Description, { className: "bd-dialog-description", children: description }))] }), hideClose === true ? null : (_jsx(DialogPrimitive.Close, { asChild: true, children: _jsx(Button, { variant: "ghost", className: "bd-icon-button", "aria-label": t('ui.close'), children: _jsx(Icon, { name: "x" }) }) }))] }), children !== undefined && _jsx("div", { className: "bd-dialog-body", children: children }), footer !== undefined && _jsx("footer", { className: "bd-dialog-footer", children: footer })] })] }) }));
|
|
54
|
+
}
|
|
55
|
+
export function Tooltip({ content, children }) {
|
|
56
|
+
const portal = usePortalContainer();
|
|
57
|
+
return (_jsx(TooltipPrimitive.Provider, { delayDuration: 400, children: _jsxs(TooltipPrimitive.Root, { children: [_jsx(TooltipPrimitive.Trigger, { asChild: true, children: children }), _jsx(TooltipPrimitive.Portal, { container: portal, children: _jsx(TooltipPrimitive.Content, { className: "bd-tooltip", sideOffset: 6, children: content }) })] }) }));
|
|
58
|
+
}
|
|
59
|
+
export function Toggle({ pressed, onPressedChange, children, label, disabled }) {
|
|
60
|
+
return (_jsx(TogglePrimitive.Root, { pressed: pressed, onPressedChange: onPressedChange, className: "bd-toggle", ...(label !== undefined ? { 'aria-label': label } : {}), ...(disabled !== undefined ? { disabled } : {}), children: children }));
|
|
61
|
+
}
|
|
62
|
+
export function ContextMenu({ children, items, label }) {
|
|
63
|
+
const portal = usePortalContainer();
|
|
64
|
+
return (_jsxs(ContextMenuPrimitive.Root, { children: [_jsx(ContextMenuPrimitive.Trigger, { asChild: true, children: children }), _jsx(ContextMenuPrimitive.Portal, { container: portal, children: _jsx(ContextMenuPrimitive.Content, { className: "bd-menu bd-portal", "aria-label": label, children: items.map((item) => (_jsx(ContextMenuPrimitive.Item, { className: "bd-menu-item", disabled: item.disabled ?? false, "data-danger": item.danger === true ? '' : undefined, onSelect: item.onSelect, children: item.label }, item.id))) }) })] }));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A row of mutually exclusive toggle buttons (`aria-pressed`) for a short list of choices: value
|
|
68
|
+
* modes, direction, alignment. Icon-only segments take their name and tooltip from `label`.
|
|
69
|
+
*/
|
|
70
|
+
export function SegmentedControl({ options, value, onValueChange, label, disabled, className, }) {
|
|
71
|
+
return (_jsx("fieldset", { "aria-label": label, className: cx('bd-segmented', className), children: options.map((option) => {
|
|
72
|
+
const button = (_jsxs(Button, { variant: "ghost", className: "bd-segment", "aria-pressed": value === option.value, disabled: disabled === true, ...(option.iconOnly === true ? { 'aria-label': option.label } : {}), onClick: () => onValueChange(option.value), children: [option.icon !== undefined ? _jsx(Icon, { name: option.icon }) : null, option.iconOnly === true ? null : option.label] }, option.value));
|
|
73
|
+
return option.iconOnly === true ? (_jsx(Tooltip, { content: option.label, children: button }, option.value)) : (button);
|
|
74
|
+
}) }));
|
|
75
|
+
}
|
|
76
|
+
// --- NumberUnitInput -----------------------------------------------------------------------------
|
|
77
|
+
const NUMBER_WITH_UNIT = /^(-?(?:\d+\.?\d*|\.\d+))([a-z%]*)$/;
|
|
78
|
+
/** What is typed on the way to a number: digits, a sign, a lone dot ("1.", "-", ".5"). */
|
|
79
|
+
const NUMBER_DRAFT = /^-?(?:\d+\.?\d*|\.\d*)?$/;
|
|
80
|
+
/** Adds `delta` to a `12px` / `0.5` / `1.5rem` text; `undefined` when the text is not that. */
|
|
81
|
+
export function stepNumberText(text, delta, defaultUnit) {
|
|
82
|
+
const typed = text.trim();
|
|
83
|
+
const match = typed === '' ? null : NUMBER_WITH_UNIT.exec(typed);
|
|
84
|
+
if (typed !== '' && match === null)
|
|
85
|
+
return undefined;
|
|
86
|
+
const unit = match?.[2] || defaultUnit;
|
|
87
|
+
const next = Math.round((Number(match?.[1] ?? 0) + delta) * 1e4) / 1e4;
|
|
88
|
+
return `${next === 0 ? 0 : next}${unit}`;
|
|
89
|
+
}
|
|
90
|
+
/** The label of the "no unit" entry of the menu. */
|
|
91
|
+
const NO_UNIT = '—';
|
|
92
|
+
/**
|
|
93
|
+
* A field for a number with a unit chosen from a menu: the field holds only the number, the menu
|
|
94
|
+
* the unit, so nobody has to type `px`. Typing digits keeps the unit shown; typing a keyword or a
|
|
95
|
+
* token (`auto`, `$space.4`) switches to text and disables the menu until the field is emptied;
|
|
96
|
+
* typing a whole length (`1.5rem`) still works. ArrowUp/ArrowDown step the number (Shift x10, Alt
|
|
97
|
+
* /10). It only produces text; the caller checks it against the grammar before anything is stored,
|
|
98
|
+
* so it cannot make a value the grammar refuses representable.
|
|
99
|
+
*/
|
|
100
|
+
export function NumberUnitInput({ id, value, onValueChange, units, unitLabel, step = 1, bareNumber, disabled, invalid, describedBy, ariaLabel, list, placeholder, compact, unit: sharedUnit, hideUnitMenu, onBlur, onFocus, }) {
|
|
101
|
+
const typed = value.trim();
|
|
102
|
+
const match = typed === '' ? null : NUMBER_WITH_UNIT.exec(typed);
|
|
103
|
+
const isText = typed !== '' && match === null;
|
|
104
|
+
const defaultUnit = bareNumber === true ? '' : (units[0] ?? '');
|
|
105
|
+
const [pending, setPending] = useState(undefined);
|
|
106
|
+
// The unit shown: the one in the value, else the one picked while the field was empty.
|
|
107
|
+
const unit = match !== null ? (match[2] ?? '') : (pending ?? sharedUnit ?? defaultUnit);
|
|
108
|
+
const options = [
|
|
109
|
+
...(bareNumber === true ? [''] : []),
|
|
110
|
+
...units,
|
|
111
|
+
...(unit !== '' && !units.includes(unit) ? [unit] : []),
|
|
112
|
+
];
|
|
113
|
+
const shown = match !== null ? match[1] : isText ? value : '';
|
|
114
|
+
const pickUnit = (next) => {
|
|
115
|
+
setPending(next);
|
|
116
|
+
if (match !== null)
|
|
117
|
+
onValueChange(`${match[1]}${next}`);
|
|
118
|
+
};
|
|
119
|
+
const change = (text) => {
|
|
120
|
+
if (units.length > 0 && text !== '' && NUMBER_DRAFT.test(text)) {
|
|
121
|
+
onValueChange(`${text}${text === '-' ? '' : unit}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
onValueChange(text);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return (_jsxs("div", { className: cx('bd-number-unit', compact === true && 'bd-number-unit-compact'), "data-invalid": invalid === true ? '' : undefined, children: [_jsx(Input, { ...(id !== undefined ? { id } : {}), value: shown, disabled: disabled === true, list: list, placeholder: placeholder, inputMode: isText ? 'text' : 'decimal', "aria-invalid": invalid === true, "aria-describedby": describedBy, "aria-label": ariaLabel, autoComplete: "off", spellCheck: false, onChange: (event) => change(event.target.value), ...(onBlur !== undefined ? { onBlur } : {}), ...(onFocus !== undefined ? { onFocus } : {}), onKeyDown: (event) => {
|
|
128
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')
|
|
129
|
+
return;
|
|
130
|
+
const size = event.shiftKey ? step * 10 : event.altKey ? step / 10 : step;
|
|
131
|
+
const next = stepNumberText(typed, event.key === 'ArrowUp' ? size : -size, unit);
|
|
132
|
+
if (next === undefined)
|
|
133
|
+
return;
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
onValueChange(next);
|
|
136
|
+
} }), units.length > 0 && hideUnitMenu !== true ? (_jsx("select", { className: "bd-unit-select", "aria-label": unitLabel, value: unit, disabled: disabled === true || isText, onChange: (event) => pickUnit(event.target.value), children: options.map((entry) => (_jsx("option", { value: entry, children: entry === '' ? NO_UNIT : entry }, entry))) })) : null] }));
|
|
137
|
+
}
|
|
138
|
+
// --- ColorSwatch ---------------------------------------------------------------------------------
|
|
139
|
+
/** Colour notations a theme token may hold; anything else is not painted (no raw CSS from data). */
|
|
140
|
+
const SAFE_COLOR = /^(#[0-9a-f]{3,8}|(rgb|hsl|oklch|oklab|lab|lch)a?\([0-9a-z%.,\s/+-]*\))$/i;
|
|
141
|
+
/** A small decorative square filled with a colour. */
|
|
142
|
+
export function ColorSwatch({ color, className }) {
|
|
143
|
+
const safe = color !== undefined && SAFE_COLOR.test(color.trim());
|
|
144
|
+
return (_jsx("span", { "aria-hidden": "true", className: cx('bd-swatch', className), "data-empty": safe ? undefined : '', ...(safe ? { style: { backgroundColor: color.trim() } } : {}) }));
|
|
145
|
+
}
|
|
146
|
+
// --- ColorInput ----------------------------------------------------------------------------------
|
|
147
|
+
/** `#abc` and `#aabbcc` as the `#rrggbb` a colour input takes; `undefined` for any other notation. */
|
|
148
|
+
export function hexOf(color) {
|
|
149
|
+
const text = color?.trim().toLowerCase() ?? '';
|
|
150
|
+
if (/^#[0-9a-f]{6}$/.test(text))
|
|
151
|
+
return text;
|
|
152
|
+
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(text);
|
|
153
|
+
if (short === null)
|
|
154
|
+
return undefined;
|
|
155
|
+
return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The colour swatch as a button that opens the browser's colour picker. The native input sits
|
|
159
|
+
* invisibly over the swatch, so it keeps the platform's keyboard and screen-reader behaviour; what
|
|
160
|
+
* it returns is a hex colour, which the caller still checks against the grammar.
|
|
161
|
+
*/
|
|
162
|
+
export function ColorInput({ color, label, disabled, onColorChange }) {
|
|
163
|
+
return (_jsxs("span", { className: "bd-color-input", children: [_jsx(ColorSwatch, { color: color }), _jsx("input", { type: "color", className: "bd-color-native", "aria-label": label, value: hexOf(color) ?? '#000000', disabled: disabled === true, onChange: (event) => onColorChange(event.target.value) })] }));
|
|
164
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.test.d.ts","sourceRoot":"","sources":["../../src/ui/primitives.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
import { act, useState } from 'react';
|
|
4
|
+
import { createRoot } from 'react-dom/client';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { axe } from 'vitest-axe';
|
|
7
|
+
import * as matchers from 'vitest-axe/matchers';
|
|
8
|
+
import { MessagesProvider } from '../messages/index.js';
|
|
9
|
+
import { Button, ColorInput, ColorSwatch, Dialog, hexOf, IconButton, Input, NumberUnitInput, Popover, PortalContainerProvider, SegmentedControl, Select, stepNumberText, Tabs, Toggle, } from './index.js';
|
|
10
|
+
expect.extend(matchers);
|
|
11
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
|
+
let container;
|
|
13
|
+
let root;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
container = document.createElement('div');
|
|
16
|
+
document.body.append(container);
|
|
17
|
+
root = createRoot(container);
|
|
18
|
+
// Radix measures with ResizeObserver and scrolls items into view.
|
|
19
|
+
globalThis.ResizeObserver ??= class {
|
|
20
|
+
observe() { }
|
|
21
|
+
unobserve() { }
|
|
22
|
+
disconnect() { }
|
|
23
|
+
};
|
|
24
|
+
Element.prototype.scrollIntoView ??= () => { };
|
|
25
|
+
Element.prototype.hasPointerCapture ??= () => false;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
act(() => root.unmount());
|
|
29
|
+
container.remove();
|
|
30
|
+
document.body.innerHTML = '';
|
|
31
|
+
});
|
|
32
|
+
const show = async (node) => {
|
|
33
|
+
await act(async () => root.render(_jsx(MessagesProvider, { locale: "en", children: node })));
|
|
34
|
+
};
|
|
35
|
+
describe('UI primitives', () => {
|
|
36
|
+
it('Button and IconButton: variants, click, and a required accessible name', async () => {
|
|
37
|
+
const onClick = vi.fn();
|
|
38
|
+
await show(_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", onClick: onClick, children: "Save" }), _jsx(IconButton, { label: "Undo", icon: "undo-2", onClick: onClick })] }));
|
|
39
|
+
const [save, undo] = container.querySelectorAll('button');
|
|
40
|
+
expect(save?.getAttribute('data-variant')).toBe('primary');
|
|
41
|
+
expect(save?.getAttribute('type')).toBe('button');
|
|
42
|
+
expect(undo?.getAttribute('aria-label')).toBe('Undo');
|
|
43
|
+
await act(async () => save?.click());
|
|
44
|
+
await act(async () => undo?.click());
|
|
45
|
+
expect(onClick).toHaveBeenCalledTimes(2);
|
|
46
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
47
|
+
});
|
|
48
|
+
it('Input passes its props through', async () => {
|
|
49
|
+
await show(_jsx(Input, { "aria-label": "Title", defaultValue: "Hi" }));
|
|
50
|
+
const input = container.querySelector('input');
|
|
51
|
+
expect(input?.value).toBe('Hi');
|
|
52
|
+
expect(input?.className).toContain('bd-input');
|
|
53
|
+
});
|
|
54
|
+
it('Tabs show the active panel', async () => {
|
|
55
|
+
await show(_jsx(Tabs, { label: "Sections", value: "b", items: [
|
|
56
|
+
{ value: 'a', label: 'Alpha', content: _jsx("p", { children: "first" }) },
|
|
57
|
+
{ value: 'b', label: 'Beta', content: _jsx("p", { children: "second" }) },
|
|
58
|
+
] }));
|
|
59
|
+
expect(container.textContent).toContain('second');
|
|
60
|
+
expect(container.textContent).not.toContain('first');
|
|
61
|
+
const tabs = [...container.querySelectorAll('[role=tab]')].map((t) => [
|
|
62
|
+
t.textContent,
|
|
63
|
+
t.getAttribute('aria-selected'),
|
|
64
|
+
]);
|
|
65
|
+
expect(tabs).toEqual([
|
|
66
|
+
['Alpha', 'false'],
|
|
67
|
+
['Beta', 'true'],
|
|
68
|
+
]);
|
|
69
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
70
|
+
});
|
|
71
|
+
it('Toggle reports its state', async () => {
|
|
72
|
+
const seen = [];
|
|
73
|
+
function Host() {
|
|
74
|
+
const [on, setOn] = useState(false);
|
|
75
|
+
return (_jsx(Toggle, { pressed: on, onPressedChange: (v) => {
|
|
76
|
+
seen.push(v);
|
|
77
|
+
setOn(v);
|
|
78
|
+
}, label: "Bold", children: "B" }));
|
|
79
|
+
}
|
|
80
|
+
await show(_jsx(Host, {}));
|
|
81
|
+
const button = container.querySelector('button');
|
|
82
|
+
expect(button?.getAttribute('aria-pressed')).toBe('false');
|
|
83
|
+
await act(async () => button?.click());
|
|
84
|
+
expect(button?.getAttribute('aria-pressed')).toBe('true');
|
|
85
|
+
expect(seen).toEqual([true]);
|
|
86
|
+
});
|
|
87
|
+
it('Dialog opens with a title, and closes with its button', async () => {
|
|
88
|
+
function Host() {
|
|
89
|
+
const [open, setOpen] = useState(true);
|
|
90
|
+
return (_jsx(Dialog, { open: open, onOpenChange: setOpen, title: "Rename", description: "Pick a name", children: _jsx("p", { children: "body" }) }));
|
|
91
|
+
}
|
|
92
|
+
await show(_jsx(Host, {}));
|
|
93
|
+
const dialog = document.body.querySelector('[role=dialog]');
|
|
94
|
+
expect(dialog?.textContent).toContain('Rename');
|
|
95
|
+
expect(dialog?.getAttribute('aria-labelledby')).not.toBeNull();
|
|
96
|
+
const close = dialog?.querySelector('header button[aria-label="Close"]');
|
|
97
|
+
expect(close).not.toBeNull();
|
|
98
|
+
await act(async () => close?.click());
|
|
99
|
+
expect(document.body.querySelector('[role=dialog]')).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
it('Dialog returns focus to the element that opened it', async () => {
|
|
102
|
+
function Host() {
|
|
103
|
+
const [open, setOpen] = useState(false);
|
|
104
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", id: "opener", onClick: () => setOpen(true), children: "Open" }), _jsx(Dialog, { open: open, onOpenChange: setOpen, title: "Rename", children: _jsx("p", { children: "body" }) })] }));
|
|
105
|
+
}
|
|
106
|
+
await show(_jsx(Host, {}));
|
|
107
|
+
const opener = container.querySelector('#opener');
|
|
108
|
+
await act(async () => opener?.focus());
|
|
109
|
+
await act(async () => opener?.click());
|
|
110
|
+
expect(document.body.querySelector('[role=dialog]')).not.toBeNull();
|
|
111
|
+
const close = document.body.querySelector('header button[aria-label="Close"]');
|
|
112
|
+
await act(async () => close?.click());
|
|
113
|
+
expect(document.body.querySelector('[role=dialog]')).toBeNull();
|
|
114
|
+
expect(document.activeElement).toBe(opener);
|
|
115
|
+
});
|
|
116
|
+
it('Dialog has a header, a scrollable body and a footer for actions, and passes axe', async () => {
|
|
117
|
+
await show(_jsx(Dialog, { open: true, onOpenChange: () => { }, title: "Rename", footer: _jsx(Button, { variant: "primary", children: "Save" }), children: _jsx("p", { children: "body" }) }));
|
|
118
|
+
const dialog = document.body.querySelector('[role=dialog]');
|
|
119
|
+
expect(dialog.querySelector('header .bd-dialog-title')?.textContent).toBe('Rename');
|
|
120
|
+
expect(dialog.querySelector('.bd-dialog-body')?.textContent).toBe('body');
|
|
121
|
+
expect(dialog.querySelector('footer button')?.textContent).toBe('Save');
|
|
122
|
+
expect(await axe(dialog)).toHaveNoViolations();
|
|
123
|
+
});
|
|
124
|
+
it('Dialog can hide its close button', async () => {
|
|
125
|
+
await show(_jsx(Dialog, { open: true, onOpenChange: () => { }, title: "Choose", hideClose: true, children: _jsx("p", { children: "body" }) }));
|
|
126
|
+
expect(document.body.querySelector('[role=dialog] header button')).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
it('Popover opens from its trigger', async () => {
|
|
129
|
+
await show(_jsx(Popover, { trigger: _jsx(Button, { children: "Open" }), children: _jsx("p", { children: "inside" }) }));
|
|
130
|
+
expect(document.body.textContent).not.toContain('inside');
|
|
131
|
+
await act(async () => container.querySelector('button')?.click());
|
|
132
|
+
expect(document.body.textContent).toContain('inside');
|
|
133
|
+
});
|
|
134
|
+
it('floating layers render inside the portal container, so they inherit its theme', async () => {
|
|
135
|
+
const themed = document.createElement('div');
|
|
136
|
+
themed.setAttribute('data-theme', 'dark');
|
|
137
|
+
document.body.append(themed);
|
|
138
|
+
await show(_jsx(PortalContainerProvider, { container: themed, children: _jsx(Popover, { trigger: _jsx(Button, { children: "Open" }), children: _jsx("p", { children: "inside" }) }) }));
|
|
139
|
+
await act(async () => container.querySelector('button')?.click());
|
|
140
|
+
expect(themed.textContent).toContain('inside');
|
|
141
|
+
expect(themed.closest('[data-theme=dark]')).toBe(themed);
|
|
142
|
+
});
|
|
143
|
+
it('Select shows the chosen option and names its control', async () => {
|
|
144
|
+
await show(_jsx(Select, { label: "Breakpoint", value: "b", onValueChange: () => { }, options: [
|
|
145
|
+
{ value: 'a', label: 'Desktop' },
|
|
146
|
+
{ value: 'b', label: 'Tablet' },
|
|
147
|
+
] }));
|
|
148
|
+
const trigger = container.querySelector('button');
|
|
149
|
+
expect(trigger?.getAttribute('aria-label')).toBe('Breakpoint');
|
|
150
|
+
expect(trigger?.textContent).toContain('Tablet');
|
|
151
|
+
});
|
|
152
|
+
it('SegmentedControl: exclusive pressed state, icon-only names, axe', async () => {
|
|
153
|
+
const onValueChange = vi.fn();
|
|
154
|
+
await show(_jsx(SegmentedControl, { label: "Direction", value: "row", onValueChange: onValueChange, options: [
|
|
155
|
+
{ value: 'row', label: 'Row', icon: 'arrow-right', iconOnly: true },
|
|
156
|
+
{ value: 'column', label: 'Column', icon: 'arrow-down', iconOnly: true },
|
|
157
|
+
] }));
|
|
158
|
+
const [row, column] = container.querySelectorAll('button');
|
|
159
|
+
expect(container.querySelector('fieldset')?.getAttribute('aria-label')).toBe('Direction');
|
|
160
|
+
expect(row?.getAttribute('aria-pressed')).toBe('true');
|
|
161
|
+
expect(column?.getAttribute('aria-pressed')).toBe('false');
|
|
162
|
+
expect(column?.getAttribute('aria-label')).toBe('Column');
|
|
163
|
+
await act(async () => column?.click());
|
|
164
|
+
expect(onValueChange).toHaveBeenCalledWith('column');
|
|
165
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
166
|
+
});
|
|
167
|
+
it('NumberUnitInput: arrows step, Shift takes ten, bare numbers stay bare', async () => {
|
|
168
|
+
const onValueChange = vi.fn();
|
|
169
|
+
await show(_jsx(NumberUnitInput, { value: "10px", onValueChange: onValueChange, units: ['px', 'rem'], unitLabel: "Unit", ariaLabel: "Gap" }));
|
|
170
|
+
const input = container.querySelector('input');
|
|
171
|
+
const key = (init) => act(async () => {
|
|
172
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, ...init }));
|
|
173
|
+
});
|
|
174
|
+
await key({ key: 'ArrowUp' });
|
|
175
|
+
await key({ key: 'ArrowDown', shiftKey: true });
|
|
176
|
+
expect(onValueChange.mock.calls).toEqual([['11px'], ['0px']]);
|
|
177
|
+
const select = container.querySelector('select.bd-unit-select');
|
|
178
|
+
expect(select.value).toBe('px');
|
|
179
|
+
expect([...select.options].map((o) => o.value)).toEqual(['px', 'rem']);
|
|
180
|
+
expect(input.value).toBe('10');
|
|
181
|
+
expect(stepNumberText('0.5', 0.1, '')).toBe('0.6');
|
|
182
|
+
expect(stepNumberText('', 1, 'px')).toBe('1px');
|
|
183
|
+
expect(stepNumberText('auto', 1, 'px')).toBeUndefined();
|
|
184
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
185
|
+
});
|
|
186
|
+
it('NumberUnitInput: the menu picks the unit, typed digits keep it, a keyword disables the menu', async () => {
|
|
187
|
+
const onValueChange = vi.fn();
|
|
188
|
+
await show(_jsx(NumberUnitInput, { value: "10px", onValueChange: onValueChange, units: ['px', 'rem'], unitLabel: "Unit", ariaLabel: "Gap" }));
|
|
189
|
+
const select = container.querySelector('select.bd-unit-select');
|
|
190
|
+
await act(async () => {
|
|
191
|
+
const set = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value')?.set;
|
|
192
|
+
set?.call(select, 'rem');
|
|
193
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
194
|
+
});
|
|
195
|
+
expect(onValueChange).toHaveBeenLastCalledWith('10rem');
|
|
196
|
+
await show(_jsx(NumberUnitInput, { value: "auto", onValueChange: onValueChange, units: ['px', 'rem'], unitLabel: "Unit", ariaLabel: "Gap" }));
|
|
197
|
+
expect(container.querySelector('select.bd-unit-select').disabled).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
it('hexOf and ColorInput: the native picker returns a hex colour', async () => {
|
|
200
|
+
expect(hexOf('#abc')).toBe('#aabbcc');
|
|
201
|
+
expect(hexOf('$color.primary')).toBeUndefined();
|
|
202
|
+
const onColorChange = vi.fn();
|
|
203
|
+
await show(_jsx(ColorInput, { color: "#3f5ae0", label: "Colour", onColorChange: onColorChange }));
|
|
204
|
+
const native = container.querySelector('input[type=color]');
|
|
205
|
+
expect(native.value).toBe('#3f5ae0');
|
|
206
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
207
|
+
});
|
|
208
|
+
it('ColorSwatch paints only colour notations, never raw CSS', async () => {
|
|
209
|
+
await show(_jsxs(_Fragment, { children: [_jsx(ColorSwatch, { color: "#3f5ae0" }), _jsx(ColorSwatch, { color: "url(https://x.test/a.png)" }), _jsx(ColorSwatch, { color: undefined })] }));
|
|
210
|
+
const [ok, bad, none] = [...container.querySelectorAll('.bd-swatch')];
|
|
211
|
+
expect(ok?.style.backgroundColor).not.toBe('');
|
|
212
|
+
expect(bad?.getAttribute('style')).toBeNull();
|
|
213
|
+
expect(none?.hasAttribute('data-empty')).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type ToastVariant = 'success' | 'info' | 'warning' | 'error';
|
|
3
|
+
export interface ToastOptions {
|
|
4
|
+
readonly message: string;
|
|
5
|
+
/** Secondary text shown after the message, in a muted colour. */
|
|
6
|
+
readonly detail?: string | undefined;
|
|
7
|
+
readonly variant?: ToastVariant | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Showing a toast with the id of a visible one replaces it (and restarts its timer), so a
|
|
10
|
+
* repeated notice does not pile up.
|
|
11
|
+
*/
|
|
12
|
+
readonly id?: string | undefined;
|
|
13
|
+
/** Milliseconds until it goes away; `null` keeps it until dismissed. Default: `TOAST_MS`. */
|
|
14
|
+
readonly duration?: number | null | undefined;
|
|
15
|
+
/** One optional action, rendered as a button (the toast stays until it is dismissed or the timer ends). */
|
|
16
|
+
readonly action?: {
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly onSelect: () => void;
|
|
19
|
+
} | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface ToastApi {
|
|
22
|
+
/** Shows a toast; returns its id. */
|
|
23
|
+
readonly show: (options: ToastOptions) => string;
|
|
24
|
+
readonly dismiss: (id: string) => void;
|
|
25
|
+
}
|
|
26
|
+
/** How long a toast stays on screen by default. */
|
|
27
|
+
export declare const TOAST_MS = 6000;
|
|
28
|
+
/** Shows short, non-blocking messages in the one toast region. */
|
|
29
|
+
export declare function useToast(): ToastApi;
|
|
30
|
+
/**
|
|
31
|
+
* Mount once, above everything that reports. It renders the single toast region of the editor: a
|
|
32
|
+
* polite `role="status"` list for success, info and warning, and a `role="alert"` list for errors
|
|
33
|
+
* (both always mounted, so screen readers announce what is added). It is the only place in the
|
|
34
|
+
* editor that positions a message with `position: fixed`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ToastProvider({ children }: {
|
|
37
|
+
readonly children: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
//# sourceMappingURL=toast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../src/ui/toast.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAKf,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,2GAA2G;IAC3G,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;CACzF;AAED,MAAM,WAAW,QAAQ;IACvB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,mDAAmD;AACnD,eAAO,MAAM,QAAQ,OAAO,CAAC;AAkB7B,kEAAkE;AAClE,wBAAgB,QAAQ,IAAI,QAAQ,CAInC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAmC3E"}
|
package/dist/ui/toast.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { useT } from '../messages/index.js';
|
|
4
|
+
import { Icon } from './icon.js';
|
|
5
|
+
import { Button, IconButton } from './primitives.js';
|
|
6
|
+
/** How long a toast stays on screen by default. */
|
|
7
|
+
export const TOAST_MS = 6000;
|
|
8
|
+
const ICONS = {
|
|
9
|
+
success: 'circle-check',
|
|
10
|
+
info: 'info',
|
|
11
|
+
warning: 'triangle-alert',
|
|
12
|
+
error: 'circle-alert',
|
|
13
|
+
};
|
|
14
|
+
const ToastContext = createContext(undefined);
|
|
15
|
+
/** Shows short, non-blocking messages in the one toast region. */
|
|
16
|
+
export function useToast() {
|
|
17
|
+
const api = useContext(ToastContext);
|
|
18
|
+
if (api === undefined)
|
|
19
|
+
throw new Error('useToast must be used inside <ToastProvider>');
|
|
20
|
+
return api;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mount once, above everything that reports. It renders the single toast region of the editor: a
|
|
24
|
+
* polite `role="status"` list for success, info and warning, and a `role="alert"` list for errors
|
|
25
|
+
* (both always mounted, so screen readers announce what is added). It is the only place in the
|
|
26
|
+
* editor that positions a message with `position: fixed`.
|
|
27
|
+
*/
|
|
28
|
+
export function ToastProvider({ children }) {
|
|
29
|
+
const [entries, setEntries] = useState([]);
|
|
30
|
+
const counter = useRef(0);
|
|
31
|
+
const dismiss = useCallback((id) => {
|
|
32
|
+
setEntries((current) => current.filter((entry) => entry.id !== id));
|
|
33
|
+
}, []);
|
|
34
|
+
const show = useCallback((options) => {
|
|
35
|
+
counter.current += 1;
|
|
36
|
+
const serial = counter.current;
|
|
37
|
+
const id = options.id ?? `toast-${serial}`;
|
|
38
|
+
const entry = { ...options, id, variant: options.variant ?? 'info', serial };
|
|
39
|
+
setEntries((current) => [...current.filter((other) => other.id !== id), entry]);
|
|
40
|
+
return id;
|
|
41
|
+
}, []);
|
|
42
|
+
const api = useMemo(() => ({ show, dismiss }), [show, dismiss]);
|
|
43
|
+
const list = (errors) => entries
|
|
44
|
+
.filter((entry) => (entry.variant === 'error') === errors)
|
|
45
|
+
.map((entry) => _jsx(ToastItem, { entry: entry, onDismiss: dismiss }, entry.id));
|
|
46
|
+
return (_jsxs(ToastContext.Provider, { value: api, children: [children, _jsxs("div", { className: "bd-toast-region", children: [_jsx("div", { className: "bd-toast-list", role: "status", "aria-live": "polite", children: list(false) }), _jsx("div", { className: "bd-toast-list", role: "alert", children: list(true) })] })] }));
|
|
47
|
+
}
|
|
48
|
+
function ToastItem({ entry, onDismiss, }) {
|
|
49
|
+
const t = useT();
|
|
50
|
+
const { id, serial, variant, action } = entry;
|
|
51
|
+
const duration = entry.duration === undefined ? TOAST_MS : entry.duration;
|
|
52
|
+
const [paused, setPaused] = useState(false);
|
|
53
|
+
const remaining = useRef(duration);
|
|
54
|
+
// A replacement (a new serial) starts the countdown again.
|
|
55
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: serial is the restart trigger
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
remaining.current = duration;
|
|
58
|
+
}, [serial, duration]);
|
|
59
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: serial is the restart trigger
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (paused || remaining.current === null)
|
|
62
|
+
return;
|
|
63
|
+
const started = Date.now();
|
|
64
|
+
const timer = setTimeout(() => onDismiss(id), remaining.current);
|
|
65
|
+
return () => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
if (remaining.current !== null) {
|
|
68
|
+
remaining.current = Math.max(0, remaining.current - (Date.now() - started));
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}, [paused, id, serial, onDismiss]);
|
|
72
|
+
return (_jsxs("div", { className: "bd-toast", "data-variant": variant, onMouseEnter: () => setPaused(true), onMouseLeave: () => setPaused(false), onFocus: () => setPaused(true), onBlur: (event) => {
|
|
73
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
74
|
+
setPaused(false);
|
|
75
|
+
}, children: [_jsx(Icon, { name: ICONS[variant], className: "bd-toast-icon" }), _jsxs("span", { className: "bd-toast-text", children: [entry.message, entry.detail !== undefined && _jsxs("span", { className: "bd-toast-detail", children: [" ", entry.detail] })] }), action !== undefined && (_jsx(Button, { className: "bd-toast-action", onClick: action.onSelect, children: action.label })), _jsx(IconButton, { label: t('toast.dismiss'), icon: "x", className: "bd-toast-close", onClick: () => onDismiss(id) })] }));
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.test.d.ts","sourceRoot":"","sources":["../../src/ui/toast.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
import { act } from 'react';
|
|
4
|
+
import { createRoot } from 'react-dom/client';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { axe } from 'vitest-axe';
|
|
7
|
+
import * as matchers from 'vitest-axe/matchers';
|
|
8
|
+
import { MessagesProvider } from '../messages/index.js';
|
|
9
|
+
import { TOAST_MS, ToastProvider, useToast } from './toast.js';
|
|
10
|
+
expect.extend(matchers);
|
|
11
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
|
+
let container;
|
|
13
|
+
let root;
|
|
14
|
+
let api;
|
|
15
|
+
function Probe() {
|
|
16
|
+
api = useToast();
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
vi.useFakeTimers();
|
|
21
|
+
container = document.createElement('div');
|
|
22
|
+
document.body.append(container);
|
|
23
|
+
root = createRoot(container);
|
|
24
|
+
await act(async () => root.render(_jsx(MessagesProvider, { locale: "en", children: _jsx(ToastProvider, { children: _jsx(Probe, {}) }) })));
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
act(() => root.unmount());
|
|
28
|
+
container.remove();
|
|
29
|
+
vi.useRealTimers();
|
|
30
|
+
});
|
|
31
|
+
const status = () => container.querySelector('.bd-toast-region [role=status]');
|
|
32
|
+
const alert = () => container.querySelector('.bd-toast-region [role=alert]');
|
|
33
|
+
const show = (options) => act(() => void api.show(options));
|
|
34
|
+
const advance = (ms) => act(() => void vi.advanceTimersByTime(ms));
|
|
35
|
+
describe('toast region', () => {
|
|
36
|
+
it('announces success, info and warning politely and errors as alerts', () => {
|
|
37
|
+
expect(status().getAttribute('aria-live')).toBe('polite');
|
|
38
|
+
show({ message: 'Saved', variant: 'success' });
|
|
39
|
+
show({ message: 'Heads up', variant: 'warning' });
|
|
40
|
+
show({ message: 'Broke', variant: 'error' });
|
|
41
|
+
expect(status().textContent).toContain('Saved');
|
|
42
|
+
expect(status().textContent).toContain('Heads up');
|
|
43
|
+
expect(status().textContent).not.toContain('Broke');
|
|
44
|
+
expect(alert().textContent).toContain('Broke');
|
|
45
|
+
expect(container.querySelectorAll('.bd-toast')).toHaveLength(3);
|
|
46
|
+
});
|
|
47
|
+
it('dismisses after the timeout, and a persistent toast stays', () => {
|
|
48
|
+
show({ message: 'Gone soon' });
|
|
49
|
+
show({ message: 'Stays', duration: null });
|
|
50
|
+
advance(TOAST_MS + 10);
|
|
51
|
+
expect(status().textContent).not.toContain('Gone soon');
|
|
52
|
+
expect(status().textContent).toContain('Stays');
|
|
53
|
+
});
|
|
54
|
+
it('pauses the countdown while hovered or focused', () => {
|
|
55
|
+
show({ message: 'Hold on' });
|
|
56
|
+
const toast = () => container.querySelector('.bd-toast');
|
|
57
|
+
advance(TOAST_MS - 1000);
|
|
58
|
+
act(() => void toast().dispatchEvent(new MouseEvent('mouseover', { bubbles: true })));
|
|
59
|
+
advance(TOAST_MS * 3);
|
|
60
|
+
expect(status().textContent).toContain('Hold on');
|
|
61
|
+
act(() => void toast().dispatchEvent(new MouseEvent('mouseout', { bubbles: true })));
|
|
62
|
+
advance(1010);
|
|
63
|
+
expect(status().textContent).not.toContain('Hold on');
|
|
64
|
+
show({ message: 'Focus me' });
|
|
65
|
+
const close = container.querySelector('.bd-toast-close');
|
|
66
|
+
act(() => close.focus());
|
|
67
|
+
advance(TOAST_MS * 3);
|
|
68
|
+
expect(status().textContent).toContain('Focus me');
|
|
69
|
+
});
|
|
70
|
+
it('replaces a toast with the same id and restarts its timer', () => {
|
|
71
|
+
show({ id: 'n', message: 'First' });
|
|
72
|
+
advance(TOAST_MS - 100);
|
|
73
|
+
show({ id: 'n', message: 'Second' });
|
|
74
|
+
advance(1000);
|
|
75
|
+
expect(container.querySelectorAll('.bd-toast')).toHaveLength(1);
|
|
76
|
+
expect(status().textContent).toContain('Second');
|
|
77
|
+
});
|
|
78
|
+
it('dismisses with the close button and runs an action', () => {
|
|
79
|
+
const onSelect = vi.fn();
|
|
80
|
+
show({ message: 'Newer', duration: null, action: { label: 'Reload', onSelect } });
|
|
81
|
+
const reload = [...container.querySelectorAll('.bd-toast button')].find((b) => b.textContent === 'Reload');
|
|
82
|
+
act(() => reload.click());
|
|
83
|
+
expect(onSelect).toHaveBeenCalledOnce();
|
|
84
|
+
const close = container.querySelector('button[aria-label="Dismiss notification"]');
|
|
85
|
+
act(() => close.click());
|
|
86
|
+
expect(container.querySelector('.bd-toast')).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
it('has no axe violations with every variant shown', async () => {
|
|
89
|
+
vi.useRealTimers();
|
|
90
|
+
for (const variant of ['success', 'info', 'warning', 'error']) {
|
|
91
|
+
show({ message: `A ${variant} message`, variant, detail: 'more', duration: null });
|
|
92
|
+
}
|
|
93
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
94
|
+
});
|
|
95
|
+
});
|