basalt-ui 1.26.0 → 1.28.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/AGENTS.md +23 -23
- package/CHANGELOG.md +67 -0
- package/MIGRATING.md +896 -37
- package/README.md +33 -3
- package/agent/rules/basalt-batteries.md +25 -10
- package/agent/rules/basalt-controls.md +73 -43
- package/agent/rules/basalt-mantine.md +17 -6
- package/agent/rules/basalt-state.md +5 -0
- package/agent/rules/basalt-tokens.md +1 -1
- package/configs/oxlint-plugin.js +1286 -73
- package/configs/oxlint-plugin.test.ts +1338 -29
- package/configs/oxlint.json +12 -5
- package/dist/agent/coalesce.d.ts.map +1 -1
- package/dist/agent/coalesce.js +2 -1
- package/dist/agent/merge.d.ts.map +1 -1
- package/dist/agent/merge.js +2 -1
- package/dist/agent/part-list.d.ts.map +1 -1
- package/dist/agent/part-list.js +2 -0
- package/dist/agent/stick-to-bottom.d.ts +3 -6
- package/dist/agent/stick-to-bottom.d.ts.map +1 -1
- package/dist/agent/stick-to-bottom.js +6 -3
- package/dist/agent-chat/composer.d.ts +6 -2
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +49 -33
- package/dist/agent-chat/relative-time.d.ts +0 -9
- package/dist/agent-chat/relative-time.d.ts.map +1 -1
- package/dist/agent-chat/relative-time.js +2 -1
- package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
- package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
- package/dist/agent-chat/thread-detail-panel.js +8 -5
- package/dist/agent-chat/thread-feed-row.d.ts +2 -1
- package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed-row.js +92 -92
- package/dist/agent-chat/thread-feed.d.ts +3 -2
- package/dist/agent-chat/thread-feed.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed.js +8 -3
- package/dist/agent-chat/thread-message.d.ts +4 -3
- package/dist/agent-chat/thread-message.d.ts.map +1 -1
- package/dist/agent-chat/thread-message.js +70 -31
- package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
- package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
- package/dist/agent-chat/thread-outcome-card.js +9 -2
- package/dist/agent-chat/thread-workspace.d.ts +7 -2
- package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
- package/dist/agent-chat/thread-workspace.js +62 -29
- package/dist/agent-chat/tool-chip.d.ts +3 -2
- package/dist/agent-chat/tool-chip.d.ts.map +1 -1
- package/dist/agent-chat/tool-chip.js +7 -2
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
- package/dist/charts/hooks/useBandPlot.js +15 -8
- package/dist/charts/index.d.ts +8 -7
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +45 -5
- package/dist/charts/kinds/BandStrip.d.ts +10 -7
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +12 -7
- package/dist/charts/kinds/Bars.d.ts +12 -6
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +37 -11
- package/dist/charts/kinds/Donut.d.ts +6 -5
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +19 -2
- package/dist/charts/kinds/DualPanel.d.ts +6 -5
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +36 -8
- package/dist/charts/kinds/Heatmap.d.ts +10 -6
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +58 -31
- package/dist/charts/kinds/MirroredBars.d.ts +6 -5
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +9 -2
- package/dist/charts/kinds/MultiLine.d.ts +11 -6
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +24 -15
- package/dist/charts/kinds/StackedArea.d.ts +18 -8
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +29 -4
- package/dist/charts/kinds/ZonedLine.d.ts +12 -8
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +33 -23
- package/dist/charts/layout/auto-margin.d.ts +65 -8
- package/dist/charts/layout/auto-margin.d.ts.map +1 -1
- package/dist/charts/layout/auto-margin.js +45 -10
- package/dist/charts/layout/log-ticks.d.ts +27 -0
- package/dist/charts/layout/log-ticks.d.ts.map +1 -0
- package/dist/charts/layout/log-ticks.js +42 -0
- package/dist/charts/primitives/Axes.d.ts +11 -2
- package/dist/charts/primitives/Axes.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.js +15 -5
- package/dist/charts/primitives/CartesianChart.d.ts +45 -7
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +86 -28
- package/dist/charts/primitives/ChartCard.d.ts +27 -2
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +37 -17
- package/dist/charts/primitives/ChartFrame.d.ts +30 -4
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +45 -15
- package/dist/charts/primitives/ChartLegend.d.ts +13 -2
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +33 -13
- package/dist/charts/primitives/ChartPending.d.ts +77 -4
- package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
- package/dist/charts/primitives/ChartPending.js +100 -6
- package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +18 -5
- package/dist/charts/primitives/Crosshair.d.ts +3 -0
- package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
- package/dist/charts/primitives/Crosshair.js +3 -1
- package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
- package/dist/charts/primitives/chart-frame-layout.js +89 -0
- package/dist/charts/primitives/chart-tier.d.ts +16 -0
- package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
- package/dist/charts/primitives/chart-tier.js +20 -0
- package/dist/charts/series.d.ts +55 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +27 -1
- package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
- package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/BarSparkline.js +9 -3
- package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
- package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
- package/dist/charts/sparklines/LineSparkline.js +15 -3
- package/dist/charts/sparklines/index.d.ts +2 -2
- package/dist/charts/sparklines/index.d.ts.map +1 -1
- package/dist/charts/utils/format.d.ts +92 -2
- package/dist/charts/utils/format.d.ts.map +1 -1
- package/dist/charts/utils/format.js +64 -1
- package/dist/charts/utils/ticks.d.ts +81 -2
- package/dist/charts/utils/ticks.d.ts.map +1 -1
- package/dist/charts/utils/ticks.js +54 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/commands/ShortcutsHelp.d.ts +3 -2
- package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
- package/dist/commands/ShortcutsHelp.js +8 -2
- package/dist/commands/define-commands.d.ts.map +1 -1
- package/dist/commands/define-commands.js +2 -2
- package/dist/commands/define-overlays.d.ts +102 -0
- package/dist/commands/define-overlays.d.ts.map +1 -1
- package/dist/commands/define-overlays.js +102 -2
- package/dist/commands/index.d.ts +10 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +3 -1
- package/dist/commands/overlays-mount.d.ts +13 -6
- package/dist/commands/overlays-mount.d.ts.map +1 -1
- package/dist/commands/overlays-mount.js +23 -14
- package/dist/common/errors.d.ts +34 -0
- package/dist/common/errors.d.ts.map +1 -0
- package/dist/common/errors.js +36 -0
- package/dist/common/index.d.ts +15 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +29 -0
- package/dist/common/props.d.ts +63 -0
- package/dist/common/props.d.ts.map +1 -0
- package/dist/common/props.js +6 -0
- package/dist/common/query-state-like.d.ts +43 -0
- package/dist/common/query-state-like.d.ts.map +1 -0
- package/dist/common/query-state-like.js +20 -0
- package/dist/common/refs.d.ts +29 -0
- package/dist/common/refs.d.ts.map +1 -0
- package/dist/common/refs.js +29 -0
- package/dist/common/scroll-parent.d.ts +23 -0
- package/dist/common/scroll-parent.d.ts.map +1 -0
- package/dist/common/scroll-parent.js +21 -0
- package/dist/common/validate.d.ts +38 -0
- package/dist/common/validate.d.ts.map +1 -0
- package/dist/common/validate.js +36 -0
- package/dist/connectivity/connectivity-indicator.d.ts +3 -1
- package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
- package/dist/connectivity/connectivity-indicator.js +3 -1
- package/dist/connectivity/connectivity-provider.d.ts.map +1 -1
- package/dist/connectivity/connectivity-provider.js +4 -1
- package/dist/connectivity/index.d.ts +1 -0
- package/dist/connectivity/index.d.ts.map +1 -1
- package/dist/connectivity/types.d.ts +2 -2
- package/dist/connectivity/types.d.ts.map +1 -1
- package/dist/connectivity/use-connectivity.d.ts.map +1 -1
- package/dist/content/article-layout.module.css +11 -4
- package/dist/content/callout.d.ts +8 -4
- package/dist/content/callout.d.ts.map +1 -1
- package/dist/content/callout.js +15 -8
- package/dist/content/guide.d.ts +5 -4
- package/dist/content/guide.d.ts.map +1 -1
- package/dist/content/guide.js +13 -4
- package/dist/content/heading-anchor.d.ts +24 -2
- package/dist/content/heading-anchor.d.ts.map +1 -1
- package/dist/content/heading-anchor.js +23 -9
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +2 -0
- package/dist/content/prose.module.css +9 -7
- package/dist/content/reading-progress.d.ts +10 -4
- package/dist/content/reading-progress.d.ts.map +1 -1
- package/dist/content/reading-progress.js +26 -10
- package/dist/content/slug.d.ts +4 -0
- package/dist/content/slug.d.ts.map +1 -1
- package/dist/content/toc.d.ts +11 -0
- package/dist/content/toc.d.ts.map +1 -1
- package/dist/content/toc.js +23 -10
- package/dist/controls/actions.d.ts +49 -6
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/actions.js +39 -8
- package/dist/controls/compare-filter.d.ts +3 -2
- package/dist/controls/compare-filter.d.ts.map +1 -1
- package/dist/controls/compare-filter.js +17 -2
- package/dist/controls/control-group.d.ts +3 -3
- package/dist/controls/control-group.d.ts.map +1 -1
- package/dist/controls/control-group.js +9 -2
- package/dist/controls/controls.module.css +169 -186
- package/dist/controls/controls.module.css.d.ts +9 -11
- package/dist/controls/enum-filter.d.ts +13 -3
- package/dist/controls/enum-filter.d.ts.map +1 -1
- package/dist/controls/enum-filter.js +32 -15
- package/dist/controls/filter-context.d.ts +15 -6
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/filter-pill.d.ts +3 -3
- package/dist/controls/filter-pill.d.ts.map +1 -1
- package/dist/controls/filter-pill.js +4 -1
- package/dist/controls/filter-set.d.ts +3 -2
- package/dist/controls/filter-set.d.ts.map +1 -1
- package/dist/controls/filter-set.js +66 -41
- package/dist/controls/filter-sheet.d.ts +20 -80
- package/dist/controls/filter-sheet.d.ts.map +1 -1
- package/dist/controls/filter-sheet.js +9 -95
- package/dist/controls/index.d.ts +3 -0
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +6 -0
- package/dist/controls/multi-select-filter.d.ts +18 -2
- package/dist/controls/multi-select-filter.d.ts.map +1 -1
- package/dist/controls/multi-select-filter.js +124 -22
- package/dist/controls/number-filter.d.ts +28 -0
- package/dist/controls/number-filter.d.ts.map +1 -0
- package/dist/controls/number-filter.js +172 -0
- package/dist/controls/panel-row.d.ts +132 -0
- package/dist/controls/panel-row.d.ts.map +1 -0
- package/dist/controls/panel-row.js +173 -0
- package/dist/controls/panel-row.module.css +86 -0
- package/dist/controls/panel-row.module.css.d.ts +13 -0
- package/dist/controls/range-filter.d.ts +20 -5
- package/dist/controls/range-filter.d.ts.map +1 -1
- package/dist/controls/range-filter.js +75 -38
- package/dist/controls/search-filter.d.ts +3 -2
- package/dist/controls/search-filter.d.ts.map +1 -1
- package/dist/controls/search-filter.js +26 -11
- package/dist/controls/select-filter.d.ts +2 -1
- package/dist/controls/select-filter.d.ts.map +1 -1
- package/dist/controls/select-filter.js +4 -0
- package/dist/controls/slider-control.d.ts +36 -0
- package/dist/controls/slider-control.d.ts.map +1 -0
- package/dist/controls/slider-control.js +64 -0
- package/dist/controls/sync-button.d.ts +3 -2
- package/dist/controls/sync-button.d.ts.map +1 -1
- package/dist/controls/sync-button.js +16 -3
- package/dist/controls/toggle-filter.d.ts +3 -2
- package/dist/controls/toggle-filter.d.ts.map +1 -1
- package/dist/controls/toggle-filter.js +29 -12
- package/dist/controls/view-tabs.d.ts +3 -2
- package/dist/controls/view-tabs.d.ts.map +1 -1
- package/dist/controls/view-tabs.js +43 -11
- package/dist/controls-dates/date-range-picker.d.ts +1 -1
- package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
- package/dist/controls-dates/date-range-picker.js +6 -1
- package/dist/dashboard/delta-badge.d.ts +5 -1
- package/dist/dashboard/delta-badge.d.ts.map +1 -1
- package/dist/dashboard/empty-state.d.ts +10 -3
- package/dist/dashboard/empty-state.d.ts.map +1 -1
- package/dist/dashboard/empty-state.js +37 -6
- package/dist/dashboard/index.d.ts +6 -4
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +10 -2
- package/dist/dashboard/query-state.d.ts +48 -27
- package/dist/dashboard/query-state.d.ts.map +1 -1
- package/dist/dashboard/query-state.js +111 -57
- package/dist/dashboard/settings-section.d.ts +19 -9
- package/dist/dashboard/settings-section.d.ts.map +1 -1
- package/dist/dashboard/settings-section.js +74 -25
- package/dist/dashboard/stat-card.d.ts +76 -19
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +78 -38
- package/dist/dashboard/stat-card.module.css +112 -26
- package/dist/dashboard/stat-card.module.css.d.ts +5 -0
- package/dist/dashboard/stat-group.d.ts +41 -0
- package/dist/dashboard/stat-group.d.ts.map +1 -0
- package/dist/dashboard/stat-group.js +30 -0
- package/dist/dashboard/stat-group.module.css +66 -0
- package/dist/dashboard/stat-group.module.css.d.ts +8 -0
- package/dist/dashboard/widget-grid.d.ts +27 -0
- package/dist/dashboard/widget-grid.d.ts.map +1 -0
- package/dist/dashboard/widget-grid.js +41 -0
- package/dist/dashboard/widget-grid.module.css +53 -0
- package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
- package/dist/data/data-table.d.ts +151 -12
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +392 -138
- package/dist/data/data-table.module.css +64 -0
- package/dist/data/data-table.module.css.d.ts +11 -0
- package/dist/data/index.d.ts +2 -2
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +24 -2
- package/dist/data/query-branch.d.ts +28 -0
- package/dist/data/query-branch.d.ts.map +1 -0
- package/dist/data/query-branch.js +10 -0
- package/dist/data/table.d.ts +2 -2
- package/dist/data/table.d.ts.map +1 -1
- package/dist/data/virtual-list.d.ts +65 -6
- package/dist/data/virtual-list.d.ts.map +1 -1
- package/dist/data/virtual-list.js +89 -38
- package/dist/data/virtual.d.ts +1 -1
- package/dist/data/virtual.d.ts.map +1 -1
- package/dist/forms/FormErrorSummary.d.ts +4 -3
- package/dist/forms/FormErrorSummary.d.ts.map +1 -1
- package/dist/forms/FormErrorSummary.js +6 -4
- package/dist/forms/create-form.d.ts +2 -2
- package/dist/forms/field.d.ts +46 -6
- package/dist/forms/field.d.ts.map +1 -1
- package/dist/forms/field.js +10 -5
- package/dist/forms/form-layout.d.ts +142 -0
- package/dist/forms/form-layout.d.ts.map +1 -0
- package/dist/forms/form-layout.js +110 -0
- package/dist/forms/form-layout.module.css +145 -0
- package/dist/forms/form-layout.module.css.d.ts +20 -0
- package/dist/forms/form-state.d.ts +42 -0
- package/dist/forms/form-state.d.ts.map +1 -0
- package/dist/forms/form-state.js +26 -0
- package/dist/forms/index.d.ts +22 -6
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +19 -3
- package/dist/forms/use-field-array.d.ts +60 -0
- package/dist/forms/use-field-array.d.ts.map +1 -0
- package/dist/forms/use-field-array.js +20 -0
- package/dist/forms/use-form-draft.d.ts +25 -10
- package/dist/forms/use-form-draft.d.ts.map +1 -1
- package/dist/forms/use-form-draft.js +55 -18
- package/dist/forms/use-form-submit.d.ts +88 -0
- package/dist/forms/use-form-submit.d.ts.map +1 -0
- package/dist/forms/use-form-submit.js +63 -0
- package/dist/guard/index.d.ts +1 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +27 -11
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -4
- package/dist/nav/types.d.ts +15 -6
- package/dist/nav/types.d.ts.map +1 -1
- package/dist/notifications/bell.d.ts +3 -2
- package/dist/notifications/bell.d.ts.map +1 -1
- package/dist/notifications/bell.js +73 -56
- package/dist/notifications/center.d.ts +15 -4
- package/dist/notifications/center.d.ts.map +1 -1
- package/dist/notifications/center.js +5 -2
- package/dist/notifications/define-notifications.d.ts +56 -14
- package/dist/notifications/define-notifications.d.ts.map +1 -1
- package/dist/notifications/define-notifications.js +2 -1
- package/dist/notifications/index.d.ts +6 -2
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +5 -1
- package/dist/notifications/mount-guard.d.ts +8 -0
- package/dist/notifications/mount-guard.d.ts.map +1 -0
- package/dist/notifications/mount-guard.js +19 -0
- package/dist/notifications/notify.d.ts +58 -1
- package/dist/notifications/notify.d.ts.map +1 -1
- package/dist/notifications/notify.js +46 -2
- package/dist/notifications/overlay.d.ts.map +1 -1
- package/dist/notifications/overlay.js +2 -0
- package/dist/notifications/store.d.ts +7 -0
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/provider/index.d.ts +37 -5
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +66 -28
- package/dist/provider/lab-theme.d.ts.map +1 -1
- package/dist/provider/lab-theme.js +2 -1
- package/dist/query/devtools.d.ts.map +1 -1
- package/dist/query/devtools.js +1 -0
- package/dist/router-tanstack/field.d.ts +1 -1
- package/dist/router-tanstack/field.d.ts.map +1 -1
- package/dist/router-tanstack/index.d.ts +1 -1
- package/dist/router-tanstack/index.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.d.ts +4 -1
- package/dist/router-tanstack/search-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.js +92 -8
- package/dist/router-tanstack/use-nav.d.ts.map +1 -1
- package/dist/router-tanstack/use-nav.js +1 -1
- package/dist/section/index.d.ts +1 -1
- package/dist/section/index.d.ts.map +1 -1
- package/dist/section/section.d.ts +36 -2
- package/dist/section/section.d.ts.map +1 -1
- package/dist/section/section.js +49 -57
- package/dist/section/section.module.css +11 -0
- package/dist/shell/account-types.d.ts +5 -2
- package/dist/shell/account-types.d.ts.map +1 -1
- package/dist/shell/app-brand.d.ts +13 -0
- package/dist/shell/app-brand.d.ts.map +1 -0
- package/dist/shell/app-brand.js +94 -0
- package/dist/shell/app-brand.module.css +109 -0
- package/dist/shell/app-brand.module.css.d.ts +14 -0
- package/dist/shell/app-breadcrumbs.d.ts +4 -2
- package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
- package/dist/shell/app-breadcrumbs.js +33 -22
- package/dist/shell/app-header.module.css +10 -5
- package/dist/shell/app-main.module.css +107 -0
- package/dist/shell/app-main.module.css.d.ts +11 -0
- package/dist/shell/app-mobile-nav.d.ts +3 -2
- package/dist/shell/app-mobile-nav.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.js +29 -15
- package/dist/shell/app-mobile-nav.module.css +48 -27
- package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
- package/dist/shell/app-sidebar-account.d.ts +2 -1
- package/dist/shell/app-sidebar-account.d.ts.map +1 -1
- package/dist/shell/app-sidebar-account.js +57 -22
- package/dist/shell/app-sidebar.d.ts +9 -3
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +55 -109
- package/dist/shell/app-sidebar.module.css +53 -96
- package/dist/shell/app-sidebar.module.css.d.ts +0 -5
- package/dist/shell/index.d.ts +14 -6
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +47 -12
- package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
- package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/shell/isomorphic-layout-effect.js +5 -0
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/shell/mobile-nav-model.js +27 -6
- package/dist/shell/nav-count-badge.d.ts +5 -3
- package/dist/shell/nav-count-badge.d.ts.map +1 -1
- package/dist/shell/nav-count-badge.js +3 -1
- package/dist/shell/page-aside.d.ts +43 -0
- package/dist/shell/page-aside.d.ts.map +1 -0
- package/dist/shell/page-aside.js +202 -0
- package/dist/shell/page-aside.module.css +174 -0
- package/dist/shell/page-aside.module.css.d.ts +14 -0
- package/dist/shell/page-bar.d.ts +55 -15
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +130 -41
- package/dist/shell/page-bar.module.css +107 -13
- package/dist/shell/page-bar.module.css.d.ts +4 -1
- package/dist/shell/sidebar-search.d.ts +4 -2
- package/dist/shell/sidebar-search.d.ts.map +1 -1
- package/dist/shell/sidebar-search.js +9 -4
- package/dist/shell/sidebar-search.module.css +1 -1
- package/dist/state/fields.d.ts +227 -34
- package/dist/state/fields.d.ts.map +1 -1
- package/dist/state/fields.js +77 -30
- package/dist/state/persisted-or-local.d.ts +9 -0
- package/dist/state/persisted-or-local.d.ts.map +1 -0
- package/dist/state/persisted-or-local.js +24 -0
- package/dist/state/persisted.d.ts +12 -0
- package/dist/state/persisted.d.ts.map +1 -1
- package/dist/state/persisted.js +36 -18
- package/dist/state.d.ts +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +2 -6
- package/dist/surfaces.d.ts +12 -11
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +43 -12
- package/dist/theme/ctl-theme.d.ts.map +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +102 -17
- package/dist/theme/nav-link.module.css +37 -26
- package/dist/theme/segmented-control.module.css +22 -0
- package/dist/theme/segmented-control.module.css.d.ts +1 -0
- package/dist/theme-lab/boot.d.ts +66 -0
- package/dist/theme-lab/boot.d.ts.map +1 -0
- package/dist/theme-lab/boot.js +82 -0
- package/dist/theme-lab/index.d.ts +4 -43
- package/dist/theme-lab/index.d.ts.map +1 -1
- package/dist/theme-lab/index.js +9 -69
- package/dist/theme-toggle/index.d.ts +3 -2
- package/dist/theme-toggle/index.d.ts.map +1 -1
- package/dist/theme-toggle/index.js +8 -2
- package/dist/tokens/index.d.ts +42 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +35 -6
- package/dist/tokens/palette.d.ts +45 -25
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +31 -14
- package/dist/tokens.css +4 -5
- package/dist/utils/is-dev.d.ts +3 -0
- package/dist/utils/is-dev.d.ts.map +1 -0
- package/dist/utils/is-dev.js +6 -0
- package/dist/widget-header/delta-badge.d.ts +7 -1
- package/dist/widget-header/delta-badge.d.ts.map +1 -1
- package/dist/widget-header/delta-badge.js +8 -1
- package/dist/widget-header/index.d.ts +2 -2
- package/dist/widget-header/index.d.ts.map +1 -1
- package/dist/widget-header/index.js +3 -1
- package/dist/widget-header/widget-header.d.ts +99 -4
- package/dist/widget-header/widget-header.d.ts.map +1 -1
- package/dist/widget-header/widget-header.js +45 -18
- package/dist/widget-header/widget-header.module.css +87 -7
- package/dist/widget-header/widget-header.module.css.d.ts +1 -0
- package/llms.txt +6 -6
- package/package.json +49 -25
- package/src/agent/coalesce.ts +2 -1
- package/src/agent/merge.ts +2 -1
- package/src/agent/part-list.tsx +2 -0
- package/src/agent/stick-to-bottom.tsx +9 -11
- package/src/agent-chat/composer.tsx +124 -103
- package/src/agent-chat/relative-time.ts +2 -1
- package/src/agent-chat/thread-detail-panel.tsx +9 -5
- package/src/agent-chat/thread-feed-row.tsx +17 -9
- package/src/agent-chat/thread-feed.tsx +9 -3
- package/src/agent-chat/thread-message.tsx +48 -13
- package/src/agent-chat/thread-outcome-card.tsx +9 -1
- package/src/agent-chat/thread-workspace.tsx +72 -33
- package/src/agent-chat/tool-chip.tsx +8 -2
- package/src/charts/hooks/useBandPlot.ts +26 -8
- package/src/charts/index.ts +39 -4
- package/src/charts/kinds/BandStrip.tsx +36 -14
- package/src/charts/kinds/Bars.tsx +67 -16
- package/src/charts/kinds/Donut.tsx +30 -3
- package/src/charts/kinds/DualPanel.tsx +56 -10
- package/src/charts/kinds/Heatmap.tsx +90 -33
- package/src/charts/kinds/MirroredBars.tsx +21 -3
- package/src/charts/kinds/MultiLine.tsx +61 -18
- package/src/charts/kinds/StackedArea.tsx +77 -6
- package/src/charts/kinds/ZonedLine.tsx +71 -31
- package/src/charts/layout/auto-margin.ts +134 -21
- package/src/charts/layout/log-ticks.ts +90 -0
- package/src/charts/primitives/Axes.tsx +33 -5
- package/src/charts/primitives/CartesianChart.tsx +194 -41
- package/src/charts/primitives/ChartCard.tsx +66 -25
- package/src/charts/primitives/ChartFrame.tsx +107 -26
- package/src/charts/primitives/ChartLegend.tsx +43 -13
- package/src/charts/primitives/ChartPending.tsx +185 -7
- package/src/charts/primitives/ChartTooltip.tsx +37 -5
- package/src/charts/primitives/Crosshair.tsx +6 -3
- package/src/charts/primitives/chart-frame-layout.ts +212 -0
- package/src/charts/primitives/chart-tier.tsx +37 -0
- package/src/charts/series.ts +77 -1
- package/src/charts/sparklines/BarSparkline.tsx +11 -3
- package/src/charts/sparklines/LineSparkline.tsx +18 -4
- package/src/charts/sparklines/index.ts +2 -2
- package/src/charts/utils/format.ts +163 -2
- package/src/charts/utils/ticks.ts +149 -4
- package/src/cli/index.ts +8 -3
- package/src/commands/ShortcutsHelp.tsx +10 -3
- package/src/commands/define-commands.ts +2 -2
- package/src/commands/define-overlays.ts +216 -1
- package/src/commands/index.ts +12 -0
- package/src/commands/overlays-mount.tsx +60 -26
- package/src/common/errors.ts +73 -0
- package/src/common/index.ts +21 -0
- package/src/common/props.ts +71 -0
- package/src/common/query-state-like.ts +64 -0
- package/src/common/refs.ts +58 -0
- package/src/common/scroll-parent.ts +42 -0
- package/src/common/validate.ts +94 -0
- package/src/connectivity/connectivity-indicator.tsx +6 -1
- package/src/connectivity/connectivity-provider.tsx +4 -1
- package/src/connectivity/index.ts +1 -0
- package/src/connectivity/types.ts +5 -2
- package/src/connectivity/use-connectivity.ts +3 -2
- package/src/content/article-layout.module.css +11 -4
- package/src/content/callout.tsx +13 -7
- package/src/content/guide.tsx +14 -4
- package/src/content/heading-anchor.tsx +14 -3
- package/src/content/index.ts +2 -0
- package/src/content/markdown.tsx +2 -0
- package/src/content/prose.module.css +9 -7
- package/src/content/reading-progress.tsx +40 -15
- package/src/content/slug.ts +4 -0
- package/src/content/toc.tsx +47 -10
- package/src/controls/actions.tsx +89 -9
- package/src/controls/compare-filter.tsx +20 -3
- package/src/controls/control-group.tsx +11 -4
- package/src/controls/controls.module.css +169 -186
- package/src/controls/controls.module.css.d.ts +9 -11
- package/src/controls/enum-filter.tsx +46 -19
- package/src/controls/filter-context.tsx +21 -8
- package/src/controls/filter-pill.tsx +6 -3
- package/src/controls/filter-set.tsx +32 -13
- package/src/controls/filter-sheet.tsx +44 -194
- package/src/controls/index.ts +8 -0
- package/src/controls/multi-select-filter.tsx +179 -29
- package/src/controls/number-filter.tsx +329 -0
- package/src/controls/panel-row.module.css +86 -0
- package/src/controls/panel-row.module.css.d.ts +13 -0
- package/src/controls/panel-row.tsx +392 -0
- package/src/controls/range-filter.tsx +133 -43
- package/src/controls/search-filter.tsx +31 -13
- package/src/controls/select-filter.tsx +11 -1
- package/src/controls/slider-control.tsx +159 -0
- package/src/controls/sync-button.tsx +13 -3
- package/src/controls/toggle-filter.tsx +37 -17
- package/src/controls/view-tabs.tsx +63 -11
- package/src/controls-dates/date-range-picker.tsx +8 -1
- package/src/dashboard/delta-badge.tsx +5 -1
- package/src/dashboard/empty-state.tsx +72 -25
- package/src/dashboard/index.ts +19 -2
- package/src/dashboard/query-state.tsx +154 -84
- package/src/dashboard/settings-section.tsx +87 -31
- package/src/dashboard/stat-card.module.css +112 -26
- package/src/dashboard/stat-card.module.css.d.ts +5 -0
- package/src/dashboard/stat-card.tsx +207 -68
- package/src/dashboard/stat-group.module.css +66 -0
- package/src/dashboard/stat-group.module.css.d.ts +8 -0
- package/src/dashboard/stat-group.tsx +74 -0
- package/src/dashboard/widget-grid.module.css +53 -0
- package/src/dashboard/widget-grid.module.css.d.ts +9 -0
- package/src/dashboard/widget-grid.tsx +127 -0
- package/src/data/data-table.module.css +64 -0
- package/src/data/data-table.module.css.d.ts +11 -0
- package/src/data/data-table.tsx +927 -346
- package/src/data/index.ts +30 -2
- package/src/data/query-branch.ts +53 -0
- package/src/data/table.ts +5 -0
- package/src/data/virtual-list.tsx +189 -55
- package/src/data/virtual.ts +5 -1
- package/src/forms/FormErrorSummary.tsx +13 -6
- package/src/forms/create-form.ts +2 -2
- package/src/forms/field.ts +60 -11
- package/src/forms/form-layout.module.css +145 -0
- package/src/forms/form-layout.module.css.d.ts +20 -0
- package/src/forms/form-layout.tsx +305 -0
- package/src/forms/form-state.tsx +94 -0
- package/src/forms/index.ts +38 -7
- package/src/forms/use-field-array.ts +94 -0
- package/src/forms/use-form-draft.ts +145 -53
- package/src/forms/use-form-submit.ts +199 -0
- package/src/guard/index.ts +91 -23
- package/src/index.ts +79 -10
- package/src/nav/types.ts +15 -6
- package/src/notifications/bell.tsx +18 -4
- package/src/notifications/center.tsx +18 -5
- package/src/notifications/define-notifications.ts +64 -18
- package/src/notifications/index.ts +12 -1
- package/src/notifications/mount-guard.ts +41 -0
- package/src/notifications/notify.tsx +122 -3
- package/src/notifications/overlay.tsx +2 -0
- package/src/notifications/store.ts +7 -0
- package/src/provider/index.tsx +101 -13
- package/src/provider/lab-theme.ts +2 -1
- package/src/query/devtools.tsx +1 -0
- package/src/router-tanstack/field.ts +5 -0
- package/src/router-tanstack/index.ts +4 -0
- package/src/router-tanstack/search-store.ts +171 -12
- package/src/router-tanstack/use-nav.ts +18 -13
- package/src/section/index.ts +1 -1
- package/src/section/section.module.css +11 -0
- package/src/section/section.tsx +163 -96
- package/src/shell/account-types.ts +5 -2
- package/src/shell/app-brand.module.css +109 -0
- package/src/shell/app-brand.module.css.d.ts +14 -0
- package/src/shell/app-brand.tsx +140 -0
- package/src/shell/app-breadcrumbs.tsx +19 -9
- package/src/shell/app-header.module.css +10 -5
- package/src/shell/app-main.module.css +107 -0
- package/src/shell/app-main.module.css.d.ts +11 -0
- package/src/shell/app-mobile-nav.module.css +48 -27
- package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
- package/src/shell/app-mobile-nav.tsx +43 -11
- package/src/shell/app-sidebar-account.tsx +29 -6
- package/src/shell/app-sidebar.module.css +53 -96
- package/src/shell/app-sidebar.module.css.d.ts +0 -5
- package/src/shell/app-sidebar.tsx +40 -113
- package/src/shell/index.tsx +156 -65
- package/src/shell/isomorphic-layout-effect.ts +14 -0
- package/src/shell/mobile-nav-model.ts +66 -10
- package/src/shell/nav-count-badge.tsx +6 -1
- package/src/shell/page-aside.module.css +174 -0
- package/src/shell/page-aside.module.css.d.ts +14 -0
- package/src/shell/page-aside.tsx +377 -0
- package/src/shell/page-bar.module.css +107 -13
- package/src/shell/page-bar.module.css.d.ts +4 -1
- package/src/shell/page-bar.tsx +294 -94
- package/src/shell/sidebar-search.module.css +1 -1
- package/src/shell/sidebar-search.tsx +13 -4
- package/src/state/fields.ts +360 -69
- package/src/state/persisted-or-local.ts +50 -0
- package/src/state/persisted.ts +76 -26
- package/src/state.ts +4 -7
- package/src/surfaces.ts +43 -12
- package/src/theme/ctl-theme.tsx +10 -0
- package/src/theme/index.ts +129 -16
- package/src/theme/nav-link.module.css +37 -26
- package/src/theme/segmented-control.module.css +22 -0
- package/src/theme/segmented-control.module.css.d.ts +1 -0
- package/src/theme-lab/boot.ts +140 -0
- package/src/theme-lab/index.tsx +21 -112
- package/src/theme-toggle/index.tsx +10 -3
- package/src/tokens/index.ts +50 -6
- package/src/tokens/palette.ts +58 -30
- package/src/utils/is-dev.ts +4 -0
- package/src/widget-header/delta-badge.tsx +25 -5
- package/src/widget-header/index.ts +10 -2
- package/src/widget-header/widget-header.module.css +87 -7
- package/src/widget-header/widget-header.module.css.d.ts +1 -0
- package/src/widget-header/widget-header.tsx +166 -42
- package/dist/agent/adapter.test.d.ts +0 -2
- package/dist/agent/adapter.test.d.ts.map +0 -1
- package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
- package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
- package/dist/agent/coalesce.test.d.ts +0 -2
- package/dist/agent/coalesce.test.d.ts.map +0 -1
- package/dist/agent/foreign.test.d.ts +0 -2
- package/dist/agent/foreign.test.d.ts.map +0 -1
- package/dist/agent/id.test.d.ts +0 -2
- package/dist/agent/id.test.d.ts.map +0 -1
- package/dist/agent/merge.test.d.ts +0 -2
- package/dist/agent/merge.test.d.ts.map +0 -1
- package/dist/agent/part-list.test.d.ts +0 -2
- package/dist/agent/part-list.test.d.ts.map +0 -1
- package/dist/agent/parts.test.d.ts +0 -2
- package/dist/agent/parts.test.d.ts.map +0 -1
- package/dist/agent/thread.test.d.ts +0 -2
- package/dist/agent/thread.test.d.ts.map +0 -1
- package/dist/agent/transport.test.d.ts +0 -2
- package/dist/agent/transport.test.d.ts.map +0 -1
- package/dist/agent/use-agent-stream.test.d.ts +0 -2
- package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
- package/dist/agent-chat/composer.test.d.ts +0 -2
- package/dist/agent-chat/composer.test.d.ts.map +0 -1
- package/dist/agent-chat/relative-time.test.d.ts +0 -2
- package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
- package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-feed.test.d.ts +0 -2
- package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-message.test.d.ts +0 -2
- package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
- package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
- package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
- package/dist/agent-chat/tool-chip.test.d.ts +0 -2
- package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
- package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
- package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
- package/dist/charts/cursor/resolve.test.d.ts +0 -2
- package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
- package/dist/charts/cursor/store.test.d.ts +0 -2
- package/dist/charts/cursor/store.test.d.ts.map +0 -1
- package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
- package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
- package/dist/charts/kinds/Bars.test.d.ts +0 -2
- package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
- package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
- package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
- package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
- package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
- package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
- package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
- package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
- package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
- package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
- package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
- package/dist/charts/layout/auto-margin.test.d.ts +0 -2
- package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
- package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
- package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
- package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
- package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
- package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
- package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
- package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
- package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
- package/dist/charts/series.test.d.ts +0 -2
- package/dist/charts/series.test.d.ts.map +0 -1
- package/dist/charts/utils/domain.test.d.ts +0 -2
- package/dist/charts/utils/domain.test.d.ts.map +0 -1
- package/dist/charts/utils/measure-text.test.d.ts +0 -2
- package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
- package/dist/cli/check-coverage.test.d.ts +0 -2
- package/dist/cli/check-coverage.test.d.ts.map +0 -1
- package/dist/cli/check-theme.test.d.ts +0 -2
- package/dist/cli/check-theme.test.d.ts.map +0 -1
- package/dist/cli/help.test.d.ts +0 -2
- package/dist/cli/help.test.d.ts.map +0 -1
- package/dist/cli/init-sync.test.d.ts +0 -2
- package/dist/cli/init-sync.test.d.ts.map +0 -1
- package/dist/cli/placement-engine.test.d.ts +0 -2
- package/dist/cli/placement-engine.test.d.ts.map +0 -1
- package/dist/cli/shipped-versions.test.d.ts +0 -2
- package/dist/cli/shipped-versions.test.d.ts.map +0 -1
- package/dist/cli/tarball-parity.test.d.ts +0 -2
- package/dist/cli/tarball-parity.test.d.ts.map +0 -1
- package/dist/cli/template-format.test.d.ts +0 -2
- package/dist/cli/template-format.test.d.ts.map +0 -1
- package/dist/cli/template-ledger.test.d.ts +0 -2
- package/dist/cli/template-ledger.test.d.ts.map +0 -1
- package/dist/cli/tokens-css.test.d.ts +0 -2
- package/dist/cli/tokens-css.test.d.ts.map +0 -1
- package/dist/cli/toolchain-wiring.test.d.ts +0 -2
- package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
- package/dist/cli/waiver-audit.test.d.ts +0 -2
- package/dist/cli/waiver-audit.test.d.ts.map +0 -1
- package/dist/content/article-actions.test.d.ts +0 -2
- package/dist/content/article-actions.test.d.ts.map +0 -1
- package/dist/content/article-model.test.d.ts +0 -2
- package/dist/content/article-model.test.d.ts.map +0 -1
- package/dist/content/block-split.test.d.ts +0 -2
- package/dist/content/block-split.test.d.ts.map +0 -1
- package/dist/content/heading-components.test.d.ts +0 -2
- package/dist/content/heading-components.test.d.ts.map +0 -1
- package/dist/content/markdown.test.d.ts +0 -2
- package/dist/content/markdown.test.d.ts.map +0 -1
- package/dist/content/prose.module.css.test.d.ts +0 -2
- package/dist/content/prose.module.css.test.d.ts.map +0 -1
- package/dist/content/sanitize.test.d.ts +0 -2
- package/dist/content/sanitize.test.d.ts.map +0 -1
- package/dist/content/slug.test.d.ts +0 -2
- package/dist/content/slug.test.d.ts.map +0 -1
- package/dist/controls/actions.test.d.ts +0 -2
- package/dist/controls/actions.test.d.ts.map +0 -1
- package/dist/controls/actions.type-guard.test.d.ts +0 -2
- package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
- package/dist/controls/control-group.test.d.ts +0 -2
- package/dist/controls/control-group.test.d.ts.map +0 -1
- package/dist/controls/controls.router.test.d.ts +0 -2
- package/dist/controls/controls.router.test.d.ts.map +0 -1
- package/dist/controls/filter-set.test.d.ts +0 -2
- package/dist/controls/filter-set.test.d.ts.map +0 -1
- package/dist/controls/peer-boundary.test.d.ts +0 -2
- package/dist/controls/peer-boundary.test.d.ts.map +0 -1
- package/dist/controls/props-contract.test.d.ts +0 -2
- package/dist/controls/props-contract.test.d.ts.map +0 -1
- package/dist/controls/sync-button.test.d.ts +0 -2
- package/dist/controls/sync-button.test.d.ts.map +0 -1
- package/dist/dashboard/query-state.test.d.ts +0 -2
- package/dist/dashboard/query-state.test.d.ts.map +0 -1
- package/dist/dashboard/settings-section.test.d.ts +0 -2
- package/dist/dashboard/settings-section.test.d.ts.map +0 -1
- package/dist/dashboard/stat-card.test.d.ts +0 -2
- package/dist/dashboard/stat-card.test.d.ts.map +0 -1
- package/dist/data/data-table.test.d.ts +0 -2
- package/dist/data/data-table.test.d.ts.map +0 -1
- package/dist/guard/check-source.test.d.ts +0 -2
- package/dist/guard/check-source.test.d.ts.map +0 -1
- package/dist/guard/grace.test.d.ts +0 -20
- package/dist/guard/grace.test.d.ts.map +0 -1
- package/dist/guard/guard-hook.test.d.ts +0 -2
- package/dist/guard/guard-hook.test.d.ts.map +0 -1
- package/dist/provider/index.test.d.ts +0 -2
- package/dist/provider/index.test.d.ts.map +0 -1
- package/dist/provider/lab-theme.test.d.ts +0 -2
- package/dist/provider/lab-theme.test.d.ts.map +0 -1
- package/dist/query/error-message.test.d.ts +0 -2
- package/dist/query/error-message.test.d.ts.map +0 -1
- package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
- package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
- package/dist/router-tanstack/nav.test.d.ts +0 -2
- package/dist/router-tanstack/nav.test.d.ts.map +0 -1
- package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
- package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
- package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
- package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
- package/dist/router-tanstack/search-store.test.d.ts +0 -2
- package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
- package/dist/router-tanstack/use-nav.test.d.ts +0 -2
- package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
- package/dist/section/section.test.d.ts +0 -2
- package/dist/section/section.test.d.ts.map +0 -1
- package/dist/shell/app-mobile-nav.test.d.ts +0 -2
- package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
- package/dist/shell/app-sidebar.test.d.ts +0 -2
- package/dist/shell/app-sidebar.test.d.ts.map +0 -1
- package/dist/shell/index.test.d.ts +0 -2
- package/dist/shell/index.test.d.ts.map +0 -1
- package/dist/shell/mobile-nav-model.test.d.ts +0 -2
- package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
- package/dist/shell/page-bar.test.d.ts +0 -2
- package/dist/shell/page-bar.test.d.ts.map +0 -1
- package/dist/state/fields.test.d.ts +0 -2
- package/dist/state/fields.test.d.ts.map +0 -1
- package/dist/styles.floor.test.d.ts +0 -2
- package/dist/styles.floor.test.d.ts.map +0 -1
- package/dist/theme/border-coverage.test.d.ts +0 -18
- package/dist/theme/border-coverage.test.d.ts.map +0 -1
- package/dist/theme/contrast.test.d.ts +0 -2
- package/dist/theme/contrast.test.d.ts.map +0 -1
- package/dist/theme/ctl-theme.test.d.ts +0 -2
- package/dist/theme/ctl-theme.test.d.ts.map +0 -1
- package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
- package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
- package/dist/theme/density-option.test.d.ts +0 -2
- package/dist/theme/density-option.test.d.ts.map +0 -1
- package/dist/theme/density-relations.test.d.ts +0 -2
- package/dist/theme/density-relations.test.d.ts.map +0 -1
- package/dist/theme/derive-theme.test.d.ts +0 -2
- package/dist/theme/derive-theme.test.d.ts.map +0 -1
- package/dist/theme/divider-law.test.d.ts +0 -2
- package/dist/theme/divider-law.test.d.ts.map +0 -1
- package/dist/theme/fonts-option.test.d.ts +0 -2
- package/dist/theme/fonts-option.test.d.ts.map +0 -1
- package/dist/theme/icon-slot.test.d.ts +0 -2
- package/dist/theme/icon-slot.test.d.ts.map +0 -1
- package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
- package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
- package/dist/theme/radius-option.test.d.ts +0 -2
- package/dist/theme/radius-option.test.d.ts.map +0 -1
- package/dist/theme/radius.test.d.ts +0 -2
- package/dist/theme/radius.test.d.ts.map +0 -1
- package/dist/theme/shadow-surfaces.test.d.ts +0 -26
- package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
- package/dist/theme/spacing.test.d.ts +0 -2
- package/dist/theme/spacing.test.d.ts.map +0 -1
- package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
- package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
- package/dist/theme-lab/derive-controls.test.d.ts +0 -2
- package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
- package/dist/theme-lab/derive-state.test.d.ts +0 -2
- package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
- package/dist/theme-lab/theme-lab.test.d.ts +0 -2
- package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
- package/dist/tokens/build-fonts-css.test.d.ts +0 -2
- package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
- package/dist/tokens/build-radius-css.test.d.ts +0 -2
- package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
- package/dist/tokens/chart-margin.test.d.ts +0 -2
- package/dist/tokens/chart-margin.test.d.ts.map +0 -1
- package/dist/tokens/density.test.d.ts +0 -2
- package/dist/tokens/density.test.d.ts.map +0 -1
- package/dist/tokens/derive-radius.test.d.ts +0 -2
- package/dist/tokens/derive-radius.test.d.ts.map +0 -1
- package/dist/tokens/derive.test.d.ts +0 -2
- package/dist/tokens/derive.test.d.ts.map +0 -1
- package/dist/tokens/palette.test.d.ts +0 -2
- package/dist/tokens/palette.test.d.ts.map +0 -1
- package/dist/tokens/text-scale.test.d.ts +0 -2
- package/dist/tokens/text-scale.test.d.ts.map +0 -1
- package/dist/vite-color-mix.test.d.ts +0 -2
- package/dist/vite-color-mix.test.d.ts.map +0 -1
- package/dist/vite.test.d.ts +0 -2
- package/dist/vite.test.d.ts.map +0 -1
- package/dist/widget-header/delta-badge.test.d.ts +0 -2
- package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
- package/dist/widget-header/no-mantine.test.d.ts +0 -2
- package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
- package/dist/widget-header/widget-header.test.d.ts +0 -2
- package/dist/widget-header/widget-header.test.d.ts.map +0 -1
- package/src/agent/adapter.test.ts +0 -1640
- package/src/agent/ai-sdk-transport.test.ts +0 -658
- package/src/agent/coalesce.test.ts +0 -387
- package/src/agent/foreign.test.ts +0 -51
- package/src/agent/id.test.ts +0 -169
- package/src/agent/merge.test.ts +0 -206
- package/src/agent/part-list.test.tsx +0 -112
- package/src/agent/parts.test.ts +0 -720
- package/src/agent/thread.test.ts +0 -220
- package/src/agent/transport.test.ts +0 -70
- package/src/agent/use-agent-stream.test.tsx +0 -287
- package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
- package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
- package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
- package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
- package/src/agent-chat/composer.test.tsx +0 -924
- package/src/agent-chat/relative-time.test.ts +0 -95
- package/src/agent-chat/thread-feed-row.test.tsx +0 -510
- package/src/agent-chat/thread-feed.test.tsx +0 -241
- package/src/agent-chat/thread-message.test.tsx +0 -1285
- package/src/agent-chat/thread-workspace.test.tsx +0 -169
- package/src/agent-chat/tool-chip.test.tsx +0 -139
- package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
- package/src/charts/cursor/resolve.test.ts +0 -151
- package/src/charts/cursor/store.test.ts +0 -64
- package/src/charts/kinds/BandStrip.test.tsx +0 -282
- package/src/charts/kinds/Bars.test.tsx +0 -183
- package/src/charts/kinds/DualPanel.test.tsx +0 -527
- package/src/charts/kinds/MirroredBars.test.tsx +0 -232
- package/src/charts/kinds/MultiLine.test.tsx +0 -218
- package/src/charts/kinds/StackedArea.test.tsx +0 -114
- package/src/charts/kinds/ZonedLine.test.tsx +0 -147
- package/src/charts/layout/auto-margin.test.ts +0 -42
- package/src/charts/primitives/CartesianChart.test.tsx +0 -647
- package/src/charts/primitives/ChartCard.test.tsx +0 -66
- package/src/charts/primitives/ChartFrame.test.tsx +0 -142
- package/src/charts/primitives/ChartLegend.test.tsx +0 -78
- package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
- package/src/charts/primitives/XZoneRects.test.tsx +0 -123
- package/src/charts/series.test.ts +0 -112
- package/src/charts/utils/domain.test.ts +0 -54
- package/src/charts/utils/measure-text.test.ts +0 -36
- package/src/cli/check-coverage.test.ts +0 -297
- package/src/cli/check-theme.test.ts +0 -837
- package/src/cli/help.test.ts +0 -170
- package/src/cli/init-sync.test.ts +0 -343
- package/src/cli/placement-engine.test.ts +0 -298
- package/src/cli/shipped-versions.test.ts +0 -83
- package/src/cli/tarball-parity.test.ts +0 -44
- package/src/cli/template-format.test.ts +0 -64
- package/src/cli/template-ledger.test.ts +0 -219
- package/src/cli/tokens-css.test.ts +0 -249
- package/src/cli/toolchain-wiring.test.ts +0 -1358
- package/src/cli/waiver-audit.test.ts +0 -215
- package/src/content/article-actions.test.ts +0 -78
- package/src/content/article-model.test.ts +0 -119
- package/src/content/block-split.test.ts +0 -75
- package/src/content/heading-components.test.tsx +0 -65
- package/src/content/markdown.test.tsx +0 -603
- package/src/content/prose.module.css.test.ts +0 -39
- package/src/content/sanitize.test.ts +0 -659
- package/src/content/slug.test.ts +0 -62
- package/src/controls/actions.test.tsx +0 -252
- package/src/controls/actions.type-guard.test.ts +0 -63
- package/src/controls/control-group.test.tsx +0 -229
- package/src/controls/controls.router.test.tsx +0 -637
- package/src/controls/filter-set.test.tsx +0 -472
- package/src/controls/peer-boundary.test.ts +0 -85
- package/src/controls/props-contract.test.tsx +0 -106
- package/src/controls/sync-button.test.tsx +0 -183
- package/src/dashboard/query-state.test.tsx +0 -152
- package/src/dashboard/settings-section.test.tsx +0 -56
- package/src/dashboard/stat-card.test.tsx +0 -154
- package/src/data/data-table.test.tsx +0 -434
- package/src/guard/check-source.test.ts +0 -3355
- package/src/guard/grace.test.ts +0 -149
- package/src/guard/guard-hook.test.ts +0 -203
- package/src/provider/index.test.tsx +0 -101
- package/src/provider/lab-theme.test.ts +0 -147
- package/src/query/error-message.test.ts +0 -67
- package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
- package/src/router-tanstack/nav.test.ts +0 -111
- package/src/router-tanstack/search-param-store.test.ts +0 -135
- package/src/router-tanstack/search-store.router.test.tsx +0 -661
- package/src/router-tanstack/search-store.test.ts +0 -418
- package/src/router-tanstack/use-nav.test.tsx +0 -187
- package/src/section/section.test.tsx +0 -204
- package/src/shell/app-mobile-nav.test.tsx +0 -403
- package/src/shell/app-sidebar.test.tsx +0 -509
- package/src/shell/index.test.tsx +0 -428
- package/src/shell/mobile-nav-model.test.ts +0 -517
- package/src/shell/page-bar.test.tsx +0 -316
- package/src/state/fields.test.tsx +0 -258
- package/src/styles.floor.test.ts +0 -36
- package/src/theme/border-coverage.test.ts +0 -272
- package/src/theme/contrast.test.ts +0 -354
- package/src/theme/ctl-theme.test.tsx +0 -275
- package/src/theme/ctl-tier-coverage.test.ts +0 -170
- package/src/theme/density-option.test.ts +0 -96
- package/src/theme/density-relations.test.ts +0 -506
- package/src/theme/derive-theme.test.ts +0 -101
- package/src/theme/divider-law.test.ts +0 -259
- package/src/theme/fonts-option.test.ts +0 -75
- package/src/theme/icon-slot.test.tsx +0 -275
- package/src/theme/layout-rhythm-css.test.ts +0 -72
- package/src/theme/radius-option.test.ts +0 -86
- package/src/theme/radius.test.ts +0 -142
- package/src/theme/shadow-surfaces.test.ts +0 -488
- package/src/theme/spacing.test.ts +0 -495
- package/src/theme/use-basalt-spacing.test.tsx +0 -55
- package/src/theme-lab/derive-controls.test.ts +0 -94
- package/src/theme-lab/derive-state.test.ts +0 -71
- package/src/theme-lab/theme-lab.test.ts +0 -46
- package/src/tokens/build-fonts-css.test.ts +0 -52
- package/src/tokens/build-radius-css.test.ts +0 -25
- package/src/tokens/chart-margin.test.ts +0 -39
- package/src/tokens/density.test.ts +0 -259
- package/src/tokens/derive-radius.test.ts +0 -62
- package/src/tokens/derive.test.ts +0 -30
- package/src/tokens/palette.test.ts +0 -114
- package/src/tokens/text-scale.test.ts +0 -51
- package/src/vite-color-mix.test.ts +0 -46
- package/src/vite.test.ts +0 -423
- package/src/widget-header/delta-badge.test.tsx +0 -60
- package/src/widget-header/no-mantine.test.ts +0 -33
- package/src/widget-header/widget-header.test.tsx +0 -187
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ./forms — useFieldArray: the list-field surface over Mantine's four list actions.
|
|
3
|
+
* Mantine-coupled. Optional peer: @mantine/form.
|
|
4
|
+
*
|
|
5
|
+
* The gap (audit B §4): `insertListItem` / `removeListItem` / `reorderListItem` are all reachable
|
|
6
|
+
* off the raw form object, but they are unexported by name, undocumented in the forms doctrine, and
|
|
7
|
+
* each takes a different argument shape (`(path, item, index?)`, `(path, index)`,
|
|
8
|
+
* `(path, { from, to })`). The list ALSO has to be read back out of the values by dotted path, and
|
|
9
|
+
* every row needs `form.key(\`${path}.${i}\`)` or the uncontrolled reconciler reuses the wrong DOM
|
|
10
|
+
* node when a row is removed from the middle — which is the defect this hook exists to make
|
|
11
|
+
* unwritable.
|
|
12
|
+
*/
|
|
13
|
+
import type { FormArrayElement, LooseKeys, UseFormReturnType } from '@mantine/form'
|
|
14
|
+
|
|
15
|
+
export type UseFieldArrayReturn<Item> = {
|
|
16
|
+
/** The live list. `[]` when the path holds nothing, so a call site never guards before mapping. */
|
|
17
|
+
items: Item[]
|
|
18
|
+
/** Push one item onto the end. */
|
|
19
|
+
append: (value: Item) => void
|
|
20
|
+
/** Drop the item at `index`. Mantine re-indexes the field errors with it. */
|
|
21
|
+
remove: (index: number) => void
|
|
22
|
+
/** Move one item, e.g. from a drag handle or a pair of arrow buttons. */
|
|
23
|
+
move: (from: number, to: number) => void
|
|
24
|
+
/**
|
|
25
|
+
* Mantine's reconciler key for row `index` (`form.key('<path>.<index>')`) — pass it as the row's
|
|
26
|
+
* JSX `key`, never spread it.
|
|
27
|
+
*
|
|
28
|
+
* **Positional, and that is a real limit worth stating**: it identifies the SLOT, not the item.
|
|
29
|
+
* Mantine's list actions write values through `$values` directly and never bump the form-level
|
|
30
|
+
* key generation (verified in 9.3.2's use-form-list), so removing row 0 does not change row 0's
|
|
31
|
+
* key — it changes which item sits there. That is correct for an append/remove list, where the
|
|
32
|
+
* uncontrolled inputs below re-read their `defaultValue` from the new values anyway. A list the
|
|
33
|
+
* USER reorders wants an identity key instead: put a `randomId()` on the item and key the row on
|
|
34
|
+
* that.
|
|
35
|
+
*/
|
|
36
|
+
key: (index: number) => string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reads and edits a list field as one object.
|
|
41
|
+
*
|
|
42
|
+
* **`items` is read from `form.getValues()`, not from `form.values`**, so it is correct under the
|
|
43
|
+
* `mode: 'uncontrolled'` default `useBasaltForm` sets: the list actions all re-render the form
|
|
44
|
+
* (they call `setValues` with `updateState: true`), so the snapshot this returns is the one the
|
|
45
|
+
* caller just produced.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const items = useFieldArray(form, 'contacts')
|
|
49
|
+
*
|
|
50
|
+
* {items.items.map((_, index) => (
|
|
51
|
+
* <FormRow key={items.key(index)} label={`Contact ${index + 1}`}>
|
|
52
|
+
* <TextInput
|
|
53
|
+
* key={fieldKey(form, `contacts.${index}.email`)}
|
|
54
|
+
* {...inputProps(form, `contacts.${index}.email`)}
|
|
55
|
+
* />
|
|
56
|
+
* <Button onClick={() => items.remove(index)}>Remove</Button>
|
|
57
|
+
* </FormRow>
|
|
58
|
+
* ))}
|
|
59
|
+
* <Button onClick={() => items.append({ email: '' })}>Add contact</Button>
|
|
60
|
+
*/
|
|
61
|
+
export function useFieldArray<
|
|
62
|
+
Values extends Record<string, unknown>,
|
|
63
|
+
Path extends LooseKeys<Values>,
|
|
64
|
+
>(
|
|
65
|
+
form: UseFormReturnType<Values>,
|
|
66
|
+
path: Path,
|
|
67
|
+
): UseFieldArrayReturn<FormArrayElement<Values, Path & string>> {
|
|
68
|
+
type Item = FormArrayElement<Values, Path & string>
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
items: readList<Item>(form.getValues(), String(path)),
|
|
72
|
+
append: (value) => form.insertListItem(path, value),
|
|
73
|
+
remove: (index) => form.removeListItem(path, index),
|
|
74
|
+
move: (from, to) => form.reorderListItem(path, { from, to }),
|
|
75
|
+
key: (index) => form.key(`${String(path)}.${index}`),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The dotted-path read. Mantine's own `getPath` is internal to `@mantine/form/paths` and is not on
|
|
81
|
+
* the package's public entry, so this walks the path itself rather than reaching past the export
|
|
82
|
+
* map for six lines.
|
|
83
|
+
*
|
|
84
|
+
* Returns `[]` for a missing path or a non-array value: the alternative is a `.map` on `undefined`
|
|
85
|
+
* at the call site, and "the field is not a list" is a typing error the compiler already reports.
|
|
86
|
+
*/
|
|
87
|
+
function readList<Item>(values: Record<string, unknown>, path: string): Item[] {
|
|
88
|
+
let current: unknown = values
|
|
89
|
+
for (const segment of path.split('.')) {
|
|
90
|
+
if (current === null || typeof current !== 'object') return []
|
|
91
|
+
current = (current as Record<string, unknown>)[segment]
|
|
92
|
+
}
|
|
93
|
+
return Array.isArray(current) ? (current as Item[]) : []
|
|
94
|
+
}
|
|
@@ -2,28 +2,21 @@
|
|
|
2
2
|
* ./forms — useFormDraft: autosave/restore/clear draft persistence via createPersistedState.
|
|
3
3
|
* Mantine-coupled (reads from UseFormReturnType). Optional peer: @mantine/form.
|
|
4
4
|
*
|
|
5
|
-
* AUTOSAVE
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* fully-automatic autosave:
|
|
5
|
+
* AUTOSAVE: `autosave: true` and the hook owns its own subscription. It used to be a documented
|
|
6
|
+
* ref dance — declare a `saveDraftRef`, pass `onValuesChange: () => saveDraftRef.current?.()` into
|
|
7
|
+
* `useBasaltForm`, assign the ref after the hook returns — which was copied verbatim into the
|
|
8
|
+
* playground, and a five-line init-order puzzle a consumer has to re-derive is a hook that has not
|
|
9
|
+
* finished being written. The dance still works; it is no longer the recommended path.
|
|
11
10
|
*
|
|
12
|
-
* @example (
|
|
11
|
+
* @example (automatic)
|
|
13
12
|
* const form = useBasaltForm({ initialValues, schema })
|
|
14
|
-
* const { clearDraft,
|
|
15
|
-
* <TextInput {...field(form, 'name')} onBlur={saveDraft} />
|
|
13
|
+
* const { clearDraft, hasDraft } = useFormDraft(form, { key: 'my-form', version: 1, autosave: true })
|
|
16
14
|
*
|
|
17
|
-
* @example (
|
|
18
|
-
* const saveDraftRef = useRef<(() => void) | null>(null)
|
|
19
|
-
* const form = useBasaltForm({ initialValues, schema, onValuesChange: () => saveDraftRef.current?.() })
|
|
15
|
+
* @example (manual — a blur-only save, or a form not created by useBasaltForm)
|
|
20
16
|
* const { clearDraft, saveDraft } = useFormDraft(form, { key: 'my-form', version: 1 })
|
|
21
|
-
*
|
|
22
|
-
* // NOTE: the ref pattern avoids the init-order problem — form is created first, the ref
|
|
23
|
-
* // is populated after useFormDraft runs (hooks run top-to-bottom), so by the time
|
|
24
|
-
* // onValuesChange fires on user interaction the ref is always populated.
|
|
17
|
+
* <TextInput key={fieldKey(form, 'name')} {...inputProps(form, 'name')} onBlur={saveDraft} />
|
|
25
18
|
*/
|
|
26
|
-
import { useEffect, useRef } from 'react'
|
|
19
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
27
20
|
import type { UseFormReturnType } from '@mantine/form'
|
|
28
21
|
import { createPersistedState } from '../state'
|
|
29
22
|
import type { StandardSchemaV1 } from '../register'
|
|
@@ -42,8 +35,26 @@ export type UseFormDraftOptions<Values> = {
|
|
|
42
35
|
version: number
|
|
43
36
|
/** Optional Standard Schema to validate the persisted draft before restoring. */
|
|
44
37
|
schema?: StandardSchemaV1<unknown, Values>
|
|
38
|
+
/**
|
|
39
|
+
* Persist on every value change, debounced. `true` uses {@link DEFAULT_AUTOSAVE_DEBOUNCE_MS};
|
|
40
|
+
* pass `{ debounceMs }` to tune it (`0` saves on the next macrotask).
|
|
41
|
+
*
|
|
42
|
+
* **The on/off decision and the watched key set are read ONCE, on mount.** The subscription is
|
|
43
|
+
* `form.watch` per top-level key, and `watch` calls `useEffect` internally, so both are a hook
|
|
44
|
+
* count and neither may move between renders. Flipping this prop later therefore neither starts
|
|
45
|
+
* nor stops autosaving; a form whose top-level SHAPE changes after mount needs `saveDraft` called
|
|
46
|
+
* by hand for the new keys. `debounceMs` is the exception — it is read at FIRE time, so a form
|
|
47
|
+
* that widens its window mid-edit gets the new one.
|
|
48
|
+
*
|
|
49
|
+
* Nested edits are covered: Mantine notifies a parent-path subscriber for a `a.b.c` change, so
|
|
50
|
+
* watching the top level watches the whole tree.
|
|
51
|
+
*/
|
|
52
|
+
autosave?: boolean | { debounceMs?: number }
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/** Long enough that a typed word is one write, short enough that a closed tab keeps the sentence. */
|
|
56
|
+
export const DEFAULT_AUTOSAVE_DEBOUNCE_MS = 500
|
|
57
|
+
|
|
47
58
|
/** Return value of useFormDraft. */
|
|
48
59
|
export type UseFormDraftReturn = {
|
|
49
60
|
/** True when a draft different from the initial values is persisted. */
|
|
@@ -63,15 +74,13 @@ export type UseFormDraftReturn = {
|
|
|
63
74
|
* MUST-HAVES:
|
|
64
75
|
* - Restores the draft once on mount if a draft is persisted.
|
|
65
76
|
* - `clearDraft()` removes the persisted draft (call in onSubmit success).
|
|
66
|
-
* - `saveDraft()` snapshots current form values (call onBlur or
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* The cleanest path is to pass `saveDraft` to `useBasaltForm({ onValuesChange: () => saveDraft() })`.
|
|
70
|
-
* For forms not created via useBasaltForm, call `saveDraft` manually on blur.
|
|
77
|
+
* - `saveDraft()` snapshots current form values (call onBlur, or leave it to `autosave`).
|
|
78
|
+
* - `autosave: true` subscribes for you — see {@link UseFormDraftOptions.autosave} for the two
|
|
79
|
+
* properties that are frozen at mount, and why.
|
|
71
80
|
*
|
|
72
81
|
* @example
|
|
73
82
|
* import * as v from 'valibot'
|
|
74
|
-
* import { useBasaltForm,
|
|
83
|
+
* import { useBasaltForm, inputProps, fieldKey, FormErrorSummary, useFormDraft } from 'basalt-ui/forms'
|
|
75
84
|
*
|
|
76
85
|
* const Schema = v.object({ name: v.pipe(v.string(), v.minLength(2)), amount: v.number() })
|
|
77
86
|
* type Values = v.InferOutput<typeof Schema>
|
|
@@ -79,13 +88,11 @@ export type UseFormDraftReturn = {
|
|
|
79
88
|
*
|
|
80
89
|
* function MyForm() {
|
|
81
90
|
* const form = useBasaltForm({ initialValues: INITIAL, schema: Schema, mode: 'uncontrolled' })
|
|
82
|
-
* const { clearDraft,
|
|
83
|
-
* // Wire saveDraft into onValuesChange after form creation via a ref or external callback:
|
|
84
|
-
* // form is created above; wire via onBlur as the safe fallback shown here.
|
|
91
|
+
* const { clearDraft, hasDraft } = useFormDraft(form, { key: 'my-form', version: 1, autosave: true })
|
|
85
92
|
* return (
|
|
86
93
|
* <form onSubmit={form.onSubmit((values) => { submit(values); clearDraft() })}>
|
|
87
94
|
* <FormErrorSummary form={form} />
|
|
88
|
-
* <TextInput {
|
|
95
|
+
* <TextInput key={fieldKey(form, 'name')} {...inputProps(form, 'name')} />
|
|
89
96
|
* {hasDraft && <Button onClick={clearDraft}>Clear draft</Button>}
|
|
90
97
|
* </form>
|
|
91
98
|
* )
|
|
@@ -95,40 +102,21 @@ export function useFormDraft<Values extends Record<string, unknown>>(
|
|
|
95
102
|
form: UseFormReturnType<Values>,
|
|
96
103
|
opts: UseFormDraftOptions<Values>,
|
|
97
104
|
): UseFormDraftReturn {
|
|
98
|
-
const { key, version, schema } = opts
|
|
105
|
+
const { key, version, schema, autosave } = opts
|
|
99
106
|
|
|
100
107
|
// Create ONE stable store instance per key. useRef + lazy init so it is never
|
|
101
108
|
// recreated on re-render. `key` MUST be stable (document this invariant above).
|
|
102
109
|
// The storage key becomes `basalt:form:<key>` (createPersistedState auto-namespaces `basalt:*`).
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
const storeRef = useRef<ReturnType<typeof createPersistedState<DraftEnvelope<Values>>> | null>(
|
|
111
|
+
null,
|
|
112
|
+
)
|
|
105
113
|
if (storeRef.current === null) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Only created when schema is provided — exactOptionalPropertyTypes forbids `schema: undefined`.
|
|
109
|
-
const envelopeSchema: StandardSchemaV1<unknown, DraftEnvelope> | undefined =
|
|
110
|
-
schema !== undefined
|
|
111
|
-
? {
|
|
112
|
-
'~standard': {
|
|
113
|
-
version: 1 as const,
|
|
114
|
-
vendor: 'basalt-form-draft',
|
|
115
|
-
validate: (raw: unknown) => {
|
|
116
|
-
if (typeof raw !== 'object' || raw === null || !('values' in raw)) {
|
|
117
|
-
return { value: null }
|
|
118
|
-
}
|
|
119
|
-
const result = schema['~standard'].validate((raw as { values: unknown }).values)
|
|
120
|
-
if (result instanceof Promise) return { value: null }
|
|
121
|
-
if (result.issues !== undefined) return { value: null }
|
|
122
|
-
return { value: { values: result.value } }
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
: undefined
|
|
127
|
-
|
|
128
|
-
storeRef.current = createPersistedState<DraftEnvelope>({
|
|
114
|
+
const envelopeSchema = wrapEnvelopeSchema(schema)
|
|
115
|
+
storeRef.current = createPersistedState<DraftEnvelope<Values>>({
|
|
129
116
|
key: `form:${key}`,
|
|
130
117
|
version,
|
|
131
118
|
initial: null,
|
|
119
|
+
// exactOptionalPropertyTypes forbids `schema: undefined`, so it is spread or omitted.
|
|
132
120
|
...(envelopeSchema !== undefined && { schema: envelopeSchema }),
|
|
133
121
|
})
|
|
134
122
|
}
|
|
@@ -166,5 +154,109 @@ export function useFormDraft<Values extends Record<string, unknown>>(
|
|
|
166
154
|
setDraft(null)
|
|
167
155
|
}
|
|
168
156
|
|
|
157
|
+
useAutosave(form, autosave, saveDraft)
|
|
158
|
+
|
|
169
159
|
return { hasDraft, saveDraft, clearDraft }
|
|
170
160
|
}
|
|
161
|
+
|
|
162
|
+
// ── the two pieces the hook composes ─────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/** What `createPersistedState` stores: the values under one key, or nothing persisted at all. */
|
|
165
|
+
type DraftEnvelope<Values> = { values: Values } | null
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Lifts a `Values` schema into a `DraftEnvelope<Values>` one, through the `~standard` interface.
|
|
169
|
+
*
|
|
170
|
+
* Only the inner `values` field is validated, and every failure resolves to `null` rather than to an
|
|
171
|
+
* issue list: a draft is a convenience, so a stale or hand-edited one is DROPPED, never surfaced as
|
|
172
|
+
* a validation error against a form the user has not touched yet. The async arm resolves to `null`
|
|
173
|
+
* for the same reason `useBasaltForm` pins `sync: true` — `createPersistedState` reads storage
|
|
174
|
+
* synchronously and has nowhere to await.
|
|
175
|
+
*
|
|
176
|
+
* Outside the hook because it is pure, closes over nothing, and inside it read as a third
|
|
177
|
+
* responsibility in a body that already owned the store, the restore and the autosave.
|
|
178
|
+
*/
|
|
179
|
+
function wrapEnvelopeSchema<Values>(
|
|
180
|
+
schema: StandardSchemaV1<unknown, Values> | undefined,
|
|
181
|
+
): StandardSchemaV1<unknown, DraftEnvelope<Values>> | undefined {
|
|
182
|
+
if (schema === undefined) return undefined
|
|
183
|
+
return {
|
|
184
|
+
'~standard': {
|
|
185
|
+
version: 1 as const,
|
|
186
|
+
vendor: 'basalt-form-draft',
|
|
187
|
+
validate: (raw: unknown) => {
|
|
188
|
+
if (typeof raw !== 'object' || raw === null || !('values' in raw)) return { value: null }
|
|
189
|
+
const result = schema['~standard'].validate((raw as { values: unknown }).values)
|
|
190
|
+
if (result instanceof Promise) return { value: null }
|
|
191
|
+
if (result.issues !== undefined) return { value: null }
|
|
192
|
+
return { value: { values: result.value } }
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The debounced `form.watch` subscription behind `autosave`.
|
|
200
|
+
*
|
|
201
|
+
* **It relies on `form.watch` calling exactly one `useEffect` per call**, which is why the watched
|
|
202
|
+
* path set is frozen on the first render: `watch` registers that effect INSIDE itself (checked in
|
|
203
|
+
* the installed `@mantine/form` 9.3.2 source, `use-form-watch.ts`), so the number of `watch` calls
|
|
204
|
+
* IS a hook count and a set that changed between renders would trip React's hook-order invariant.
|
|
205
|
+
* Freezing it is what makes the loop below legal.
|
|
206
|
+
*
|
|
207
|
+
* That is a load-bearing dependency on an implementation detail of a peer, so state it plainly: a
|
|
208
|
+
* `@mantine/form` minor that moves `watch` off `useEffect` — or calls a second hook from it —
|
|
209
|
+
* changes the shape of this file. The failure would be loud (a hook-order error in dev on the first
|
|
210
|
+
* autosaving form), not silent, but it is a real upgrade risk and belongs in the upgrade's diff
|
|
211
|
+
* review rather than in a consumer's bug report.
|
|
212
|
+
*
|
|
213
|
+
* Nested edits need no extra paths: Mantine notifies a parent-path subscriber for an `a.b.c` write,
|
|
214
|
+
* so watching every top-level key watches the whole tree.
|
|
215
|
+
*/
|
|
216
|
+
function useAutosave<Values extends Record<string, unknown>>(
|
|
217
|
+
form: UseFormReturnType<Values>,
|
|
218
|
+
autosave: UseFormDraftOptions<Values>['autosave'],
|
|
219
|
+
saveDraft: () => void,
|
|
220
|
+
): void {
|
|
221
|
+
const watchedPathsRef = useRef<string[] | null>(null)
|
|
222
|
+
if (watchedPathsRef.current === null) {
|
|
223
|
+
watchedPathsRef.current =
|
|
224
|
+
autosave === undefined || autosave === false ? [] : Object.keys(form.getValues())
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Read at FIRE time, not at schedule time, so a window that opened three renders ago still writes
|
|
228
|
+
// the current values and the current debounce.
|
|
229
|
+
const debounceRef = useRef(DEFAULT_AUTOSAVE_DEBOUNCE_MS)
|
|
230
|
+
debounceRef.current =
|
|
231
|
+
typeof autosave === 'object'
|
|
232
|
+
? (autosave.debounceMs ?? DEFAULT_AUTOSAVE_DEBOUNCE_MS)
|
|
233
|
+
: DEFAULT_AUTOSAVE_DEBOUNCE_MS
|
|
234
|
+
// `saveDraft` is a fresh closure each render and `watch`'s effect re-subscribes whenever its
|
|
235
|
+
// callback identity changes, so the callback handed to `watch` must be stable and read the ref.
|
|
236
|
+
const saveRef = useRef<() => void>(saveDraft)
|
|
237
|
+
saveRef.current = saveDraft
|
|
238
|
+
|
|
239
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
240
|
+
const scheduleSave = useCallback(() => {
|
|
241
|
+
if (timerRef.current !== null) clearTimeout(timerRef.current)
|
|
242
|
+
timerRef.current = setTimeout(() => {
|
|
243
|
+
timerRef.current = null
|
|
244
|
+
saveRef.current()
|
|
245
|
+
}, debounceRef.current)
|
|
246
|
+
}, [])
|
|
247
|
+
|
|
248
|
+
// An unmount mid-window flushes the pending write rather than dropping it — a closed tab keeps
|
|
249
|
+
// the sentence.
|
|
250
|
+
useEffect(
|
|
251
|
+
() => () => {
|
|
252
|
+
if (timerRef.current !== null) {
|
|
253
|
+
clearTimeout(timerRef.current)
|
|
254
|
+
timerRef.current = null
|
|
255
|
+
saveRef.current()
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
[],
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
for (const path of watchedPathsRef.current) form.watch(path, scheduleSave)
|
|
262
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ./forms — useFormSubmit: the submit lifecycle Mantine leaves to every call site.
|
|
3
|
+
* Mantine-coupled. Optional peer: @mantine/form.
|
|
4
|
+
*
|
|
5
|
+
* The gap (audit B §4): `form.onSubmit` gives you validation and a `submitting` flag and nothing
|
|
6
|
+
* else. A real submit also has to catch the handler's throw, turn it into a message a human can
|
|
7
|
+
* read, route a server's per-FIELD errors back onto the fields, and put the caret on the first
|
|
8
|
+
* thing that is wrong. Four jobs, re-written per form, and the last one is the one everybody skips.
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useRef, useState } from 'react'
|
|
11
|
+
import type { SyntheticEvent } from 'react'
|
|
12
|
+
import type { UseFormReturnType } from '@mantine/form'
|
|
13
|
+
import { toErrorMessage } from '../query/error-message'
|
|
14
|
+
|
|
15
|
+
// ── The server field-error envelope ───────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Per-field messages keyed by the same dotted paths `inputProps(form, path)` uses
|
|
19
|
+
* (`'email'`, `'addresses.0.city'`). The shape a server returns for a 422, and the shape
|
|
20
|
+
* `validateAsync` returns for a uniqueness check.
|
|
21
|
+
*/
|
|
22
|
+
export type FormFieldErrors = Record<string, string>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether a thrown value carries `{ fieldErrors }` — the envelope `useFormSubmit` routes back onto
|
|
26
|
+
* the fields instead of into one form-level message.
|
|
27
|
+
*
|
|
28
|
+
* Structural, not `instanceof`: the throw crosses a transport (Eden, `fetch`, a mutation wrapper)
|
|
29
|
+
* and arrives as a plain object, so an `Error` subclass would only match the one shape basalt
|
|
30
|
+
* itself constructed. The keys are checked to be STRINGS — a `{ fieldErrors: { email: 422 } }`
|
|
31
|
+
* would otherwise reach `form.setErrors` and render a number under an input.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* throw Object.assign(new Error('Validation failed'), { fieldErrors: { email: 'Already taken' } })
|
|
35
|
+
*/
|
|
36
|
+
export function isFieldErrorEnvelope(err: unknown): err is { fieldErrors: FormFieldErrors } {
|
|
37
|
+
if (err === null || typeof err !== 'object') return false
|
|
38
|
+
const fieldErrors = (err as { fieldErrors?: unknown }).fieldErrors
|
|
39
|
+
if (fieldErrors === null || typeof fieldErrors !== 'object' || Array.isArray(fieldErrors)) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
return Object.values(fieldErrors).every((message) => typeof message === 'string')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Focus-first-error ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Puts the caret on the first errored field. `form.getInputNode(path)` is Mantine v9's own lookup
|
|
49
|
+
* (a `[data-path]` query over the attribute `getInputProps` writes, verified in the installed
|
|
50
|
+
* 9.3.2 source); the `[name]` query is the fallback for a control that spreads its own props and
|
|
51
|
+
* never got the attribute.
|
|
52
|
+
*
|
|
53
|
+
* Order is the ERROR RECORD's key order, not the DOM's: Mantine builds it from the schema, so it is
|
|
54
|
+
* the order the fields were declared in — which for a form written top-to-bottom is the visual
|
|
55
|
+
* order, and unlike a DOM sweep it costs no traversal.
|
|
56
|
+
*
|
|
57
|
+
* A no-op without a document (SSR), because `getInputNode` reaches straight for `document`.
|
|
58
|
+
*/
|
|
59
|
+
function focusFirstError<Values extends Record<string, unknown>>(
|
|
60
|
+
form: UseFormReturnType<Values>,
|
|
61
|
+
errors: Record<string, unknown>,
|
|
62
|
+
): void {
|
|
63
|
+
if (typeof document === 'undefined') return
|
|
64
|
+
for (const [path, message] of Object.entries(errors)) {
|
|
65
|
+
if (message === null || message === undefined || message === false) continue
|
|
66
|
+
const node =
|
|
67
|
+
form.getInputNode<HTMLElement, string>(path) ??
|
|
68
|
+
document.querySelector<HTMLElement>(`[name="${path}"]`)
|
|
69
|
+
if (node !== null) {
|
|
70
|
+
node.focus()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── useFormSubmit ─────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export type UseFormSubmitOptions<Values extends Record<string, unknown>> = {
|
|
79
|
+
/** Ran after the handler resolves. The success path — close the modal, clear the draft, navigate. */
|
|
80
|
+
onSuccess?: (values: Values) => void
|
|
81
|
+
/** Ran on any throw, after `submitError` is set. For telemetry or a toast; not required for either. */
|
|
82
|
+
onError?: (err: unknown) => void
|
|
83
|
+
/**
|
|
84
|
+
* Turns a thrown value into the message `submitError` carries. Defaults to `toErrorMessage`
|
|
85
|
+
* (`basalt-ui/query`), which already unwraps `Error`, `{ message }` and Eden's `{ status, value }`.
|
|
86
|
+
* Override it to brand the copy or to branch on `errorStatus(err)`.
|
|
87
|
+
*/
|
|
88
|
+
mapError?: (err: unknown) => string
|
|
89
|
+
/**
|
|
90
|
+
* The async half of validation, run AFTER the schema passes and BEFORE the handler — a uniqueness
|
|
91
|
+
* check, a server-side cross-field rule, anything that needs the network.
|
|
92
|
+
*
|
|
93
|
+
* It lives here rather than on `useBasaltForm` because that hook hard-codes
|
|
94
|
+
* `schemaResolver(schema, { sync: true })` so `validate()`/`isValid()` stay synchronous for every
|
|
95
|
+
* field-level read. Moving the async rule to submit time keeps that property AND gives the rule a
|
|
96
|
+
* home; the cost, stated plainly, is that it does not run on blur.
|
|
97
|
+
*
|
|
98
|
+
* Return `null` (or an empty record) for "valid". Anything else is set on the fields, the first
|
|
99
|
+
* one is focused, and the handler never runs.
|
|
100
|
+
*/
|
|
101
|
+
validateAsync?: (values: Values) => Promise<FormFieldErrors | null>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type UseFormSubmitReturn = {
|
|
105
|
+
/** Hand straight to `<form onSubmit={submit}>`, or call it from a button's `onClick`. */
|
|
106
|
+
submit: (event?: SyntheticEvent<HTMLFormElement>) => void
|
|
107
|
+
/** True from the moment a submit starts until the handler settles. Mantine's own `form.submitting`. */
|
|
108
|
+
isSubmitting: boolean
|
|
109
|
+
/** The form-level failure message, or `null`. Cleared at the start of every submit. */
|
|
110
|
+
submitError: string | null
|
|
111
|
+
/** Clears `submitError` without submitting — for a "dismiss" on the error banner. */
|
|
112
|
+
reset: () => void
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Wraps `form.onSubmit` with the four things a real submit needs: a busy flag, a caught and decoded
|
|
117
|
+
* error, a server field-error envelope routed back onto the fields, and focus on the first thing
|
|
118
|
+
* that is wrong.
|
|
119
|
+
*
|
|
120
|
+
* **`isSubmitting` IS `form.submitting`**, not a second piece of state beside it. Mantine already
|
|
121
|
+
* flips that flag around a handler returning a promise, and this handler always does; duplicating it
|
|
122
|
+
* would give a form two busy flags that can disagree, which is the failure a `FormStateProvider`
|
|
123
|
+
* reading the wrong one produces.
|
|
124
|
+
*
|
|
125
|
+
* **Focus moves on BOTH failure paths** — a schema failure (through `onSubmit`'s own
|
|
126
|
+
* `handleValidationFailure`) and a thrown `{ fieldErrors }` envelope. A server's 422 lands exactly
|
|
127
|
+
* where a client-side error does.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const { submit, isSubmitting, submitError } = useFormSubmit(
|
|
131
|
+
* form,
|
|
132
|
+
* async (values) => { await unwrap(api.users.post({ body: values })) },
|
|
133
|
+
* { onSuccess: () => { clearDraft(); close() }, validateAsync: checkHandleFree },
|
|
134
|
+
* )
|
|
135
|
+
*
|
|
136
|
+
* <form onSubmit={submit}>
|
|
137
|
+
* <FormStateProvider submitting={isSubmitting}>
|
|
138
|
+
* {submitError !== null && <Alert color="red">{submitError}</Alert>}
|
|
139
|
+
* …
|
|
140
|
+
* </FormStateProvider>
|
|
141
|
+
* </form>
|
|
142
|
+
*/
|
|
143
|
+
export function useFormSubmit<Values extends Record<string, unknown>>(
|
|
144
|
+
form: UseFormReturnType<Values>,
|
|
145
|
+
handler: (values: Values) => void | Promise<void>,
|
|
146
|
+
options: UseFormSubmitOptions<Values> = {},
|
|
147
|
+
): UseFormSubmitReturn {
|
|
148
|
+
const [submitError, setSubmitError] = useState<string | null>(null)
|
|
149
|
+
|
|
150
|
+
// The handler and the callbacks are read at FIRE time, never captured into a memo: a submit
|
|
151
|
+
// handler closes over the freshest props by definition, and a stale one here would post the
|
|
152
|
+
// previous render's values.
|
|
153
|
+
const handlerRef = useRef(handler)
|
|
154
|
+
handlerRef.current = handler
|
|
155
|
+
const optionsRef = useRef(options)
|
|
156
|
+
optionsRef.current = options
|
|
157
|
+
const formRef = useRef(form)
|
|
158
|
+
formRef.current = form
|
|
159
|
+
|
|
160
|
+
const submit = form.onSubmit(
|
|
161
|
+
async (values) => {
|
|
162
|
+
const { onSuccess, onError, mapError, validateAsync } = optionsRef.current
|
|
163
|
+
setSubmitError(null)
|
|
164
|
+
try {
|
|
165
|
+
const fieldErrors = await validateAsync?.(values)
|
|
166
|
+
if (fieldErrors !== undefined && fieldErrors !== null && hasEntries(fieldErrors)) {
|
|
167
|
+
applyFieldErrors(formRef.current, fieldErrors)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
await handlerRef.current(values)
|
|
171
|
+
onSuccess?.(values)
|
|
172
|
+
} catch (err) {
|
|
173
|
+
// Both, not either: the envelope's per-field messages go on the fields AND its decoded
|
|
174
|
+
// message goes in the banner. A 422 whose fields are all off-screen would otherwise report
|
|
175
|
+
// nothing at all where the user is looking.
|
|
176
|
+
if (isFieldErrorEnvelope(err)) applyFieldErrors(formRef.current, err.fieldErrors)
|
|
177
|
+
setSubmitError(mapError === undefined ? toErrorMessage(err) : mapError(err))
|
|
178
|
+
onError?.(err)
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
(errors) => focusFirstError(formRef.current, errors),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
const reset = useCallback(() => setSubmitError(null), [])
|
|
185
|
+
|
|
186
|
+
return { submit, isSubmitting: form.submitting, submitError, reset }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasEntries(errors: FormFieldErrors): boolean {
|
|
190
|
+
return Object.keys(errors).length > 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function applyFieldErrors<Values extends Record<string, unknown>>(
|
|
194
|
+
form: UseFormReturnType<Values>,
|
|
195
|
+
fieldErrors: FormFieldErrors,
|
|
196
|
+
): void {
|
|
197
|
+
form.setErrors(fieldErrors)
|
|
198
|
+
focusFirstError(form, fieldErrors)
|
|
199
|
+
}
|