@eifi1/ui-kit 0.48.1 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -13
- package/dist/admin/admin-action-confirm.js +1 -1
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.js +5 -1
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/user-roster.d.ts +11 -5
- package/dist/admin/user-roster.js +15 -4
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/accept-invitation.js +2 -2
- package/dist/auth/accept-invitation.js.map +1 -1
- package/dist/auth/not-found-page.js +1 -1
- package/dist/auth/not-found-page.js.map +1 -1
- package/dist/auth/reset-password-form.js +2 -2
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/verify-email.js +2 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/plan-card.js +2 -2
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/components/breadcrumbs.d.ts +19 -1
- package/dist/components/breadcrumbs.js +11 -4
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-look.d.ts +37 -7
- package/dist/components/button-look.js +33 -6
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +4 -1
- package/dist/components/button.js +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -1
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chart-edge.d.ts +10 -0
- package/dist/components/chart-edge.js +57 -0
- package/dist/components/chart-edge.js.map +1 -0
- package/dist/components/chart.js +2 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/chip.d.ts +8 -1
- package/dist/components/chip.js +4 -1
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-context.d.ts +5 -0
- package/dist/components/confirm-context.js +7 -0
- package/dist/components/confirm-context.js.map +1 -0
- package/dist/components/confirm-dialog.js +3 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +2 -2
- package/dist/components/danger-confirm.js +1 -1
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +8 -1
- package/dist/components/data-table-fit.js +28 -4
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-groups.d.ts +3 -1
- package/dist/components/data-table-groups.js +11 -5
- package/dist/components/data-table-groups.js.map +1 -1
- package/dist/components/data-table-head.d.ts +8 -1
- package/dist/components/data-table-head.js +11 -3
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-leading-row.d.ts +3 -1
- package/dist/components/data-table-leading-row.js +2 -1
- package/dist/components/data-table-leading-row.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +48 -2
- package/dist/components/data-table-mobile.js +87 -10
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -1
- package/dist/components/data-table-row-actions.js +9 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +84 -0
- package/dist/components/data-table-row.js +261 -0
- package/dist/components/data-table-row.js.map +1 -0
- package/dist/components/data-table-selection.d.ts +3 -0
- package/dist/components/data-table-selection.js +12 -1
- package/dist/components/data-table-selection.js.map +1 -1
- package/dist/components/data-table-virtual.d.ts +98 -0
- package/dist/components/data-table-virtual.js +369 -0
- package/dist/components/data-table-virtual.js.map +1 -0
- package/dist/components/data-table.d.ts +74 -2
- package/dist/components/data-table.js +241 -221
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/floating-panel.d.ts +36 -13
- package/dist/components/floating-panel.js +27 -7
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/form-actions.js +1 -1
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/icon-button.d.ts +11 -0
- package/dist/components/icon-button.js +7 -5
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/nav-pills.d.ts +8 -1
- package/dist/components/nav-pills.js +61 -38
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/page-header-context.d.ts +8 -0
- package/dist/components/page-header-context.js +7 -0
- package/dist/components/page-header-context.js.map +1 -0
- package/dist/components/page-header.d.ts +14 -2
- package/dist/components/page-header.js +10 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/pie-chart.js +2 -1
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/progress-bar.js +5 -4
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.js +1 -1
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/row-actions-labels.js +3 -1
- package/dist/components/row-actions-labels.js.map +1 -1
- package/dist/components/row-actions.d.ts +43 -9
- package/dist/components/row-actions.js +78 -15
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +37 -0
- package/dist/components/section-nav.js +33 -0
- package/dist/components/section-nav.js.map +1 -0
- package/dist/components/series-chart.js +8 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +1 -1
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/signature-pad.js +1 -1
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/sparkline.js +2 -1
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.js +14 -7
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +5 -1
- package/dist/components/status-dot.js +4 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review-editor.js +2 -2
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap.d.ts +5 -2
- package/dist/components/treemap.js +26 -6
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.js +37 -6
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +1 -0
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-banner.js +1 -1
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/demo/demo-ended.js +1 -1
- package/dist/demo/demo-ended.js.map +1 -1
- package/dist/demo/demo-start.js +1 -1
- package/dist/demo/demo-start.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-inbox.js +1 -1
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/hooks/use-anchored-panel.js +2 -1
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +9 -1
- package/dist/hooks/use-dismiss.js +25 -5
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-document-title.d.ts +14 -0
- package/dist/hooks/use-document-title.js +22 -0
- package/dist/hooks/use-document-title.js.map +1 -0
- package/dist/i18n/german.js +6 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +31 -1
- package/dist/i18n/kit-labels.js +43 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +6 -2
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +6 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +6 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +6 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +6 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +6 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +2 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +1 -0
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.js +2 -2
- package/dist/landing/landing-actions.js.map +1 -1
- package/dist/landing/public-header.js +2 -2
- package/dist/landing/public-header.js.map +1 -1
- package/dist/lib/clipboard-minus.d.ts +27 -0
- package/dist/lib/clipboard-minus.js +63 -0
- package/dist/lib/clipboard-minus.js.map +1 -0
- package/dist/lib/document-title.d.ts +17 -0
- package/dist/lib/document-title.js +29 -0
- package/dist/lib/document-title.js.map +1 -0
- package/dist/lib/float-clearance.js +8 -2
- package/dist/lib/float-clearance.js.map +1 -1
- package/dist/lib/format.d.ts +35 -10
- package/dist/lib/format.js +55 -11
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/minus.d.ts +22 -0
- package/dist/lib/minus.js +14 -0
- package/dist/lib/minus.js.map +1 -0
- package/dist/lib/status-tone.d.ts +30 -0
- package/dist/lib/status-tone.js +17 -0
- package/dist/lib/status-tone.js.map +1 -0
- package/dist/settings/settings-layout.js +3 -2
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +1 -1
- package/dist/shell/account-menu-language.js +9 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/app-shell-labels.js +2 -1
- package/dist/shell/app-shell-labels.js.map +1 -1
- package/dist/shell/app-shell.d.ts +58 -16
- package/dist/shell/app-shell.js +158 -44
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/language-code.d.ts +3 -0
- package/dist/shell/language-code.js +8 -0
- package/dist/shell/language-code.js.map +1 -0
- package/dist/shell/topbar-controls.d.ts +14 -6
- package/dist/shell/topbar-controls.js +14 -17
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +1 -1
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +82 -15
- package/dist/theme/chart-palette.js +51 -24
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +9 -0
- package/dist/theme/palette-presets.js +77 -62
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/tour/tour.js +1 -1
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +3 -3
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/admin-action-confirm.tsx +1 -1
- package/src/admin/invitations-panel.tsx +7 -3
- package/src/admin/user-roster.tsx +23 -6
- package/src/auth/accept-invitation.tsx +3 -3
- package/src/auth/not-found-page.tsx +1 -1
- package/src/auth/reset-password-form.tsx +3 -3
- package/src/auth/verify-email.tsx +3 -3
- package/src/billing/plan-card.tsx +2 -2
- package/src/components/breadcrumbs.tsx +32 -4
- package/src/components/button-look.ts +86 -15
- package/src/components/button.tsx +6 -3
- package/src/components/calendar-heatmap.tsx +2 -1
- package/src/components/chart-edge.ts +73 -0
- package/src/components/chart.tsx +3 -1
- package/src/components/chip.tsx +10 -1
- package/src/components/confirm-context.ts +7 -0
- package/src/components/confirm-dialog.tsx +3 -3
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-fit.ts +58 -8
- package/src/components/data-table-groups.tsx +15 -4
- package/src/components/data-table-head.tsx +25 -2
- package/src/components/data-table-leading-row.tsx +4 -1
- package/src/components/data-table-mobile.tsx +162 -11
- package/src/components/data-table-row-actions.tsx +12 -3
- package/src/components/data-table-row.tsx +485 -0
- package/src/components/data-table-selection.ts +17 -1
- package/src/components/data-table-virtual.ts +547 -0
- package/src/components/data-table.tsx +367 -326
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/floating-panel.tsx +67 -16
- package/src/components/form-actions.tsx +2 -2
- package/src/components/icon-button.tsx +21 -8
- package/src/components/nav-pills.tsx +29 -2
- package/src/components/page-header-context.ts +8 -0
- package/src/components/page-header.tsx +34 -2
- package/src/components/pie-chart.tsx +2 -1
- package/src/components/progress-bar.tsx +6 -3
- package/src/components/reauth-dialog.tsx +1 -1
- package/src/components/row-actions-labels.ts +2 -0
- package/src/components/row-actions.tsx +154 -33
- package/src/components/section-nav.tsx +65 -0
- package/src/components/series-chart.tsx +12 -3
- package/src/components/share-card.tsx +1 -1
- package/src/components/signature-pad.tsx +1 -1
- package/src/components/sparkline.tsx +2 -1
- package/src/components/stat-tile.tsx +19 -7
- package/src/components/status-dot.tsx +7 -1
- package/src/components/tooltip.tsx +3 -1
- package/src/components/translation-review-editor.tsx +2 -2
- package/src/components/translation-review.tsx +2 -2
- package/src/components/treemap.tsx +34 -8
- package/src/components/use-table-state.ts +63 -8
- package/src/data-table.ts +5 -3
- package/src/demo/demo-banner.tsx +1 -1
- package/src/demo/demo-ended.tsx +1 -1
- package/src/demo/demo-start.tsx +1 -1
- package/src/feedback/feedback-inbox.tsx +1 -1
- package/src/hooks/use-anchored-panel.ts +9 -1
- package/src/hooks/use-dismiss.ts +43 -4
- package/src/hooks/use-document-title.ts +31 -0
- package/src/i18n/german.ts +4 -0
- package/src/i18n/kit-labels.tsx +76 -1
- package/src/i18n/locales/es.ts +4 -0
- package/src/i18n/locales/fr.ts +4 -0
- package/src/i18n/locales/hu.ts +4 -0
- package/src/i18n/locales/it.ts +4 -0
- package/src/i18n/locales/zh.ts +4 -0
- package/src/i18n/review.ts +2 -0
- package/src/i18n/shared-labels.ts +1 -0
- package/src/index.ts +13 -0
- package/src/landing/landing-actions.tsx +2 -2
- package/src/landing/public-header.tsx +2 -2
- package/src/lib/clipboard-minus.ts +94 -0
- package/src/lib/document-title.ts +41 -0
- package/src/lib/float-clearance.ts +19 -3
- package/src/lib/format.ts +115 -30
- package/src/lib/minus.ts +40 -0
- package/src/lib/status-tone.ts +43 -0
- package/src/settings/settings-layout.tsx +3 -2
- package/src/shell/account-menu-language.tsx +14 -2
- package/src/shell/app-shell-labels.ts +1 -0
- package/src/shell/app-shell.tsx +298 -69
- package/src/shell/language-code.ts +5 -0
- package/src/shell/topbar-controls.tsx +35 -15
- package/src/testing/css-variables.generated.ts +6 -6
- package/src/theme/chart-palette.ts +102 -20
- package/src/theme/palette-presets.ts +121 -84
- package/src/tour/tour.tsx +1 -1
- package/src/wizard/stepper-nav.tsx +4 -4
- package/tokens.css +28 -18
|
@@ -15,7 +15,9 @@ import type { DataTableColumn } from "./data-table";
|
|
|
15
15
|
// room for them (its need re-measured, plus the column's own header width from when
|
|
16
16
|
// it was left out), so narrower content (a filter, another page) counts as much as a
|
|
17
17
|
// wider scroller. The scroller's own width, through a ResizeObserver: a sidebar, a
|
|
18
|
-
// narrower window, the Columns drawer and Large text all count the same.
|
|
18
|
+
// narrower window, the Columns drawer and Large text all count the same. A column
|
|
19
|
+
// whose return the very next measure undoes stays out until the table has more room
|
|
20
|
+
// for it than it had then (0.48.3, keksdose B2: see the bring-back in `measure`).
|
|
19
21
|
|
|
20
22
|
/** The least a `grow` column keeps before the table starts leaving columns out: about
|
|
21
23
|
* a sentence of a title. In rem, so Large text asks for more. */
|
|
@@ -65,6 +67,7 @@ export function useColumnFit<T>({
|
|
|
65
67
|
headRefs,
|
|
66
68
|
scroller,
|
|
67
69
|
enabled,
|
|
70
|
+
content,
|
|
68
71
|
}: {
|
|
69
72
|
columns: DataTableColumn<T>[];
|
|
70
73
|
/** The user's hidden columns: never candidates (they are out already). */
|
|
@@ -74,6 +77,13 @@ export function useColumnFit<T>({
|
|
|
74
77
|
scroller: () => HTMLElement | null;
|
|
75
78
|
/** The wide layout. On a phone (the card list) nothing is left out for width. */
|
|
76
79
|
enabled: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* What can change the columns' widths besides the scroller (0.48.2): the rows on
|
|
82
|
+
* screen, the columns, the stored widths, the text size. Compared item by item after
|
|
83
|
+
* each render, and the table is measured only when one changed — a selection tick no
|
|
84
|
+
* longer forces a layout of the whole table. Without it, every render measures.
|
|
85
|
+
*/
|
|
86
|
+
content?: readonly unknown[];
|
|
77
87
|
}): {
|
|
78
88
|
/** The columns left out for width now — empty while off. */
|
|
79
89
|
autoHidden: Set<string>;
|
|
@@ -86,6 +96,13 @@ export function useColumnFit<T>({
|
|
|
86
96
|
// table's need as it is now, plus this, fits the scroller (keksdose S3, 0.48: not the
|
|
87
97
|
// whole table's need at hide time, which went stale when the content narrowed).
|
|
88
98
|
const ownWidth = useRef(new Map<string, number>());
|
|
99
|
+
// A column whose return the very next measure undid, at the same scroller width: the
|
|
100
|
+
// room the table had for it when it came back (keksdose B2, 0.48.3). It stays out
|
|
101
|
+
// until the table has more. Dropped once the column is no longer left out for width.
|
|
102
|
+
const stuck = useRef(new Map<string, number>());
|
|
103
|
+
// What the last measure brought back, each column with the room it had then, and at
|
|
104
|
+
// which scroller width: read (and cleared) by the measure right after it.
|
|
105
|
+
const lastShown = useRef<{ avail: number; rooms: Map<string, number> } | null>(null);
|
|
89
106
|
|
|
90
107
|
const hasFit = columns.some((c) => c.fitPriority != null);
|
|
91
108
|
const active = enabled && hasFit;
|
|
@@ -110,6 +127,10 @@ export function useColumnFit<T>({
|
|
|
110
127
|
|
|
111
128
|
const measure = useCallback(() => {
|
|
112
129
|
const { order, current, growKey, active, scroller } = latest.current;
|
|
130
|
+
// Only the measure right after a bring-back can undo it.
|
|
131
|
+
const flip = lastShown.current;
|
|
132
|
+
lastShown.current = null;
|
|
133
|
+
for (const k of stuck.current.keys()) if (!current.includes(k)) stuck.current.delete(k);
|
|
113
134
|
const el = scroller();
|
|
114
135
|
if (!active || !el) return;
|
|
115
136
|
const table = el.querySelector("table");
|
|
@@ -142,25 +163,54 @@ export function useColumnFit<T>({
|
|
|
142
163
|
need -= w;
|
|
143
164
|
next.push(col.key);
|
|
144
165
|
}
|
|
145
|
-
if (next.length
|
|
166
|
+
if (next.length === current.length) return;
|
|
167
|
+
// The last measure brought these back at this same width, and the table cannot
|
|
168
|
+
// hold them: their header width fell short of what they cost drawn (see below).
|
|
169
|
+
if (flip && Math.abs(flip.avail - avail) <= 0.5)
|
|
170
|
+
for (const k of next.slice(current.length)) {
|
|
171
|
+
const room = flip.rooms.get(k);
|
|
172
|
+
if (room != null) stuck.current.set(k, room);
|
|
173
|
+
}
|
|
174
|
+
setHidden(next);
|
|
146
175
|
return;
|
|
147
176
|
}
|
|
148
177
|
// Bring back, the last left out first, while the table as it is now has room for
|
|
149
|
-
// them
|
|
150
|
-
//
|
|
178
|
+
// them by their recorded header width. A column that costs more drawn than that is
|
|
179
|
+
// left out again by the next measure, before paint. That alone does not settle it:
|
|
180
|
+
// the re-hide records its header's width again, and the header can measure the same
|
|
181
|
+
// while the column costs more (`virtualize`'s locked widths, keksdose B2, 0.48.3: the
|
|
182
|
+
// drawn column cost the table ~80–110px more than its header), so it came back, went, came back … until React's
|
|
183
|
+
// update limit (#185). So a column whose return the next measure undid, at the same
|
|
184
|
+
// scroller width, waits for more room than it had then: a wider scroller or narrower
|
|
185
|
+
// content. Whatever the error in its width, that is one invisible flip per room it is
|
|
186
|
+
// tried at.
|
|
151
187
|
let n = current.length;
|
|
188
|
+
const rooms = new Map<string, number>();
|
|
152
189
|
while (n > 0) {
|
|
153
|
-
const
|
|
190
|
+
const k = current[n - 1];
|
|
191
|
+
const w = ownWidth.current.get(k);
|
|
154
192
|
if (w == null || need + w > avail + 0.5) break;
|
|
193
|
+
const room = stuck.current.get(k);
|
|
194
|
+
if (room != null && avail - need <= room + 0.5) break;
|
|
195
|
+
rooms.set(k, avail - need);
|
|
155
196
|
need += w;
|
|
156
197
|
n--;
|
|
157
198
|
}
|
|
158
|
-
if (n
|
|
199
|
+
if (n === current.length) return;
|
|
200
|
+
lastShown.current = { avail, rooms };
|
|
201
|
+
setHidden(current.slice(0, n));
|
|
159
202
|
}, [headRefs]);
|
|
160
203
|
|
|
161
|
-
// After
|
|
162
|
-
// Before paint, so a column the table cannot hold is never
|
|
204
|
+
// After a render that changed what is drawn: rows, a filter or a page can widen a
|
|
205
|
+
// column as much as a resize. Before paint, so a column the table cannot hold is never
|
|
206
|
+
// drawn first. A render that changed none of it (a ticked box, a focused row) skips the
|
|
207
|
+
// measure, which forces a layout of the whole table (0.48.2, keksdose dev #632).
|
|
208
|
+
const lastContent = useRef<readonly unknown[] | null>(null);
|
|
163
209
|
useLayoutEffect(() => {
|
|
210
|
+
const now = content ? [...content, order, current, growKey, active] : null;
|
|
211
|
+
const prev = lastContent.current;
|
|
212
|
+
lastContent.current = now;
|
|
213
|
+
if (now && prev && prev.length === now.length && prev.every((v, i) => Object.is(v, now[i]))) return;
|
|
164
214
|
measure();
|
|
165
215
|
});
|
|
166
216
|
|
|
@@ -94,19 +94,27 @@ export function useGroupBandTops(
|
|
|
94
94
|
useLayoutEffect(() => {
|
|
95
95
|
const table = tableRef.current;
|
|
96
96
|
if (!enabled || !table) return;
|
|
97
|
-
|
|
97
|
+
// Looked up afresh on each measure: under `virtualize` (0.48.2) the first band can
|
|
98
|
+
// leave the DOM as the rows scroll, and a detached cell measures 0 — the bands would
|
|
99
|
+
// stack on one another. A band missing for now keeps the height it had.
|
|
100
|
+
const parts = () => [
|
|
98
101
|
table.tHead,
|
|
99
102
|
table.querySelector<HTMLElement>("[data-leading-row='sticky']"),
|
|
100
103
|
table.querySelector<HTMLElement>("th[data-group-band='1']"),
|
|
101
104
|
];
|
|
105
|
+
let bandHeight = 0;
|
|
102
106
|
const measure = () => {
|
|
103
|
-
const [
|
|
107
|
+
const [headEl, leadEl, bandEl] = parts();
|
|
108
|
+
const head = headEl ? headEl.getBoundingClientRect().height : 0;
|
|
109
|
+
const lead = leadEl ? leadEl.getBoundingClientRect().height : 0;
|
|
110
|
+
if (bandEl) bandHeight = bandEl.getBoundingClientRect().height;
|
|
111
|
+
const band = bandHeight;
|
|
104
112
|
setTops((prev) => (prev[0] === head + lead && prev[1] === head + lead + band ? prev : [head + lead, head + lead + band]));
|
|
105
113
|
};
|
|
106
114
|
measure();
|
|
107
115
|
if (typeof ResizeObserver === "undefined") return;
|
|
108
116
|
const observer = new ResizeObserver(measure);
|
|
109
|
-
for (const el of parts) if (el) observer.observe(el);
|
|
117
|
+
for (const el of parts()) if (el) observer.observe(el);
|
|
110
118
|
return () => observer.disconnect();
|
|
111
119
|
}, [tableRef, enabled, signature]);
|
|
112
120
|
return tops;
|
|
@@ -127,6 +135,7 @@ export function DataTableGroupBand({
|
|
|
127
135
|
compact,
|
|
128
136
|
label,
|
|
129
137
|
end,
|
|
138
|
+
ariaRowIndex,
|
|
130
139
|
}: {
|
|
131
140
|
level: 1 | 2;
|
|
132
141
|
colSpan: number;
|
|
@@ -134,9 +143,11 @@ export function DataTableGroupBand({
|
|
|
134
143
|
compact: boolean;
|
|
135
144
|
label: ReactNode;
|
|
136
145
|
end: ReactNode;
|
|
146
|
+
/** Under `virtualize` (0.48.2). */
|
|
147
|
+
ariaRowIndex?: number;
|
|
137
148
|
}) {
|
|
138
149
|
return (
|
|
139
|
-
<tr data-group-row={level}>
|
|
150
|
+
<tr data-group-row={level} aria-rowindex={ariaRowIndex}>
|
|
140
151
|
<th
|
|
141
152
|
scope="rowgroup"
|
|
142
153
|
colSpan={colSpan}
|
|
@@ -45,6 +45,9 @@ export function DataTableHead<T>({
|
|
|
45
45
|
startResize,
|
|
46
46
|
moveResize,
|
|
47
47
|
finishResize,
|
|
48
|
+
ariaRowIndex,
|
|
49
|
+
lockedWidths,
|
|
50
|
+
selectionLockKey,
|
|
48
51
|
}: {
|
|
49
52
|
visibleColumns: DataTableColumn<T>[];
|
|
50
53
|
selection: DataTableProps<T>["selection"];
|
|
@@ -71,13 +74,25 @@ export function DataTableHead<T>({
|
|
|
71
74
|
startResize: (e: React.PointerEvent<HTMLElement>, key: string) => void;
|
|
72
75
|
moveResize: (e: React.PointerEvent<HTMLElement>) => void;
|
|
73
76
|
finishResize: (e: React.PointerEvent<HTMLElement>, commit: boolean) => void;
|
|
77
|
+
/** Under `virtualize` (0.48.2): 1. */
|
|
78
|
+
ariaRowIndex?: number;
|
|
79
|
+
/** Under `virtualize` (0.48.2): each column's locked width, as the header cell's
|
|
80
|
+
* `min-width` — a `<col>` width alone gave way to a `grow` column's 100%. */
|
|
81
|
+
lockedWidths?: Record<string, number> | null;
|
|
82
|
+
/** The selection column's key in `lockedWidths`. */
|
|
83
|
+
selectionLockKey?: string;
|
|
74
84
|
}) {
|
|
75
85
|
return (
|
|
76
86
|
<thead className="bg-surface-2 text-secondary">
|
|
77
|
-
<tr className="text-start">
|
|
87
|
+
<tr className="text-start" aria-rowindex={ariaRowIndex}>
|
|
78
88
|
{selection && (
|
|
79
89
|
<th
|
|
80
90
|
scope="col"
|
|
91
|
+
style={
|
|
92
|
+
selectionLockKey && lockedWidths?.[selectionLockKey] !== undefined
|
|
93
|
+
? { minWidth: lockedWidths[selectionLockKey] }
|
|
94
|
+
: undefined
|
|
95
|
+
}
|
|
81
96
|
className={cn(
|
|
82
97
|
"sticky top-0 z-10 bg-surface-2 align-middle backdrop-blur-sm",
|
|
83
98
|
compact ? "w-8 px-2 py-1" : "w-10 px-3 py-2",
|
|
@@ -140,7 +155,15 @@ export function DataTableHead<T>({
|
|
|
140
155
|
: "none"
|
|
141
156
|
: undefined
|
|
142
157
|
}
|
|
143
|
-
style={
|
|
158
|
+
style={
|
|
159
|
+
ownHead.style || pinnedHead || lockedWidths?.[col.key] !== undefined
|
|
160
|
+
? {
|
|
161
|
+
...ownHead.style,
|
|
162
|
+
...(lockedWidths?.[col.key] !== undefined ? { minWidth: lockedWidths[col.key] } : null),
|
|
163
|
+
...pinnedHead,
|
|
164
|
+
}
|
|
165
|
+
: undefined
|
|
166
|
+
}
|
|
144
167
|
className={cn(
|
|
145
168
|
"relative font-medium align-middle whitespace-nowrap",
|
|
146
169
|
compact ? "px-2 py-1" : "px-3 py-2",
|
|
@@ -16,10 +16,13 @@ import { cn } from "../lib/cn";
|
|
|
16
16
|
export function DataTableLeadingRow({
|
|
17
17
|
colSpan,
|
|
18
18
|
sticky = false,
|
|
19
|
+
ariaRowIndex,
|
|
19
20
|
children,
|
|
20
21
|
}: {
|
|
21
22
|
colSpan: number;
|
|
22
23
|
sticky?: boolean;
|
|
24
|
+
/** Under `virtualize` (0.48.2). */
|
|
25
|
+
ariaRowIndex?: number;
|
|
23
26
|
children: ReactNode;
|
|
24
27
|
}) {
|
|
25
28
|
const cellRef = useRef<HTMLTableCellElement>(null);
|
|
@@ -36,7 +39,7 @@ export function DataTableLeadingRow({
|
|
|
36
39
|
return () => observer.disconnect();
|
|
37
40
|
}, [sticky]);
|
|
38
41
|
return (
|
|
39
|
-
<tr className={cn(!sticky && "border-t border-subtle")}>
|
|
42
|
+
<tr aria-rowindex={ariaRowIndex} className={cn(!sticky && "border-t border-subtle")}>
|
|
40
43
|
<td
|
|
41
44
|
ref={cellRef}
|
|
42
45
|
colSpan={colSpan}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fragment, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { Fragment, isValidElement, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, ReactNode } from "react";
|
|
3
3
|
import { ArrowDownWideNarrow, ArrowUpNarrowWide, ChevronDown, ChevronRight, Filter } from "lucide-react";
|
|
4
4
|
import { FOCUS_RING } from "./focus-ring";
|
|
@@ -13,6 +13,9 @@ import { FilterPopover } from "./data-table-filter-popover";
|
|
|
13
13
|
import { SwipeableRow, type SwipeAction } from "./swipeable-row";
|
|
14
14
|
import { DialogFrame } from "./dialog-frame";
|
|
15
15
|
import { Checkbox } from "./checkbox";
|
|
16
|
+
import { Chip } from "./chip";
|
|
17
|
+
import { BooleanMark } from "./data-table-cells";
|
|
18
|
+
import { EMPTY_FORMATTED_VALUE } from "../lib/format";
|
|
16
19
|
import { useLongPress } from "../hooks/use-long-press";
|
|
17
20
|
import type { DataTableLabels } from "./data-table-labels";
|
|
18
21
|
import { RowLink, cleanAttrs, columnLabel, fromOutside, fromOwnControl } from "./data-table-shared";
|
|
@@ -70,19 +73,21 @@ const CARD_NO_TRUNCATE =
|
|
|
70
73
|
* header's `sr-only` text (an actions column's), as every kit container must.
|
|
71
74
|
*/
|
|
72
75
|
export function StackedFields<T>({ columns, row, compact }: { columns: DataTableColumn<T>[]; row: T; compact: boolean }) {
|
|
76
|
+
const fields = filledFields(columns, row);
|
|
77
|
+
if (fields.length === 0) return null;
|
|
73
78
|
return (
|
|
74
79
|
<dl
|
|
75
80
|
data-card-fields="stacked"
|
|
76
81
|
className={cn("relative flex min-w-0 flex-col", compact ? "gap-1.5 text-xs" : "gap-2.5 text-sm", CARD_NO_TRUNCATE)}
|
|
77
82
|
>
|
|
78
|
-
{
|
|
83
|
+
{fields.map(({ col, value }) => (
|
|
79
84
|
<div key={col.key} className="min-w-0">
|
|
80
85
|
<dt className="text-xs uppercase tracking-wide text-muted">{col.header}</dt>
|
|
81
86
|
<dd
|
|
82
87
|
{...dataAttrsOf(col.cellProps?.(row))}
|
|
83
88
|
className="mt-0.5 min-w-0 break-words text-secondary"
|
|
84
89
|
>
|
|
85
|
-
{
|
|
90
|
+
{value}
|
|
86
91
|
</dd>
|
|
87
92
|
</div>
|
|
88
93
|
))}
|
|
@@ -90,6 +95,112 @@ export function StackedFields<T>({ columns, row, compact }: { columns: DataTable
|
|
|
90
95
|
);
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
/** The most fields the default phone card shows under its title (0.49 KIT-7). */
|
|
99
|
+
export const MOBILE_CARD_FIELDS = 3;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The default phone card's columns (0.49 KIT-7), out of the phone's columns (visible,
|
|
103
|
+
* not `mobileHidden`): the title — `mobile: "primary"` or `mobilePrimary`, else the
|
|
104
|
+
* first that is not a status — the `role: "status"` columns for beside it, at most
|
|
105
|
+
* {@link MOBILE_CARD_FIELDS} fields: those marked `mobile: "secondary"` when any is,
|
|
106
|
+
* else the first after the title, and the `role: "actions"` columns, drawn after the
|
|
107
|
+
* fields. An actions column is never the title and never one of the fields, so the
|
|
108
|
+
* fields and the marks leave it on the card; only `mobileHidden` / `mobile: "hidden"`
|
|
109
|
+
* (which keep it out of `columns`) remove it.
|
|
110
|
+
*/
|
|
111
|
+
export function mobileCardColumns<T>(columns: DataTableColumn<T>[]): {
|
|
112
|
+
primary: DataTableColumn<T> | null;
|
|
113
|
+
status: DataTableColumn<T>[];
|
|
114
|
+
fields: DataTableColumn<T>[];
|
|
115
|
+
actions: DataTableColumn<T>[];
|
|
116
|
+
} {
|
|
117
|
+
const titles = columns.filter((c) => c.role !== "actions");
|
|
118
|
+
const primary =
|
|
119
|
+
titles.find((c) => c.mobile === "primary") ??
|
|
120
|
+
titles.find((c) => c.mobilePrimary) ??
|
|
121
|
+
titles.find((c) => c.role !== "status") ??
|
|
122
|
+
titles[0] ??
|
|
123
|
+
null;
|
|
124
|
+
const status = columns.filter((c) => c !== primary && c.role === "status");
|
|
125
|
+
const actions = columns.filter((c) => c.role === "actions");
|
|
126
|
+
const rest = titles.filter((c) => c !== primary && c.role !== "status");
|
|
127
|
+
const marked = rest.filter((c) => c.mobile === "secondary");
|
|
128
|
+
return { primary, status, fields: (marked.length > 0 ? marked : rest).slice(0, MOBILE_CARD_FIELDS), actions };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A column whose cell keeps its own clicks — `noRowLink`, or `role: "actions"`: no row
|
|
132
|
+
* link may wrap it, and a card that draws it is not a link itself. */
|
|
133
|
+
export function keepsOwnClicks<T>(col: DataTableColumn<T>): boolean {
|
|
134
|
+
return !!col.noRowLink || col.role === "actions";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a
|
|
138
|
+
* production one, never under vitest. Read per call so a test can switch it on. */
|
|
139
|
+
function devWarnings(): boolean {
|
|
140
|
+
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A development build's note on the default phone card (0.49 KIT-7): a `noRowLink`
|
|
145
|
+
* column past its three fields. Such a column holds the cell's own controls — a per-row
|
|
146
|
+
* delete, a link — and the card leaves it out with them, so on a phone the action is
|
|
147
|
+
* gone. Never for a `role: "actions"` column, which the card draws after its fields.
|
|
148
|
+
* Read off the declared `columns` (not the ones a person or the width hid), and only
|
|
149
|
+
* while the table lets the card choose: `enabled` is false with a `mobileCard` or
|
|
150
|
+
* `mobileDetailsInRow` (the column is then in the opened row), and a column marked
|
|
151
|
+
* `mobile: "secondary"` means the app chose. Once per table and column key; `name`
|
|
152
|
+
* (the table's `storageKey` or `urlSync` prefix) says which table, else its title
|
|
153
|
+
* column does.
|
|
154
|
+
*/
|
|
155
|
+
export function useCardColumnWarnings<T>(columns: DataTableColumn<T>[], enabled: boolean, name?: string): void {
|
|
156
|
+
const warned = useRef<Set<string> | null>(null);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!enabled || !devWarnings()) return;
|
|
159
|
+
const phone = columns.filter((c) => !c.mobileHidden && c.mobile !== "hidden");
|
|
160
|
+
if (phone.some((c) => c.mobile === "secondary")) return;
|
|
161
|
+
const { primary, status, fields, actions } = mobileCardColumns(phone);
|
|
162
|
+
const onCard = new Set<DataTableColumn<T> | null>([primary, ...status, ...fields, ...actions]);
|
|
163
|
+
const table = name ? ` "${name}"` : primary ? ` (title column "${primary.key}")` : "";
|
|
164
|
+
warned.current ??= new Set();
|
|
165
|
+
for (const col of phone) {
|
|
166
|
+
if (onCard.has(col) || !col.noRowLink || warned.current.has(col.key)) continue;
|
|
167
|
+
warned.current.add(col.key);
|
|
168
|
+
console.warn(
|
|
169
|
+
`[ui-kit] DataTable${table}: column "${col.key}" (noRowLink) is past the phone card's three fields and ` +
|
|
170
|
+
'leaves the card — give it role: "actions" (drawn after the fields), or move its action to rowActions.',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}, [columns, enabled, name]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Whether a rendered cell shows nothing worth a line (0.49 KIT-7): `null`, `undefined`,
|
|
178
|
+
* a boolean (React draws neither), `""` or blank text, and the formatters' empty value
|
|
179
|
+
* "—" ({@link EMPTY_FORMATTED_VALUE}), and `booleanColumn`'s mark for an unset value.
|
|
180
|
+
* Any other element is never empty here — its output is the component's — so a column
|
|
181
|
+
* whose empty value should drop off the card returns `null` (or "—") from `cell` for it.
|
|
182
|
+
*/
|
|
183
|
+
export function isEmptyCell(value: ReactNode): boolean {
|
|
184
|
+
if (value === null || value === undefined || typeof value === "boolean") return true;
|
|
185
|
+
if (isValidElement<{ value?: unknown }>(value) && value.type === BooleanMark) return value.props.value == null;
|
|
186
|
+
if (typeof value === "string") {
|
|
187
|
+
const text = value.trim();
|
|
188
|
+
return text === "" || text === EMPTY_FORMATTED_VALUE;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Each column's rendered cell, without the empty ones. */
|
|
194
|
+
function filledFields<T>(columns: DataTableColumn<T>[], row: T): { col: DataTableColumn<T>; value: ReactNode }[] {
|
|
195
|
+
return columns.map((col) => ({ col, value: col.cell(row) })).filter(({ value }) => !isEmptyCell(value));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A status column's value beside the card's title: text in a neutral chip, an element
|
|
199
|
+
* (the app's own Chip or StatusDot) as it is. */
|
|
200
|
+
function StatusChip({ value }: { value: ReactNode }) {
|
|
201
|
+
return typeof value === "string" || typeof value === "number" ? <Chip size="sm">{value}</Chip> : <>{value}</>;
|
|
202
|
+
}
|
|
203
|
+
|
|
93
204
|
// ---------- Phone sort control (keksdose K10) ----------
|
|
94
205
|
|
|
95
206
|
/**
|
|
@@ -199,7 +310,11 @@ export interface MobileRowProps<T> {
|
|
|
199
310
|
mobileCardLinkable: boolean;
|
|
200
311
|
mobileCard: DataTableProps<T>["mobileCard"];
|
|
201
312
|
mobilePrimaryCol: DataTableColumn<T> | null;
|
|
313
|
+
/** The `role: "status"` columns, drawn as chips beside the title (0.49 KIT-7). */
|
|
314
|
+
mobileStatusColumns: DataTableColumn<T>[];
|
|
202
315
|
mobileSecondaryColumns: DataTableColumn<T>[];
|
|
316
|
+
/** The `role: "actions"` columns, drawn after the fields at the card's end (0.49 KIT-7). */
|
|
317
|
+
mobileActionColumns: DataTableColumn<T>[];
|
|
203
318
|
mobileExpandAsDialog: boolean;
|
|
204
319
|
compact: boolean;
|
|
205
320
|
/** The text size is Large or Extra large (§4). */
|
|
@@ -236,7 +351,9 @@ export function MobileRow<T>({
|
|
|
236
351
|
mobileCardLinkable,
|
|
237
352
|
mobileCard,
|
|
238
353
|
mobilePrimaryCol,
|
|
354
|
+
mobileStatusColumns,
|
|
239
355
|
mobileSecondaryColumns,
|
|
356
|
+
mobileActionColumns,
|
|
240
357
|
mobileExpandAsDialog,
|
|
241
358
|
compact,
|
|
242
359
|
large,
|
|
@@ -305,6 +422,11 @@ export function MobileRow<T>({
|
|
|
305
422
|
// itself is the anchor, and routing it through `linkColumn` too would nest an
|
|
306
423
|
// <a> inside an <a>. Desktop and mobile are mutually exclusive (`isMdUp`), so
|
|
307
424
|
// exactly one anchor exists per row.
|
|
425
|
+
// The default card's filled fields and status values (0.49 KIT-7): an empty one
|
|
426
|
+
// gets no line. Not computed for a `mobileCard`, which draws its own.
|
|
427
|
+
const fields = mobileCard ? [] : filledFields(mobileSecondaryColumns, row);
|
|
428
|
+
const statuses = mobileCard ? [] : filledFields(mobileStatusColumns, row);
|
|
429
|
+
const actionCells = mobileCard ? [] : filledFields(mobileActionColumns, row);
|
|
308
430
|
const cardInner = (
|
|
309
431
|
<>
|
|
310
432
|
{/* The card's own content keeps the column it always had; the chevron sits
|
|
@@ -323,24 +445,42 @@ export function MobileRow<T>({
|
|
|
323
445
|
mobileCard(row)
|
|
324
446
|
) : (
|
|
325
447
|
<>
|
|
326
|
-
{/* Only the `data-*` half of `cellProps` — see its note.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
448
|
+
{/* Only the `data-*` half of `cellProps` — see its note. The title row
|
|
449
|
+
carries the status chips at its end (0.49 KIT-7). */}
|
|
450
|
+
{(mobilePrimaryCol || statuses.length > 0) && (
|
|
451
|
+
<div data-card-head="" className="flex min-w-0 items-start justify-between gap-2">
|
|
452
|
+
{mobilePrimaryCol && (
|
|
453
|
+
<div
|
|
454
|
+
{...dataAttrsOf(mobilePrimaryCol.cellProps?.(row))}
|
|
455
|
+
data-card-title=""
|
|
456
|
+
className="min-w-0 flex-1 break-words font-medium"
|
|
457
|
+
>
|
|
458
|
+
{mobilePrimaryCol.cell(row)}
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
{statuses.length > 0 && (
|
|
462
|
+
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1">
|
|
463
|
+
{statuses.map(({ col, value }) => (
|
|
464
|
+
<span key={col.key} {...dataAttrsOf(col.cellProps?.(row))} data-card-status="" className="inline-flex">
|
|
465
|
+
<StatusChip value={value} />
|
|
466
|
+
</span>
|
|
467
|
+
))}
|
|
468
|
+
</div>
|
|
469
|
+
)}
|
|
330
470
|
</div>
|
|
331
471
|
)}
|
|
332
|
-
{
|
|
472
|
+
{fields.length > 0 && large ? (
|
|
333
473
|
// §4: each label ABOVE its value at Large. Side by side, a label column
|
|
334
474
|
// sized to the widest label leaves a value a sliver of a 240 px card.
|
|
335
475
|
<StackedFields columns={mobileSecondaryColumns} row={row} compact={compact} />
|
|
336
|
-
) :
|
|
476
|
+
) : fields.length > 0 && (
|
|
337
477
|
<dl
|
|
338
478
|
className={cn(
|
|
339
479
|
"grid grid-cols-[auto_1fr] gap-x-3",
|
|
340
480
|
compact ? "gap-y-0.5 text-xs" : "gap-y-1 text-sm",
|
|
341
481
|
)}
|
|
342
482
|
>
|
|
343
|
-
{
|
|
483
|
+
{fields.map(({ col, value }) => (
|
|
344
484
|
<Fragment key={col.key}>
|
|
345
485
|
<dt className="text-xs uppercase tracking-wide text-muted self-center">
|
|
346
486
|
{col.header}
|
|
@@ -349,12 +489,23 @@ export function MobileRow<T>({
|
|
|
349
489
|
{...dataAttrsOf(col.cellProps?.(row))}
|
|
350
490
|
className="min-w-0 text-secondary self-center"
|
|
351
491
|
>
|
|
352
|
-
{
|
|
492
|
+
{value}
|
|
353
493
|
</dd>
|
|
354
494
|
</Fragment>
|
|
355
495
|
))}
|
|
356
496
|
</dl>
|
|
357
497
|
)}
|
|
498
|
+
{/* The row's actions after the fields, at the card's end (0.49 KIT-7): its
|
|
499
|
+
buttons keep their own clicks, and RowActions folds into "⋯" at Large. */}
|
|
500
|
+
{actionCells.length > 0 && (
|
|
501
|
+
<div data-card-actions="" className="flex min-w-0 flex-wrap items-center justify-end gap-2">
|
|
502
|
+
{actionCells.map(({ col, value }) => (
|
|
503
|
+
<div key={col.key} {...dataAttrsOf(col.cellProps?.(row))} className="flex min-w-0 justify-end">
|
|
504
|
+
{value}
|
|
505
|
+
</div>
|
|
506
|
+
))}
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
358
509
|
</>
|
|
359
510
|
)}
|
|
360
511
|
</div>
|
|
@@ -64,7 +64,8 @@ function swipeTone(action: SwipeAction): RowActionTone {
|
|
|
64
64
|
* `end` then `start` — the physical `right` / `left` only where the logical side is
|
|
65
65
|
* missing, as the card reads them — each action once, told apart by its `id`, else by its
|
|
66
66
|
* label (two stages with one label would be one button twice). Each button runs the
|
|
67
|
-
* swipe's own `onCommit
|
|
67
|
+
* swipe's own `onCommit` — with no confirmation of the kit's (`confirm: false`), as the
|
|
68
|
+
* swipe has none; a swipe's `danger` / `warning` / `info` tone carries over, any
|
|
68
69
|
* other reads `muted`. `options.omit` leaves an action out, `options.commit` gives it a
|
|
69
70
|
* `CommitScope` (see {@link DataTableDesktopSwipeActions}). A `null` plan is `[]`.
|
|
70
71
|
*
|
|
@@ -94,6 +95,10 @@ export function swipePlanRowActions<T>(
|
|
|
94
95
|
: undefined,
|
|
95
96
|
tone: swipeTone(action),
|
|
96
97
|
commit,
|
|
98
|
+
// A swipe commits without the kit asking (the plan's `onCommit` asks, as keksdose's
|
|
99
|
+
// does), so its button does not ask twice (0.49 KIT-12); a danger one still sits in
|
|
100
|
+
// the row's menu.
|
|
101
|
+
confirm: false,
|
|
97
102
|
onSelect: action.onCommit,
|
|
98
103
|
});
|
|
99
104
|
}
|
|
@@ -142,6 +147,9 @@ function toRowAction<T>(a: ResolvedRowAction<T>, row: T): RowAction {
|
|
|
142
147
|
icon: isValidElement(a.icon) ? a.icon : a.icon != null && a.icon !== false ? <>{a.icon}</> : undefined,
|
|
143
148
|
tone: a.tone,
|
|
144
149
|
disabledReason: a.action.disabledReason?.(row),
|
|
150
|
+
// The table's own `confirm` asks in `runRowAction`; RowActions' default question
|
|
151
|
+
// (0.49 KIT-12) is for a delete that brought none.
|
|
152
|
+
confirm: a.action.confirm ? false : undefined,
|
|
145
153
|
onSelect: () => void runRowAction(a.action, row),
|
|
146
154
|
};
|
|
147
155
|
}
|
|
@@ -272,7 +280,8 @@ export function useRowActionColumn<T>({
|
|
|
272
280
|
() => (swipeColumn ? [...actionColumns, swipeColumn] : actionColumns),
|
|
273
281
|
[actionColumns, swipeColumn],
|
|
274
282
|
);
|
|
275
|
-
|
|
283
|
+
// A stable function (0.48.2): the phone cards compare it by identity.
|
|
284
|
+
const swipesFromActions = useCallback((row: T): MobileSwipeActions | null => {
|
|
276
285
|
const swipes = rowActionsFor(row).filter((a) => a.swipe && !a.action.disabledReason?.(row));
|
|
277
286
|
if (!swipes.length) return null;
|
|
278
287
|
const toSwipe = (a: ResolvedRowAction<T>): SwipeAction => ({
|
|
@@ -287,6 +296,6 @@ export function useRowActionColumn<T>({
|
|
|
287
296
|
start: swipes.filter((a) => a.swipe === "start").map(toSwipe),
|
|
288
297
|
end: swipes.filter((a) => a.swipe === "end").map(toSwipe),
|
|
289
298
|
};
|
|
290
|
-
};
|
|
299
|
+
}, [rowActionsFor]);
|
|
291
300
|
return { columns, swipesFromActions };
|
|
292
301
|
}
|