@xemahq/ui-kernel 0.9.2 → 0.11.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/dist/lib/biome-host/agent-validation.d.ts.map +1 -1
- package/dist/lib/biome-host/agent-validation.js +26 -13
- package/dist/lib/biome-host/agent-validation.js.map +1 -1
- package/dist/lib/biome-host/biome-registry.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-registry.js +27 -10
- package/dist/lib/biome-host/biome-registry.js.map +1 -1
- package/dist/lib/biome-host/biome-scope-hooks.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-scope-hooks.js +2 -1
- package/dist/lib/biome-host/biome-scope-hooks.js.map +1 -1
- package/dist/lib/biome-host/biome-scoped-query.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-scoped-query.js +5 -3
- package/dist/lib/biome-host/biome-scoped-query.js.map +1 -1
- package/dist/lib/biome-host/frontend-biome.d.ts +1 -1
- package/dist/lib/biome-host/frontend-biome.d.ts.map +1 -1
- package/dist/lib/biome-host/host-bridge.d.ts +1 -1
- package/dist/lib/biome-host/host-bridge.d.ts.map +1 -1
- package/dist/lib/biome-host/host-bridge.js.map +1 -1
- package/dist/lib/biome-host/i18n-hooks.d.ts.map +1 -1
- package/dist/lib/biome-host/i18n-hooks.js.map +1 -1
- package/dist/lib/biome-host/index.d.ts +0 -1
- package/dist/lib/biome-host/index.d.ts.map +1 -1
- package/dist/lib/biome-host/index.js +0 -1
- package/dist/lib/biome-host/index.js.map +1 -1
- package/dist/lib/biome-host/realtime-hooks.d.ts.map +1 -1
- package/dist/lib/biome-host/realtime-hooks.js.map +1 -1
- package/dist/lib/biome-host/session-contributions.d.ts +10 -5
- package/dist/lib/biome-host/session-contributions.d.ts.map +1 -1
- package/dist/lib/biome-host/use-mutation-with-error-toast.d.ts.map +1 -1
- package/dist/lib/biome-host/use-mutation-with-error-toast.js +3 -2
- package/dist/lib/biome-host/use-mutation-with-error-toast.js.map +1 -1
- package/dist/lib/biome-host/use-page-state.d.ts.map +1 -1
- package/dist/lib/biome-host/use-page-state.js +19 -10
- package/dist/lib/biome-host/use-page-state.js.map +1 -1
- package/dist/lib/capabilities/capability-provider.d.ts +1 -1
- package/dist/lib/capabilities/capability-provider.d.ts.map +1 -1
- package/dist/lib/capabilities/capability-provider.js +2 -2
- package/dist/lib/capabilities/capability-provider.js.map +1 -1
- package/dist/lib/capabilities/use-capability.d.ts +1 -1
- package/dist/lib/capabilities/use-capability.d.ts.map +1 -1
- package/dist/lib/capabilities/use-capability.js +2 -1
- package/dist/lib/capabilities/use-capability.js.map +1 -1
- package/dist/lib/contribution-registry/contribution-registry.d.ts.map +1 -1
- package/dist/lib/contribution-registry/contribution-registry.js +2 -1
- package/dist/lib/contribution-registry/contribution-registry.js.map +1 -1
- package/dist/lib/system-bus/deeplink.d.ts.map +1 -1
- package/dist/lib/system-bus/deeplink.js +4 -2
- package/dist/lib/system-bus/deeplink.js.map +1 -1
- package/dist/lib/system-bus/host-ports.d.ts +2 -2
- package/dist/lib/system-bus/host-ports.d.ts.map +1 -1
- package/dist/lib/system-bus/intent-registry.d.ts.map +1 -1
- package/dist/lib/system-bus/intent-registry.js +6 -3
- package/dist/lib/system-bus/intent-registry.js.map +1 -1
- package/dist/lib/system-bus/intents.d.ts +1 -1
- package/dist/lib/system-bus/intents.d.ts.map +1 -1
- package/dist/lib/system-bus/palette.d.ts +1 -1
- package/dist/lib/system-bus/palette.d.ts.map +1 -1
- package/dist/lib/system-bus/system-bus-builder.d.ts.map +1 -1
- package/dist/lib/system-bus/system-bus-builder.js +1 -1
- package/dist/lib/system-bus/system-bus-builder.js.map +1 -1
- package/dist/lib/system-bus/windows.d.ts +1 -1
- package/dist/lib/system-bus/windows.d.ts.map +1 -1
- package/dist/offline/outbox.d.ts +1 -0
- package/dist/offline/outbox.d.ts.map +1 -1
- package/dist/offline/outbox.js +4 -1
- package/dist/offline/outbox.js.map +1 -1
- package/dist/offline/react-hooks.d.ts +1 -1
- package/dist/offline/react-hooks.d.ts.map +1 -1
- package/dist/offline/react-hooks.js.map +1 -1
- package/dist/org-db/components/DbResultTable.d.ts.map +1 -1
- package/dist/org-db/components/DbResultTable.js +15 -6
- package/dist/org-db/components/DbResultTable.js.map +1 -1
- package/dist/registry/lib/agent-validation-host.d.ts.map +1 -1
- package/dist/registry/lib/agent-validation-host.js +1 -1
- package/dist/registry/lib/agent-validation-host.js.map +1 -1
- package/dist/registry/lib/biome-slot.d.ts.map +1 -1
- package/dist/registry/lib/biome-slot.js +5 -3
- package/dist/registry/lib/biome-slot.js.map +1 -1
- package/dist/registry/lib/primitives/SessionEventCard.d.ts.map +1 -1
- package/dist/registry/lib/primitives/SessionEventCard.js +14 -2
- package/dist/registry/lib/primitives/SessionEventCard.js.map +1 -1
- package/dist/registry/lib/primitives/SessionEventTimeline.d.ts.map +1 -1
- package/dist/registry/lib/primitives/SessionEventTimeline.js +6 -3
- package/dist/registry/lib/primitives/SessionEventTimeline.js.map +1 -1
- package/dist/registry/lib/primitives/SessionTabbedDrawer.d.ts.map +1 -1
- package/dist/registry/lib/primitives/SessionTabbedDrawer.js +10 -5
- package/dist/registry/lib/primitives/SessionTabbedDrawer.js.map +1 -1
- package/dist/registry/lib/session-context-builder.d.ts +10 -2
- package/dist/registry/lib/session-context-builder.d.ts.map +1 -1
- package/dist/registry/lib/session-context-builder.js +14 -7
- package/dist/registry/lib/session-context-builder.js.map +1 -1
- package/dist/registry/lib/session-context-provider.d.ts +1 -1
- package/dist/registry/lib/session-context-provider.d.ts.map +1 -1
- package/dist/registry/lib/session-context-provider.js.map +1 -1
- package/dist/registry/lib/session-selectors.d.ts.map +1 -1
- package/dist/registry/lib/session-selectors.js +35 -20
- package/dist/registry/lib/session-selectors.js.map +1 -1
- package/dist/session/comments/CommentRail.d.ts.map +1 -1
- package/dist/session/comments/CommentRail.js.map +1 -1
- package/dist/session/shell/SessionWorkspaceShell.d.ts.map +1 -1
- package/dist/session/shell/SessionWorkspaceShell.js.map +1 -1
- package/dist/session-kit/approvals/ApprovalCard.d.ts.map +1 -1
- package/dist/session-kit/approvals/ApprovalCard.js +6 -2
- package/dist/session-kit/approvals/ApprovalCard.js.map +1 -1
- package/dist/session-kit/approvals/ApprovalsCenter.d.ts.map +1 -1
- package/dist/session-kit/approvals/ApprovalsCenter.js +6 -3
- package/dist/session-kit/approvals/ApprovalsCenter.js.map +1 -1
- package/dist/session-kit/combobox/Combobox.d.ts.map +1 -1
- package/dist/session-kit/combobox/Combobox.js +20 -6
- package/dist/session-kit/combobox/Combobox.js.map +1 -1
- package/dist/session-kit/display/MD.d.ts.map +1 -1
- package/dist/session-kit/display/MD.js +7 -4
- package/dist/session-kit/display/MD.js.map +1 -1
- package/dist/ui/branding/app-branding-provider.d.ts.map +1 -1
- package/dist/ui/branding/app-branding-provider.js.map +1 -1
- package/dist/ui/branding/sanitize.d.ts.map +1 -1
- package/dist/ui/branding/sanitize.js +12 -6
- package/dist/ui/branding/sanitize.js.map +1 -1
- package/dist/ui/capture/document-preview.d.ts +1 -1
- package/dist/ui/capture/document-preview.d.ts.map +1 -1
- package/dist/ui/capture/document-preview.js +8 -6
- package/dist/ui/capture/document-preview.js.map +1 -1
- package/dist/ui/capture/field-spec-to-zod.d.ts.map +1 -1
- package/dist/ui/capture/field-spec-to-zod.js +45 -16
- package/dist/ui/capture/field-spec-to-zod.js.map +1 -1
- package/dist/ui/capture/field-spec-validate.d.ts.map +1 -1
- package/dist/ui/capture/field-spec-validate.js +79 -38
- package/dist/ui/capture/field-spec-validate.js.map +1 -1
- package/dist/ui/capture/field-spec.d.ts.map +1 -1
- package/dist/ui/capture/field-spec.js +17 -6
- package/dist/ui/capture/field-spec.js.map +1 -1
- package/dist/ui/capture/schema-field-input.d.ts.map +1 -1
- package/dist/ui/capture/schema-field-input.js +23 -18
- package/dist/ui/capture/schema-field-input.js.map +1 -1
- package/dist/ui/capture/schema-form.d.ts +1 -1
- package/dist/ui/capture/schema-form.d.ts.map +1 -1
- package/dist/ui/capture/schema-form.js +7 -6
- package/dist/ui/capture/schema-form.js.map +1 -1
- package/dist/ui/capture/use-prefill.d.ts.map +1 -1
- package/dist/ui/capture/use-prefill.js +32 -14
- package/dist/ui/capture/use-prefill.js.map +1 -1
- package/dist/ui/capture/use-schema-validation.d.ts +1 -1
- package/dist/ui/capture/use-schema-validation.d.ts.map +1 -1
- package/dist/ui/capture/use-schema-validation.js +13 -8
- package/dist/ui/capture/use-schema-validation.js.map +1 -1
- package/dist/ui/capture/use-wizard.d.ts.map +1 -1
- package/dist/ui/capture/use-wizard.js +23 -8
- package/dist/ui/capture/use-wizard.js.map +1 -1
- package/dist/ui/capture/value-format.d.ts.map +1 -1
- package/dist/ui/capture/value-format.js +6 -3
- package/dist/ui/capture/value-format.js.map +1 -1
- package/dist/ui/capture/wizard.d.ts.map +1 -1
- package/dist/ui/capture/wizard.js +3 -3
- package/dist/ui/capture/wizard.js.map +1 -1
- package/dist/ui/charts/bar-chart.d.ts +1 -1
- package/dist/ui/charts/bar-chart.d.ts.map +1 -1
- package/dist/ui/charts/bar-chart.js +1 -1
- package/dist/ui/charts/bar-chart.js.map +1 -1
- package/dist/ui/charts/chart-frame.d.ts +2 -2
- package/dist/ui/charts/chart-frame.d.ts.map +1 -1
- package/dist/ui/charts/chart-frame.js +4 -3
- package/dist/ui/charts/chart-frame.js.map +1 -1
- package/dist/ui/charts/chart-utils.d.ts.map +1 -1
- package/dist/ui/charts/chart-utils.js +32 -14
- package/dist/ui/charts/chart-utils.js.map +1 -1
- package/dist/ui/charts/donut-chart.d.ts +1 -1
- package/dist/ui/charts/donut-chart.d.ts.map +1 -1
- package/dist/ui/charts/donut-chart.js +7 -3
- package/dist/ui/charts/donut-chart.js.map +1 -1
- package/dist/ui/charts/line-chart.d.ts +1 -1
- package/dist/ui/charts/line-chart.d.ts.map +1 -1
- package/dist/ui/charts/line-chart.js +7 -4
- package/dist/ui/charts/line-chart.js.map +1 -1
- package/dist/ui/charts/sparkline.d.ts +1 -1
- package/dist/ui/charts/sparkline.d.ts.map +1 -1
- package/dist/ui/charts/sparkline.js +10 -6
- package/dist/ui/charts/sparkline.js.map +1 -1
- package/dist/ui/chrome/AsyncBoundary.d.ts +2 -2
- package/dist/ui/chrome/AsyncBoundary.d.ts.map +1 -1
- package/dist/ui/chrome/AsyncBoundary.js +3 -3
- package/dist/ui/chrome/AsyncBoundary.js.map +1 -1
- package/dist/ui/chrome/EmptyState.d.ts +19 -8
- package/dist/ui/chrome/EmptyState.d.ts.map +1 -1
- package/dist/ui/chrome/EmptyState.js +6 -4
- package/dist/ui/chrome/EmptyState.js.map +1 -1
- package/dist/ui/chrome/RecordCardList.d.ts +19 -0
- package/dist/ui/chrome/RecordCardList.d.ts.map +1 -0
- package/dist/ui/chrome/RecordCardList.js +12 -0
- package/dist/ui/chrome/RecordCardList.js.map +1 -0
- package/dist/ui/chrome/page-shell.d.ts.map +1 -1
- package/dist/ui/chrome/page-shell.js +9 -3
- package/dist/ui/chrome/page-shell.js.map +1 -1
- package/dist/ui/chrome/status-badge.d.ts.map +1 -1
- package/dist/ui/chrome/status-badge.js +1 -1
- package/dist/ui/chrome/status-badge.js.map +1 -1
- package/dist/ui/design-tokens.d.ts +4 -6
- package/dist/ui/design-tokens.d.ts.map +1 -1
- package/dist/ui/design-tokens.js +17 -11
- package/dist/ui/design-tokens.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/primitives/async-combobox.d.ts.map +1 -1
- package/dist/ui/primitives/async-combobox.js +4 -2
- package/dist/ui/primitives/async-combobox.js.map +1 -1
- package/dist/ui/primitives/badge.d.ts +2 -2
- package/dist/ui/primitives/badge.d.ts.map +1 -1
- package/dist/ui/primitives/badge.js +3 -3
- package/dist/ui/primitives/badge.js.map +1 -1
- package/dist/ui/primitives/button.d.ts +2 -2
- package/dist/ui/primitives/button.d.ts.map +1 -1
- package/dist/ui/primitives/button.js +5 -5
- package/dist/ui/primitives/button.js.map +1 -1
- package/dist/ui/primitives/overflow-tabs.d.ts.map +1 -1
- package/dist/ui/primitives/overflow-tabs.js +19 -12
- package/dist/ui/primitives/overflow-tabs.js.map +1 -1
- package/dist/ui/primitives/preview-device-frame.js +8 -4
- package/dist/ui/primitives/preview-device-frame.js.map +1 -1
- package/dist/ui/primitives/tag-multi-select.d.ts.map +1 -1
- package/dist/ui/primitives/tag-multi-select.js +10 -5
- package/dist/ui/primitives/tag-multi-select.js.map +1 -1
- package/dist/ui/primitives/url-preview-chrome.d.ts.map +1 -1
- package/dist/ui/primitives/url-preview-chrome.js.map +1 -1
- package/dist/vista/GenericCatalogueSurface.d.ts.map +1 -1
- package/dist/vista/GenericCatalogueSurface.js +5 -3
- package/dist/vista/GenericCatalogueSurface.js.map +1 -1
- package/dist/vista/VistaPanel.d.ts.map +1 -1
- package/dist/vista/VistaPanel.js +8 -4
- package/dist/vista/VistaPanel.js.map +1 -1
- package/dist/vista/html/CustomUiFrame.d.ts.map +1 -1
- package/dist/vista/html/CustomUiFrame.js +2 -1
- package/dist/vista/html/CustomUiFrame.js.map +1 -1
- package/dist/vista/html/custom-ui-frame-bridge.d.ts.map +1 -1
- package/dist/vista/html/custom-ui-frame-bridge.js +6 -3
- package/dist/vista/html/custom-ui-frame-bridge.js.map +1 -1
- package/dist/vista/html/custom-ui-intents.d.ts.map +1 -1
- package/dist/vista/html/custom-ui-intents.js +13 -15
- package/dist/vista/html/custom-ui-intents.js.map +1 -1
- package/dist/vista/surface-catalogue.d.ts.map +1 -1
- package/dist/vista/surface-catalogue.js +6 -3
- package/dist/vista/surface-catalogue.js.map +1 -1
- package/dist/vista/surfaces/FormSurface.d.ts.map +1 -1
- package/dist/vista/surfaces/FormSurface.js +20 -7
- package/dist/vista/surfaces/FormSurface.js.map +1 -1
- package/dist/vista/surfaces/TableSurface.d.ts.map +1 -1
- package/dist/vista/surfaces/TableSurface.js +2 -1
- package/dist/vista/surfaces/TableSurface.js.map +1 -1
- package/dist/vista/surfaces/blob-surfaces.d.ts.map +1 -1
- package/dist/vista/surfaces/blob-surfaces.js +7 -2
- package/dist/vista/surfaces/blob-surfaces.js.map +1 -1
- package/dist/vista/surfaces/elicitation-transport.d.ts.map +1 -1
- package/dist/vista/surfaces/elicitation-transport.js +14 -7
- package/dist/vista/surfaces/elicitation-transport.js.map +1 -1
- package/dist/vista/surfaces/file-surface-utils.d.ts.map +1 -1
- package/dist/vista/surfaces/file-surface-utils.js +8 -4
- package/dist/vista/surfaces/file-surface-utils.js.map +1 -1
- package/dist/vista/surfaces/register-surfaces.js +2 -1
- package/dist/vista/surfaces/register-surfaces.js.map +1 -1
- package/dist/vista/surfaces/text-surfaces.d.ts +1 -1
- package/dist/vista/surfaces/text-surfaces.d.ts.map +1 -1
- package/dist/vista/surfaces/text-surfaces.js +13 -7
- package/dist/vista/surfaces/text-surfaces.js.map +1 -1
- package/dist/vista/use-vista-tabs.d.ts.map +1 -1
- package/dist/vista/use-vista-tabs.js +8 -4
- package/dist/vista/use-vista-tabs.js.map +1 -1
- package/dist/vista/vista-code-renderer.d.ts.map +1 -1
- package/dist/vista/vista-code-renderer.js.map +1 -1
- package/dist/vista/vista-file-source.d.ts.map +1 -1
- package/dist/vista/vista-file-source.js +9 -5
- package/dist/vista/vista-file-source.js.map +1 -1
- package/dist/vista/vista-form-host.d.ts.map +1 -1
- package/dist/vista/vista-form-host.js +1 -1
- package/dist/vista/vista-form-host.js.map +1 -1
- package/dist/vista/vista-model.d.ts.map +1 -1
- package/dist/vista/vista-model.js +5 -3
- package/dist/vista/vista-model.js.map +1 -1
- package/dist/vista/vista-open-context.d.ts.map +1 -1
- package/dist/vista/vista-open-context.js +1 -1
- package/dist/vista/vista-open-context.js.map +1 -1
- package/dist/vista/vista-payloads.d.ts.map +1 -1
- package/dist/vista/vista-payloads.js +127 -63
- package/dist/vista/vista-payloads.js.map +1 -1
- package/dist/vista/vista-tabs-manifest.d.ts.map +1 -1
- package/dist/vista/vista-tabs-manifest.js +18 -9
- package/dist/vista/vista-tabs-manifest.js.map +1 -1
- package/dist/vista/vista-tools.d.ts.map +1 -1
- package/dist/vista/vista-tools.js +12 -6
- package/dist/vista/vista-tools.js.map +1 -1
- package/package.json +3 -3
- package/src/lib/biome-host/agent-validation.ts +37 -13
- package/src/lib/biome-host/biome-registry.ts +48 -13
- package/src/lib/biome-host/biome-scope-hooks.ts +3 -1
- package/src/lib/biome-host/biome-scoped-query.ts +7 -6
- package/src/lib/biome-host/frontend-biome.ts +1 -1
- package/src/lib/biome-host/host-bridge.ts +5 -4
- package/src/lib/biome-host/i18n-hooks.ts +1 -0
- package/src/lib/biome-host/index.ts +0 -1
- package/src/lib/biome-host/realtime-hooks.ts +1 -0
- package/src/lib/biome-host/session-contributions.ts +22 -19
- package/src/lib/biome-host/use-mutation-with-error-toast.ts +4 -2
- package/src/lib/biome-host/use-page-state.ts +27 -10
- package/src/lib/capabilities/capability-provider.tsx +12 -5
- package/src/lib/capabilities/use-capability.ts +8 -4
- package/src/lib/contribution-registry/contribution-registry.ts +3 -1
- package/src/lib/system-bus/deeplink.ts +14 -4
- package/src/lib/system-bus/host-ports.ts +3 -7
- package/src/lib/system-bus/intent-registry.ts +12 -8
- package/src/lib/system-bus/intents.ts +6 -2
- package/src/lib/system-bus/palette.ts +2 -1
- package/src/lib/system-bus/system-bus-builder.ts +4 -2
- package/src/lib/system-bus/windows.ts +1 -1
- package/src/offline/outbox.test.ts +12 -3
- package/src/offline/outbox.ts +5 -1
- package/src/offline/react-hooks.ts +5 -2
- package/src/org-db/components/DbResultTable.tsx +48 -15
- package/src/registry/lib/agent-validation-host.ts +1 -2
- package/src/registry/lib/biome-slot.tsx +7 -4
- package/src/registry/lib/primitives/SessionEventCard.tsx +29 -5
- package/src/registry/lib/primitives/SessionEventTimeline.tsx +9 -3
- package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +15 -5
- package/src/registry/lib/session-context-builder.ts +33 -17
- package/src/registry/lib/session-context-provider.tsx +1 -1
- package/src/registry/lib/session-selectors.ts +66 -27
- package/src/session/comments/CommentRail.tsx +2 -2
- package/src/session/shell/SessionWorkspaceShell.tsx +2 -2
- package/src/session-kit/approvals/ApprovalCard.tsx +7 -2
- package/src/session-kit/approvals/ApprovalsCenter.tsx +13 -4
- package/src/session-kit/combobox/Combobox.tsx +42 -11
- package/src/session-kit/display/MD.tsx +14 -5
- package/src/ui/branding/app-branding-provider.tsx +2 -3
- package/src/ui/branding/sanitize.ts +18 -6
- package/src/ui/capture/document-preview.tsx +84 -19
- package/src/ui/capture/field-spec-to-zod.ts +62 -14
- package/src/ui/capture/field-spec-validate.ts +132 -43
- package/src/ui/capture/field-spec.test.ts +2 -2
- package/src/ui/capture/field-spec.ts +41 -10
- package/src/ui/capture/schema-field-input.tsx +218 -38
- package/src/ui/capture/schema-form.tsx +54 -13
- package/src/ui/capture/use-prefill.ts +50 -17
- package/src/ui/capture/use-schema-validation.ts +45 -13
- package/src/ui/capture/use-wizard.ts +57 -11
- package/src/ui/capture/value-format.ts +14 -4
- package/src/ui/capture/wizard.tsx +9 -5
- package/src/ui/charts/bar-chart.tsx +8 -4
- package/src/ui/charts/chart-frame.tsx +18 -5
- package/src/ui/charts/chart-utils.ts +47 -15
- package/src/ui/charts/donut-chart.tsx +24 -6
- package/src/ui/charts/line-chart.tsx +34 -12
- package/src/ui/charts/sparkline.tsx +17 -9
- package/src/ui/chrome/AsyncBoundary.tsx +14 -6
- package/src/ui/chrome/EmptyState.tsx +116 -43
- package/src/ui/chrome/RecordCardList.tsx +98 -0
- package/src/ui/chrome/page-shell.tsx +32 -8
- package/src/ui/chrome/status-badge.tsx +8 -4
- package/src/ui/design-tokens.ts +39 -13
- package/src/ui/index.ts +5 -0
- package/src/ui/primitives/async-combobox.tsx +7 -3
- package/src/ui/primitives/badge.tsx +14 -5
- package/src/ui/primitives/button.tsx +13 -6
- package/src/ui/primitives/overflow-tabs.tsx +46 -16
- package/src/ui/primitives/preview-device-frame.tsx +12 -4
- package/src/ui/primitives/tag-multi-select.tsx +30 -9
- package/src/ui/primitives/url-preview-chrome.tsx +1 -0
- package/src/vista/GenericCatalogueSurface.tsx +7 -5
- package/src/vista/VistaPanel.tsx +12 -4
- package/src/vista/html/CustomUiFrame.spec.tsx +3 -1
- package/src/vista/html/CustomUiFrame.tsx +3 -1
- package/src/vista/html/custom-ui-frame-bridge.ts +9 -3
- package/src/vista/html/custom-ui-intents.spec.ts +3 -1
- package/src/vista/html/custom-ui-intents.ts +19 -15
- package/src/vista/surface-catalogue.ts +9 -3
- package/src/vista/surfaces/FormSurface.tsx +27 -10
- package/src/vista/surfaces/TableSurface.tsx +4 -3
- package/src/vista/surfaces/blob-surfaces.tsx +13 -3
- package/src/vista/surfaces/elicitation-transport.ts +21 -7
- package/src/vista/surfaces/file-surface-utils.ts +11 -4
- package/src/vista/surfaces/register-surfaces.tsx +3 -1
- package/src/vista/surfaces/text-surfaces.tsx +21 -8
- package/src/vista/use-vista-tabs.ts +12 -4
- package/src/vista/vista-code-renderer.tsx +6 -5
- package/src/vista/vista-file-source.tsx +15 -14
- package/src/vista/vista-form-host.tsx +3 -8
- package/src/vista/vista-model.ts +10 -3
- package/src/vista/vista-open-context.tsx +3 -8
- package/src/vista/vista-payloads.ts +186 -63
- package/src/vista/vista-tabs-manifest.ts +28 -13
- package/src/vista/vista-tools.ts +18 -6
- package/dist/lib/biome-host/session-profiles.d.ts +0 -18
- package/dist/lib/biome-host/session-profiles.d.ts.map +0 -1
- package/dist/lib/biome-host/session-profiles.js +0 -38
- package/dist/lib/biome-host/session-profiles.js.map +0 -1
- package/src/lib/biome-host/session-profiles.ts +0 -105
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
// can nudge "you still need to fill X" without waiting for a 422.
|
|
9
9
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
10
10
|
import { useMemo } from 'react';
|
|
11
|
-
import { z } from 'zod';
|
|
11
|
+
import { type z } from 'zod';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CaptureFieldType,
|
|
15
|
+
type CaptureFieldSpec,
|
|
16
|
+
type CaptureSchema,
|
|
17
|
+
type CaptureSection,
|
|
18
|
+
type CaptureValues,
|
|
19
|
+
toFieldList,
|
|
20
|
+
} from './field-spec';
|
|
14
21
|
import { fieldSpecToZod, isEmptyValue } from './field-spec-to-zod';
|
|
15
22
|
|
|
16
23
|
export interface SchemaValidationResult {
|
|
@@ -29,34 +36,52 @@ type ValidationInput =
|
|
|
29
36
|
| readonly CaptureSection[];
|
|
30
37
|
|
|
31
38
|
function isZodSchema(input: ValidationInput): input is z.ZodType {
|
|
32
|
-
return
|
|
39
|
+
return 'safeParse' in input && typeof input.safeParse === 'function';
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
/** Deterministic required sweep over the flat spec list (nested-aware). */
|
|
36
|
-
function sweepMissing(
|
|
43
|
+
function sweepMissing(
|
|
44
|
+
fields: readonly CaptureFieldSpec[],
|
|
45
|
+
values: CaptureValues,
|
|
46
|
+
prefix = '',
|
|
47
|
+
): string[] {
|
|
37
48
|
const missing: string[] = [];
|
|
38
49
|
for (const field of fields) {
|
|
39
50
|
const path = prefix ? `${prefix}.${field.key}` : field.key;
|
|
40
|
-
const value = values
|
|
51
|
+
const value = values[field.key];
|
|
41
52
|
if (field.type === CaptureFieldType.Object) {
|
|
42
|
-
if (field.required && isEmptyValue(value))
|
|
53
|
+
if (field.required && isEmptyValue(value)) {
|
|
54
|
+
missing.push(path);
|
|
55
|
+
}
|
|
43
56
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
44
|
-
missing.push(
|
|
57
|
+
missing.push(
|
|
58
|
+
...sweepMissing(field.fields ?? [], value as CaptureValues, path),
|
|
59
|
+
);
|
|
45
60
|
}
|
|
46
61
|
continue;
|
|
47
62
|
}
|
|
48
63
|
if (field.type === CaptureFieldType.List) {
|
|
49
|
-
if (field.required && isEmptyValue(value))
|
|
64
|
+
if (field.required && isEmptyValue(value)) {
|
|
65
|
+
missing.push(path);
|
|
66
|
+
}
|
|
50
67
|
if (Array.isArray(value)) {
|
|
51
68
|
value.forEach((item, i) => {
|
|
52
69
|
if (item && typeof item === 'object') {
|
|
53
|
-
missing.push(
|
|
70
|
+
missing.push(
|
|
71
|
+
...sweepMissing(
|
|
72
|
+
field.fields ?? [],
|
|
73
|
+
item as CaptureValues,
|
|
74
|
+
`${path}.${i}`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
54
77
|
}
|
|
55
78
|
});
|
|
56
79
|
}
|
|
57
80
|
continue;
|
|
58
81
|
}
|
|
59
|
-
if (field.required && isEmptyValue(value))
|
|
82
|
+
if (field.required && isEmptyValue(value)) {
|
|
83
|
+
missing.push(path);
|
|
84
|
+
}
|
|
60
85
|
}
|
|
61
86
|
return missing;
|
|
62
87
|
}
|
|
@@ -70,7 +95,10 @@ function sweepMissing(fields: readonly CaptureFieldSpec[], values: CaptureValues
|
|
|
70
95
|
* // gate a wizard step / submit button:
|
|
71
96
|
* <Button disabled={!isValid}>Continue</Button>
|
|
72
97
|
*/
|
|
73
|
-
export function useSchemaValidation(
|
|
98
|
+
export function useSchemaValidation(
|
|
99
|
+
input: ValidationInput,
|
|
100
|
+
values: CaptureValues,
|
|
101
|
+
): SchemaValidationResult {
|
|
74
102
|
// Field specs (when supplied) drive the deterministic required sweep and the
|
|
75
103
|
// auto-adapted zod schema; a raw zod schema is used as-is.
|
|
76
104
|
const fields = useMemo<readonly CaptureFieldSpec[] | null>(
|
|
@@ -89,7 +117,9 @@ export function useSchemaValidation(input: ValidationInput, values: CaptureValue
|
|
|
89
117
|
for (const issue of result.error.issues) {
|
|
90
118
|
const path = issue.path.map(String).join('.');
|
|
91
119
|
// First error per path wins (deterministic, matches display order).
|
|
92
|
-
if (!(path in errors))
|
|
120
|
+
if (!(path in errors)) {
|
|
121
|
+
errors[path] = issue.message;
|
|
122
|
+
}
|
|
93
123
|
}
|
|
94
124
|
}
|
|
95
125
|
|
|
@@ -100,7 +130,9 @@ export function useSchemaValidation(input: ValidationInput, values: CaptureValue
|
|
|
100
130
|
: Object.keys(errors).filter((path) => {
|
|
101
131
|
const segments = path.split('.');
|
|
102
132
|
let cur: unknown = values;
|
|
103
|
-
for (const seg of segments)
|
|
133
|
+
for (const seg of segments) {
|
|
134
|
+
cur = (cur as CaptureValues | undefined)?.[seg];
|
|
135
|
+
}
|
|
104
136
|
return isEmptyValue(cur);
|
|
105
137
|
});
|
|
106
138
|
|
|
@@ -50,6 +50,29 @@ export interface WizardApi {
|
|
|
50
50
|
readonly status: (index: number) => FormStepStatus;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Strict access guard — keeps `steps[index]` typed as defined.
|
|
55
|
+
*
|
|
56
|
+
* `activeIndex` is clamped into `[0, count - 1]`, so the only way this misses is
|
|
57
|
+
* an EMPTY `steps` array: `clamp` floors at 0 and `steps[0]` is then `undefined`.
|
|
58
|
+
* `WizardApi.activeStep` is declared non-optional and consumers dereference it
|
|
59
|
+
* (`api.activeStep.key`), so a zero-step wizard must fail here with a message
|
|
60
|
+
* naming the cause rather than hand back an `undefined` typed as `WizardStepDef`.
|
|
61
|
+
*/
|
|
62
|
+
function requireStep(
|
|
63
|
+
steps: readonly WizardStepDef[],
|
|
64
|
+
index: number,
|
|
65
|
+
): WizardStepDef {
|
|
66
|
+
const step = steps[index];
|
|
67
|
+
if (!step) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`useWizard: no step at index ${index} (have ${steps.length}). ` +
|
|
70
|
+
'`steps` must contain at least one entry.',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return step;
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
/**
|
|
54
77
|
* @example
|
|
55
78
|
* const wizard = useWizard({
|
|
@@ -64,20 +87,30 @@ export interface WizardApi {
|
|
|
64
87
|
export function useWizard(options: UseWizardOptions): WizardApi {
|
|
65
88
|
const { steps, canAdvance, initialStep = 0, step, onStepChange } = options;
|
|
66
89
|
const count = steps.length;
|
|
67
|
-
const clamp = useCallback(
|
|
90
|
+
const clamp = useCallback(
|
|
91
|
+
(i: number) => Math.max(0, Math.min(count - 1, i)),
|
|
92
|
+
[count],
|
|
93
|
+
);
|
|
68
94
|
|
|
69
95
|
const isControlled = step !== undefined;
|
|
70
96
|
const [internal, setInternal] = useState(() => clamp(initialStep));
|
|
71
97
|
const activeIndex = clamp(isControlled ? step : internal);
|
|
72
98
|
|
|
73
|
-
const gate = useCallback(
|
|
99
|
+
const gate = useCallback(
|
|
100
|
+
(i: number) => (canAdvance ? canAdvance(i) : true),
|
|
101
|
+
[canAdvance],
|
|
102
|
+
);
|
|
74
103
|
|
|
75
104
|
const request = useCallback(
|
|
76
105
|
(target: number) => {
|
|
77
106
|
const next = clamp(target);
|
|
78
|
-
if (next === activeIndex)
|
|
107
|
+
if (next === activeIndex) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
79
110
|
onStepChange?.(next);
|
|
80
|
-
if (!isControlled)
|
|
111
|
+
if (!isControlled) {
|
|
112
|
+
setInternal(next);
|
|
113
|
+
}
|
|
81
114
|
},
|
|
82
115
|
[activeIndex, clamp, isControlled, onStepChange],
|
|
83
116
|
);
|
|
@@ -91,7 +124,9 @@ export function useWizard(options: UseWizardOptions): WizardApi {
|
|
|
91
124
|
}
|
|
92
125
|
// Forward: every step from active..target-1 must pass its gate.
|
|
93
126
|
for (let i = activeIndex; i < target; i += 1) {
|
|
94
|
-
if (!gate(i))
|
|
127
|
+
if (!gate(i)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
95
130
|
}
|
|
96
131
|
request(target);
|
|
97
132
|
},
|
|
@@ -99,17 +134,28 @@ export function useWizard(options: UseWizardOptions): WizardApi {
|
|
|
99
134
|
);
|
|
100
135
|
|
|
101
136
|
const next = useCallback(() => {
|
|
102
|
-
if (activeIndex >= count - 1)
|
|
103
|
-
|
|
137
|
+
if (activeIndex >= count - 1) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!gate(activeIndex)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
104
143
|
request(activeIndex + 1);
|
|
105
144
|
}, [activeIndex, count, gate, request]);
|
|
106
145
|
|
|
107
|
-
const prev = useCallback(
|
|
146
|
+
const prev = useCallback(
|
|
147
|
+
() => request(activeIndex - 1),
|
|
148
|
+
[activeIndex, request],
|
|
149
|
+
);
|
|
108
150
|
|
|
109
151
|
const status = useCallback(
|
|
110
152
|
(index: number): FormStepStatus => {
|
|
111
|
-
if (index < activeIndex)
|
|
112
|
-
|
|
153
|
+
if (index < activeIndex) {
|
|
154
|
+
return FormStepStatus.DONE;
|
|
155
|
+
}
|
|
156
|
+
if (index === activeIndex) {
|
|
157
|
+
return FormStepStatus.ACTIVE;
|
|
158
|
+
}
|
|
113
159
|
return FormStepStatus.PENDING;
|
|
114
160
|
},
|
|
115
161
|
[activeIndex],
|
|
@@ -119,7 +165,7 @@ export function useWizard(options: UseWizardOptions): WizardApi {
|
|
|
119
165
|
() => ({
|
|
120
166
|
steps,
|
|
121
167
|
activeIndex,
|
|
122
|
-
activeStep: steps
|
|
168
|
+
activeStep: requireStep(steps, activeIndex),
|
|
123
169
|
isFirst: activeIndex === 0,
|
|
124
170
|
isLast: activeIndex === count - 1,
|
|
125
171
|
canAdvance: gate(activeIndex),
|
|
@@ -5,14 +5,24 @@ import { CaptureFieldType, type CaptureFieldSpec } from './field-spec';
|
|
|
5
5
|
|
|
6
6
|
/** Coerce any stored value to a display/edit string (empty for absent). */
|
|
7
7
|
export function asString(value: unknown): string {
|
|
8
|
-
if (typeof value === 'string')
|
|
9
|
-
|
|
8
|
+
if (typeof value === 'string') {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (value === undefined || value === null) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
10
14
|
return String(value);
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
/** Human-readable rendering of a scalar field value for the read-only preview. */
|
|
14
|
-
export function formatScalar(
|
|
15
|
-
|
|
18
|
+
export function formatScalar(
|
|
19
|
+
field: CaptureFieldSpec,
|
|
20
|
+
value: unknown,
|
|
21
|
+
emptyText: string,
|
|
22
|
+
): string {
|
|
23
|
+
if (value === undefined || value === null || value === '') {
|
|
24
|
+
return emptyText;
|
|
25
|
+
}
|
|
16
26
|
switch (field.type) {
|
|
17
27
|
case CaptureFieldType.Boolean:
|
|
18
28
|
return value === true ? 'Yes' : 'No';
|
|
@@ -6,14 +6,18 @@
|
|
|
6
6
|
// renders a Back / Next / Finish nav bar. All state lives in the `useWizard`
|
|
7
7
|
// engine you pass via `api`; this component is presentation only.
|
|
8
8
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Children,
|
|
11
|
+
isValidElement,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
} from 'react';
|
|
10
14
|
|
|
11
15
|
import { cn } from '../cn';
|
|
12
16
|
import { Button } from '../primitives/button';
|
|
13
17
|
import { FormStepper } from '../primitives/form-stepper';
|
|
14
|
-
import type { FormStep } from '../primitives/form-stepper.types';
|
|
15
18
|
|
|
16
19
|
import type { WizardApi } from './use-wizard';
|
|
20
|
+
import type { FormStep } from '../primitives/form-stepper.types';
|
|
17
21
|
|
|
18
22
|
export interface WizardStepProps {
|
|
19
23
|
/** Must match a `useWizard` step `key`. */
|
|
@@ -46,8 +50,8 @@ export interface WizardProps {
|
|
|
46
50
|
function stepBodies(children: ReactNode): Map<string, ReactNode> {
|
|
47
51
|
const map = new Map<string, ReactNode>();
|
|
48
52
|
Children.forEach(children, (child) => {
|
|
49
|
-
if (isValidElement(child) &&
|
|
50
|
-
const p =
|
|
53
|
+
if (isValidElement<WizardStepProps>(child) && child.props.stepKey) {
|
|
54
|
+
const p = child.props;
|
|
51
55
|
map.set(p.stepKey, p.children);
|
|
52
56
|
}
|
|
53
57
|
});
|
|
@@ -73,7 +77,7 @@ export function Wizard({
|
|
|
73
77
|
status: api.status(index),
|
|
74
78
|
// Render the active step's body inline; pending/done steps stay empty so
|
|
75
79
|
// the rail reads as a guided one-step-at-a-time flow.
|
|
76
|
-
children: index === api.activeIndex ? bodies.get(step.key) ?? null : null,
|
|
80
|
+
children: index === api.activeIndex ? (bodies.get(step.key) ?? null) : null,
|
|
77
81
|
}));
|
|
78
82
|
|
|
79
83
|
return (
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
2
|
// BarChart — categorical bars, pure SVG (Wave 9.2)
|
|
3
3
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
-
import type { ReactNode } from 'react';
|
|
5
4
|
|
|
6
5
|
import { ChartEmpty, ChartFrame, type ChartLegendItem } from './chart-frame';
|
|
7
|
-
import type { ChartBaseProps, ChartDatum } from './chart-types';
|
|
8
6
|
import {
|
|
9
7
|
buildAriaSummary,
|
|
10
8
|
chartColorAt,
|
|
@@ -13,6 +11,9 @@ import {
|
|
|
13
11
|
niceScale,
|
|
14
12
|
} from './chart-utils';
|
|
15
13
|
|
|
14
|
+
import type { ChartBaseProps, ChartDatum } from './chart-types';
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
|
|
16
17
|
// Internal coordinate space — the `viewBox`. `width="100%"` makes the SVG scale
|
|
17
18
|
// to its container (D20 fluid), so these are ratios, not device pixels.
|
|
18
19
|
const VIEW_W = 640;
|
|
@@ -42,13 +43,16 @@ export function BarChart({
|
|
|
42
43
|
const points = finitePoints(data);
|
|
43
44
|
|
|
44
45
|
if (points.length === 0) {
|
|
45
|
-
return
|
|
46
|
+
return (
|
|
47
|
+
<ChartEmpty title={title} label={emptyLabel} className={className} />
|
|
48
|
+
);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
const values = points.map((p) => p.value);
|
|
49
52
|
const scale = niceScale(Math.min(...values), Math.max(...values));
|
|
50
53
|
const span = scale.max - scale.min;
|
|
51
|
-
const yToPx = (v: number): number =>
|
|
54
|
+
const yToPx = (v: number): number =>
|
|
55
|
+
PLOT_B - ((v - scale.min) / span) * PLOT_H;
|
|
52
56
|
const baseline = yToPx(0);
|
|
53
57
|
|
|
54
58
|
const band = PLOT_W / points.length;
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
// below `md`. That decision is driven by `useIsMobile()` — which reads the one
|
|
7
7
|
// shared `MOBILE_BREAKPOINT` token — never a second breakpoint.
|
|
8
8
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
-
import type { ReactNode } from 'react';
|
|
10
9
|
|
|
11
10
|
import { cn } from '../cn';
|
|
12
11
|
import { useIsMobile } from '../hooks/use-is-mobile';
|
|
13
12
|
|
|
13
|
+
import type { ReactNode } from 'react';
|
|
14
|
+
|
|
14
15
|
/** One row of the legend: a color swatch + its label + (optional) value. */
|
|
15
16
|
export interface ChartLegendItem {
|
|
16
17
|
readonly label: string;
|
|
@@ -29,7 +30,9 @@ interface ChartLegendProps {
|
|
|
29
30
|
* secondary cue, so a color-blind or monochrome reader loses nothing.
|
|
30
31
|
*/
|
|
31
32
|
export function ChartLegend({ items, layout }: ChartLegendProps): ReactNode {
|
|
32
|
-
if (items.length === 0)
|
|
33
|
+
if (items.length === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
33
36
|
return (
|
|
34
37
|
<ul
|
|
35
38
|
className={cn(
|
|
@@ -48,7 +51,9 @@ export function ChartLegend({ items, layout }: ChartLegendProps): ReactNode {
|
|
|
48
51
|
/>
|
|
49
52
|
<span className="text-ink-3">{item.label}</span>
|
|
50
53
|
{item.value !== undefined && (
|
|
51
|
-
<span className="ml-auto font-medium tabular-nums text-ink">
|
|
54
|
+
<span className="ml-auto font-medium tabular-nums text-ink">
|
|
55
|
+
{item.value}
|
|
56
|
+
</span>
|
|
52
57
|
)}
|
|
53
58
|
</li>
|
|
54
59
|
))}
|
|
@@ -68,7 +73,11 @@ interface ChartFrameProps {
|
|
|
68
73
|
* so below `md` the legend renders BELOW the plot; at/above `md` it sits to the
|
|
69
74
|
* right. One token (`MOBILE_BREAKPOINT`), no second breakpoint.
|
|
70
75
|
*/
|
|
71
|
-
export function ChartFrame({
|
|
76
|
+
export function ChartFrame({
|
|
77
|
+
plot,
|
|
78
|
+
legend,
|
|
79
|
+
className,
|
|
80
|
+
}: ChartFrameProps): ReactNode {
|
|
72
81
|
const isMobile = useIsMobile();
|
|
73
82
|
return (
|
|
74
83
|
<div className={cn('w-full min-w-0 text-ink-3', className)}>
|
|
@@ -96,7 +105,11 @@ interface ChartEmptyProps {
|
|
|
96
105
|
* no finite points. Still carries `role="img"` + an `aria-label` so assistive
|
|
97
106
|
* tech announces the (empty) chart rather than a silent gap.
|
|
98
107
|
*/
|
|
99
|
-
export function ChartEmpty({
|
|
108
|
+
export function ChartEmpty({
|
|
109
|
+
title,
|
|
110
|
+
label,
|
|
111
|
+
className,
|
|
112
|
+
}: ChartEmptyProps): ReactNode {
|
|
100
113
|
return (
|
|
101
114
|
<div
|
|
102
115
|
role="img"
|
|
@@ -29,8 +29,23 @@ export const CHART_PALETTE: readonly string[] = [
|
|
|
29
29
|
|
|
30
30
|
/** Resolve the color for the datum at `index`: explicit override wins, else palette (wraps). */
|
|
31
31
|
export function chartColorAt(index: number, override?: string): string {
|
|
32
|
-
if (override !== undefined && override.length > 0)
|
|
33
|
-
|
|
32
|
+
if (override !== undefined && override.length > 0) {
|
|
33
|
+
return override;
|
|
34
|
+
}
|
|
35
|
+
const size = CHART_PALETTE.length;
|
|
36
|
+
// `index % size` alone is NOT a wrap: JS `%` keeps the sign, so a negative
|
|
37
|
+
// index lands on a negative slot and reads back `undefined` — which used to
|
|
38
|
+
// be returned as a `string` and rendered as a missing `fill`/`stroke`. Wrap
|
|
39
|
+
// forward instead. A non-finite index yields a NaN slot and falls through to
|
|
40
|
+
// the throw rather than silently painting the wrong series color.
|
|
41
|
+
const slot = ((Math.trunc(index) % size) + size) % size;
|
|
42
|
+
const color = CHART_PALETTE[slot];
|
|
43
|
+
if (color === undefined) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`chartColorAt: no palette color for index ${index} (resolved slot ${slot} of ${size}).`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return color;
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
/**
|
|
@@ -39,8 +54,12 @@ export function chartColorAt(index: number, override?: string): string {
|
|
|
39
54
|
* SVG is byte-identical across machines/locales.
|
|
40
55
|
*/
|
|
41
56
|
export function defaultFormatValue(value: number): string {
|
|
42
|
-
if (!Number.isFinite(value))
|
|
43
|
-
|
|
57
|
+
if (!Number.isFinite(value)) {
|
|
58
|
+
return '—';
|
|
59
|
+
}
|
|
60
|
+
if (Number.isInteger(value)) {
|
|
61
|
+
return String(value);
|
|
62
|
+
}
|
|
44
63
|
return String(Number(value.toFixed(2)));
|
|
45
64
|
}
|
|
46
65
|
|
|
@@ -70,15 +89,25 @@ function niceNum(range: number, round: boolean): number {
|
|
|
70
89
|
const fraction = range / pow;
|
|
71
90
|
let niceFraction: number;
|
|
72
91
|
if (round) {
|
|
73
|
-
if (fraction < 1.5)
|
|
74
|
-
|
|
75
|
-
else if (fraction <
|
|
76
|
-
|
|
92
|
+
if (fraction < 1.5) {
|
|
93
|
+
niceFraction = 1;
|
|
94
|
+
} else if (fraction < 3) {
|
|
95
|
+
niceFraction = 2;
|
|
96
|
+
} else if (fraction < 7) {
|
|
97
|
+
niceFraction = 5;
|
|
98
|
+
} else {
|
|
99
|
+
niceFraction = 10;
|
|
100
|
+
}
|
|
77
101
|
} else {
|
|
78
|
-
if (fraction <= 1)
|
|
79
|
-
|
|
80
|
-
else if (fraction <=
|
|
81
|
-
|
|
102
|
+
if (fraction <= 1) {
|
|
103
|
+
niceFraction = 1;
|
|
104
|
+
} else if (fraction <= 2) {
|
|
105
|
+
niceFraction = 2;
|
|
106
|
+
} else if (fraction <= 5) {
|
|
107
|
+
niceFraction = 5;
|
|
108
|
+
} else {
|
|
109
|
+
niceFraction = 10;
|
|
110
|
+
}
|
|
82
111
|
}
|
|
83
112
|
return niceFraction * pow;
|
|
84
113
|
}
|
|
@@ -117,7 +146,7 @@ export function niceScale(
|
|
|
117
146
|
): NiceScale {
|
|
118
147
|
const pinZero = options.pinZero ?? true;
|
|
119
148
|
const maxTicks = options.maxTicks ?? 5;
|
|
120
|
-
|
|
149
|
+
const lo = pinZero ? Math.min(0, dataMin) : dataMin;
|
|
121
150
|
let hi = pinZero ? Math.max(0, dataMax) : dataMax;
|
|
122
151
|
if (lo === hi) {
|
|
123
152
|
// Flat series (e.g. all equal): open a unit window so we never divide by 0.
|
|
@@ -149,10 +178,13 @@ export function buildAriaSummary(
|
|
|
149
178
|
noun: string,
|
|
150
179
|
maxItems = 8,
|
|
151
180
|
): string {
|
|
152
|
-
if (points.length === 0)
|
|
181
|
+
if (points.length === 0) {
|
|
182
|
+
return `${title}: no data.`;
|
|
183
|
+
}
|
|
153
184
|
const shown = points.slice(0, maxItems);
|
|
154
185
|
const pairs = shown.map((p) => `${p.label} ${format(p.value)}`).join(', ');
|
|
155
|
-
const suffix =
|
|
186
|
+
const suffix =
|
|
187
|
+
points.length > maxItems ? `, and ${points.length - maxItems} more` : '';
|
|
156
188
|
const count = points.length === 1 ? `1 ${noun}` : `${points.length} ${noun}s`;
|
|
157
189
|
return `${title}: ${count}. ${pairs}${suffix}.`;
|
|
158
190
|
}
|
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
// If no finite positive value remains, an explicit empty state renders instead
|
|
6
6
|
// of a broken ring.
|
|
7
7
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
8
|
-
import type { ReactNode } from 'react';
|
|
9
8
|
|
|
10
9
|
import { ChartEmpty, ChartFrame, type ChartLegendItem } from './chart-frame';
|
|
10
|
+
import {
|
|
11
|
+
buildAriaSummary,
|
|
12
|
+
chartColorAt,
|
|
13
|
+
defaultFormatValue,
|
|
14
|
+
} from './chart-utils';
|
|
15
|
+
|
|
11
16
|
import type { ChartBaseProps, ChartDatum } from './chart-types';
|
|
12
|
-
import {
|
|
17
|
+
import type { ReactNode } from 'react';
|
|
13
18
|
|
|
14
19
|
const SIZE = 200;
|
|
15
20
|
const CX = SIZE / 2;
|
|
@@ -57,7 +62,9 @@ export function DonutChart({
|
|
|
57
62
|
const total = slices.reduce((sum, d) => sum + d.value, 0);
|
|
58
63
|
|
|
59
64
|
if (slices.length === 0 || total <= 0) {
|
|
60
|
-
return
|
|
65
|
+
return (
|
|
66
|
+
<ChartEmpty title={title} label={emptyLabel} className={className} />
|
|
67
|
+
);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
const legend: ChartLegendItem[] = slices.map((d, i) => ({
|
|
@@ -67,7 +74,8 @@ export function DonutChart({
|
|
|
67
74
|
}));
|
|
68
75
|
|
|
69
76
|
const summaryPoints = slices.map((d) => ({ label: d.label, value: d.value }));
|
|
70
|
-
const summary =
|
|
77
|
+
const summary =
|
|
78
|
+
ariaLabel ?? buildAriaSummary(title, summaryPoints, format, 'slice');
|
|
71
79
|
|
|
72
80
|
let cursor = 0;
|
|
73
81
|
const arcs = slices.map((d, i) => {
|
|
@@ -77,7 +85,11 @@ export function DonutChart({
|
|
|
77
85
|
const sweep = slices.length === 1 ? FULL - 1e-3 : (d.value / total) * FULL;
|
|
78
86
|
const end = start + sweep;
|
|
79
87
|
cursor = end;
|
|
80
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
path: slicePath(start, end),
|
|
90
|
+
color: chartColorAt(i, d.color),
|
|
91
|
+
key: `${d.label}-${i}`,
|
|
92
|
+
};
|
|
81
93
|
});
|
|
82
94
|
|
|
83
95
|
const plot = (
|
|
@@ -90,7 +102,13 @@ export function DonutChart({
|
|
|
90
102
|
className="mx-auto block h-auto w-full max-w-[240px] text-ink-4"
|
|
91
103
|
>
|
|
92
104
|
{arcs.map((a) => (
|
|
93
|
-
<path
|
|
105
|
+
<path
|
|
106
|
+
key={a.key}
|
|
107
|
+
d={a.path}
|
|
108
|
+
fill={a.color}
|
|
109
|
+
stroke="var(--card, transparent)"
|
|
110
|
+
strokeWidth={1}
|
|
111
|
+
/>
|
|
94
112
|
))}
|
|
95
113
|
<text
|
|
96
114
|
x={CX}
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
// Non-finite points break the line (a new segment starts after the gap) rather
|
|
5
5
|
// than emitting a NaN coordinate.
|
|
6
6
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
-
import type { ReactNode } from 'react';
|
|
8
7
|
|
|
9
8
|
import { ChartEmpty, ChartFrame, type ChartLegendItem } from './chart-frame';
|
|
10
|
-
import type { ChartBaseProps, ChartLineSeries } from './chart-types';
|
|
11
9
|
import {
|
|
12
10
|
buildAriaSummary,
|
|
13
11
|
chartColorAt,
|
|
@@ -15,6 +13,9 @@ import {
|
|
|
15
13
|
niceScale,
|
|
16
14
|
} from './chart-utils';
|
|
17
15
|
|
|
16
|
+
import type { ChartBaseProps, ChartLineSeries } from './chart-types';
|
|
17
|
+
import type { ReactNode } from 'react';
|
|
18
|
+
|
|
18
19
|
const VIEW_W = 640;
|
|
19
20
|
const VIEW_H = 360;
|
|
20
21
|
const PAD = { top: 12, right: 12, bottom: 44, left: 48 } as const;
|
|
@@ -46,7 +47,9 @@ function toSegments(
|
|
|
46
47
|
current = [];
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
|
-
if (current.length > 0)
|
|
50
|
+
if (current.length > 0) {
|
|
51
|
+
segments.push(current.join(' '));
|
|
52
|
+
}
|
|
50
53
|
return segments;
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -65,23 +68,35 @@ export function LineChart({
|
|
|
65
68
|
let maxLen = 0;
|
|
66
69
|
for (const s of series) {
|
|
67
70
|
maxLen = Math.max(maxLen, s.points.length);
|
|
68
|
-
for (const p of s.points)
|
|
71
|
+
for (const p of s.points) {
|
|
72
|
+
if (Number.isFinite(p.value)) {
|
|
73
|
+
finiteValues.push(p.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
if (finiteValues.length === 0) {
|
|
72
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
<ChartEmpty title={title} label={emptyLabel} className={className} />
|
|
81
|
+
);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
const scale = niceScale(
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
const scale = niceScale(
|
|
85
|
+
Math.min(...finiteValues),
|
|
86
|
+
Math.max(...finiteValues),
|
|
87
|
+
{
|
|
88
|
+
pinZero: false,
|
|
89
|
+
},
|
|
90
|
+
);
|
|
78
91
|
const span = scale.max - scale.min;
|
|
79
|
-
const yToPx = (v: number): number =>
|
|
92
|
+
const yToPx = (v: number): number =>
|
|
93
|
+
PLOT_B - ((v - scale.min) / span) * PLOT_H;
|
|
80
94
|
const xToPx = (i: number): number =>
|
|
81
95
|
maxLen <= 1 ? PLOT_L + PLOT_W / 2 : PLOT_L + (i / (maxLen - 1)) * PLOT_W;
|
|
82
96
|
|
|
83
97
|
// x labels from the longest series (deterministic pick: first of that length).
|
|
84
|
-
const xLabelSource =
|
|
98
|
+
const xLabelSource =
|
|
99
|
+
series.find((s) => s.points.length === maxLen)?.points ?? [];
|
|
85
100
|
|
|
86
101
|
const legend: ChartLegendItem[] = series.map((s, i) => ({
|
|
87
102
|
label: s.name,
|
|
@@ -94,7 +109,8 @@ export function LineChart({
|
|
|
94
109
|
.filter((p) => Number.isFinite(p.value))
|
|
95
110
|
.map((p) => ({ label: `${s.name} ${p.label}`, value: p.value })),
|
|
96
111
|
);
|
|
97
|
-
const summary =
|
|
112
|
+
const summary =
|
|
113
|
+
ariaLabel ?? buildAriaSummary(title, summaryPoints, format, 'point');
|
|
98
114
|
|
|
99
115
|
const plot = (
|
|
100
116
|
<svg
|
|
@@ -165,7 +181,13 @@ export function LineChart({
|
|
|
165
181
|
))}
|
|
166
182
|
{s.points.map((p, i) =>
|
|
167
183
|
Number.isFinite(p.value) ? (
|
|
168
|
-
<circle
|
|
184
|
+
<circle
|
|
185
|
+
key={`pt-${i}`}
|
|
186
|
+
cx={xToPx(i)}
|
|
187
|
+
cy={yToPx(p.value)}
|
|
188
|
+
r={2.5}
|
|
189
|
+
fill={color}
|
|
190
|
+
>
|
|
169
191
|
<title>{`${s.name} — ${p.label}: ${format(p.value)}`}</title>
|
|
170
192
|
</circle>
|
|
171
193
|
) : null,
|