@trackunit/react-table 2.1.57 → 2.1.60
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/index.cjs.js +276 -0
- package/index.esm.js +278 -3
- package/migrations/entry.js +3 -0
- package/migrations.json +3 -0
- package/package.json +12 -9
- package/src/hooks/useTablePersistence/useTablePersistence.d.ts +95 -0
- package/src/hooks/useTablePersistence/utils/useCompactTableUrl.d.ts +24 -0
- package/src/index.d.ts +1 -0
package/index.cjs.js
CHANGED
|
@@ -7,6 +7,8 @@ var sharedUtils = require('@trackunit/shared-utils');
|
|
|
7
7
|
var react = require('react');
|
|
8
8
|
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
9
9
|
var reactRouter = require('@tanstack/react-router');
|
|
10
|
+
var reactCoreHooks = require('@trackunit/react-core-hooks');
|
|
11
|
+
var zod = require('zod');
|
|
10
12
|
var reactFormComponents = require('@trackunit/react-form-components');
|
|
11
13
|
var update = require('immutability-helper');
|
|
12
14
|
var reactDnd = require('react-dnd');
|
|
@@ -375,6 +377,279 @@ const ActionSheet = ({ actions, dropdownActions, moreActions = [], selections, r
|
|
|
375
377
|
moreActions: moreActions.map(action => actionDataToMenuItem(action, dataTestId)) })] }));
|
|
376
378
|
};
|
|
377
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Short keys used in URL-hash-encoded table state to minimize fragment
|
|
382
|
+
* length. localStorage continues to use the full key names. The full URL
|
|
383
|
+
* still benefits from the shorter payload — even though the hash fragment
|
|
384
|
+
* is no longer subject to the AWS WAF query-string cap, the resulting
|
|
385
|
+
* fragment is what shows in shared links and browser history entries.
|
|
386
|
+
*/
|
|
387
|
+
const URL_KEY_MAP = {
|
|
388
|
+
columnOrder: "co",
|
|
389
|
+
columnVisibility: "cv",
|
|
390
|
+
sorting: "s",
|
|
391
|
+
columnPinning: "cp",
|
|
392
|
+
columnSizing: "cs",
|
|
393
|
+
};
|
|
394
|
+
const REVERSE_URL_KEY_MAP = Object.fromEntries(Object.entries(URL_KEY_MAP).map(([full, short]) => [short, full]));
|
|
395
|
+
const compactPinningSchema = zod.z.object({
|
|
396
|
+
l: zod.z.array(zod.z.string()).optional(),
|
|
397
|
+
r: zod.z.array(zod.z.string()).optional(),
|
|
398
|
+
left: zod.z.array(zod.z.string()).optional(),
|
|
399
|
+
right: zod.z.array(zod.z.string()).optional(),
|
|
400
|
+
});
|
|
401
|
+
/**
|
|
402
|
+
* Compacts table state for URL encoding:
|
|
403
|
+
* - Uses short keys (co, cv, s, cp)
|
|
404
|
+
* - Stores columnVisibility as a visible-column array only
|
|
405
|
+
* - Stores columnOrder as visible-column order when columnVisibility is available
|
|
406
|
+
* - Drops empty pinning arrays
|
|
407
|
+
* - Excludes `expanded` (not shareable)
|
|
408
|
+
*/
|
|
409
|
+
const compactForUrl = (state) => {
|
|
410
|
+
const compact = {};
|
|
411
|
+
const columnVisibilityEntries = state.columnVisibility ? Object.entries(state.columnVisibility) : undefined;
|
|
412
|
+
const visibleColumns = columnVisibilityEntries?.filter(([, visible]) => visible).map(([key]) => key) ?? [];
|
|
413
|
+
const visibleColumnSet = new Set(visibleColumns);
|
|
414
|
+
if (state.columnVisibility && Object.keys(state.columnVisibility).length > 0) {
|
|
415
|
+
compact[URL_KEY_MAP.columnVisibility] = visibleColumns;
|
|
416
|
+
}
|
|
417
|
+
if (state.columnOrder && state.columnOrder.length > 0) {
|
|
418
|
+
compact[URL_KEY_MAP.columnOrder] =
|
|
419
|
+
state.columnVisibility && Object.keys(state.columnVisibility).length > 0
|
|
420
|
+
? [
|
|
421
|
+
...state.columnOrder.filter(columnId => visibleColumnSet.has(columnId)),
|
|
422
|
+
...visibleColumns.filter(columnId => !state.columnOrder?.includes(columnId)),
|
|
423
|
+
]
|
|
424
|
+
: state.columnOrder;
|
|
425
|
+
}
|
|
426
|
+
if (state.sorting && state.sorting.length > 0) {
|
|
427
|
+
compact[URL_KEY_MAP.sorting] = state.sorting;
|
|
428
|
+
}
|
|
429
|
+
if (state.columnSizing && Object.keys(state.columnSizing).length > 0) {
|
|
430
|
+
compact[URL_KEY_MAP.columnSizing] = state.columnSizing;
|
|
431
|
+
}
|
|
432
|
+
if (state.columnPinning) {
|
|
433
|
+
const pinning = {};
|
|
434
|
+
if (state.columnPinning.left && state.columnPinning.left.length > 0) {
|
|
435
|
+
pinning.l = state.columnPinning.left;
|
|
436
|
+
}
|
|
437
|
+
if (state.columnPinning.right && state.columnPinning.right.length > 0) {
|
|
438
|
+
pinning.r = state.columnPinning.right;
|
|
439
|
+
}
|
|
440
|
+
if (Object.keys(pinning).length > 0) {
|
|
441
|
+
compact[URL_KEY_MAP.columnPinning] = pinning;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return compact;
|
|
445
|
+
};
|
|
446
|
+
const compactRecordSchema = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
447
|
+
const visibilityArraySchema = zod.z.array(zod.z.string());
|
|
448
|
+
const compactVisibilitySchema = zod.z
|
|
449
|
+
.object({
|
|
450
|
+
v: zod.z.array(zod.z.string()).optional(),
|
|
451
|
+
h: zod.z.array(zod.z.string()).optional(),
|
|
452
|
+
})
|
|
453
|
+
.strict();
|
|
454
|
+
/**
|
|
455
|
+
* Expands a compact URL-decoded object back into full TableColumnOptions shape.
|
|
456
|
+
*/
|
|
457
|
+
const expandFromUrl = (decoded) => {
|
|
458
|
+
const parsed = compactRecordSchema.safeParse(decoded);
|
|
459
|
+
if (!parsed.success) {
|
|
460
|
+
return decoded;
|
|
461
|
+
}
|
|
462
|
+
const expanded = {};
|
|
463
|
+
for (const [key, value] of Object.entries(parsed.data)) {
|
|
464
|
+
const fullKey = REVERSE_URL_KEY_MAP[key] ?? key;
|
|
465
|
+
expanded[fullKey] = value;
|
|
466
|
+
}
|
|
467
|
+
const visibilityResult = visibilityArraySchema.safeParse(expanded.columnVisibility);
|
|
468
|
+
if (visibilityResult.success) {
|
|
469
|
+
expanded.columnVisibility = Object.fromEntries([
|
|
470
|
+
[sharedUtils.VISIBLE_ONLY_COLUMN_VISIBILITY_KEY, true],
|
|
471
|
+
...visibilityResult.data.map(col => [col, true]),
|
|
472
|
+
]);
|
|
473
|
+
}
|
|
474
|
+
const compactVisibilityResult = compactVisibilitySchema.safeParse(expanded.columnVisibility);
|
|
475
|
+
if (compactVisibilityResult.success) {
|
|
476
|
+
expanded.columnVisibility = Object.fromEntries([
|
|
477
|
+
...(compactVisibilityResult.data.v ?? []).map(col => [col, true]),
|
|
478
|
+
...(compactVisibilityResult.data.h ?? []).map(col => [col, false]),
|
|
479
|
+
]);
|
|
480
|
+
}
|
|
481
|
+
const pinningResult = compactPinningSchema.safeParse(expanded.columnPinning);
|
|
482
|
+
if (pinningResult.success) {
|
|
483
|
+
expanded.columnPinning = {
|
|
484
|
+
left: pinningResult.data.l ?? pinningResult.data.left ?? [],
|
|
485
|
+
right: pinningResult.data.r ?? pinningResult.data.right ?? [],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return expanded;
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* Provides transform functions for compacting table state before URL encoding
|
|
492
|
+
* and expanding it back after URL decoding.
|
|
493
|
+
*
|
|
494
|
+
* The compaction strategy:
|
|
495
|
+
* - Short property keys (columnOrder → co, columnVisibility → cv, etc.)
|
|
496
|
+
* - columnVisibility stored as an array of visible column names only
|
|
497
|
+
* - columnOrder stored as visible-column order when visibility is available
|
|
498
|
+
* - columnSizing stored under a short key (`cs`)
|
|
499
|
+
* - `expanded` is excluded from the URL and restored from localStorage via
|
|
500
|
+
* `usePersistedState`'s `mergeWithStorageOnRead`
|
|
501
|
+
* - Empty pinning arrays are excluded
|
|
502
|
+
*
|
|
503
|
+
* These transforms are designed to plug into {@link usePersistedState}'s
|
|
504
|
+
* `toUrlValue` and `fromUrlValue` options.
|
|
505
|
+
*/
|
|
506
|
+
const useCompactTableUrl = () => {
|
|
507
|
+
const toUrlValue = react.useCallback((state) => {
|
|
508
|
+
return compactForUrl(state);
|
|
509
|
+
}, []);
|
|
510
|
+
const fromUrlValue = react.useCallback((decoded) => {
|
|
511
|
+
return expandFromUrl(decoded);
|
|
512
|
+
}, []);
|
|
513
|
+
return react.useMemo(() => ({ toUrlValue, fromUrlValue }), [toUrlValue, fromUrlValue]);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const PERSISTENCE_KEY_MAX_LENGTH = 15;
|
|
517
|
+
const camelCaseRegex = /^[a-z][a-zA-Z0-9]*$/;
|
|
518
|
+
const persistenceKeySchema = zod.z
|
|
519
|
+
.string()
|
|
520
|
+
.max(PERSISTENCE_KEY_MAX_LENGTH, {
|
|
521
|
+
message: `persistenceKey must be at most ${PERSISTENCE_KEY_MAX_LENGTH} characters`,
|
|
522
|
+
})
|
|
523
|
+
.regex(camelCaseRegex, {
|
|
524
|
+
message: "persistenceKey must be camelCase (start with lowercase letter, no underscores/hyphens)",
|
|
525
|
+
});
|
|
526
|
+
const expandedStateSchema = zod.z.union([zod.z.literal(true), zod.z.record(zod.z.string(), zod.z.boolean())]);
|
|
527
|
+
/** Zod schema describing the persisted subset of table state (column order, sizing, visibility, sorting, pinning, and expanded). */
|
|
528
|
+
const tableStateSchema = zod.z.object({
|
|
529
|
+
columnOrder: zod.z.array(zod.z.string()).optional(),
|
|
530
|
+
sorting: zod.z.array(zod.z.object({ id: zod.z.string(), desc: zod.z.boolean() })).optional(),
|
|
531
|
+
columnVisibility: zod.z.record(zod.z.string(), zod.z.boolean()).optional(),
|
|
532
|
+
columnSizing: zod.z.record(zod.z.string(), zod.z.number()).optional(),
|
|
533
|
+
columnPinning: zod.z
|
|
534
|
+
.object({
|
|
535
|
+
left: zod.z.array(zod.z.string()).optional(),
|
|
536
|
+
right: zod.z.array(zod.z.string()).optional(),
|
|
537
|
+
})
|
|
538
|
+
.optional(),
|
|
539
|
+
expanded: expandedStateSchema.optional(),
|
|
540
|
+
});
|
|
541
|
+
const validateTableState = (raw) => {
|
|
542
|
+
const result = tableStateSchema.safeParse(raw);
|
|
543
|
+
return result.success ? result.data : undefined;
|
|
544
|
+
};
|
|
545
|
+
/**
|
|
546
|
+
* Persists and restores table column state (order, sizing, visibility, sorting, pinning, expanded) using
|
|
547
|
+
* the URL **hash fragment** and localStorage.
|
|
548
|
+
*
|
|
549
|
+
* Storage scope:
|
|
550
|
+
* - URL hash (shareable): `columnOrder`, `columnVisibility`, `sorting`, `columnPinning`, `columnSizing`.
|
|
551
|
+
* - localStorage: everything above plus `expanded`.
|
|
552
|
+
*
|
|
553
|
+
* The hash slot is used instead of search params because some deployments
|
|
554
|
+
* route through AWS WAF, whose `SizeRestrictions_QUERYSTRING` managed rule
|
|
555
|
+
* caps the query string at ~5000 bytes — easily breached by wide tables.
|
|
556
|
+
* The fragment is client-only and bypasses that cap entirely (subject to a
|
|
557
|
+
* 64 KiB soft limit enforced by `useHashParamSync`).
|
|
558
|
+
*
|
|
559
|
+
* Legacy compatibility: shared links that still use `?<persistenceKey>Tp=`
|
|
560
|
+
* search params from before the hash migration are decoded on first load
|
|
561
|
+
* and the search param is stripped via a `replace` navigation so old
|
|
562
|
+
* bookmarks and forwarded URLs continue to restore state without polluting
|
|
563
|
+
* browser history.
|
|
564
|
+
*
|
|
565
|
+
* On mount, state is loaded from the hash (or legacy search param as a
|
|
566
|
+
* one-shot fallback) and merged with localStorage so fields omitted from
|
|
567
|
+
* the URL, such as `expanded`, still survive reloads. Changes are
|
|
568
|
+
* debounced (300 ms) and synced to both the hash and localStorage.
|
|
569
|
+
*
|
|
570
|
+
* @param persistenceKey - Unique key used to store and retrieve the table state.
|
|
571
|
+
* Must be camelCase and at most 15 characters (e.g. "userTable", "assetList").
|
|
572
|
+
* @returns {UseTablePersistenceResult} An object containing:
|
|
573
|
+
* - `onTableStateChange` — callback to invoke when the table state changes.
|
|
574
|
+
* - `initialState` — the previously persisted {@link TableColumnOptions}, or `undefined` if none exists.
|
|
575
|
+
* @throws {Error} If persistenceKey is not camelCase or exceeds 15 characters.
|
|
576
|
+
*/
|
|
577
|
+
const useTablePersistence = (persistenceKey) => {
|
|
578
|
+
const parseResult = persistenceKeySchema.safeParse(persistenceKey);
|
|
579
|
+
if (!parseResult.success) {
|
|
580
|
+
throw new Error(parseResult.error.errors.map(e => e.message).join(", "));
|
|
581
|
+
}
|
|
582
|
+
const { clientSideUserId } = reactCoreHooks.useCurrentUser();
|
|
583
|
+
const localStorageEnabled = clientSideUserId !== undefined;
|
|
584
|
+
const { toUrlValue, fromUrlValue } = useCompactTableUrl();
|
|
585
|
+
const { initialState, persistState } = reactComponents.usePersistedState({
|
|
586
|
+
key: `${persistenceKey}Tp`,
|
|
587
|
+
validate: validateTableState,
|
|
588
|
+
toUrlValue,
|
|
589
|
+
fromUrlValue,
|
|
590
|
+
replace: false,
|
|
591
|
+
clientSideUserId,
|
|
592
|
+
localStorageEnabled,
|
|
593
|
+
// `toUrlValue` strips fields that should not be shareable from the URL.
|
|
594
|
+
// The merge restores omitted fields from localStorage on read so they
|
|
595
|
+
// survive reloads.
|
|
596
|
+
mergeWithStorageOnRead: true,
|
|
597
|
+
// Move the persisted blob into the URL hash so it bypasses the AWS WAF
|
|
598
|
+
// query-string cap. `usePersistedState` also migrates any legacy
|
|
599
|
+
// `?<key>=...` search param into the hash on first load.
|
|
600
|
+
urlLocation: "hash",
|
|
601
|
+
});
|
|
602
|
+
const [pendingState, setPendingState] = react.useState(null);
|
|
603
|
+
const debouncedPending = reactComponents.useDebounce(pendingState, { delay: 300 });
|
|
604
|
+
const currentState = react.useMemo(() => {
|
|
605
|
+
if (!debouncedPending && !initialState) {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
const columnOrder = debouncedPending?.columnOrder ?? initialState?.columnOrder;
|
|
609
|
+
const columnSizing = debouncedPending?.columnSizing ?? initialState?.columnSizing;
|
|
610
|
+
const columnVisibility = debouncedPending?.columnVisibility ?? initialState?.columnVisibility;
|
|
611
|
+
const sorting = debouncedPending?.sorting ?? initialState?.sorting;
|
|
612
|
+
const columnPinning = debouncedPending?.columnPinning ?? initialState?.columnPinning;
|
|
613
|
+
const expanded = debouncedPending?.expanded ?? initialState?.expanded;
|
|
614
|
+
const state = {};
|
|
615
|
+
if (columnOrder && columnOrder.length > 0) {
|
|
616
|
+
state.columnOrder = columnOrder;
|
|
617
|
+
}
|
|
618
|
+
if (columnSizing && Object.keys(columnSizing).length > 0) {
|
|
619
|
+
state.columnSizing = columnSizing;
|
|
620
|
+
}
|
|
621
|
+
if (columnVisibility && Object.keys(columnVisibility).length > 0) {
|
|
622
|
+
state.columnVisibility = columnVisibility;
|
|
623
|
+
}
|
|
624
|
+
if (sorting && sorting.length > 0) {
|
|
625
|
+
state.sorting = sorting;
|
|
626
|
+
}
|
|
627
|
+
if (columnPinning && ((columnPinning.left?.length ?? 0) > 0 || (columnPinning.right?.length ?? 0) > 0)) {
|
|
628
|
+
state.columnPinning = columnPinning;
|
|
629
|
+
}
|
|
630
|
+
if (expanded === true || (typeof expanded === "object" && Object.keys(expanded).length > 0)) {
|
|
631
|
+
state.expanded = expanded;
|
|
632
|
+
}
|
|
633
|
+
return Object.keys(state).length > 0 ? state : undefined;
|
|
634
|
+
}, [debouncedPending, initialState]);
|
|
635
|
+
const onTableStateChange = react.useCallback((newTableState) => {
|
|
636
|
+
if (!newTableState) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
setPendingState(newTableState);
|
|
640
|
+
}, []);
|
|
641
|
+
react.useEffect(() => {
|
|
642
|
+
if (!currentState) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
persistState(currentState);
|
|
646
|
+
}, [currentState, persistState]);
|
|
647
|
+
return react.useMemo(() => ({
|
|
648
|
+
onTableStateChange,
|
|
649
|
+
initialState,
|
|
650
|
+
}), [initialState, onTableStateChange]);
|
|
651
|
+
};
|
|
652
|
+
|
|
378
653
|
const cvaColumnFilterGrabbable = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "justify-center"], {
|
|
379
654
|
variants: {
|
|
380
655
|
disabled: {
|
|
@@ -1778,4 +2053,5 @@ exports.fromTUSortToTanStack = fromTUSortToTanStack;
|
|
|
1778
2053
|
exports.fromTanStackToTUSort = fromTanStackToTUSort;
|
|
1779
2054
|
exports.useColumnHelper = useColumnHelper;
|
|
1780
2055
|
exports.useTable = useTable;
|
|
2056
|
+
exports.useTablePersistence = useTablePersistence;
|
|
1781
2057
|
exports.useTableSelection = useTableSelection;
|
package/index.esm.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { registerTranslations, useNamespaceTranslation, NamespaceTrans } from '@trackunit/i18n-library-translation';
|
|
3
|
-
import { MenuItem, Icon, Button, Tooltip, useOverflowItems, MoreMenu, MenuList, Spacer, cvaInteractableItem, Text, Popover, PopoverTrigger, IconButton, PopoverContent, useBidirectionalScroll, noPagination, Card, Spinner, EmptyState } from '@trackunit/react-components';
|
|
3
|
+
import { MenuItem, Icon, Button, Tooltip, useOverflowItems, MoreMenu, MenuList, Spacer, usePersistedState, useDebounce, cvaInteractableItem, Text, Popover, PopoverTrigger, IconButton, PopoverContent, useBidirectionalScroll, noPagination, Card, Spinner, EmptyState } from '@trackunit/react-components';
|
|
4
4
|
import { objectValues, VISIBLE_ONLY_COLUMN_VISIBILITY_KEY, nonNullable, objectKeys, objectEntries } from '@trackunit/shared-utils';
|
|
5
|
-
import { useMemo, Children, cloneElement,
|
|
5
|
+
import { useMemo, Children, cloneElement, useCallback, useState, useEffect, useRef, createElement } from 'react';
|
|
6
6
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
7
7
|
import { Link } from '@tanstack/react-router';
|
|
8
|
+
import { useCurrentUser } from '@trackunit/react-core-hooks';
|
|
9
|
+
import { z } from 'zod';
|
|
8
10
|
import { cvaLabel, ToggleSwitch, Search, RadioGroup, RadioItem, Checkbox } from '@trackunit/react-form-components';
|
|
9
11
|
import update from 'immutability-helper';
|
|
10
12
|
import { useDrop, useDrag, DndProvider } from 'react-dnd';
|
|
@@ -374,6 +376,279 @@ const ActionSheet = ({ actions, dropdownActions, moreActions = [], selections, r
|
|
|
374
376
|
moreActions: moreActions.map(action => actionDataToMenuItem(action, dataTestId)) })] }));
|
|
375
377
|
};
|
|
376
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Short keys used in URL-hash-encoded table state to minimize fragment
|
|
381
|
+
* length. localStorage continues to use the full key names. The full URL
|
|
382
|
+
* still benefits from the shorter payload — even though the hash fragment
|
|
383
|
+
* is no longer subject to the AWS WAF query-string cap, the resulting
|
|
384
|
+
* fragment is what shows in shared links and browser history entries.
|
|
385
|
+
*/
|
|
386
|
+
const URL_KEY_MAP = {
|
|
387
|
+
columnOrder: "co",
|
|
388
|
+
columnVisibility: "cv",
|
|
389
|
+
sorting: "s",
|
|
390
|
+
columnPinning: "cp",
|
|
391
|
+
columnSizing: "cs",
|
|
392
|
+
};
|
|
393
|
+
const REVERSE_URL_KEY_MAP = Object.fromEntries(Object.entries(URL_KEY_MAP).map(([full, short]) => [short, full]));
|
|
394
|
+
const compactPinningSchema = z.object({
|
|
395
|
+
l: z.array(z.string()).optional(),
|
|
396
|
+
r: z.array(z.string()).optional(),
|
|
397
|
+
left: z.array(z.string()).optional(),
|
|
398
|
+
right: z.array(z.string()).optional(),
|
|
399
|
+
});
|
|
400
|
+
/**
|
|
401
|
+
* Compacts table state for URL encoding:
|
|
402
|
+
* - Uses short keys (co, cv, s, cp)
|
|
403
|
+
* - Stores columnVisibility as a visible-column array only
|
|
404
|
+
* - Stores columnOrder as visible-column order when columnVisibility is available
|
|
405
|
+
* - Drops empty pinning arrays
|
|
406
|
+
* - Excludes `expanded` (not shareable)
|
|
407
|
+
*/
|
|
408
|
+
const compactForUrl = (state) => {
|
|
409
|
+
const compact = {};
|
|
410
|
+
const columnVisibilityEntries = state.columnVisibility ? Object.entries(state.columnVisibility) : undefined;
|
|
411
|
+
const visibleColumns = columnVisibilityEntries?.filter(([, visible]) => visible).map(([key]) => key) ?? [];
|
|
412
|
+
const visibleColumnSet = new Set(visibleColumns);
|
|
413
|
+
if (state.columnVisibility && Object.keys(state.columnVisibility).length > 0) {
|
|
414
|
+
compact[URL_KEY_MAP.columnVisibility] = visibleColumns;
|
|
415
|
+
}
|
|
416
|
+
if (state.columnOrder && state.columnOrder.length > 0) {
|
|
417
|
+
compact[URL_KEY_MAP.columnOrder] =
|
|
418
|
+
state.columnVisibility && Object.keys(state.columnVisibility).length > 0
|
|
419
|
+
? [
|
|
420
|
+
...state.columnOrder.filter(columnId => visibleColumnSet.has(columnId)),
|
|
421
|
+
...visibleColumns.filter(columnId => !state.columnOrder?.includes(columnId)),
|
|
422
|
+
]
|
|
423
|
+
: state.columnOrder;
|
|
424
|
+
}
|
|
425
|
+
if (state.sorting && state.sorting.length > 0) {
|
|
426
|
+
compact[URL_KEY_MAP.sorting] = state.sorting;
|
|
427
|
+
}
|
|
428
|
+
if (state.columnSizing && Object.keys(state.columnSizing).length > 0) {
|
|
429
|
+
compact[URL_KEY_MAP.columnSizing] = state.columnSizing;
|
|
430
|
+
}
|
|
431
|
+
if (state.columnPinning) {
|
|
432
|
+
const pinning = {};
|
|
433
|
+
if (state.columnPinning.left && state.columnPinning.left.length > 0) {
|
|
434
|
+
pinning.l = state.columnPinning.left;
|
|
435
|
+
}
|
|
436
|
+
if (state.columnPinning.right && state.columnPinning.right.length > 0) {
|
|
437
|
+
pinning.r = state.columnPinning.right;
|
|
438
|
+
}
|
|
439
|
+
if (Object.keys(pinning).length > 0) {
|
|
440
|
+
compact[URL_KEY_MAP.columnPinning] = pinning;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return compact;
|
|
444
|
+
};
|
|
445
|
+
const compactRecordSchema = z.record(z.string(), z.unknown());
|
|
446
|
+
const visibilityArraySchema = z.array(z.string());
|
|
447
|
+
const compactVisibilitySchema = z
|
|
448
|
+
.object({
|
|
449
|
+
v: z.array(z.string()).optional(),
|
|
450
|
+
h: z.array(z.string()).optional(),
|
|
451
|
+
})
|
|
452
|
+
.strict();
|
|
453
|
+
/**
|
|
454
|
+
* Expands a compact URL-decoded object back into full TableColumnOptions shape.
|
|
455
|
+
*/
|
|
456
|
+
const expandFromUrl = (decoded) => {
|
|
457
|
+
const parsed = compactRecordSchema.safeParse(decoded);
|
|
458
|
+
if (!parsed.success) {
|
|
459
|
+
return decoded;
|
|
460
|
+
}
|
|
461
|
+
const expanded = {};
|
|
462
|
+
for (const [key, value] of Object.entries(parsed.data)) {
|
|
463
|
+
const fullKey = REVERSE_URL_KEY_MAP[key] ?? key;
|
|
464
|
+
expanded[fullKey] = value;
|
|
465
|
+
}
|
|
466
|
+
const visibilityResult = visibilityArraySchema.safeParse(expanded.columnVisibility);
|
|
467
|
+
if (visibilityResult.success) {
|
|
468
|
+
expanded.columnVisibility = Object.fromEntries([
|
|
469
|
+
[VISIBLE_ONLY_COLUMN_VISIBILITY_KEY, true],
|
|
470
|
+
...visibilityResult.data.map(col => [col, true]),
|
|
471
|
+
]);
|
|
472
|
+
}
|
|
473
|
+
const compactVisibilityResult = compactVisibilitySchema.safeParse(expanded.columnVisibility);
|
|
474
|
+
if (compactVisibilityResult.success) {
|
|
475
|
+
expanded.columnVisibility = Object.fromEntries([
|
|
476
|
+
...(compactVisibilityResult.data.v ?? []).map(col => [col, true]),
|
|
477
|
+
...(compactVisibilityResult.data.h ?? []).map(col => [col, false]),
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
const pinningResult = compactPinningSchema.safeParse(expanded.columnPinning);
|
|
481
|
+
if (pinningResult.success) {
|
|
482
|
+
expanded.columnPinning = {
|
|
483
|
+
left: pinningResult.data.l ?? pinningResult.data.left ?? [],
|
|
484
|
+
right: pinningResult.data.r ?? pinningResult.data.right ?? [],
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
return expanded;
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* Provides transform functions for compacting table state before URL encoding
|
|
491
|
+
* and expanding it back after URL decoding.
|
|
492
|
+
*
|
|
493
|
+
* The compaction strategy:
|
|
494
|
+
* - Short property keys (columnOrder → co, columnVisibility → cv, etc.)
|
|
495
|
+
* - columnVisibility stored as an array of visible column names only
|
|
496
|
+
* - columnOrder stored as visible-column order when visibility is available
|
|
497
|
+
* - columnSizing stored under a short key (`cs`)
|
|
498
|
+
* - `expanded` is excluded from the URL and restored from localStorage via
|
|
499
|
+
* `usePersistedState`'s `mergeWithStorageOnRead`
|
|
500
|
+
* - Empty pinning arrays are excluded
|
|
501
|
+
*
|
|
502
|
+
* These transforms are designed to plug into {@link usePersistedState}'s
|
|
503
|
+
* `toUrlValue` and `fromUrlValue` options.
|
|
504
|
+
*/
|
|
505
|
+
const useCompactTableUrl = () => {
|
|
506
|
+
const toUrlValue = useCallback((state) => {
|
|
507
|
+
return compactForUrl(state);
|
|
508
|
+
}, []);
|
|
509
|
+
const fromUrlValue = useCallback((decoded) => {
|
|
510
|
+
return expandFromUrl(decoded);
|
|
511
|
+
}, []);
|
|
512
|
+
return useMemo(() => ({ toUrlValue, fromUrlValue }), [toUrlValue, fromUrlValue]);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const PERSISTENCE_KEY_MAX_LENGTH = 15;
|
|
516
|
+
const camelCaseRegex = /^[a-z][a-zA-Z0-9]*$/;
|
|
517
|
+
const persistenceKeySchema = z
|
|
518
|
+
.string()
|
|
519
|
+
.max(PERSISTENCE_KEY_MAX_LENGTH, {
|
|
520
|
+
message: `persistenceKey must be at most ${PERSISTENCE_KEY_MAX_LENGTH} characters`,
|
|
521
|
+
})
|
|
522
|
+
.regex(camelCaseRegex, {
|
|
523
|
+
message: "persistenceKey must be camelCase (start with lowercase letter, no underscores/hyphens)",
|
|
524
|
+
});
|
|
525
|
+
const expandedStateSchema = z.union([z.literal(true), z.record(z.string(), z.boolean())]);
|
|
526
|
+
/** Zod schema describing the persisted subset of table state (column order, sizing, visibility, sorting, pinning, and expanded). */
|
|
527
|
+
const tableStateSchema = z.object({
|
|
528
|
+
columnOrder: z.array(z.string()).optional(),
|
|
529
|
+
sorting: z.array(z.object({ id: z.string(), desc: z.boolean() })).optional(),
|
|
530
|
+
columnVisibility: z.record(z.string(), z.boolean()).optional(),
|
|
531
|
+
columnSizing: z.record(z.string(), z.number()).optional(),
|
|
532
|
+
columnPinning: z
|
|
533
|
+
.object({
|
|
534
|
+
left: z.array(z.string()).optional(),
|
|
535
|
+
right: z.array(z.string()).optional(),
|
|
536
|
+
})
|
|
537
|
+
.optional(),
|
|
538
|
+
expanded: expandedStateSchema.optional(),
|
|
539
|
+
});
|
|
540
|
+
const validateTableState = (raw) => {
|
|
541
|
+
const result = tableStateSchema.safeParse(raw);
|
|
542
|
+
return result.success ? result.data : undefined;
|
|
543
|
+
};
|
|
544
|
+
/**
|
|
545
|
+
* Persists and restores table column state (order, sizing, visibility, sorting, pinning, expanded) using
|
|
546
|
+
* the URL **hash fragment** and localStorage.
|
|
547
|
+
*
|
|
548
|
+
* Storage scope:
|
|
549
|
+
* - URL hash (shareable): `columnOrder`, `columnVisibility`, `sorting`, `columnPinning`, `columnSizing`.
|
|
550
|
+
* - localStorage: everything above plus `expanded`.
|
|
551
|
+
*
|
|
552
|
+
* The hash slot is used instead of search params because some deployments
|
|
553
|
+
* route through AWS WAF, whose `SizeRestrictions_QUERYSTRING` managed rule
|
|
554
|
+
* caps the query string at ~5000 bytes — easily breached by wide tables.
|
|
555
|
+
* The fragment is client-only and bypasses that cap entirely (subject to a
|
|
556
|
+
* 64 KiB soft limit enforced by `useHashParamSync`).
|
|
557
|
+
*
|
|
558
|
+
* Legacy compatibility: shared links that still use `?<persistenceKey>Tp=`
|
|
559
|
+
* search params from before the hash migration are decoded on first load
|
|
560
|
+
* and the search param is stripped via a `replace` navigation so old
|
|
561
|
+
* bookmarks and forwarded URLs continue to restore state without polluting
|
|
562
|
+
* browser history.
|
|
563
|
+
*
|
|
564
|
+
* On mount, state is loaded from the hash (or legacy search param as a
|
|
565
|
+
* one-shot fallback) and merged with localStorage so fields omitted from
|
|
566
|
+
* the URL, such as `expanded`, still survive reloads. Changes are
|
|
567
|
+
* debounced (300 ms) and synced to both the hash and localStorage.
|
|
568
|
+
*
|
|
569
|
+
* @param persistenceKey - Unique key used to store and retrieve the table state.
|
|
570
|
+
* Must be camelCase and at most 15 characters (e.g. "userTable", "assetList").
|
|
571
|
+
* @returns {UseTablePersistenceResult} An object containing:
|
|
572
|
+
* - `onTableStateChange` — callback to invoke when the table state changes.
|
|
573
|
+
* - `initialState` — the previously persisted {@link TableColumnOptions}, or `undefined` if none exists.
|
|
574
|
+
* @throws {Error} If persistenceKey is not camelCase or exceeds 15 characters.
|
|
575
|
+
*/
|
|
576
|
+
const useTablePersistence = (persistenceKey) => {
|
|
577
|
+
const parseResult = persistenceKeySchema.safeParse(persistenceKey);
|
|
578
|
+
if (!parseResult.success) {
|
|
579
|
+
throw new Error(parseResult.error.errors.map(e => e.message).join(", "));
|
|
580
|
+
}
|
|
581
|
+
const { clientSideUserId } = useCurrentUser();
|
|
582
|
+
const localStorageEnabled = clientSideUserId !== undefined;
|
|
583
|
+
const { toUrlValue, fromUrlValue } = useCompactTableUrl();
|
|
584
|
+
const { initialState, persistState } = usePersistedState({
|
|
585
|
+
key: `${persistenceKey}Tp`,
|
|
586
|
+
validate: validateTableState,
|
|
587
|
+
toUrlValue,
|
|
588
|
+
fromUrlValue,
|
|
589
|
+
replace: false,
|
|
590
|
+
clientSideUserId,
|
|
591
|
+
localStorageEnabled,
|
|
592
|
+
// `toUrlValue` strips fields that should not be shareable from the URL.
|
|
593
|
+
// The merge restores omitted fields from localStorage on read so they
|
|
594
|
+
// survive reloads.
|
|
595
|
+
mergeWithStorageOnRead: true,
|
|
596
|
+
// Move the persisted blob into the URL hash so it bypasses the AWS WAF
|
|
597
|
+
// query-string cap. `usePersistedState` also migrates any legacy
|
|
598
|
+
// `?<key>=...` search param into the hash on first load.
|
|
599
|
+
urlLocation: "hash",
|
|
600
|
+
});
|
|
601
|
+
const [pendingState, setPendingState] = useState(null);
|
|
602
|
+
const debouncedPending = useDebounce(pendingState, { delay: 300 });
|
|
603
|
+
const currentState = useMemo(() => {
|
|
604
|
+
if (!debouncedPending && !initialState) {
|
|
605
|
+
return undefined;
|
|
606
|
+
}
|
|
607
|
+
const columnOrder = debouncedPending?.columnOrder ?? initialState?.columnOrder;
|
|
608
|
+
const columnSizing = debouncedPending?.columnSizing ?? initialState?.columnSizing;
|
|
609
|
+
const columnVisibility = debouncedPending?.columnVisibility ?? initialState?.columnVisibility;
|
|
610
|
+
const sorting = debouncedPending?.sorting ?? initialState?.sorting;
|
|
611
|
+
const columnPinning = debouncedPending?.columnPinning ?? initialState?.columnPinning;
|
|
612
|
+
const expanded = debouncedPending?.expanded ?? initialState?.expanded;
|
|
613
|
+
const state = {};
|
|
614
|
+
if (columnOrder && columnOrder.length > 0) {
|
|
615
|
+
state.columnOrder = columnOrder;
|
|
616
|
+
}
|
|
617
|
+
if (columnSizing && Object.keys(columnSizing).length > 0) {
|
|
618
|
+
state.columnSizing = columnSizing;
|
|
619
|
+
}
|
|
620
|
+
if (columnVisibility && Object.keys(columnVisibility).length > 0) {
|
|
621
|
+
state.columnVisibility = columnVisibility;
|
|
622
|
+
}
|
|
623
|
+
if (sorting && sorting.length > 0) {
|
|
624
|
+
state.sorting = sorting;
|
|
625
|
+
}
|
|
626
|
+
if (columnPinning && ((columnPinning.left?.length ?? 0) > 0 || (columnPinning.right?.length ?? 0) > 0)) {
|
|
627
|
+
state.columnPinning = columnPinning;
|
|
628
|
+
}
|
|
629
|
+
if (expanded === true || (typeof expanded === "object" && Object.keys(expanded).length > 0)) {
|
|
630
|
+
state.expanded = expanded;
|
|
631
|
+
}
|
|
632
|
+
return Object.keys(state).length > 0 ? state : undefined;
|
|
633
|
+
}, [debouncedPending, initialState]);
|
|
634
|
+
const onTableStateChange = useCallback((newTableState) => {
|
|
635
|
+
if (!newTableState) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
setPendingState(newTableState);
|
|
639
|
+
}, []);
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
if (!currentState) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
persistState(currentState);
|
|
645
|
+
}, [currentState, persistState]);
|
|
646
|
+
return useMemo(() => ({
|
|
647
|
+
onTableStateChange,
|
|
648
|
+
initialState,
|
|
649
|
+
}), [initialState, onTableStateChange]);
|
|
650
|
+
};
|
|
651
|
+
|
|
377
652
|
const cvaColumnFilterGrabbable = cvaMerge(["flex", "items-center", "justify-center"], {
|
|
378
653
|
variants: {
|
|
379
654
|
disabled: {
|
|
@@ -1763,4 +2038,4 @@ const fromTanStackToTUSort = (input) => {
|
|
|
1763
2038
|
*/
|
|
1764
2039
|
setupLibraryTranslations();
|
|
1765
2040
|
|
|
1766
|
-
export { ActionSheet, ColumnFilter, SelectAllBanner, Sorting, Table, TableEvents, fromTUSortToTanStack, fromTanStackToTUSort, useColumnHelper, useTable, useTableSelection };
|
|
2041
|
+
export { ActionSheet, ColumnFilter, SelectAllBanner, Sorting, Table, TableEvents, fromTUSortToTanStack, fromTanStackToTUSort, useColumnHelper, useTable, useTablePersistence, useTableSelection };
|
package/migrations.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-table",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.60",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
"react-dnd": "16.0.1",
|
|
12
12
|
"react-dnd-html5-backend": "16.0.1",
|
|
13
13
|
"tailwind-merge": "^2.0.0",
|
|
14
|
-
"@trackunit/react-components": "2.1.
|
|
15
|
-
"@trackunit/
|
|
16
|
-
"@trackunit/
|
|
17
|
-
"@trackunit/
|
|
18
|
-
"@trackunit/
|
|
19
|
-
"@trackunit/react-
|
|
20
|
-
"@trackunit/
|
|
21
|
-
"@trackunit/
|
|
14
|
+
"@trackunit/react-components": "2.1.55",
|
|
15
|
+
"@trackunit/react-core-hooks": "1.17.66",
|
|
16
|
+
"@trackunit/shared-utils": "1.15.55",
|
|
17
|
+
"@trackunit/css-class-variance-utilities": "1.13.54",
|
|
18
|
+
"@trackunit/ui-icons": "1.13.55",
|
|
19
|
+
"@trackunit/react-table-base-components": "2.1.58",
|
|
20
|
+
"@trackunit/react-form-components": "2.1.57",
|
|
21
|
+
"@trackunit/i18n-library-translation": "2.0.55",
|
|
22
|
+
"@trackunit/iris-app-runtime-core-api": "1.16.60",
|
|
23
|
+
"zod": "^3.25.76"
|
|
22
24
|
},
|
|
23
25
|
"peerDependencies": {
|
|
24
26
|
"@tanstack/react-router": "^1.114.29",
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
"react": "^19.0.0",
|
|
28
30
|
"react-hook-form": "^7.62.0"
|
|
29
31
|
},
|
|
32
|
+
"migrations": "./migrations.json",
|
|
30
33
|
"module": "./index.esm.js",
|
|
31
34
|
"main": "./index.cjs.js",
|
|
32
35
|
"types": "./index.d.ts"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const persistenceKeySchema: z.ZodString;
|
|
3
|
+
export type PersistenceKey = z.infer<typeof persistenceKeySchema>;
|
|
4
|
+
/** Zod schema describing the persisted subset of table state (column order, sizing, visibility, sorting, pinning, and expanded). */
|
|
5
|
+
declare const tableStateSchema: z.ZodObject<{
|
|
6
|
+
columnOrder: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
7
|
+
sorting: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
desc: z.ZodBoolean;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
id: string;
|
|
12
|
+
desc: boolean;
|
|
13
|
+
}, {
|
|
14
|
+
id: string;
|
|
15
|
+
desc: boolean;
|
|
16
|
+
}>, "many">>;
|
|
17
|
+
columnVisibility: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
18
|
+
columnSizing: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
19
|
+
columnPinning: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
left: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
21
|
+
right: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
right?: string[] | undefined;
|
|
24
|
+
left?: string[] | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
right?: string[] | undefined;
|
|
27
|
+
left?: string[] | undefined;
|
|
28
|
+
}>>;
|
|
29
|
+
expanded: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<true>, z.ZodRecord<z.ZodString, z.ZodBoolean>]>>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
columnOrder?: string[] | undefined;
|
|
32
|
+
columnVisibility?: Record<string, boolean> | undefined;
|
|
33
|
+
sorting?: {
|
|
34
|
+
id: string;
|
|
35
|
+
desc: boolean;
|
|
36
|
+
}[] | undefined;
|
|
37
|
+
columnPinning?: {
|
|
38
|
+
right?: string[] | undefined;
|
|
39
|
+
left?: string[] | undefined;
|
|
40
|
+
} | undefined;
|
|
41
|
+
columnSizing?: Record<string, number> | undefined;
|
|
42
|
+
expanded?: true | Record<string, boolean> | undefined;
|
|
43
|
+
}, {
|
|
44
|
+
columnOrder?: string[] | undefined;
|
|
45
|
+
columnVisibility?: Record<string, boolean> | undefined;
|
|
46
|
+
sorting?: {
|
|
47
|
+
id: string;
|
|
48
|
+
desc: boolean;
|
|
49
|
+
}[] | undefined;
|
|
50
|
+
columnPinning?: {
|
|
51
|
+
right?: string[] | undefined;
|
|
52
|
+
left?: string[] | undefined;
|
|
53
|
+
} | undefined;
|
|
54
|
+
columnSizing?: Record<string, number> | undefined;
|
|
55
|
+
expanded?: true | Record<string, boolean> | undefined;
|
|
56
|
+
}>;
|
|
57
|
+
export type TableColumnOptions = z.infer<typeof tableStateSchema>;
|
|
58
|
+
type UseTablePersistenceResult = {
|
|
59
|
+
readonly onTableStateChange: (newTableState: Partial<TableColumnOptions> | null) => void;
|
|
60
|
+
readonly initialState: TableColumnOptions | undefined;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Persists and restores table column state (order, sizing, visibility, sorting, pinning, expanded) using
|
|
64
|
+
* the URL **hash fragment** and localStorage.
|
|
65
|
+
*
|
|
66
|
+
* Storage scope:
|
|
67
|
+
* - URL hash (shareable): `columnOrder`, `columnVisibility`, `sorting`, `columnPinning`, `columnSizing`.
|
|
68
|
+
* - localStorage: everything above plus `expanded`.
|
|
69
|
+
*
|
|
70
|
+
* The hash slot is used instead of search params because some deployments
|
|
71
|
+
* route through AWS WAF, whose `SizeRestrictions_QUERYSTRING` managed rule
|
|
72
|
+
* caps the query string at ~5000 bytes — easily breached by wide tables.
|
|
73
|
+
* The fragment is client-only and bypasses that cap entirely (subject to a
|
|
74
|
+
* 64 KiB soft limit enforced by `useHashParamSync`).
|
|
75
|
+
*
|
|
76
|
+
* Legacy compatibility: shared links that still use `?<persistenceKey>Tp=`
|
|
77
|
+
* search params from before the hash migration are decoded on first load
|
|
78
|
+
* and the search param is stripped via a `replace` navigation so old
|
|
79
|
+
* bookmarks and forwarded URLs continue to restore state without polluting
|
|
80
|
+
* browser history.
|
|
81
|
+
*
|
|
82
|
+
* On mount, state is loaded from the hash (or legacy search param as a
|
|
83
|
+
* one-shot fallback) and merged with localStorage so fields omitted from
|
|
84
|
+
* the URL, such as `expanded`, still survive reloads. Changes are
|
|
85
|
+
* debounced (300 ms) and synced to both the hash and localStorage.
|
|
86
|
+
*
|
|
87
|
+
* @param persistenceKey - Unique key used to store and retrieve the table state.
|
|
88
|
+
* Must be camelCase and at most 15 characters (e.g. "userTable", "assetList").
|
|
89
|
+
* @returns {UseTablePersistenceResult} An object containing:
|
|
90
|
+
* - `onTableStateChange` — callback to invoke when the table state changes.
|
|
91
|
+
* - `initialState` — the previously persisted {@link TableColumnOptions}, or `undefined` if none exists.
|
|
92
|
+
* @throws {Error} If persistenceKey is not camelCase or exceeds 15 characters.
|
|
93
|
+
*/
|
|
94
|
+
export declare const useTablePersistence: (persistenceKey: PersistenceKey) => UseTablePersistenceResult;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TableColumnOptions } from "../useTablePersistence";
|
|
2
|
+
type CompactTableUrl = Record<string, unknown>;
|
|
3
|
+
type UseCompactTableUrlReturn = {
|
|
4
|
+
readonly toUrlValue: (state: TableColumnOptions) => CompactTableUrl;
|
|
5
|
+
readonly fromUrlValue: (decoded: unknown) => unknown;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Provides transform functions for compacting table state before URL encoding
|
|
9
|
+
* and expanding it back after URL decoding.
|
|
10
|
+
*
|
|
11
|
+
* The compaction strategy:
|
|
12
|
+
* - Short property keys (columnOrder → co, columnVisibility → cv, etc.)
|
|
13
|
+
* - columnVisibility stored as an array of visible column names only
|
|
14
|
+
* - columnOrder stored as visible-column order when visibility is available
|
|
15
|
+
* - columnSizing stored under a short key (`cs`)
|
|
16
|
+
* - `expanded` is excluded from the URL and restored from localStorage via
|
|
17
|
+
* `usePersistedState`'s `mergeWithStorageOnRead`
|
|
18
|
+
* - Empty pinning arrays are excluded
|
|
19
|
+
*
|
|
20
|
+
* These transforms are designed to plug into {@link usePersistedState}'s
|
|
21
|
+
* `toUrlValue` and `fromUrlValue` options.
|
|
22
|
+
*/
|
|
23
|
+
export declare const useCompactTableUrl: () => UseCompactTableUrlReturn;
|
|
24
|
+
export {};
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ColumnSort } from "@tanstack/react-table";
|
|
2
2
|
export * from "./ActionSheet/Actions";
|
|
3
3
|
export * from "./ActionSheet/ActionSheet";
|
|
4
|
+
export * from "./hooks/useTablePersistence/useTablePersistence";
|
|
4
5
|
export * from "./menus/ColumnFilter";
|
|
5
6
|
export * from "./menus/Sorting";
|
|
6
7
|
export * from "./SelectAllBanner";
|