@trackunit/react-components 1.21.15 → 1.21.17
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
CHANGED
|
@@ -20,8 +20,9 @@ var reactVirtual = require('@tanstack/react-virtual');
|
|
|
20
20
|
var reactHelmetAsync = require('react-helmet-async');
|
|
21
21
|
var reactTabs = require('@radix-ui/react-tabs');
|
|
22
22
|
var fflate = require('fflate');
|
|
23
|
-
var zod = require('zod');
|
|
24
23
|
var superjson = require('superjson');
|
|
24
|
+
var zod = require('zod');
|
|
25
|
+
var dequal = require('dequal');
|
|
25
26
|
|
|
26
27
|
const cvaIcon = cssClassVarianceUtilities.cvaMerge(["aspect-square", "inline-grid", "relative", "shrink-0"], {
|
|
27
28
|
variants: {
|
|
@@ -7829,14 +7830,15 @@ const getSheetPanelStyle = ({ snapHeight, isOpen, closePhase, variant, autoHeigh
|
|
|
7829
7830
|
height: autoHeight ? "fit-content" : `var(--sheet-drag-height, ${snapHeight})`,
|
|
7830
7831
|
maxHeight: maxHeight ?? `calc(100cqh - ${FULL_HEIGHT_TOP_MARGIN_PX}px)`,
|
|
7831
7832
|
pointerEvents: "auto",
|
|
7832
|
-
transform: isOpen
|
|
7833
|
-
|
|
7833
|
+
transform: isOpen
|
|
7834
|
+
? "translateY(0) scale(var(--sheet-stack-scale, 1))"
|
|
7835
|
+
: "translateY(100%) scale(var(--sheet-stack-scale, 1))",
|
|
7836
|
+
transformOrigin: "bottom center",
|
|
7837
|
+
transition: isDragging
|
|
7834
7838
|
? "none"
|
|
7835
|
-
:
|
|
7836
|
-
?
|
|
7837
|
-
:
|
|
7838
|
-
? SHEET_TRANSFORM_TRANSITION
|
|
7839
|
-
: SHEET_OPEN_TRANSITION,
|
|
7839
|
+
: suppressTransition || autoHeight || !isOpen
|
|
7840
|
+
? SHEET_TRANSFORM_TRANSITION
|
|
7841
|
+
: SHEET_OPEN_TRANSITION,
|
|
7840
7842
|
};
|
|
7841
7843
|
};
|
|
7842
7844
|
|
|
@@ -10316,8 +10318,11 @@ const useCustomEncoding = () => {
|
|
|
10316
10318
|
// If it's already a string, use it directly; otherwise stringify the object
|
|
10317
10319
|
const json = typeof input === "string" ? input : JSON.stringify(input);
|
|
10318
10320
|
const textInput = new TextEncoder().encode(json);
|
|
10319
|
-
// Use fflate for synchronous gzip compression
|
|
10320
|
-
|
|
10321
|
+
// Use fflate for synchronous gzip compression.
|
|
10322
|
+
// mtime: 0 ensures deterministic output — without it the gzip header
|
|
10323
|
+
// includes the current timestamp, making the same input produce a
|
|
10324
|
+
// different encoded string on every call.
|
|
10325
|
+
const compressed = fflate.gzipSync(textInput, { mtime: 0 });
|
|
10321
10326
|
return b64urlEncode(compressed);
|
|
10322
10327
|
}
|
|
10323
10328
|
catch (_) {
|
|
@@ -10369,6 +10374,36 @@ const useCustomEncoding = () => {
|
|
|
10369
10374
|
return react.useMemo(() => ({ encode, decode }), [encode, decode]);
|
|
10370
10375
|
};
|
|
10371
10376
|
|
|
10377
|
+
/**
|
|
10378
|
+
* Internal envelope used to tag superjson-serialized data in web storage.
|
|
10379
|
+
*
|
|
10380
|
+
* `__serializer` is a **reserved internal key** — consumer state objects must
|
|
10381
|
+
* not include it as a top-level key. The double-underscore prefix is a
|
|
10382
|
+
* deliberate signal that this is a private implementation detail.
|
|
10383
|
+
*
|
|
10384
|
+
* A runtime warning is emitted (via `writeToStorage`) if reserved keys are
|
|
10385
|
+
* detected in the value being written.
|
|
10386
|
+
*/
|
|
10387
|
+
const taggedSuperjsonEnvelopeSchema = zod.z.object({
|
|
10388
|
+
__serializer: zod.z.literal("superjson"),
|
|
10389
|
+
json: zod.z.custom(),
|
|
10390
|
+
meta: zod.z.custom().optional(),
|
|
10391
|
+
});
|
|
10392
|
+
const storageSerializer = {
|
|
10393
|
+
serialize: (value) => {
|
|
10394
|
+
const serialized = superjson.serialize(value);
|
|
10395
|
+
return JSON.stringify({ __serializer: "superjson", ...serialized });
|
|
10396
|
+
},
|
|
10397
|
+
deserialize: (value) => {
|
|
10398
|
+
const parsed = JSON.parse(value);
|
|
10399
|
+
const result = taggedSuperjsonEnvelopeSchema.safeParse(parsed);
|
|
10400
|
+
if (result.success) {
|
|
10401
|
+
return superjson.deserialize({ json: result.data.json, meta: result.data.meta });
|
|
10402
|
+
}
|
|
10403
|
+
return parsed;
|
|
10404
|
+
},
|
|
10405
|
+
};
|
|
10406
|
+
|
|
10372
10407
|
/**
|
|
10373
10408
|
* Runs a sequential migration pipeline on the provided data, applying
|
|
10374
10409
|
* each migration whose version is in the range (fromVersion, toVersion].
|
|
@@ -10474,36 +10509,6 @@ const salvageState = (schema, rawData, defaultState) => {
|
|
|
10474
10509
|
}
|
|
10475
10510
|
};
|
|
10476
10511
|
|
|
10477
|
-
/**
|
|
10478
|
-
* Internal envelope used to tag superjson-serialized data in web storage.
|
|
10479
|
-
*
|
|
10480
|
-
* `__serializer` is a **reserved internal key** — consumer state objects must
|
|
10481
|
-
* not include it as a top-level key. The double-underscore prefix is a
|
|
10482
|
-
* deliberate signal that this is a private implementation detail.
|
|
10483
|
-
*
|
|
10484
|
-
* A runtime warning is emitted (via `writeToStorage`) if reserved keys are
|
|
10485
|
-
* detected in the value being written.
|
|
10486
|
-
*/
|
|
10487
|
-
const taggedSuperjsonEnvelopeSchema = zod.z.object({
|
|
10488
|
-
__serializer: zod.z.literal("superjson"),
|
|
10489
|
-
json: zod.z.custom(),
|
|
10490
|
-
meta: zod.z.custom().optional(),
|
|
10491
|
-
});
|
|
10492
|
-
const storageSerializer = {
|
|
10493
|
-
serialize: (value) => {
|
|
10494
|
-
const serialized = superjson.serialize(value);
|
|
10495
|
-
return JSON.stringify({ __serializer: "superjson", ...serialized });
|
|
10496
|
-
},
|
|
10497
|
-
deserialize: (value) => {
|
|
10498
|
-
const parsed = JSON.parse(value);
|
|
10499
|
-
const result = taggedSuperjsonEnvelopeSchema.safeParse(parsed);
|
|
10500
|
-
if (result.success) {
|
|
10501
|
-
return superjson.deserialize({ json: result.data.json, meta: result.data.meta });
|
|
10502
|
-
}
|
|
10503
|
-
return parsed;
|
|
10504
|
-
},
|
|
10505
|
-
};
|
|
10506
|
-
|
|
10507
10512
|
/**
|
|
10508
10513
|
* Internal envelope that pairs stored data with a schema version number.
|
|
10509
10514
|
*
|
|
@@ -10979,6 +10984,202 @@ const useSessionStorage = (options) => useWebStorage(globalThis.sessionStorage,
|
|
|
10979
10984
|
*/
|
|
10980
10985
|
const useSessionStorageReducer = (options) => useWebStorageReducer(globalThis.sessionStorage, options);
|
|
10981
10986
|
|
|
10987
|
+
const MAX_URL_LENGTH = 5000;
|
|
10988
|
+
/**
|
|
10989
|
+
* Syncs an encoded string value with a URL search parameter via Tanstack Router.
|
|
10990
|
+
*
|
|
10991
|
+
* Provides a write function that updates the URL through the application
|
|
10992
|
+
* router (preserving other search params), guards against exceeding
|
|
10993
|
+
* {@link MAX_URL_LENGTH}, and detects external URL changes (browser
|
|
10994
|
+
* back/forward, shared links) via an optional callback.
|
|
10995
|
+
*
|
|
10996
|
+
* @param options - Configuration for the search param sync.
|
|
10997
|
+
* @param options.key - The URL search parameter name.
|
|
10998
|
+
* @param options.enabled - Set to `false` to disable all URL interaction (default `true`).
|
|
10999
|
+
* @param options.onExternalChange - Called when the param changes externally.
|
|
11000
|
+
* @param options.replace - `true` to always use `replaceState`.
|
|
11001
|
+
*/
|
|
11002
|
+
const useSearchParamSync = ({ key, enabled = true, onExternalChange, replace: replaceOption, }) => {
|
|
11003
|
+
const navigate = reactRouter.useNavigate();
|
|
11004
|
+
const location = reactRouter.useLocation();
|
|
11005
|
+
const search = reactRouter.useSearch({ strict: false, shouldThrow: false });
|
|
11006
|
+
const lastWrittenRef = react.useRef(undefined);
|
|
11007
|
+
const onExternalChangeRef = react.useRef(onExternalChange);
|
|
11008
|
+
react.useEffect(() => {
|
|
11009
|
+
onExternalChangeRef.current = onExternalChange;
|
|
11010
|
+
}, [onExternalChange]);
|
|
11011
|
+
const currentSearchValue = react.useMemo(() => {
|
|
11012
|
+
if (!enabled) {
|
|
11013
|
+
return undefined;
|
|
11014
|
+
}
|
|
11015
|
+
const value = search?.[key];
|
|
11016
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
11017
|
+
return String(value);
|
|
11018
|
+
}
|
|
11019
|
+
return undefined;
|
|
11020
|
+
}, [enabled, search, key]);
|
|
11021
|
+
react.useEffect(() => {
|
|
11022
|
+
if (!enabled) {
|
|
11023
|
+
return;
|
|
11024
|
+
}
|
|
11025
|
+
if (currentSearchValue === undefined) {
|
|
11026
|
+
return;
|
|
11027
|
+
}
|
|
11028
|
+
if (currentSearchValue === lastWrittenRef.current) {
|
|
11029
|
+
return;
|
|
11030
|
+
}
|
|
11031
|
+
if (lastWrittenRef.current === undefined) {
|
|
11032
|
+
return;
|
|
11033
|
+
}
|
|
11034
|
+
requestAnimationFrame(() => {
|
|
11035
|
+
onExternalChangeRef.current?.();
|
|
11036
|
+
});
|
|
11037
|
+
}, [currentSearchValue, enabled]);
|
|
11038
|
+
const getUrlLengthWithSearchParam = react.useCallback((paramKey, paramValue, params) => {
|
|
11039
|
+
const otherParamsLength = sharedUtils.objectKeys(params)
|
|
11040
|
+
.filter(k => k !== paramKey)
|
|
11041
|
+
.reduce((totalLength, k) => {
|
|
11042
|
+
const kLen = encodeURIComponent(String(k)).length;
|
|
11043
|
+
const v = params[k];
|
|
11044
|
+
const vLen = v !== null && v !== undefined ? encodeURIComponent(String(v)).length : 0;
|
|
11045
|
+
return totalLength + kLen + vLen + (totalLength > 0 ? 2 : 1);
|
|
11046
|
+
}, 0);
|
|
11047
|
+
const urlBase = location.href.length - (location.searchStr.length || 0) + (location.hash.length || 0);
|
|
11048
|
+
return urlBase + otherParamsLength + 1 + paramKey.length + 1 + paramValue.length;
|
|
11049
|
+
}, [location]);
|
|
11050
|
+
const updateSearchParam = react.useCallback((encodedValue) => {
|
|
11051
|
+
if (!enabled) {
|
|
11052
|
+
return;
|
|
11053
|
+
}
|
|
11054
|
+
if (encodedValue !== undefined && encodedValue === lastWrittenRef.current) {
|
|
11055
|
+
return;
|
|
11056
|
+
}
|
|
11057
|
+
lastWrittenRef.current = encodedValue;
|
|
11058
|
+
if (currentSearchValue === encodedValue) {
|
|
11059
|
+
return;
|
|
11060
|
+
}
|
|
11061
|
+
requestAnimationFrame(() => {
|
|
11062
|
+
const shouldReplace = replaceOption ?? !Boolean(currentSearchValue);
|
|
11063
|
+
void navigate({
|
|
11064
|
+
to: ".",
|
|
11065
|
+
search: (prev) => {
|
|
11066
|
+
if (getUrlLengthWithSearchParam(key, encodedValue || "", prev) <= MAX_URL_LENGTH) {
|
|
11067
|
+
return { ...prev, [key]: encodedValue };
|
|
11068
|
+
}
|
|
11069
|
+
else {
|
|
11070
|
+
return { ...prev, [key]: undefined };
|
|
11071
|
+
}
|
|
11072
|
+
},
|
|
11073
|
+
hash: location.hash,
|
|
11074
|
+
replace: shouldReplace,
|
|
11075
|
+
});
|
|
11076
|
+
});
|
|
11077
|
+
}, [enabled, navigate, key, replaceOption, location.hash, getUrlLengthWithSearchParam, currentSearchValue]);
|
|
11078
|
+
return react.useMemo(() => ({ searchValue: currentSearchValue, updateSearchParam }), [currentSearchValue, updateSearchParam]);
|
|
11079
|
+
};
|
|
11080
|
+
|
|
11081
|
+
/**
|
|
11082
|
+
* Generates a localStorage key, optionally scoped to a specific user.
|
|
11083
|
+
*
|
|
11084
|
+
* When `userId` is provided the key is `"key-userId"`.
|
|
11085
|
+
* When omitted the key is returned as-is (unscoped).
|
|
11086
|
+
*
|
|
11087
|
+
* @param key - Unique persistence identifier.
|
|
11088
|
+
* @param userId - Client-side user id to scope storage per user.
|
|
11089
|
+
* @returns {string} The combined storage key.
|
|
11090
|
+
*/
|
|
11091
|
+
const useStorageKey = (key, userId) => {
|
|
11092
|
+
return react.useMemo(() => (userId ? `${key}-${userId}` : key), [key, userId]);
|
|
11093
|
+
};
|
|
11094
|
+
|
|
11095
|
+
/**
|
|
11096
|
+
* Generic persistence hook that loads state from URL search params (with
|
|
11097
|
+
* localStorage fallback) and writes changes to both.
|
|
11098
|
+
*
|
|
11099
|
+
* On mount the hook tries, in order:
|
|
11100
|
+
* 1. Decode the URL search param and pass it through `validate`.
|
|
11101
|
+
* 2. If that yields nothing, read localStorage and pass the parsed JSON through `validate`.
|
|
11102
|
+
*
|
|
11103
|
+
* `persistState` writes the state to localStorage (via `serialize`, default
|
|
11104
|
+
* `JSON.stringify`) and syncs to the URL (via `toUrlValue` + encoding).
|
|
11105
|
+
* Duplicate writes where the state has not changed (deep equality) are skipped.
|
|
11106
|
+
*
|
|
11107
|
+
* @param options - Configuration for the persisted state.
|
|
11108
|
+
* @param options.key - Unique identifier used for both the URL search param and the localStorage key.
|
|
11109
|
+
* @param options.validate - Called with the decoded/parsed value; must return `TState` or `undefined`.
|
|
11110
|
+
* @param options.serialize - Custom localStorage serialiser (default `storageSerializer.serialize`).
|
|
11111
|
+
* @param options.toUrlValue - Transform applied before URL encoding (default: encode state as-is).
|
|
11112
|
+
* @param options.fromUrlValue - Transform applied after URL decoding, before validation (default: identity).
|
|
11113
|
+
* @param options.enabled - When `false` the URL is neither read nor written (default `true`).
|
|
11114
|
+
* @param options.onExternalChange - Fired when the URL param changes externally (e.g. browser back).
|
|
11115
|
+
* @param options.replace - Forwarded to `useSearchParamSync`.
|
|
11116
|
+
* @param options.clientSideUserId - The user ID to use for the localStorage key.
|
|
11117
|
+
*/
|
|
11118
|
+
const usePersistedState = ({ key, validate, serialize, toUrlValue, fromUrlValue, enabled = true, onExternalChange, replace, clientSideUserId, }) => {
|
|
11119
|
+
const { encode, decode } = useCustomEncoding();
|
|
11120
|
+
const { searchValue, updateSearchParam } = useSearchParamSync({
|
|
11121
|
+
key,
|
|
11122
|
+
enabled,
|
|
11123
|
+
onExternalChange,
|
|
11124
|
+
replace,
|
|
11125
|
+
});
|
|
11126
|
+
const storageKey = useStorageKey(key, clientSideUserId);
|
|
11127
|
+
const validateRef = react.useRef(validate);
|
|
11128
|
+
react.useEffect(() => {
|
|
11129
|
+
validateRef.current = validate;
|
|
11130
|
+
}, [validate]);
|
|
11131
|
+
const toUrlValueRef = react.useRef(toUrlValue);
|
|
11132
|
+
react.useEffect(() => {
|
|
11133
|
+
toUrlValueRef.current = toUrlValue;
|
|
11134
|
+
}, [toUrlValue]);
|
|
11135
|
+
const fromUrlValueRef = react.useRef(fromUrlValue);
|
|
11136
|
+
react.useEffect(() => {
|
|
11137
|
+
fromUrlValueRef.current = fromUrlValue;
|
|
11138
|
+
}, [fromUrlValue]);
|
|
11139
|
+
const serializeRef = react.useRef(serialize);
|
|
11140
|
+
react.useEffect(() => {
|
|
11141
|
+
serializeRef.current = serialize;
|
|
11142
|
+
}, [serialize]);
|
|
11143
|
+
const [initialState] = react.useState(() => {
|
|
11144
|
+
if (enabled && searchValue) {
|
|
11145
|
+
try {
|
|
11146
|
+
const decoded = decode(searchValue);
|
|
11147
|
+
const transformed = fromUrlValue ? fromUrlValue(decoded) : decoded;
|
|
11148
|
+
const validated = validate(transformed);
|
|
11149
|
+
if (validated !== undefined) {
|
|
11150
|
+
return validated;
|
|
11151
|
+
}
|
|
11152
|
+
}
|
|
11153
|
+
catch {
|
|
11154
|
+
// fall through to localStorage
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
try {
|
|
11158
|
+
const raw = localStorage.getItem(storageKey);
|
|
11159
|
+
if (raw) {
|
|
11160
|
+
const parsed = storageSerializer.deserialize(raw);
|
|
11161
|
+
return validate(parsed);
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
catch {
|
|
11165
|
+
// no valid stored state
|
|
11166
|
+
}
|
|
11167
|
+
return undefined;
|
|
11168
|
+
});
|
|
11169
|
+
const lastPersistedRef = react.useRef(initialState);
|
|
11170
|
+
const persistState = react.useCallback((state) => {
|
|
11171
|
+
if (dequal.dequal(lastPersistedRef.current, state)) {
|
|
11172
|
+
return;
|
|
11173
|
+
}
|
|
11174
|
+
lastPersistedRef.current = state;
|
|
11175
|
+
const serialized = serializeRef.current ? serializeRef.current(state) : storageSerializer.serialize(state);
|
|
11176
|
+
localStorage.setItem(storageKey, serialized);
|
|
11177
|
+
const urlValue = toUrlValueRef.current ? toUrlValueRef.current(state) : state;
|
|
11178
|
+
updateSearchParam(encode(urlValue));
|
|
11179
|
+
}, [storageKey, encode, updateSearchParam]);
|
|
11180
|
+
return react.useMemo(() => ({ initialState, persistState }), [initialState, persistState]);
|
|
11181
|
+
};
|
|
11182
|
+
|
|
10982
11183
|
const OVERSCAN = 10;
|
|
10983
11184
|
const DEFAULT_ROW_HEIGHT = 50;
|
|
10984
11185
|
/**
|
|
@@ -12154,6 +12355,7 @@ exports.KPICardSkeleton = KPICardSkeleton;
|
|
|
12154
12355
|
exports.KPISkeleton = KPISkeleton;
|
|
12155
12356
|
exports.List = List;
|
|
12156
12357
|
exports.ListItem = ListItem;
|
|
12358
|
+
exports.MAX_URL_LENGTH = MAX_URL_LENGTH;
|
|
12157
12359
|
exports.MenuDivider = MenuDivider;
|
|
12158
12360
|
exports.MenuItem = MenuItem;
|
|
12159
12361
|
exports.MenuList = MenuList;
|
|
@@ -12255,6 +12457,7 @@ exports.iconColorNames = iconColorNames;
|
|
|
12255
12457
|
exports.iconPalette = iconPalette;
|
|
12256
12458
|
exports.noPagination = noPagination;
|
|
12257
12459
|
exports.preferenceCardGrid = preferenceCardGrid;
|
|
12460
|
+
exports.storageSerializer = storageSerializer;
|
|
12258
12461
|
exports.useBidirectionalScroll = useBidirectionalScroll;
|
|
12259
12462
|
exports.useClickOutside = useClickOutside;
|
|
12260
12463
|
exports.useContainerBreakpoints = useContainerBreakpoints;
|
|
@@ -12282,6 +12485,7 @@ exports.useMergeRefs = useMergeRefs;
|
|
|
12282
12485
|
exports.useModifierKey = useModifierKey;
|
|
12283
12486
|
exports.useOverflowBorder = useOverflowBorder;
|
|
12284
12487
|
exports.useOverflowItems = useOverflowItems;
|
|
12488
|
+
exports.usePersistedState = usePersistedState;
|
|
12285
12489
|
exports.usePopoverContext = usePopoverContext;
|
|
12286
12490
|
exports.usePrevious = usePrevious;
|
|
12287
12491
|
exports.usePrompt = usePrompt;
|
|
@@ -12290,11 +12494,13 @@ exports.useRelayPagination = useRelayPagination;
|
|
|
12290
12494
|
exports.useResize = useResize;
|
|
12291
12495
|
exports.useScrollBlock = useScrollBlock;
|
|
12292
12496
|
exports.useScrollDetection = useScrollDetection;
|
|
12497
|
+
exports.useSearchParamSync = useSearchParamSync;
|
|
12293
12498
|
exports.useSelfUpdatingRef = useSelfUpdatingRef;
|
|
12294
12499
|
exports.useSessionStorage = useSessionStorage;
|
|
12295
12500
|
exports.useSessionStorageReducer = useSessionStorageReducer;
|
|
12296
12501
|
exports.useSheet = useSheet;
|
|
12297
12502
|
exports.useSheetSnap = useSheetSnap;
|
|
12503
|
+
exports.useStorageKey = useStorageKey;
|
|
12298
12504
|
exports.useTextSearch = useTextSearch;
|
|
12299
12505
|
exports.useTimeout = useTimeout;
|
|
12300
12506
|
exports.useViewportBreakpoints = useViewportBreakpoints;
|
package/index.esm.js
CHANGED
|
@@ -10,7 +10,7 @@ import IconSpriteSolid from '@trackunit/ui-icons/icons-sprite-solid.svg';
|
|
|
10
10
|
import { snakeCase, titleCase } from 'string-ts';
|
|
11
11
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
12
12
|
import { Slot, Slottable } from '@radix-ui/react-slot';
|
|
13
|
-
import { Link, useBlocker,
|
|
13
|
+
import { Link, useBlocker, useNavigate, useLocation, useSearch } from '@tanstack/react-router';
|
|
14
14
|
import { isEqual, omit } from 'es-toolkit';
|
|
15
15
|
import { useFloating, offset, flip, shift, size, autoUpdate, useClick, useDismiss, useHover as useHover$1, safePolygon, useRole, useInteractions, FloatingPortal, useMergeRefs as useMergeRefs$1, FloatingFocusManager, arrow, useTransitionStatus, FloatingArrow } from '@floating-ui/react';
|
|
16
16
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -18,8 +18,9 @@ import { useVirtualizer } from '@tanstack/react-virtual';
|
|
|
18
18
|
import { HelmetProvider, Helmet } from 'react-helmet-async';
|
|
19
19
|
import { Trigger, Content as Content$1, List as List$1, Root } from '@radix-ui/react-tabs';
|
|
20
20
|
import { gzipSync, gunzipSync } from 'fflate';
|
|
21
|
-
import { z } from 'zod';
|
|
22
21
|
import superjson from 'superjson';
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
import { dequal } from 'dequal';
|
|
23
24
|
|
|
24
25
|
const cvaIcon = cvaMerge(["aspect-square", "inline-grid", "relative", "shrink-0"], {
|
|
25
26
|
variants: {
|
|
@@ -7827,14 +7828,15 @@ const getSheetPanelStyle = ({ snapHeight, isOpen, closePhase, variant, autoHeigh
|
|
|
7827
7828
|
height: autoHeight ? "fit-content" : `var(--sheet-drag-height, ${snapHeight})`,
|
|
7828
7829
|
maxHeight: maxHeight ?? `calc(100cqh - ${FULL_HEIGHT_TOP_MARGIN_PX}px)`,
|
|
7829
7830
|
pointerEvents: "auto",
|
|
7830
|
-
transform: isOpen
|
|
7831
|
-
|
|
7831
|
+
transform: isOpen
|
|
7832
|
+
? "translateY(0) scale(var(--sheet-stack-scale, 1))"
|
|
7833
|
+
: "translateY(100%) scale(var(--sheet-stack-scale, 1))",
|
|
7834
|
+
transformOrigin: "bottom center",
|
|
7835
|
+
transition: isDragging
|
|
7832
7836
|
? "none"
|
|
7833
|
-
:
|
|
7834
|
-
?
|
|
7835
|
-
:
|
|
7836
|
-
? SHEET_TRANSFORM_TRANSITION
|
|
7837
|
-
: SHEET_OPEN_TRANSITION,
|
|
7837
|
+
: suppressTransition || autoHeight || !isOpen
|
|
7838
|
+
? SHEET_TRANSFORM_TRANSITION
|
|
7839
|
+
: SHEET_OPEN_TRANSITION,
|
|
7838
7840
|
};
|
|
7839
7841
|
};
|
|
7840
7842
|
|
|
@@ -10314,8 +10316,11 @@ const useCustomEncoding = () => {
|
|
|
10314
10316
|
// If it's already a string, use it directly; otherwise stringify the object
|
|
10315
10317
|
const json = typeof input === "string" ? input : JSON.stringify(input);
|
|
10316
10318
|
const textInput = new TextEncoder().encode(json);
|
|
10317
|
-
// Use fflate for synchronous gzip compression
|
|
10318
|
-
|
|
10319
|
+
// Use fflate for synchronous gzip compression.
|
|
10320
|
+
// mtime: 0 ensures deterministic output — without it the gzip header
|
|
10321
|
+
// includes the current timestamp, making the same input produce a
|
|
10322
|
+
// different encoded string on every call.
|
|
10323
|
+
const compressed = gzipSync(textInput, { mtime: 0 });
|
|
10319
10324
|
return b64urlEncode(compressed);
|
|
10320
10325
|
}
|
|
10321
10326
|
catch (_) {
|
|
@@ -10367,6 +10372,36 @@ const useCustomEncoding = () => {
|
|
|
10367
10372
|
return useMemo(() => ({ encode, decode }), [encode, decode]);
|
|
10368
10373
|
};
|
|
10369
10374
|
|
|
10375
|
+
/**
|
|
10376
|
+
* Internal envelope used to tag superjson-serialized data in web storage.
|
|
10377
|
+
*
|
|
10378
|
+
* `__serializer` is a **reserved internal key** — consumer state objects must
|
|
10379
|
+
* not include it as a top-level key. The double-underscore prefix is a
|
|
10380
|
+
* deliberate signal that this is a private implementation detail.
|
|
10381
|
+
*
|
|
10382
|
+
* A runtime warning is emitted (via `writeToStorage`) if reserved keys are
|
|
10383
|
+
* detected in the value being written.
|
|
10384
|
+
*/
|
|
10385
|
+
const taggedSuperjsonEnvelopeSchema = z.object({
|
|
10386
|
+
__serializer: z.literal("superjson"),
|
|
10387
|
+
json: z.custom(),
|
|
10388
|
+
meta: z.custom().optional(),
|
|
10389
|
+
});
|
|
10390
|
+
const storageSerializer = {
|
|
10391
|
+
serialize: (value) => {
|
|
10392
|
+
const serialized = superjson.serialize(value);
|
|
10393
|
+
return JSON.stringify({ __serializer: "superjson", ...serialized });
|
|
10394
|
+
},
|
|
10395
|
+
deserialize: (value) => {
|
|
10396
|
+
const parsed = JSON.parse(value);
|
|
10397
|
+
const result = taggedSuperjsonEnvelopeSchema.safeParse(parsed);
|
|
10398
|
+
if (result.success) {
|
|
10399
|
+
return superjson.deserialize({ json: result.data.json, meta: result.data.meta });
|
|
10400
|
+
}
|
|
10401
|
+
return parsed;
|
|
10402
|
+
},
|
|
10403
|
+
};
|
|
10404
|
+
|
|
10370
10405
|
/**
|
|
10371
10406
|
* Runs a sequential migration pipeline on the provided data, applying
|
|
10372
10407
|
* each migration whose version is in the range (fromVersion, toVersion].
|
|
@@ -10472,36 +10507,6 @@ const salvageState = (schema, rawData, defaultState) => {
|
|
|
10472
10507
|
}
|
|
10473
10508
|
};
|
|
10474
10509
|
|
|
10475
|
-
/**
|
|
10476
|
-
* Internal envelope used to tag superjson-serialized data in web storage.
|
|
10477
|
-
*
|
|
10478
|
-
* `__serializer` is a **reserved internal key** — consumer state objects must
|
|
10479
|
-
* not include it as a top-level key. The double-underscore prefix is a
|
|
10480
|
-
* deliberate signal that this is a private implementation detail.
|
|
10481
|
-
*
|
|
10482
|
-
* A runtime warning is emitted (via `writeToStorage`) if reserved keys are
|
|
10483
|
-
* detected in the value being written.
|
|
10484
|
-
*/
|
|
10485
|
-
const taggedSuperjsonEnvelopeSchema = z.object({
|
|
10486
|
-
__serializer: z.literal("superjson"),
|
|
10487
|
-
json: z.custom(),
|
|
10488
|
-
meta: z.custom().optional(),
|
|
10489
|
-
});
|
|
10490
|
-
const storageSerializer = {
|
|
10491
|
-
serialize: (value) => {
|
|
10492
|
-
const serialized = superjson.serialize(value);
|
|
10493
|
-
return JSON.stringify({ __serializer: "superjson", ...serialized });
|
|
10494
|
-
},
|
|
10495
|
-
deserialize: (value) => {
|
|
10496
|
-
const parsed = JSON.parse(value);
|
|
10497
|
-
const result = taggedSuperjsonEnvelopeSchema.safeParse(parsed);
|
|
10498
|
-
if (result.success) {
|
|
10499
|
-
return superjson.deserialize({ json: result.data.json, meta: result.data.meta });
|
|
10500
|
-
}
|
|
10501
|
-
return parsed;
|
|
10502
|
-
},
|
|
10503
|
-
};
|
|
10504
|
-
|
|
10505
10510
|
/**
|
|
10506
10511
|
* Internal envelope that pairs stored data with a schema version number.
|
|
10507
10512
|
*
|
|
@@ -10977,6 +10982,202 @@ const useSessionStorage = (options) => useWebStorage(globalThis.sessionStorage,
|
|
|
10977
10982
|
*/
|
|
10978
10983
|
const useSessionStorageReducer = (options) => useWebStorageReducer(globalThis.sessionStorage, options);
|
|
10979
10984
|
|
|
10985
|
+
const MAX_URL_LENGTH = 5000;
|
|
10986
|
+
/**
|
|
10987
|
+
* Syncs an encoded string value with a URL search parameter via Tanstack Router.
|
|
10988
|
+
*
|
|
10989
|
+
* Provides a write function that updates the URL through the application
|
|
10990
|
+
* router (preserving other search params), guards against exceeding
|
|
10991
|
+
* {@link MAX_URL_LENGTH}, and detects external URL changes (browser
|
|
10992
|
+
* back/forward, shared links) via an optional callback.
|
|
10993
|
+
*
|
|
10994
|
+
* @param options - Configuration for the search param sync.
|
|
10995
|
+
* @param options.key - The URL search parameter name.
|
|
10996
|
+
* @param options.enabled - Set to `false` to disable all URL interaction (default `true`).
|
|
10997
|
+
* @param options.onExternalChange - Called when the param changes externally.
|
|
10998
|
+
* @param options.replace - `true` to always use `replaceState`.
|
|
10999
|
+
*/
|
|
11000
|
+
const useSearchParamSync = ({ key, enabled = true, onExternalChange, replace: replaceOption, }) => {
|
|
11001
|
+
const navigate = useNavigate();
|
|
11002
|
+
const location = useLocation();
|
|
11003
|
+
const search = useSearch({ strict: false, shouldThrow: false });
|
|
11004
|
+
const lastWrittenRef = useRef(undefined);
|
|
11005
|
+
const onExternalChangeRef = useRef(onExternalChange);
|
|
11006
|
+
useEffect(() => {
|
|
11007
|
+
onExternalChangeRef.current = onExternalChange;
|
|
11008
|
+
}, [onExternalChange]);
|
|
11009
|
+
const currentSearchValue = useMemo(() => {
|
|
11010
|
+
if (!enabled) {
|
|
11011
|
+
return undefined;
|
|
11012
|
+
}
|
|
11013
|
+
const value = search?.[key];
|
|
11014
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
11015
|
+
return String(value);
|
|
11016
|
+
}
|
|
11017
|
+
return undefined;
|
|
11018
|
+
}, [enabled, search, key]);
|
|
11019
|
+
useEffect(() => {
|
|
11020
|
+
if (!enabled) {
|
|
11021
|
+
return;
|
|
11022
|
+
}
|
|
11023
|
+
if (currentSearchValue === undefined) {
|
|
11024
|
+
return;
|
|
11025
|
+
}
|
|
11026
|
+
if (currentSearchValue === lastWrittenRef.current) {
|
|
11027
|
+
return;
|
|
11028
|
+
}
|
|
11029
|
+
if (lastWrittenRef.current === undefined) {
|
|
11030
|
+
return;
|
|
11031
|
+
}
|
|
11032
|
+
requestAnimationFrame(() => {
|
|
11033
|
+
onExternalChangeRef.current?.();
|
|
11034
|
+
});
|
|
11035
|
+
}, [currentSearchValue, enabled]);
|
|
11036
|
+
const getUrlLengthWithSearchParam = useCallback((paramKey, paramValue, params) => {
|
|
11037
|
+
const otherParamsLength = objectKeys(params)
|
|
11038
|
+
.filter(k => k !== paramKey)
|
|
11039
|
+
.reduce((totalLength, k) => {
|
|
11040
|
+
const kLen = encodeURIComponent(String(k)).length;
|
|
11041
|
+
const v = params[k];
|
|
11042
|
+
const vLen = v !== null && v !== undefined ? encodeURIComponent(String(v)).length : 0;
|
|
11043
|
+
return totalLength + kLen + vLen + (totalLength > 0 ? 2 : 1);
|
|
11044
|
+
}, 0);
|
|
11045
|
+
const urlBase = location.href.length - (location.searchStr.length || 0) + (location.hash.length || 0);
|
|
11046
|
+
return urlBase + otherParamsLength + 1 + paramKey.length + 1 + paramValue.length;
|
|
11047
|
+
}, [location]);
|
|
11048
|
+
const updateSearchParam = useCallback((encodedValue) => {
|
|
11049
|
+
if (!enabled) {
|
|
11050
|
+
return;
|
|
11051
|
+
}
|
|
11052
|
+
if (encodedValue !== undefined && encodedValue === lastWrittenRef.current) {
|
|
11053
|
+
return;
|
|
11054
|
+
}
|
|
11055
|
+
lastWrittenRef.current = encodedValue;
|
|
11056
|
+
if (currentSearchValue === encodedValue) {
|
|
11057
|
+
return;
|
|
11058
|
+
}
|
|
11059
|
+
requestAnimationFrame(() => {
|
|
11060
|
+
const shouldReplace = replaceOption ?? !Boolean(currentSearchValue);
|
|
11061
|
+
void navigate({
|
|
11062
|
+
to: ".",
|
|
11063
|
+
search: (prev) => {
|
|
11064
|
+
if (getUrlLengthWithSearchParam(key, encodedValue || "", prev) <= MAX_URL_LENGTH) {
|
|
11065
|
+
return { ...prev, [key]: encodedValue };
|
|
11066
|
+
}
|
|
11067
|
+
else {
|
|
11068
|
+
return { ...prev, [key]: undefined };
|
|
11069
|
+
}
|
|
11070
|
+
},
|
|
11071
|
+
hash: location.hash,
|
|
11072
|
+
replace: shouldReplace,
|
|
11073
|
+
});
|
|
11074
|
+
});
|
|
11075
|
+
}, [enabled, navigate, key, replaceOption, location.hash, getUrlLengthWithSearchParam, currentSearchValue]);
|
|
11076
|
+
return useMemo(() => ({ searchValue: currentSearchValue, updateSearchParam }), [currentSearchValue, updateSearchParam]);
|
|
11077
|
+
};
|
|
11078
|
+
|
|
11079
|
+
/**
|
|
11080
|
+
* Generates a localStorage key, optionally scoped to a specific user.
|
|
11081
|
+
*
|
|
11082
|
+
* When `userId` is provided the key is `"key-userId"`.
|
|
11083
|
+
* When omitted the key is returned as-is (unscoped).
|
|
11084
|
+
*
|
|
11085
|
+
* @param key - Unique persistence identifier.
|
|
11086
|
+
* @param userId - Client-side user id to scope storage per user.
|
|
11087
|
+
* @returns {string} The combined storage key.
|
|
11088
|
+
*/
|
|
11089
|
+
const useStorageKey = (key, userId) => {
|
|
11090
|
+
return useMemo(() => (userId ? `${key}-${userId}` : key), [key, userId]);
|
|
11091
|
+
};
|
|
11092
|
+
|
|
11093
|
+
/**
|
|
11094
|
+
* Generic persistence hook that loads state from URL search params (with
|
|
11095
|
+
* localStorage fallback) and writes changes to both.
|
|
11096
|
+
*
|
|
11097
|
+
* On mount the hook tries, in order:
|
|
11098
|
+
* 1. Decode the URL search param and pass it through `validate`.
|
|
11099
|
+
* 2. If that yields nothing, read localStorage and pass the parsed JSON through `validate`.
|
|
11100
|
+
*
|
|
11101
|
+
* `persistState` writes the state to localStorage (via `serialize`, default
|
|
11102
|
+
* `JSON.stringify`) and syncs to the URL (via `toUrlValue` + encoding).
|
|
11103
|
+
* Duplicate writes where the state has not changed (deep equality) are skipped.
|
|
11104
|
+
*
|
|
11105
|
+
* @param options - Configuration for the persisted state.
|
|
11106
|
+
* @param options.key - Unique identifier used for both the URL search param and the localStorage key.
|
|
11107
|
+
* @param options.validate - Called with the decoded/parsed value; must return `TState` or `undefined`.
|
|
11108
|
+
* @param options.serialize - Custom localStorage serialiser (default `storageSerializer.serialize`).
|
|
11109
|
+
* @param options.toUrlValue - Transform applied before URL encoding (default: encode state as-is).
|
|
11110
|
+
* @param options.fromUrlValue - Transform applied after URL decoding, before validation (default: identity).
|
|
11111
|
+
* @param options.enabled - When `false` the URL is neither read nor written (default `true`).
|
|
11112
|
+
* @param options.onExternalChange - Fired when the URL param changes externally (e.g. browser back).
|
|
11113
|
+
* @param options.replace - Forwarded to `useSearchParamSync`.
|
|
11114
|
+
* @param options.clientSideUserId - The user ID to use for the localStorage key.
|
|
11115
|
+
*/
|
|
11116
|
+
const usePersistedState = ({ key, validate, serialize, toUrlValue, fromUrlValue, enabled = true, onExternalChange, replace, clientSideUserId, }) => {
|
|
11117
|
+
const { encode, decode } = useCustomEncoding();
|
|
11118
|
+
const { searchValue, updateSearchParam } = useSearchParamSync({
|
|
11119
|
+
key,
|
|
11120
|
+
enabled,
|
|
11121
|
+
onExternalChange,
|
|
11122
|
+
replace,
|
|
11123
|
+
});
|
|
11124
|
+
const storageKey = useStorageKey(key, clientSideUserId);
|
|
11125
|
+
const validateRef = useRef(validate);
|
|
11126
|
+
useEffect(() => {
|
|
11127
|
+
validateRef.current = validate;
|
|
11128
|
+
}, [validate]);
|
|
11129
|
+
const toUrlValueRef = useRef(toUrlValue);
|
|
11130
|
+
useEffect(() => {
|
|
11131
|
+
toUrlValueRef.current = toUrlValue;
|
|
11132
|
+
}, [toUrlValue]);
|
|
11133
|
+
const fromUrlValueRef = useRef(fromUrlValue);
|
|
11134
|
+
useEffect(() => {
|
|
11135
|
+
fromUrlValueRef.current = fromUrlValue;
|
|
11136
|
+
}, [fromUrlValue]);
|
|
11137
|
+
const serializeRef = useRef(serialize);
|
|
11138
|
+
useEffect(() => {
|
|
11139
|
+
serializeRef.current = serialize;
|
|
11140
|
+
}, [serialize]);
|
|
11141
|
+
const [initialState] = useState(() => {
|
|
11142
|
+
if (enabled && searchValue) {
|
|
11143
|
+
try {
|
|
11144
|
+
const decoded = decode(searchValue);
|
|
11145
|
+
const transformed = fromUrlValue ? fromUrlValue(decoded) : decoded;
|
|
11146
|
+
const validated = validate(transformed);
|
|
11147
|
+
if (validated !== undefined) {
|
|
11148
|
+
return validated;
|
|
11149
|
+
}
|
|
11150
|
+
}
|
|
11151
|
+
catch {
|
|
11152
|
+
// fall through to localStorage
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11155
|
+
try {
|
|
11156
|
+
const raw = localStorage.getItem(storageKey);
|
|
11157
|
+
if (raw) {
|
|
11158
|
+
const parsed = storageSerializer.deserialize(raw);
|
|
11159
|
+
return validate(parsed);
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
catch {
|
|
11163
|
+
// no valid stored state
|
|
11164
|
+
}
|
|
11165
|
+
return undefined;
|
|
11166
|
+
});
|
|
11167
|
+
const lastPersistedRef = useRef(initialState);
|
|
11168
|
+
const persistState = useCallback((state) => {
|
|
11169
|
+
if (dequal(lastPersistedRef.current, state)) {
|
|
11170
|
+
return;
|
|
11171
|
+
}
|
|
11172
|
+
lastPersistedRef.current = state;
|
|
11173
|
+
const serialized = serializeRef.current ? serializeRef.current(state) : storageSerializer.serialize(state);
|
|
11174
|
+
localStorage.setItem(storageKey, serialized);
|
|
11175
|
+
const urlValue = toUrlValueRef.current ? toUrlValueRef.current(state) : state;
|
|
11176
|
+
updateSearchParam(encode(urlValue));
|
|
11177
|
+
}, [storageKey, encode, updateSearchParam]);
|
|
11178
|
+
return useMemo(() => ({ initialState, persistState }), [initialState, persistState]);
|
|
11179
|
+
};
|
|
11180
|
+
|
|
10980
11181
|
const OVERSCAN = 10;
|
|
10981
11182
|
const DEFAULT_ROW_HEIGHT = 50;
|
|
10982
11183
|
/**
|
|
@@ -12121,4 +12322,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
|
|
|
12121
12322
|
return useMemo(() => ({ focused }), [focused]);
|
|
12122
12323
|
};
|
|
12123
12324
|
|
|
12124
|
-
export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowBorder, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
|
|
12325
|
+
export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MAX_URL_LENGTH, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, storageSerializer, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.17",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
"@trackunit/ui-icons": "1.11.93",
|
|
20
20
|
"es-toolkit": "^1.39.10",
|
|
21
21
|
"@tanstack/react-virtual": "3.13.12",
|
|
22
|
+
"dequal": "^2.0.3",
|
|
22
23
|
"fflate": "^0.8.2",
|
|
23
24
|
"superjson": "^2.2.6",
|
|
24
|
-
"zod": "^3.
|
|
25
|
+
"zod": "^3.25.76"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
28
|
"react": "^19.0.0",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type UsePersistedStateOptions<TState> = {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly validate: (raw: unknown) => TState | undefined;
|
|
4
|
+
readonly serialize?: (state: TState) => string;
|
|
5
|
+
readonly toUrlValue?: (state: TState) => string | object | Array<object>;
|
|
6
|
+
readonly fromUrlValue?: (decoded: unknown) => unknown;
|
|
7
|
+
readonly enabled?: boolean;
|
|
8
|
+
readonly onExternalChange?: () => void;
|
|
9
|
+
readonly replace?: boolean;
|
|
10
|
+
readonly clientSideUserId?: string;
|
|
11
|
+
};
|
|
12
|
+
type UsePersistedStateReturn<TState> = {
|
|
13
|
+
readonly initialState: TState | undefined;
|
|
14
|
+
readonly persistState: (state: TState) => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Generic persistence hook that loads state from URL search params (with
|
|
18
|
+
* localStorage fallback) and writes changes to both.
|
|
19
|
+
*
|
|
20
|
+
* On mount the hook tries, in order:
|
|
21
|
+
* 1. Decode the URL search param and pass it through `validate`.
|
|
22
|
+
* 2. If that yields nothing, read localStorage and pass the parsed JSON through `validate`.
|
|
23
|
+
*
|
|
24
|
+
* `persistState` writes the state to localStorage (via `serialize`, default
|
|
25
|
+
* `JSON.stringify`) and syncs to the URL (via `toUrlValue` + encoding).
|
|
26
|
+
* Duplicate writes where the state has not changed (deep equality) are skipped.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Configuration for the persisted state.
|
|
29
|
+
* @param options.key - Unique identifier used for both the URL search param and the localStorage key.
|
|
30
|
+
* @param options.validate - Called with the decoded/parsed value; must return `TState` or `undefined`.
|
|
31
|
+
* @param options.serialize - Custom localStorage serialiser (default `storageSerializer.serialize`).
|
|
32
|
+
* @param options.toUrlValue - Transform applied before URL encoding (default: encode state as-is).
|
|
33
|
+
* @param options.fromUrlValue - Transform applied after URL decoding, before validation (default: identity).
|
|
34
|
+
* @param options.enabled - When `false` the URL is neither read nor written (default `true`).
|
|
35
|
+
* @param options.onExternalChange - Fired when the URL param changes externally (e.g. browser back).
|
|
36
|
+
* @param options.replace - Forwarded to `useSearchParamSync`.
|
|
37
|
+
* @param options.clientSideUserId - The user ID to use for the localStorage key.
|
|
38
|
+
*/
|
|
39
|
+
export declare const usePersistedState: <TState extends object>({ key, validate, serialize, toUrlValue, fromUrlValue, enabled, onExternalChange, replace, clientSideUserId, }: UsePersistedStateOptions<TState>) => UsePersistedStateReturn<TState>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const MAX_URL_LENGTH = 5000;
|
|
2
|
+
type UseSearchParamSyncOptions = {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly enabled?: boolean;
|
|
5
|
+
readonly onExternalChange?: () => void;
|
|
6
|
+
readonly replace?: boolean;
|
|
7
|
+
};
|
|
8
|
+
type UseSearchParamSyncReturn = {
|
|
9
|
+
readonly searchValue: string | undefined;
|
|
10
|
+
readonly updateSearchParam: (encodedValue: string | undefined) => void;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Syncs an encoded string value with a URL search parameter via Tanstack Router.
|
|
14
|
+
*
|
|
15
|
+
* Provides a write function that updates the URL through the application
|
|
16
|
+
* router (preserving other search params), guards against exceeding
|
|
17
|
+
* {@link MAX_URL_LENGTH}, and detects external URL changes (browser
|
|
18
|
+
* back/forward, shared links) via an optional callback.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Configuration for the search param sync.
|
|
21
|
+
* @param options.key - The URL search parameter name.
|
|
22
|
+
* @param options.enabled - Set to `false` to disable all URL interaction (default `true`).
|
|
23
|
+
* @param options.onExternalChange - Called when the param changes externally.
|
|
24
|
+
* @param options.replace - `true` to always use `replaceState`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const useSearchParamSync: ({ key, enabled, onExternalChange, replace: replaceOption, }: UseSearchParamSyncOptions) => UseSearchParamSyncReturn;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a localStorage key, optionally scoped to a specific user.
|
|
3
|
+
*
|
|
4
|
+
* When `userId` is provided the key is `"key-userId"`.
|
|
5
|
+
* When omitted the key is returned as-is (unscoped).
|
|
6
|
+
*
|
|
7
|
+
* @param key - Unique persistence identifier.
|
|
8
|
+
* @param userId - Client-side user id to scope storage per user.
|
|
9
|
+
* @returns {string} The combined storage key.
|
|
10
|
+
*/
|
|
11
|
+
export declare const useStorageKey: (key: string, userId?: string) => string;
|
package/src/index.d.ts
CHANGED
|
@@ -110,12 +110,16 @@ export * from "./components/ValueBar/ValueBarTypes";
|
|
|
110
110
|
export * from "./components/ZStack/ZStack";
|
|
111
111
|
export * from "./components/ZStack/ZStack.variants";
|
|
112
112
|
export * from "./hooks/encoding/useCustomEncoding";
|
|
113
|
+
export * from "./hooks/localStorage/storageSerializer";
|
|
113
114
|
export * from "./hooks/localStorage/types";
|
|
114
115
|
export * from "./hooks/localStorage/useLocalStorage";
|
|
115
116
|
export * from "./hooks/localStorage/useLocalStorageReducer";
|
|
116
117
|
export * from "./hooks/localStorage/useSessionStorage";
|
|
117
118
|
export * from "./hooks/localStorage/useSessionStorageReducer";
|
|
118
119
|
export * from "./hooks/noPagination";
|
|
120
|
+
export * from "./hooks/persistence/usePersistedState";
|
|
121
|
+
export * from "./hooks/persistence/useSearchParamSync";
|
|
122
|
+
export * from "./hooks/persistence/useStorageKey";
|
|
119
123
|
export * from "./hooks/useBidirectionalScroll";
|
|
120
124
|
export * from "./hooks/useClickOutside";
|
|
121
125
|
export * from "./hooks/useContainerBreakpoints";
|