@eifi1/ui-kit 0.4.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 +219 -0
- package/dist/components/account-settings.d.ts +71 -0
- package/dist/components/account-settings.js +159 -0
- package/dist/components/account-settings.js.map +1 -0
- package/dist/components/alert-banner.d.ts +20 -0
- package/dist/components/alert-banner.js +39 -0
- package/dist/components/alert-banner.js.map +1 -0
- package/dist/components/amount-input.d.ts +117 -0
- package/dist/components/amount-input.js +243 -0
- package/dist/components/amount-input.js.map +1 -0
- package/dist/components/calculator.d.ts +42 -0
- package/dist/components/calculator.js +161 -0
- package/dist/components/calculator.js.map +1 -0
- package/dist/components/chart.d.ts +58 -0
- package/dist/components/chart.js +158 -0
- package/dist/components/chart.js.map +1 -0
- package/dist/components/combobox-core.d.ts +77 -0
- package/dist/components/combobox-core.js +263 -0
- package/dist/components/combobox-core.js.map +1 -0
- package/dist/components/combobox.d.ts +141 -0
- package/dist/components/combobox.js +550 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/currency-select.d.ts +37 -0
- package/dist/components/currency-select.js +131 -0
- package/dist/components/currency-select.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +28 -0
- package/dist/components/data-table-filter-popover.js +201 -0
- package/dist/components/data-table-filter-popover.js.map +1 -0
- package/dist/components/data-table-filters.d.ts +5 -0
- package/dist/components/data-table-filters.js +118 -0
- package/dist/components/data-table-filters.js.map +1 -0
- package/dist/components/data-table-labels.d.ts +33 -0
- package/dist/components/data-table-labels.js +53 -0
- package/dist/components/data-table-labels.js.map +1 -0
- package/dist/components/data-table-pagination.d.ts +18 -0
- package/dist/components/data-table-pagination.js +92 -0
- package/dist/components/data-table-pagination.js.map +1 -0
- package/dist/components/data-table-sort.d.ts +23 -0
- package/dist/components/data-table-sort.js +55 -0
- package/dist/components/data-table-sort.js.map +1 -0
- package/dist/components/data-table.d.ts +5 -0
- package/dist/components/data-table.js +926 -0
- package/dist/components/data-table.js.map +1 -0
- package/dist/components/date-picker.d.ts +85 -0
- package/dist/components/date-picker.js +253 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dropdown.d.ts +98 -0
- package/dist/components/dropdown.js +122 -0
- package/dist/components/dropdown.js.map +1 -0
- package/dist/components/entity-combobox.d.ts +49 -0
- package/dist/components/entity-combobox.js +115 -0
- package/dist/components/entity-combobox.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +18 -0
- package/dist/components/file-dropzone.js +101 -0
- package/dist/components/file-dropzone.js.map +1 -0
- package/dist/components/full-bleed-dialog.d.ts +57 -0
- package/dist/components/full-bleed-dialog.js +68 -0
- package/dist/components/full-bleed-dialog.js.map +1 -0
- package/dist/components/grouped-picker.d.ts +37 -0
- package/dist/components/grouped-picker.js +87 -0
- package/dist/components/grouped-picker.js.map +1 -0
- package/dist/components/hover-menu.d.ts +16 -0
- package/dist/components/hover-menu.js +113 -0
- package/dist/components/hover-menu.js.map +1 -0
- package/dist/components/mini-calendar.d.ts +33 -0
- package/dist/components/mini-calendar.js +133 -0
- package/dist/components/mini-calendar.js.map +1 -0
- package/dist/components/modal.d.ts +76 -0
- package/dist/components/modal.js +156 -0
- package/dist/components/modal.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +47 -0
- package/dist/components/multi-entity-combobox.js +116 -0
- package/dist/components/multi-entity-combobox.js.map +1 -0
- package/dist/components/multi-select.d.ts +39 -0
- package/dist/components/multi-select.js +132 -0
- package/dist/components/multi-select.js.map +1 -0
- package/dist/components/number-input.d.ts +74 -0
- package/dist/components/number-input.js +114 -0
- package/dist/components/number-input.js.map +1 -0
- package/dist/components/numpad-sheet.d.ts +45 -0
- package/dist/components/numpad-sheet.js +102 -0
- package/dist/components/numpad-sheet.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +44 -0
- package/dist/components/picker-sheet.js +78 -0
- package/dist/components/picker-sheet.js.map +1 -0
- package/dist/components/popover.d.ts +20 -0
- package/dist/components/popover.js +46 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/search-field.d.ts +52 -0
- package/dist/components/search-field.js +64 -0
- package/dist/components/search-field.js.map +1 -0
- package/dist/components/settings-fields.d.ts +39 -0
- package/dist/components/settings-fields.js +17 -0
- package/dist/components/settings-fields.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +60 -0
- package/dist/components/swipeable-row.js +115 -0
- package/dist/components/swipeable-row.js.map +1 -0
- package/dist/components/toggle-group.d.ts +34 -0
- package/dist/components/toggle-group.js +90 -0
- package/dist/components/toggle-group.js.map +1 -0
- package/dist/components/tooltip.d.ts +86 -0
- package/dist/components/tooltip.js +171 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/ui.d.ts +239 -0
- package/dist/components/ui.js +512 -0
- package/dist/components/ui.js.map +1 -0
- package/dist/components/use-mobile-reveal.d.ts +31 -0
- package/dist/components/use-mobile-reveal.js +33 -0
- package/dist/components/use-mobile-reveal.js.map +1 -0
- package/dist/components/use-table-state.d.ts +54 -0
- package/dist/components/use-table-state.js +153 -0
- package/dist/components/use-table-state.js.map +1 -0
- package/dist/components/user-avatar.d.ts +23 -0
- package/dist/components/user-avatar.js +33 -0
- package/dist/components/user-avatar.js.map +1 -0
- package/dist/components/wizard-stepper.d.ts +14 -0
- package/dist/components/wizard-stepper.js +27 -0
- package/dist/components/wizard-stepper.js.map +1 -0
- package/dist/data-table-filters-aXyf0Xub.d.ts +261 -0
- package/dist/feedback/feedback-attachment.d.ts +63 -0
- package/dist/feedback/feedback-attachment.js +160 -0
- package/dist/feedback/feedback-attachment.js.map +1 -0
- package/dist/feedback/feedback-dialog.d.ts +72 -0
- package/dist/feedback/feedback-dialog.js +109 -0
- package/dist/feedback/feedback-dialog.js.map +1 -0
- package/dist/feedback/feedback-inbox.d.ts +226 -0
- package/dist/feedback/feedback-inbox.js +315 -0
- package/dist/feedback/feedback-inbox.js.map +1 -0
- package/dist/hooks/use-anchored-panel.d.ts +80 -0
- package/dist/hooks/use-anchored-panel.js +71 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -0
- package/dist/hooks/use-anchored-rect.d.ts +24 -0
- package/dist/hooks/use-anchored-rect.js +40 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.d.ts +9 -0
- package/dist/hooks/use-body-scroll-lock.js +41 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -0
- package/dist/hooks/use-close-transition.d.ts +41 -0
- package/dist/hooks/use-close-transition.js +36 -0
- package/dist/hooks/use-close-transition.js.map +1 -0
- package/dist/hooks/use-dismiss.d.ts +34 -0
- package/dist/hooks/use-dismiss.js +41 -0
- package/dist/hooks/use-dismiss.js.map +1 -0
- package/dist/hooks/use-media-query.d.ts +8 -0
- package/dist/hooks/use-media-query.js +18 -0
- package/dist/hooks/use-media-query.js.map +1 -0
- package/dist/hooks/use-overlay-history.d.ts +10 -0
- package/dist/hooks/use-overlay-history.js +99 -0
- package/dist/hooks/use-overlay-history.js.map +1 -0
- package/dist/hooks/use-row-swipe.d.ts +30 -0
- package/dist/hooks/use-row-swipe.js +128 -0
- package/dist/hooks/use-row-swipe.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/calc.d.ts +89 -0
- package/dist/lib/calc.js +180 -0
- package/dist/lib/calc.js.map +1 -0
- package/dist/lib/cn.d.ts +6 -0
- package/dist/lib/cn.js +7 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/dates.d.ts +84 -0
- package/dist/lib/dates.js +108 -0
- package/dist/lib/dates.js.map +1 -0
- package/dist/lib/logger.d.ts +17 -0
- package/dist/lib/logger.js +30 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/safe-storage.d.ts +23 -0
- package/dist/lib/safe-storage.js +20 -0
- package/dist/lib/safe-storage.js.map +1 -0
- package/dist/search/command-palette.d.ts +71 -0
- package/dist/search/command-palette.js +198 -0
- package/dist/search/command-palette.js.map +1 -0
- package/dist/shell/app-shell.d.ts +64 -0
- package/dist/shell/app-shell.js +237 -0
- package/dist/shell/app-shell.js.map +1 -0
- package/dist/shell/option-switcher-menu.d.ts +24 -0
- package/dist/shell/option-switcher-menu.js +44 -0
- package/dist/shell/option-switcher-menu.js.map +1 -0
- package/dist/shell/role-switcher.d.ts +19 -0
- package/dist/shell/role-switcher.js +28 -0
- package/dist/shell/role-switcher.js.map +1 -0
- package/dist/shell/top-bar.d.ts +15 -0
- package/dist/shell/top-bar.js +29 -0
- package/dist/shell/top-bar.js.map +1 -0
- package/dist/shell/topbar-action-menu.d.ts +44 -0
- package/dist/shell/topbar-action-menu.js +49 -0
- package/dist/shell/topbar-action-menu.js.map +1 -0
- package/dist/shell/topbar-controls.d.ts +54 -0
- package/dist/shell/topbar-controls.js +152 -0
- package/dist/shell/topbar-controls.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +60 -0
- package/dist/theme/chart-palette.js +79 -0
- package/dist/theme/chart-palette.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +39 -0
- package/dist/theme/palette-presets.js +440 -0
- package/dist/theme/palette-presets.js.map +1 -0
- package/dist/theme/palette-store.d.ts +45 -0
- package/dist/theme/palette-store.js +52 -0
- package/dist/theme/palette-store.js.map +1 -0
- package/dist/theme/theme-store.d.ts +57 -0
- package/dist/theme/theme-store.js +96 -0
- package/dist/theme/theme-store.js.map +1 -0
- package/dist/tour/tour.d.ts +104 -0
- package/dist/tour/tour.js +351 -0
- package/dist/tour/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.d.ts +30 -0
- package/dist/wizard/stepper-nav.js +146 -0
- package/dist/wizard/stepper-nav.js.map +1 -0
- package/dist/wizard/types.d.ts +124 -0
- package/dist/wizard/types.js +24 -0
- package/dist/wizard/types.js.map +1 -0
- package/dist/wizard/use-wizard.d.ts +6 -0
- package/dist/wizard/use-wizard.js +222 -0
- package/dist/wizard/use-wizard.js.map +1 -0
- package/dist/wizard/validation.d.ts +23 -0
- package/dist/wizard/validation.js +13 -0
- package/dist/wizard/validation.js.map +1 -0
- package/dist/wizard/wizard-context.d.ts +19 -0
- package/dist/wizard/wizard-context.js +15 -0
- package/dist/wizard/wizard-context.js.map +1 -0
- package/dist/wizard/wizard-step.d.ts +11 -0
- package/dist/wizard/wizard-step.js +12 -0
- package/dist/wizard/wizard-step.js.map +1 -0
- package/dist/wizard/wizard-summary.d.ts +18 -0
- package/dist/wizard/wizard-summary.js +41 -0
- package/dist/wizard/wizard-summary.js.map +1 -0
- package/package.json +87 -0
- package/tokens.css +254 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
function resolveFilter(col) {
|
|
2
|
+
if (col.filter) return col.filter;
|
|
3
|
+
if (col.filterBy) return { type: "text", getValue: col.filterBy };
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
function isFilterActive(v) {
|
|
7
|
+
if (!v) return false;
|
|
8
|
+
switch (v.type) {
|
|
9
|
+
case "text":
|
|
10
|
+
return v.q.trim().length > 0;
|
|
11
|
+
case "select":
|
|
12
|
+
return v.values.length > 0;
|
|
13
|
+
case "date":
|
|
14
|
+
return !!v.from || !!v.to;
|
|
15
|
+
case "number":
|
|
16
|
+
return v.min !== "" || v.max !== "" || v.abs;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function rowMatches(col, row, state) {
|
|
20
|
+
const filter = resolveFilter(col);
|
|
21
|
+
if (!filter) return true;
|
|
22
|
+
switch (filter.type) {
|
|
23
|
+
case "text": {
|
|
24
|
+
if (state.type !== "text" || !state.q.trim()) return true;
|
|
25
|
+
return filter.getValue(row).toLowerCase().includes(state.q.trim().toLowerCase());
|
|
26
|
+
}
|
|
27
|
+
case "select": {
|
|
28
|
+
if (state.type !== "select" || state.values.length === 0) return true;
|
|
29
|
+
return state.values.includes(filter.getValue(row));
|
|
30
|
+
}
|
|
31
|
+
case "date": {
|
|
32
|
+
if (state.type !== "date") return true;
|
|
33
|
+
const raw = filter.getValue(row);
|
|
34
|
+
if (!raw) return !state.from && !state.to;
|
|
35
|
+
const v = raw.slice(0, 10);
|
|
36
|
+
if (state.from && v < state.from) return false;
|
|
37
|
+
if (state.to && v > state.to) return false;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
case "number": {
|
|
41
|
+
if (state.type !== "number") return true;
|
|
42
|
+
const raw = filter.getValue(row);
|
|
43
|
+
if (raw == null || Number.isNaN(raw)) return !state.min && !state.max;
|
|
44
|
+
const x = state.abs ? Math.abs(raw) : raw;
|
|
45
|
+
if (state.min !== "" && x < Number(state.min)) return false;
|
|
46
|
+
if (state.max !== "" && x > Number(state.max)) return false;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function defaultFilterState(filter) {
|
|
52
|
+
switch (filter.type) {
|
|
53
|
+
case "text":
|
|
54
|
+
return { type: "text", q: "" };
|
|
55
|
+
case "select":
|
|
56
|
+
return { type: "select", values: [] };
|
|
57
|
+
case "date":
|
|
58
|
+
return { type: "date", from: "", to: "" };
|
|
59
|
+
case "number":
|
|
60
|
+
return { type: "number", min: "", max: "", abs: false };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function escapeSelectValue(v) {
|
|
64
|
+
return v.replace(/%/g, "%25").replace(/,/g, "%2C");
|
|
65
|
+
}
|
|
66
|
+
function unescapeSelectValue(v) {
|
|
67
|
+
return v.replace(/%2C/g, ",").replace(/%25/g, "%");
|
|
68
|
+
}
|
|
69
|
+
function encodeFilterValue(v) {
|
|
70
|
+
switch (v.type) {
|
|
71
|
+
case "text":
|
|
72
|
+
return v.q.trim() ? v.q : null;
|
|
73
|
+
case "select":
|
|
74
|
+
return v.values.length ? v.values.map(escapeSelectValue).join(",") : null;
|
|
75
|
+
case "date":
|
|
76
|
+
return v.from || v.to ? `${v.from}..${v.to}` : null;
|
|
77
|
+
case "number": {
|
|
78
|
+
const hasRange = v.min !== "" || v.max !== "";
|
|
79
|
+
if (!hasRange && !v.abs) return null;
|
|
80
|
+
return `${v.min}..${v.max}${v.abs ? "!" : ""}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function decodeFilterValueOfType(type, raw) {
|
|
85
|
+
switch (type) {
|
|
86
|
+
case "text":
|
|
87
|
+
return { type: "text", q: raw };
|
|
88
|
+
case "select":
|
|
89
|
+
return {
|
|
90
|
+
type: "select",
|
|
91
|
+
values: raw.split(",").filter((v) => v.length > 0).map(unescapeSelectValue)
|
|
92
|
+
};
|
|
93
|
+
case "date": {
|
|
94
|
+
const [from = "", to = ""] = raw.split("..");
|
|
95
|
+
return { type: "date", from, to };
|
|
96
|
+
}
|
|
97
|
+
case "number": {
|
|
98
|
+
let body = raw;
|
|
99
|
+
const abs = body.endsWith("!");
|
|
100
|
+
if (abs) body = body.slice(0, -1);
|
|
101
|
+
const [min = "", max = ""] = body.split("..");
|
|
102
|
+
return { type: "number", min, max, abs };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function decodeFilterValue(filter, raw) {
|
|
107
|
+
return decodeFilterValueOfType(filter.type, raw);
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
decodeFilterValue,
|
|
111
|
+
decodeFilterValueOfType,
|
|
112
|
+
defaultFilterState,
|
|
113
|
+
encodeFilterValue,
|
|
114
|
+
isFilterActive,
|
|
115
|
+
resolveFilter,
|
|
116
|
+
rowMatches
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=data-table-filters.js.map
|
|
@@ -0,0 +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/** Whether `row` passes column `col`'s filter given the user's `state`. */\nexport function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue): 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.getValue(row).toLowerCase().includes(state.q.trim().toLowerCase());\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":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface DataTableLabels {
|
|
2
|
+
columns: string;
|
|
3
|
+
selectAllRows: string;
|
|
4
|
+
sortHint: string;
|
|
5
|
+
filter: string;
|
|
6
|
+
close: string;
|
|
7
|
+
selectRow: string;
|
|
8
|
+
autoSize: string;
|
|
9
|
+
loading: string;
|
|
10
|
+
filters: string;
|
|
11
|
+
clearAll: string;
|
|
12
|
+
done: string;
|
|
13
|
+
pageSize: string;
|
|
14
|
+
pageSizeAll: string;
|
|
15
|
+
prevPage: string;
|
|
16
|
+
nextPage: string;
|
|
17
|
+
clearFilter: string;
|
|
18
|
+
filterPlaceholder: string;
|
|
19
|
+
selectFilter: string;
|
|
20
|
+
selectAll: string;
|
|
21
|
+
selectNone: string;
|
|
22
|
+
dateFrom: string;
|
|
23
|
+
dateTo: string;
|
|
24
|
+
numberMin: string;
|
|
25
|
+
numberMax: string;
|
|
26
|
+
numberAbs: string;
|
|
27
|
+
presets: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
declare const DEFAULT_DATA_TABLE_LABELS: DataTableLabels;
|
|
30
|
+
/** Merge caller overrides onto the English defaults (presets merged too). */
|
|
31
|
+
declare function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels;
|
|
32
|
+
|
|
33
|
+
export { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels, resolveDataTableLabels };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const DEFAULT_DATA_TABLE_LABELS = {
|
|
2
|
+
columns: "Columns",
|
|
3
|
+
selectAllRows: "Select all rows",
|
|
4
|
+
sortHint: "Click to sort \xB7 Shift-click to add a sort",
|
|
5
|
+
filter: "Filter",
|
|
6
|
+
close: "Close",
|
|
7
|
+
selectRow: "Select row",
|
|
8
|
+
autoSize: "Auto-size columns",
|
|
9
|
+
loading: "Loading\u2026",
|
|
10
|
+
filters: "Filters",
|
|
11
|
+
clearAll: "Clear all",
|
|
12
|
+
done: "Done",
|
|
13
|
+
pageSize: "Rows per page",
|
|
14
|
+
pageSizeAll: "All",
|
|
15
|
+
prevPage: "Previous page",
|
|
16
|
+
nextPage: "Next page",
|
|
17
|
+
clearFilter: "Clear filter",
|
|
18
|
+
filterPlaceholder: "Filter\u2026",
|
|
19
|
+
selectFilter: "Select",
|
|
20
|
+
selectAll: "All",
|
|
21
|
+
selectNone: "None",
|
|
22
|
+
dateFrom: "From",
|
|
23
|
+
dateTo: "To",
|
|
24
|
+
numberMin: "Min",
|
|
25
|
+
numberMax: "Max",
|
|
26
|
+
numberAbs: "Absolute value",
|
|
27
|
+
presets: {
|
|
28
|
+
today: "Today",
|
|
29
|
+
yesterday: "Yesterday",
|
|
30
|
+
this_week: "This week",
|
|
31
|
+
last_week: "Last week",
|
|
32
|
+
last_7_days: "Last 7 days",
|
|
33
|
+
last_30_days: "Last 30 days",
|
|
34
|
+
this_month: "This month",
|
|
35
|
+
last_month: "Last month",
|
|
36
|
+
last_3_months: "Last 3 months",
|
|
37
|
+
ytd: "Year to date",
|
|
38
|
+
last_year: "Last year"
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function resolveDataTableLabels(partial) {
|
|
42
|
+
if (!partial) return DEFAULT_DATA_TABLE_LABELS;
|
|
43
|
+
return {
|
|
44
|
+
...DEFAULT_DATA_TABLE_LABELS,
|
|
45
|
+
...partial,
|
|
46
|
+
presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...partial.presets ?? {} }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
DEFAULT_DATA_TABLE_LABELS,
|
|
51
|
+
resolveDataTableLabels
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=data-table-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-labels.ts"],"sourcesContent":["// All user-facing strings for the DataTable system, injected as props so the\n// package stays i18n-free. Consumers pass translated overrides; the English\n// defaults below make the table usable with no 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\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};\n\n/** Merge caller overrides onto the English defaults (presets merged too). */\nexport function resolveDataTableLabels(partial?: Partial<DataTableLabels>): DataTableLabels {\n if (!partial) return DEFAULT_DATA_TABLE_LABELS;\n return {\n ...DEFAULT_DATA_TABLE_LABELS,\n ...partial,\n presets: { ...DEFAULT_DATA_TABLE_LABELS.presets, ...(partial.presets ?? {}) },\n };\n}\n"],"mappings":"AAqCO,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;AACF;AAGO,SAAS,uBAAuB,SAAqD;AAC1F,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,EAAE,GAAG,0BAA0B,SAAS,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,EAC9E;AACF;","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { DataTableLabels } from './data-table-labels.js';
|
|
3
|
+
|
|
4
|
+
declare const PAGE_SIZE_OPTIONS: readonly [10, 25, 50, 100, 200];
|
|
5
|
+
interface PaginationProps {
|
|
6
|
+
page: number;
|
|
7
|
+
totalPages: number;
|
|
8
|
+
pageSize: number;
|
|
9
|
+
total: number;
|
|
10
|
+
onPage: (p: number) => void;
|
|
11
|
+
onPageSize: (n: number) => void;
|
|
12
|
+
labels?: DataTableLabels;
|
|
13
|
+
}
|
|
14
|
+
/** Footer for {@link DataTable}: range summary, page-size select, and a
|
|
15
|
+
* windowed page-number strip (first/last + ±2 around the current page). */
|
|
16
|
+
declare function Pagination({ page, totalPages, pageSize, total, onPage, onPageSize, labels, }: PaginationProps): react.JSX.Element;
|
|
17
|
+
|
|
18
|
+
export { PAGE_SIZE_OPTIONS, Pagination };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { DEFAULT_DATA_TABLE_LABELS } from "./data-table-labels.js";
|
|
6
|
+
const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200];
|
|
7
|
+
function Pagination({
|
|
8
|
+
page,
|
|
9
|
+
totalPages,
|
|
10
|
+
pageSize,
|
|
11
|
+
total,
|
|
12
|
+
onPage,
|
|
13
|
+
onPageSize,
|
|
14
|
+
labels = DEFAULT_DATA_TABLE_LABELS
|
|
15
|
+
}) {
|
|
16
|
+
const visible = useMemo(() => {
|
|
17
|
+
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i);
|
|
18
|
+
const set = /* @__PURE__ */ new Set([0, totalPages - 1, page]);
|
|
19
|
+
for (let d = 1; d <= 2; d++) {
|
|
20
|
+
if (page - d >= 0) set.add(page - d);
|
|
21
|
+
if (page + d <= totalPages - 1) set.add(page + d);
|
|
22
|
+
}
|
|
23
|
+
return Array.from(set).sort((a, b) => a - b);
|
|
24
|
+
}, [page, totalPages]);
|
|
25
|
+
const items = [];
|
|
26
|
+
visible.forEach((p, idx) => {
|
|
27
|
+
if (idx > 0 && p - visible[idx - 1] > 1) items.push("ellipsis");
|
|
28
|
+
items.push(p);
|
|
29
|
+
});
|
|
30
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-t border-slate-100 dark:border-slate-800 px-3 py-2 text-xs text-slate-600 dark:text-slate-400", children: [
|
|
31
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
32
|
+
/* @__PURE__ */ jsx("span", { children: pageSize === Infinity ? `${total}` : `${page * pageSize + 1}\u2013${Math.min(total, (page + 1) * pageSize)} / ${total}` }),
|
|
33
|
+
/* @__PURE__ */ jsxs(
|
|
34
|
+
"select",
|
|
35
|
+
{
|
|
36
|
+
value: pageSize === Infinity ? "all" : pageSize,
|
|
37
|
+
onChange: (e) => onPageSize(e.target.value === "all" ? Infinity : Number(e.target.value)),
|
|
38
|
+
className: "rounded border border-slate-200 bg-white px-1 py-0.5 text-xs dark:border-slate-700 dark:bg-slate-900",
|
|
39
|
+
"aria-label": labels.pageSize,
|
|
40
|
+
children: [
|
|
41
|
+
PAGE_SIZE_OPTIONS.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n)),
|
|
42
|
+
/* @__PURE__ */ jsx("option", { value: "all", children: labels.pageSizeAll })
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
] }),
|
|
47
|
+
pageSize !== Infinity && totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
48
|
+
/* @__PURE__ */ jsx(
|
|
49
|
+
"button",
|
|
50
|
+
{
|
|
51
|
+
type: "button",
|
|
52
|
+
onClick: () => onPage(Math.max(0, page - 1)),
|
|
53
|
+
disabled: page === 0,
|
|
54
|
+
className: "rounded p-1 hover:bg-slate-100 disabled:opacity-40 dark:hover:bg-slate-800",
|
|
55
|
+
"aria-label": labels.prevPage,
|
|
56
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
items.map(
|
|
60
|
+
(it, i) => it === "ellipsis" ? /* @__PURE__ */ jsx("span", { className: "px-1 text-slate-400 dark:text-slate-500", children: "\u2026" }, `e${i}`) : /* @__PURE__ */ jsx(
|
|
61
|
+
"button",
|
|
62
|
+
{
|
|
63
|
+
type: "button",
|
|
64
|
+
onClick: () => onPage(it),
|
|
65
|
+
className: cn(
|
|
66
|
+
"min-w-7 rounded px-2 py-0.5",
|
|
67
|
+
it === page ? "bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900" : "hover:bg-slate-100 dark:hover:bg-slate-800"
|
|
68
|
+
),
|
|
69
|
+
children: it + 1
|
|
70
|
+
},
|
|
71
|
+
it
|
|
72
|
+
)
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"button",
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
onClick: () => onPage(Math.min(totalPages - 1, page + 1)),
|
|
79
|
+
disabled: page >= totalPages - 1,
|
|
80
|
+
className: "rounded p-1 hover:bg-slate-100 disabled:opacity-40 dark:hover:bg-slate-800",
|
|
81
|
+
"aria-label": labels.nextPage,
|
|
82
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] })
|
|
86
|
+
] });
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
PAGE_SIZE_OPTIONS,
|
|
90
|
+
Pagination
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=data-table-pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-pagination.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\n\nexport const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;\n\ninterface PaginationProps {\n page: number;\n totalPages: number;\n pageSize: number;\n total: number;\n onPage: (p: number) => void;\n onPageSize: (n: number) => void;\n labels?: DataTableLabels;\n}\n\n/** Footer for {@link DataTable}: range summary, page-size select, and a\n * windowed page-number strip (first/last + ±2 around the current page). */\nexport function Pagination({\n page,\n totalPages,\n pageSize,\n total,\n onPage,\n onPageSize,\n labels = DEFAULT_DATA_TABLE_LABELS,\n}: PaginationProps) {\n const visible = useMemo(() => {\n if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i);\n const set = new Set<number>([0, totalPages - 1, page]);\n for (let d = 1; d <= 2; d++) {\n if (page - d >= 0) set.add(page - d);\n if (page + d <= totalPages - 1) set.add(page + d);\n }\n return Array.from(set).sort((a, b) => a - b);\n }, [page, totalPages]);\n\n const items: (number | \"ellipsis\")[] = [];\n visible.forEach((p, idx) => {\n if (idx > 0 && p - visible[idx - 1] > 1) items.push(\"ellipsis\");\n items.push(p);\n });\n\n return (\n <div className=\"flex flex-wrap items-center justify-between gap-2 border-t border-slate-100 dark:border-slate-800 px-3 py-2 text-xs text-slate-600 dark:text-slate-400\">\n <div className=\"flex items-center gap-2\">\n <span>\n {pageSize === Infinity\n ? `${total}`\n : `${page * pageSize + 1}–${Math.min(total, (page + 1) * pageSize)} / ${total}`}\n </span>\n <select\n value={pageSize === Infinity ? \"all\" : pageSize}\n onChange={(e) => onPageSize(e.target.value === \"all\" ? Infinity : Number(e.target.value))}\n className=\"rounded border border-slate-200 bg-white px-1 py-0.5 text-xs dark:border-slate-700 dark:bg-slate-900\"\n aria-label={labels.pageSize}\n >\n {PAGE_SIZE_OPTIONS.map((n) => (\n <option key={n} value={n}>\n {n}\n </option>\n ))}\n <option value=\"all\">{labels.pageSizeAll}</option>\n </select>\n </div>\n {pageSize !== Infinity && totalPages > 1 && (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onPage(Math.max(0, page - 1))}\n disabled={page === 0}\n className=\"rounded p-1 hover:bg-slate-100 disabled:opacity-40 dark:hover:bg-slate-800\"\n aria-label={labels.prevPage}\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n {items.map((it, i) =>\n it === \"ellipsis\" ? (\n <span key={`e${i}`} className=\"px-1 text-slate-400 dark:text-slate-500\">\n …\n </span>\n ) : (\n <button\n key={it}\n type=\"button\"\n onClick={() => onPage(it)}\n className={cn(\n \"min-w-7 rounded px-2 py-0.5\",\n it === page\n ? \"bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900\"\n : \"hover:bg-slate-100 dark:hover:bg-slate-800\",\n )}\n >\n {it + 1}\n </button>\n ),\n )}\n <button\n type=\"button\"\n onClick={() => onPage(Math.min(totalPages - 1, page + 1))}\n disabled={page >= totalPages - 1}\n className=\"rounded p-1 hover:bg-slate-100 disabled:opacity-40 dark:hover:bg-slate-800\"\n aria-label={labels.nextPage}\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Multi-column sort state for DataTable: ordered by priority (index 0 is the
|
|
2
|
+
* primary sort). Encoded as `sort=date.desc,amount` in URLs and the server's
|
|
3
|
+
* transactions `sort` param — the pre-multi-sort single-value format
|
|
4
|
+
* (`sort=date.desc`) decodes as a one-element list. */
|
|
5
|
+
type SortDir = "asc" | "desc";
|
|
6
|
+
interface SortState {
|
|
7
|
+
key: string;
|
|
8
|
+
dir: SortDir;
|
|
9
|
+
}
|
|
10
|
+
/** Coerce any persisted/URL-derived shape into a `SortState[]`. The
|
|
11
|
+
* pre-multi-sort localStorage format stored one `{key, dir}` object (or
|
|
12
|
+
* null); arrays pass through with junk entries dropped. */
|
|
13
|
+
declare function normalizeSorts(raw: unknown): SortState[];
|
|
14
|
+
declare function encodeSorts(sorts: SortState[]): string | null;
|
|
15
|
+
/** Decode a `sort` URL param, dropping unknown and duplicated keys. */
|
|
16
|
+
declare function decodeSorts(raw: string | null, validKeys: ReadonlySet<string>): SortState[];
|
|
17
|
+
/** Header-click semantics. Plain click: make `key` the only sort — cycling
|
|
18
|
+
* asc → desc → none when it already is the sole criterion. Shift-click
|
|
19
|
+
* (`additive`): append `key` as the next-priority sort, or cycle an existing
|
|
20
|
+
* entry's direction in place (asc → desc → removed). */
|
|
21
|
+
declare function nextSorts(prev: SortState[], key: string, additive: boolean): SortState[];
|
|
22
|
+
|
|
23
|
+
export { type SortDir, type SortState, decodeSorts, encodeSorts, nextSorts, normalizeSorts };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function normalizeSorts(raw) {
|
|
2
|
+
const one = (v) => {
|
|
3
|
+
if (!v || typeof v !== "object") return null;
|
|
4
|
+
const { key, dir } = v;
|
|
5
|
+
if (typeof key !== "string" || !key) return null;
|
|
6
|
+
return { key, dir: dir === "desc" ? "desc" : "asc" };
|
|
7
|
+
};
|
|
8
|
+
if (Array.isArray(raw)) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const entry of raw) {
|
|
11
|
+
const s = one(entry);
|
|
12
|
+
if (s && !out.some((e) => e.key === s.key)) out.push(s);
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
const single = one(raw);
|
|
17
|
+
return single ? [single] : [];
|
|
18
|
+
}
|
|
19
|
+
function encodeSorts(sorts) {
|
|
20
|
+
if (!sorts.length) return null;
|
|
21
|
+
return sorts.map((s) => s.dir === "desc" ? `${s.key}.desc` : s.key).join(",");
|
|
22
|
+
}
|
|
23
|
+
function decodeSorts(raw, validKeys) {
|
|
24
|
+
if (!raw) return [];
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const part of raw.split(",")) {
|
|
27
|
+
const desc = part.endsWith(".desc");
|
|
28
|
+
const asc = part.endsWith(".asc");
|
|
29
|
+
const key = desc ? part.slice(0, -".desc".length) : asc ? part.slice(0, -".asc".length) : part;
|
|
30
|
+
if (!validKeys.has(key) || out.some((s) => s.key === key)) continue;
|
|
31
|
+
out.push({ key, dir: desc ? "desc" : "asc" });
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function nextSorts(prev, key, additive) {
|
|
36
|
+
const idx = prev.findIndex((s) => s.key === key);
|
|
37
|
+
if (!additive) {
|
|
38
|
+
if (prev.length === 1 && idx === 0) {
|
|
39
|
+
return prev[0].dir === "asc" ? [{ key, dir: "desc" }] : [];
|
|
40
|
+
}
|
|
41
|
+
return [{ key, dir: "asc" }];
|
|
42
|
+
}
|
|
43
|
+
if (idx === -1) return [...prev, { key, dir: "asc" }];
|
|
44
|
+
if (prev[idx].dir === "asc") {
|
|
45
|
+
return prev.map((s, i) => i === idx ? { key, dir: "desc" } : s);
|
|
46
|
+
}
|
|
47
|
+
return prev.filter((_, i) => i !== idx);
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
decodeSorts,
|
|
51
|
+
encodeSorts,
|
|
52
|
+
nextSorts,
|
|
53
|
+
normalizeSorts
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=data-table-sort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-sort.ts"],"sourcesContent":["/** Multi-column sort state for DataTable: ordered by priority (index 0 is the\n * primary sort). Encoded as `sort=date.desc,amount` in URLs and the server's\n * transactions `sort` param — the pre-multi-sort single-value format\n * (`sort=date.desc`) decodes as a one-element list. */\n\nexport type SortDir = \"asc\" | \"desc\";\nexport interface SortState {\n key: string;\n dir: SortDir;\n}\n\n/** Coerce any persisted/URL-derived shape into a `SortState[]`. The\n * pre-multi-sort localStorage format stored one `{key, dir}` object (or\n * null); arrays pass through with junk entries dropped. */\nexport function normalizeSorts(raw: unknown): SortState[] {\n const one = (v: unknown): SortState | null => {\n if (!v || typeof v !== \"object\") return null;\n const { key, dir } = v as { key?: unknown; dir?: unknown };\n if (typeof key !== \"string\" || !key) return null;\n return { key, dir: dir === \"desc\" ? \"desc\" : \"asc\" };\n };\n if (Array.isArray(raw)) {\n const out: SortState[] = [];\n for (const entry of raw) {\n const s = one(entry);\n if (s && !out.some((e) => e.key === s.key)) out.push(s);\n }\n return out;\n }\n const single = one(raw);\n return single ? [single] : [];\n}\n\nexport function encodeSorts(sorts: SortState[]): string | null {\n if (!sorts.length) return null;\n return sorts.map((s) => (s.dir === \"desc\" ? `${s.key}.desc` : s.key)).join(\",\");\n}\n\n/** Decode a `sort` URL param, dropping unknown and duplicated keys. */\nexport function decodeSorts(raw: string | null, validKeys: ReadonlySet<string>): SortState[] {\n if (!raw) return [];\n const out: SortState[] = [];\n for (const part of raw.split(\",\")) {\n const desc = part.endsWith(\".desc\");\n const asc = part.endsWith(\".asc\");\n const key = desc ? part.slice(0, -\".desc\".length) : asc ? part.slice(0, -\".asc\".length) : part;\n if (!validKeys.has(key) || out.some((s) => s.key === key)) continue;\n out.push({ key, dir: desc ? \"desc\" : \"asc\" });\n }\n return out;\n}\n\n/** Header-click semantics. Plain click: make `key` the only sort — cycling\n * asc → desc → none when it already is the sole criterion. Shift-click\n * (`additive`): append `key` as the next-priority sort, or cycle an existing\n * entry's direction in place (asc → desc → removed). */\nexport function nextSorts(prev: SortState[], key: string, additive: boolean): SortState[] {\n const idx = prev.findIndex((s) => s.key === key);\n if (!additive) {\n if (prev.length === 1 && idx === 0) {\n return prev[0].dir === \"asc\" ? [{ key, dir: \"desc\" }] : [];\n }\n return [{ key, dir: \"asc\" }];\n }\n if (idx === -1) return [...prev, { key, dir: \"asc\" }];\n if (prev[idx].dir === \"asc\") {\n return prev.map((s, i) => (i === idx ? { key, dir: \"desc\" as SortDir } : s));\n }\n return prev.filter((_, i) => i !== idx);\n}\n"],"mappings":"AAcO,SAAS,eAAe,KAA2B;AACxD,QAAM,MAAM,CAAC,MAAiC;AAC5C,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,EAAE,KAAK,IAAI,IAAI;AACrB,QAAI,OAAO,QAAQ,YAAY,CAAC,IAAK,QAAO;AAC5C,WAAO,EAAE,KAAK,KAAK,QAAQ,SAAS,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,UAAM,MAAmB,CAAC;AAC1B,eAAW,SAAS,KAAK;AACvB,YAAM,IAAI,IAAI,KAAK;AACnB,UAAI,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAG,KAAI,KAAK,CAAC;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,IAAI,GAAG;AACtB,SAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AAC9B;AAEO,SAAS,YAAY,OAAmC;AAC7D,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO,MAAM,IAAI,CAAC,MAAO,EAAE,QAAQ,SAAS,GAAG,EAAE,GAAG,UAAU,EAAE,GAAI,EAAE,KAAK,GAAG;AAChF;AAGO,SAAS,YAAY,KAAoB,WAA6C;AAC3F,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,MAAmB,CAAC;AAC1B,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,UAAM,OAAO,KAAK,SAAS,OAAO;AAClC,UAAM,MAAM,KAAK,SAAS,MAAM;AAChC,UAAM,MAAM,OAAO,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM,IAAI,MAAM,KAAK,MAAM,GAAG,CAAC,OAAO,MAAM,IAAI;AAC1F,QAAI,CAAC,UAAU,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,EAAG;AAC3D,QAAI,KAAK,EAAE,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAMO,SAAS,UAAU,MAAmB,KAAa,UAAgC;AACxF,QAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;AAC/C,MAAI,CAAC,UAAU;AACb,QAAI,KAAK,WAAW,KAAK,QAAQ,GAAG;AAClC,aAAO,KAAK,CAAC,EAAE,QAAQ,QAAQ,CAAC,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC;AAAA,IAC3D;AACA,WAAO,CAAC,EAAE,KAAK,KAAK,MAAM,CAAC;AAAA,EAC7B;AACA,MAAI,QAAQ,GAAI,QAAO,CAAC,GAAG,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC;AACpD,MAAI,KAAK,GAAG,EAAE,QAAQ,OAAO;AAC3B,WAAO,KAAK,IAAI,CAAC,GAAG,MAAO,MAAM,MAAM,EAAE,KAAK,KAAK,OAAkB,IAAI,CAAE;AAAA,EAC7E;AACA,SAAO,KAAK,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG;AACxC;","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import 'react';
|
|
2
|
+
export { D as DataTable, a as DataTableColumn, b as DataTableProps, F as FilterState, S as ServerPagination } from '../data-table-filters-aXyf0Xub.js';
|
|
3
|
+
export { SortState } from './data-table-sort.js';
|
|
4
|
+
import './swipeable-row.js';
|
|
5
|
+
import './data-table-labels.js';
|