@trackunit/react-components 1.10.95 → 1.10.96
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 +152 -85
- package/index.esm.js +153 -86
- package/package.json +1 -1
package/index.cjs.js
CHANGED
|
@@ -2168,22 +2168,85 @@ const useDebounce = (value, { onBounce, delay = 500 } = {}) => {
|
|
|
2168
2168
|
return debouncedValue;
|
|
2169
2169
|
};
|
|
2170
2170
|
|
|
2171
|
+
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
|
2171
2172
|
/**
|
|
2172
|
-
*
|
|
2173
|
+
* Hook to watch for changes in a value and react to them.
|
|
2174
|
+
* Uses deep equality comparison via es-toolkit's isEqual.
|
|
2173
2175
|
*
|
|
2174
|
-
* @
|
|
2176
|
+
* @param props - The hook properties
|
|
2177
|
+
* @param props.value - The value to watch for changes
|
|
2178
|
+
* @param props.onChange - Function to call when the value changes
|
|
2179
|
+
* @param props.immediate - Whether to run the callback immediately on mount (default: false)
|
|
2180
|
+
* @param props.skip - Whether to skip watching for changes (default: false)
|
|
2175
2181
|
*/
|
|
2176
|
-
const
|
|
2177
|
-
const
|
|
2178
|
-
react.
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
}, []);
|
|
2183
|
-
|
|
2182
|
+
const useWatch = ({ value, onChange, immediate = false, skip = false }) => {
|
|
2183
|
+
const prevValue = react.useRef(UNINITIALIZED);
|
|
2184
|
+
const onChangeRef = react.useRef(onChange);
|
|
2185
|
+
// Update the ref whenever onChange changes
|
|
2186
|
+
react.useEffect(() => {
|
|
2187
|
+
onChangeRef.current = onChange;
|
|
2188
|
+
}, [onChange]);
|
|
2189
|
+
react.useEffect(() => {
|
|
2190
|
+
if (skip) {
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
const prev = prevValue.current;
|
|
2194
|
+
const hasChanged = prev === UNINITIALIZED ? false : !esToolkit.isEqual(value, prev);
|
|
2195
|
+
if (immediate && prev === UNINITIALIZED) {
|
|
2196
|
+
onChangeRef.current(value, null);
|
|
2197
|
+
}
|
|
2198
|
+
else if (hasChanged && prev !== UNINITIALIZED) {
|
|
2199
|
+
onChangeRef.current(value, prev);
|
|
2200
|
+
}
|
|
2201
|
+
prevValue.current = value;
|
|
2202
|
+
}, [value, immediate, skip]);
|
|
2184
2203
|
};
|
|
2185
2204
|
|
|
2186
2205
|
const SCROLL_DEBOUNCE_TIME = 50;
|
|
2206
|
+
const scrollStateReducer = (state, action) => {
|
|
2207
|
+
switch (action.type) {
|
|
2208
|
+
case "UPDATE_SCROLLABLE": {
|
|
2209
|
+
if (state.isScrollable === action.payload) {
|
|
2210
|
+
return state;
|
|
2211
|
+
}
|
|
2212
|
+
return { ...state, isScrollable: action.payload };
|
|
2213
|
+
}
|
|
2214
|
+
case "UPDATE_POSITION": {
|
|
2215
|
+
if (state.isAtBeginning === action.payload.isAtBeginning &&
|
|
2216
|
+
state.isAtEnd === action.payload.isAtEnd &&
|
|
2217
|
+
state.scrollPosition.start === action.payload.scrollPosition.start &&
|
|
2218
|
+
state.scrollPosition.end === action.payload.scrollPosition.end) {
|
|
2219
|
+
return state;
|
|
2220
|
+
}
|
|
2221
|
+
return {
|
|
2222
|
+
...state,
|
|
2223
|
+
isAtBeginning: action.payload.isAtBeginning,
|
|
2224
|
+
isAtEnd: action.payload.isAtEnd,
|
|
2225
|
+
scrollPosition: action.payload.scrollPosition,
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
case "UPDATE_ALL": {
|
|
2229
|
+
if (state.isScrollable === action.payload.isScrollable &&
|
|
2230
|
+
state.isAtBeginning === action.payload.isAtBeginning &&
|
|
2231
|
+
state.isAtEnd === action.payload.isAtEnd &&
|
|
2232
|
+
state.scrollPosition.start === action.payload.scrollPosition.start &&
|
|
2233
|
+
state.scrollPosition.end === action.payload.scrollPosition.end) {
|
|
2234
|
+
return state;
|
|
2235
|
+
}
|
|
2236
|
+
return action.payload;
|
|
2237
|
+
}
|
|
2238
|
+
default: {
|
|
2239
|
+
const exhaustiveCheck = action;
|
|
2240
|
+
throw new Error(`Unknown action: ${exhaustiveCheck}`);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
const initialScrollState = {
|
|
2245
|
+
isScrollable: false,
|
|
2246
|
+
isAtBeginning: true,
|
|
2247
|
+
isAtEnd: false,
|
|
2248
|
+
scrollPosition: { start: 0, end: 0 },
|
|
2249
|
+
};
|
|
2187
2250
|
/**
|
|
2188
2251
|
* Hook for detecting scroll values in horizontal or vertical direction.
|
|
2189
2252
|
* Returns a ref callback to attach to the element you want to observe.
|
|
@@ -2200,12 +2263,8 @@ const SCROLL_DEBOUNCE_TIME = 50;
|
|
|
2200
2263
|
const useScrollDetection = (options) => {
|
|
2201
2264
|
const { direction = "vertical", onScrollStateChange, skip = false } = options ?? {};
|
|
2202
2265
|
const [element, setElement] = react.useState(null);
|
|
2203
|
-
const [
|
|
2204
|
-
const [isAtBeginning, setIsAtBeginning] = react.useState(true);
|
|
2205
|
-
const [isAtEnd, setIsAtEnd] = react.useState(false);
|
|
2206
|
-
const [scrollPosition, setScrollPosition] = react.useState({ start: 0, end: 0 });
|
|
2266
|
+
const [scrollState, dispatch] = react.useReducer(scrollStateReducer, initialScrollState);
|
|
2207
2267
|
const observerRef = react.useRef(null);
|
|
2208
|
-
const isFirstRender = useIsFirstRender();
|
|
2209
2268
|
// Callback ref to track the element
|
|
2210
2269
|
const ref = react.useCallback((node) => {
|
|
2211
2270
|
setElement(node);
|
|
@@ -2217,13 +2276,7 @@ const useScrollDetection = (options) => {
|
|
|
2217
2276
|
const hasOverflow = direction === "horizontal"
|
|
2218
2277
|
? element.scrollWidth > element.clientWidth
|
|
2219
2278
|
: element.scrollHeight > element.clientHeight;
|
|
2220
|
-
|
|
2221
|
-
if (prev !== hasOverflow) {
|
|
2222
|
-
// State will be updated, so we'll notify in the next effect
|
|
2223
|
-
return hasOverflow;
|
|
2224
|
-
}
|
|
2225
|
-
return prev;
|
|
2226
|
-
});
|
|
2279
|
+
dispatch({ type: "UPDATE_SCROLLABLE", payload: hasOverflow });
|
|
2227
2280
|
}, [element, direction]);
|
|
2228
2281
|
const checkScrollPosition = react.useCallback(() => {
|
|
2229
2282
|
if (!element) {
|
|
@@ -2231,21 +2284,23 @@ const useScrollDetection = (options) => {
|
|
|
2231
2284
|
}
|
|
2232
2285
|
if (direction === "horizontal") {
|
|
2233
2286
|
const { scrollLeft, scrollWidth, clientWidth } = element;
|
|
2234
|
-
const
|
|
2235
|
-
const
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2287
|
+
const isAtBeginning = scrollLeft === 0;
|
|
2288
|
+
const isAtEnd = Math.abs(scrollWidth - scrollLeft - clientWidth) <= 1;
|
|
2289
|
+
const scrollPosition = { start: scrollLeft, end: clientWidth - scrollLeft };
|
|
2290
|
+
dispatch({
|
|
2291
|
+
type: "UPDATE_POSITION",
|
|
2292
|
+
payload: { isAtBeginning, isAtEnd, scrollPosition },
|
|
2293
|
+
});
|
|
2240
2294
|
}
|
|
2241
2295
|
else {
|
|
2242
2296
|
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
2243
|
-
const
|
|
2244
|
-
const
|
|
2245
|
-
const
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2297
|
+
const isAtBeginning = scrollTop === 0;
|
|
2298
|
+
const isAtEnd = Math.abs(scrollHeight - scrollTop - clientHeight) <= 1;
|
|
2299
|
+
const scrollPosition = { start: scrollTop, end: clientHeight - scrollTop };
|
|
2300
|
+
dispatch({
|
|
2301
|
+
type: "UPDATE_POSITION",
|
|
2302
|
+
payload: { isAtBeginning, isAtEnd, scrollPosition },
|
|
2303
|
+
});
|
|
2249
2304
|
}
|
|
2250
2305
|
}, [element, direction]);
|
|
2251
2306
|
const [scrollTrigger, setScrollTrigger] = react.useState(0);
|
|
@@ -2264,25 +2319,49 @@ const useScrollDetection = (options) => {
|
|
|
2264
2319
|
react.useEffect(() => {
|
|
2265
2320
|
checkScrollPositionRef.current = checkScrollPosition;
|
|
2266
2321
|
}, [checkScrollPosition]);
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
onScrollStateChange?.({
|
|
2273
|
-
isScrollable,
|
|
2274
|
-
isAtBeginning,
|
|
2275
|
-
isAtEnd,
|
|
2276
|
-
scrollPosition,
|
|
2277
|
-
});
|
|
2278
|
-
}, [isScrollable, isAtBeginning, isAtEnd, scrollPosition, onScrollStateChange, isFirstRender]);
|
|
2322
|
+
useWatch({
|
|
2323
|
+
value: scrollState,
|
|
2324
|
+
onChange: (changedState) => onScrollStateChange?.(changedState),
|
|
2325
|
+
skip: skip || !onScrollStateChange,
|
|
2326
|
+
});
|
|
2279
2327
|
react.useEffect(() => {
|
|
2280
2328
|
if (skip || !element) {
|
|
2281
2329
|
return;
|
|
2282
2330
|
}
|
|
2283
|
-
// Initial checks
|
|
2284
|
-
|
|
2285
|
-
|
|
2331
|
+
// Initial checks - batch into single update
|
|
2332
|
+
const hasOverflow = direction === "horizontal"
|
|
2333
|
+
? element.scrollWidth > element.clientWidth
|
|
2334
|
+
: element.scrollHeight > element.clientHeight;
|
|
2335
|
+
if (direction === "horizontal") {
|
|
2336
|
+
const { scrollLeft, scrollWidth, clientWidth } = element;
|
|
2337
|
+
const isAtBeginning = scrollLeft === 0;
|
|
2338
|
+
const isAtEnd = Math.abs(scrollWidth - scrollLeft - clientWidth) <= 1;
|
|
2339
|
+
const scrollPosition = { start: scrollLeft, end: clientWidth - scrollLeft };
|
|
2340
|
+
dispatch({
|
|
2341
|
+
type: "UPDATE_ALL",
|
|
2342
|
+
payload: {
|
|
2343
|
+
isScrollable: hasOverflow,
|
|
2344
|
+
isAtBeginning,
|
|
2345
|
+
isAtEnd,
|
|
2346
|
+
scrollPosition,
|
|
2347
|
+
},
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
else {
|
|
2351
|
+
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
2352
|
+
const isAtBeginning = scrollTop === 0;
|
|
2353
|
+
const isAtEnd = Math.abs(scrollHeight - scrollTop - clientHeight) <= 1;
|
|
2354
|
+
const scrollPosition = { start: scrollTop, end: clientHeight - scrollTop };
|
|
2355
|
+
dispatch({
|
|
2356
|
+
type: "UPDATE_ALL",
|
|
2357
|
+
payload: {
|
|
2358
|
+
isScrollable: hasOverflow,
|
|
2359
|
+
isAtBeginning,
|
|
2360
|
+
isAtEnd,
|
|
2361
|
+
scrollPosition,
|
|
2362
|
+
},
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2286
2365
|
observerRef.current = new ResizeObserver(() => {
|
|
2287
2366
|
checkScrollableRef.current();
|
|
2288
2367
|
checkScrollPositionRef.current();
|
|
@@ -2295,8 +2374,15 @@ const useScrollDetection = (options) => {
|
|
|
2295
2374
|
}
|
|
2296
2375
|
element.removeEventListener("scroll", handleScroll);
|
|
2297
2376
|
};
|
|
2298
|
-
}, [element, handleScroll, skip]);
|
|
2299
|
-
return react.useMemo(() => ({
|
|
2377
|
+
}, [element, handleScroll, skip, direction]);
|
|
2378
|
+
return react.useMemo(() => ({
|
|
2379
|
+
ref,
|
|
2380
|
+
element,
|
|
2381
|
+
isScrollable: scrollState.isScrollable,
|
|
2382
|
+
isAtBeginning: scrollState.isAtBeginning,
|
|
2383
|
+
isAtEnd: scrollState.isAtEnd,
|
|
2384
|
+
scrollPosition: scrollState.scrollPosition,
|
|
2385
|
+
}), [ref, element, scrollState]);
|
|
2300
2386
|
};
|
|
2301
2387
|
|
|
2302
2388
|
const cvaZStackContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-1", "grid-rows-1"]);
|
|
@@ -5611,6 +5697,21 @@ const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debo
|
|
|
5611
5697
|
}), [onMouseEnter, onMouseLeave, value]);
|
|
5612
5698
|
};
|
|
5613
5699
|
|
|
5700
|
+
/**
|
|
5701
|
+
* Differentiate between the first and subsequent renders.
|
|
5702
|
+
*
|
|
5703
|
+
* @returns {boolean} Returns true if it is the first render, false otherwise.
|
|
5704
|
+
*/
|
|
5705
|
+
const useIsFirstRender = () => {
|
|
5706
|
+
const [isFirstRender, setIsFirstRender] = react.useState(true);
|
|
5707
|
+
react.useLayoutEffect(() => {
|
|
5708
|
+
queueMicrotask(() => {
|
|
5709
|
+
setIsFirstRender(false);
|
|
5710
|
+
});
|
|
5711
|
+
}, []);
|
|
5712
|
+
return isFirstRender;
|
|
5713
|
+
};
|
|
5714
|
+
|
|
5614
5715
|
/**
|
|
5615
5716
|
* Custom hook for checking if the browser is in fullscreen mode.
|
|
5616
5717
|
*/
|
|
@@ -5928,40 +6029,6 @@ function useTextSearch(items, props) {
|
|
|
5928
6029
|
return react.useMemo(() => [result, searchText, setSearchText], [result, searchText, setSearchText]);
|
|
5929
6030
|
}
|
|
5930
6031
|
|
|
5931
|
-
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
|
5932
|
-
/**
|
|
5933
|
-
* Hook to watch for changes in a value and react to them.
|
|
5934
|
-
* Uses deep equality comparison via es-toolkit's isEqual.
|
|
5935
|
-
*
|
|
5936
|
-
* @param props - The hook properties
|
|
5937
|
-
* @param props.value - The value to watch for changes
|
|
5938
|
-
* @param props.onChange - Function to call when the value changes
|
|
5939
|
-
* @param props.immediate - Whether to run the callback immediately on mount (default: false)
|
|
5940
|
-
* @param props.skip - Whether to skip watching for changes (default: false)
|
|
5941
|
-
*/
|
|
5942
|
-
const useWatch = ({ value, onChange, immediate = false, skip = false }) => {
|
|
5943
|
-
const prevValue = react.useRef(UNINITIALIZED);
|
|
5944
|
-
const onChangeRef = react.useRef(onChange);
|
|
5945
|
-
// Update the ref whenever onChange changes
|
|
5946
|
-
react.useEffect(() => {
|
|
5947
|
-
onChangeRef.current = onChange;
|
|
5948
|
-
}, [onChange]);
|
|
5949
|
-
react.useEffect(() => {
|
|
5950
|
-
if (skip) {
|
|
5951
|
-
return;
|
|
5952
|
-
}
|
|
5953
|
-
const prev = prevValue.current;
|
|
5954
|
-
const hasChanged = prev === UNINITIALIZED ? false : !esToolkit.isEqual(value, prev);
|
|
5955
|
-
if (immediate && prev === UNINITIALIZED) {
|
|
5956
|
-
onChangeRef.current(value, null);
|
|
5957
|
-
}
|
|
5958
|
-
else if (hasChanged && prev !== UNINITIALIZED) {
|
|
5959
|
-
onChangeRef.current(value, prev);
|
|
5960
|
-
}
|
|
5961
|
-
prevValue.current = value;
|
|
5962
|
-
}, [value, immediate, skip]);
|
|
5963
|
-
};
|
|
5964
|
-
|
|
5965
6032
|
const hasFocus = () => typeof document !== "undefined" && document.hasFocus();
|
|
5966
6033
|
/**
|
|
5967
6034
|
* Use this hook to disable functionality while the tab is hidden within the browser or to react to focus or blur events
|
package/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import { useRef, useMemo, useEffect, useState, useCallback, createElement, forwardRef, Fragment, memo,
|
|
2
|
+
import { useRef, useMemo, useEffect, useState, useCallback, createElement, forwardRef, Fragment, memo, useReducer, Children, isValidElement, cloneElement, createContext, useContext, useLayoutEffect } from 'react';
|
|
3
3
|
import { objectKeys, uuidv4, objectEntries, objectValues, nonNullable, filterByMultiple } from '@trackunit/shared-utils';
|
|
4
4
|
import { intentPalette, generalPalette, criticalityPalette, activityPalette, utilizationPalette, sitesPalette, rentalStatusPalette, themeScreenSizeAsNumber, color } from '@trackunit/ui-design-tokens';
|
|
5
5
|
import { iconNames } from '@trackunit/ui-icons';
|
|
@@ -2166,22 +2166,85 @@ const useDebounce = (value, { onBounce, delay = 500 } = {}) => {
|
|
|
2166
2166
|
return debouncedValue;
|
|
2167
2167
|
};
|
|
2168
2168
|
|
|
2169
|
+
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
|
2169
2170
|
/**
|
|
2170
|
-
*
|
|
2171
|
+
* Hook to watch for changes in a value and react to them.
|
|
2172
|
+
* Uses deep equality comparison via es-toolkit's isEqual.
|
|
2171
2173
|
*
|
|
2172
|
-
* @
|
|
2174
|
+
* @param props - The hook properties
|
|
2175
|
+
* @param props.value - The value to watch for changes
|
|
2176
|
+
* @param props.onChange - Function to call when the value changes
|
|
2177
|
+
* @param props.immediate - Whether to run the callback immediately on mount (default: false)
|
|
2178
|
+
* @param props.skip - Whether to skip watching for changes (default: false)
|
|
2173
2179
|
*/
|
|
2174
|
-
const
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}, []);
|
|
2181
|
-
|
|
2180
|
+
const useWatch = ({ value, onChange, immediate = false, skip = false }) => {
|
|
2181
|
+
const prevValue = useRef(UNINITIALIZED);
|
|
2182
|
+
const onChangeRef = useRef(onChange);
|
|
2183
|
+
// Update the ref whenever onChange changes
|
|
2184
|
+
useEffect(() => {
|
|
2185
|
+
onChangeRef.current = onChange;
|
|
2186
|
+
}, [onChange]);
|
|
2187
|
+
useEffect(() => {
|
|
2188
|
+
if (skip) {
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
const prev = prevValue.current;
|
|
2192
|
+
const hasChanged = prev === UNINITIALIZED ? false : !isEqual(value, prev);
|
|
2193
|
+
if (immediate && prev === UNINITIALIZED) {
|
|
2194
|
+
onChangeRef.current(value, null);
|
|
2195
|
+
}
|
|
2196
|
+
else if (hasChanged && prev !== UNINITIALIZED) {
|
|
2197
|
+
onChangeRef.current(value, prev);
|
|
2198
|
+
}
|
|
2199
|
+
prevValue.current = value;
|
|
2200
|
+
}, [value, immediate, skip]);
|
|
2182
2201
|
};
|
|
2183
2202
|
|
|
2184
2203
|
const SCROLL_DEBOUNCE_TIME = 50;
|
|
2204
|
+
const scrollStateReducer = (state, action) => {
|
|
2205
|
+
switch (action.type) {
|
|
2206
|
+
case "UPDATE_SCROLLABLE": {
|
|
2207
|
+
if (state.isScrollable === action.payload) {
|
|
2208
|
+
return state;
|
|
2209
|
+
}
|
|
2210
|
+
return { ...state, isScrollable: action.payload };
|
|
2211
|
+
}
|
|
2212
|
+
case "UPDATE_POSITION": {
|
|
2213
|
+
if (state.isAtBeginning === action.payload.isAtBeginning &&
|
|
2214
|
+
state.isAtEnd === action.payload.isAtEnd &&
|
|
2215
|
+
state.scrollPosition.start === action.payload.scrollPosition.start &&
|
|
2216
|
+
state.scrollPosition.end === action.payload.scrollPosition.end) {
|
|
2217
|
+
return state;
|
|
2218
|
+
}
|
|
2219
|
+
return {
|
|
2220
|
+
...state,
|
|
2221
|
+
isAtBeginning: action.payload.isAtBeginning,
|
|
2222
|
+
isAtEnd: action.payload.isAtEnd,
|
|
2223
|
+
scrollPosition: action.payload.scrollPosition,
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
case "UPDATE_ALL": {
|
|
2227
|
+
if (state.isScrollable === action.payload.isScrollable &&
|
|
2228
|
+
state.isAtBeginning === action.payload.isAtBeginning &&
|
|
2229
|
+
state.isAtEnd === action.payload.isAtEnd &&
|
|
2230
|
+
state.scrollPosition.start === action.payload.scrollPosition.start &&
|
|
2231
|
+
state.scrollPosition.end === action.payload.scrollPosition.end) {
|
|
2232
|
+
return state;
|
|
2233
|
+
}
|
|
2234
|
+
return action.payload;
|
|
2235
|
+
}
|
|
2236
|
+
default: {
|
|
2237
|
+
const exhaustiveCheck = action;
|
|
2238
|
+
throw new Error(`Unknown action: ${exhaustiveCheck}`);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
const initialScrollState = {
|
|
2243
|
+
isScrollable: false,
|
|
2244
|
+
isAtBeginning: true,
|
|
2245
|
+
isAtEnd: false,
|
|
2246
|
+
scrollPosition: { start: 0, end: 0 },
|
|
2247
|
+
};
|
|
2185
2248
|
/**
|
|
2186
2249
|
* Hook for detecting scroll values in horizontal or vertical direction.
|
|
2187
2250
|
* Returns a ref callback to attach to the element you want to observe.
|
|
@@ -2198,12 +2261,8 @@ const SCROLL_DEBOUNCE_TIME = 50;
|
|
|
2198
2261
|
const useScrollDetection = (options) => {
|
|
2199
2262
|
const { direction = "vertical", onScrollStateChange, skip = false } = options ?? {};
|
|
2200
2263
|
const [element, setElement] = useState(null);
|
|
2201
|
-
const [
|
|
2202
|
-
const [isAtBeginning, setIsAtBeginning] = useState(true);
|
|
2203
|
-
const [isAtEnd, setIsAtEnd] = useState(false);
|
|
2204
|
-
const [scrollPosition, setScrollPosition] = useState({ start: 0, end: 0 });
|
|
2264
|
+
const [scrollState, dispatch] = useReducer(scrollStateReducer, initialScrollState);
|
|
2205
2265
|
const observerRef = useRef(null);
|
|
2206
|
-
const isFirstRender = useIsFirstRender();
|
|
2207
2266
|
// Callback ref to track the element
|
|
2208
2267
|
const ref = useCallback((node) => {
|
|
2209
2268
|
setElement(node);
|
|
@@ -2215,13 +2274,7 @@ const useScrollDetection = (options) => {
|
|
|
2215
2274
|
const hasOverflow = direction === "horizontal"
|
|
2216
2275
|
? element.scrollWidth > element.clientWidth
|
|
2217
2276
|
: element.scrollHeight > element.clientHeight;
|
|
2218
|
-
|
|
2219
|
-
if (prev !== hasOverflow) {
|
|
2220
|
-
// State will be updated, so we'll notify in the next effect
|
|
2221
|
-
return hasOverflow;
|
|
2222
|
-
}
|
|
2223
|
-
return prev;
|
|
2224
|
-
});
|
|
2277
|
+
dispatch({ type: "UPDATE_SCROLLABLE", payload: hasOverflow });
|
|
2225
2278
|
}, [element, direction]);
|
|
2226
2279
|
const checkScrollPosition = useCallback(() => {
|
|
2227
2280
|
if (!element) {
|
|
@@ -2229,21 +2282,23 @@ const useScrollDetection = (options) => {
|
|
|
2229
2282
|
}
|
|
2230
2283
|
if (direction === "horizontal") {
|
|
2231
2284
|
const { scrollLeft, scrollWidth, clientWidth } = element;
|
|
2232
|
-
const
|
|
2233
|
-
const
|
|
2234
|
-
const
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2285
|
+
const isAtBeginning = scrollLeft === 0;
|
|
2286
|
+
const isAtEnd = Math.abs(scrollWidth - scrollLeft - clientWidth) <= 1;
|
|
2287
|
+
const scrollPosition = { start: scrollLeft, end: clientWidth - scrollLeft };
|
|
2288
|
+
dispatch({
|
|
2289
|
+
type: "UPDATE_POSITION",
|
|
2290
|
+
payload: { isAtBeginning, isAtEnd, scrollPosition },
|
|
2291
|
+
});
|
|
2238
2292
|
}
|
|
2239
2293
|
else {
|
|
2240
2294
|
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
2241
|
-
const
|
|
2242
|
-
const
|
|
2243
|
-
const
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2295
|
+
const isAtBeginning = scrollTop === 0;
|
|
2296
|
+
const isAtEnd = Math.abs(scrollHeight - scrollTop - clientHeight) <= 1;
|
|
2297
|
+
const scrollPosition = { start: scrollTop, end: clientHeight - scrollTop };
|
|
2298
|
+
dispatch({
|
|
2299
|
+
type: "UPDATE_POSITION",
|
|
2300
|
+
payload: { isAtBeginning, isAtEnd, scrollPosition },
|
|
2301
|
+
});
|
|
2247
2302
|
}
|
|
2248
2303
|
}, [element, direction]);
|
|
2249
2304
|
const [scrollTrigger, setScrollTrigger] = useState(0);
|
|
@@ -2262,25 +2317,49 @@ const useScrollDetection = (options) => {
|
|
|
2262
2317
|
useEffect(() => {
|
|
2263
2318
|
checkScrollPositionRef.current = checkScrollPosition;
|
|
2264
2319
|
}, [checkScrollPosition]);
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
onScrollStateChange?.({
|
|
2271
|
-
isScrollable,
|
|
2272
|
-
isAtBeginning,
|
|
2273
|
-
isAtEnd,
|
|
2274
|
-
scrollPosition,
|
|
2275
|
-
});
|
|
2276
|
-
}, [isScrollable, isAtBeginning, isAtEnd, scrollPosition, onScrollStateChange, isFirstRender]);
|
|
2320
|
+
useWatch({
|
|
2321
|
+
value: scrollState,
|
|
2322
|
+
onChange: (changedState) => onScrollStateChange?.(changedState),
|
|
2323
|
+
skip: skip || !onScrollStateChange,
|
|
2324
|
+
});
|
|
2277
2325
|
useEffect(() => {
|
|
2278
2326
|
if (skip || !element) {
|
|
2279
2327
|
return;
|
|
2280
2328
|
}
|
|
2281
|
-
// Initial checks
|
|
2282
|
-
|
|
2283
|
-
|
|
2329
|
+
// Initial checks - batch into single update
|
|
2330
|
+
const hasOverflow = direction === "horizontal"
|
|
2331
|
+
? element.scrollWidth > element.clientWidth
|
|
2332
|
+
: element.scrollHeight > element.clientHeight;
|
|
2333
|
+
if (direction === "horizontal") {
|
|
2334
|
+
const { scrollLeft, scrollWidth, clientWidth } = element;
|
|
2335
|
+
const isAtBeginning = scrollLeft === 0;
|
|
2336
|
+
const isAtEnd = Math.abs(scrollWidth - scrollLeft - clientWidth) <= 1;
|
|
2337
|
+
const scrollPosition = { start: scrollLeft, end: clientWidth - scrollLeft };
|
|
2338
|
+
dispatch({
|
|
2339
|
+
type: "UPDATE_ALL",
|
|
2340
|
+
payload: {
|
|
2341
|
+
isScrollable: hasOverflow,
|
|
2342
|
+
isAtBeginning,
|
|
2343
|
+
isAtEnd,
|
|
2344
|
+
scrollPosition,
|
|
2345
|
+
},
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
2350
|
+
const isAtBeginning = scrollTop === 0;
|
|
2351
|
+
const isAtEnd = Math.abs(scrollHeight - scrollTop - clientHeight) <= 1;
|
|
2352
|
+
const scrollPosition = { start: scrollTop, end: clientHeight - scrollTop };
|
|
2353
|
+
dispatch({
|
|
2354
|
+
type: "UPDATE_ALL",
|
|
2355
|
+
payload: {
|
|
2356
|
+
isScrollable: hasOverflow,
|
|
2357
|
+
isAtBeginning,
|
|
2358
|
+
isAtEnd,
|
|
2359
|
+
scrollPosition,
|
|
2360
|
+
},
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2284
2363
|
observerRef.current = new ResizeObserver(() => {
|
|
2285
2364
|
checkScrollableRef.current();
|
|
2286
2365
|
checkScrollPositionRef.current();
|
|
@@ -2293,8 +2372,15 @@ const useScrollDetection = (options) => {
|
|
|
2293
2372
|
}
|
|
2294
2373
|
element.removeEventListener("scroll", handleScroll);
|
|
2295
2374
|
};
|
|
2296
|
-
}, [element, handleScroll, skip]);
|
|
2297
|
-
return useMemo(() => ({
|
|
2375
|
+
}, [element, handleScroll, skip, direction]);
|
|
2376
|
+
return useMemo(() => ({
|
|
2377
|
+
ref,
|
|
2378
|
+
element,
|
|
2379
|
+
isScrollable: scrollState.isScrollable,
|
|
2380
|
+
isAtBeginning: scrollState.isAtBeginning,
|
|
2381
|
+
isAtEnd: scrollState.isAtEnd,
|
|
2382
|
+
scrollPosition: scrollState.scrollPosition,
|
|
2383
|
+
}), [ref, element, scrollState]);
|
|
2298
2384
|
};
|
|
2299
2385
|
|
|
2300
2386
|
const cvaZStackContainer = cvaMerge(["grid", "grid-cols-1", "grid-rows-1"]);
|
|
@@ -5609,6 +5695,21 @@ const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debo
|
|
|
5609
5695
|
}), [onMouseEnter, onMouseLeave, value]);
|
|
5610
5696
|
};
|
|
5611
5697
|
|
|
5698
|
+
/**
|
|
5699
|
+
* Differentiate between the first and subsequent renders.
|
|
5700
|
+
*
|
|
5701
|
+
* @returns {boolean} Returns true if it is the first render, false otherwise.
|
|
5702
|
+
*/
|
|
5703
|
+
const useIsFirstRender = () => {
|
|
5704
|
+
const [isFirstRender, setIsFirstRender] = useState(true);
|
|
5705
|
+
useLayoutEffect(() => {
|
|
5706
|
+
queueMicrotask(() => {
|
|
5707
|
+
setIsFirstRender(false);
|
|
5708
|
+
});
|
|
5709
|
+
}, []);
|
|
5710
|
+
return isFirstRender;
|
|
5711
|
+
};
|
|
5712
|
+
|
|
5612
5713
|
/**
|
|
5613
5714
|
* Custom hook for checking if the browser is in fullscreen mode.
|
|
5614
5715
|
*/
|
|
@@ -5926,40 +6027,6 @@ function useTextSearch(items, props) {
|
|
|
5926
6027
|
return useMemo(() => [result, searchText, setSearchText], [result, searchText, setSearchText]);
|
|
5927
6028
|
}
|
|
5928
6029
|
|
|
5929
|
-
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
|
5930
|
-
/**
|
|
5931
|
-
* Hook to watch for changes in a value and react to them.
|
|
5932
|
-
* Uses deep equality comparison via es-toolkit's isEqual.
|
|
5933
|
-
*
|
|
5934
|
-
* @param props - The hook properties
|
|
5935
|
-
* @param props.value - The value to watch for changes
|
|
5936
|
-
* @param props.onChange - Function to call when the value changes
|
|
5937
|
-
* @param props.immediate - Whether to run the callback immediately on mount (default: false)
|
|
5938
|
-
* @param props.skip - Whether to skip watching for changes (default: false)
|
|
5939
|
-
*/
|
|
5940
|
-
const useWatch = ({ value, onChange, immediate = false, skip = false }) => {
|
|
5941
|
-
const prevValue = useRef(UNINITIALIZED);
|
|
5942
|
-
const onChangeRef = useRef(onChange);
|
|
5943
|
-
// Update the ref whenever onChange changes
|
|
5944
|
-
useEffect(() => {
|
|
5945
|
-
onChangeRef.current = onChange;
|
|
5946
|
-
}, [onChange]);
|
|
5947
|
-
useEffect(() => {
|
|
5948
|
-
if (skip) {
|
|
5949
|
-
return;
|
|
5950
|
-
}
|
|
5951
|
-
const prev = prevValue.current;
|
|
5952
|
-
const hasChanged = prev === UNINITIALIZED ? false : !isEqual(value, prev);
|
|
5953
|
-
if (immediate && prev === UNINITIALIZED) {
|
|
5954
|
-
onChangeRef.current(value, null);
|
|
5955
|
-
}
|
|
5956
|
-
else if (hasChanged && prev !== UNINITIALIZED) {
|
|
5957
|
-
onChangeRef.current(value, prev);
|
|
5958
|
-
}
|
|
5959
|
-
prevValue.current = value;
|
|
5960
|
-
}, [value, immediate, skip]);
|
|
5961
|
-
};
|
|
5962
|
-
|
|
5963
6030
|
const hasFocus = () => typeof document !== "undefined" && document.hasFocus();
|
|
5964
6031
|
/**
|
|
5965
6032
|
* Use this hook to disable functionality while the tab is hidden within the browser or to react to focus or blur events
|