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
package/src/data/data-table.tsx
CHANGED
|
@@ -32,7 +32,17 @@
|
|
|
32
32
|
* enablePagination
|
|
33
33
|
* />
|
|
34
34
|
*/
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
Box,
|
|
37
|
+
Checkbox,
|
|
38
|
+
Group,
|
|
39
|
+
Pagination,
|
|
40
|
+
Select,
|
|
41
|
+
Skeleton,
|
|
42
|
+
Table,
|
|
43
|
+
Text,
|
|
44
|
+
TextInput,
|
|
45
|
+
} from '@mantine/core'
|
|
36
46
|
import type { MantineSpacing } from '@mantine/core'
|
|
37
47
|
import type {
|
|
38
48
|
Column,
|
|
@@ -42,6 +52,7 @@ import type {
|
|
|
42
52
|
FilterFn,
|
|
43
53
|
PaginationState,
|
|
44
54
|
RowData,
|
|
55
|
+
RowSelectionState,
|
|
45
56
|
SortingState,
|
|
46
57
|
Table as TanstackTable,
|
|
47
58
|
Updater,
|
|
@@ -54,9 +65,20 @@ import {
|
|
|
54
65
|
getSortedRowModel,
|
|
55
66
|
useReactTable,
|
|
56
67
|
} from '@tanstack/react-table'
|
|
57
|
-
import type {
|
|
58
|
-
|
|
68
|
+
import type {
|
|
69
|
+
CSSProperties,
|
|
70
|
+
KeyboardEvent as ReactKeyboardEvent,
|
|
71
|
+
ReactNode,
|
|
72
|
+
RefObject,
|
|
73
|
+
} from 'react'
|
|
74
|
+
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
75
|
+
import { cx } from '../common/props'
|
|
76
|
+
import type { BasaltProps, SlotStylesProps } from '../common/props'
|
|
77
|
+
import { BASALT_PREFIX } from '../common/errors'
|
|
78
|
+
import { assertRequiredProps, useValidateProps } from '../common/validate'
|
|
59
79
|
import { FilterSet } from '../controls'
|
|
80
|
+
import { ActionGroup, BarActionSlot } from '../controls/actions'
|
|
81
|
+
import type { BarAction, SlotActions } from '../controls/actions'
|
|
60
82
|
// `EnumFilter` is deliberately NOT on the `./controls` barrel (`docs/CONTROLS-SPEC.md` §3 —
|
|
61
83
|
// reaching for it directly is hand-rolling a filter, `basalt/hand-rolled-filter`); a facet column
|
|
62
84
|
// is the one place inside the framework itself that legitimately builds a `FieldHandle` over
|
|
@@ -67,6 +89,11 @@ import { CtlSlot } from '../theme'
|
|
|
67
89
|
import { alpha, VX } from '../tokens'
|
|
68
90
|
import type { EnumField, FieldHandle, MultiField } from '../state'
|
|
69
91
|
import { WidgetHeader } from '../widget-header'
|
|
92
|
+
import { ErrorState } from '../dashboard/query-state'
|
|
93
|
+
import type { QueryStateLike } from '../dashboard/query-state'
|
|
94
|
+
import { dataQueryBranch } from './query-branch'
|
|
95
|
+
import { isDev } from '../utils/is-dev'
|
|
96
|
+
import classes from './data-table.module.css'
|
|
70
97
|
|
|
71
98
|
// ── Column alignment ──────────────────────────────────────────────────────────
|
|
72
99
|
|
|
@@ -122,20 +149,30 @@ export type DataTableFacetOption = {
|
|
|
122
149
|
label: string
|
|
123
150
|
}
|
|
124
151
|
|
|
152
|
+
/**
|
|
153
|
+
* A TanStack column id typed off the row shape `T` where `accessor`/manual `id` inference allows
|
|
154
|
+
* it — `Extract<keyof T, string>` for a real field name, widened by `(string & {})` so a
|
|
155
|
+
* synthetic/derived column id (no matching key on `T`) still type-checks. Defaults to `unknown` so
|
|
156
|
+
* a bare `DataTableFacet[]` / `DataTableColumnPinning` (no type argument) keeps working exactly as
|
|
157
|
+
* before. Shared by {@link DataTableFacet}'s `columnId` and {@link DataTableColumnPinning}'s
|
|
158
|
+
* `left`/`right`.
|
|
159
|
+
*/
|
|
160
|
+
export type DataTableColumnId<T = unknown> = Extract<keyof T, string> | (string & {})
|
|
161
|
+
|
|
125
162
|
/**
|
|
126
163
|
* Declares one faceted column filter, rendered as an `EnumFilter` pill (or `MultiSelectFilter`
|
|
127
164
|
* when `multiple` is set) inside a `FilterSet` in the toolbar (`docs/CONTROLS-SPEC.md` §3).
|
|
128
165
|
*
|
|
129
166
|
* @example
|
|
130
|
-
* const facets: DataTableFacet[] = [
|
|
167
|
+
* const facets: DataTableFacet<Employee>[] = [
|
|
131
168
|
* { columnId: 'department', label: 'Department', options: [{ value: 'Engineering', label: 'Engineering' }] },
|
|
132
169
|
* { columnId: 'role', label: 'Role', multiple: true, options: roleOptions },
|
|
133
170
|
* ]
|
|
134
171
|
* <BasaltDataTable data={rows} columns={columns} facets={facets} />
|
|
135
172
|
*/
|
|
136
|
-
export type DataTableFacet = {
|
|
173
|
+
export type DataTableFacet<T = unknown> = {
|
|
137
174
|
/** The TanStack column id — the column's manual `id`, or its `accessorKey` string. */
|
|
138
|
-
columnId:
|
|
175
|
+
columnId: DataTableColumnId<T>
|
|
139
176
|
/** The pill's label — read at rest (single-select) and as the `Filters`/count fallback (multi). */
|
|
140
177
|
label: string
|
|
141
178
|
/** Selectable options for this facet. */
|
|
@@ -144,6 +181,17 @@ export type DataTableFacet = {
|
|
|
144
181
|
multiple?: boolean
|
|
145
182
|
}
|
|
146
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Column ids pinned to each edge — the same `T`-derived id union as {@link DataTableFacet}'s
|
|
186
|
+
* `columnId`, applied to TanStack's own `ColumnPinningState` shape (`{ left, right }`, each a bare
|
|
187
|
+
* `string[]` unrelated to any row type). Structurally assignable to `ColumnPinningState`, so no
|
|
188
|
+
* cast is needed where this flows into `useState<ColumnPinningState>`.
|
|
189
|
+
*/
|
|
190
|
+
export type DataTableColumnPinning<T = unknown> = {
|
|
191
|
+
left?: DataTableColumnId<T>[]
|
|
192
|
+
right?: DataTableColumnId<T>[]
|
|
193
|
+
}
|
|
194
|
+
|
|
147
195
|
/**
|
|
148
196
|
* The "no filter" member of a single-select facet's `FieldHandle`. A closed enum field always has
|
|
149
197
|
* a real member as its fallback (C4) — there is no "unset" — so a facet, whose underlying
|
|
@@ -161,7 +209,7 @@ export type DataTableFacet = {
|
|
|
161
209
|
const FACET_ALL_VALUE = '__basalt_facet_all__'
|
|
162
210
|
|
|
163
211
|
/** The synthetic row's label — see {@link FACET_ALL_VALUE}. */
|
|
164
|
-
function facetAllLabel(facet: DataTableFacet): string {
|
|
212
|
+
function facetAllLabel<T>(facet: DataTableFacet<T>): string {
|
|
165
213
|
return `Any ${facet.label.toLowerCase()}`
|
|
166
214
|
}
|
|
167
215
|
|
|
@@ -174,7 +222,7 @@ function facetAllLabel(facet: DataTableFacet): string {
|
|
|
174
222
|
*/
|
|
175
223
|
function facetEnumHandle<T>(
|
|
176
224
|
column: Column<T, unknown>,
|
|
177
|
-
facet: DataTableFacet
|
|
225
|
+
facet: DataTableFacet<T>,
|
|
178
226
|
): FieldHandle<EnumField<string>> {
|
|
179
227
|
return {
|
|
180
228
|
kind: 'enum',
|
|
@@ -189,6 +237,10 @@ function facetEnumHandle<T>(
|
|
|
189
237
|
},
|
|
190
238
|
] as const
|
|
191
239
|
},
|
|
240
|
+
// A facet column has no lane to unset: clearing the column filter IS its fallback state.
|
|
241
|
+
clear: () => {
|
|
242
|
+
column.setFilterValue(undefined)
|
|
243
|
+
},
|
|
192
244
|
isDefault: (value) => value === FACET_ALL_VALUE,
|
|
193
245
|
}
|
|
194
246
|
}
|
|
@@ -200,7 +252,7 @@ function facetEnumHandle<T>(
|
|
|
200
252
|
*/
|
|
201
253
|
function facetMultiHandle<T>(
|
|
202
254
|
column: Column<T, unknown>,
|
|
203
|
-
facet: DataTableFacet
|
|
255
|
+
facet: DataTableFacet<T>,
|
|
204
256
|
): FieldHandle<MultiField<string>> {
|
|
205
257
|
return {
|
|
206
258
|
kind: 'multi',
|
|
@@ -215,12 +267,114 @@ function facetMultiHandle<T>(
|
|
|
215
267
|
},
|
|
216
268
|
] as const
|
|
217
269
|
},
|
|
270
|
+
// Same as the single-select facet: an empty column filter is this handle's fallback.
|
|
271
|
+
clear: () => {
|
|
272
|
+
column.setFilterValue(undefined)
|
|
273
|
+
},
|
|
218
274
|
isDefault: (value) => value.length === 0,
|
|
219
275
|
}
|
|
220
276
|
}
|
|
221
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Resolves a facet's `columnId` against the table. A mistyped id used to fall through to
|
|
280
|
+
* `if (!column) return null` — the facet pill silently vanished from the toolbar, the same class
|
|
281
|
+
* of defect the sibling `meta.align` lane and `manualPagination` both treat as a dev-time throw.
|
|
282
|
+
* Mirrors `resolveAlign`'s dev-throw shape: throw naming the id in dev
|
|
283
|
+
* (`basaltViteConfig` defines `process.env.NODE_ENV`, so a production bundle constant-folds this
|
|
284
|
+
* away), degrade to "no pill for this facet" in production.
|
|
285
|
+
*/
|
|
286
|
+
function resolveFacetColumn<T>(
|
|
287
|
+
table: TanstackTable<T>,
|
|
288
|
+
facet: DataTableFacet<T>,
|
|
289
|
+
): Column<T, unknown> | undefined {
|
|
290
|
+
const column = table.getColumn(facet.columnId)
|
|
291
|
+
if (column) return column
|
|
292
|
+
if (isDev()) {
|
|
293
|
+
const knownIds = table
|
|
294
|
+
.getAllColumns()
|
|
295
|
+
.map((known) => known.id)
|
|
296
|
+
.join(', ')
|
|
297
|
+
throw new Error(
|
|
298
|
+
`BasaltDataTable: facet columnId "${facet.columnId}" matches no column — known column ids: ` +
|
|
299
|
+
`${knownIds}.`,
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
return undefined
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Row selection ─────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The synthetic checkbox column `enableRowSelection` prepends. Its id is namespaced so it can never
|
|
309
|
+
* collide with a real accessor key, and it is excluded from sorting — a checkbox column sorts by
|
|
310
|
+
* selection state, which is a reordering nobody asked for.
|
|
311
|
+
*/
|
|
312
|
+
const SELECT_COLUMN_ID = '__basalt_select__'
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Built per render inside a `useMemo`, not hoisted as a constant: `ColumnDef` is generic in `T`, and
|
|
316
|
+
* a single shared instance would have to be cast at every call site.
|
|
317
|
+
*
|
|
318
|
+
* `onClick`'s `stopPropagation` is the load-bearing line. The checkbox lives inside the row, and a
|
|
319
|
+
* row carrying `onRowActivate` opens a detail from a click — so without it, ticking the box also
|
|
320
|
+
* navigated away from the table the tick was meant to act on.
|
|
321
|
+
*/
|
|
322
|
+
function selectionColumn<T>(): ColumnDef<T, unknown> {
|
|
323
|
+
return {
|
|
324
|
+
id: SELECT_COLUMN_ID,
|
|
325
|
+
enableSorting: false,
|
|
326
|
+
header: ({ table }) => (
|
|
327
|
+
<Checkbox
|
|
328
|
+
size="xs"
|
|
329
|
+
aria-label="Select all rows"
|
|
330
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
331
|
+
indeterminate={table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected()}
|
|
332
|
+
onChange={table.getToggleAllPageRowsSelectedHandler()}
|
|
333
|
+
/>
|
|
334
|
+
),
|
|
335
|
+
cell: ({ row }) => (
|
|
336
|
+
<Checkbox
|
|
337
|
+
size="xs"
|
|
338
|
+
aria-label={`Select row ${row.index + 1}`}
|
|
339
|
+
checked={row.getIsSelected()}
|
|
340
|
+
disabled={!row.getCanSelect()}
|
|
341
|
+
onChange={row.getToggleSelectedHandler()}
|
|
342
|
+
onClick={(event) => event.stopPropagation()}
|
|
343
|
+
/>
|
|
344
|
+
),
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* The bar a selection raises, above the table and below its header — the count, then the actions,
|
|
350
|
+
* through the SAME `BarAction[]` vocabulary `PageBar` uses, so the ≤3-inline fold and the mobile
|
|
351
|
+
* kebab are basalt's here too (law C7/C15) instead of a hand-rolled `Group` of buttons.
|
|
352
|
+
*
|
|
353
|
+
* It renders only while at least one row is selected. A bar reserved at zero is a row of page
|
|
354
|
+
* furniture that says nothing, and a table that changes height when the first box is ticked is the
|
|
355
|
+
* price of not reserving it — the cheaper of the two, because the selection is what the reader just
|
|
356
|
+
* caused.
|
|
357
|
+
*/
|
|
358
|
+
function BulkActionBar({ count, actions }: { count: number; actions: BarAction[] }) {
|
|
359
|
+
return (
|
|
360
|
+
<Group className={classes.bulkBar} gap="xs" align="center" wrap="wrap" mb="xs">
|
|
361
|
+
<Text className={classes.bulkCount}>{count} selected</Text>
|
|
362
|
+
<CtlSlot>
|
|
363
|
+
<ActionGroup secondary={actions} />
|
|
364
|
+
</CtlSlot>
|
|
365
|
+
</Group>
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
222
369
|
// ── Props ─────────────────────────────────────────────────────────────────────
|
|
223
370
|
|
|
371
|
+
/**
|
|
372
|
+
* The four boxes `BasaltDataTable` paints: `root` is the outer fragment's replacement wrapper,
|
|
373
|
+
* `toolbar` is the search/facets/actions row, `table` is the Mantine `Table` itself, `footer` is
|
|
374
|
+
* the pagination bar.
|
|
375
|
+
*/
|
|
376
|
+
export type BasaltDataTableSlot = 'root' | 'toolbar' | 'table' | 'footer'
|
|
377
|
+
|
|
224
378
|
/**
|
|
225
379
|
* Props for {@link BasaltDataTable}.
|
|
226
380
|
*
|
|
@@ -234,268 +388,383 @@ function facetMultiHandle<T>(
|
|
|
234
388
|
* emptyState: <Text c="dimmed">No results found.</Text>,
|
|
235
389
|
* }
|
|
236
390
|
*/
|
|
237
|
-
export type BasaltDataTableProps<T> =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
391
|
+
export type BasaltDataTableProps<T> = BasaltProps &
|
|
392
|
+
SlotStylesProps<BasaltDataTableSlot> & {
|
|
393
|
+
// ── Header (WidgetHeader, docs/CONTROLS-SPEC.md §2.2) ──────────────────────────
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Optional heading rendered above the toolbar via `WidgetHeader tier="widget"`. `count` always
|
|
397
|
+
* reads `table.getRowCount()` (C11) — never a raw `data.length` — so it tracks the row model
|
|
398
|
+
* (post-filter/-pagination) rather than the unfiltered input.
|
|
399
|
+
*/
|
|
400
|
+
title?: string
|
|
401
|
+
/** Optional leading icon, forwarded to `WidgetHeader`. */
|
|
402
|
+
icon?: ReactNode
|
|
403
|
+
/** Optional muted line rendered below the title row. */
|
|
404
|
+
subtitle?: string
|
|
405
|
+
|
|
406
|
+
/** Row data array. */
|
|
407
|
+
data: T[]
|
|
408
|
+
/**
|
|
409
|
+
* Column definitions. Use `createColumnHelper<T>()` from `@tanstack/react-table`
|
|
410
|
+
* for a typed accessor builder — `col.accessor('field', …)` columns sort by value.
|
|
411
|
+
*/
|
|
412
|
+
// The cell-value type varies per column, so the array is heterogeneous in TValue. `any` here
|
|
413
|
+
// is the TanStack-idiomatic escape (accessor columns infer their own TValue); row typing stays
|
|
414
|
+
// exact via the `T` data generic.
|
|
415
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- TanStack-idiomatic heterogeneous column array
|
|
416
|
+
columns: ColumnDef<T, any>[]
|
|
417
|
+
/**
|
|
418
|
+
* Enable column sorting via clickable headers. Client-side — the rows in `data` are what get
|
|
419
|
+
* reordered.
|
|
420
|
+
*
|
|
421
|
+
* Under `manualPagination` that is one page, so leaving this on without also passing
|
|
422
|
+
* `manualSorting` is a contract violation and throws in dev — see `manualPagination`.
|
|
423
|
+
* @default true
|
|
424
|
+
*/
|
|
425
|
+
enableSorting?: boolean
|
|
426
|
+
/**
|
|
427
|
+
* Hand sorting to the server: the table stops reordering `data` locally and renders it in the
|
|
428
|
+
* order given, while the headers keep toggling `SortingState` and reporting it through
|
|
429
|
+
* `onSortingChange` — which is where you re-request the sorted page.
|
|
430
|
+
*
|
|
431
|
+
* This is the required companion to `manualPagination` whenever sort headers are live; without
|
|
432
|
+
* it a server-paginated table sorts one page and the "of N" bar presents that as a sort of all N.
|
|
433
|
+
* @default false
|
|
434
|
+
* @example
|
|
435
|
+
* <BasaltDataTable
|
|
436
|
+
* data={page.rows}
|
|
437
|
+
* columns={columns}
|
|
438
|
+
* enablePagination
|
|
439
|
+
* manualPagination
|
|
440
|
+
* manualSorting
|
|
441
|
+
* rowCount={page.total}
|
|
442
|
+
* onSortingChange={(s) => refetch({ sort: s[0] })}
|
|
443
|
+
* />
|
|
444
|
+
*/
|
|
445
|
+
manualSorting?: boolean
|
|
446
|
+
/** Stripe alternate rows. Forwarded to Mantine `Table` — `'odd'`/`'even'` pick the phase. */
|
|
447
|
+
striped?: boolean | 'odd' | 'even'
|
|
448
|
+
/** Highlight hovered rows. Forwarded to Mantine `Table`. */
|
|
449
|
+
highlightOnHover?: boolean
|
|
450
|
+
/**
|
|
451
|
+
* Rendered when no rows are VISIBLE — `data` is empty, or a filter/page matches none of it.
|
|
452
|
+
* Falls back to a simple message when omitted. (Before 1.25.0 this tracked `data.length` alone,
|
|
453
|
+
* so a search matching nothing rendered a blank `<tbody>` with no message at all.)
|
|
454
|
+
*/
|
|
455
|
+
emptyState?: ReactNode
|
|
456
|
+
/**
|
|
457
|
+
* When true, renders skeleton placeholder rows instead of the empty-state branch.
|
|
458
|
+
* The header remains visible. Use while async data is loading.
|
|
459
|
+
*
|
|
460
|
+
* Superseded by `query`, which resolves this branch AND the two this prop cannot express — pass
|
|
461
|
+
* both and `query` wins, with a dev warning.
|
|
462
|
+
*/
|
|
463
|
+
isLoading?: boolean
|
|
464
|
+
/**
|
|
465
|
+
* The result behind `data`, resolved into a body: pending → the skeleton rows below, error with
|
|
466
|
+
* no data → an `ErrorState` row spanning the table (with the query's own `refetch` behind
|
|
467
|
+
* Retry), anything else → the rows, or `emptyState` when none are visible.
|
|
468
|
+
*
|
|
469
|
+
* This is law C3's uniform container contract (`docs/CONTROLS-SPEC.md` §1). The table had NO
|
|
470
|
+
* error branch at all (components audit #3), so a 500 rendered *No data to display.* — the
|
|
471
|
+
* exact false claim `QueryState` was built to delete, one container over.
|
|
472
|
+
*
|
|
473
|
+
* The header, toolbar, `<thead>` and pagination bar stay drawn through every branch: only the
|
|
474
|
+
* `<tbody>` swaps, which is why this is not `QueryState` wrapped around the table. A refetch
|
|
475
|
+
* that fails while rows are already on screen keeps the rows — compose `QueryState` AROUND the
|
|
476
|
+
* table when you want its cached-data banner too.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* const q = useQuery({ queryKey: ['rows'], queryFn: () => api.rows() })
|
|
480
|
+
* <BasaltDataTable data={q.data ?? []} columns={columns} query={q} />
|
|
481
|
+
*/
|
|
482
|
+
query?: QueryStateLike<unknown>
|
|
483
|
+
/**
|
|
484
|
+
* Number of skeleton rows to render when `isLoading` is true.
|
|
485
|
+
* @default 5
|
|
486
|
+
*/
|
|
487
|
+
skeletonRows?: number
|
|
488
|
+
/**
|
|
489
|
+
* Initial sorting state. Drives `useState` initial value — useful for restoring
|
|
490
|
+
* sort order from URL search params (e.g. `initialSorting={Route.useSearch().sorting}`).
|
|
491
|
+
* @example
|
|
492
|
+
* // URL-sync pattern with TanStack Router
|
|
493
|
+
* const { sorting } = Route.useSearch()
|
|
494
|
+
* <BasaltDataTable initialSorting={sorting} onSortingChange={(s) => navigate({ search: { sorting: s } })} … />
|
|
495
|
+
*/
|
|
496
|
+
initialSorting?: SortingState
|
|
497
|
+
/**
|
|
498
|
+
* Called whenever the internal sorting state changes. Receives the new `SortingState`.
|
|
499
|
+
* The table continues to manage sorting internally (uncontrolled) when this is omitted.
|
|
500
|
+
*/
|
|
501
|
+
onSortingChange?: (sorting: SortingState) => void
|
|
502
|
+
|
|
503
|
+
// ── Toolbar (search + facets + actions) ──────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Shows a global search `TextInput` above the table, wired to TanStack's `globalFilter` state
|
|
507
|
+
* (substring match against every column's stringified cell value).
|
|
508
|
+
* @default false
|
|
509
|
+
* @example
|
|
510
|
+
* <BasaltDataTable data={rows} columns={columns} enableGlobalFilter searchIcon={<IconSearch size={14} />} />
|
|
511
|
+
*/
|
|
512
|
+
enableGlobalFilter?: boolean
|
|
513
|
+
/**
|
|
514
|
+
* Placeholder for the global search input.
|
|
515
|
+
* @default 'Search…'
|
|
516
|
+
*/
|
|
517
|
+
globalFilterPlaceholder?: string
|
|
518
|
+
/**
|
|
519
|
+
* Leading icon rendered inside the global search input (Mantine `leftSection`). basalt-ui ships
|
|
520
|
+
* no icon set — pass any `ReactNode` (e.g. `<IconSearch size={14} />`).
|
|
521
|
+
*/
|
|
522
|
+
searchIcon?: ReactNode
|
|
523
|
+
/**
|
|
524
|
+
* Initial global filter value. Drives `useState` initial value — mirrors `initialSorting` for
|
|
525
|
+
* restoring the search term from a URL search param.
|
|
526
|
+
*/
|
|
527
|
+
initialGlobalFilter?: string
|
|
528
|
+
/** Called whenever the internal global filter value changes. */
|
|
529
|
+
onGlobalFilterChange?: (value: string) => void
|
|
530
|
+
/**
|
|
531
|
+
* Faceted column filters, rendered in the toolbar as `EnumFilter`/`MultiSelectFilter` pills
|
|
532
|
+
* inside a `FilterSet`, wired to TanStack's per-column `columnFilters` state. The toolbar
|
|
533
|
+
* renders whenever this array is non-empty, `enableGlobalFilter` is set, or `actions` is passed.
|
|
534
|
+
* @example
|
|
535
|
+
* facets={[{ columnId: 'department', label: 'Department', options: departmentOptions }]}
|
|
536
|
+
*/
|
|
537
|
+
facets?: DataTableFacet<T>[]
|
|
538
|
+
/** Called whenever the faceted column filters change — the seam for server-side faceting. */
|
|
539
|
+
onColumnFiltersChange?: (filters: ColumnFiltersState) => void
|
|
540
|
+
/**
|
|
541
|
+
* Hand filtering to the server: the table stops narrowing `data` locally and renders every row
|
|
542
|
+
* given, while the search input and facets keep reporting through `onGlobalFilterChange` /
|
|
543
|
+
* `onColumnFiltersChange` — which is where you re-request the filtered page.
|
|
544
|
+
*
|
|
545
|
+
* Required alongside `manualPagination` whenever `enableGlobalFilter` or `facets` is set;
|
|
546
|
+
* without it the controls narrow one page while "of N" keeps counting the whole set.
|
|
547
|
+
* @default false
|
|
548
|
+
*/
|
|
549
|
+
manualFiltering?: boolean
|
|
550
|
+
/**
|
|
551
|
+
* Right-aligned toolbar slot (e.g. an "Export" button). Renders the toolbar row even when no
|
|
552
|
+
* search input or facets are configured. Renamed from `toolbarActions` — the toolbar (search +
|
|
553
|
+
* facets + this slot) is wrapped in `CtlSlot` (C1/C5), so its controls resolve to the `ctl` tier.
|
|
554
|
+
*
|
|
555
|
+
* Two accepted forms (law C15): a typed `BarAction[]`, projected through the same row `PageBar`
|
|
556
|
+
* and `ActionGroup` use — ≤3 inline, the rest folded into `More`, one kebab below `sm` — or an
|
|
557
|
+
* opaque `ReactNode`, rendered verbatim exactly as before.
|
|
558
|
+
*/
|
|
559
|
+
actions?: SlotActions
|
|
560
|
+
|
|
561
|
+
// ── Row activation + selection ────────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Makes each row openable — click, or Enter on a focused row — with the ROW's original datum.
|
|
565
|
+
* The hook that pairs a table with `PageAside`: row → detail, without the caller adding an
|
|
566
|
+
* `onClick` to every cell renderer.
|
|
567
|
+
*
|
|
568
|
+
* The row keeps its native `<tr>` semantics and takes `tabIndex=0` plus `data-activatable`,
|
|
569
|
+
* never `role="button"`: a role swap takes the row out of the table's row/cell semantics, so a
|
|
570
|
+
* screen reader stops announcing each cell's column header — which is the reason the data is in
|
|
571
|
+
* a table. Cursor and focus ring come from the module class.
|
|
572
|
+
*
|
|
573
|
+
* A click that originates inside the selection checkbox does NOT activate (the checkbox stops
|
|
574
|
+
* the event); a click on a control your own `cell` renders should do the same.
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* <BasaltDataTable data={rows} columns={columns} onRowActivate={(row) => setSelected(row)} />
|
|
578
|
+
*/
|
|
579
|
+
onRowActivate?: (row: T) => void
|
|
580
|
+
/**
|
|
581
|
+
* Prepends a checkbox column (header = select-all on the page) and arms TanStack's row-selection
|
|
582
|
+
* feature. Selection is uncontrolled unless `rowSelection` is passed.
|
|
583
|
+
* @default false
|
|
584
|
+
*/
|
|
585
|
+
enableRowSelection?: boolean
|
|
586
|
+
/**
|
|
587
|
+
* Controlled selection — TanStack's own `RowSelectionState` (`Record<rowId, boolean>`), keyed by
|
|
588
|
+
* `getRowId` (row index by default). Omit for uncontrolled selection held by the table.
|
|
589
|
+
*/
|
|
590
|
+
rowSelection?: RowSelectionState
|
|
591
|
+
/** Called with the next `RowSelectionState` on every selection change, controlled or not. */
|
|
592
|
+
onRowSelectionChange?: (selection: RowSelectionState) => void
|
|
593
|
+
/**
|
|
594
|
+
* Stable row id, handed to TanStack. Pass one whenever the selection must survive a re-sort, a
|
|
595
|
+
* page change or a refetch — the default id is the row's INDEX, so row 3 stays "selected" when a
|
|
596
|
+
* different record moves into position 3.
|
|
597
|
+
*/
|
|
598
|
+
getRowId?: (row: T, index: number) => string
|
|
599
|
+
/**
|
|
600
|
+
* The actions a selection offers, rendered in a bar above the table while at least one row is
|
|
601
|
+
* selected — and nothing at all while none is.
|
|
602
|
+
*
|
|
603
|
+
* A FUNCTION of the selected rows, not a bare array, and that is the whole signature decision:
|
|
604
|
+
* `BarActionItem.onClick` is `() => void` (the one vocabulary every home shares, unforked), so
|
|
605
|
+
* the rows have to reach the handler through the closure the caller writes here. It also means
|
|
606
|
+
* an UNCONTROLLED table can offer bulk actions at all — the selection lives inside the table,
|
|
607
|
+
* and this is how it gets out.
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* bulkActions={(rows) => [
|
|
611
|
+
* { key: 'export', label: `Export ${rows.length}`, onClick: () => exportCsv(rows) },
|
|
612
|
+
* { key: 'delete', label: 'Delete', danger: true, onClick: () => remove(rows) },
|
|
613
|
+
* ]}
|
|
614
|
+
*/
|
|
615
|
+
bulkActions?: (rows: T[]) => BarAction[]
|
|
616
|
+
|
|
617
|
+
// ── Pagination ────────────────────────────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Enables the bottom pagination bar ("Showing X–Y of N", rows-per-page `Select`, Mantine
|
|
621
|
+
* `Pagination`) wired to TanStack's `pagination` state. Client-side by default — see
|
|
622
|
+
* `manualPagination` for server-driven pagination.
|
|
623
|
+
* @default false
|
|
624
|
+
*/
|
|
625
|
+
enablePagination?: boolean
|
|
626
|
+
/**
|
|
627
|
+
* Rows-per-page choices offered in the pagination bar's Select.
|
|
628
|
+
* @default [10, 25, 50, 100]
|
|
629
|
+
*/
|
|
630
|
+
pageSizeOptions?: number[]
|
|
631
|
+
/**
|
|
632
|
+
* Initial pagination state. Drives `useState` initial value — mirrors `initialSorting` for
|
|
633
|
+
* restoring the page/size from a URL search param.
|
|
634
|
+
*/
|
|
635
|
+
initialPagination?: PaginationState
|
|
636
|
+
/** Called whenever the internal pagination state changes. */
|
|
637
|
+
onPaginationChange?: (pagination: PaginationState) => void
|
|
638
|
+
/**
|
|
639
|
+
* Disables local pagination slicing — pass the already-paginated page of `data` plus `rowCount`
|
|
640
|
+
* (and optionally `pageCount`) for server-driven pagination. `onPaginationChange` is where you
|
|
641
|
+
* fetch the next page.
|
|
642
|
+
*
|
|
643
|
+
* **It imposes a contract, enforced at render.** `data` is now one page, but the pagination bar
|
|
644
|
+
* says "Showing 1–25 of 412" — so every OTHER client-side control becomes a claim about 412 rows
|
|
645
|
+
* that it can only make about 25. Each has to be resolved explicitly, because only the call site
|
|
646
|
+
* knows whether the server does the work:
|
|
647
|
+
*
|
|
648
|
+
* - `rowCount` (or `pageCount`) must be given, or "of N" counts the page and the pager collapses
|
|
649
|
+
* to a single page nobody can leave;
|
|
650
|
+
* - sorting must be handed over with `manualSorting`, or switched off with `enableSorting={false}`;
|
|
651
|
+
* - `enableGlobalFilter` / `facets`, if used, must be handed over with `manualFiltering`;
|
|
652
|
+
* - `enablePagination` must be on, or `manualPagination` is inert and the page renders as if it
|
|
653
|
+
* were the whole table.
|
|
654
|
+
*
|
|
655
|
+
* Unresolved, each throws in dev and degrades to the honest render in production (no sort
|
|
656
|
+
* headers, no filter controls, no "of N") — never to the plausible wrong answer.
|
|
657
|
+
* @default false
|
|
658
|
+
* @example
|
|
659
|
+
* <BasaltDataTable
|
|
660
|
+
* data={page.rows}
|
|
661
|
+
* columns={columns}
|
|
662
|
+
* enablePagination
|
|
663
|
+
* manualPagination
|
|
664
|
+
* rowCount={page.total}
|
|
665
|
+
* initialPagination={{ pageIndex: page.index, pageSize: page.size }}
|
|
666
|
+
* onPaginationChange={(p) => fetchPage(p)}
|
|
667
|
+
* />
|
|
668
|
+
*/
|
|
669
|
+
manualPagination?: boolean
|
|
670
|
+
/** Total row count across all pages. Required for `manualPagination` to render "of N" and compute page count. */
|
|
671
|
+
rowCount?: number
|
|
672
|
+
/** Total page count, when known. Only consulted under `manualPagination`; omit to derive it from `rowCount`/`pageSize`. */
|
|
673
|
+
pageCount?: number
|
|
674
|
+
|
|
675
|
+
// ── Column pinning ────────────────────────────────────────────────────────────
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Enables sticky left/right column pinning — pinned columns stick to the edge with a panel
|
|
679
|
+
* background and a hairline shadow separator while the table scrolls horizontally. Pin columns
|
|
680
|
+
* via `initialColumnPinning` (`{ left: string[], right: string[] }`).
|
|
681
|
+
* @default false
|
|
682
|
+
* @example
|
|
683
|
+
* <BasaltDataTable data={rows} columns={columns} enablePinning initialColumnPinning={{ left: ['name'] }} />
|
|
684
|
+
*/
|
|
685
|
+
enablePinning?: boolean
|
|
686
|
+
/** Initial column-pinning state — which column ids stick to the left/right edge. */
|
|
687
|
+
initialColumnPinning?: DataTableColumnPinning<T>
|
|
688
|
+
|
|
689
|
+
// ── Body chrome ─────────────────────────────────────────────────────────────
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Caps the scrolling body (px number or CSS length) so a long table cannot blow a card out
|
|
693
|
+
* vertically — the header stays put and the rows scroll under it. Renders Mantine's
|
|
694
|
+
* `Table.ScrollContainer type="native"`, which is the same node the docs sanction as the raw
|
|
695
|
+
* escape for a bespoke table: the blessed lane and the escape hatch produce identical DOM, so
|
|
696
|
+
* adopting one does not contradict the other.
|
|
697
|
+
*
|
|
698
|
+
* `type="native"` is required rather than preferred: `ScrollArea`'s custom viewport is the
|
|
699
|
+
* positioning context a sticky `<thead>` resolves against, so the default `'scrollarea'` type
|
|
700
|
+
* pins the header to the page viewport instead of the table's box.
|
|
701
|
+
*/
|
|
702
|
+
maxHeight?: number | string
|
|
703
|
+
/**
|
|
704
|
+
* `min-width` below which the body scrolls horizontally. Setting either this or `maxHeight`
|
|
705
|
+
* turns on the CAPPED scroll container; `maxHeight` alone implies `minWidth: 0`.
|
|
706
|
+
*
|
|
707
|
+
* It is a FLOOR, not the containment switch — since the shell made `AppShell.Main` the
|
|
708
|
+
* scrollport, every table renders inside a `Table.ScrollContainer type="native"` whether or not
|
|
709
|
+
* this is set (`minWidth: 0` when it is not), so a table wider than its column scrolls inside
|
|
710
|
+
* its own card instead of widening the page. Reach for this only to hold a MINIMUM table width
|
|
711
|
+
* — e.g. so eight columns keep their readable widths and scroll rather than compress.
|
|
712
|
+
*
|
|
713
|
+
* The one table whose wrapper is MEASURED rather than declared is `stickyHeader` with neither
|
|
714
|
+
* this nor `maxHeight`; see that prop and `useMeasuredContainment` in the implementation for
|
|
715
|
+
* why, and pair it with `maxHeight` if the table must both scroll and stick.
|
|
716
|
+
*/
|
|
717
|
+
minWidth?: number | string
|
|
718
|
+
/**
|
|
719
|
+
* Sticky header row. Pair with `maxHeight` to stick within the table rather than the page.
|
|
720
|
+
*
|
|
721
|
+
* On its own — no `maxHeight`, no `minWidth` — the header sticks against the page's scrollport
|
|
722
|
+
* (`AppShell.Main`) only while the table FITS its container, and basalt measures that: the
|
|
723
|
+
* wrapper declares no overflow while it fits and flips to `overflow-x: auto` once the table is
|
|
724
|
+
* wider, publishing `data-contained`. The columns stay reachable either way and the page never
|
|
725
|
+
* scrolls sideways, but a sticky header cannot stick inside an uncapped overflow box — it has
|
|
726
|
+
* no scroll range — so it is inert at that width. A dev warning states the trade; pair this
|
|
727
|
+
* with `maxHeight` for a table that must both scroll and stick at every width.
|
|
728
|
+
*/
|
|
729
|
+
stickyHeader?: boolean
|
|
730
|
+
/**
|
|
731
|
+
* Offset for a sticky header sitting under whatever chrome scrolls above it (px number or CSS
|
|
732
|
+
* length).
|
|
733
|
+
*
|
|
734
|
+
* PAGE-scroll only, and IGNORED whenever `maxHeight`/`minWidth` turn on the scroll container:
|
|
735
|
+
* that box is then the header's own scrollport, so an offset would park the `<thead>` that many
|
|
736
|
+
* pixels down inside the body — painting it over the rows instead of above them. Inside the
|
|
737
|
+
* container the header sticks to the scroller's own top edge.
|
|
738
|
+
*
|
|
739
|
+
* Inside `BasaltShell` the page's scrollport is `AppShell.Main`, and the app header sits
|
|
740
|
+
* OUTSIDE it — so the only chrome left to clear is `PageBar` row 2's band:
|
|
741
|
+
* `var(--basalt-page-bar-h, 0px)`, not the header height plus it.
|
|
742
|
+
*/
|
|
743
|
+
stickyHeaderOffset?: number | string
|
|
744
|
+
/** Vertical cell padding. Forwarded to Mantine `Table`. */
|
|
745
|
+
verticalSpacing?: MantineSpacing
|
|
746
|
+
/** Horizontal cell padding. Forwarded to Mantine `Table`. */
|
|
747
|
+
horizontalSpacing?: MantineSpacing
|
|
748
|
+
/** Row separators. Forwarded to Mantine `Table`. @default true (Mantine's own default) */
|
|
749
|
+
withRowBorders?: boolean
|
|
750
|
+
/**
|
|
751
|
+
* Outer table border — a full box around the table.
|
|
752
|
+
*
|
|
753
|
+
* **`false` by default since this minor** (it was `true`). Three separate faults, all visible on
|
|
754
|
+
* the playground's `Controls (mobile)` page at 390px: its TOP edge sat directly under the table's
|
|
755
|
+
* own `WidgetHeader`, separating a header from its own content; its BOTTOM edge landed on the last
|
|
756
|
+
* row's own hairline, two rules at zero distance; and its left/right edges carried no information
|
|
757
|
+
* at all on a table that fills the page (`docs/DESIGN-SPEC.md` §8 — a border is a layout divider,
|
|
758
|
+
* and there is nothing here to divide). The head-row rule and the between-row rules stay, so the
|
|
759
|
+
* table still reads as a table.
|
|
760
|
+
*
|
|
761
|
+
* Pass `true` for a table that genuinely needs a frame — a small table floating in whitespace
|
|
762
|
+
* beside other content, where the box IS the grouping.
|
|
763
|
+
*
|
|
764
|
+
* @default false
|
|
765
|
+
*/
|
|
766
|
+
withTableBorder?: boolean
|
|
767
|
+
}
|
|
499
768
|
|
|
500
769
|
// ── Sort indicator ────────────────────────────────────────────────────────────
|
|
501
770
|
|
|
@@ -530,19 +799,38 @@ const RANGE_LABEL_STYLE: CSSProperties = {
|
|
|
530
799
|
const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50, 100]
|
|
531
800
|
|
|
532
801
|
/**
|
|
533
|
-
* The header row's two halves. The lead
|
|
534
|
-
*
|
|
535
|
-
*
|
|
802
|
+
* The header row's two halves. The lead GROWS and the toolbar does not — a title can ellipsize into
|
|
803
|
+
* whatever is left, and a search field that grew with it was 600px on a wide table.
|
|
804
|
+
*
|
|
805
|
+
* Both SHRINK, and that half is not a preference. `CtlSlot` between the header row and the toolbar
|
|
806
|
+
* is `display: contents`, so the toolbar `Group` IS the row's flex item — and with the
|
|
807
|
+
* `flex: 0 0 auto` it used to carry (plus flex's default `min-width: auto`) it could not give up a
|
|
808
|
+
* pixel. MEASURED at 390x844 on `/data`: a 220px search beside a 230px facet pill row, gap 11, is a
|
|
809
|
+
* 461px item in a 302px column, and `AppShell.Main` read `scrollWidth` 505 against `clientWidth`
|
|
810
|
+
* 390 — the whole app draggable sideways over a table header. `0 1 auto` + `min-width: 0` lets the
|
|
811
|
+
* item shrink to the column, and `wrap="wrap"` then does the rest: the search takes one line and
|
|
812
|
+
* the pills the next. Nothing here is width-gated, so it holds in a narrow grid cell at any
|
|
813
|
+
* viewport, the same way the chart tier resolves.
|
|
536
814
|
*/
|
|
537
815
|
const TABLE_HEADER_LEAD_STYLE: CSSProperties = { flex: '1 1 auto', minWidth: 0 }
|
|
538
|
-
const TABLE_TOOLBAR_STYLE: CSSProperties = { flex: '0
|
|
816
|
+
const TABLE_TOOLBAR_STYLE: CSSProperties = { flex: '0 1 auto', minWidth: 0 }
|
|
539
817
|
/**
|
|
540
818
|
* The toolbar search's width. Mantine's Input grows to its container, which in a `justify:
|
|
541
819
|
* space-between` row means "whatever is left" — so the field was 600px on a wide table and 180px on a
|
|
542
820
|
* narrow one, for the same one-word query. A stated width also makes the toolbar's own minimum width
|
|
543
821
|
* knowable, which is what lets it wrap under the title below `sm` with no media query.
|
|
822
|
+
*
|
|
823
|
+
* It is a BASIS, not a `width`: `flex: 0 1 <SEARCH_WIDTH>px` holds the field at 220px wherever 220
|
|
824
|
+
* is available and lets it shrink where it is not, floored at `12ch` (a legible query plus the
|
|
825
|
+
* search glyph) and capped at `100%` so it can never be the box that outgrows its column. A literal
|
|
826
|
+
* `w={220}` in a 302px column was half of the overflow above.
|
|
544
827
|
*/
|
|
545
828
|
const SEARCH_WIDTH = 220
|
|
829
|
+
const TABLE_SEARCH_STYLE: CSSProperties = {
|
|
830
|
+
flex: `0 1 ${SEARCH_WIDTH}px`,
|
|
831
|
+
minWidth: '12ch',
|
|
832
|
+
maxWidth: '100%',
|
|
833
|
+
}
|
|
546
834
|
/** The rows-per-page Select's width — see `SEARCH_WIDTH` for why a toolbar field states one. */
|
|
547
835
|
const PAGE_SIZE_WIDTH = 116
|
|
548
836
|
|
|
@@ -598,13 +886,6 @@ function getPinnedCellStyle<T>(
|
|
|
598
886
|
|
|
599
887
|
// ── The manual-pagination contract ────────────────────────────────────────────
|
|
600
888
|
|
|
601
|
-
/** The house dev gate (`src/provider`, `src/charts/kinds/BandStrip` use the same expression):
|
|
602
|
-
* `basaltViteConfig` defines `process.env.NODE_ENV`, so a production bundle constant-folds this to
|
|
603
|
-
* `false` and drops the throw. Read per call, never hoisted, so a test can flip it. */
|
|
604
|
-
function isDev(): boolean {
|
|
605
|
-
return process.env['NODE_ENV'] !== 'production'
|
|
606
|
-
}
|
|
607
|
-
|
|
608
889
|
/** One client-side control that `manualPagination` turns into a claim the table cannot support. */
|
|
609
890
|
type ManualPaginationBreach = 'inert' | 'total' | 'sorting' | 'filtering'
|
|
610
891
|
|
|
@@ -687,6 +968,99 @@ function enforceManualPaginationContract(breaches: ManualPaginationBreach[]): vo
|
|
|
687
968
|
console.error(message)
|
|
688
969
|
}
|
|
689
970
|
|
|
971
|
+
/**
|
|
972
|
+
* MEASURED CONTAINMENT — the wrapper for the ONE table shape whose overflow mode cannot be decided
|
|
973
|
+
* statically: `stickyHeader` with neither `maxHeight` nor `minWidth`.
|
|
974
|
+
*
|
|
975
|
+
* Every other table renders inside `Table.ScrollContainer type="native"`, because a bare `<table>`
|
|
976
|
+
* sizes to its own min-content and a five-column one measured 448px inside a 390px viewport,
|
|
977
|
+
* dragging the whole page sideways (`tests/layout/no-horizontal-overflow.layout.test.ts`). This
|
|
978
|
+
* shape cannot take that container unconditionally: an `overflow-x: auto` box computes `overflow-y`
|
|
979
|
+
* to `auto` as well, which makes it the sticky header's own scrollport — and with no height cap
|
|
980
|
+
* that box has no scroll range at all, so the `<thead>` scrolls clean away (MEASURED in Chrome:
|
|
981
|
+
* top 10.5 → −389.5 across a 400px scroll). Containing it unconditionally deletes the feature;
|
|
982
|
+
* leaving it bare unconditionally widens the page.
|
|
983
|
+
*
|
|
984
|
+
* So the wrapper's overflow is decided by MEASUREMENT, the same fit-check `useTrackFits`
|
|
985
|
+
* (`controls/panel-row.tsx`) runs for a segmented track. While the table FITS its wrapper the
|
|
986
|
+
* wrapper is `overflow: visible` — bare, and the page-sticky header sticks against `AppShell.Main`
|
|
987
|
+
* exactly as it did before. Once the table is WIDER the wrapper flips to `overflow-x: auto`, the
|
|
988
|
+
* columns stay reachable by horizontal scroll, and the header is necessarily inert at that width.
|
|
989
|
+
* That is the honest trade: a header that stops sticking is recoverable, a page that scrolls
|
|
990
|
+
* sideways takes every fixed and sticky element on it with it.
|
|
991
|
+
*
|
|
992
|
+
* Three properties that are easy to lose:
|
|
993
|
+
*
|
|
994
|
+
* - **The observer stays alive in BOTH states** — no one-way latch, unlike `useTrackFits`. The
|
|
995
|
+
* wrapper must revert to bare when the space comes back (the window widened, the sidebar
|
|
996
|
+
* collapsed, the aside closed), and a contained table's `offsetWidth` is still its min-content
|
|
997
|
+
* width, so the same comparison keeps answering correctly from inside the contained state.
|
|
998
|
+
* - **Equality counts as FITS**, which is what stops the two states oscillating: a contained
|
|
999
|
+
* wrapper that grows past the table reads `offsetWidth === clientWidth` (the `width: 100%` table
|
|
1000
|
+
* resolves against the container) and flips back exactly once.
|
|
1001
|
+
* - **A zero `clientWidth` is `unknown`, not overflow** — the ancestor chain has not been laid out
|
|
1002
|
+
* yet (the aside animates its width in from 0, `docs/ASIDE-SPEC.md` §0). Committing to contained
|
|
1003
|
+
* on that reading would wrap a table that was never too wide.
|
|
1004
|
+
*
|
|
1005
|
+
* `useLayoutEffect` runs before paint, so a table that is already measurable resolves without a
|
|
1006
|
+
* flash of the wrong mode. SSR reaches neither the effect nor a `window`, and the default state is
|
|
1007
|
+
* bare — the same node the server and the first client paint both render.
|
|
1008
|
+
*/
|
|
1009
|
+
function useMeasuredContainment(active: boolean): {
|
|
1010
|
+
wrapperRef: RefObject<HTMLDivElement | null>
|
|
1011
|
+
contained: boolean
|
|
1012
|
+
} {
|
|
1013
|
+
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
1014
|
+
const [contained, setContained] = useState(false)
|
|
1015
|
+
|
|
1016
|
+
useLayoutEffect(() => {
|
|
1017
|
+
if (!active) return
|
|
1018
|
+
const wrapper = wrapperRef.current
|
|
1019
|
+
if (wrapper === null) return
|
|
1020
|
+
|
|
1021
|
+
// Three answers, not two — see the docblock. `unknown` moves neither the state nor the
|
|
1022
|
+
// observer; it is simply not an answer yet.
|
|
1023
|
+
const checkFit = (): 'fits' | 'contained' | 'unknown' => {
|
|
1024
|
+
const table = wrapper.querySelector('table')
|
|
1025
|
+
if (table === null) return 'unknown'
|
|
1026
|
+
if (wrapper.clientWidth === 0 || table.offsetWidth === 0) return 'unknown'
|
|
1027
|
+
return table.offsetWidth <= wrapper.clientWidth ? 'fits' : 'contained'
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const apply = (): void => {
|
|
1031
|
+
const result = checkFit()
|
|
1032
|
+
if (result === 'unknown') return
|
|
1033
|
+
const next = result === 'contained'
|
|
1034
|
+
setContained((current) => (current === next ? current : next))
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
1038
|
+
// No recovery path without an observer, so the one same-tick read is this environment's
|
|
1039
|
+
// only chance. `unknown` stays bare, which is the pre-measurement default anyway.
|
|
1040
|
+
apply()
|
|
1041
|
+
return
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const observer = new ResizeObserver(apply)
|
|
1045
|
+
observer.observe(wrapper)
|
|
1046
|
+
const table = wrapper.querySelector('table')
|
|
1047
|
+
// The TABLE too, not only the wrapper: a column set that re-measures (a facet filter changing
|
|
1048
|
+
// the widest cell, a font settling) moves the table's min-content width while the wrapper's
|
|
1049
|
+
// box never changes at all.
|
|
1050
|
+
if (table !== null) observer.observe(table)
|
|
1051
|
+
|
|
1052
|
+
// Same check once, synchronously, so an already-settled table resolves before paint rather
|
|
1053
|
+
// than waiting for the observer's first async tick.
|
|
1054
|
+
apply()
|
|
1055
|
+
|
|
1056
|
+
return () => {
|
|
1057
|
+
observer.disconnect()
|
|
1058
|
+
}
|
|
1059
|
+
}, [active])
|
|
1060
|
+
|
|
1061
|
+
return { wrapperRef, contained: active && contained }
|
|
1062
|
+
}
|
|
1063
|
+
|
|
690
1064
|
// ── Component ─────────────────────────────────────────────────────────────────
|
|
691
1065
|
|
|
692
1066
|
/**
|
|
@@ -714,48 +1088,63 @@ function enforceManualPaginationContract(breaches: ManualPaginationBreach[]): vo
|
|
|
714
1088
|
* emptyState={<Text c="dimmed">No users found.</Text>}
|
|
715
1089
|
* />
|
|
716
1090
|
*/
|
|
717
|
-
export function BasaltDataTable<T>({
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
data,
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1091
|
+
export function BasaltDataTable<T>(props: BasaltDataTableProps<T>) {
|
|
1092
|
+
// F-ERR-1: without this, a table missing `data`/`columns` fails deep inside `useReactTable` as a
|
|
1093
|
+
// raw `TypeError` caught by `BasaltErrorBoundary` — a blank subtree with no message naming either
|
|
1094
|
+
// prop.
|
|
1095
|
+
assertRequiredProps('BasaltDataTable', props, ['data', 'columns'])
|
|
1096
|
+
const {
|
|
1097
|
+
title,
|
|
1098
|
+
icon,
|
|
1099
|
+
subtitle,
|
|
1100
|
+
data,
|
|
1101
|
+
columns,
|
|
1102
|
+
enableSorting = true,
|
|
1103
|
+
manualSorting = false,
|
|
1104
|
+
striped,
|
|
1105
|
+
highlightOnHover,
|
|
1106
|
+
emptyState,
|
|
1107
|
+
isLoading = false,
|
|
1108
|
+
query,
|
|
1109
|
+
skeletonRows = 5,
|
|
1110
|
+
initialSorting,
|
|
1111
|
+
onSortingChange,
|
|
1112
|
+
enableGlobalFilter = false,
|
|
1113
|
+
globalFilterPlaceholder = 'Search…',
|
|
1114
|
+
searchIcon,
|
|
1115
|
+
initialGlobalFilter,
|
|
1116
|
+
onGlobalFilterChange,
|
|
1117
|
+
facets,
|
|
1118
|
+
onColumnFiltersChange,
|
|
1119
|
+
manualFiltering = false,
|
|
1120
|
+
actions,
|
|
1121
|
+
onRowActivate,
|
|
1122
|
+
enableRowSelection = false,
|
|
1123
|
+
rowSelection,
|
|
1124
|
+
onRowSelectionChange,
|
|
1125
|
+
getRowId,
|
|
1126
|
+
bulkActions,
|
|
1127
|
+
enablePagination = false,
|
|
1128
|
+
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
1129
|
+
initialPagination,
|
|
1130
|
+
onPaginationChange,
|
|
1131
|
+
manualPagination = false,
|
|
1132
|
+
rowCount,
|
|
1133
|
+
pageCount,
|
|
1134
|
+
enablePinning = false,
|
|
1135
|
+
initialColumnPinning,
|
|
1136
|
+
maxHeight,
|
|
1137
|
+
minWidth,
|
|
1138
|
+
stickyHeader,
|
|
1139
|
+
stickyHeaderOffset,
|
|
1140
|
+
verticalSpacing,
|
|
1141
|
+
horizontalSpacing,
|
|
1142
|
+
withRowBorders,
|
|
1143
|
+
withTableBorder = false,
|
|
1144
|
+
className,
|
|
1145
|
+
style,
|
|
1146
|
+
classNames,
|
|
1147
|
+
} = props
|
|
759
1148
|
const [sorting, setSorting] = useState<SortingState>(initialSorting ?? [])
|
|
760
1149
|
const [globalFilter, setGlobalFilter] = useState(initialGlobalFilter ?? '')
|
|
761
1150
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
|
@@ -763,6 +1152,51 @@ export function BasaltDataTable<T>({
|
|
|
763
1152
|
initialPagination ?? { pageIndex: 0, pageSize: pageSizeOptions[0] ?? 10 },
|
|
764
1153
|
)
|
|
765
1154
|
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>(initialColumnPinning ?? {})
|
|
1155
|
+
// Uncontrolled by default; `rowSelection`, when passed, is the truth and this only mirrors it so
|
|
1156
|
+
// the updater below has a base to apply against.
|
|
1157
|
+
const [internalRowSelection, setInternalRowSelection] = useState<RowSelectionState>({})
|
|
1158
|
+
const rowSelectionState = rowSelection ?? internalRowSelection
|
|
1159
|
+
|
|
1160
|
+
// `stickyHeaderOffset` is DROPPED whenever the table owns a scroll container — see
|
|
1161
|
+
// `resolvedStickyHeaderOffset` below for why it is always wrong there. Dropping it silently is
|
|
1162
|
+
// the part worth a message: the header still sticks, so the table looks right until it overlaps
|
|
1163
|
+
// the page chrome the offset was measured against, and nothing anywhere says the prop was ignored.
|
|
1164
|
+
//
|
|
1165
|
+
// The second check is `query` beside `isLoading`: `query` resolves the pending branch AND the two
|
|
1166
|
+
// `isLoading` cannot express, so it wins outright. Silently, that reads as the boolean having no
|
|
1167
|
+
// effect — which is indistinguishable from a bug in the caller's own loading flag.
|
|
1168
|
+
useValidateProps(
|
|
1169
|
+
'BasaltDataTable',
|
|
1170
|
+
() => [
|
|
1171
|
+
stickyHeaderOffset === undefined || (maxHeight === undefined && minWidth === undefined)
|
|
1172
|
+
? null
|
|
1173
|
+
: `${BASALT_PREFIX} BasaltDataTable: prop "stickyHeaderOffset" is ignored beside ` +
|
|
1174
|
+
'"maxHeight"/"minWidth" — those render a scroll container, and the sticky `<thead>` then ' +
|
|
1175
|
+
"anchors to that box's own top edge rather than to the window. Drop the offset, or drop " +
|
|
1176
|
+
'the cap and let the page scroll.',
|
|
1177
|
+
query === undefined || props.isLoading === undefined
|
|
1178
|
+
? null
|
|
1179
|
+
: `${BASALT_PREFIX} BasaltDataTable: props "query" and "isLoading" are both set — "query" ` +
|
|
1180
|
+
'wins and "isLoading" is ignored. `query` already resolves the pending branch, plus the ' +
|
|
1181
|
+
'error and empty ones the boolean cannot express. Drop "isLoading".',
|
|
1182
|
+
stickyHeader !== true || maxHeight !== undefined || minWidth !== undefined
|
|
1183
|
+
? null
|
|
1184
|
+
: `${BASALT_PREFIX} BasaltDataTable: prop "stickyHeader" with neither "maxHeight" nor ` +
|
|
1185
|
+
'"minWidth" sticks only while the table FITS its container. basalt measures it: a table ' +
|
|
1186
|
+
'wider than the space it has flips its wrapper to `overflow-x: auto` so the columns ' +
|
|
1187
|
+
'stay reachable and the page never scrolls sideways, and a sticky header cannot stick ' +
|
|
1188
|
+
'inside that box — it has no scroll range. Pair it with "maxHeight" for a table that ' +
|
|
1189
|
+
'must both scroll and stick.',
|
|
1190
|
+
],
|
|
1191
|
+
[
|
|
1192
|
+
stickyHeaderOffset,
|
|
1193
|
+
maxHeight,
|
|
1194
|
+
minWidth,
|
|
1195
|
+
stickyHeader,
|
|
1196
|
+
query === undefined,
|
|
1197
|
+
props.isLoading === undefined,
|
|
1198
|
+
],
|
|
1199
|
+
)
|
|
766
1200
|
|
|
767
1201
|
const handleSortingChange = useCallback(
|
|
768
1202
|
(updater: Updater<SortingState>) => {
|
|
@@ -808,10 +1242,29 @@ export function BasaltDataTable<T>({
|
|
|
808
1242
|
[onPaginationChange],
|
|
809
1243
|
)
|
|
810
1244
|
|
|
1245
|
+
const handleRowSelectionChange = useCallback(
|
|
1246
|
+
(updater: Updater<RowSelectionState>) => {
|
|
1247
|
+
// CONTROLLED: the caller's map is the base AND the only writer. Mirroring it into state too
|
|
1248
|
+
// would leave a copy that the caller never moves, and that stale copy becomes the selection
|
|
1249
|
+
// the moment `rowSelection` goes back to `undefined` — plus every tick would render twice for
|
|
1250
|
+
// one change. Report the next map and let the caller own it.
|
|
1251
|
+
if (rowSelection !== undefined) {
|
|
1252
|
+
onRowSelectionChange?.(typeof updater === 'function' ? updater(rowSelection) : updater)
|
|
1253
|
+
return
|
|
1254
|
+
}
|
|
1255
|
+
setInternalRowSelection((prev) => {
|
|
1256
|
+
const next = typeof updater === 'function' ? updater(prev) : updater
|
|
1257
|
+
onRowSelectionChange?.(next)
|
|
1258
|
+
return next
|
|
1259
|
+
})
|
|
1260
|
+
},
|
|
1261
|
+
[rowSelection, onRowSelectionChange],
|
|
1262
|
+
)
|
|
1263
|
+
|
|
811
1264
|
// Facets are TanStack `columnFilters` entries under the hood — inject an exact-match (or
|
|
812
1265
|
// any-of, for `multiple`) filterFn onto the matching column so the built-in `includesString`
|
|
813
1266
|
// auto-filter (substring match) never misfires against a Select's exact option value.
|
|
814
|
-
const
|
|
1267
|
+
const facetColumns = useMemo(() => {
|
|
815
1268
|
if (!facets || facets.length === 0) return columns
|
|
816
1269
|
const facetById = new Map(facets.map((facet) => [facet.columnId, facet]))
|
|
817
1270
|
return columns.map((column) => {
|
|
@@ -836,6 +1289,14 @@ export function BasaltDataTable<T>({
|
|
|
836
1289
|
})
|
|
837
1290
|
}, [columns, facets])
|
|
838
1291
|
|
|
1292
|
+
// The checkbox column is PREPENDED here rather than asked of the caller, so `columns` stays the
|
|
1293
|
+
// caller's own data columns and `meta.align` / `numeral` never have to reason about a synthetic
|
|
1294
|
+
// one. Everything counting columns downstream reads `columnCount`, below.
|
|
1295
|
+
const tableColumns = useMemo(
|
|
1296
|
+
() => (enableRowSelection ? [selectionColumn<T>(), ...facetColumns] : facetColumns),
|
|
1297
|
+
[facetColumns, enableRowSelection],
|
|
1298
|
+
)
|
|
1299
|
+
|
|
839
1300
|
// Enforced before the table is built, so the degraded options below are the ones TanStack gets —
|
|
840
1301
|
// a degraded table must not merely hide a control, it must not compute the answer either.
|
|
841
1302
|
const hasFacets = Boolean(facets && facets.length > 0)
|
|
@@ -866,12 +1327,18 @@ export function BasaltDataTable<T>({
|
|
|
866
1327
|
columnFilters,
|
|
867
1328
|
columnPinning,
|
|
868
1329
|
...(enablePagination && { pagination }),
|
|
1330
|
+
...(enableRowSelection && { rowSelection: rowSelectionState }),
|
|
869
1331
|
},
|
|
870
1332
|
onSortingChange: handleSortingChange,
|
|
871
1333
|
onGlobalFilterChange: handleGlobalFilterChange,
|
|
872
1334
|
onColumnFiltersChange: handleColumnFiltersChange,
|
|
873
1335
|
onColumnPinningChange: setColumnPinning,
|
|
874
1336
|
...(enablePagination && { onPaginationChange: handlePaginationChange }),
|
|
1337
|
+
...(enableRowSelection && {
|
|
1338
|
+
enableRowSelection: true,
|
|
1339
|
+
onRowSelectionChange: handleRowSelectionChange,
|
|
1340
|
+
}),
|
|
1341
|
+
...(getRowId !== undefined && { getRowId }),
|
|
875
1342
|
getCoreRowModel: getCoreRowModel(),
|
|
876
1343
|
getSortedRowModel: getSortedRowModel(),
|
|
877
1344
|
getFilteredRowModel: getFilteredRowModel(),
|
|
@@ -895,17 +1362,76 @@ export function BasaltDataTable<T>({
|
|
|
895
1362
|
const showSearch = enableGlobalFilter && filteringEnabled
|
|
896
1363
|
const showFacets = hasFacets && filteringEnabled
|
|
897
1364
|
const showToolbar = showSearch || showFacets || Boolean(actions)
|
|
1365
|
+
|
|
1366
|
+
// ── The query branch (law C3) ───────────────────────────────────────────────
|
|
1367
|
+
// `query`, when given, owns the body: it resolves the pending branch `isLoading` used to own,
|
|
1368
|
+
// plus the error branch the table had none of. `isLoading` stays live for every caller who never
|
|
1369
|
+
// had a query result to hand — the dev warning above covers the pair.
|
|
1370
|
+
const branch = query === undefined ? undefined : dataQueryBranch('BasaltDataTable', query)
|
|
1371
|
+
const showSkeleton = branch === undefined ? isLoading : branch === 'pending'
|
|
1372
|
+
const queryError = branch === 'error' && query !== undefined ? query : undefined
|
|
1373
|
+
|
|
1374
|
+
// Every colSpan and every skeleton row counts the RENDERED columns, which is one more than
|
|
1375
|
+
// `columns` once the checkbox column is prepended — a stale count left the empty state and the
|
|
1376
|
+
// error row one cell short of the header.
|
|
1377
|
+
const columnCount = columns.length + (enableRowSelection ? 1 : 0)
|
|
1378
|
+
|
|
1379
|
+
// `getSelectedRowModel()` is TanStack's own memo, so its `rows` array keeps its identity until
|
|
1380
|
+
// the selection or the row model actually moves — which is what makes both memos below hold.
|
|
1381
|
+
const selectedModelRows = enableRowSelection ? table.getSelectedRowModel().rows : undefined
|
|
1382
|
+
const selectedRows = useMemo(
|
|
1383
|
+
() => selectedModelRows?.map((row) => row.original) ?? [],
|
|
1384
|
+
[selectedModelRows],
|
|
1385
|
+
)
|
|
1386
|
+
// `bulkActions` is the caller's function of the selection, and calling it inline rebuilt every
|
|
1387
|
+
// action object on every render of the table — new `onClick` identities into the whole bar, for a
|
|
1388
|
+
// selection that had not changed.
|
|
1389
|
+
const bulkBarActions = useMemo(
|
|
1390
|
+
() =>
|
|
1391
|
+
bulkActions === undefined || selectedRows.length === 0
|
|
1392
|
+
? undefined
|
|
1393
|
+
: bulkActions(selectedRows),
|
|
1394
|
+
[bulkActions, selectedRows],
|
|
1395
|
+
)
|
|
898
1396
|
const headerGroups = enablePinning ? getOrderedHeaderGroups(table) : table.getHeaderGroups()
|
|
899
1397
|
// The rows actually rendered, not `data` — a filter or a page index that matches nothing leaves
|
|
900
1398
|
// `data` non-empty while the body has nothing in it, and a blank body reads as a broken table.
|
|
901
1399
|
const rows = table.getRowModel().rows
|
|
902
1400
|
|
|
1401
|
+
// A capped body or a horizontal floor both need the same native scroll node. Everything else —
|
|
1402
|
+
// pinning included — either takes that same container (the uncapped, non-sticky default) or the
|
|
1403
|
+
// measured wrapper below; there is no third overflow box.
|
|
1404
|
+
const scrolls = maxHeight !== undefined || minWidth !== undefined
|
|
1405
|
+
// `stickyHeaderOffset` is a WINDOW-scroll concept — the height of whatever fixed chrome the page
|
|
1406
|
+
// scrolls under (the AppShell header plus `PageBar` row 2). Inside the scroll container it is
|
|
1407
|
+
// always wrong: that box is the sticky header's own scrollport, so the offset parks the `<thead>`
|
|
1408
|
+
// that many pixels DOWN INSIDE the body rather than above it. MEASURED on `/data-stress`
|
|
1409
|
+
// (`stickyHeader` + `minWidth`, no `maxHeight`): `calc(48px + 46px)` put the header at y=339 with
|
|
1410
|
+
// row 1 at y=284 and row 2 at y=327 — the header painted over row 2 at initial scroll, at 1440x900
|
|
1411
|
+
// and 390x844 alike. `overflow-x: auto` computes `overflow-y` to `auto` too, so the container is a
|
|
1412
|
+
// scrollport even with no `maxHeight` and the header never scrolls back into place. The scroller's
|
|
1413
|
+
// own top edge is the only correct anchor here, so the offset is dropped rather than honoured.
|
|
1414
|
+
//
|
|
1415
|
+
// THE ONE SHAPE WHOSE OVERFLOW IS MEASURED, NOT DECLARED. Every other table renders inside a
|
|
1416
|
+
// `Table.ScrollContainer` (see the render branch below), because a bare `<table>` sizes to its
|
|
1417
|
+
// own min-content and a 5-column table is ~450px wide — it widened the whole page at 390px, which
|
|
1418
|
+
// is the defect `tests/layout/no-horizontal-overflow.layout.test.ts` guards. A page-scrolled
|
|
1419
|
+
// sticky header cannot take that container unconditionally, so it takes the wrapper
|
|
1420
|
+
// `useMeasuredContainment` drives instead: bare while the table fits, `overflow-x: auto` once it
|
|
1421
|
+
// does not. Its docblock carries the full accounting.
|
|
1422
|
+
const pageStickyHeader = !scrolls && stickyHeader === true
|
|
1423
|
+
const containment = useMeasuredContainment(pageStickyHeader)
|
|
1424
|
+
const resolvedStickyHeaderOffset = scrolls ? undefined : stickyHeaderOffset
|
|
1425
|
+
|
|
903
1426
|
const tableNode = (
|
|
904
1427
|
<Table
|
|
1428
|
+
className={cx(classNames?.table)}
|
|
905
1429
|
{...(striped !== undefined && { striped })}
|
|
906
1430
|
{...(highlightOnHover !== undefined && { highlightOnHover })}
|
|
907
1431
|
{...(stickyHeader !== undefined && { stickyHeader })}
|
|
908
|
-
{...(
|
|
1432
|
+
{...(resolvedStickyHeaderOffset !== undefined && {
|
|
1433
|
+
stickyHeaderOffset: resolvedStickyHeaderOffset,
|
|
1434
|
+
})}
|
|
909
1435
|
{...(verticalSpacing !== undefined && { verticalSpacing })}
|
|
910
1436
|
{...(horizontalSpacing !== undefined && { horizontalSpacing })}
|
|
911
1437
|
{...(withRowBorders !== undefined && { withRowBorders })}
|
|
@@ -962,10 +1488,25 @@ export function BasaltDataTable<T>({
|
|
|
962
1488
|
))}
|
|
963
1489
|
</Table.Thead>
|
|
964
1490
|
<Table.Tbody>
|
|
965
|
-
{
|
|
1491
|
+
{queryError !== undefined ? (
|
|
1492
|
+
// The branch the table did not have (components audit #3). It spans every rendered
|
|
1493
|
+
// column and carries the query's OWN `refetch` behind Retry, so the failure is reported
|
|
1494
|
+
// where the rows would have been — not as an empty table saying nothing failed.
|
|
1495
|
+
<Table.Tr>
|
|
1496
|
+
<Table.Td colSpan={columnCount}>
|
|
1497
|
+
<ErrorState
|
|
1498
|
+
error={queryError.error}
|
|
1499
|
+
title="Could not load"
|
|
1500
|
+
tier="section"
|
|
1501
|
+
retrying={queryError.fetchStatus === 'fetching'}
|
|
1502
|
+
onRetry={() => void queryError.refetch()}
|
|
1503
|
+
/>
|
|
1504
|
+
</Table.Td>
|
|
1505
|
+
</Table.Tr>
|
|
1506
|
+
) : showSkeleton ? (
|
|
966
1507
|
Array.from({ length: skeletonRows }, (_, rowIndex) => (
|
|
967
1508
|
<Table.Tr key={`skeleton-${rowIndex}`}>
|
|
968
|
-
{
|
|
1509
|
+
{Array.from({ length: columnCount }, (__, colIndex) => (
|
|
969
1510
|
<Table.Td key={`skeleton-${rowIndex}-${colIndex}`}>
|
|
970
1511
|
<Skeleton height={16} radius="sm" />
|
|
971
1512
|
</Table.Td>
|
|
@@ -974,7 +1515,7 @@ export function BasaltDataTable<T>({
|
|
|
974
1515
|
))
|
|
975
1516
|
) : rows.length === 0 ? (
|
|
976
1517
|
<Table.Tr>
|
|
977
|
-
<Table.Td colSpan={
|
|
1518
|
+
<Table.Td colSpan={columnCount}>
|
|
978
1519
|
{emptyState ?? (
|
|
979
1520
|
<Text c="dimmed" ta="center" size="sm" py="sm">
|
|
980
1521
|
No data to display.
|
|
@@ -992,7 +1533,28 @@ export function BasaltDataTable<T>({
|
|
|
992
1533
|
]
|
|
993
1534
|
: row.getVisibleCells()
|
|
994
1535
|
return (
|
|
995
|
-
<Table.Tr
|
|
1536
|
+
<Table.Tr
|
|
1537
|
+
key={row.id}
|
|
1538
|
+
{...(enableRowSelection && row.getIsSelected() && { 'data-selected': true })}
|
|
1539
|
+
{...(onRowActivate !== undefined && {
|
|
1540
|
+
className: classes.activatable,
|
|
1541
|
+
'data-activatable': true,
|
|
1542
|
+
tabIndex: 0,
|
|
1543
|
+
onClick: () => onRowActivate(row.original),
|
|
1544
|
+
// Enter only. Space is the browser's own page-scroll on a focused non-button, and
|
|
1545
|
+
// stealing it from a keyboard reader moving down a long table costs more than the
|
|
1546
|
+
// second activation key buys.
|
|
1547
|
+
onKeyDown: (event: ReactKeyboardEvent<HTMLTableRowElement>) => {
|
|
1548
|
+
if (event.key !== 'Enter') return
|
|
1549
|
+
// Only the ROW's own Enter. A cell may hold a button, a link or the selection
|
|
1550
|
+
// checkbox, and keydown bubbles — so without this an Enter on a nested control
|
|
1551
|
+
// fired that control AND opened the row's detail behind it.
|
|
1552
|
+
if (event.target !== event.currentTarget) return
|
|
1553
|
+
event.preventDefault()
|
|
1554
|
+
onRowActivate(row.original)
|
|
1555
|
+
},
|
|
1556
|
+
})}
|
|
1557
|
+
>
|
|
996
1558
|
{cells.map((cell) => {
|
|
997
1559
|
const pinnedStyle = enablePinning
|
|
998
1560
|
? getPinnedCellStyle(table, cell.column)
|
|
@@ -1022,10 +1584,6 @@ export function BasaltDataTable<T>({
|
|
|
1022
1584
|
</Table>
|
|
1023
1585
|
)
|
|
1024
1586
|
|
|
1025
|
-
// A capped body or a horizontal floor both need the same native scroll node; pinning keeps its
|
|
1026
|
-
// simpler overflow-x Box when neither is set.
|
|
1027
|
-
const scrolls = maxHeight !== undefined || minWidth !== undefined
|
|
1028
|
-
|
|
1029
1587
|
const paginationState = table.getState().pagination
|
|
1030
1588
|
// The CURRENT page size is always an option, even when it is not in `pageSizeOptions`. Mantine's
|
|
1031
1589
|
// Select renders EMPTY when its `value` matches no row, so a table opened at
|
|
@@ -1039,7 +1597,7 @@ export function BasaltDataTable<T>({
|
|
|
1039
1597
|
const rangeEnd = Math.min((paginationState.pageIndex + 1) * paginationState.pageSize, total)
|
|
1040
1598
|
|
|
1041
1599
|
return (
|
|
1042
|
-
|
|
1600
|
+
<div className={cx(classNames?.root, className)} {...(style !== undefined && { style })}>
|
|
1043
1601
|
{/*
|
|
1044
1602
|
* ONE header row: the `WidgetHeader` (title · count) on the left, the toolbar right-aligned in
|
|
1045
1603
|
* the SAME row. It was two stacked rows with `mb="xs"` on each, so a titled table with search
|
|
@@ -1066,11 +1624,17 @@ export function BasaltDataTable<T>({
|
|
|
1066
1624
|
) : null}
|
|
1067
1625
|
{showToolbar && (
|
|
1068
1626
|
<CtlSlot>
|
|
1069
|
-
<Group
|
|
1627
|
+
<Group
|
|
1628
|
+
className={cx(classNames?.toolbar)}
|
|
1629
|
+
gap="xs"
|
|
1630
|
+
wrap="wrap"
|
|
1631
|
+
align="center"
|
|
1632
|
+
style={TABLE_TOOLBAR_STYLE}
|
|
1633
|
+
>
|
|
1070
1634
|
{showSearch && (
|
|
1071
1635
|
<TextInput
|
|
1072
1636
|
radius="md"
|
|
1073
|
-
|
|
1637
|
+
style={TABLE_SEARCH_STYLE}
|
|
1074
1638
|
placeholder={globalFilterPlaceholder}
|
|
1075
1639
|
leftSection={searchIcon}
|
|
1076
1640
|
value={globalFilter}
|
|
@@ -1080,7 +1644,7 @@ export function BasaltDataTable<T>({
|
|
|
1080
1644
|
{showFacets && (
|
|
1081
1645
|
<FilterSet>
|
|
1082
1646
|
{facets?.map((facet) => {
|
|
1083
|
-
const column = table
|
|
1647
|
+
const column = resolveFacetColumn(table, facet)
|
|
1084
1648
|
if (!column) return null
|
|
1085
1649
|
return facet.multiple ? (
|
|
1086
1650
|
<MultiSelectFilter
|
|
@@ -1098,13 +1662,27 @@ export function BasaltDataTable<T>({
|
|
|
1098
1662
|
})}
|
|
1099
1663
|
</FilterSet>
|
|
1100
1664
|
)}
|
|
1101
|
-
{actions}
|
|
1665
|
+
{actions !== undefined && <BarActionSlot actions={actions} />}
|
|
1102
1666
|
</Group>
|
|
1103
1667
|
</CtlSlot>
|
|
1104
1668
|
)}
|
|
1105
1669
|
</Group>
|
|
1106
1670
|
)}
|
|
1107
|
-
{
|
|
1671
|
+
{bulkBarActions !== undefined && (
|
|
1672
|
+
<BulkActionBar count={selectedRows.length} actions={bulkBarActions} />
|
|
1673
|
+
)}
|
|
1674
|
+
{pageStickyHeader ? (
|
|
1675
|
+
// Pinning takes this wrapper too — a pinned column's offsets are `position: sticky` on the
|
|
1676
|
+
// cells and need no overflow box of their own, so the `overflow-x: auto` Box that used to
|
|
1677
|
+
// sit here was the exact inert-sticky defect this shape exists to avoid.
|
|
1678
|
+
<div
|
|
1679
|
+
ref={containment.wrapperRef}
|
|
1680
|
+
className={classes.containment}
|
|
1681
|
+
data-contained={containment.contained ? 'true' : 'false'}
|
|
1682
|
+
>
|
|
1683
|
+
{tableNode}
|
|
1684
|
+
</div>
|
|
1685
|
+
) : (
|
|
1108
1686
|
<Table.ScrollContainer
|
|
1109
1687
|
type="native"
|
|
1110
1688
|
minWidth={minWidth ?? 0}
|
|
@@ -1112,13 +1690,16 @@ export function BasaltDataTable<T>({
|
|
|
1112
1690
|
>
|
|
1113
1691
|
{tableNode}
|
|
1114
1692
|
</Table.ScrollContainer>
|
|
1115
|
-
) : enablePinning ? (
|
|
1116
|
-
<Box style={{ overflowX: 'auto' }}>{tableNode}</Box>
|
|
1117
|
-
) : (
|
|
1118
|
-
tableNode
|
|
1119
1693
|
)}
|
|
1120
1694
|
{enablePagination && (
|
|
1121
|
-
<Group
|
|
1695
|
+
<Group
|
|
1696
|
+
className={cx(classNames?.footer)}
|
|
1697
|
+
justify="space-between"
|
|
1698
|
+
mt="xs"
|
|
1699
|
+
wrap="wrap"
|
|
1700
|
+
gap="xs"
|
|
1701
|
+
align="center"
|
|
1702
|
+
>
|
|
1122
1703
|
<Text style={RANGE_LABEL_STYLE}>
|
|
1123
1704
|
Showing {rangeStart}–{rangeEnd}
|
|
1124
1705
|
{totalIsAuthoritative ? ` of ${total}` : ''}
|
|
@@ -1143,6 +1724,6 @@ export function BasaltDataTable<T>({
|
|
|
1143
1724
|
</Group>
|
|
1144
1725
|
</Group>
|
|
1145
1726
|
)}
|
|
1146
|
-
|
|
1727
|
+
</div>
|
|
1147
1728
|
)
|
|
1148
1729
|
}
|