@trackunit/react-components 1.10.42 → 1.10.43
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 +224 -116
- package/index.esm.js +228 -120
- package/package.json +5 -5
- package/src/components/HorizontalOverflowScroller/HorizontalOverflowScroller.d.ts +8 -3
- package/src/hooks/index.d.ts +2 -3
- package/src/hooks/{useMeasure/useMeasure.d.ts → useMeasure.d.ts} +15 -3
- package/src/hooks/useMergeRefs.d.ts +23 -0
- package/src/hooks/useScrollDetection.d.ts +14 -4
- package/src/hooks/useMeasure/useMeasureElement.d.ts +0 -19
- package/src/hooks/useMeasure/useMeasureShared.d.ts +0 -19
package/index.cjs.js
CHANGED
|
@@ -1334,34 +1334,40 @@ const useElevatedState = (initialState, customState) => {
|
|
|
1334
1334
|
* @returns {object} The object containing the onMouseEnter, onMouseLeave and hovering props
|
|
1335
1335
|
*/
|
|
1336
1336
|
const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
|
|
1337
|
-
const [
|
|
1338
|
-
const [
|
|
1339
|
-
react.useEffect(() => {
|
|
1340
|
-
if (!debounced) {
|
|
1341
|
-
setDebouncedHovering(hovering);
|
|
1342
|
-
return undefined;
|
|
1343
|
-
}
|
|
1344
|
-
const shouldDebounce = direction === "both" || (direction === "in" && hovering) || (direction === "out" && !hovering);
|
|
1345
|
-
if (shouldDebounce) {
|
|
1346
|
-
const timer = setTimeout(() => {
|
|
1347
|
-
setDebouncedHovering(hovering);
|
|
1348
|
-
}, delay);
|
|
1349
|
-
return () => clearTimeout(timer);
|
|
1350
|
-
}
|
|
1351
|
-
setDebouncedHovering(hovering);
|
|
1352
|
-
return undefined;
|
|
1353
|
-
}, [debounced, direction, delay, hovering]);
|
|
1337
|
+
const [isHovering, setIsHovering] = react.useState(false);
|
|
1338
|
+
const [debouncedIsHovering, setDebouncedIsHovering] = react.useState(false);
|
|
1354
1339
|
const onMouseEnter = react.useCallback(() => {
|
|
1355
|
-
|
|
1340
|
+
setIsHovering(true);
|
|
1356
1341
|
}, []);
|
|
1357
1342
|
const onMouseLeave = react.useCallback(() => {
|
|
1358
|
-
|
|
1343
|
+
setIsHovering(false);
|
|
1359
1344
|
}, []);
|
|
1345
|
+
// Determine if the current transition should be debounced based on direction
|
|
1346
|
+
const isTransitionDebounced = debounced && (direction === "both" || (direction === "in" && isHovering) || (direction === "out" && !isHovering));
|
|
1347
|
+
// Sync debouncedIsHovering immediately for non-debounced transitions
|
|
1348
|
+
react.useLayoutEffect(() => {
|
|
1349
|
+
if (!debounced || isTransitionDebounced) {
|
|
1350
|
+
return undefined;
|
|
1351
|
+
}
|
|
1352
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Synchronizing derived state for directional debouncing
|
|
1353
|
+
setDebouncedIsHovering(isHovering);
|
|
1354
|
+
}, [debounced, isTransitionDebounced, isHovering]);
|
|
1355
|
+
// Apply delay for debounced transitions
|
|
1356
|
+
react.useEffect(() => {
|
|
1357
|
+
if (!isTransitionDebounced) {
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}
|
|
1360
|
+
const timer = setTimeout(() => {
|
|
1361
|
+
setDebouncedIsHovering(isHovering);
|
|
1362
|
+
}, delay);
|
|
1363
|
+
return () => clearTimeout(timer);
|
|
1364
|
+
}, [isTransitionDebounced, delay, isHovering]);
|
|
1365
|
+
const value = debounced ? (isTransitionDebounced ? debouncedIsHovering : isHovering) : isHovering;
|
|
1360
1366
|
return react.useMemo(() => ({
|
|
1361
1367
|
onMouseEnter,
|
|
1362
1368
|
onMouseLeave,
|
|
1363
|
-
hovering:
|
|
1364
|
-
}), [onMouseEnter, onMouseLeave,
|
|
1369
|
+
hovering: value,
|
|
1370
|
+
}), [onMouseEnter, onMouseLeave, value]);
|
|
1365
1371
|
};
|
|
1366
1372
|
|
|
1367
1373
|
const OVERSCAN = 10;
|
|
@@ -1391,10 +1397,13 @@ const DEFAULT_ROW_HEIGHT = 50;
|
|
|
1391
1397
|
* });
|
|
1392
1398
|
*/
|
|
1393
1399
|
const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, }) => {
|
|
1400
|
+
"use no memo"; //! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
1394
1401
|
const handleChange = react.useCallback((virtualizer) => {
|
|
1395
1402
|
onChange?.(virtualizer);
|
|
1396
1403
|
}, [onChange]);
|
|
1397
1404
|
const handleEstimateSize = react.useCallback((index) => estimateSize?.(index) ?? DEFAULT_ROW_HEIGHT, [estimateSize]);
|
|
1405
|
+
//! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
1406
|
+
// eslint-disable-next-line react-hooks/incompatible-library
|
|
1398
1407
|
const rowVirtualizer = reactVirtual.useVirtualizer({
|
|
1399
1408
|
count,
|
|
1400
1409
|
getScrollElement: () => scrollElementRef.current,
|
|
@@ -1443,8 +1452,10 @@ const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize,
|
|
|
1443
1452
|
*/
|
|
1444
1453
|
const useIsFirstRender = () => {
|
|
1445
1454
|
const [isFirstRender, setIsFirstRender] = react.useState(true);
|
|
1446
|
-
react.
|
|
1447
|
-
|
|
1455
|
+
react.useLayoutEffect(() => {
|
|
1456
|
+
queueMicrotask(() => {
|
|
1457
|
+
setIsFirstRender(false);
|
|
1458
|
+
});
|
|
1448
1459
|
}, []);
|
|
1449
1460
|
return isFirstRender;
|
|
1450
1461
|
};
|
|
@@ -1516,27 +1527,51 @@ const useIsTextTruncated = (text, { skip = false } = {}) => {
|
|
|
1516
1527
|
};
|
|
1517
1528
|
|
|
1518
1529
|
/**
|
|
1519
|
-
*
|
|
1520
|
-
*
|
|
1530
|
+
* Custom hook to measure the geometry of an element using a callback ref.
|
|
1531
|
+
* Measures the size and position of the element relative to the viewport.
|
|
1532
|
+
*
|
|
1533
|
+
* @template TElement extends HTMLElement
|
|
1534
|
+
* @returns {UseMeasureResult<HTMLElement>} An object containing `geometry`, `ref` callback, and `element`.
|
|
1535
|
+
* @example
|
|
1536
|
+
* ```tsx
|
|
1537
|
+
* const { geometry, ref } = useMeasure();
|
|
1538
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
1539
|
+
* ```
|
|
1521
1540
|
*/
|
|
1522
|
-
const
|
|
1541
|
+
const useMeasure = ({ skip = false, onChange, } = {}) => {
|
|
1542
|
+
const [element, setElement] = react.useState(null);
|
|
1523
1543
|
const [geometry, setGeometry] = react.useState(undefined);
|
|
1524
1544
|
const observerRef = react.useRef(null);
|
|
1525
1545
|
const onChangeRef = react.useRef(onChange);
|
|
1526
1546
|
const isInitialRender = react.useRef(true);
|
|
1527
|
-
|
|
1547
|
+
// Callback ref to track the element
|
|
1548
|
+
const ref = react.useCallback((node) => {
|
|
1549
|
+
setElement(node);
|
|
1550
|
+
}, []);
|
|
1528
1551
|
const disconnectObserver = () => {
|
|
1529
1552
|
if (observerRef.current !== null) {
|
|
1530
1553
|
observerRef.current.disconnect();
|
|
1531
1554
|
observerRef.current = null;
|
|
1532
1555
|
}
|
|
1533
1556
|
};
|
|
1557
|
+
react.useEffect(() => {
|
|
1558
|
+
onChangeRef.current = onChange;
|
|
1559
|
+
}, [onChange]);
|
|
1534
1560
|
react.useEffect(() => {
|
|
1535
1561
|
if (skip || element === null) {
|
|
1536
1562
|
return;
|
|
1537
1563
|
}
|
|
1538
1564
|
const updateGeometry = (rect) => {
|
|
1539
|
-
const newGeometry =
|
|
1565
|
+
const newGeometry = {
|
|
1566
|
+
width: rect.width,
|
|
1567
|
+
height: rect.height,
|
|
1568
|
+
top: rect.top,
|
|
1569
|
+
bottom: rect.bottom,
|
|
1570
|
+
left: rect.left,
|
|
1571
|
+
right: rect.right,
|
|
1572
|
+
x: rect.x,
|
|
1573
|
+
y: rect.y,
|
|
1574
|
+
};
|
|
1540
1575
|
setGeometry(prevGeometry => {
|
|
1541
1576
|
if (esToolkit.isEqual(prevGeometry, newGeometry)) {
|
|
1542
1577
|
return prevGeometry;
|
|
@@ -1562,48 +1597,72 @@ const useMeasureShared = (element, { skip = false, onChange } = {}) => {
|
|
|
1562
1597
|
updateGeometry(initialRect);
|
|
1563
1598
|
return disconnectObserver;
|
|
1564
1599
|
}, [element, skip]);
|
|
1565
|
-
return geometry;
|
|
1600
|
+
return react.useMemo(() => ({ geometry, ref, element }), [geometry, ref, element]);
|
|
1566
1601
|
};
|
|
1567
1602
|
|
|
1603
|
+
// This hook is _heavily_ inspired by floating-ui's useMergeRefs
|
|
1568
1604
|
/**
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1605
|
+
* Merges an array of refs into a single memoized callback ref or `null`.
|
|
1606
|
+
* Useful when you need to attach multiple refs to the same element,
|
|
1607
|
+
* such as when composing multiple hooks that each need a ref.
|
|
1571
1608
|
*
|
|
1572
|
-
* @
|
|
1573
|
-
* @
|
|
1609
|
+
* @template TInstance - The type of the element instance
|
|
1610
|
+
* @param {ReadonlyArray<MergeableRef<TInstance> | undefined>} refs - Array of refs to merge (can be RefObjects, RefCallbacks, or undefined)
|
|
1611
|
+
* @returns {null | RefCallback<TInstance>} A single ref callback that will update all provided refs, or null if all refs are null
|
|
1574
1612
|
* @example
|
|
1575
1613
|
* ```tsx
|
|
1576
|
-
* const {
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*/
|
|
1580
|
-
const useMeasure = ({ skip = false, onChange, } = {}) => {
|
|
1581
|
-
const [element, setElement] = react.useState(null);
|
|
1582
|
-
// Callback ref to track the element
|
|
1583
|
-
const ref = react.useCallback((node) => {
|
|
1584
|
-
setElement(node);
|
|
1585
|
-
}, []);
|
|
1586
|
-
const geometry = useMeasureShared(element, { skip, onChange });
|
|
1587
|
-
return { geometry, ref, element };
|
|
1588
|
-
};
|
|
1589
|
-
|
|
1590
|
-
/**
|
|
1591
|
-
* Custom hook to measure the geometry of an element from a RefObject.
|
|
1592
|
-
* Use this when you already have a ref (e.g., from useRef or for composition with other hooks).
|
|
1593
|
-
* Measures the size and position of the element relative to the viewport.
|
|
1614
|
+
* const { ref: measureRef } = useMeasure();
|
|
1615
|
+
* const { ref: scrollRef } = useScrollDetection();
|
|
1616
|
+
* const mergedRef = useMergeRefs([measureRef, scrollRef]);
|
|
1594
1617
|
*
|
|
1595
|
-
*
|
|
1596
|
-
* @returns {Geometry} The geometry of the element
|
|
1597
|
-
* @example
|
|
1598
|
-
* ```tsx
|
|
1599
|
-
* const ref = useRef<HTMLDivElement>(null);
|
|
1600
|
-
* const geometry = useMeasureElement(ref);
|
|
1601
|
-
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
1618
|
+
* return <div ref={mergedRef}>Content</div>;
|
|
1602
1619
|
* ```
|
|
1603
1620
|
*/
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1621
|
+
function useMergeRefs(refs) {
|
|
1622
|
+
const cleanupRef = react.useRef(undefined);
|
|
1623
|
+
const refsRef = react.useRef(refs);
|
|
1624
|
+
react.useEffect(() => {
|
|
1625
|
+
refsRef.current = refs;
|
|
1626
|
+
}, [refs]);
|
|
1627
|
+
const refEffect = react.useCallback((instance) => {
|
|
1628
|
+
const cleanups = refsRef.current.map(ref => {
|
|
1629
|
+
if (ref === null || ref === undefined) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
if (typeof ref === "function") {
|
|
1633
|
+
const refCallback = ref;
|
|
1634
|
+
const refCleanup = refCallback(instance);
|
|
1635
|
+
return typeof refCleanup === "function"
|
|
1636
|
+
? refCleanup
|
|
1637
|
+
: () => {
|
|
1638
|
+
refCallback(null);
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
ref.current = instance;
|
|
1642
|
+
return () => {
|
|
1643
|
+
ref.current = null;
|
|
1644
|
+
};
|
|
1645
|
+
});
|
|
1646
|
+
return () => {
|
|
1647
|
+
cleanups.forEach(refCleanup => refCleanup?.());
|
|
1648
|
+
};
|
|
1649
|
+
}, []);
|
|
1650
|
+
return react.useMemo(() => {
|
|
1651
|
+
// eslint-disable-next-line react-hooks/refs
|
|
1652
|
+
if (refsRef.current.every(ref => ref === null)) {
|
|
1653
|
+
return null;
|
|
1654
|
+
}
|
|
1655
|
+
return (value) => {
|
|
1656
|
+
if (cleanupRef.current) {
|
|
1657
|
+
cleanupRef.current();
|
|
1658
|
+
cleanupRef.current = undefined;
|
|
1659
|
+
}
|
|
1660
|
+
if (value !== null) {
|
|
1661
|
+
cleanupRef.current = refEffect(value);
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
}, [refEffect]);
|
|
1665
|
+
}
|
|
1607
1666
|
|
|
1608
1667
|
/**
|
|
1609
1668
|
* Hook that returns true if any modifier key (Ctrl, Alt, Shift, Meta/Cmd) is pressed
|
|
@@ -1643,22 +1702,24 @@ const useRelayPagination = ({ onReset, pageSize } = { pageSize: defaultPageSize
|
|
|
1643
1702
|
const [variables, setVariables] = react.useState({ first: pageSize });
|
|
1644
1703
|
const [pageInfo, setPageInfo] = react.useState();
|
|
1645
1704
|
const [isLoading, setIsLoading] = react.useState(true);
|
|
1705
|
+
// Destructure pageInfo properties early to avoid depending on the entire object
|
|
1706
|
+
const { hasNextPage, endCursor, hasPreviousPage, startCursor } = pageInfo ?? {};
|
|
1646
1707
|
const nextPage = react.useCallback(() => {
|
|
1647
|
-
if (
|
|
1708
|
+
if (hasNextPage === true) {
|
|
1648
1709
|
setVariables({
|
|
1649
|
-
after:
|
|
1710
|
+
after: endCursor === null ? undefined : endCursor,
|
|
1650
1711
|
first: pageSize,
|
|
1651
1712
|
});
|
|
1652
1713
|
}
|
|
1653
|
-
}, [
|
|
1714
|
+
}, [hasNextPage, endCursor, pageSize]);
|
|
1654
1715
|
const previousPage = react.useCallback(() => {
|
|
1655
|
-
if (
|
|
1716
|
+
if (hasPreviousPage === true) {
|
|
1656
1717
|
setVariables({
|
|
1657
|
-
before:
|
|
1718
|
+
before: startCursor === null ? undefined : startCursor,
|
|
1658
1719
|
last: pageSize,
|
|
1659
1720
|
});
|
|
1660
1721
|
}
|
|
1661
|
-
}, [
|
|
1722
|
+
}, [hasPreviousPage, startCursor, pageSize]);
|
|
1662
1723
|
const reset = react.useCallback(() => {
|
|
1663
1724
|
setVariables({
|
|
1664
1725
|
last: undefined,
|
|
@@ -1873,21 +1934,31 @@ const useScrollBlock = (scrollContainer = typeof document !== "undefined" ? docu
|
|
|
1873
1934
|
const SCROLL_DEBOUNCE_TIME = 50;
|
|
1874
1935
|
/**
|
|
1875
1936
|
* Hook for detecting scroll values in horizontal or vertical direction.
|
|
1937
|
+
* Returns a ref callback to attach to the element you want to observe.
|
|
1876
1938
|
*
|
|
1877
|
-
* @param {useRef} elementRef - Ref hook holding the element that needs to be observed during scrolling
|
|
1878
1939
|
* @param {object} options - Options object containing direction, onScrollStateChange callback, and skip
|
|
1879
|
-
* @returns {object} An object containing
|
|
1940
|
+
* @returns {object} An object containing ref callback, element, and scroll state (isScrollable, isAtBeginning, isAtEnd, scrollPosition)
|
|
1941
|
+
* @template TElement extends HTMLElement
|
|
1942
|
+
* @example
|
|
1943
|
+
* ```tsx
|
|
1944
|
+
* const { ref, isScrollable, isAtBeginning } = useScrollDetection({ direction: "horizontal" });
|
|
1945
|
+
* return <div ref={ref}>Scrollable content</div>;
|
|
1946
|
+
* ```
|
|
1880
1947
|
*/
|
|
1881
|
-
const useScrollDetection = (
|
|
1948
|
+
const useScrollDetection = (options) => {
|
|
1882
1949
|
const { direction = "vertical", onScrollStateChange, skip = false } = options ?? {};
|
|
1950
|
+
const [element, setElement] = react.useState(null);
|
|
1883
1951
|
const [isScrollable, setIsScrollable] = react.useState(false);
|
|
1884
1952
|
const [isAtBeginning, setIsAtBeginning] = react.useState(true);
|
|
1885
1953
|
const [isAtEnd, setIsAtEnd] = react.useState(false);
|
|
1886
1954
|
const [scrollPosition, setScrollPosition] = react.useState({ start: 0, end: 0 });
|
|
1887
1955
|
const observerRef = react.useRef(null);
|
|
1888
1956
|
const isFirstRender = useIsFirstRender();
|
|
1957
|
+
// Callback ref to track the element
|
|
1958
|
+
const ref = react.useCallback((node) => {
|
|
1959
|
+
setElement(node);
|
|
1960
|
+
}, []);
|
|
1889
1961
|
const checkScrollable = react.useCallback(() => {
|
|
1890
|
-
const element = elementRef?.current;
|
|
1891
1962
|
if (!element) {
|
|
1892
1963
|
return;
|
|
1893
1964
|
}
|
|
@@ -1901,9 +1972,8 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1901
1972
|
}
|
|
1902
1973
|
return prev;
|
|
1903
1974
|
});
|
|
1904
|
-
}, [
|
|
1975
|
+
}, [element, direction]);
|
|
1905
1976
|
const checkScrollPosition = react.useCallback(() => {
|
|
1906
|
-
const element = elementRef?.current;
|
|
1907
1977
|
if (!element) {
|
|
1908
1978
|
return;
|
|
1909
1979
|
}
|
|
@@ -1925,8 +1995,16 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1925
1995
|
setIsAtEnd(newIsAtEnd);
|
|
1926
1996
|
setScrollPosition(newScrollPosition);
|
|
1927
1997
|
}
|
|
1928
|
-
}, [
|
|
1998
|
+
}, [element, direction]);
|
|
1929
1999
|
const debouncedCheckScrollPosition = usehooksTs.useDebounceCallback(checkScrollPosition, SCROLL_DEBOUNCE_TIME);
|
|
2000
|
+
const checkScrollableRef = react.useRef(checkScrollable);
|
|
2001
|
+
react.useEffect(() => {
|
|
2002
|
+
checkScrollableRef.current = checkScrollable;
|
|
2003
|
+
}, [checkScrollable]);
|
|
2004
|
+
const checkScrollPositionRef = react.useRef(checkScrollPosition);
|
|
2005
|
+
react.useEffect(() => {
|
|
2006
|
+
checkScrollPositionRef.current = checkScrollPosition;
|
|
2007
|
+
}, [checkScrollPosition]);
|
|
1930
2008
|
// Notify about state changes whenever any state value changes
|
|
1931
2009
|
react.useEffect(() => {
|
|
1932
2010
|
if (isFirstRender) {
|
|
@@ -1940,19 +2018,15 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1940
2018
|
});
|
|
1941
2019
|
}, [isScrollable, isAtBeginning, isAtEnd, scrollPosition, onScrollStateChange, isFirstRender]);
|
|
1942
2020
|
react.useEffect(() => {
|
|
1943
|
-
if (skip) {
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
const element = elementRef?.current;
|
|
1947
|
-
if (!element) {
|
|
2021
|
+
if (skip || !element) {
|
|
1948
2022
|
return;
|
|
1949
2023
|
}
|
|
1950
2024
|
// Initial checks
|
|
1951
|
-
|
|
1952
|
-
|
|
2025
|
+
checkScrollableRef.current();
|
|
2026
|
+
checkScrollPositionRef.current();
|
|
1953
2027
|
observerRef.current = new ResizeObserver(() => {
|
|
1954
|
-
|
|
1955
|
-
|
|
2028
|
+
checkScrollableRef.current();
|
|
2029
|
+
checkScrollPositionRef.current();
|
|
1956
2030
|
});
|
|
1957
2031
|
observerRef.current.observe(element);
|
|
1958
2032
|
element.addEventListener("scroll", debouncedCheckScrollPosition);
|
|
@@ -1962,8 +2036,8 @@ const useScrollDetection = (elementRef, options) => {
|
|
|
1962
2036
|
}
|
|
1963
2037
|
element.removeEventListener("scroll", debouncedCheckScrollPosition);
|
|
1964
2038
|
};
|
|
1965
|
-
}, [
|
|
1966
|
-
return react.useMemo(() => ({ isScrollable, isAtBeginning, isAtEnd, scrollPosition }), [isScrollable, isAtBeginning, isAtEnd, scrollPosition]);
|
|
2039
|
+
}, [element, debouncedCheckScrollPosition, skip]);
|
|
2040
|
+
return react.useMemo(() => ({ ref, element, isScrollable, isAtBeginning, isAtEnd, scrollPosition }), [ref, element, isScrollable, isAtBeginning, isAtEnd, scrollPosition]);
|
|
1967
2041
|
};
|
|
1968
2042
|
|
|
1969
2043
|
/**
|
|
@@ -2016,24 +2090,28 @@ const useViewportBreakpoints = (options = {}) => {
|
|
|
2016
2090
|
}), { ...defaultBreakpointState });
|
|
2017
2091
|
setViewportSize(newViewportSize);
|
|
2018
2092
|
}, []);
|
|
2093
|
+
const updateViewportSizeRef = react.useRef(updateViewportSize);
|
|
2094
|
+
react.useEffect(() => {
|
|
2095
|
+
updateViewportSizeRef.current = updateViewportSize;
|
|
2096
|
+
}, [updateViewportSize]);
|
|
2019
2097
|
react.useEffect(() => {
|
|
2020
2098
|
if (skip) {
|
|
2021
2099
|
return;
|
|
2022
2100
|
}
|
|
2023
2101
|
// Initial check
|
|
2024
|
-
|
|
2102
|
+
updateViewportSizeRef.current();
|
|
2025
2103
|
// Set up listeners for each breakpoint
|
|
2026
2104
|
const mediaQueryLists = sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).map(([_, minWidth]) => window.matchMedia(`(min-width: ${minWidth}px)`));
|
|
2027
2105
|
mediaQueryLists.forEach(mql => {
|
|
2028
|
-
mql.addEventListener("change",
|
|
2106
|
+
mql.addEventListener("change", updateViewportSizeRef.current);
|
|
2029
2107
|
});
|
|
2030
2108
|
// Cleanup
|
|
2031
2109
|
return () => {
|
|
2032
2110
|
mediaQueryLists.forEach(mql => {
|
|
2033
|
-
mql.removeEventListener("change",
|
|
2111
|
+
mql.removeEventListener("change", updateViewportSizeRef.current);
|
|
2034
2112
|
});
|
|
2035
2113
|
};
|
|
2036
|
-
}, [
|
|
2114
|
+
}, [skip]);
|
|
2037
2115
|
return viewportSize;
|
|
2038
2116
|
};
|
|
2039
2117
|
|
|
@@ -2051,11 +2129,15 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
|
|
|
2051
2129
|
setFocused(hasFocus());
|
|
2052
2130
|
onBlur?.();
|
|
2053
2131
|
}, [onBlur]);
|
|
2132
|
+
const setFocusedRef = react.useRef(setFocused);
|
|
2133
|
+
react.useEffect(() => {
|
|
2134
|
+
setFocusedRef.current = setFocused;
|
|
2135
|
+
}, [setFocused]);
|
|
2054
2136
|
react.useEffect(() => {
|
|
2055
2137
|
if (skip) {
|
|
2056
2138
|
return;
|
|
2057
2139
|
}
|
|
2058
|
-
|
|
2140
|
+
setFocusedRef.current(hasFocus());
|
|
2059
2141
|
window.addEventListener("focus", onFocusInternal);
|
|
2060
2142
|
window.addEventListener("blur", onBlurInternal);
|
|
2061
2143
|
return () => {
|
|
@@ -2969,16 +3051,20 @@ const OverflowIndicator = ({ className, dataTestId, direction, onClickScroll, })
|
|
|
2969
3051
|
};
|
|
2970
3052
|
|
|
2971
3053
|
/**
|
|
2972
|
-
* Container for displaying components in a horizontal layout with overflow detection
|
|
3054
|
+
* Container for displaying components in a horizontal layout with overflow detection.
|
|
3055
|
+
* Provides visual indicators when content overflows and can be scrolled.
|
|
2973
3056
|
*
|
|
2974
|
-
* @param
|
|
2975
|
-
* @
|
|
3057
|
+
* @param props - Component properties
|
|
3058
|
+
* @param props.children - The content to display in the horizontal scroller
|
|
3059
|
+
* @param props.className - Optional CSS class name for styling
|
|
3060
|
+
* @param props.dataTestId - Optional test ID for testing purposes
|
|
3061
|
+
* @param props.onScrollStateChange - Optional callback fired when scroll state changes
|
|
3062
|
+
* @returns {ReactElement} A horizontal overflow scroller component with visual indicators
|
|
2976
3063
|
*/
|
|
2977
3064
|
const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollStateChange, }) => {
|
|
2978
3065
|
const childrenArray = react.Children.toArray(children);
|
|
2979
|
-
const
|
|
2980
|
-
const
|
|
2981
|
-
const { isScrollable, isAtBeginning, isAtEnd } = useScrollDetection(containerRef, {
|
|
3066
|
+
const { geometry: containerGeometry, ref: measureRef, element } = useMeasure();
|
|
3067
|
+
const { ref: scrollRef, isScrollable, isAtBeginning, isAtEnd, } = useScrollDetection({
|
|
2982
3068
|
direction: "horizontal",
|
|
2983
3069
|
onScrollStateChange: onScrollStateChange
|
|
2984
3070
|
? (state) => onScrollStateChange({
|
|
@@ -2988,23 +3074,24 @@ const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollS
|
|
|
2988
3074
|
})
|
|
2989
3075
|
: undefined,
|
|
2990
3076
|
});
|
|
3077
|
+
const mergedRef = useMergeRefs([measureRef, scrollRef]);
|
|
2991
3078
|
const handleScrollLeft = () => {
|
|
2992
|
-
if (!
|
|
3079
|
+
if (!element || containerGeometry?.width === undefined)
|
|
2993
3080
|
return;
|
|
2994
|
-
|
|
3081
|
+
element.scrollBy({
|
|
2995
3082
|
left: -containerGeometry.width,
|
|
2996
3083
|
behavior: "smooth",
|
|
2997
3084
|
});
|
|
2998
3085
|
};
|
|
2999
3086
|
const handleScrollRight = () => {
|
|
3000
|
-
if (!
|
|
3087
|
+
if (!element || containerGeometry?.width === undefined)
|
|
3001
3088
|
return;
|
|
3002
|
-
|
|
3089
|
+
element.scrollBy({
|
|
3003
3090
|
left: containerGeometry.width,
|
|
3004
3091
|
behavior: "smooth",
|
|
3005
3092
|
});
|
|
3006
3093
|
};
|
|
3007
|
-
return (jsxRuntime.jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), children: [jsxRuntime.jsx("div", { className: cvaHorizontalOverflowScroller(), "data-testid": dataTestId, ref:
|
|
3094
|
+
return (jsxRuntime.jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), children: [jsxRuntime.jsx("div", { className: cvaHorizontalOverflowScroller(), "data-testid": dataTestId, ref: mergedRef, children: childrenArray.map((child, index) => (jsxRuntime.jsx(react.Fragment, { children: child }, index))) }), isScrollable && !isAtBeginning ? (jsxRuntime.jsx(OverflowIndicator, { dataTestId: `${dataTestId}-left-indicator`, direction: "left", onClickScroll: handleScrollLeft })) : null, isScrollable && !isAtEnd ? (jsxRuntime.jsx(OverflowIndicator, { dataTestId: `${dataTestId}-right-indicator`, direction: "right", onClickScroll: handleScrollRight })) : null] }));
|
|
3008
3095
|
};
|
|
3009
3096
|
|
|
3010
3097
|
const PADDING = 12;
|
|
@@ -3314,12 +3401,14 @@ const PopoverTrigger = function PopoverTrigger({ children, renderButton = false,
|
|
|
3314
3401
|
const context = usePopoverContext();
|
|
3315
3402
|
const ref = react$1.useMergeRefs([context.refs.setReference, propRef]);
|
|
3316
3403
|
if (!renderButton && react.isValidElement(children)) {
|
|
3317
|
-
|
|
3318
|
-
ref,
|
|
3404
|
+
const referenceProps = context.getReferenceProps({
|
|
3319
3405
|
...props,
|
|
3320
3406
|
...children.props,
|
|
3321
3407
|
"data-state": context.isOpen === true ? "open" : "closed",
|
|
3322
|
-
})
|
|
3408
|
+
});
|
|
3409
|
+
const cloneProps = { ...referenceProps };
|
|
3410
|
+
cloneProps.ref = ref;
|
|
3411
|
+
return react.cloneElement(children, cloneProps);
|
|
3323
3412
|
}
|
|
3324
3413
|
return (jsxRuntime.jsx(Button, { "data-state": context.isOpen === true ? "open" : "closed", ref: ref, type: "button", ...context.getReferenceProps(props), children: children }));
|
|
3325
3414
|
};
|
|
@@ -4133,7 +4222,10 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
4133
4222
|
const isAtTop = react.useMemo(() => virtualizer.scrollOffset === 0, [virtualizer.scrollOffset]);
|
|
4134
4223
|
// totalSize must be out from from useMemo since otherwise we'll not be able to have the the totalSize as a dependency for the contentFillsContainer
|
|
4135
4224
|
const totalSize = virtualizer.getTotalSize();
|
|
4136
|
-
const contentFillsContainer = react.useMemo(() =>
|
|
4225
|
+
const contentFillsContainer = react.useMemo(() => {
|
|
4226
|
+
// eslint-disable-next-line react-hooks/refs
|
|
4227
|
+
return containerRef.current ? totalSize >= containerRef.current.clientHeight : false;
|
|
4228
|
+
}, [totalSize]);
|
|
4137
4229
|
return {
|
|
4138
4230
|
...virtualizer,
|
|
4139
4231
|
containerRef,
|
|
@@ -4799,12 +4891,20 @@ const Pagination = ({ previousPage, nextPage, canPreviousPage = false, canNextPa
|
|
|
4799
4891
|
if (!loading && pageCount === undefined) {
|
|
4800
4892
|
throw Error("Pagination should be provided with a pageCount");
|
|
4801
4893
|
}
|
|
4894
|
+
const setCurrentPageRef = react.useRef(setCurrentPage);
|
|
4895
|
+
react.useEffect(() => {
|
|
4896
|
+
setCurrentPageRef.current = setCurrentPage;
|
|
4897
|
+
}, [setCurrentPage]);
|
|
4898
|
+
const setPageRef = react.useRef(setPage);
|
|
4899
|
+
react.useEffect(() => {
|
|
4900
|
+
setPageRef.current = setPage;
|
|
4901
|
+
}, [setPage]);
|
|
4802
4902
|
react.useEffect(() => {
|
|
4803
4903
|
if (pageIndex !== undefined && (isNaN(pageIndex) || pageIndex < 0)) {
|
|
4804
|
-
|
|
4805
|
-
|
|
4904
|
+
setPageRef.current(pageIndex);
|
|
4905
|
+
setCurrentPageRef.current(String(pageIndex + 1));
|
|
4806
4906
|
}
|
|
4807
|
-
}, [pageIndex,
|
|
4907
|
+
}, [pageIndex, pageCount]);
|
|
4808
4908
|
const handlePageChange = react.useCallback((action) => {
|
|
4809
4909
|
const from = page;
|
|
4810
4910
|
let to = from;
|
|
@@ -5304,17 +5404,25 @@ const cvaToggleItemContent = cssClassVarianceUtilities.cvaMerge([], {
|
|
|
5304
5404
|
*/
|
|
5305
5405
|
const ToggleGroup = ({ list, selected, setSelected, onChange, disabled = false, size = "medium", isIconOnly = false, className, dataTestId, }) => {
|
|
5306
5406
|
const [isMounted, setIsMounted] = react.useState(false);
|
|
5407
|
+
const [slidingLeft, setSlidingLeft] = react.useState(0);
|
|
5408
|
+
const [slidingWidth, setSlidingWidth] = react.useState(0);
|
|
5307
5409
|
const buttonRefs = react.useRef([]);
|
|
5308
5410
|
const selectedIndex = list.findIndex(item => item.id === selected);
|
|
5309
5411
|
const validIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
const slidingWidth = buttonRefs.current[validIndex]?.offsetWidth ?? 0;
|
|
5315
|
-
react.useEffect(() => {
|
|
5316
|
-
setIsMounted(true);
|
|
5412
|
+
react.useLayoutEffect(() => {
|
|
5413
|
+
queueMicrotask(() => {
|
|
5414
|
+
setIsMounted(true);
|
|
5415
|
+
});
|
|
5317
5416
|
}, []);
|
|
5417
|
+
react.useEffect(() => {
|
|
5418
|
+
const containerPadding = 2; // p-0.5 = 2px
|
|
5419
|
+
const gap = 4;
|
|
5420
|
+
const left = containerPadding +
|
|
5421
|
+
buttonRefs.current.slice(0, validIndex).reduce((sum, ref) => sum + (ref?.offsetWidth ?? 0) + gap, 0);
|
|
5422
|
+
const width = buttonRefs.current[validIndex]?.offsetWidth ?? 0;
|
|
5423
|
+
setSlidingLeft(left);
|
|
5424
|
+
setSlidingWidth(width);
|
|
5425
|
+
}, [validIndex]);
|
|
5318
5426
|
return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge(cvaToggleGroup({ className }), cvaToggleGroupWithSlidingBackground({ isMounted })), "data-testid": dataTestId, style:
|
|
5319
5427
|
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
5320
5428
|
{
|
|
@@ -5613,7 +5721,7 @@ exports.useIsTextTruncated = useIsTextTruncated;
|
|
|
5613
5721
|
exports.useList = useList;
|
|
5614
5722
|
exports.useListItemHeight = useListItemHeight;
|
|
5615
5723
|
exports.useMeasure = useMeasure;
|
|
5616
|
-
exports.
|
|
5724
|
+
exports.useMergeRefs = useMergeRefs;
|
|
5617
5725
|
exports.useModifierKey = useModifierKey;
|
|
5618
5726
|
exports.useOverflowItems = useOverflowItems;
|
|
5619
5727
|
exports.usePopoverContext = usePopoverContext;
|