@miragon/mcp-toolkit-ui 2.6.0 → 2.8.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/README.md +14 -5
- package/dist/app/adapt-data-widget.d.ts +1 -22
- package/dist/app/adapt-data-widget.d.ts.map +1 -1
- package/dist/app/adapt-data-widget.js +1 -35
- package/dist/app/adapt-data-widget.js.map +1 -1
- package/dist/app/builder/BuilderBanners.d.ts +7 -2
- package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
- package/dist/app/builder/BuilderBanners.js +8 -3
- package/dist/app/builder/BuilderBanners.js.map +1 -1
- package/dist/app/builder/CanvasRow.d.ts +4 -2
- package/dist/app/builder/CanvasRow.d.ts.map +1 -1
- package/dist/app/builder/CanvasRow.js +29 -17
- package/dist/app/builder/CanvasRow.js.map +1 -1
- package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
- package/dist/app/builder/CatalogueSheet.js +14 -6
- package/dist/app/builder/CatalogueSheet.js.map +1 -1
- package/dist/app/builder/CellPropsSheet.d.ts +7 -3
- package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
- package/dist/app/builder/CellPropsSheet.js +10 -3
- package/dist/app/builder/CellPropsSheet.js.map +1 -1
- package/dist/app/builder/DiscardDialog.d.ts +18 -0
- package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
- package/dist/app/builder/DiscardDialog.js +18 -0
- package/dist/app/builder/DiscardDialog.js.map +1 -0
- package/dist/app/builder/EmptyHint.d.ts +4 -0
- package/dist/app/builder/EmptyHint.d.ts.map +1 -1
- package/dist/app/builder/EmptyHint.js +6 -1
- package/dist/app/builder/EmptyHint.js.map +1 -1
- package/dist/app/builder/LayoutSurface.d.ts +33 -9
- package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
- package/dist/app/builder/LayoutSurface.js +74 -23
- package/dist/app/builder/LayoutSurface.js.map +1 -1
- package/dist/app/builder/PipelineStrip.d.ts +5 -2
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +33 -15
- package/dist/app/builder/PipelineStrip.js.map +1 -1
- package/dist/app/builder/SaveDialog.d.ts +9 -1
- package/dist/app/builder/SaveDialog.d.ts.map +1 -1
- package/dist/app/builder/SaveDialog.js +9 -3
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +14 -4
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +24 -7
- package/dist/app/builder/Toolbar.js.map +1 -1
- package/dist/app/builder/Workspace.d.ts +23 -3
- package/dist/app/builder/Workspace.d.ts.map +1 -1
- package/dist/app/builder/Workspace.js +58 -8
- package/dist/app/builder/Workspace.js.map +1 -1
- package/dist/app/builder/builder-model.d.ts +146 -3
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +196 -5
- package/dist/app/builder/builder-model.js.map +1 -1
- package/dist/app/builder/labels.d.ts +13 -62
- package/dist/app/builder/labels.d.ts.map +1 -1
- package/dist/app/builder/labels.js +9 -58
- package/dist/app/builder/labels.js.map +1 -1
- package/dist/app/builder/reveal.d.ts +10 -0
- package/dist/app/builder/reveal.d.ts.map +1 -0
- package/dist/app/builder/reveal.js +10 -0
- package/dist/app/builder/reveal.js.map +1 -0
- package/dist/app/builder/status-chip.d.ts.map +1 -1
- package/dist/app/builder/status-chip.js +3 -2
- package/dist/app/builder/status-chip.js.map +1 -1
- package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +50 -17
- package/dist/app/builder/use-builder-catalogue.js.map +1 -1
- package/dist/app/builder/use-builder-session.d.ts +30 -0
- package/dist/app/builder/use-builder-session.d.ts.map +1 -0
- package/dist/app/builder/use-builder-session.js +58 -0
- package/dist/app/builder/use-builder-session.js.map +1 -0
- package/dist/app/builder/use-display-resolver.d.ts +17 -0
- package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
- package/dist/app/builder/use-display-resolver.js +16 -0
- package/dist/app/builder/use-display-resolver.js.map +1 -0
- package/dist/app/builder/use-save-dashboard.d.ts +3 -1
- package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
- package/dist/app/builder/use-save-dashboard.js +3 -1
- package/dist/app/builder/use-save-dashboard.js.map +1 -1
- package/dist/app/index.d.ts +8 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +8 -2
- package/dist/app/index.js.map +1 -1
- package/dist/app/layout-builder.d.ts +18 -25
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +84 -78
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +103 -20
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +125 -40
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +23 -0
- package/dist/app/mcp-app-view-states.d.ts.map +1 -0
- package/dist/app/mcp-app-view-states.js +55 -0
- package/dist/app/mcp-app-view-states.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +72 -5
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +241 -141
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +8 -2
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +33 -4
- package/dist/app/mcp-toolkit-app.js.map +1 -1
- package/dist/app/mcp-use-host-bridge.d.ts +15 -6
- package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
- package/dist/app/mcp-use-host-bridge.js +51 -22
- package/dist/app/mcp-use-host-bridge.js.map +1 -1
- package/dist/app/model-context.d.ts +71 -0
- package/dist/app/model-context.d.ts.map +1 -0
- package/dist/app/model-context.js +101 -0
- package/dist/app/model-context.js.map +1 -0
- package/dist/app/use-build-mode-focus.d.ts +33 -0
- package/dist/app/use-build-mode-focus.d.ts.map +1 -0
- package/dist/app/use-build-mode-focus.js +59 -0
- package/dist/app/use-build-mode-focus.js.map +1 -0
- package/dist/app/use-view-actions.d.ts +31 -0
- package/dist/app/use-view-actions.d.ts.map +1 -0
- package/dist/app/use-view-actions.js +81 -0
- package/dist/app/use-view-actions.js.map +1 -0
- package/dist/app/use-view-lifecycle.d.ts +42 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -0
- package/dist/app/use-view-lifecycle.js +81 -0
- package/dist/app/use-view-lifecycle.js.map +1 -0
- package/dist/app/view-lifecycle.d.ts +185 -0
- package/dist/app/view-lifecycle.d.ts.map +1 -0
- package/dist/app/view-lifecycle.js +207 -0
- package/dist/app/view-lifecycle.js.map +1 -0
- package/dist/app/widget-fixture.d.ts +10 -3
- package/dist/app/widget-fixture.d.ts.map +1 -1
- package/dist/app/widget-fixture.js +21 -8
- package/dist/app/widget-fixture.js.map +1 -1
- package/dist/app/widget-props-sheet.d.ts +6 -10
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +25 -20
- package/dist/app/widget-props-sheet.js.map +1 -1
- package/dist/app/widget-renderer.d.ts +41 -1
- package/dist/app/widget-renderer.d.ts.map +1 -1
- package/dist/app/widget-renderer.js +82 -31
- package/dist/app/widget-renderer.js.map +1 -1
- package/dist/components/DrillButton.d.ts +9 -6
- package/dist/components/DrillButton.d.ts.map +1 -1
- package/dist/components/DrillButton.js +9 -6
- package/dist/components/DrillButton.js.map +1 -1
- package/dist/components/EmptyState.d.ts +33 -0
- package/dist/components/EmptyState.d.ts.map +1 -0
- package/dist/components/EmptyState.js +19 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorState.d.ts +53 -0
- package/dist/components/ErrorState.d.ts.map +1 -0
- package/dist/components/ErrorState.js +77 -0
- package/dist/components/ErrorState.js.map +1 -0
- package/dist/components/FilterBar.d.ts +16 -5
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +13 -5
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/GroupCard.d.ts.map +1 -1
- package/dist/components/GroupCard.js +2 -1
- package/dist/components/GroupCard.js.map +1 -1
- package/dist/components/KpiGrid.d.ts +70 -18
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +112 -23
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +32 -4
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +35 -6
- package/dist/components/ListFooter.js.map +1 -1
- package/dist/components/PagedListFooter.d.ts +57 -0
- package/dist/components/PagedListFooter.d.ts.map +1 -0
- package/dist/components/PagedListFooter.js +27 -0
- package/dist/components/PagedListFooter.js.map +1 -0
- package/dist/components/Pills.d.ts +36 -12
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +29 -8
- package/dist/components/Pills.js.map +1 -1
- package/dist/components/SectionHeading.d.ts +3 -1
- package/dist/components/SectionHeading.d.ts.map +1 -1
- package/dist/components/SectionHeading.js +6 -2
- package/dist/components/SectionHeading.js.map +1 -1
- package/dist/components/Toast.d.ts +43 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +106 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/WidgetHeader.d.ts +34 -10
- package/dist/components/WidgetHeader.d.ts.map +1 -1
- package/dist/components/WidgetHeader.js +11 -6
- package/dist/components/WidgetHeader.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
- package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
- package/dist/hooks/tool-result-recovery-model.js +273 -0
- package/dist/hooks/tool-result-recovery-model.js.map +1 -0
- package/dist/hooks/use-tool-query.d.ts +56 -14
- package/dist/hooks/use-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-tool-query.js +102 -25
- package/dist/hooks/use-tool-query.js.map +1 -1
- package/dist/hooks/use-tool-result-recovery.d.ts +61 -44
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +62 -110
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +55 -7
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +81 -11
- package/dist/hooks/use-view-data.js.map +1 -1
- package/dist/hooks/use-view-tool-query.d.ts +12 -7
- package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-view-tool-query.js +15 -14
- package/dist/hooks/use-view-tool-query.js.map +1 -1
- package/dist/index.d.ts +27 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/focus-return.d.ts +61 -0
- package/dist/lib/focus-return.d.ts.map +1 -0
- package/dist/lib/focus-return.js +46 -0
- package/dist/lib/focus-return.js.map +1 -0
- package/dist/lib/focus-ring.d.ts +18 -0
- package/dist/lib/focus-ring.d.ts.map +1 -0
- package/dist/lib/focus-ring.js +18 -0
- package/dist/lib/focus-ring.js.map +1 -0
- package/dist/lib/icon-stroke.d.ts +12 -0
- package/dist/lib/icon-stroke.d.ts.map +1 -0
- package/dist/lib/icon-stroke.js +12 -0
- package/dist/lib/icon-stroke.js.map +1 -0
- package/dist/lib/parse-tool-result.d.ts +23 -1
- package/dist/lib/parse-tool-result.d.ts.map +1 -1
- package/dist/lib/parse-tool-result.js +38 -2
- package/dist/lib/parse-tool-result.js.map +1 -1
- package/dist/lib/toast-model.d.ts +86 -0
- package/dist/lib/toast-model.d.ts.map +1 -0
- package/dist/lib/toast-model.js +102 -0
- package/dist/lib/toast-model.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +95 -9
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +100 -9
- package/dist/lib/tone-utils.js.map +1 -1
- package/dist/lib/tool-query.d.ts +107 -0
- package/dist/lib/tool-query.d.ts.map +1 -0
- package/dist/lib/tool-query.js +182 -0
- package/dist/lib/tool-query.js.map +1 -0
- package/dist/lib/tool-result-observer.d.ts +108 -0
- package/dist/lib/tool-result-observer.d.ts.map +1 -0
- package/dist/lib/tool-result-observer.js +158 -0
- package/dist/lib/tool-result-observer.js.map +1 -0
- package/dist/lib/typography.d.ts +37 -0
- package/dist/lib/typography.d.ts.map +1 -0
- package/dist/lib/typography.js +41 -0
- package/dist/lib/typography.js.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +11 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/locale/index.d.ts +3 -1
- package/dist/locale/index.d.ts.map +1 -1
- package/dist/locale/index.js +2 -1
- package/dist/locale/index.js.map +1 -1
- package/dist/locale/locale-provider.d.ts +42 -1
- package/dist/locale/locale-provider.d.ts.map +1 -1
- package/dist/locale/locale-provider.js +44 -3
- package/dist/locale/locale-provider.js.map +1 -1
- package/dist/locale/message-types.d.ts +336 -0
- package/dist/locale/message-types.d.ts.map +1 -0
- package/dist/locale/message-types.js +7 -0
- package/dist/locale/message-types.js.map +1 -0
- package/dist/locale/messages-de.d.ts +9 -0
- package/dist/locale/messages-de.d.ts.map +1 -0
- package/dist/locale/messages-de.js +185 -0
- package/dist/locale/messages-de.js.map +1 -0
- package/dist/locale/messages-en.d.ts +8 -0
- package/dist/locale/messages-en.d.ts.map +1 -0
- package/dist/locale/messages-en.js +184 -0
- package/dist/locale/messages-en.js.map +1 -0
- package/dist/locale/toolkit-messages.d.ts +57 -0
- package/dist/locale/toolkit-messages.d.ts.map +1 -0
- package/dist/locale/toolkit-messages.js +103 -0
- package/dist/locale/toolkit-messages.js.map +1 -0
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +6 -2
- package/dist/primitives/alert.js.map +1 -1
- package/dist/primitives/badge.d.ts +1 -1
- package/dist/primitives/badge.d.ts.map +1 -1
- package/dist/primitives/badge.js +19 -6
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/button.d.ts.map +1 -1
- package/dist/primitives/button.js +12 -8
- package/dist/primitives/button.js.map +1 -1
- package/dist/primitives/card.js +4 -4
- package/dist/primitives/card.js.map +1 -1
- package/dist/primitives/dialog.d.ts +7 -3
- package/dist/primitives/dialog.d.ts.map +1 -1
- package/dist/primitives/dialog.js +22 -8
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/input.d.ts.map +1 -1
- package/dist/primitives/input.js +4 -1
- package/dist/primitives/input.js.map +1 -1
- package/dist/primitives/scroll-area.js +1 -1
- package/dist/primitives/scroll-area.js.map +1 -1
- package/dist/primitives/select.d.ts.map +1 -1
- package/dist/primitives/select.js +6 -3
- package/dist/primitives/select.js.map +1 -1
- package/dist/primitives/sheet.d.ts +3 -1
- package/dist/primitives/sheet.d.ts.map +1 -1
- package/dist/primitives/sheet.js +17 -7
- package/dist/primitives/sheet.js.map +1 -1
- package/dist/primitives/skeleton.js +1 -1
- package/dist/primitives/skeleton.js.map +1 -1
- package/dist/primitives/switch.d.ts.map +1 -1
- package/dist/primitives/switch.js +2 -1
- package/dist/primitives/switch.js.map +1 -1
- package/dist/primitives/tabs.d.ts.map +1 -1
- package/dist/primitives/tabs.js +5 -3
- package/dist/primitives/tabs.js.map +1 -1
- package/dist/providers/query-provider.d.ts +32 -1
- package/dist/providers/query-provider.d.ts.map +1 -1
- package/dist/providers/query-provider.js +53 -4
- package/dist/providers/query-provider.js.map +1 -1
- package/dist/render-cases-primitives.js +1 -1
- package/dist/render-cases-primitives.js.map +1 -1
- package/dist/render-cases.d.ts.map +1 -1
- package/dist/render-cases.js +48 -6
- package/dist/render-cases.js.map +1 -1
- package/dist/render-effects.d.ts +42 -0
- package/dist/render-effects.d.ts.map +1 -0
- package/dist/render-effects.js +81 -0
- package/dist/render-effects.js.map +1 -0
- package/dist/render-scan.d.ts +25 -0
- package/dist/render-scan.d.ts.map +1 -0
- package/dist/render-scan.js +66 -0
- package/dist/render-scan.js.map +1 -0
- package/dist/theme/cd-tokens.d.ts +66 -0
- package/dist/theme/cd-tokens.d.ts.map +1 -0
- package/dist/theme/cd-tokens.js +64 -0
- package/dist/theme/cd-tokens.js.map +1 -0
- package/dist/theme/create-theme.d.ts +214 -39
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +204 -13
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/theme/fonts.d.ts +38 -0
- package/dist/theme/fonts.d.ts.map +1 -0
- package/dist/theme/fonts.js +56 -0
- package/dist/theme/fonts.js.map +1 -0
- package/dist/theme/host-fonts.d.ts +39 -0
- package/dist/theme/host-fonts.d.ts.map +1 -0
- package/dist/theme/host-fonts.js +44 -0
- package/dist/theme/host-fonts.js.map +1 -0
- package/dist/theme/index.d.ts +8 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +5 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/portal-container.d.ts +16 -0
- package/dist/theme/portal-container.d.ts.map +1 -0
- package/dist/theme/portal-container.js +19 -0
- package/dist/theme/portal-container.js.map +1 -0
- package/dist/theme/presets.d.ts +24 -9
- package/dist/theme/presets.d.ts.map +1 -1
- package/dist/theme/presets.js +44 -11
- package/dist/theme/presets.js.map +1 -1
- package/dist/theme/theme-provider.d.ts +27 -9
- package/dist/theme/theme-provider.d.ts.map +1 -1
- package/dist/theme/theme-provider.js +42 -20
- package/dist/theme/theme-provider.js.map +1 -1
- package/package.json +11 -5
- package/src/app/adapt-data-widget.tsx +2 -42
- package/src/app/builder/BuilderBanners.tsx +26 -13
- package/src/app/builder/CanvasRow.tsx +79 -32
- package/src/app/builder/CatalogueSheet.tsx +58 -35
- package/src/app/builder/CellPropsSheet.tsx +17 -2
- package/src/app/builder/DiscardDialog.tsx +59 -0
- package/src/app/builder/EmptyHint.tsx +16 -11
- package/src/app/builder/LayoutSurface.tsx +207 -67
- package/src/app/builder/PipelineStrip.tsx +265 -156
- package/src/app/builder/SaveDialog.tsx +25 -14
- package/src/app/builder/Toolbar.tsx +154 -57
- package/src/app/builder/Workspace.tsx +226 -56
- package/src/app/builder/builder-feedback.test.tsx +75 -0
- package/src/app/builder/builder-model.test.ts +5 -0
- package/src/app/builder/builder-model.ts +346 -6
- package/src/app/builder/builder-model.ux.test.ts +456 -0
- package/src/app/builder/builder-ux.test.tsx +844 -0
- package/src/app/builder/labels.ts +15 -121
- package/src/app/builder/reveal.ts +10 -0
- package/src/app/builder/status-chip.tsx +8 -4
- package/src/app/builder/use-builder-catalogue.ts +62 -19
- package/src/app/builder/use-builder-session.ts +68 -0
- package/src/app/builder/use-display-resolver.ts +24 -0
- package/src/app/builder/use-save-dashboard.ts +5 -0
- package/src/app/index.ts +20 -3
- package/src/app/layout-builder.tsx +193 -186
- package/src/app/mcp-app-view-chrome.test.tsx +487 -14
- package/src/app/mcp-app-view-chrome.tsx +328 -100
- package/src/app/mcp-app-view-states.tsx +140 -0
- package/src/app/mcp-app-view.effects.test.tsx +292 -0
- package/src/app/mcp-app-view.test.tsx +478 -0
- package/src/app/mcp-app-view.tsx +447 -190
- package/src/app/mcp-toolkit-app.test.tsx +40 -0
- package/src/app/mcp-toolkit-app.tsx +39 -8
- package/src/app/mcp-use-host-bridge.test.tsx +180 -0
- package/src/app/mcp-use-host-bridge.tsx +67 -24
- package/src/app/model-context.test.tsx +116 -0
- package/src/app/model-context.tsx +125 -0
- package/src/app/use-build-mode-focus.ts +62 -0
- package/src/app/use-view-actions.ts +89 -0
- package/src/app/use-view-lifecycle.ts +122 -0
- package/src/app/view-lifecycle.test.ts +443 -0
- package/src/app/view-lifecycle.ts +334 -0
- package/src/app/widget-fixture.effects.test.tsx +51 -0
- package/src/app/widget-fixture.render.test.tsx +81 -0
- package/src/app/widget-fixture.tsx +34 -11
- package/src/app/widget-props-sheet.tsx +67 -39
- package/src/app/widget-renderer.render.test.tsx +131 -0
- package/src/app/widget-renderer.tsx +136 -38
- package/src/components/DrillButton.tsx +24 -10
- package/src/components/EmptyState.tsx +83 -0
- package/src/components/ErrorState.tsx +204 -0
- package/src/components/FilterBar.test.tsx +82 -0
- package/src/components/FilterBar.tsx +56 -27
- package/src/components/GroupCard.tsx +3 -2
- package/src/components/KpiGrid.test.tsx +279 -0
- package/src/components/KpiGrid.tsx +329 -91
- package/src/components/ListFooter.test.tsx +116 -0
- package/src/components/ListFooter.tsx +110 -12
- package/src/components/PagedListFooter.test.tsx +140 -0
- package/src/components/PagedListFooter.tsx +107 -0
- package/src/components/Pills.test.tsx +77 -0
- package/src/components/Pills.tsx +78 -16
- package/src/components/SectionHeading.tsx +9 -3
- package/src/components/Toast.tsx +206 -0
- package/src/components/WidgetHeader.test.tsx +78 -0
- package/src/components/WidgetHeader.tsx +63 -16
- package/src/components/feedback-states.test.tsx +314 -0
- package/src/components/tone-figures.test.tsx +58 -0
- package/src/fonts.css +19 -0
- package/src/globals.css +9 -165
- package/src/hooks/index.ts +6 -3
- package/src/hooks/tool-result-recovery-model.test.ts +744 -0
- package/src/hooks/tool-result-recovery-model.ts +418 -0
- package/src/hooks/use-tool-query.effects.test.tsx +205 -0
- package/src/hooks/use-tool-query.ts +156 -33
- package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
- package/src/hooks/use-tool-result-recovery.test.ts +28 -5
- package/src/hooks/use-tool-result-recovery.ts +101 -131
- package/src/hooks/use-view-data.test.tsx +169 -0
- package/src/hooks/use-view-data.ts +131 -10
- package/src/hooks/use-view-tool-query.ts +25 -19
- package/src/i18n-coverage.test.tsx +425 -0
- package/src/index.ts +102 -13
- package/src/lib/focus-return.test.ts +288 -0
- package/src/lib/focus-return.ts +96 -0
- package/src/lib/focus-ring.ts +20 -0
- package/src/lib/icon-stroke.test.ts +59 -0
- package/src/lib/icon-stroke.ts +13 -0
- package/src/lib/parse-tool-result.test.ts +46 -1
- package/src/lib/parse-tool-result.ts +40 -2
- package/src/lib/toast-model.test.ts +233 -0
- package/src/lib/toast-model.ts +165 -0
- package/src/lib/tone-utils.test.ts +125 -16
- package/src/lib/tone-utils.ts +144 -15
- package/src/lib/tool-query.test.ts +569 -0
- package/src/lib/tool-query.ts +253 -0
- package/src/lib/tool-result-observer.test.ts +296 -0
- package/src/lib/tool-result-observer.ts +211 -0
- package/src/lib/typography.test.ts +45 -0
- package/src/lib/typography.ts +47 -0
- package/src/lib/utils.ts +12 -1
- package/src/locale/index.ts +14 -1
- package/src/locale/locale-provider.test.tsx +87 -0
- package/src/locale/locale-provider.tsx +90 -3
- package/src/locale/message-types.ts +337 -0
- package/src/locale/messages-de.ts +198 -0
- package/src/locale/messages-en.ts +196 -0
- package/src/locale/toolkit-messages.test.ts +349 -0
- package/src/locale/toolkit-messages.ts +143 -0
- package/src/no-hardcoded-copy.test.ts +432 -0
- package/src/primitives/alert.tsx +7 -2
- package/src/primitives/badge.tsx +19 -7
- package/src/primitives/button.tsx +13 -8
- package/src/primitives/card.tsx +4 -4
- package/src/primitives/dialog.tsx +42 -8
- package/src/primitives/input.tsx +5 -3
- package/src/primitives/scroll-area.tsx +1 -1
- package/src/primitives/select.tsx +7 -4
- package/src/primitives/sheet.tsx +33 -6
- package/src/primitives/skeleton.tsx +1 -1
- package/src/primitives/switch.tsx +4 -2
- package/src/primitives/tabs.tsx +9 -6
- package/src/providers/query-provider.test.tsx +154 -0
- package/src/providers/query-provider.tsx +66 -5
- package/src/render-cases-primitives.tsx +1 -1
- package/src/render-cases.tsx +82 -6
- package/src/render-effects.tsx +96 -0
- package/src/render-scan.ts +75 -0
- package/src/theme/cd-tokens.generated.css +48 -0
- package/src/theme/cd-tokens.test.ts +109 -0
- package/src/theme/cd-tokens.ts +67 -0
- package/src/theme/create-theme.test.ts +253 -5
- package/src/theme/create-theme.ts +341 -41
- package/src/theme/fonts.test.ts +85 -0
- package/src/theme/fonts.ts +72 -0
- package/src/theme/host-fonts.test.ts +135 -0
- package/src/theme/host-fonts.ts +52 -0
- package/src/theme/index.ts +15 -1
- package/src/theme/portal-container.ts +20 -0
- package/src/theme/presets.ts +44 -11
- package/src/theme/theme-css.test.ts +619 -0
- package/src/theme/theme-provider.test.tsx +82 -0
- package/src/theme/theme-provider.tsx +60 -24
- package/src/theme.css +344 -0
- package/ui-catalog.json +783 -49
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
createToast,
|
|
4
|
+
createToastTimer,
|
|
5
|
+
MAX_VISIBLE_TOASTS,
|
|
6
|
+
resolveToastTone,
|
|
7
|
+
TOAST_DURATION_MS,
|
|
8
|
+
TOAST_ERROR_DURATION_MS,
|
|
9
|
+
toastDuration,
|
|
10
|
+
toastReducer,
|
|
11
|
+
type ToastClock,
|
|
12
|
+
type ToastItem,
|
|
13
|
+
type ToastOptions,
|
|
14
|
+
} from "./toast-model.js"
|
|
15
|
+
|
|
16
|
+
const item = (id: number): ToastItem => createToast(id, { title: `t${id}` })
|
|
17
|
+
|
|
18
|
+
describe("toastDuration (CI §8: about 2.8 s, errors about 5 s)", () => {
|
|
19
|
+
it("defaults to 2.8 s and gives errors 5 s", () => {
|
|
20
|
+
expect(TOAST_DURATION_MS).toBe(2800)
|
|
21
|
+
expect(TOAST_ERROR_DURATION_MS).toBe(5000)
|
|
22
|
+
expect(toastDuration("success")).toBe(2800)
|
|
23
|
+
expect(toastDuration("info")).toBe(2800)
|
|
24
|
+
expect(toastDuration("warning")).toBe(2800)
|
|
25
|
+
expect(toastDuration("danger")).toBe(5000)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("honours an explicit positive duration and ignores an unusable one", () => {
|
|
29
|
+
expect(toastDuration("danger", 1200)).toBe(1200)
|
|
30
|
+
expect(toastDuration("info", 0)).toBe(2800)
|
|
31
|
+
expect(toastDuration("info", -5)).toBe(2800)
|
|
32
|
+
expect(toastDuration("danger", Number.NaN)).toBe(5000)
|
|
33
|
+
expect(toastDuration("danger", Number.POSITIVE_INFINITY)).toBe(5000)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe("createToast", () => {
|
|
38
|
+
it("defaults to the info tone and drops an empty description", () => {
|
|
39
|
+
expect(createToast(1, { title: "Saved" })).toEqual({
|
|
40
|
+
id: 1,
|
|
41
|
+
tone: "info",
|
|
42
|
+
title: "Saved",
|
|
43
|
+
duration: 2800,
|
|
44
|
+
})
|
|
45
|
+
expect(createToast(2, { title: "x", description: "" })).not.toHaveProperty("description")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("keeps tone, description and the tone's duration", () => {
|
|
49
|
+
expect(createToast(3, { tone: "danger", title: "Failed", description: "503" })).toEqual({
|
|
50
|
+
id: 3,
|
|
51
|
+
tone: "danger",
|
|
52
|
+
title: "Failed",
|
|
53
|
+
description: "503",
|
|
54
|
+
duration: 5000,
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("resolves an untyped retired tone instead of keeping a name no icon exists for", () => {
|
|
59
|
+
// Plain JS or wire data: `error` is `danger`, with the error toast's 5 s.
|
|
60
|
+
const fromWire = { tone: "error", title: "Failed" } as unknown as ToastOptions
|
|
61
|
+
expect(createToast(4, fromWire)).toMatchObject({ tone: "danger", duration: 5000 })
|
|
62
|
+
const unknown = { tone: "purple", title: "x" } as unknown as ToastOptions
|
|
63
|
+
expect(createToast(5, unknown).tone).toBe("info")
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe("resolveToastTone (fail-soft for an untyped tone)", () => {
|
|
68
|
+
it("keeps the four toast tones and maps a retired name through resolveTone", () => {
|
|
69
|
+
for (const tone of ["success", "warning", "danger", "info"] as const) {
|
|
70
|
+
expect(resolveToastTone(tone)).toBe(tone)
|
|
71
|
+
}
|
|
72
|
+
expect(resolveToastTone("critical")).toBe("danger")
|
|
73
|
+
expect(resolveToastTone("Warn")).toBe("warning")
|
|
74
|
+
expect(resolveToastTone("ok")).toBe("success")
|
|
75
|
+
expect(resolveToastTone("notice")).toBe("info")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("shows neutral, unknown and missing tones as info (a toast has no neutral edge)", () => {
|
|
79
|
+
expect(resolveToastTone("neutral")).toBe("info")
|
|
80
|
+
expect(resolveToastTone("secondary")).toBe("info")
|
|
81
|
+
expect(resolveToastTone("purple")).toBe("info")
|
|
82
|
+
expect(resolveToastTone(undefined)).toBe("info")
|
|
83
|
+
expect(resolveToastTone(42)).toBe("info")
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe("toastReducer", () => {
|
|
88
|
+
it("appends and keeps at most the newest MAX_VISIBLE_TOASTS", () => {
|
|
89
|
+
expect(MAX_VISIBLE_TOASTS).toBe(3)
|
|
90
|
+
let state: ToastItem[] = []
|
|
91
|
+
for (let id = 1; id <= 4; id++) state = toastReducer(state, { type: "add", toast: item(id) })
|
|
92
|
+
expect(state.map((t) => t.id)).toEqual([2, 3, 4])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("dismisses by id and keeps the list identity when nothing matches", () => {
|
|
96
|
+
const state = [item(1), item(2)]
|
|
97
|
+
expect(toastReducer(state, { type: "dismiss", id: 1 }).map((t) => t.id)).toEqual([2])
|
|
98
|
+
expect(toastReducer(state, { type: "dismiss", id: 9 })).toBe(state)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe("createToastTimer (a held toast resumes its REMAINING time)", () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
vi.useFakeTimers()
|
|
105
|
+
})
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
vi.useRealTimers()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("expires once, after the full duration, when nothing holds it", () => {
|
|
111
|
+
const onExpire = vi.fn()
|
|
112
|
+
const timer = createToastTimer(2800, onExpire)
|
|
113
|
+
expect(timer.remaining()).toBe(2800)
|
|
114
|
+
vi.advanceTimersByTime(2799)
|
|
115
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
116
|
+
expect(timer.remaining()).toBe(1)
|
|
117
|
+
vi.advanceTimersByTime(1)
|
|
118
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
119
|
+
expect(timer.remaining()).toBe(0)
|
|
120
|
+
vi.advanceTimersByTime(10_000)
|
|
121
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("pauses while held and resumes with the time that was left, not a fresh duration", () => {
|
|
125
|
+
const onExpire = vi.fn()
|
|
126
|
+
const timer = createToastTimer(2800, onExpire)
|
|
127
|
+
vi.advanceTimersByTime(1000)
|
|
128
|
+
timer.hold("pointer")
|
|
129
|
+
expect(timer.remaining()).toBe(1800)
|
|
130
|
+
vi.advanceTimersByTime(60_000) // reading for a minute: nothing runs out
|
|
131
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
132
|
+
expect(timer.remaining()).toBe(1800)
|
|
133
|
+
timer.release("pointer")
|
|
134
|
+
vi.advanceTimersByTime(1799)
|
|
135
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
136
|
+
vi.advanceTimersByTime(1)
|
|
137
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it("keeps counting down across several holds (each resume continues the rest)", () => {
|
|
141
|
+
const onExpire = vi.fn()
|
|
142
|
+
const timer = createToastTimer(5000, onExpire)
|
|
143
|
+
vi.advanceTimersByTime(2000)
|
|
144
|
+
timer.hold("pointer")
|
|
145
|
+
vi.advanceTimersByTime(500)
|
|
146
|
+
timer.release("pointer")
|
|
147
|
+
vi.advanceTimersByTime(2000)
|
|
148
|
+
timer.hold("focus")
|
|
149
|
+
expect(timer.remaining()).toBe(1000)
|
|
150
|
+
timer.release("focus")
|
|
151
|
+
vi.advanceTimersByTime(999)
|
|
152
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
153
|
+
vi.advanceTimersByTime(1)
|
|
154
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("stays held until EVERY reason is released (hover and keyboard focus overlap)", () => {
|
|
158
|
+
const onExpire = vi.fn()
|
|
159
|
+
const timer = createToastTimer(1000, onExpire)
|
|
160
|
+
timer.hold("pointer")
|
|
161
|
+
timer.hold("focus")
|
|
162
|
+
timer.hold("focus")
|
|
163
|
+
timer.release("pointer")
|
|
164
|
+
vi.advanceTimersByTime(5000)
|
|
165
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
166
|
+
timer.release("unknown")
|
|
167
|
+
vi.advanceTimersByTime(5000)
|
|
168
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
169
|
+
timer.release("focus")
|
|
170
|
+
vi.advanceTimersByTime(1000)
|
|
171
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it("never fires after cancel, released or not", () => {
|
|
175
|
+
const onExpire = vi.fn()
|
|
176
|
+
const timer = createToastTimer(1000, onExpire)
|
|
177
|
+
vi.advanceTimersByTime(400)
|
|
178
|
+
timer.cancel()
|
|
179
|
+
expect(timer.remaining()).toBe(600)
|
|
180
|
+
timer.release("pointer")
|
|
181
|
+
vi.advanceTimersByTime(5000)
|
|
182
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
183
|
+
const held = createToastTimer(1000, onExpire)
|
|
184
|
+
held.hold("focus")
|
|
185
|
+
held.cancel()
|
|
186
|
+
held.release("focus")
|
|
187
|
+
vi.advanceTimersByTime(5000)
|
|
188
|
+
expect(onExpire).not.toHaveBeenCalled()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it("expires at once for a non-positive duration", () => {
|
|
192
|
+
const onExpire = vi.fn()
|
|
193
|
+
const timer = createToastTimer(-50, onExpire)
|
|
194
|
+
expect(timer.remaining()).toBe(0)
|
|
195
|
+
vi.advanceTimersByTime(0)
|
|
196
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("runs on an injected clock and clears the handle it scheduled", () => {
|
|
200
|
+
let now = 100
|
|
201
|
+
const pending = new Map<number, () => void>()
|
|
202
|
+
let nextHandle = 0
|
|
203
|
+
const cleared: unknown[] = []
|
|
204
|
+
const scheduled: number[] = []
|
|
205
|
+
const clock: ToastClock = {
|
|
206
|
+
now: () => now,
|
|
207
|
+
setTimeout: (callback, ms) => {
|
|
208
|
+
nextHandle += 1
|
|
209
|
+
pending.set(nextHandle, callback)
|
|
210
|
+
scheduled.push(ms)
|
|
211
|
+
return nextHandle
|
|
212
|
+
},
|
|
213
|
+
clearTimeout: (handle) => {
|
|
214
|
+
cleared.push(handle)
|
|
215
|
+
pending.delete(handle as number)
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
const onExpire = vi.fn()
|
|
219
|
+
const timer = createToastTimer(2800, onExpire, clock)
|
|
220
|
+
expect(scheduled).toEqual([2800])
|
|
221
|
+
now += 800
|
|
222
|
+
timer.hold("pointer")
|
|
223
|
+
expect(cleared).toEqual([1])
|
|
224
|
+
expect(pending.size).toBe(0)
|
|
225
|
+
now += 10_000
|
|
226
|
+
timer.release("pointer")
|
|
227
|
+
expect(scheduled).toEqual([2800, 2000])
|
|
228
|
+
now += 2000
|
|
229
|
+
pending.get(2)?.()
|
|
230
|
+
expect(onExpire).toHaveBeenCalledTimes(1)
|
|
231
|
+
expect(timer.remaining()).toBe(0)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure model behind `ToastProvider` / `useToast`: the toast list as a reducer
|
|
3
|
+
* plus the timing rules, so both are testable without a DOM (the house path,
|
|
4
|
+
* FITNESS.md) and the component stays a thin view over them.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { resolveTone } from "./tone-utils.js"
|
|
8
|
+
|
|
9
|
+
/** Status tone of a toast — the four status roles (`--success` … `--info`). */
|
|
10
|
+
export type ToastTone = "success" | "warning" | "danger" | "info"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The toast tone of any tone name: the four status tones as is, a retired
|
|
14
|
+
* name through `resolveTone` (`error` → `danger`), and `neutral` or anything
|
|
15
|
+
* unknown as `info`. Keeps a toast whose tone arrives untyped (plain JS, wire
|
|
16
|
+
* data) on the screen instead of crashing the view on a missing icon.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveToastTone(name: unknown): ToastTone {
|
|
19
|
+
const tone = resolveTone(name, "info")
|
|
20
|
+
return tone === "neutral" ? "info" : tone
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What a caller passes to `useToast().show(...)`. */
|
|
24
|
+
export interface ToastOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Status tone; drives the edge, the icon and the default duration. Default
|
|
27
|
+
* `"info"`. An untyped retired name still resolves ({@link resolveToastTone}).
|
|
28
|
+
*/
|
|
29
|
+
tone?: ToastTone
|
|
30
|
+
/** The message. Say what happened, in one short sentence ("Job retried."). */
|
|
31
|
+
title: string
|
|
32
|
+
/** Optional second line (what you can do next, or the affected object). */
|
|
33
|
+
description?: string
|
|
34
|
+
/** Auto-dismiss after this many ms. Defaults per {@link toastDuration}. */
|
|
35
|
+
duration?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A toast as it sits in the list. */
|
|
39
|
+
export interface ToastItem {
|
|
40
|
+
id: number
|
|
41
|
+
tone: ToastTone
|
|
42
|
+
title: string
|
|
43
|
+
description?: string
|
|
44
|
+
duration: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Default lifetime of a toast (CI §8: about 2.8 s). */
|
|
48
|
+
export const TOAST_DURATION_MS = 2800
|
|
49
|
+
/** Lifetime of an error toast (CI §8: about 5 s) — long enough to read the reason. */
|
|
50
|
+
export const TOAST_ERROR_DURATION_MS = 5000
|
|
51
|
+
/** At most this many toasts stack; the oldest leaves first. */
|
|
52
|
+
export const MAX_VISIBLE_TOASTS = 3
|
|
53
|
+
|
|
54
|
+
/** The lifetime of a toast: an explicit positive duration, else the tone's default. */
|
|
55
|
+
export function toastDuration(tone: ToastTone, explicit?: number): number {
|
|
56
|
+
if (explicit !== undefined && Number.isFinite(explicit) && explicit > 0) return explicit
|
|
57
|
+
return tone === "danger" ? TOAST_ERROR_DURATION_MS : TOAST_DURATION_MS
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Normalise caller options into a list item. */
|
|
61
|
+
export function createToast(id: number, options: ToastOptions): ToastItem {
|
|
62
|
+
const tone = resolveToastTone(options.tone)
|
|
63
|
+
return {
|
|
64
|
+
id,
|
|
65
|
+
tone,
|
|
66
|
+
title: options.title,
|
|
67
|
+
...(options.description ? { description: options.description } : {}),
|
|
68
|
+
duration: toastDuration(tone, options.duration),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ToastAction = { type: "add"; toast: ToastItem } | { type: "dismiss"; id: number }
|
|
73
|
+
|
|
74
|
+
/** Add appends (dropping the oldest beyond {@link MAX_VISIBLE_TOASTS}); dismiss removes by id. */
|
|
75
|
+
export function toastReducer(state: readonly ToastItem[], action: ToastAction): ToastItem[] {
|
|
76
|
+
if (action.type === "add") return [...state, action.toast].slice(-MAX_VISIBLE_TOASTS)
|
|
77
|
+
const next = state.filter((t) => t.id !== action.id)
|
|
78
|
+
return next.length === state.length ? (state as ToastItem[]) : next
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The clock a {@link createToastTimer} runs on. The default reads the global
|
|
83
|
+
* timers at call time, so fake timers installed in a test take effect.
|
|
84
|
+
*/
|
|
85
|
+
export interface ToastClock {
|
|
86
|
+
now: () => number
|
|
87
|
+
setTimeout: (callback: () => void, ms: number) => unknown
|
|
88
|
+
clearTimeout: (handle: unknown) => void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const SYSTEM_CLOCK: ToastClock = {
|
|
92
|
+
now: () => Date.now(),
|
|
93
|
+
setTimeout: (callback, ms) => globalThis.setTimeout(callback, ms),
|
|
94
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The countdown of one toast (see {@link createToastTimer}). */
|
|
98
|
+
export interface ToastTimer {
|
|
99
|
+
/**
|
|
100
|
+
* Hold the toast for `reason` (`"pointer"`, `"focus"`): the countdown
|
|
101
|
+
* pauses and keeps the time that was left.
|
|
102
|
+
*/
|
|
103
|
+
hold: (reason: string) => void
|
|
104
|
+
/** Release `reason`; once nothing holds the toast, it resumes with the time that was left. */
|
|
105
|
+
release: (reason: string) => void
|
|
106
|
+
/** The time left in ms — frozen while held. */
|
|
107
|
+
remaining: () => number
|
|
108
|
+
/** Stop for good (the toast left early or unmounted); `onExpire` never fires afterwards. */
|
|
109
|
+
cancel: () => void
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A pausable countdown: starts at once, pauses while anything holds it
|
|
114
|
+
* (hover, keyboard focus — so a reader can finish), and on release RESUMES
|
|
115
|
+
* WITH THE REMAINING TIME instead of restarting the full duration. Fires
|
|
116
|
+
* `onExpire` once.
|
|
117
|
+
*/
|
|
118
|
+
export function createToastTimer(
|
|
119
|
+
duration: number,
|
|
120
|
+
onExpire: () => void,
|
|
121
|
+
clock: ToastClock = SYSTEM_CLOCK,
|
|
122
|
+
): ToastTimer {
|
|
123
|
+
let left = Math.max(duration, 0)
|
|
124
|
+
let startedAt = 0
|
|
125
|
+
let handle: unknown
|
|
126
|
+
let running = false
|
|
127
|
+
let finished = false
|
|
128
|
+
const holds = new Set<string>()
|
|
129
|
+
|
|
130
|
+
const elapsed = () => (running ? clock.now() - startedAt : 0)
|
|
131
|
+
const start = () => {
|
|
132
|
+
if (finished || running || holds.size > 0) return
|
|
133
|
+
running = true
|
|
134
|
+
startedAt = clock.now()
|
|
135
|
+
handle = clock.setTimeout(() => {
|
|
136
|
+
running = false
|
|
137
|
+
finished = true
|
|
138
|
+
left = 0
|
|
139
|
+
onExpire()
|
|
140
|
+
}, left)
|
|
141
|
+
}
|
|
142
|
+
const pause = () => {
|
|
143
|
+
if (!running) return
|
|
144
|
+
left = Math.max(left - elapsed(), 0)
|
|
145
|
+
running = false
|
|
146
|
+
clock.clearTimeout(handle)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
start()
|
|
150
|
+
return {
|
|
151
|
+
hold: (reason) => {
|
|
152
|
+
holds.add(reason)
|
|
153
|
+
pause()
|
|
154
|
+
},
|
|
155
|
+
release: (reason) => {
|
|
156
|
+
holds.delete(reason)
|
|
157
|
+
start()
|
|
158
|
+
},
|
|
159
|
+
remaining: () => Math.max(left - elapsed(), 0),
|
|
160
|
+
cancel: () => {
|
|
161
|
+
pause()
|
|
162
|
+
finished = true
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -1,35 +1,144 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isToneVariant,
|
|
4
|
+
resolveTone,
|
|
5
|
+
TONE_ALIASES,
|
|
6
|
+
TONE_BORDER,
|
|
7
|
+
TONE_DOT,
|
|
8
|
+
TONE_ICON,
|
|
9
|
+
TONE_INK,
|
|
10
|
+
TONE_SOFT,
|
|
11
|
+
TONE_TEXT,
|
|
12
|
+
TONE_TINT,
|
|
13
|
+
TONE_VARIANTS,
|
|
14
|
+
type ToneVariant,
|
|
15
|
+
} from "./tone-utils.js"
|
|
3
16
|
|
|
4
17
|
const ALL_TONES: ToneVariant[] = ["neutral", "info", "success", "warning", "danger"]
|
|
18
|
+
const STATUS_TONES = ["info", "success", "warning", "danger"] as const
|
|
19
|
+
const ALL_MAPS = { TONE_SOFT, TONE_TINT, TONE_DOT, TONE_INK, TONE_BORDER, TONE_ICON }
|
|
5
20
|
|
|
6
21
|
describe("tone-utils", () => {
|
|
22
|
+
it("has ONE vocabulary of five tones, in severity order", () => {
|
|
23
|
+
expect(TONE_VARIANTS).toEqual(["danger", "warning", "success", "info", "neutral"])
|
|
24
|
+
expect([...TONE_VARIANTS].sort()).toEqual([...ALL_TONES].sort())
|
|
25
|
+
})
|
|
26
|
+
|
|
7
27
|
it("covers every ToneVariant in each map (no missing tones)", () => {
|
|
8
|
-
for (const map of
|
|
28
|
+
for (const map of Object.values(ALL_MAPS)) {
|
|
9
29
|
expect(Object.keys(map).sort()).toEqual([...ALL_TONES].sort())
|
|
10
30
|
}
|
|
11
31
|
})
|
|
12
32
|
|
|
13
33
|
it("maps each tone to a non-empty class string in every map", () => {
|
|
14
|
-
for (const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
for (const [name, map] of Object.entries(ALL_MAPS)) {
|
|
35
|
+
for (const tone of ALL_TONES) {
|
|
36
|
+
expect(map[tone], `${name}:${tone}`).toBeTruthy()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("routes each status tone to its three roles (colour, tint, ink)", () => {
|
|
42
|
+
for (const tone of STATUS_TONES) {
|
|
43
|
+
expect(TONE_SOFT[tone]).toBe(`bg-${tone}-soft text-foreground`)
|
|
44
|
+
expect(TONE_TINT[tone]).toBe(`bg-${tone}-soft`)
|
|
45
|
+
expect(TONE_INK[tone]).toBe(`text-${tone}-ink`)
|
|
46
|
+
expect(TONE_DOT[tone]).toBe(`bg-${tone}`)
|
|
47
|
+
expect(TONE_BORDER[tone]).toBe(`border-${tone}`)
|
|
48
|
+
expect(TONE_ICON[tone]).toBe(`text-${tone}`)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("keeps TONE_TEXT as the deprecated name of the very same TONE_INK map", () => {
|
|
53
|
+
expect(TONE_TEXT).toBe(TONE_INK)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("never sets text in a raw status colour outside the icon map (CI §3.3)", () => {
|
|
57
|
+
// Success and warning reach 1.67:1 / 1.83:1 on white; text takes the
|
|
58
|
+
// tone's -ink role. TONE_ICON is the one deliberate exception (SVG
|
|
59
|
+
// currentColor, always next to a readable label).
|
|
60
|
+
const rawStatusText = /\btext-(success|warning|danger|info)(?![-\w])/
|
|
61
|
+
for (const [name, map] of Object.entries({
|
|
62
|
+
TONE_SOFT,
|
|
63
|
+
TONE_TINT,
|
|
64
|
+
TONE_DOT,
|
|
65
|
+
TONE_INK,
|
|
66
|
+
TONE_BORDER,
|
|
67
|
+
})) {
|
|
68
|
+
for (const tone of ALL_TONES) {
|
|
69
|
+
expect(map[tone], `${name}:${tone}`).not.toMatch(rawStatusText)
|
|
70
|
+
}
|
|
18
71
|
}
|
|
19
72
|
})
|
|
20
73
|
|
|
21
|
-
it("
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
74
|
+
it("puts the foreground on every tint, never the ink or the status colour (CI §3.3)", () => {
|
|
75
|
+
// "-soft: Text darauf --cd-schwarz, Rand in der Statusfarbe" — the rim
|
|
76
|
+
// (TONE_BORDER) carries the tone; a neutral count on the muted tint is a
|
|
77
|
+
// figure and stays in the foreground too.
|
|
78
|
+
for (const tone of ALL_TONES) {
|
|
79
|
+
expect(TONE_SOFT[tone]).toBe(`${TONE_TINT[tone]} text-foreground`)
|
|
80
|
+
expect(TONE_TINT[tone]).not.toMatch(/\btext-/)
|
|
81
|
+
}
|
|
28
82
|
})
|
|
29
83
|
|
|
30
|
-
it("routes the neutral tone to the
|
|
31
|
-
expect(TONE_SOFT.neutral).toBe("bg-muted text-
|
|
84
|
+
it("routes the neutral tone to the muted ramp", () => {
|
|
85
|
+
expect(TONE_SOFT.neutral).toBe("bg-muted text-foreground")
|
|
86
|
+
expect(TONE_TINT.neutral).toBe("bg-muted")
|
|
32
87
|
expect(TONE_DOT.neutral).toBe("bg-muted-foreground")
|
|
33
|
-
expect(
|
|
88
|
+
expect(TONE_INK.neutral).toBe("text-foreground")
|
|
89
|
+
expect(TONE_BORDER.neutral).toBe("border-border")
|
|
90
|
+
expect(TONE_ICON.neutral).toBe("text-muted-foreground")
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe("the tone vocabulary: retired names map onto the five tones", () => {
|
|
95
|
+
it("pins every alias (one red, one amber: critical/error/destructive are danger)", () => {
|
|
96
|
+
expect(TONE_ALIASES).toEqual({
|
|
97
|
+
critical: "danger",
|
|
98
|
+
error: "danger",
|
|
99
|
+
destructive: "danger",
|
|
100
|
+
failure: "danger",
|
|
101
|
+
warn: "warning",
|
|
102
|
+
ok: "success",
|
|
103
|
+
positive: "success",
|
|
104
|
+
notice: "info",
|
|
105
|
+
default: "neutral",
|
|
106
|
+
muted: "neutral",
|
|
107
|
+
secondary: "neutral",
|
|
108
|
+
})
|
|
109
|
+
for (const tone of Object.values(TONE_ALIASES)) expect(isToneVariant(tone)).toBe(true)
|
|
110
|
+
for (const alias of Object.keys(TONE_ALIASES)) expect(isToneVariant(alias)).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("keeps a tone as it is", () => {
|
|
114
|
+
for (const tone of ALL_TONES) expect(resolveTone(tone)).toBe(tone)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("maps every alias onto its tone, ignoring case and padding", () => {
|
|
118
|
+
for (const [alias, tone] of Object.entries(TONE_ALIASES)) {
|
|
119
|
+
expect(resolveTone(alias), alias).toBe(tone)
|
|
120
|
+
}
|
|
121
|
+
expect(resolveTone("Critical")).toBe("danger")
|
|
122
|
+
expect(resolveTone(" WARN ")).toBe("warning")
|
|
123
|
+
expect(resolveTone(" Danger")).toBe("danger")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it("falls back to neutral (or the caller's fallback) for anything else", () => {
|
|
127
|
+
for (const unknown of ["", " ", "purple", "constructor", "toString", "__proto__"]) {
|
|
128
|
+
expect(resolveTone(unknown), JSON.stringify(unknown)).toBe("neutral")
|
|
129
|
+
}
|
|
130
|
+
for (const notAString of [undefined, null, 3, true, {}, ["danger"]]) {
|
|
131
|
+
expect(resolveTone(notAString)).toBe("neutral")
|
|
132
|
+
}
|
|
133
|
+
expect(resolveTone("purple", "info")).toBe("info")
|
|
134
|
+
expect(resolveTone(undefined, "warning")).toBe("warning")
|
|
135
|
+
expect(resolveTone("critical", "info")).toBe("danger")
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it("isToneVariant accepts exactly the five tones", () => {
|
|
139
|
+
for (const tone of ALL_TONES) expect(isToneVariant(tone)).toBe(true)
|
|
140
|
+
for (const other of ["Danger", "critical", "", undefined, null, 1, { tone: "info" }]) {
|
|
141
|
+
expect(isToneVariant(other)).toBe(false)
|
|
142
|
+
}
|
|
34
143
|
})
|
|
35
144
|
})
|