@undevy-org/tablewright 0.8.0 → 0.9.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/dist/es/components/data-table/sticky-utils.js +35 -0
- package/dist/es/hooks/table-orchestration-store.js +14 -0
- package/dist/es/hooks/useTableOrchestration.js +45 -1
- package/dist/es/index.js +2 -0
- package/dist/es/lib/export-table-csv.js +25 -3
- package/dist/tablewright.cjs +115 -4
- package/dist/types/components/data-table/sticky-utils.d.ts +11 -0
- package/dist/types/hooks/table-orchestration-store.d.ts +4 -1
- package/dist/types/hooks/useTableOrchestration.d.ts +11 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/export-table-csv.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
function columnWidthPx(columnId, meta, columnWidths) {
|
|
2
|
+
return columnWidths?.[columnId] ?? meta[columnId]?.minW ?? 0;
|
|
3
|
+
}
|
|
4
|
+
function applyColumnPinningToMeta(baseMeta, pinning, options) {
|
|
5
|
+
const order = options?.columnOrder ?? Object.keys(baseMeta);
|
|
6
|
+
const pinnedLeft = new Set(pinning.left ?? []);
|
|
7
|
+
const pinnedRight = new Set(pinning.right ?? []);
|
|
8
|
+
const result = {};
|
|
9
|
+
for (const [id, meta] of Object.entries(baseMeta)) {
|
|
10
|
+
if (pinnedLeft.has(id)) {
|
|
11
|
+
result[id] = { ...meta, sticky: "left" };
|
|
12
|
+
} else if (pinnedRight.has(id)) {
|
|
13
|
+
result[id] = { ...meta, sticky: "right" };
|
|
14
|
+
} else {
|
|
15
|
+
result[id] = { ...meta };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
let leftOffset = 0;
|
|
19
|
+
for (const id of order) {
|
|
20
|
+
const meta = result[id];
|
|
21
|
+
if (meta?.sticky !== "left") continue;
|
|
22
|
+
result[id] = { ...meta, stickyOffset: leftOffset };
|
|
23
|
+
leftOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
|
|
24
|
+
}
|
|
25
|
+
let rightOffset = 0;
|
|
26
|
+
for (const id of [...order].reverse()) {
|
|
27
|
+
const meta = result[id];
|
|
28
|
+
if (meta?.sticky !== "right") continue;
|
|
29
|
+
result[id] = { ...meta, stickyOffset: rightOffset };
|
|
30
|
+
rightOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
1
34
|
function stickyStyle(meta, isHeader) {
|
|
2
35
|
if (isHeader) {
|
|
3
36
|
return {
|
|
@@ -20,6 +53,8 @@ function stickyBorderClass(meta) {
|
|
|
20
53
|
return edge === "right" ? "[box-shadow:1px_0_0_0_var(--border-subtle)]" : "[box-shadow:-1px_0_0_0_var(--border-subtle)]";
|
|
21
54
|
}
|
|
22
55
|
export {
|
|
56
|
+
applyColumnPinningToMeta,
|
|
57
|
+
columnWidthPx,
|
|
23
58
|
stickyBorderClass,
|
|
24
59
|
stickyStyle
|
|
25
60
|
};
|
|
@@ -28,6 +28,7 @@ function createTableOrchestrationStore({
|
|
|
28
28
|
requestOpenFilterId: null,
|
|
29
29
|
currentSort: null,
|
|
30
30
|
columnVisibility: initialColumnVisibility ?? {},
|
|
31
|
+
columnPinning: { left: [], right: [] },
|
|
31
32
|
page: 1,
|
|
32
33
|
rowsPerPage: initialRowsPerPage,
|
|
33
34
|
density: "normal",
|
|
@@ -108,6 +109,19 @@ function createTableOrchestrationStore({
|
|
|
108
109
|
set({ columnVisibility: {} });
|
|
109
110
|
goCustom();
|
|
110
111
|
},
|
|
112
|
+
pinColumn: (columnId, side) => set((s) => {
|
|
113
|
+
const left = (s.columnPinning.left ?? []).filter((id) => id !== columnId);
|
|
114
|
+
const right = (s.columnPinning.right ?? []).filter((id) => id !== columnId);
|
|
115
|
+
if (side === "left") left.push(columnId);
|
|
116
|
+
else right.push(columnId);
|
|
117
|
+
return { columnPinning: { left, right } };
|
|
118
|
+
}),
|
|
119
|
+
unpinColumn: (columnId) => set((s) => ({
|
|
120
|
+
columnPinning: {
|
|
121
|
+
left: (s.columnPinning.left ?? []).filter((id) => id !== columnId),
|
|
122
|
+
right: (s.columnPinning.right ?? []).filter((id) => id !== columnId)
|
|
123
|
+
}
|
|
124
|
+
})),
|
|
111
125
|
setPage: (p) => set({ page: p }),
|
|
112
126
|
setRowsPerPage: (n) => set({ rowsPerPage: n, page: 1 }),
|
|
113
127
|
setDensity: (v) => set({ density: v }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useMemo, useCallback, useLayoutEffect, useEffect } from "react";
|
|
2
2
|
import { useStore } from "zustand";
|
|
3
3
|
import { useColumnResize } from "../components/data-table/hooks/use-column-resize.js";
|
|
4
|
+
import { applyColumnPinningToMeta } from "../components/data-table/sticky-utils.js";
|
|
4
5
|
import { getVisiblePageNumbers } from "../components/data-table/hooks/use-table-pagination.js";
|
|
5
6
|
import { createTableOrchestrationStore } from "./table-orchestration-store.js";
|
|
6
7
|
import { useColumnFilterStats } from "./table-orchestration/use-column-filter-stats.js";
|
|
@@ -8,6 +9,26 @@ import { useColumnGaps } from "./table-orchestration/use-column-gaps.js";
|
|
|
8
9
|
import { useFilteredRows } from "./table-orchestration/use-filtered-rows.js";
|
|
9
10
|
import { useOrchestrationActions } from "./table-orchestration/use-orchestration-actions.js";
|
|
10
11
|
import { useResponsiveColumns } from "./table-orchestration/use-responsive-columns.js";
|
|
12
|
+
import { gapColumnId } from "../lib/column-gaps.js";
|
|
13
|
+
function computeVisibleColumnOrder(input) {
|
|
14
|
+
const { columnMeta, toggleableColumnIds, columnVisibility, columnGaps } = input;
|
|
15
|
+
const order = [];
|
|
16
|
+
if (columnMeta.rowControl) order.push("rowControl");
|
|
17
|
+
for (const id of toggleableColumnIds) {
|
|
18
|
+
if (columnVisibility[id] !== false) order.push(id);
|
|
19
|
+
}
|
|
20
|
+
for (const gap of columnGaps) {
|
|
21
|
+
const gapId = gapColumnId(gap.afterColumnId);
|
|
22
|
+
if (gap.afterColumnId === null) {
|
|
23
|
+
order.unshift(gapId);
|
|
24
|
+
} else {
|
|
25
|
+
const afterIdx = order.indexOf(gap.afterColumnId);
|
|
26
|
+
if (afterIdx >= 0) order.splice(afterIdx + 1, 0, gapId);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (columnMeta.actions) order.push("actions");
|
|
30
|
+
return order;
|
|
31
|
+
}
|
|
11
32
|
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
12
33
|
function useTableOrchestration(config) {
|
|
13
34
|
const {
|
|
@@ -40,6 +61,9 @@ function useTableOrchestration(config) {
|
|
|
40
61
|
const requestOpenFilterId = useStore(store, (s) => s.requestOpenFilterId);
|
|
41
62
|
const currentSort = useStore(store, (s) => s.currentSort);
|
|
42
63
|
const columnVisibility = useStore(store, (s) => s.columnVisibility);
|
|
64
|
+
const columnPinning = useStore(store, (s) => s.columnPinning);
|
|
65
|
+
const pinColumn = useStore(store, (s) => s.pinColumn);
|
|
66
|
+
const unpinColumn = useStore(store, (s) => s.unpinColumn);
|
|
43
67
|
const storedPage = useStore(store, (s) => s.page);
|
|
44
68
|
const rowsPerPage = useStore(store, (s) => s.rowsPerPage);
|
|
45
69
|
const density = useStore(store, (s) => s.density);
|
|
@@ -120,13 +144,29 @@ function useTableOrchestration(config) {
|
|
|
120
144
|
);
|
|
121
145
|
const allOnPageSelected = pageIds.size > 0 && selectedOnPage === pageIds.size;
|
|
122
146
|
const headerCheckState = allOnPageSelected ? true : selectedOnPage > 0 ? "indeterminate" : false;
|
|
123
|
-
const { columnGaps, extendedColumnMeta, extendedWidths } = useColumnGaps({
|
|
147
|
+
const { columnGaps, extendedColumnMeta: gapExtendedColumnMeta, extendedWidths } = useColumnGaps({
|
|
124
148
|
columnGroups,
|
|
125
149
|
toggleableColumnIds,
|
|
126
150
|
columnVisibility,
|
|
127
151
|
columnMeta,
|
|
128
152
|
widths
|
|
129
153
|
});
|
|
154
|
+
const visibleColumnOrder = useMemo(
|
|
155
|
+
() => computeVisibleColumnOrder({
|
|
156
|
+
columnMeta,
|
|
157
|
+
toggleableColumnIds,
|
|
158
|
+
columnVisibility,
|
|
159
|
+
columnGaps
|
|
160
|
+
}),
|
|
161
|
+
[columnGaps, columnMeta, columnVisibility, toggleableColumnIds]
|
|
162
|
+
);
|
|
163
|
+
const extendedColumnMeta = useMemo(
|
|
164
|
+
() => applyColumnPinningToMeta(gapExtendedColumnMeta, columnPinning, {
|
|
165
|
+
columnOrder: visibleColumnOrder,
|
|
166
|
+
columnWidths: extendedWidths
|
|
167
|
+
}),
|
|
168
|
+
[columnPinning, extendedWidths, gapExtendedColumnMeta, visibleColumnOrder]
|
|
169
|
+
);
|
|
130
170
|
const hiddenColumnsCount = useMemo(() => {
|
|
131
171
|
return toggleableColumnIds.filter((id) => columnVisibility[id] === false).length;
|
|
132
172
|
}, [toggleableColumnIds, columnVisibility]);
|
|
@@ -185,6 +225,9 @@ function useTableOrchestration(config) {
|
|
|
185
225
|
currentSort,
|
|
186
226
|
columnVisibility,
|
|
187
227
|
setColumnVisibility,
|
|
228
|
+
columnPinning,
|
|
229
|
+
pinColumn,
|
|
230
|
+
unpinColumn,
|
|
188
231
|
page,
|
|
189
232
|
rowsPerPage,
|
|
190
233
|
density,
|
|
@@ -249,5 +292,6 @@ function useTableOrchestration(config) {
|
|
|
249
292
|
};
|
|
250
293
|
}
|
|
251
294
|
export {
|
|
295
|
+
computeVisibleColumnOrder,
|
|
252
296
|
useTableOrchestration
|
|
253
297
|
};
|
package/dist/es/index.js
CHANGED
|
@@ -67,6 +67,7 @@ import { useTableOrchestration } from "./hooks/useTableOrchestration.js";
|
|
|
67
67
|
import { createTableOrchestrationStore, defaultValueForFilter } from "./hooks/table-orchestration-store.js";
|
|
68
68
|
import { useRowDragOverride } from "./hooks/useRowDragOverride.js";
|
|
69
69
|
import { downloadCsvFile, exportTableToCsv } from "./lib/export-table-csv.js";
|
|
70
|
+
import { applyColumnPinningToMeta } from "./components/data-table/sticky-utils.js";
|
|
70
71
|
import { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
|
|
71
72
|
import { computeColumnStats } from "./lib/filter-stats.js";
|
|
72
73
|
import { formatEnumLabel } from "./lib/format-enum.js";
|
|
@@ -176,6 +177,7 @@ export {
|
|
|
176
177
|
TopBar,
|
|
177
178
|
TwoPanelMenuBase,
|
|
178
179
|
ViewChip,
|
|
180
|
+
applyColumnPinningToMeta,
|
|
179
181
|
applyManagedChanges,
|
|
180
182
|
badgeVariants,
|
|
181
183
|
buttonVariants,
|
|
@@ -8,17 +8,39 @@ function resolveTableExportButtonSize(size) {
|
|
|
8
8
|
function resolveTableExportButtonVariant(variant) {
|
|
9
9
|
return variant ?? "secondary";
|
|
10
10
|
}
|
|
11
|
+
const PLAIN_DECIMAL_CORE = /^(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?$/;
|
|
12
|
+
const FORMULA_TRIGGER_PREFIX = /^[=+\-@\t\r]/;
|
|
11
13
|
function escapeCsvField(value) {
|
|
12
14
|
if (/[",\r\n]/.test(value)) {
|
|
13
15
|
return `"${value.replace(/"/g, '""')}"`;
|
|
14
16
|
}
|
|
15
17
|
return value;
|
|
16
18
|
}
|
|
19
|
+
function isSignedPlainDecimal(value) {
|
|
20
|
+
const first = value[0];
|
|
21
|
+
if (first !== "+" && first !== "-") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return PLAIN_DECIMAL_CORE.test(value.slice(1));
|
|
25
|
+
}
|
|
26
|
+
function shouldPrefixFormulaEscape(value) {
|
|
27
|
+
if (!FORMULA_TRIGGER_PREFIX.test(value)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (isSignedPlainDecimal(value)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function formatCsvField(value, escapeFormulas) {
|
|
36
|
+
const prepared = escapeFormulas && shouldPrefixFormulaEscape(value) ? `'${value}` : value;
|
|
37
|
+
return escapeCsvField(prepared);
|
|
38
|
+
}
|
|
17
39
|
function exportTableToCsv(options) {
|
|
18
|
-
const { columns, rows, getCellText } = options;
|
|
19
|
-
const headerLine = columns.map((column) =>
|
|
40
|
+
const { columns, rows, getCellText, escapeFormulas = true } = options;
|
|
41
|
+
const headerLine = columns.map((column) => formatCsvField(column.header, escapeFormulas)).join(",");
|
|
20
42
|
const bodyLines = rows.map(
|
|
21
|
-
(row) => columns.map((column) =>
|
|
43
|
+
(row) => columns.map((column) => formatCsvField(getCellText(row, column.id), escapeFormulas)).join(",")
|
|
22
44
|
);
|
|
23
45
|
return [headerLine, ...bodyLines].join("\r\n");
|
|
24
46
|
}
|
package/dist/tablewright.cjs
CHANGED
|
@@ -931,6 +931,39 @@ function ScrollArea({ children, className }) {
|
|
|
931
931
|
);
|
|
932
932
|
}
|
|
933
933
|
TableLayout.ScrollArea = ScrollArea;
|
|
934
|
+
function columnWidthPx(columnId, meta, columnWidths) {
|
|
935
|
+
return columnWidths?.[columnId] ?? meta[columnId]?.minW ?? 0;
|
|
936
|
+
}
|
|
937
|
+
function applyColumnPinningToMeta(baseMeta, pinning, options) {
|
|
938
|
+
const order = options?.columnOrder ?? Object.keys(baseMeta);
|
|
939
|
+
const pinnedLeft = new Set(pinning.left ?? []);
|
|
940
|
+
const pinnedRight = new Set(pinning.right ?? []);
|
|
941
|
+
const result = {};
|
|
942
|
+
for (const [id, meta] of Object.entries(baseMeta)) {
|
|
943
|
+
if (pinnedLeft.has(id)) {
|
|
944
|
+
result[id] = { ...meta, sticky: "left" };
|
|
945
|
+
} else if (pinnedRight.has(id)) {
|
|
946
|
+
result[id] = { ...meta, sticky: "right" };
|
|
947
|
+
} else {
|
|
948
|
+
result[id] = { ...meta };
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
let leftOffset = 0;
|
|
952
|
+
for (const id of order) {
|
|
953
|
+
const meta = result[id];
|
|
954
|
+
if (meta?.sticky !== "left") continue;
|
|
955
|
+
result[id] = { ...meta, stickyOffset: leftOffset };
|
|
956
|
+
leftOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
|
|
957
|
+
}
|
|
958
|
+
let rightOffset = 0;
|
|
959
|
+
for (const id of [...order].reverse()) {
|
|
960
|
+
const meta = result[id];
|
|
961
|
+
if (meta?.sticky !== "right") continue;
|
|
962
|
+
result[id] = { ...meta, stickyOffset: rightOffset };
|
|
963
|
+
rightOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
|
|
964
|
+
}
|
|
965
|
+
return result;
|
|
966
|
+
}
|
|
934
967
|
function stickyStyle(meta, isHeader) {
|
|
935
968
|
if (isHeader) {
|
|
936
969
|
return {
|
|
@@ -1824,17 +1857,39 @@ function resolveTableExportButtonSize(size) {
|
|
|
1824
1857
|
function resolveTableExportButtonVariant(variant) {
|
|
1825
1858
|
return variant ?? "secondary";
|
|
1826
1859
|
}
|
|
1860
|
+
const PLAIN_DECIMAL_CORE = /^(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?$/;
|
|
1861
|
+
const FORMULA_TRIGGER_PREFIX = /^[=+\-@\t\r]/;
|
|
1827
1862
|
function escapeCsvField(value) {
|
|
1828
1863
|
if (/[",\r\n]/.test(value)) {
|
|
1829
1864
|
return `"${value.replace(/"/g, '""')}"`;
|
|
1830
1865
|
}
|
|
1831
1866
|
return value;
|
|
1832
1867
|
}
|
|
1868
|
+
function isSignedPlainDecimal(value) {
|
|
1869
|
+
const first = value[0];
|
|
1870
|
+
if (first !== "+" && first !== "-") {
|
|
1871
|
+
return false;
|
|
1872
|
+
}
|
|
1873
|
+
return PLAIN_DECIMAL_CORE.test(value.slice(1));
|
|
1874
|
+
}
|
|
1875
|
+
function shouldPrefixFormulaEscape(value) {
|
|
1876
|
+
if (!FORMULA_TRIGGER_PREFIX.test(value)) {
|
|
1877
|
+
return false;
|
|
1878
|
+
}
|
|
1879
|
+
if (isSignedPlainDecimal(value)) {
|
|
1880
|
+
return false;
|
|
1881
|
+
}
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
function formatCsvField(value, escapeFormulas) {
|
|
1885
|
+
const prepared = escapeFormulas && shouldPrefixFormulaEscape(value) ? `'${value}` : value;
|
|
1886
|
+
return escapeCsvField(prepared);
|
|
1887
|
+
}
|
|
1833
1888
|
function exportTableToCsv(options) {
|
|
1834
|
-
const { columns, rows, getCellText } = options;
|
|
1835
|
-
const headerLine = columns.map((column) =>
|
|
1889
|
+
const { columns, rows, getCellText, escapeFormulas = true } = options;
|
|
1890
|
+
const headerLine = columns.map((column) => formatCsvField(column.header, escapeFormulas)).join(",");
|
|
1836
1891
|
const bodyLines = rows.map(
|
|
1837
|
-
(row) => columns.map((column) =>
|
|
1892
|
+
(row) => columns.map((column) => formatCsvField(getCellText(row, column.id), escapeFormulas)).join(",")
|
|
1838
1893
|
);
|
|
1839
1894
|
return [headerLine, ...bodyLines].join("\r\n");
|
|
1840
1895
|
}
|
|
@@ -4846,6 +4901,7 @@ function createTableOrchestrationStore({
|
|
|
4846
4901
|
requestOpenFilterId: null,
|
|
4847
4902
|
currentSort: null,
|
|
4848
4903
|
columnVisibility: initialColumnVisibility ?? {},
|
|
4904
|
+
columnPinning: { left: [], right: [] },
|
|
4849
4905
|
page: 1,
|
|
4850
4906
|
rowsPerPage: initialRowsPerPage,
|
|
4851
4907
|
density: "normal",
|
|
@@ -4926,6 +4982,19 @@ function createTableOrchestrationStore({
|
|
|
4926
4982
|
set({ columnVisibility: {} });
|
|
4927
4983
|
goCustom();
|
|
4928
4984
|
},
|
|
4985
|
+
pinColumn: (columnId, side) => set((s) => {
|
|
4986
|
+
const left = (s.columnPinning.left ?? []).filter((id) => id !== columnId);
|
|
4987
|
+
const right = (s.columnPinning.right ?? []).filter((id) => id !== columnId);
|
|
4988
|
+
if (side === "left") left.push(columnId);
|
|
4989
|
+
else right.push(columnId);
|
|
4990
|
+
return { columnPinning: { left, right } };
|
|
4991
|
+
}),
|
|
4992
|
+
unpinColumn: (columnId) => set((s) => ({
|
|
4993
|
+
columnPinning: {
|
|
4994
|
+
left: (s.columnPinning.left ?? []).filter((id) => id !== columnId),
|
|
4995
|
+
right: (s.columnPinning.right ?? []).filter((id) => id !== columnId)
|
|
4996
|
+
}
|
|
4997
|
+
})),
|
|
4929
4998
|
setPage: (p) => set({ page: p }),
|
|
4930
4999
|
setRowsPerPage: (n) => set({ rowsPerPage: n, page: 1 }),
|
|
4931
5000
|
setDensity: (v) => set({ density: v }),
|
|
@@ -5350,6 +5419,25 @@ function useResponsiveColumns(columnPriorities, wideBreakpoint, compactBreakpoin
|
|
|
5350
5419
|
}, [isWide, columnPriorities]);
|
|
5351
5420
|
return { responsiveHiddenColumnIds, isCompact };
|
|
5352
5421
|
}
|
|
5422
|
+
function computeVisibleColumnOrder(input) {
|
|
5423
|
+
const { columnMeta, toggleableColumnIds, columnVisibility, columnGaps } = input;
|
|
5424
|
+
const order = [];
|
|
5425
|
+
if (columnMeta.rowControl) order.push("rowControl");
|
|
5426
|
+
for (const id of toggleableColumnIds) {
|
|
5427
|
+
if (columnVisibility[id] !== false) order.push(id);
|
|
5428
|
+
}
|
|
5429
|
+
for (const gap of columnGaps) {
|
|
5430
|
+
const gapId = gapColumnId(gap.afterColumnId);
|
|
5431
|
+
if (gap.afterColumnId === null) {
|
|
5432
|
+
order.unshift(gapId);
|
|
5433
|
+
} else {
|
|
5434
|
+
const afterIdx = order.indexOf(gap.afterColumnId);
|
|
5435
|
+
if (afterIdx >= 0) order.splice(afterIdx + 1, 0, gapId);
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
if (columnMeta.actions) order.push("actions");
|
|
5439
|
+
return order;
|
|
5440
|
+
}
|
|
5353
5441
|
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
5354
5442
|
function useTableOrchestration(config) {
|
|
5355
5443
|
const {
|
|
@@ -5382,6 +5470,9 @@ function useTableOrchestration(config) {
|
|
|
5382
5470
|
const requestOpenFilterId = zustand.useStore(store, (s) => s.requestOpenFilterId);
|
|
5383
5471
|
const currentSort = zustand.useStore(store, (s) => s.currentSort);
|
|
5384
5472
|
const columnVisibility = zustand.useStore(store, (s) => s.columnVisibility);
|
|
5473
|
+
const columnPinning = zustand.useStore(store, (s) => s.columnPinning);
|
|
5474
|
+
const pinColumn = zustand.useStore(store, (s) => s.pinColumn);
|
|
5475
|
+
const unpinColumn = zustand.useStore(store, (s) => s.unpinColumn);
|
|
5385
5476
|
const storedPage = zustand.useStore(store, (s) => s.page);
|
|
5386
5477
|
const rowsPerPage = zustand.useStore(store, (s) => s.rowsPerPage);
|
|
5387
5478
|
const density = zustand.useStore(store, (s) => s.density);
|
|
@@ -5462,13 +5553,29 @@ function useTableOrchestration(config) {
|
|
|
5462
5553
|
);
|
|
5463
5554
|
const allOnPageSelected = pageIds.size > 0 && selectedOnPage === pageIds.size;
|
|
5464
5555
|
const headerCheckState = allOnPageSelected ? true : selectedOnPage > 0 ? "indeterminate" : false;
|
|
5465
|
-
const { columnGaps, extendedColumnMeta, extendedWidths } = useColumnGaps({
|
|
5556
|
+
const { columnGaps, extendedColumnMeta: gapExtendedColumnMeta, extendedWidths } = useColumnGaps({
|
|
5466
5557
|
columnGroups,
|
|
5467
5558
|
toggleableColumnIds,
|
|
5468
5559
|
columnVisibility,
|
|
5469
5560
|
columnMeta,
|
|
5470
5561
|
widths
|
|
5471
5562
|
});
|
|
5563
|
+
const visibleColumnOrder = React.useMemo(
|
|
5564
|
+
() => computeVisibleColumnOrder({
|
|
5565
|
+
columnMeta,
|
|
5566
|
+
toggleableColumnIds,
|
|
5567
|
+
columnVisibility,
|
|
5568
|
+
columnGaps
|
|
5569
|
+
}),
|
|
5570
|
+
[columnGaps, columnMeta, columnVisibility, toggleableColumnIds]
|
|
5571
|
+
);
|
|
5572
|
+
const extendedColumnMeta = React.useMemo(
|
|
5573
|
+
() => applyColumnPinningToMeta(gapExtendedColumnMeta, columnPinning, {
|
|
5574
|
+
columnOrder: visibleColumnOrder,
|
|
5575
|
+
columnWidths: extendedWidths
|
|
5576
|
+
}),
|
|
5577
|
+
[columnPinning, extendedWidths, gapExtendedColumnMeta, visibleColumnOrder]
|
|
5578
|
+
);
|
|
5472
5579
|
const hiddenColumnsCount = React.useMemo(() => {
|
|
5473
5580
|
return toggleableColumnIds.filter((id) => columnVisibility[id] === false).length;
|
|
5474
5581
|
}, [toggleableColumnIds, columnVisibility]);
|
|
@@ -5527,6 +5634,9 @@ function useTableOrchestration(config) {
|
|
|
5527
5634
|
currentSort,
|
|
5528
5635
|
columnVisibility,
|
|
5529
5636
|
setColumnVisibility,
|
|
5637
|
+
columnPinning,
|
|
5638
|
+
pinColumn,
|
|
5639
|
+
unpinColumn,
|
|
5530
5640
|
page,
|
|
5531
5641
|
rowsPerPage,
|
|
5532
5642
|
density,
|
|
@@ -6067,6 +6177,7 @@ exports.TimestampCell = TimestampCell;
|
|
|
6067
6177
|
exports.TopBar = TopBar;
|
|
6068
6178
|
exports.TwoPanelMenuBase = TwoPanelMenuBase;
|
|
6069
6179
|
exports.ViewChip = ViewChip;
|
|
6180
|
+
exports.applyColumnPinningToMeta = applyColumnPinningToMeta;
|
|
6070
6181
|
exports.applyManagedChanges = applyManagedChanges;
|
|
6071
6182
|
exports.badgeVariants = badgeVariants;
|
|
6072
6183
|
exports.buttonVariants = buttonVariants;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import type { ColumnPinningState } from "@tanstack/react-table";
|
|
1
2
|
import type { CSSProperties } from "react";
|
|
2
3
|
import type { ColumnMetaDef } from "./types.js";
|
|
4
|
+
export interface ApplyColumnPinningToMetaOptions {
|
|
5
|
+
/** Visible columns in left-to-right display order (include gap columns when present). */
|
|
6
|
+
columnOrder: readonly string[];
|
|
7
|
+
/** Resolved widths in px; falls back to `meta.minW`. */
|
|
8
|
+
columnWidths?: Record<string, number>;
|
|
9
|
+
}
|
|
10
|
+
/** @internal Exported for unit tests (not re-exported from package entry). */
|
|
11
|
+
export declare function columnWidthPx(columnId: string, meta: Record<string, ColumnMetaDef>, columnWidths?: Record<string, number>): number;
|
|
12
|
+
/** Merges TanStack `ColumnPinningState` into column meta sticky fields for rendering. */
|
|
13
|
+
export declare function applyColumnPinningToMeta(baseMeta: Record<string, ColumnMetaDef>, pinning: ColumnPinningState, options?: ApplyColumnPinningToMetaOptions): Record<string, ColumnMetaDef>;
|
|
3
14
|
export declare function stickyStyle(meta: ColumnMetaDef | undefined, isHeader: boolean): CSSProperties | undefined;
|
|
4
15
|
export declare function stickyBorderClass(meta: ColumnMetaDef | undefined): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VisibilityState } from "@tanstack/react-table";
|
|
1
|
+
import type { ColumnPinningState, VisibilityState } from "@tanstack/react-table";
|
|
2
2
|
import type { ActiveFilter, ActiveFilterValue, ColumnFilterConfig } from "../components/data-table/filter-types.js";
|
|
3
3
|
export interface TableOrchestrationState<TViewKey extends string = string> {
|
|
4
4
|
searchInput: string;
|
|
@@ -11,6 +11,7 @@ export interface TableOrchestrationState<TViewKey extends string = string> {
|
|
|
11
11
|
direction: "asc" | "desc";
|
|
12
12
|
} | null;
|
|
13
13
|
columnVisibility: VisibilityState;
|
|
14
|
+
columnPinning: ColumnPinningState;
|
|
14
15
|
page: number;
|
|
15
16
|
rowsPerPage: number;
|
|
16
17
|
density: "normal" | "dense";
|
|
@@ -39,6 +40,8 @@ export interface TableOrchestrationActions<TViewKey extends string = string> {
|
|
|
39
40
|
hideColumn: (columnId: string) => void;
|
|
40
41
|
showColumns: (columnIds: string[]) => void;
|
|
41
42
|
resetColumns: () => void;
|
|
43
|
+
pinColumn: (columnId: string, side: "left" | "right") => void;
|
|
44
|
+
unpinColumn: (columnId: string) => void;
|
|
42
45
|
setPage: (p: number) => void;
|
|
43
46
|
setRowsPerPage: (n: number) => void;
|
|
44
47
|
setDensity: (v: "normal" | "dense") => void;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import type { VisibilityState } from "@tanstack/react-table";
|
|
1
|
+
import type { ColumnPinningState, VisibilityState } from "@tanstack/react-table";
|
|
2
2
|
import type { ColumnMetaDef, SortDirection } from "../components/data-table/types.js";
|
|
3
3
|
import type { ActiveFilter, ActiveFilterValue, ColumnFilterConfig, ColumnGap, ColumnGroup, FilterStatsSlice, ViewPreset } from "../components/data-table/filter-types.js";
|
|
4
4
|
import { type TableOrchestrationStoreApi } from "./table-orchestration-store.js";
|
|
5
|
+
/** @internal Exported for unit tests (not re-exported from package entry). */
|
|
6
|
+
export declare function computeVisibleColumnOrder(input: {
|
|
7
|
+
columnMeta: Record<string, ColumnMetaDef>;
|
|
8
|
+
toggleableColumnIds: readonly string[];
|
|
9
|
+
columnVisibility: VisibilityState;
|
|
10
|
+
columnGaps: ColumnGap[];
|
|
11
|
+
}): string[];
|
|
5
12
|
export interface TableOrchestrationConfig<TRow extends object, TViewKey extends string> {
|
|
6
13
|
rows: TRow[];
|
|
7
14
|
getRowId: (row: TRow) => string;
|
|
@@ -49,6 +56,9 @@ export interface TableOrchestrationReturn<TRow extends object, TViewKey extends
|
|
|
49
56
|
} | null;
|
|
50
57
|
columnVisibility: VisibilityState;
|
|
51
58
|
setColumnVisibility: React.Dispatch<React.SetStateAction<VisibilityState>>;
|
|
59
|
+
columnPinning: ColumnPinningState;
|
|
60
|
+
pinColumn: (columnId: string, side: "left" | "right") => void;
|
|
61
|
+
unpinColumn: (columnId: string) => void;
|
|
52
62
|
page: number;
|
|
53
63
|
rowsPerPage: number;
|
|
54
64
|
density: "normal" | "dense";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -104,6 +104,8 @@ export { useRowDragOverride } from "./hooks/useRowDragOverride.js";
|
|
|
104
104
|
export type { UseRowDragOverrideResult } from "./hooks/useRowDragOverride.js";
|
|
105
105
|
export { downloadCsvFile, exportTableToCsv, } from "./lib/export-table-csv.js";
|
|
106
106
|
export type { DownloadCsvFileOptions, ExportTableToCsvOptions, } from "./lib/export-table-csv.js";
|
|
107
|
+
export { applyColumnPinningToMeta } from "./components/data-table/sticky-utils.js";
|
|
108
|
+
export type { ApplyColumnPinningToMetaOptions } from "./components/data-table/sticky-utils.js";
|
|
107
109
|
export { computeColumnGaps, gapColumnId } from "./lib/column-gaps.js";
|
|
108
110
|
export { computeColumnStats } from "./lib/filter-stats.js";
|
|
109
111
|
export { formatEnumLabel } from "./lib/format-enum.js";
|
|
@@ -6,6 +6,11 @@ export type ExportTableToCsvOptions<TRow> = {
|
|
|
6
6
|
}[];
|
|
7
7
|
rows: TRow[];
|
|
8
8
|
getCellText: (row: TRow, columnId: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Prefix formula-triggering cell text with `'` so spreadsheet apps do not evaluate it.
|
|
11
|
+
* Plain decimal numbers (`-5`, `+3.2`) are left unchanged. @default true
|
|
12
|
+
*/
|
|
13
|
+
escapeFormulas?: boolean;
|
|
9
14
|
};
|
|
10
15
|
export declare const DEFAULT_CSV_EXPORT_LABEL = "Export CSV";
|
|
11
16
|
export declare function resolveCsvExportLabel(labels?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undevy-org/tablewright",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "React data-table and application-shell components: filters, drawers, row controls, sortable and resizable columns, built on Radix UI and TanStack Table.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|