@eifi1/ui-kit 0.11.0 → 0.12.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 +57 -32
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +2 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/account-settings-labels.d.ts +105 -0
- package/dist/components/account-settings-labels.js +81 -0
- package/dist/components/account-settings-labels.js.map +1 -0
- package/dist/components/account-settings.d.ts +36 -52
- package/dist/components/account-settings.js +44 -8
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +24 -4
- package/dist/components/amount-input.js +5 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/authed-image.d.ts +68 -0
- package/dist/components/authed-image.js +117 -0
- package/dist/components/authed-image.js.map +1 -0
- package/dist/components/breadcrumbs.d.ts +2 -1
- package/dist/components/breadcrumbs.js +5 -2
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-group.d.ts +51 -2
- package/dist/components/button-group.js +5 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +15 -4
- package/dist/components/calendar-heatmap.d.ts +41 -3
- package/dist/components/calendar-heatmap.js +48 -7
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +6 -3
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +35 -3
- package/dist/components/choice-card.js +83 -25
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox-core.js +2 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +31 -9
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +51 -70
- package/dist/components/copy-button.js +1 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +63 -0
- package/dist/components/data-table-cells.js +64 -0
- package/dist/components/data-table-cells.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +31 -1
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +13 -0
- package/dist/components/data-table-labels.js +8 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +102 -12
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +3 -1
- package/dist/components/date-picker.js +15 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +23 -3
- package/dist/components/description-list.js +23 -3
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +1 -0
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js +18 -3
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.d.ts +92 -0
- package/dist/components/error-boundary.js +111 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/field.d.ts +30 -4
- package/dist/components/field.js +40 -17
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +50 -208
- package/dist/components/file-dropzone.d.ts +49 -2
- package/dist/components/floating-panel.d.ts +13 -4
- package/dist/components/floating-panel.js +7 -3
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +53 -0
- package/dist/components/form-actions.js +98 -0
- package/dist/components/form-actions.js.map +1 -0
- package/dist/components/image-grid.d.ts +67 -0
- package/dist/components/image-grid.js +112 -0
- package/dist/components/image-grid.js.map +1 -0
- package/dist/components/lightbox.d.ts +104 -0
- package/dist/components/lightbox.js +229 -0
- package/dist/components/lightbox.js.map +1 -0
- package/dist/components/line-items.d.ts +114 -0
- package/dist/components/line-items.js +238 -0
- package/dist/components/line-items.js.map +1 -0
- package/dist/components/list.d.ts +23 -2
- package/dist/components/list.js +32 -13
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-state.d.ts +24 -0
- package/dist/components/loading-state.js +32 -0
- package/dist/components/loading-state.js.map +1 -0
- package/dist/components/menu-item.d.ts +2 -1
- package/dist/components/menu-item.js +5 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +13 -1
- package/dist/components/mini-calendar.js +46 -2
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +20 -1
- package/dist/components/modal.js +26 -2
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +21 -1
- package/dist/components/month-picker.js +139 -26
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +1 -1
- package/dist/components/multi-entity-combobox.js +19 -3
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/nav-pills.d.ts +2 -1
- package/dist/components/nav-pills.js +5 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-field.d.ts +24 -5
- package/dist/components/number-field.js +5 -7
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +25 -5
- package/dist/components/number-input.js +7 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +15 -4
- package/dist/components/passkeys-setting.d.ts +67 -0
- package/dist/components/passkeys-setting.js +214 -0
- package/dist/components/passkeys-setting.js.map +1 -0
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +20 -0
- package/dist/components/pie-chart-labels.js +12 -0
- package/dist/components/pie-chart-labels.js.map +1 -0
- package/dist/components/pie-chart.d.ts +101 -0
- package/dist/components/pie-chart.js +283 -0
- package/dist/components/pie-chart.js.map +1 -0
- package/dist/components/progress-bar.d.ts +44 -3
- package/dist/components/progress-bar.js +58 -22
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/qr-code.d.ts +34 -0
- package/dist/components/qr-code.js +57 -0
- package/dist/components/qr-code.js.map +1 -0
- package/dist/components/search-field.d.ts +15 -3
- package/dist/components/search-field.js +5 -1
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/settings-fields.d.ts +49 -1
- package/dist/components/signed-amount.d.ts +121 -0
- package/dist/components/signed-amount.js +152 -0
- package/dist/components/signed-amount.js.map +1 -0
- package/dist/components/skeleton.d.ts +10 -3
- package/dist/components/skeleton.js +12 -2
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/stat-tile.d.ts +2 -1
- package/dist/components/stat-tile.js +5 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +39 -5
- package/dist/components/table.js +45 -11
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +176 -0
- package/dist/components/text-link.js +109 -0
- package/dist/components/text-link.js.map +1 -0
- package/dist/components/time-input.d.ts +49 -1
- package/dist/components/toggle-group.d.ts +13 -0
- package/dist/components/toggle-group.js +35 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +36 -3
- package/dist/components/toggle-legend.js +49 -0
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/ui.d.ts +51 -779
- package/dist/components/ui.js +215 -23
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +3 -52
- package/dist/components/use-table-state.js +128 -30
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
- package/dist/data-table.d.ts +2 -1
- package/dist/data-table.js +5 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +26 -3
- package/dist/feedback/feedback-dialog.js +16 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +133 -0
- package/dist/feedback/feedback-thread.js +237 -0
- package/dist/feedback/feedback-thread.js.map +1 -0
- package/dist/feedback.d.ts +2 -1
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-authed-src.d.ts +71 -0
- package/dist/hooks/use-authed-src.js +63 -0
- package/dist/hooks/use-authed-src.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +49 -2
- package/dist/hooks/use-hotkey.d.ts +59 -0
- package/dist/hooks/use-hotkey.js +105 -0
- package/dist/hooks/use-hotkey.js.map +1 -0
- package/dist/hooks/use-search-param-state.d.ts +83 -0
- package/dist/hooks/use-search-param-state.js +94 -0
- package/dist/hooks/use-search-param-state.js.map +1 -0
- package/dist/i18n/defaults.d.ts +15 -4
- package/dist/i18n/defaults.js +31 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +46 -312
- package/dist/i18n/kit-labels.js +20 -5
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
- package/dist/i18n/locales/de-CH.d.ts +15 -4
- package/dist/i18n/locales/de-informal.d.ts +15 -4
- package/dist/i18n/locales/de.d.ts +15 -4
- package/dist/i18n/locales/de.js +156 -3
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +15 -4
- package/dist/i18n/locales/es.js +153 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +15 -4
- package/dist/i18n/locales/fr.js +153 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +15 -4
- package/dist/i18n/locales/hu.js +153 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +15 -4
- package/dist/i18n/locales/it.js +153 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +15 -4
- package/dist/i18n/locales/zh.js +153 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +37 -17
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
- package/dist/lib/format.d.ts +144 -0
- package/dist/lib/format.js +149 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/qr-encode.d.ts +44 -0
- package/dist/lib/qr-encode.js +338 -0
- package/dist/lib/qr-encode.js.map +1 -0
- package/dist/rhf/fields.d.ts +241 -0
- package/dist/rhf/fields.js +601 -0
- package/dist/rhf/fields.js.map +1 -0
- package/dist/rhf/form.d.ts +49 -1
- package/dist/rhf/line-items.d.ts +31 -0
- package/dist/rhf/line-items.js +59 -0
- package/dist/rhf/line-items.js.map +1 -0
- package/dist/rhf.d.ts +61 -1
- package/dist/rhf.js +2 -0
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +99 -2
- package/dist/shell/app-shell.js +58 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.d.ts +74 -0
- package/dist/shell/auth-layout.js +97 -0
- package/dist/shell/auth-layout.js.map +1 -0
- package/dist/shell/top-bar-brand.d.ts +81 -0
- package/dist/shell/top-bar-brand.js +47 -0
- package/dist/shell/top-bar-brand.js.map +1 -0
- package/dist/shell/topbar-action-menu.d.ts +12 -1
- package/dist/shell/topbar-action-menu.js +16 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +56 -1
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +48 -1
- package/dist/wizard/wizard-context.d.ts +24 -1
- package/dist/wizard/wizard-context.js +19 -2
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.d.ts +20 -5
- package/dist/wizard/wizard-step.js +16 -3
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard.d.ts +50 -3
- package/package.json +1 -1
- package/src/chart.ts +4 -0
- package/src/components/account-settings-labels.ts +190 -0
- package/src/components/account-settings.tsx +110 -63
- package/src/components/amount-input.tsx +13 -1
- package/src/components/authed-image.tsx +190 -0
- package/src/components/breadcrumbs.tsx +8 -2
- package/src/components/button-group.tsx +7 -2
- package/src/components/calendar-heatmap.tsx +105 -10
- package/src/components/chip.tsx +8 -4
- package/src/components/choice-card.tsx +136 -25
- package/src/components/combobox-core.tsx +5 -0
- package/src/components/combobox.tsx +31 -9
- package/src/components/copy-button.tsx +3 -2
- package/src/components/data-table-cells.tsx +119 -0
- package/src/components/data-table-filters.ts +77 -0
- package/src/components/data-table-labels.ts +23 -0
- package/src/components/data-table.tsx +224 -13
- package/src/components/date-picker.tsx +19 -3
- package/src/components/description-list.tsx +52 -3
- package/src/components/entity-combobox.tsx +18 -3
- package/src/components/error-boundary.tsx +216 -0
- package/src/components/field.tsx +56 -5
- package/src/components/floating-panel.tsx +20 -7
- package/src/components/form-actions.tsx +191 -0
- package/src/components/image-grid.tsx +181 -0
- package/src/components/lightbox.tsx +374 -0
- package/src/components/line-items.tsx +385 -0
- package/src/components/list.tsx +65 -16
- package/src/components/loading-state.tsx +47 -0
- package/src/components/menu-item.tsx +7 -2
- package/src/components/mini-calendar.tsx +72 -3
- package/src/components/modal.tsx +61 -3
- package/src/components/month-picker.tsx +181 -26
- package/src/components/multi-entity-combobox.tsx +19 -3
- package/src/components/nav-pills.tsx +8 -1
- package/src/components/number-field.tsx +15 -10
- package/src/components/number-input.tsx +16 -1
- package/src/components/passkeys-setting.tsx +331 -0
- package/src/components/picker-sheet.tsx +7 -1
- package/src/components/pie-chart-labels.ts +32 -0
- package/src/components/pie-chart.tsx +479 -0
- package/src/components/progress-bar.tsx +129 -19
- package/src/components/qr-code.tsx +83 -0
- package/src/components/search-field.tsx +20 -4
- package/src/components/signed-amount.tsx +286 -0
- package/src/components/skeleton.tsx +21 -3
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/table.tsx +102 -13
- package/src/components/text-link.tsx +248 -0
- package/src/components/toggle-group.tsx +58 -10
- package/src/components/toggle-legend.tsx +87 -2
- package/src/components/ui.tsx +426 -36
- package/src/components/use-table-state.ts +265 -33
- package/src/data-table.ts +10 -0
- package/src/feedback/feedback-dialog.tsx +42 -3
- package/src/feedback/feedback-thread.tsx +434 -0
- package/src/feedback.ts +1 -0
- package/src/hooks/use-authed-src.ts +161 -0
- package/src/hooks/use-hotkey.ts +172 -0
- package/src/hooks/use-search-param-state.ts +187 -0
- package/src/i18n/defaults.ts +30 -0
- package/src/i18n/kit-labels.tsx +102 -8
- package/src/i18n/locales/de.ts +153 -0
- package/src/i18n/locales/es.ts +150 -0
- package/src/i18n/locales/fr.ts +153 -0
- package/src/i18n/locales/hu.ts +151 -0
- package/src/i18n/locales/it.ts +151 -0
- package/src/i18n/locales/zh.ts +150 -0
- package/src/index.ts +59 -0
- package/src/lib/format.ts +325 -0
- package/src/lib/qr-encode.ts +420 -0
- package/src/rhf/fields.tsx +1054 -0
- package/src/rhf/line-items.tsx +130 -0
- package/src/rhf.ts +6 -0
- package/src/shell/app-shell.tsx +147 -2
- package/src/shell/auth-layout.tsx +190 -0
- package/src/shell/top-bar-brand.tsx +76 -0
- package/src/shell/topbar-action-menu.tsx +21 -0
- package/src/shell.ts +2 -0
- package/src/wizard/wizard-context.tsx +41 -1
- package/src/wizard/wizard-step.tsx +43 -6
- package/tokens.css +12 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { DataTableLabels } from './data-table-labels.js';
|
|
4
|
+
import { b as DataTableColumn } from '../data-table-Drun8Fk9.js';
|
|
5
|
+
import './data-table-sort.js';
|
|
6
|
+
import './swipeable-row.js';
|
|
7
|
+
|
|
8
|
+
interface BooleanMarkProps {
|
|
9
|
+
/** `true` → a check, `false` → a cross (or a dash, see `falseAs`), `null` /
|
|
10
|
+
* `undefined` → a dash. */
|
|
11
|
+
value: boolean | null | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* How `false` is drawn. `"cross"` (default) when "no" is news worth seeing — a
|
|
14
|
+
* failed check, a missing document. `"dash"` for a column that is mostly `false` and
|
|
15
|
+
* reads as a list of the `true` ones (kastlan's BoolCell): a column of crosses is
|
|
16
|
+
* noise. The screen-reader text says "No" either way.
|
|
17
|
+
*/
|
|
18
|
+
falseAs?: "cross" | "dash";
|
|
19
|
+
/** The spoken words, over `dataTable.booleanTrue` / `booleanFalse` / `booleanUnset`
|
|
20
|
+
* from `<UiKitProvider labels>`. */
|
|
21
|
+
labels?: Partial<Pick<DataTableLabels, "booleanTrue" | "booleanFalse" | "booleanUnset">>;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A yes/no value in a table cell: a green check, a muted cross, or a faint dash for
|
|
26
|
+
* "not set" — with the word for a screen reader, since a glyph alone is announced as
|
|
27
|
+
* nothing at all (or as "image").
|
|
28
|
+
*
|
|
29
|
+
* kastlan's `BoolCell` (shared/components/data-table/bool-cell.tsx) drew the icons with
|
|
30
|
+
* no text, so a reader walking the table heard an empty cell for every row; the apps'
|
|
31
|
+
* Yes/No chips were the other answer, and too loud for a column of them.
|
|
32
|
+
*/
|
|
33
|
+
declare function BooleanMark({ value, falseAs, labels: labelsProp, className }: BooleanMarkProps): react.JSX.Element;
|
|
34
|
+
interface BooleanColumnOptions<T> {
|
|
35
|
+
key: string;
|
|
36
|
+
header: ReactNode;
|
|
37
|
+
/** The value of this row. */
|
|
38
|
+
value: (row: T) => boolean | null | undefined;
|
|
39
|
+
/** See {@link BooleanMarkProps.falseAs}. */
|
|
40
|
+
falseAs?: "cross" | "dash";
|
|
41
|
+
/** Sortable, `true` first on the first click (a column is sorted to find the yes
|
|
42
|
+
* rows). Default true. */
|
|
43
|
+
sortable?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* A select filter over the column, with these option labels — pass them translated.
|
|
46
|
+
* Omitted: no filter. The option values are `"true"` / `"false"` (and `"unset"` when
|
|
47
|
+
* `unset` is given), which is also what `filterHref` takes for this column.
|
|
48
|
+
*/
|
|
49
|
+
filterOptions?: {
|
|
50
|
+
true: string;
|
|
51
|
+
false: string;
|
|
52
|
+
unset?: string;
|
|
53
|
+
};
|
|
54
|
+
/** Further column settings (`mobileHidden`, `headClassName`, …), merged over. */
|
|
55
|
+
column?: Partial<DataTableColumn<T>>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A {@link DataTableColumn} for a yes/no field: {@link BooleanMark} cells,
|
|
59
|
+
* sortable with `true` first, and optionally a Yes/No select filter.
|
|
60
|
+
*/
|
|
61
|
+
declare function booleanColumn<T>({ key, header, value, falseAs, sortable, filterOptions, column, }: BooleanColumnOptions<T>): DataTableColumn<T>;
|
|
62
|
+
|
|
63
|
+
export { type BooleanColumnOptions, BooleanMark, type BooleanMarkProps, booleanColumn };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, Minus, X } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
6
|
+
import { DEFAULT_DATA_TABLE_LABELS } from "./data-table-labels.js";
|
|
7
|
+
function BooleanMark({ value, falseAs = "cross", labels: labelsProp, className }) {
|
|
8
|
+
const labels = useKitLabels("dataTable", DEFAULT_DATA_TABLE_LABELS, labelsProp);
|
|
9
|
+
const state = value === true ? "true" : value === false ? "false" : "unset";
|
|
10
|
+
const text = state === "true" ? labels.booleanTrue : state === "false" ? labels.booleanFalse : labels.booleanUnset;
|
|
11
|
+
const Icon = state === "true" ? Check : state === "false" && falseAs === "cross" ? X : Minus;
|
|
12
|
+
return /* @__PURE__ */ jsxs("span", { "data-value": state, className: cn("inline-flex items-center align-middle", className), children: [
|
|
13
|
+
/* @__PURE__ */ jsx(
|
|
14
|
+
Icon,
|
|
15
|
+
{
|
|
16
|
+
"aria-hidden": true,
|
|
17
|
+
className: cn(
|
|
18
|
+
"size-4",
|
|
19
|
+
state === "true" ? "text-[var(--success)]" : state === "false" && falseAs === "cross" ? "text-[var(--text-muted)]" : "text-[var(--text-placeholder)]"
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
),
|
|
23
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: text })
|
|
24
|
+
] });
|
|
25
|
+
}
|
|
26
|
+
function booleanColumn({
|
|
27
|
+
key,
|
|
28
|
+
header,
|
|
29
|
+
value,
|
|
30
|
+
falseAs,
|
|
31
|
+
sortable = true,
|
|
32
|
+
filterOptions,
|
|
33
|
+
column
|
|
34
|
+
}) {
|
|
35
|
+
const asKey = (row) => {
|
|
36
|
+
const v = value(row);
|
|
37
|
+
return v === true ? "true" : v === false ? "false" : "unset";
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
key,
|
|
41
|
+
header,
|
|
42
|
+
cell: (row) => /* @__PURE__ */ jsx(BooleanMark, { value: value(row), falseAs }),
|
|
43
|
+
// 0 for yes, 1 for no, null for unset (which the table sorts last either way).
|
|
44
|
+
sortBy: sortable ? (row) => {
|
|
45
|
+
const v = value(row);
|
|
46
|
+
return v == null ? null : v ? 0 : 1;
|
|
47
|
+
} : void 0,
|
|
48
|
+
filter: filterOptions ? {
|
|
49
|
+
type: "select",
|
|
50
|
+
getValue: asKey,
|
|
51
|
+
options: [
|
|
52
|
+
{ value: "true", label: filterOptions.true },
|
|
53
|
+
{ value: "false", label: filterOptions.false },
|
|
54
|
+
...filterOptions.unset !== void 0 ? [{ value: "unset", label: filterOptions.unset }] : []
|
|
55
|
+
]
|
|
56
|
+
} : void 0,
|
|
57
|
+
...column
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
BooleanMark,
|
|
62
|
+
booleanColumn
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=data-table-cells.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-cells.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { Check, Minus, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\nimport type { DataTableColumn } from \"./data-table\";\n\nexport interface BooleanMarkProps {\n /** `true` → a check, `false` → a cross (or a dash, see `falseAs`), `null` /\n * `undefined` → a dash. */\n value: boolean | null | undefined;\n /**\n * How `false` is drawn. `\"cross\"` (default) when \"no\" is news worth seeing — a\n * failed check, a missing document. `\"dash\"` for a column that is mostly `false` and\n * reads as a list of the `true` ones (kastlan's BoolCell): a column of crosses is\n * noise. The screen-reader text says \"No\" either way.\n */\n falseAs?: \"cross\" | \"dash\";\n /** The spoken words, over `dataTable.booleanTrue` / `booleanFalse` / `booleanUnset`\n * from `<UiKitProvider labels>`. */\n labels?: Partial<Pick<DataTableLabels, \"booleanTrue\" | \"booleanFalse\" | \"booleanUnset\">>;\n className?: string;\n}\n\n/**\n * A yes/no value in a table cell: a green check, a muted cross, or a faint dash for\n * \"not set\" — with the word for a screen reader, since a glyph alone is announced as\n * nothing at all (or as \"image\").\n *\n * kastlan's `BoolCell` (shared/components/data-table/bool-cell.tsx) drew the icons with\n * no text, so a reader walking the table heard an empty cell for every row; the apps'\n * Yes/No chips were the other answer, and too loud for a column of them.\n */\nexport function BooleanMark({ value, falseAs = \"cross\", labels: labelsProp, className }: BooleanMarkProps) {\n const labels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS, labelsProp);\n const state = value === true ? \"true\" : value === false ? \"false\" : \"unset\";\n const text =\n state === \"true\" ? labels.booleanTrue : state === \"false\" ? labels.booleanFalse : labels.booleanUnset;\n const Icon = state === \"true\" ? Check : state === \"false\" && falseAs === \"cross\" ? X : Minus;\n return (\n <span data-value={state} className={cn(\"inline-flex items-center align-middle\", className)}>\n <Icon\n aria-hidden\n className={cn(\n \"size-4\",\n state === \"true\"\n ? \"text-[var(--success)]\"\n : state === \"false\" && falseAs === \"cross\"\n ? \"text-[var(--text-muted)]\"\n : \"text-[var(--text-placeholder)]\",\n )}\n />\n <span className=\"sr-only\">{text}</span>\n </span>\n );\n}\n\nexport interface BooleanColumnOptions<T> {\n key: string;\n header: ReactNode;\n /** The value of this row. */\n value: (row: T) => boolean | null | undefined;\n /** See {@link BooleanMarkProps.falseAs}. */\n falseAs?: \"cross\" | \"dash\";\n /** Sortable, `true` first on the first click (a column is sorted to find the yes\n * rows). Default true. */\n sortable?: boolean;\n /**\n * A select filter over the column, with these option labels — pass them translated.\n * Omitted: no filter. The option values are `\"true\"` / `\"false\"` (and `\"unset\"` when\n * `unset` is given), which is also what `filterHref` takes for this column.\n */\n filterOptions?: { true: string; false: string; unset?: string };\n /** Further column settings (`mobileHidden`, `headClassName`, …), merged over. */\n column?: Partial<DataTableColumn<T>>;\n}\n\n/**\n * A {@link DataTableColumn} for a yes/no field: {@link BooleanMark} cells,\n * sortable with `true` first, and optionally a Yes/No select filter.\n */\nexport function booleanColumn<T>({\n key,\n header,\n value,\n falseAs,\n sortable = true,\n filterOptions,\n column,\n}: BooleanColumnOptions<T>): DataTableColumn<T> {\n const asKey = (row: T) => {\n const v = value(row);\n return v === true ? \"true\" : v === false ? \"false\" : \"unset\";\n };\n return {\n key,\n header,\n cell: (row) => <BooleanMark value={value(row)} falseAs={falseAs} />,\n // 0 for yes, 1 for no, null for unset (which the table sorts last either way).\n sortBy: sortable\n ? (row) => {\n const v = value(row);\n return v == null ? null : v ? 0 : 1;\n }\n : undefined,\n filter: filterOptions\n ? {\n type: \"select\",\n getValue: asKey,\n options: [\n { value: \"true\", label: filterOptions.true },\n { value: \"false\", label: filterOptions.false },\n ...(filterOptions.unset !== undefined ? [{ value: \"unset\", label: filterOptions.unset }] : []),\n ],\n }\n : undefined,\n ...column,\n };\n}\n"],"mappings":";AAwCI,SACE,KADF;AAvCJ,SAAS,OAAO,OAAO,SAAS;AAChC,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,iCAAuD;AA6BzD,SAAS,YAAY,EAAE,OAAO,UAAU,SAAS,QAAQ,YAAY,UAAU,GAAqB;AACzG,QAAM,SAAS,aAAa,aAAa,2BAA2B,UAAU;AAC9E,QAAM,QAAQ,UAAU,OAAO,SAAS,UAAU,QAAQ,UAAU;AACpE,QAAM,OACJ,UAAU,SAAS,OAAO,cAAc,UAAU,UAAU,OAAO,eAAe,OAAO;AAC3F,QAAM,OAAO,UAAU,SAAS,QAAQ,UAAU,WAAW,YAAY,UAAU,IAAI;AACvF,SACE,qBAAC,UAAK,cAAY,OAAO,WAAW,GAAG,yCAAyC,SAAS,GACvF;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA,UAAU,SACN,0BACA,UAAU,WAAW,YAAY,UAC/B,6BACA;AAAA,QACR;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,UAAK,WAAU,WAAW,gBAAK;AAAA,KAClC;AAEJ;AA0BO,SAAS,cAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAAgD;AAC9C,QAAM,QAAQ,CAAC,QAAW;AACxB,UAAM,IAAI,MAAM,GAAG;AACnB,WAAO,MAAM,OAAO,SAAS,MAAM,QAAQ,UAAU;AAAA,EACvD;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,CAAC,QAAQ,oBAAC,eAAY,OAAO,MAAM,GAAG,GAAG,SAAkB;AAAA;AAAA,IAEjE,QAAQ,WACJ,CAAC,QAAQ;AACP,YAAM,IAAI,MAAM,GAAG;AACnB,aAAO,KAAK,OAAO,OAAO,IAAI,IAAI;AAAA,IACpC,IACA;AAAA,IACJ,QAAQ,gBACJ;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,QACP,EAAE,OAAO,QAAQ,OAAO,cAAc,KAAK;AAAA,QAC3C,EAAE,OAAO,SAAS,OAAO,cAAc,MAAM;AAAA,QAC7C,GAAI,cAAc,UAAU,SAAY,CAAC,EAAE,OAAO,SAAS,OAAO,cAAc,MAAM,CAAC,IAAI,CAAC;AAAA,MAC9F;AAAA,IACF,IACA;AAAA,IACJ,GAAG;AAAA,EACL;AACF;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { b as DataTableColumn,
|
|
2
|
+
import { b as DataTableColumn, f as FilterValue } from '../data-table-Drun8Fk9.js';
|
|
3
3
|
import { DataTableLabels } from './data-table-labels.js';
|
|
4
4
|
import './data-table-sort.js';
|
|
5
5
|
import './swipeable-row.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as ColumnFilter,
|
|
1
|
+
export { C as ColumnFilter, f as FilterValue, j as dateFilter, k as decodeFilterValue, l as decodeFilterValueOfType, m as defaultFilterState, n as encodeFilterValue, o as filterHref, p as isFilterActive, q as numberFilter, s as resolveFilter, t as rowMatches, u as selectFilter, v as textFilter } from '../data-table-Drun8Fk9.js';
|
|
2
2
|
import 'react';
|
|
3
3
|
import './data-table-sort.js';
|
|
4
4
|
import './swipeable-row.js';
|
|
@@ -107,13 +107,43 @@ function decodeFilterValueOfType(type, raw) {
|
|
|
107
107
|
function decodeFilterValue(filter, raw) {
|
|
108
108
|
return decodeFilterValueOfType(filter.type, raw);
|
|
109
109
|
}
|
|
110
|
+
function textFilter(getValue) {
|
|
111
|
+
return { type: "text", getValue };
|
|
112
|
+
}
|
|
113
|
+
function selectFilter(getValue, options) {
|
|
114
|
+
const opts = Array.isArray(options) ? options : options ? Object.entries(options).map(([value, label]) => ({ value, label })) : void 0;
|
|
115
|
+
return { type: "select", getValue, options: opts };
|
|
116
|
+
}
|
|
117
|
+
function dateFilter(getValue) {
|
|
118
|
+
return { type: "date", getValue };
|
|
119
|
+
}
|
|
120
|
+
function numberFilter(getValue) {
|
|
121
|
+
return { type: "number", getValue };
|
|
122
|
+
}
|
|
123
|
+
function filterHref(path, column, value, options) {
|
|
124
|
+
const state = typeof value === "string" ? { type: "text", q: value } : Array.isArray(value) ? { type: "select", values: value } : value;
|
|
125
|
+
const encoded = encodeFilterValue(state);
|
|
126
|
+
if (encoded == null) return path;
|
|
127
|
+
const hashAt = path.indexOf("#");
|
|
128
|
+
const base = hashAt >= 0 ? path.slice(0, hashAt) : path;
|
|
129
|
+
const hash = hashAt >= 0 ? path.slice(hashAt) : "";
|
|
130
|
+
const queryAt = base.indexOf("?");
|
|
131
|
+
const params = new URLSearchParams(queryAt >= 0 ? base.slice(queryAt + 1) : "");
|
|
132
|
+
params.set(`${options?.prefix ?? ""}f.${column}`, encoded);
|
|
133
|
+
return `${queryAt >= 0 ? base.slice(0, queryAt) : base}?${params.toString()}${hash}`;
|
|
134
|
+
}
|
|
110
135
|
export {
|
|
136
|
+
dateFilter,
|
|
111
137
|
decodeFilterValue,
|
|
112
138
|
decodeFilterValueOfType,
|
|
113
139
|
defaultFilterState,
|
|
114
140
|
encodeFilterValue,
|
|
141
|
+
filterHref,
|
|
115
142
|
isFilterActive,
|
|
143
|
+
numberFilter,
|
|
116
144
|
resolveFilter,
|
|
117
|
-
rowMatches
|
|
145
|
+
rowMatches,
|
|
146
|
+
selectFilter,
|
|
147
|
+
textFilter
|
|
118
148
|
};
|
|
119
149
|
//# sourceMappingURL=data-table-filters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table-filters.ts"],"sourcesContent":["import type { DataTableColumn } from \"./data-table\";\n\n/** How a column is filtered. `text`/`select`/`date`/`number` each read a value\n * off the row and compare it against the user's {@link FilterValue}. */\nexport type ColumnFilter<T> =\n | { type: \"text\"; getValue: (row: T) => string }\n | { type: \"select\"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }\n | { type: \"date\"; getValue: (row: T) => string | null | undefined }\n | { type: \"number\"; getValue: (row: T) => number | null | undefined };\n\n/** The current state of a column's filter (what the user has entered). */\nexport type FilterValue =\n | { type: \"text\"; q: string }\n | { type: \"select\"; values: string[] }\n | { type: \"date\"; from: string; to: string }\n | { type: \"number\"; min: string; max: string; abs: boolean };\n\n/** A column's filter config, falling back to a plain text filter over `filterBy`. */\nexport function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null {\n if (col.filter) return col.filter;\n if (col.filterBy) return { type: \"text\", getValue: col.filterBy };\n return null;\n}\n\n/** Whether a filter value is actually narrowing anything (vs its empty default). */\nexport function isFilterActive(v: FilterValue | undefined): boolean {\n if (!v) return false;\n switch (v.type) {\n case \"text\":\n return v.q.trim().length > 0;\n case \"select\":\n return v.values.length > 0;\n case \"date\":\n return !!v.from || !!v.to;\n case \"number\":\n return v.min !== \"\" || v.max !== \"\" || v.abs;\n }\n}\n\n/**\n * Whether `row` passes column `col`'s filter given the user's `state`.\n *\n * `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the\n * root rules, under which a Turkish user typing \"istanbul\" does not find \"İSTANBUL\";\n * `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.\n */\nexport function rowMatches<T>(\n col: DataTableColumn<T>,\n row: T,\n state: FilterValue,\n locale?: string,\n): boolean {\n const filter = resolveFilter(col);\n if (!filter) return true;\n switch (filter.type) {\n case \"text\": {\n if (state.type !== \"text\" || !state.q.trim()) return true;\n return filter\n .getValue(row)\n .toLocaleLowerCase(locale)\n .includes(state.q.trim().toLocaleLowerCase(locale));\n }\n case \"select\": {\n if (state.type !== \"select\" || state.values.length === 0) return true;\n return state.values.includes(filter.getValue(row));\n }\n case \"date\": {\n if (state.type !== \"date\") return true;\n const raw = filter.getValue(row);\n if (!raw) return !state.from && !state.to;\n const v = raw.slice(0, 10);\n if (state.from && v < state.from) return false;\n if (state.to && v > state.to) return false;\n return true;\n }\n case \"number\": {\n if (state.type !== \"number\") return true;\n const raw = filter.getValue(row);\n if (raw == null || Number.isNaN(raw)) return !state.min && !state.max;\n const x = state.abs ? Math.abs(raw) : raw;\n if (state.min !== \"\" && x < Number(state.min)) return false;\n if (state.max !== \"\" && x > Number(state.max)) return false;\n return true;\n }\n }\n}\n\n/** The empty filter state for a column's filter type. */\nexport function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue {\n switch (filter.type) {\n case \"text\":\n return { type: \"text\", q: \"\" };\n case \"select\":\n return { type: \"select\", values: [] };\n case \"date\":\n return { type: \"date\", from: \"\", to: \"\" };\n case \"number\":\n return { type: \"number\", min: \"\", max: \"\", abs: false };\n }\n}\n\n/**\n * Escape the ONE character the select codec uses as its separator, plus the escape\n * marker itself so the transform is reversible.\n *\n * A minimal escape rather than `encodeURIComponent`, deliberately: these strings sit\n * inside a query-parameter value that `URLSearchParams` already encodes, so a full\n * percent-encode would double-encode every space into `%2520` and make shared links\n * unreadable for no gain. Only `,` can be misread here.\n *\n * Backwards compatible with every link written before this existed: a value with\n * neither `,` nor `%` passes through both directions untouched, and `%` on its own\n * never matched `%25` either.\n */\nfunction escapeSelectValue(v: string): string {\n return v.replace(/%/g, \"%25\").replace(/,/g, \"%2C\");\n}\n\n/** Inverse of {@link escapeSelectValue}. The order matters and is the reverse of the\n * escape: undoing `%2C` first cannot see the `2C` inside an escaped `%252C`. */\nfunction unescapeSelectValue(v: string): string {\n return v.replace(/%2C/g, \",\").replace(/%25/g, \"%\");\n}\n\n/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */\nexport function encodeFilterValue(v: FilterValue): string | null {\n switch (v.type) {\n case \"text\":\n return v.q.trim() ? v.q : null;\n case \"select\":\n // Escaped before joining: an option value may legitimately contain the\n // separator (a free-text title, a payee or category name), and without this it\n // came back as two values and the filter silently matched nothing.\n return v.values.length ? v.values.map(escapeSelectValue).join(\",\") : null;\n case \"date\":\n return v.from || v.to ? `${v.from}..${v.to}` : null;\n case \"number\": {\n const hasRange = v.min !== \"\" || v.max !== \"\";\n if (!hasRange && !v.abs) return null;\n return `${v.min}..${v.max}${v.abs ? \"!\" : \"\"}`;\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue} given just the filter type — for\n * callers that know a column's type without holding its full config. */\nexport function decodeFilterValueOfType(type: FilterValue[\"type\"], raw: string): FilterValue {\n switch (type) {\n case \"text\":\n return { type: \"text\", q: raw };\n case \"select\":\n return {\n type: \"select\",\n values: raw\n .split(\",\")\n .filter((v) => v.length > 0)\n .map(unescapeSelectValue),\n };\n case \"date\": {\n const [from = \"\", to = \"\"] = raw.split(\"..\");\n return { type: \"date\", from, to };\n }\n case \"number\": {\n let body = raw;\n const abs = body.endsWith(\"!\");\n if (abs) body = body.slice(0, -1);\n const [min = \"\", max = \"\"] = body.split(\"..\");\n return { type: \"number\", min, max, abs };\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */\nexport function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {\n return decodeFilterValueOfType(filter.type, raw);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-filters.ts"],"sourcesContent":["import type { DataTableColumn } from \"./data-table\";\n\n/** How a column is filtered. `text`/`select`/`date`/`number` each read a value\n * off the row and compare it against the user's {@link FilterValue}. */\nexport type ColumnFilter<T> =\n | { type: \"text\"; getValue: (row: T) => string }\n | { type: \"select\"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }\n | { type: \"date\"; getValue: (row: T) => string | null | undefined }\n | { type: \"number\"; getValue: (row: T) => number | null | undefined };\n\n/** The current state of a column's filter (what the user has entered). */\nexport type FilterValue =\n | { type: \"text\"; q: string }\n | { type: \"select\"; values: string[] }\n | { type: \"date\"; from: string; to: string }\n | { type: \"number\"; min: string; max: string; abs: boolean };\n\n/** A column's filter config, falling back to a plain text filter over `filterBy`. */\nexport function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null {\n if (col.filter) return col.filter;\n if (col.filterBy) return { type: \"text\", getValue: col.filterBy };\n return null;\n}\n\n/** Whether a filter value is actually narrowing anything (vs its empty default). */\nexport function isFilterActive(v: FilterValue | undefined): boolean {\n if (!v) return false;\n switch (v.type) {\n case \"text\":\n return v.q.trim().length > 0;\n case \"select\":\n return v.values.length > 0;\n case \"date\":\n return !!v.from || !!v.to;\n case \"number\":\n return v.min !== \"\" || v.max !== \"\" || v.abs;\n }\n}\n\n/**\n * Whether `row` passes column `col`'s filter given the user's `state`.\n *\n * `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the\n * root rules, under which a Turkish user typing \"istanbul\" does not find \"İSTANBUL\";\n * `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.\n */\nexport function rowMatches<T>(\n col: DataTableColumn<T>,\n row: T,\n state: FilterValue,\n locale?: string,\n): boolean {\n const filter = resolveFilter(col);\n if (!filter) return true;\n switch (filter.type) {\n case \"text\": {\n if (state.type !== \"text\" || !state.q.trim()) return true;\n return filter\n .getValue(row)\n .toLocaleLowerCase(locale)\n .includes(state.q.trim().toLocaleLowerCase(locale));\n }\n case \"select\": {\n if (state.type !== \"select\" || state.values.length === 0) return true;\n return state.values.includes(filter.getValue(row));\n }\n case \"date\": {\n if (state.type !== \"date\") return true;\n const raw = filter.getValue(row);\n if (!raw) return !state.from && !state.to;\n const v = raw.slice(0, 10);\n if (state.from && v < state.from) return false;\n if (state.to && v > state.to) return false;\n return true;\n }\n case \"number\": {\n if (state.type !== \"number\") return true;\n const raw = filter.getValue(row);\n if (raw == null || Number.isNaN(raw)) return !state.min && !state.max;\n const x = state.abs ? Math.abs(raw) : raw;\n if (state.min !== \"\" && x < Number(state.min)) return false;\n if (state.max !== \"\" && x > Number(state.max)) return false;\n return true;\n }\n }\n}\n\n/** The empty filter state for a column's filter type. */\nexport function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue {\n switch (filter.type) {\n case \"text\":\n return { type: \"text\", q: \"\" };\n case \"select\":\n return { type: \"select\", values: [] };\n case \"date\":\n return { type: \"date\", from: \"\", to: \"\" };\n case \"number\":\n return { type: \"number\", min: \"\", max: \"\", abs: false };\n }\n}\n\n/**\n * Escape the ONE character the select codec uses as its separator, plus the escape\n * marker itself so the transform is reversible.\n *\n * A minimal escape rather than `encodeURIComponent`, deliberately: these strings sit\n * inside a query-parameter value that `URLSearchParams` already encodes, so a full\n * percent-encode would double-encode every space into `%2520` and make shared links\n * unreadable for no gain. Only `,` can be misread here.\n *\n * Backwards compatible with every link written before this existed: a value with\n * neither `,` nor `%` passes through both directions untouched, and `%` on its own\n * never matched `%25` either.\n */\nfunction escapeSelectValue(v: string): string {\n return v.replace(/%/g, \"%25\").replace(/,/g, \"%2C\");\n}\n\n/** Inverse of {@link escapeSelectValue}. The order matters and is the reverse of the\n * escape: undoing `%2C` first cannot see the `2C` inside an escaped `%252C`. */\nfunction unescapeSelectValue(v: string): string {\n return v.replace(/%2C/g, \",\").replace(/%25/g, \"%\");\n}\n\n/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */\nexport function encodeFilterValue(v: FilterValue): string | null {\n switch (v.type) {\n case \"text\":\n return v.q.trim() ? v.q : null;\n case \"select\":\n // Escaped before joining: an option value may legitimately contain the\n // separator (a free-text title, a payee or category name), and without this it\n // came back as two values and the filter silently matched nothing.\n return v.values.length ? v.values.map(escapeSelectValue).join(\",\") : null;\n case \"date\":\n return v.from || v.to ? `${v.from}..${v.to}` : null;\n case \"number\": {\n const hasRange = v.min !== \"\" || v.max !== \"\";\n if (!hasRange && !v.abs) return null;\n return `${v.min}..${v.max}${v.abs ? \"!\" : \"\"}`;\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue} given just the filter type — for\n * callers that know a column's type without holding its full config. */\nexport function decodeFilterValueOfType(type: FilterValue[\"type\"], raw: string): FilterValue {\n switch (type) {\n case \"text\":\n return { type: \"text\", q: raw };\n case \"select\":\n return {\n type: \"select\",\n values: raw\n .split(\",\")\n .filter((v) => v.length > 0)\n .map(unescapeSelectValue),\n };\n case \"date\": {\n const [from = \"\", to = \"\"] = raw.split(\"..\");\n return { type: \"date\", from, to };\n }\n case \"number\": {\n let body = raw;\n const abs = body.endsWith(\"!\");\n if (abs) body = body.slice(0, -1);\n const [min = \"\", max = \"\"] = body.split(\"..\");\n return { type: \"number\", min, max, abs };\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */\nexport function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {\n return decodeFilterValueOfType(filter.type, raw);\n}\n\n// ---------- Filter builders ----------\n//\n// Terse builders for `DataTableColumn.filter`, lifted from kastlan's column-filters.ts,\n// which every converted list page imports. The one that earns its keep is `selectFilter`:\n// in `serverPagination` mode a select filter MUST declare its options (the table would\n// otherwise derive them from the rows of the page on screen), and the options an app has\n// to hand are usually a translated `{ value: label }` map.\n\n/** A free-text filter over `getValue`. */\nexport function textFilter<T>(getValue: (row: T) => string): ColumnFilter<T> {\n return { type: \"text\", getValue };\n}\n\n/**\n * A multi-select filter. `options` is an options array, or a `{ value: label }` record\n * (a translated status map) — kept in its key order. Left out, the table derives the\n * options from the rows it holds.\n */\nexport function selectFilter<T>(\n getValue: (row: T) => string,\n options?: { value: string; label?: string }[] | Record<string, string>,\n): ColumnFilter<T> {\n const opts = Array.isArray(options)\n ? options\n : options\n ? Object.entries(options).map(([value, label]) => ({ value, label }))\n : undefined;\n return { type: \"select\", getValue, options: opts };\n}\n\n/** A from/to date filter over an ISO date (or date-time) string. */\nexport function dateFilter<T>(getValue: (row: T) => string | null | undefined): ColumnFilter<T> {\n return { type: \"date\", getValue };\n}\n\n/** A min/max (and absolute-value) filter over a number. */\nexport function numberFilter<T>(getValue: (row: T) => number | null | undefined): ColumnFilter<T> {\n return { type: \"number\", getValue };\n}\n\n/**\n * A link to a list page with one column filter already applied — in the table's own URL\n * scheme (`f.<column>=<encoded>`), which a table with `urlSync` (or\n * {@link useTableUrlState}) reads on mount. kastlan's `buildListUrl`, which had to\n * restate the `f.` prefix to do it.\n *\n * `value` is a string for a text filter, an array for a select filter, or any\n * {@link FilterValue}. A filter that narrows nothing returns `path` unchanged. A `path`\n * that already carries a query keeps it; a `#fragment` stays at the end.\n *\n * filterHref(\"/units\", \"status\", [\"vacant\"]) // \"/units?f.status=vacant\"\n */\nexport function filterHref(\n path: string,\n column: string,\n value: string | string[] | FilterValue,\n /** The target table's `urlSync` prefix, if it has one (`{ prefix: \"inv.\" }` →\n * `inv.f.<column>`). */\n options?: { prefix?: string },\n): string {\n const state: FilterValue =\n typeof value === \"string\"\n ? { type: \"text\", q: value }\n : Array.isArray(value)\n ? { type: \"select\", values: value }\n : value;\n const encoded = encodeFilterValue(state);\n if (encoded == null) return path;\n const hashAt = path.indexOf(\"#\");\n const base = hashAt >= 0 ? path.slice(0, hashAt) : path;\n const hash = hashAt >= 0 ? path.slice(hashAt) : \"\";\n const queryAt = base.indexOf(\"?\");\n const params = new URLSearchParams(queryAt >= 0 ? base.slice(queryAt + 1) : \"\");\n params.set(`${options?.prefix ?? \"\"}f.${column}`, encoded);\n return `${queryAt >= 0 ? base.slice(0, queryAt) : base}?${params.toString()}${hash}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -47,6 +47,19 @@ interface DataTableLabels {
|
|
|
47
47
|
rowCount: (total: number) => string;
|
|
48
48
|
/** The column-settings rail and its heading: "Columns (4/9)". */
|
|
49
49
|
columnsCount: (visible: number, total: number) => string;
|
|
50
|
+
/** The body of a table with no rows and no `empty` prop of its own. A sentence, not
|
|
51
|
+
* "—": a dash in the only row of a table reads as one row with a missing value. */
|
|
52
|
+
empty: string;
|
|
53
|
+
/** The accessible name of the `rowActions` column (its header is visually empty). */
|
|
54
|
+
actions: string;
|
|
55
|
+
/** The `kind: "edit"` row action's name — its tooltip and its swipe label. */
|
|
56
|
+
edit: string;
|
|
57
|
+
/** The `kind: "delete"` row action's name. */
|
|
58
|
+
delete: string;
|
|
59
|
+
/** What {@link BooleanMark} says to a screen reader for `true` / `false` / nothing. */
|
|
60
|
+
booleanTrue: string;
|
|
61
|
+
booleanFalse: string;
|
|
62
|
+
booleanUnset: string;
|
|
50
63
|
}
|
|
51
64
|
declare const DEFAULT_DATA_TABLE_LABELS: DataTableLabels;
|
|
52
65
|
/**
|
|
@@ -47,7 +47,14 @@ const DEFAULT_DATA_TABLE_LABELS = {
|
|
|
47
47
|
// The separators are the ones that shipped, so adding the seam changes no screen.
|
|
48
48
|
pageRange: (from, to, total) => `${from}\u2013${to} / ${total}`,
|
|
49
49
|
rowCount: (total) => `${total}`,
|
|
50
|
-
columnsCount: (visible, total) => `Columns (${visible}/${total})
|
|
50
|
+
columnsCount: (visible, total) => `Columns (${visible}/${total})`,
|
|
51
|
+
empty: "No entries",
|
|
52
|
+
actions: "Actions",
|
|
53
|
+
edit: "Edit",
|
|
54
|
+
delete: "Delete",
|
|
55
|
+
booleanTrue: "Yes",
|
|
56
|
+
booleanFalse: "No",
|
|
57
|
+
booleanUnset: "Not set"
|
|
51
58
|
};
|
|
52
59
|
function resolveDataTableLabels(partial) {
|
|
53
60
|
if (!partial) return DEFAULT_DATA_TABLE_LABELS;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table-labels.ts"],"sourcesContent":["// All user-facing strings for the DataTable system — the `dataTable` namespace of\n// `<UiKitProvider labels>`, and per table the `labels` prop over that. The package\n// ships no translations; the English defaults below make the table usable with no\n// wiring.\n\nexport interface DataTableLabels {\n // Table chrome\n columns: string;\n selectAllRows: string;\n sortHint: string;\n filter: string;\n close: string;\n selectRow: string;\n autoSize: string;\n loading: string;\n filters: string;\n clearAll: string;\n done: string;\n // Pagination\n pageSize: string;\n pageSizeAll: string;\n prevPage: string;\n nextPage: string;\n // Filter popover\n clearFilter: string;\n filterPlaceholder: string;\n selectFilter: string;\n selectAll: string;\n selectNone: string;\n dateFrom: string;\n dateTo: string;\n numberMin: string;\n numberMax: string;\n numberAbs: string;\n // Date-range preset labels, keyed by preset key (see dateRangePresets()).\n presets: Record<string, string>;\n\n /**\n * The table's own accessible name. Deliberately a label rather than a second,\n * parallel `tableLabel` prop: the audit already records \"three different prop\n * names for the accessible name\" as a finding, and a table that is not \"Data\n * table\" says so with `labels={{ table: \"Transactions\" }}` at the call site.\n */\n table: string;\n\n // ---- Announcements ----\n // Spoken through the live region, never rendered. Each takes its numbers as\n // ARGUMENTS: a count glued into an English template literal is untranslatable,\n // and the grammar around it moves with the number in most languages.\n\n /** After a filter changes: \"12 of 137 rows\". */\n filterResults: (shown: number, total: number) => string;\n /** After a header is activated: \"Sorted by Name, ascending\". */\n sortedAscending: (column: string) => string;\n sortedDescending: (column: string) => string;\n /** The third click, which removes the column from the sort entirely. */\n sortCleared: (column: string) => string;\n /** After paging: \"Page 3 of 14\". `page` is 1-BASED, as spoken. */\n pageChanged: (page: number, totalPages: number) => string;\n\n // ---- Counted chrome ----\n // Visible text that interpolates numbers. These were template literals until the\n // audit flagged them: a consumer could translate every key in this file and still\n // be left with English (and with ASCII digit grouping) in the footer.\n\n /** The pagination range summary: \"1–25 / 137\". */\n pageRange: (from: number, to: number, total: number) => string;\n /** The same summary when the page size is \"all\" — just the row count. */\n rowCount: (total: number) => string;\n /** The column-settings rail and its heading: \"Columns (4/9)\". */\n columnsCount: (visible: number, total: number) => string;\n}\n\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n columns: \"Columns\",\n selectAllRows: \"Select all rows\",\n sortHint: \"Click to sort · Shift-click to add a sort\",\n filter: \"Filter\",\n close: \"Close\",\n selectRow: \"Select row\",\n autoSize: \"Auto-size columns\",\n loading: \"Loading…\",\n filters: \"Filters\",\n clearAll: \"Clear all\",\n done: \"Done\",\n pageSize: \"Rows per page\",\n pageSizeAll: \"All\",\n prevPage: \"Previous page\",\n nextPage: \"Next page\",\n clearFilter: \"Clear filter\",\n filterPlaceholder: \"Filter…\",\n selectFilter: \"Select\",\n selectAll: \"All\",\n selectNone: \"None\",\n dateFrom: \"From\",\n dateTo: \"To\",\n numberMin: \"Min\",\n numberMax: \"Max\",\n numberAbs: \"Absolute value\",\n presets: {\n today: \"Today\",\n yesterday: \"Yesterday\",\n this_week: \"This week\",\n last_week: \"Last week\",\n last_7_days: \"Last 7 days\",\n last_30_days: \"Last 30 days\",\n this_month: \"This month\",\n last_month: \"Last month\",\n last_3_months: \"Last 3 months\",\n ytd: \"Year to date\",\n last_year: \"Last year\",\n },\n table: \"Data table\",\n filterResults: (shown, total) => `${shown} of ${total} rows`,\n sortedAscending: (column) => `Sorted by ${column}, ascending`,\n sortedDescending: (column) => `Sorted by ${column}, descending`,\n sortCleared: (column) => `Sort cleared on ${column}`,\n pageChanged: (page, totalPages) => `Page ${page} of ${totalPages}`,\n // The separators are the ones that shipped, so adding the seam changes no screen.\n pageRange: (from, to, total) => `${from}–${to} / ${total}`,\n rowCount: (total) => `${total}`,\n columnsCount: (visible, total) => `Columns (${visible}/${total})`,\n};\n\n/**\n * Merge caller overrides onto the English defaults (presets merged too).\n *\n * The parameter is `Partial` on purpose — a table with no labels at all still\n * renders — but that is also how a translated app ends up half English and never\n * hears about it: lenkbank ships nineteen of these keys in English in a German UI\n * today, because every one of them is backfilled silently. The kit will not start\n * throwing over it (three apps depend on the lenient merge), so\n * {@link missingDataTableLabels} is the other half: it answers which keys fell back,\n * and an app asserts on that in its OWN test, where the answer is actionable.\n */\nexport function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels {\n if (!partial) return DEFAULT_DATA_TABLE_LABELS;\n const merged: DataTableLabels = {\n ...DEFAULT_DATA_TABLE_LABELS,\n ...partial,\n presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...(partial.presets ?? {}) },\n };\n // `columnsCount` was split out of `columns` + a template literal. Consumers have\n // been translating `columns` since 0.1, and backfilling the English default over\n // that would hand them \"Columns (4/9)\" back for having translated the key that\n // existed at the time — the same silent-English failure the audit records against\n // this file. So the count is derived from the RESOLVED `columns` unless the caller\n // states the whole sentence, which is the escape hatch for a language where the\n // parenthetical does not fit.\n if (partial.columns !== undefined && partial.columnsCount === undefined) {\n merged.columnsCount = (visible, total) => `${merged.columns} (${visible}/${total})`;\n }\n return merged;\n}\n\n/**\n * The label keys `labels` does not supply, and which therefore come back English\n * from {@link resolveDataTableLabels}.\n *\n * For a consumer's own test:\n *\n * ```ts\n * expect(missingDataTableLabels(de.dataTable)).toEqual([]);\n * ```\n *\n * which fails the day the kit adds a key — which is the point. Adding a label is\n * additive here and a silent regression in a translated app, and nothing else in\n * either repository can tell the two apart.\n *\n * Preset labels are reported one at a time (`presets.yesterday`), because `presets`\n * is a `Record` merged key by key: answering `\"presets\"` would say nothing about\n * which of the eleven are still English.\n */\nexport function missingDataTableLabels(labels?: Partial<DataTableLabels>): string[] {\n const missing: string[] = [];\n for (const key of Object.keys(DEFAULT_DATA_TABLE_LABELS) as Array<keyof DataTableLabels>) {\n if (key === \"presets\") continue;\n if (labels?.[key] !== undefined) continue;\n // `columnsCount` is DERIVED from a translated `columns` by the resolver above,\n // so a caller who set that one is not leaking English here. Reporting it would\n // send them to translate a key they have already covered, and an `toEqual([])`\n // assertion built on this would never go green.\n if (key === \"columnsCount\" && labels?.columns !== undefined) continue;\n missing.push(key);\n }\n for (const preset of Object.keys(DEFAULT_DATA_TABLE_LABELS.presets)) {\n if (labels?.presets?.[preset] === undefined) missing.push(`presets.${preset}`);\n }\n return missing;\n}\n"],"mappings":";AAyEO,MAAM,4BAA6C;AAAA,EACxD,SAAS;AAAA,EACT,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,KAAK;AAAA,IACL,WAAW;AAAA,EACb;AAAA,EACA,OAAO;AAAA,EACP,eAAe,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,KAAK;AAAA,EACrD,iBAAiB,CAAC,WAAW,aAAa,MAAM;AAAA,EAChD,kBAAkB,CAAC,WAAW,aAAa,MAAM;AAAA,EACjD,aAAa,CAAC,WAAW,mBAAmB,MAAM;AAAA,EAClD,aAAa,CAAC,MAAM,eAAe,QAAQ,IAAI,OAAO,UAAU;AAAA;AAAA,EAEhE,WAAW,CAAC,MAAM,IAAI,UAAU,GAAG,IAAI,SAAI,EAAE,MAAM,KAAK;AAAA,EACxD,UAAU,CAAC,UAAU,GAAG,KAAK;AAAA,EAC7B,cAAc,CAAC,SAAS,UAAU,YAAY,OAAO,IAAI,KAAK;AAChE;AAaO,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,EAAE,GAAG,0BAA0B,SAAS,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,EAC9E;AAQA,MAAI,QAAQ,YAAY,UAAa,QAAQ,iBAAiB,QAAW;AACvE,WAAO,eAAe,CAAC,SAAS,UAAU,GAAG,OAAO,OAAO,KAAK,OAAO,IAAI,KAAK;AAAA,EAClF;AACA,SAAO;AACT;AAoBO,SAAS,uBAAuB,QAA6C;AAClF,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,OAAO,KAAK,yBAAyB,GAAmC;AACxF,QAAI,QAAQ,UAAW;AACvB,QAAI,SAAS,GAAG,MAAM,OAAW;AAKjC,QAAI,QAAQ,kBAAkB,QAAQ,YAAY,OAAW;AAC7D,YAAQ,KAAK,GAAG;AAAA,EAClB;AACA,aAAW,UAAU,OAAO,KAAK,0BAA0B,OAAO,GAAG;AACnE,QAAI,QAAQ,UAAU,MAAM,MAAM,OAAW,SAAQ,KAAK,WAAW,MAAM,EAAE;AAAA,EAC/E;AACA,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-labels.ts"],"sourcesContent":["// All user-facing strings for the DataTable system — the `dataTable` namespace of\n// `<UiKitProvider labels>`, and per table the `labels` prop over that. The package\n// ships no translations; the English defaults below make the table usable with no\n// wiring.\n\nexport interface DataTableLabels {\n // Table chrome\n columns: string;\n selectAllRows: string;\n sortHint: string;\n filter: string;\n close: string;\n selectRow: string;\n autoSize: string;\n loading: string;\n filters: string;\n clearAll: string;\n done: string;\n // Pagination\n pageSize: string;\n pageSizeAll: string;\n prevPage: string;\n nextPage: string;\n // Filter popover\n clearFilter: string;\n filterPlaceholder: string;\n selectFilter: string;\n selectAll: string;\n selectNone: string;\n dateFrom: string;\n dateTo: string;\n numberMin: string;\n numberMax: string;\n numberAbs: string;\n // Date-range preset labels, keyed by preset key (see dateRangePresets()).\n presets: Record<string, string>;\n\n /**\n * The table's own accessible name. Deliberately a label rather than a second,\n * parallel `tableLabel` prop: the audit already records \"three different prop\n * names for the accessible name\" as a finding, and a table that is not \"Data\n * table\" says so with `labels={{ table: \"Transactions\" }}` at the call site.\n */\n table: string;\n\n // ---- Announcements ----\n // Spoken through the live region, never rendered. Each takes its numbers as\n // ARGUMENTS: a count glued into an English template literal is untranslatable,\n // and the grammar around it moves with the number in most languages.\n\n /** After a filter changes: \"12 of 137 rows\". */\n filterResults: (shown: number, total: number) => string;\n /** After a header is activated: \"Sorted by Name, ascending\". */\n sortedAscending: (column: string) => string;\n sortedDescending: (column: string) => string;\n /** The third click, which removes the column from the sort entirely. */\n sortCleared: (column: string) => string;\n /** After paging: \"Page 3 of 14\". `page` is 1-BASED, as spoken. */\n pageChanged: (page: number, totalPages: number) => string;\n\n // ---- Counted chrome ----\n // Visible text that interpolates numbers. These were template literals until the\n // audit flagged them: a consumer could translate every key in this file and still\n // be left with English (and with ASCII digit grouping) in the footer.\n\n /** The pagination range summary: \"1–25 / 137\". */\n pageRange: (from: number, to: number, total: number) => string;\n /** The same summary when the page size is \"all\" — just the row count. */\n rowCount: (total: number) => string;\n /** The column-settings rail and its heading: \"Columns (4/9)\". */\n columnsCount: (visible: number, total: number) => string;\n\n // ---- Empty state, row actions, boolean cells (0.12.0) ----\n\n /** The body of a table with no rows and no `empty` prop of its own. A sentence, not\n * \"—\": a dash in the only row of a table reads as one row with a missing value. */\n empty: string;\n /** The accessible name of the `rowActions` column (its header is visually empty). */\n actions: string;\n /** The `kind: \"edit\"` row action's name — its tooltip and its swipe label. */\n edit: string;\n /** The `kind: \"delete\"` row action's name. */\n delete: string;\n /** What {@link BooleanMark} says to a screen reader for `true` / `false` / nothing. */\n booleanTrue: string;\n booleanFalse: string;\n booleanUnset: string;\n}\n\nexport const DEFAULT_DATA_TABLE_LABELS: DataTableLabels = {\n columns: \"Columns\",\n selectAllRows: \"Select all rows\",\n sortHint: \"Click to sort · Shift-click to add a sort\",\n filter: \"Filter\",\n close: \"Close\",\n selectRow: \"Select row\",\n autoSize: \"Auto-size columns\",\n loading: \"Loading…\",\n filters: \"Filters\",\n clearAll: \"Clear all\",\n done: \"Done\",\n pageSize: \"Rows per page\",\n pageSizeAll: \"All\",\n prevPage: \"Previous page\",\n nextPage: \"Next page\",\n clearFilter: \"Clear filter\",\n filterPlaceholder: \"Filter…\",\n selectFilter: \"Select\",\n selectAll: \"All\",\n selectNone: \"None\",\n dateFrom: \"From\",\n dateTo: \"To\",\n numberMin: \"Min\",\n numberMax: \"Max\",\n numberAbs: \"Absolute value\",\n presets: {\n today: \"Today\",\n yesterday: \"Yesterday\",\n this_week: \"This week\",\n last_week: \"Last week\",\n last_7_days: \"Last 7 days\",\n last_30_days: \"Last 30 days\",\n this_month: \"This month\",\n last_month: \"Last month\",\n last_3_months: \"Last 3 months\",\n ytd: \"Year to date\",\n last_year: \"Last year\",\n },\n table: \"Data table\",\n filterResults: (shown, total) => `${shown} of ${total} rows`,\n sortedAscending: (column) => `Sorted by ${column}, ascending`,\n sortedDescending: (column) => `Sorted by ${column}, descending`,\n sortCleared: (column) => `Sort cleared on ${column}`,\n pageChanged: (page, totalPages) => `Page ${page} of ${totalPages}`,\n // The separators are the ones that shipped, so adding the seam changes no screen.\n pageRange: (from, to, total) => `${from}–${to} / ${total}`,\n rowCount: (total) => `${total}`,\n columnsCount: (visible, total) => `Columns (${visible}/${total})`,\n empty: \"No entries\",\n actions: \"Actions\",\n edit: \"Edit\",\n delete: \"Delete\",\n booleanTrue: \"Yes\",\n booleanFalse: \"No\",\n booleanUnset: \"Not set\",\n};\n\n/**\n * Merge caller overrides onto the English defaults (presets merged too).\n *\n * The parameter is `Partial` on purpose — a table with no labels at all still\n * renders — but that is also how a translated app ends up half English and never\n * hears about it: lenkbank ships nineteen of these keys in English in a German UI\n * today, because every one of them is backfilled silently. The kit will not start\n * throwing over it (three apps depend on the lenient merge), so\n * {@link missingDataTableLabels} is the other half: it answers which keys fell back,\n * and an app asserts on that in its OWN test, where the answer is actionable.\n */\nexport function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels {\n if (!partial) return DEFAULT_DATA_TABLE_LABELS;\n const merged: DataTableLabels = {\n ...DEFAULT_DATA_TABLE_LABELS,\n ...partial,\n presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...(partial.presets ?? {}) },\n };\n // `columnsCount` was split out of `columns` + a template literal. Consumers have\n // been translating `columns` since 0.1, and backfilling the English default over\n // that would hand them \"Columns (4/9)\" back for having translated the key that\n // existed at the time — the same silent-English failure the audit records against\n // this file. So the count is derived from the RESOLVED `columns` unless the caller\n // states the whole sentence, which is the escape hatch for a language where the\n // parenthetical does not fit.\n if (partial.columns !== undefined && partial.columnsCount === undefined) {\n merged.columnsCount = (visible, total) => `${merged.columns} (${visible}/${total})`;\n }\n return merged;\n}\n\n/**\n * The label keys `labels` does not supply, and which therefore come back English\n * from {@link resolveDataTableLabels}.\n *\n * For a consumer's own test:\n *\n * ```ts\n * expect(missingDataTableLabels(de.dataTable)).toEqual([]);\n * ```\n *\n * which fails the day the kit adds a key — which is the point. Adding a label is\n * additive here and a silent regression in a translated app, and nothing else in\n * either repository can tell the two apart.\n *\n * Preset labels are reported one at a time (`presets.yesterday`), because `presets`\n * is a `Record` merged key by key: answering `\"presets\"` would say nothing about\n * which of the eleven are still English.\n */\nexport function missingDataTableLabels(labels?: Partial<DataTableLabels>): string[] {\n const missing: string[] = [];\n for (const key of Object.keys(DEFAULT_DATA_TABLE_LABELS) as Array<keyof DataTableLabels>) {\n if (key === \"presets\") continue;\n if (labels?.[key] !== undefined) continue;\n // `columnsCount` is DERIVED from a translated `columns` by the resolver above,\n // so a caller who set that one is not leaking English here. Reporting it would\n // send them to translate a key they have already covered, and an `toEqual([])`\n // assertion built on this would never go green.\n if (key === \"columnsCount\" && labels?.columns !== undefined) continue;\n missing.push(key);\n }\n for (const preset of Object.keys(DEFAULT_DATA_TABLE_LABELS.presets)) {\n if (labels?.presets?.[preset] === undefined) missing.push(`presets.${preset}`);\n }\n return missing;\n}\n"],"mappings":";AAyFO,MAAM,4BAA6C;AAAA,EACxD,SAAS;AAAA,EACT,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,KAAK;AAAA,IACL,WAAW;AAAA,EACb;AAAA,EACA,OAAO;AAAA,EACP,eAAe,CAAC,OAAO,UAAU,GAAG,KAAK,OAAO,KAAK;AAAA,EACrD,iBAAiB,CAAC,WAAW,aAAa,MAAM;AAAA,EAChD,kBAAkB,CAAC,WAAW,aAAa,MAAM;AAAA,EACjD,aAAa,CAAC,WAAW,mBAAmB,MAAM;AAAA,EAClD,aAAa,CAAC,MAAM,eAAe,QAAQ,IAAI,OAAO,UAAU;AAAA;AAAA,EAEhE,WAAW,CAAC,MAAM,IAAI,UAAU,GAAG,IAAI,SAAI,EAAE,MAAM,KAAK;AAAA,EACxD,UAAU,CAAC,UAAU,GAAG,KAAK;AAAA,EAC7B,cAAc,CAAC,SAAS,UAAU,YAAY,OAAO,IAAI,KAAK;AAAA,EAC9D,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAChB;AAaO,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAA0B;AAAA,IAC9B,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,EAAE,GAAG,0BAA0B,SAAS,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,EAC9E;AAQA,MAAI,QAAQ,YAAY,UAAa,QAAQ,iBAAiB,QAAW;AACvE,WAAO,eAAe,CAAC,SAAS,UAAU,GAAG,OAAO,OAAO,KAAK,OAAO,IAAI,KAAK;AAAA,EAClF;AACA,SAAO;AACT;AAoBO,SAAS,uBAAuB,QAA6C;AAClF,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,OAAO,KAAK,yBAAyB,GAAmC;AACxF,QAAI,QAAQ,UAAW;AACvB,QAAI,SAAS,GAAG,MAAM,OAAW;AAKjC,QAAI,QAAQ,kBAAkB,QAAQ,YAAY,OAAW;AAC7D,YAAQ,KAAK,GAAG;AAAA,EAClB;AACA,aAAW,UAAU,OAAO,KAAK,0BAA0B,OAAO,GAAG;AACnE,QAAI,QAAQ,UAAU,MAAM,MAAM,OAAW,SAAQ,KAAK,WAAW,MAAM,EAAE;AAAA,EAC/E;AACA,SAAO;AACT;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export { D as DataTable,
|
|
2
|
+
export { D as DataTable, x as DataTableCellProps, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, y as DataTableHeadProps, d as DataTableProps, e as DataTableRowAction, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-Drun8Fk9.js';
|
|
3
3
|
export { SortState } from './data-table-sort.js';
|
|
4
4
|
import './swipeable-row.js';
|
|
5
5
|
import './data-table-labels.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createElement } from "react";
|
|
4
|
-
import { Fragment as Fragment2, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Fragment as Fragment2, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import {
|
|
7
7
|
ArrowDown,
|
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
ChevronDown,
|
|
11
11
|
ChevronRight,
|
|
12
12
|
Filter,
|
|
13
|
+
Pencil,
|
|
14
|
+
Trash2,
|
|
13
15
|
X
|
|
14
16
|
} from "lucide-react";
|
|
15
|
-
import { Card, Spinner } from "./ui.js";
|
|
17
|
+
import { Card, IconButton, Spinner } from "./ui.js";
|
|
16
18
|
import { cn } from "../lib/cn.js";
|
|
17
19
|
import {
|
|
18
20
|
defaultFilterState,
|
|
@@ -52,6 +54,22 @@ function dataAttrsOf(props) {
|
|
|
52
54
|
for (const [k, v] of Object.entries(props)) if (k.startsWith("data-") && k !== "data-col") out[k] = v;
|
|
53
55
|
return out;
|
|
54
56
|
}
|
|
57
|
+
const ROW_ACTIONS_KEY = "__rowActions";
|
|
58
|
+
function resolveRowAction(action, index, labels) {
|
|
59
|
+
const tone = action.tone ?? (action.kind === "delete" ? "danger" : "default");
|
|
60
|
+
return {
|
|
61
|
+
key: action.kind ?? `${index}:${action.label ?? ""}`,
|
|
62
|
+
label: action.label ?? (action.kind === "edit" ? labels.edit : action.kind === "delete" ? labels.delete : ""),
|
|
63
|
+
icon: action.icon ?? (action.kind === "edit" ? /* @__PURE__ */ jsx(Pencil, {}) : action.kind === "delete" ? /* @__PURE__ */ jsx(Trash2, {}) : null),
|
|
64
|
+
tone,
|
|
65
|
+
swipe: action.swipe ?? (tone === "danger" ? "start" : "end"),
|
|
66
|
+
action
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function runRowAction(action, row) {
|
|
70
|
+
if (action.confirm && !await action.confirm(row)) return;
|
|
71
|
+
action.onAction(row);
|
|
72
|
+
}
|
|
55
73
|
function physicalSwipe(actions, dir) {
|
|
56
74
|
const rtl = dir === "rtl";
|
|
57
75
|
return {
|
|
@@ -76,10 +94,14 @@ function cleanAttrs(attrs) {
|
|
|
76
94
|
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) out[k] = v;
|
|
77
95
|
return out;
|
|
78
96
|
}
|
|
79
|
-
function columnLabel(col) {
|
|
97
|
+
function columnLabel(col, labels) {
|
|
98
|
+
if (col.key === ROW_ACTIONS_KEY && labels) return labels.actions;
|
|
80
99
|
return typeof col.header === "string" ? col.header : col.key;
|
|
81
100
|
}
|
|
82
101
|
const OWN_CONTROL = 'a[href],button,input,select,textarea,label,summary,[role="button"],[role="link"],[role="checkbox"],[role="switch"],[role="menuitem"],[role="option"],[contenteditable=""],[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
|
|
102
|
+
function fromOutside(target, row) {
|
|
103
|
+
return !(target instanceof Node) || !row.contains(target);
|
|
104
|
+
}
|
|
83
105
|
function fromOwnControl(target, row) {
|
|
84
106
|
if (!(target instanceof Element)) return false;
|
|
85
107
|
const hit = target.closest(OWN_CONTROL);
|
|
@@ -114,9 +136,11 @@ function RowLink({
|
|
|
114
136
|
draggable: false,
|
|
115
137
|
className,
|
|
116
138
|
onClickCapture: (e) => {
|
|
139
|
+
if (fromOutside(e.target, e.currentTarget)) return;
|
|
117
140
|
if (isPlainLeftClick(e)) e.preventDefault();
|
|
118
141
|
},
|
|
119
142
|
onClick: (e) => {
|
|
143
|
+
if (fromOutside(e.target, e.currentTarget)) return;
|
|
120
144
|
if (!isPlainLeftClick(e)) {
|
|
121
145
|
e.stopPropagation();
|
|
122
146
|
return;
|
|
@@ -131,7 +155,7 @@ import { DEFAULT_PERSIST_PREFIX, useTableState } from "./use-table-state.js";
|
|
|
131
155
|
import { useMobileReveal } from "./use-mobile-reveal.js";
|
|
132
156
|
function DataTable({
|
|
133
157
|
rows,
|
|
134
|
-
columns,
|
|
158
|
+
columns: columnsProp,
|
|
135
159
|
rowKey,
|
|
136
160
|
defaultPageSize = 25,
|
|
137
161
|
onRowClick,
|
|
@@ -168,7 +192,9 @@ function DataTable({
|
|
|
168
192
|
resizable: resizableProp,
|
|
169
193
|
multiSort: multiSortProp,
|
|
170
194
|
frame = true,
|
|
171
|
-
className
|
|
195
|
+
className,
|
|
196
|
+
rowActions,
|
|
197
|
+
toolbar
|
|
172
198
|
}) {
|
|
173
199
|
const compact = density === "compact";
|
|
174
200
|
const minimal = chrome === "minimal";
|
|
@@ -189,6 +215,54 @@ function DataTable({
|
|
|
189
215
|
[labelOverrides, labelsProp]
|
|
190
216
|
);
|
|
191
217
|
const locale = useKitLocale(localeProp);
|
|
218
|
+
const rowActionsFor = useCallback(
|
|
219
|
+
(row) => rowActions ? (typeof rowActions === "function" ? rowActions(row) : rowActions).filter((a) => !a.hidden?.(row)).map((a, i) => resolveRowAction(a, i, labels)) : [],
|
|
220
|
+
[rowActions, labels]
|
|
221
|
+
);
|
|
222
|
+
const columns = useMemo(
|
|
223
|
+
() => rowActions ? [
|
|
224
|
+
...columnsProp,
|
|
225
|
+
{
|
|
226
|
+
key: ROW_ACTIONS_KEY,
|
|
227
|
+
header: /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.actions }),
|
|
228
|
+
cell: (row) => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-0.5", children: rowActionsFor(row).map((a) => /* @__PURE__ */ jsx(
|
|
229
|
+
IconButton,
|
|
230
|
+
{
|
|
231
|
+
size: density === "compact" ? "2xs" : "xs",
|
|
232
|
+
label: a.label,
|
|
233
|
+
tone: a.tone === "danger" ? "danger" : void 0,
|
|
234
|
+
disabledReason: a.action.disabledReason?.(row),
|
|
235
|
+
stopPropagation: true,
|
|
236
|
+
onClick: () => void runRowAction(a.action, row),
|
|
237
|
+
children: a.icon
|
|
238
|
+
},
|
|
239
|
+
a.key
|
|
240
|
+
)) }),
|
|
241
|
+
className: "w-px whitespace-nowrap text-end",
|
|
242
|
+
headClassName: "w-px",
|
|
243
|
+
// On a phone the same actions are the card's swipes.
|
|
244
|
+
mobileHidden: true,
|
|
245
|
+
noRowLink: true
|
|
246
|
+
}
|
|
247
|
+
] : columnsProp,
|
|
248
|
+
[columnsProp, rowActions, rowActionsFor, labels.actions, density]
|
|
249
|
+
);
|
|
250
|
+
const swipesFromActions = (row) => {
|
|
251
|
+
const swipes = rowActionsFor(row).filter((a) => a.swipe && !a.action.disabledReason?.(row));
|
|
252
|
+
if (!swipes.length) return null;
|
|
253
|
+
const toSwipe = (a) => ({
|
|
254
|
+
label: a.label,
|
|
255
|
+
icon: a.icon,
|
|
256
|
+
onCommit: () => void runRowAction(a.action, row),
|
|
257
|
+
className: a.tone === "danger" ? "bg-[var(--danger)]" : "bg-[var(--brand)]",
|
|
258
|
+
armedClassName: a.tone === "danger" ? "bg-[var(--danger-hover)]" : "bg-[var(--brand-hover)]"
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
start: swipes.filter((a) => a.swipe === "start").map(toSwipe),
|
|
262
|
+
end: swipes.filter((a) => a.swipe === "end").map(toSwipe)
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
const swipeActionsFor = mobileSwipeActions ?? (rowActions ? swipesFromActions : void 0);
|
|
192
266
|
const { announce, regionProps } = useAnnounce();
|
|
193
267
|
const {
|
|
194
268
|
sorts,
|
|
@@ -291,7 +365,7 @@ function DataTable({
|
|
|
291
365
|
});
|
|
292
366
|
if (onSortsChange) onSortsChange(next);
|
|
293
367
|
else setInternalSorts(next);
|
|
294
|
-
const name = col ? columnLabel(col) : key;
|
|
368
|
+
const name = col ? columnLabel(col, labels) : key;
|
|
295
369
|
const entry = next.find((s) => s.key === key);
|
|
296
370
|
announce(
|
|
297
371
|
!entry ? labels.sortCleared(name) : entry.dir === "asc" ? labels.sortedAscending(name) : labels.sortedDescending(name)
|
|
@@ -443,7 +517,7 @@ function DataTable({
|
|
|
443
517
|
const visibleCount = visibleColumns.length;
|
|
444
518
|
const totalCount = columns.length;
|
|
445
519
|
const totalColSpan = visibleCount + (selection ? 1 : 0);
|
|
446
|
-
const columnsCountLabel = labels.columnsCount(visibleCount, totalCount);
|
|
520
|
+
const columnsCountLabel = rowActions ? labels.columnsCount(visibleCount - 1, totalCount - 1) : labels.columnsCount(visibleCount, totalCount);
|
|
447
521
|
const isMdUp = useMediaQuery("(min-width: 768px)", true);
|
|
448
522
|
const mobileColumns = visibleColumns.filter((c) => !c.mobileHidden);
|
|
449
523
|
const mobilePrimaryCol = mobileColumns.find((c) => c.mobilePrimary) ?? mobileColumns[0] ?? null;
|
|
@@ -495,7 +569,7 @@ function DataTable({
|
|
|
495
569
|
const tint = rowClassName?.(row);
|
|
496
570
|
const interactive = !!onRowClick;
|
|
497
571
|
const opensDetail = interactive && !!expandedRow;
|
|
498
|
-
const swipeActions =
|
|
572
|
+
const swipeActions = swipeActionsFor?.(row);
|
|
499
573
|
const swipe = swipeActions ? physicalSwipe(swipeActions, mobileDir) : null;
|
|
500
574
|
const swipeEnabled = !!swipe && !expanded;
|
|
501
575
|
const href = mobileCardLinkable ? rowHref?.(row) : void 0;
|
|
@@ -581,7 +655,10 @@ function DataTable({
|
|
|
581
655
|
{
|
|
582
656
|
role: interactive ? "button" : void 0,
|
|
583
657
|
tabIndex: interactive ? 0 : void 0,
|
|
584
|
-
onClick: interactive ? () =>
|
|
658
|
+
onClick: interactive ? (e) => {
|
|
659
|
+
if (fromOutside(e.target, e.currentTarget)) return;
|
|
660
|
+
onRowClick(row);
|
|
661
|
+
} : void 0,
|
|
585
662
|
onKeyDown: interactive ? (e) => {
|
|
586
663
|
if (e.target !== e.currentTarget) return;
|
|
587
664
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -630,6 +707,17 @@ function DataTable({
|
|
|
630
707
|
),
|
|
631
708
|
children: [
|
|
632
709
|
/* @__PURE__ */ jsx("span", { ...regionProps }),
|
|
710
|
+
toolbar != null && toolbar !== false && /* @__PURE__ */ jsx(
|
|
711
|
+
"div",
|
|
712
|
+
{
|
|
713
|
+
"data-table-toolbar": "",
|
|
714
|
+
className: cn(
|
|
715
|
+
"flex flex-wrap items-center gap-2",
|
|
716
|
+
frame ? cn("border-b border-[var(--border)]", compact ? "px-2 py-1.5" : "px-3 py-2") : "pb-3"
|
|
717
|
+
),
|
|
718
|
+
children: toolbar
|
|
719
|
+
}
|
|
720
|
+
),
|
|
633
721
|
!isMdUp && /* @__PURE__ */ jsxs("div", { ref: mobileRootRef, children: [
|
|
634
722
|
(!isServer || onFiltersChange) && /* @__PURE__ */ jsx(
|
|
635
723
|
MobileFilters,
|
|
@@ -667,7 +755,7 @@ function DataTable({
|
|
|
667
755
|
"text-center text-[var(--text-muted)]",
|
|
668
756
|
compact ? "px-3 py-3 text-xs" : "px-4 py-6 text-sm"
|
|
669
757
|
),
|
|
670
|
-
children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ??
|
|
758
|
+
children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? labels.empty
|
|
671
759
|
}
|
|
672
760
|
),
|
|
673
761
|
canRevealMoreMobile && /* @__PURE__ */ jsx(
|
|
@@ -842,7 +930,7 @@ function DataTable({
|
|
|
842
930
|
]
|
|
843
931
|
}
|
|
844
932
|
),
|
|
845
|
-
resizable && /* @__PURE__ */ jsx(
|
|
933
|
+
resizable && col.key !== ROW_ACTIONS_KEY && /* @__PURE__ */ jsx(
|
|
846
934
|
"span",
|
|
847
935
|
{
|
|
848
936
|
role: "separator",
|
|
@@ -907,6 +995,7 @@ function DataTable({
|
|
|
907
995
|
),
|
|
908
996
|
onClick: rowInteractive ? (e) => {
|
|
909
997
|
if (fromOwnControl(e.target, e.currentTarget)) return;
|
|
998
|
+
if (fromOutside(e.target, e.currentTarget)) return;
|
|
910
999
|
if (selection && canSelect(row)) {
|
|
911
1000
|
if (e.shiftKey) {
|
|
912
1001
|
window.getSelection?.()?.removeAllRanges();
|
|
@@ -992,7 +1081,7 @@ function DataTable({
|
|
|
992
1081
|
{
|
|
993
1082
|
colSpan: totalColSpan,
|
|
994
1083
|
className: cn("text-center text-[var(--text-muted)]", compact ? "px-2 py-2" : "px-3 py-4"),
|
|
995
|
-
children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ??
|
|
1084
|
+
children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? labels.empty
|
|
996
1085
|
}
|
|
997
1086
|
) })
|
|
998
1087
|
]
|
|
@@ -1077,6 +1166,7 @@ function DataTable({
|
|
|
1077
1166
|
}
|
|
1078
1167
|
),
|
|
1079
1168
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: columns.map((col) => {
|
|
1169
|
+
if (col.key === ROW_ACTIONS_KEY) return null;
|
|
1080
1170
|
const checked = !hiddenCols.has(col.key);
|
|
1081
1171
|
const headerLabel = columnLabel(col);
|
|
1082
1172
|
return /* @__PURE__ */ jsxs(
|