@trackunit/react-components 1.21.17 → 1.22.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/index.cjs.js
CHANGED
|
@@ -9128,10 +9128,11 @@ const useSheetMotionOverflow = ({ panelEl, isDragging, scrollAreaEl, separatorEl
|
|
|
9128
9128
|
* CSS transitions on transform; the component stays mounted during the
|
|
9129
9129
|
* close animation and unmounts after the transition completes.
|
|
9130
9130
|
*/
|
|
9131
|
-
const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor = "center", snapping = true, resizable = true, variant = "default", trapFocus = true, container, dockedContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation = "subsequent", children, }) => {
|
|
9131
|
+
const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor = "center", snapping = true, resizable = true, variant = "default", trapFocus = true, container, dockedContent, persistentContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation = "subsequent", children, }) => {
|
|
9132
9132
|
const isFirstRender = useIsFirstRender();
|
|
9133
9133
|
const skipEntryAnimation = entryAnimation === "never" || (entryAnimation === "subsequent" && isFirstRender);
|
|
9134
9134
|
const effectiveSnapping = resizable && snapping;
|
|
9135
|
+
const dockingEnabled = dockedContent !== undefined || persistentContent !== undefined;
|
|
9135
9136
|
const [animState, animDispatch] = react.useReducer(sheetAnimationReducer, INITIAL_ANIMATION_STATE);
|
|
9136
9137
|
if (isOpen !== animState.prevIsOpen) {
|
|
9137
9138
|
animDispatch({
|
|
@@ -9143,7 +9144,7 @@ const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, o
|
|
|
9143
9144
|
const measurements = useSheetMeasurements({
|
|
9144
9145
|
shouldRender: animState.shouldRender,
|
|
9145
9146
|
state,
|
|
9146
|
-
dockingEnabled
|
|
9147
|
+
dockingEnabled,
|
|
9147
9148
|
snapping: effectiveSnapping,
|
|
9148
9149
|
externalRef: ref,
|
|
9149
9150
|
onGeometryChange,
|
|
@@ -9218,7 +9219,7 @@ const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, o
|
|
|
9218
9219
|
? `${state.effectiveDockedHeight}px`
|
|
9219
9220
|
: fitHeightMeasured
|
|
9220
9221
|
? `${cappedFitHeight}px`
|
|
9221
|
-
: getSnapLevelCssHeight(state.level, state.totalLevels,
|
|
9222
|
+
: getSnapLevelCssHeight(state.level, state.totalLevels, dockingEnabled, state.effectiveDockedHeight);
|
|
9222
9223
|
const fitMaxHeight = state.sizingMode === "fit" ? `calc(100cqh - ${FULL_HEIGHT_TOP_MARGIN_PX}px)` : undefined;
|
|
9223
9224
|
if (!animState.shouldRender)
|
|
9224
9225
|
return null;
|
|
@@ -9242,7 +9243,7 @@ const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, o
|
|
|
9242
9243
|
snapHeight: snapHeightCss,
|
|
9243
9244
|
suppressTransition: skipEntryAnimation && animState.visuallyOpen,
|
|
9244
9245
|
variant,
|
|
9245
|
-
}), children: [resizable ? (jsxRuntime.jsx(SheetHandle, { "data-testid": dataTestId !== undefined ? `${dataTestId}-handle` : undefined, isDragging: gestures.isDragging, onClick: handleClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ...gestureHandlers })) : null, jsxRuntime.jsx("div", { className: "h-px shrink-0 bg-neutral-200 opacity-0 transition-opacity duration-200", ref: setSeparatorEl }), jsxRuntime.
|
|
9246
|
+
}), children: [resizable ? (jsxRuntime.jsx(SheetHandle, { "data-testid": dataTestId !== undefined ? `${dataTestId}-handle` : undefined, isDragging: gestures.isDragging, onClick: handleClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ...gestureHandlers })) : null, jsxRuntime.jsx("div", { className: "h-px shrink-0 bg-neutral-200 opacity-0 transition-opacity duration-200", ref: setSeparatorEl }), jsxRuntime.jsxs("div", { className: cvaSheetScrollArea({ fillHeight: state.sizingMode !== "fit" }), "data-sheet-scroll-area": true, ref: setScrollAreaEl, children: [persistentContent, state.sizingMode === "docked" ? dockedContent : children] })] }));
|
|
9246
9247
|
return (jsxRuntime.jsx(Portal, { root: container, children: jsxRuntime.jsxs("div", { className: cvaSheetContainer({
|
|
9247
9248
|
docked: state.sizingMode === "docked",
|
|
9248
9249
|
}), "data-testid": dataTestId !== undefined ? `${dataTestId}-container` : undefined, ref: containerRef, children: [jsxRuntime.jsx(SheetOverlay, { "data-testid": dataTestId !== undefined ? `${dataTestId}-overlay` : undefined, visible: showOverlay }), shouldTrapFocus === true ? (jsxRuntime.jsx(react$1.FloatingFocusManager, { context: floatingUi.context, children: panel })) : (panel)] }) }));
|
|
@@ -11047,7 +11048,7 @@ const useSearchParamSync = ({ key, enabled = true, onExternalChange, replace: re
|
|
|
11047
11048
|
const urlBase = location.href.length - (location.searchStr.length || 0) + (location.hash.length || 0);
|
|
11048
11049
|
return urlBase + otherParamsLength + 1 + paramKey.length + 1 + paramValue.length;
|
|
11049
11050
|
}, [location]);
|
|
11050
|
-
const updateSearchParam = react.useCallback((encodedValue) => {
|
|
11051
|
+
const updateSearchParam = react.useCallback((encodedValue, options) => {
|
|
11051
11052
|
if (!enabled) {
|
|
11052
11053
|
return;
|
|
11053
11054
|
}
|
|
@@ -11059,7 +11060,7 @@ const useSearchParamSync = ({ key, enabled = true, onExternalChange, replace: re
|
|
|
11059
11060
|
return;
|
|
11060
11061
|
}
|
|
11061
11062
|
requestAnimationFrame(() => {
|
|
11062
|
-
const shouldReplace = replaceOption ?? !Boolean(currentSearchValue);
|
|
11063
|
+
const shouldReplace = options?.replace ?? replaceOption ?? !Boolean(currentSearchValue);
|
|
11063
11064
|
void navigate({
|
|
11064
11065
|
to: ".",
|
|
11065
11066
|
search: (prev) => {
|
|
@@ -11140,14 +11141,14 @@ const usePersistedState = ({ key, validate, serialize, toUrlValue, fromUrlValue,
|
|
|
11140
11141
|
react.useEffect(() => {
|
|
11141
11142
|
serializeRef.current = serialize;
|
|
11142
11143
|
}, [serialize]);
|
|
11143
|
-
const [initialState] = react.useState(() => {
|
|
11144
|
+
const [{ initialState, loadedFromStorage }] = react.useState(() => {
|
|
11144
11145
|
if (enabled && searchValue) {
|
|
11145
11146
|
try {
|
|
11146
11147
|
const decoded = decode(searchValue);
|
|
11147
11148
|
const transformed = fromUrlValue ? fromUrlValue(decoded) : decoded;
|
|
11148
11149
|
const validated = validate(transformed);
|
|
11149
11150
|
if (validated !== undefined) {
|
|
11150
|
-
return validated;
|
|
11151
|
+
return { initialState: validated, loadedFromStorage: false };
|
|
11151
11152
|
}
|
|
11152
11153
|
}
|
|
11153
11154
|
catch {
|
|
@@ -11158,15 +11159,28 @@ const usePersistedState = ({ key, validate, serialize, toUrlValue, fromUrlValue,
|
|
|
11158
11159
|
const raw = localStorage.getItem(storageKey);
|
|
11159
11160
|
if (raw) {
|
|
11160
11161
|
const parsed = storageSerializer.deserialize(raw);
|
|
11161
|
-
|
|
11162
|
+
const validated = validate(parsed);
|
|
11163
|
+
return { initialState: validated, loadedFromStorage: validated !== undefined };
|
|
11162
11164
|
}
|
|
11163
11165
|
}
|
|
11164
11166
|
catch {
|
|
11165
11167
|
// no valid stored state
|
|
11166
11168
|
}
|
|
11167
|
-
return undefined;
|
|
11169
|
+
return { initialState: undefined, loadedFromStorage: false };
|
|
11168
11170
|
});
|
|
11169
11171
|
const lastPersistedRef = react.useRef(initialState);
|
|
11172
|
+
const hasRestoredUrlRef = react.useRef(false);
|
|
11173
|
+
react.useEffect(() => {
|
|
11174
|
+
if (hasRestoredUrlRef.current) {
|
|
11175
|
+
return;
|
|
11176
|
+
}
|
|
11177
|
+
if (!enabled || !loadedFromStorage || initialState === undefined || searchValue !== undefined) {
|
|
11178
|
+
return;
|
|
11179
|
+
}
|
|
11180
|
+
hasRestoredUrlRef.current = true;
|
|
11181
|
+
const urlValue = toUrlValueRef.current ? toUrlValueRef.current(initialState) : initialState;
|
|
11182
|
+
updateSearchParam(encode(urlValue), { replace: true });
|
|
11183
|
+
}, [enabled, loadedFromStorage, initialState, searchValue, updateSearchParam, encode]);
|
|
11170
11184
|
const persistState = react.useCallback((state) => {
|
|
11171
11185
|
if (dequal.dequal(lastPersistedRef.current, state)) {
|
|
11172
11186
|
return;
|
package/index.esm.js
CHANGED
|
@@ -9126,10 +9126,11 @@ const useSheetMotionOverflow = ({ panelEl, isDragging, scrollAreaEl, separatorEl
|
|
|
9126
9126
|
* CSS transitions on transform; the component stays mounted during the
|
|
9127
9127
|
* close animation and unmounts after the transition completes.
|
|
9128
9128
|
*/
|
|
9129
|
-
const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor = "center", snapping = true, resizable = true, variant = "default", trapFocus = true, container, dockedContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation = "subsequent", children, }) => {
|
|
9129
|
+
const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor = "center", snapping = true, resizable = true, variant = "default", trapFocus = true, container, dockedContent, persistentContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation = "subsequent", children, }) => {
|
|
9130
9130
|
const isFirstRender = useIsFirstRender();
|
|
9131
9131
|
const skipEntryAnimation = entryAnimation === "never" || (entryAnimation === "subsequent" && isFirstRender);
|
|
9132
9132
|
const effectiveSnapping = resizable && snapping;
|
|
9133
|
+
const dockingEnabled = dockedContent !== undefined || persistentContent !== undefined;
|
|
9133
9134
|
const [animState, animDispatch] = useReducer(sheetAnimationReducer, INITIAL_ANIMATION_STATE);
|
|
9134
9135
|
if (isOpen !== animState.prevIsOpen) {
|
|
9135
9136
|
animDispatch({
|
|
@@ -9141,7 +9142,7 @@ const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, o
|
|
|
9141
9142
|
const measurements = useSheetMeasurements({
|
|
9142
9143
|
shouldRender: animState.shouldRender,
|
|
9143
9144
|
state,
|
|
9144
|
-
dockingEnabled
|
|
9145
|
+
dockingEnabled,
|
|
9145
9146
|
snapping: effectiveSnapping,
|
|
9146
9147
|
externalRef: ref,
|
|
9147
9148
|
onGeometryChange,
|
|
@@ -9216,7 +9217,7 @@ const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, o
|
|
|
9216
9217
|
? `${state.effectiveDockedHeight}px`
|
|
9217
9218
|
: fitHeightMeasured
|
|
9218
9219
|
? `${cappedFitHeight}px`
|
|
9219
|
-
: getSnapLevelCssHeight(state.level, state.totalLevels,
|
|
9220
|
+
: getSnapLevelCssHeight(state.level, state.totalLevels, dockingEnabled, state.effectiveDockedHeight);
|
|
9220
9221
|
const fitMaxHeight = state.sizingMode === "fit" ? `calc(100cqh - ${FULL_HEIGHT_TOP_MARGIN_PX}px)` : undefined;
|
|
9221
9222
|
if (!animState.shouldRender)
|
|
9222
9223
|
return null;
|
|
@@ -9240,7 +9241,7 @@ const Sheet = ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, o
|
|
|
9240
9241
|
snapHeight: snapHeightCss,
|
|
9241
9242
|
suppressTransition: skipEntryAnimation && animState.visuallyOpen,
|
|
9242
9243
|
variant,
|
|
9243
|
-
}), children: [resizable ? (jsx(SheetHandle, { "data-testid": dataTestId !== undefined ? `${dataTestId}-handle` : undefined, isDragging: gestures.isDragging, onClick: handleClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ...gestureHandlers })) : null, jsx("div", { className: "h-px shrink-0 bg-neutral-200 opacity-0 transition-opacity duration-200", ref: setSeparatorEl }),
|
|
9244
|
+
}), children: [resizable ? (jsx(SheetHandle, { "data-testid": dataTestId !== undefined ? `${dataTestId}-handle` : undefined, isDragging: gestures.isDragging, onClick: handleClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ...gestureHandlers })) : null, jsx("div", { className: "h-px shrink-0 bg-neutral-200 opacity-0 transition-opacity duration-200", ref: setSeparatorEl }), jsxs("div", { className: cvaSheetScrollArea({ fillHeight: state.sizingMode !== "fit" }), "data-sheet-scroll-area": true, ref: setScrollAreaEl, children: [persistentContent, state.sizingMode === "docked" ? dockedContent : children] })] }));
|
|
9244
9245
|
return (jsx(Portal, { root: container, children: jsxs("div", { className: cvaSheetContainer({
|
|
9245
9246
|
docked: state.sizingMode === "docked",
|
|
9246
9247
|
}), "data-testid": dataTestId !== undefined ? `${dataTestId}-container` : undefined, ref: containerRef, children: [jsx(SheetOverlay, { "data-testid": dataTestId !== undefined ? `${dataTestId}-overlay` : undefined, visible: showOverlay }), shouldTrapFocus === true ? (jsx(FloatingFocusManager, { context: floatingUi.context, children: panel })) : (panel)] }) }));
|
|
@@ -11045,7 +11046,7 @@ const useSearchParamSync = ({ key, enabled = true, onExternalChange, replace: re
|
|
|
11045
11046
|
const urlBase = location.href.length - (location.searchStr.length || 0) + (location.hash.length || 0);
|
|
11046
11047
|
return urlBase + otherParamsLength + 1 + paramKey.length + 1 + paramValue.length;
|
|
11047
11048
|
}, [location]);
|
|
11048
|
-
const updateSearchParam = useCallback((encodedValue) => {
|
|
11049
|
+
const updateSearchParam = useCallback((encodedValue, options) => {
|
|
11049
11050
|
if (!enabled) {
|
|
11050
11051
|
return;
|
|
11051
11052
|
}
|
|
@@ -11057,7 +11058,7 @@ const useSearchParamSync = ({ key, enabled = true, onExternalChange, replace: re
|
|
|
11057
11058
|
return;
|
|
11058
11059
|
}
|
|
11059
11060
|
requestAnimationFrame(() => {
|
|
11060
|
-
const shouldReplace = replaceOption ?? !Boolean(currentSearchValue);
|
|
11061
|
+
const shouldReplace = options?.replace ?? replaceOption ?? !Boolean(currentSearchValue);
|
|
11061
11062
|
void navigate({
|
|
11062
11063
|
to: ".",
|
|
11063
11064
|
search: (prev) => {
|
|
@@ -11138,14 +11139,14 @@ const usePersistedState = ({ key, validate, serialize, toUrlValue, fromUrlValue,
|
|
|
11138
11139
|
useEffect(() => {
|
|
11139
11140
|
serializeRef.current = serialize;
|
|
11140
11141
|
}, [serialize]);
|
|
11141
|
-
const [initialState] = useState(() => {
|
|
11142
|
+
const [{ initialState, loadedFromStorage }] = useState(() => {
|
|
11142
11143
|
if (enabled && searchValue) {
|
|
11143
11144
|
try {
|
|
11144
11145
|
const decoded = decode(searchValue);
|
|
11145
11146
|
const transformed = fromUrlValue ? fromUrlValue(decoded) : decoded;
|
|
11146
11147
|
const validated = validate(transformed);
|
|
11147
11148
|
if (validated !== undefined) {
|
|
11148
|
-
return validated;
|
|
11149
|
+
return { initialState: validated, loadedFromStorage: false };
|
|
11149
11150
|
}
|
|
11150
11151
|
}
|
|
11151
11152
|
catch {
|
|
@@ -11156,15 +11157,28 @@ const usePersistedState = ({ key, validate, serialize, toUrlValue, fromUrlValue,
|
|
|
11156
11157
|
const raw = localStorage.getItem(storageKey);
|
|
11157
11158
|
if (raw) {
|
|
11158
11159
|
const parsed = storageSerializer.deserialize(raw);
|
|
11159
|
-
|
|
11160
|
+
const validated = validate(parsed);
|
|
11161
|
+
return { initialState: validated, loadedFromStorage: validated !== undefined };
|
|
11160
11162
|
}
|
|
11161
11163
|
}
|
|
11162
11164
|
catch {
|
|
11163
11165
|
// no valid stored state
|
|
11164
11166
|
}
|
|
11165
|
-
return undefined;
|
|
11167
|
+
return { initialState: undefined, loadedFromStorage: false };
|
|
11166
11168
|
});
|
|
11167
11169
|
const lastPersistedRef = useRef(initialState);
|
|
11170
|
+
const hasRestoredUrlRef = useRef(false);
|
|
11171
|
+
useEffect(() => {
|
|
11172
|
+
if (hasRestoredUrlRef.current) {
|
|
11173
|
+
return;
|
|
11174
|
+
}
|
|
11175
|
+
if (!enabled || !loadedFromStorage || initialState === undefined || searchValue !== undefined) {
|
|
11176
|
+
return;
|
|
11177
|
+
}
|
|
11178
|
+
hasRestoredUrlRef.current = true;
|
|
11179
|
+
const urlValue = toUrlValueRef.current ? toUrlValueRef.current(initialState) : initialState;
|
|
11180
|
+
updateSearchParam(encode(urlValue), { replace: true });
|
|
11181
|
+
}, [enabled, loadedFromStorage, initialState, searchValue, updateSearchParam, encode]);
|
|
11168
11182
|
const persistState = useCallback((state) => {
|
|
11169
11183
|
if (dequal(lastPersistedRef.current, state)) {
|
|
11170
11184
|
return;
|
package/package.json
CHANGED
|
@@ -26,4 +26,4 @@ import type { SheetProps } from "./sheet-types";
|
|
|
26
26
|
* CSS transitions on transform; the component stays mounted during the
|
|
27
27
|
* close animation and unmounts after the transition completes.
|
|
28
28
|
*/
|
|
29
|
-
export declare const Sheet: ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor, snapping, resizable, variant, trapFocus, container, dockedContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation, children, }: SheetProps) => ReactElement | null;
|
|
29
|
+
export declare const Sheet: ({ isOpen, state, snap, onGeometryChange, onSnap, onClickHandle, onCloseGesture, floatingUi, ref, anchor, snapping, resizable, variant, trapFocus, container, dockedContent, persistentContent, className, "data-testid": dataTestId, onCloseComplete, entryAnimation, children, }: SheetProps) => ReactElement | null;
|
|
@@ -187,6 +187,15 @@ export type SheetProps = {
|
|
|
187
187
|
readonly container: HTMLElement | null;
|
|
188
188
|
/** Content rendered when in docked mode. Presence enables docking behavior. */
|
|
189
189
|
readonly dockedContent?: ReactNode;
|
|
190
|
+
/**
|
|
191
|
+
* Content rendered above `dockedContent`/`children` in every sizing mode.
|
|
192
|
+
* Use this when the same subtree (e.g. a filter bar with an input) must
|
|
193
|
+
* stay mounted across snap transitions so its DOM — and any focused
|
|
194
|
+
* element inside it — is preserved. Presence alone also enables docking
|
|
195
|
+
* behavior even when `dockedContent` is omitted; in that case the docked
|
|
196
|
+
* mode shows only `persistentContent`.
|
|
197
|
+
*/
|
|
198
|
+
readonly persistentContent?: ReactNode;
|
|
190
199
|
/** Custom class name. */
|
|
191
200
|
readonly className?: string;
|
|
192
201
|
/** Test ID for the sheet. */
|
|
@@ -7,7 +7,9 @@ type UseSearchParamSyncOptions = {
|
|
|
7
7
|
};
|
|
8
8
|
type UseSearchParamSyncReturn = {
|
|
9
9
|
readonly searchValue: string | undefined;
|
|
10
|
-
readonly updateSearchParam: (encodedValue: string | undefined
|
|
10
|
+
readonly updateSearchParam: (encodedValue: string | undefined, options?: {
|
|
11
|
+
readonly replace?: boolean;
|
|
12
|
+
}) => void;
|
|
11
13
|
};
|
|
12
14
|
/**
|
|
13
15
|
* Syncs an encoded string value with a URL search parameter via Tanstack Router.
|