@trackunit/react-components 1.10.17 → 1.10.19
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 +102 -117
- package/index.esm.js +101 -117
- package/package.json +6 -6
- package/src/hooks/index.d.ts +3 -1
- package/src/hooks/useMeasure/useMeasure.d.ts +22 -0
- package/src/hooks/useMeasure/useMeasureElement.d.ts +19 -0
- package/src/hooks/useMeasure/useMeasureShared.d.ts +19 -0
- package/src/hooks/useGeometry.d.ts +0 -21
package/index.cjs.js
CHANGED
|
@@ -1325,109 +1325,6 @@ const useElevatedState = (initialState, customState) => {
|
|
|
1325
1325
|
return react.useMemo(() => customState ?? [fallbackValue, fallbackSetter], [customState, fallbackValue, fallbackSetter]);
|
|
1326
1326
|
};
|
|
1327
1327
|
|
|
1328
|
-
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
|
1329
|
-
/**
|
|
1330
|
-
* Custom hook to get the geometry of an element.
|
|
1331
|
-
* Size and position of the element relative to the viewport.
|
|
1332
|
-
*/
|
|
1333
|
-
const useGeometry = (ref, { skip = false, onChange } = {}) => {
|
|
1334
|
-
const [geometry, setGeometry] = react.useState(() => {
|
|
1335
|
-
const rect = ref.current?.getBoundingClientRect();
|
|
1336
|
-
if (!rect) {
|
|
1337
|
-
return {
|
|
1338
|
-
width: 0,
|
|
1339
|
-
height: 0,
|
|
1340
|
-
top: 0,
|
|
1341
|
-
bottom: 0,
|
|
1342
|
-
left: 0,
|
|
1343
|
-
right: 0,
|
|
1344
|
-
x: 0,
|
|
1345
|
-
y: 0,
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
return {
|
|
1349
|
-
width: rect.width,
|
|
1350
|
-
height: rect.height,
|
|
1351
|
-
top: rect.top,
|
|
1352
|
-
bottom: rect.bottom,
|
|
1353
|
-
left: rect.left,
|
|
1354
|
-
right: rect.right,
|
|
1355
|
-
x: rect.x,
|
|
1356
|
-
y: rect.y,
|
|
1357
|
-
};
|
|
1358
|
-
});
|
|
1359
|
-
const resizeObserver = react.useRef(null);
|
|
1360
|
-
const [element, setElement] = react.useState(ref.current);
|
|
1361
|
-
const prevGeometry = react.useRef(UNINITIALIZED);
|
|
1362
|
-
// Track changes to ref.current on every render
|
|
1363
|
-
if (ref.current !== element) {
|
|
1364
|
-
setElement(ref.current);
|
|
1365
|
-
}
|
|
1366
|
-
react.useEffect(() => {
|
|
1367
|
-
if (skip || !element) {
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
// Update geometry immediately when element changes
|
|
1371
|
-
const elementRect = element.getBoundingClientRect();
|
|
1372
|
-
const newGeometry = {
|
|
1373
|
-
width: elementRect.width,
|
|
1374
|
-
height: elementRect.height,
|
|
1375
|
-
top: elementRect.top,
|
|
1376
|
-
bottom: elementRect.bottom,
|
|
1377
|
-
left: elementRect.left,
|
|
1378
|
-
right: elementRect.right,
|
|
1379
|
-
x: elementRect.x,
|
|
1380
|
-
y: elementRect.y,
|
|
1381
|
-
};
|
|
1382
|
-
const prev = prevGeometry.current;
|
|
1383
|
-
const hasChanged = prev === UNINITIALIZED ? false : !esToolkit.isEqual(newGeometry, prev);
|
|
1384
|
-
if (!hasChanged && prev !== UNINITIALIZED) {
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
setGeometry(newGeometry);
|
|
1388
|
-
if (hasChanged && prev !== UNINITIALIZED) {
|
|
1389
|
-
onChange?.(newGeometry);
|
|
1390
|
-
}
|
|
1391
|
-
prevGeometry.current = newGeometry;
|
|
1392
|
-
const observe = () => {
|
|
1393
|
-
if (!resizeObserver.current) {
|
|
1394
|
-
resizeObserver.current = new ResizeObserver(entries => {
|
|
1395
|
-
for (const entry of entries) {
|
|
1396
|
-
const entryRect = entry.target.getBoundingClientRect();
|
|
1397
|
-
const observedGeometry = {
|
|
1398
|
-
width: entryRect.width,
|
|
1399
|
-
height: entryRect.height,
|
|
1400
|
-
top: entryRect.top,
|
|
1401
|
-
bottom: entryRect.bottom,
|
|
1402
|
-
left: entryRect.left,
|
|
1403
|
-
right: entryRect.right,
|
|
1404
|
-
x: entryRect.x,
|
|
1405
|
-
y: entryRect.y,
|
|
1406
|
-
};
|
|
1407
|
-
const prevObserved = prevGeometry.current;
|
|
1408
|
-
const hasObservedChanged = prevObserved === UNINITIALIZED ? false : !esToolkit.isEqual(observedGeometry, prevObserved);
|
|
1409
|
-
if (hasObservedChanged || prevObserved === UNINITIALIZED) {
|
|
1410
|
-
setGeometry(observedGeometry);
|
|
1411
|
-
if (hasObservedChanged && prevObserved !== UNINITIALIZED) {
|
|
1412
|
-
onChange?.(observedGeometry);
|
|
1413
|
-
}
|
|
1414
|
-
prevGeometry.current = observedGeometry;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
resizeObserver.current.observe(element);
|
|
1420
|
-
};
|
|
1421
|
-
observe();
|
|
1422
|
-
return () => {
|
|
1423
|
-
if (resizeObserver.current) {
|
|
1424
|
-
resizeObserver.current.disconnect();
|
|
1425
|
-
}
|
|
1426
|
-
};
|
|
1427
|
-
}, [element, onChange, skip]);
|
|
1428
|
-
return geometry;
|
|
1429
|
-
};
|
|
1430
|
-
|
|
1431
1328
|
/**
|
|
1432
1329
|
* The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
|
|
1433
1330
|
* The boolean will be true if the element is being hovered, and false if it is not.
|
|
@@ -1618,6 +1515,96 @@ const useIsTextTruncated = (text, { skip = false } = {}) => {
|
|
|
1618
1515
|
return react.useMemo(() => ({ ref, isTextTruncated }), [isTextTruncated]);
|
|
1619
1516
|
};
|
|
1620
1517
|
|
|
1518
|
+
/**
|
|
1519
|
+
* Shared measurement logic used by both useMeasure and useMeasureElement.
|
|
1520
|
+
* This hook observes an element and measures its geometry.
|
|
1521
|
+
*/
|
|
1522
|
+
const useMeasureShared = (element, { skip = false, onChange } = {}) => {
|
|
1523
|
+
const [geometry, setGeometry] = react.useState(undefined);
|
|
1524
|
+
const observerRef = react.useRef(null);
|
|
1525
|
+
const onChangeRef = react.useRef(onChange);
|
|
1526
|
+
const isInitialRender = react.useRef(true);
|
|
1527
|
+
onChangeRef.current = onChange;
|
|
1528
|
+
const disconnectObserver = () => {
|
|
1529
|
+
if (observerRef.current !== null) {
|
|
1530
|
+
observerRef.current.disconnect();
|
|
1531
|
+
observerRef.current = null;
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
react.useEffect(() => {
|
|
1535
|
+
if (skip || element === null) {
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
const updateGeometry = (rect) => {
|
|
1539
|
+
const newGeometry = esToolkit.omit(rect, ["toJSON"]);
|
|
1540
|
+
setGeometry(prevGeometry => {
|
|
1541
|
+
if (esToolkit.isEqual(prevGeometry, newGeometry)) {
|
|
1542
|
+
return prevGeometry;
|
|
1543
|
+
}
|
|
1544
|
+
if (isInitialRender.current === false) {
|
|
1545
|
+
onChangeRef.current?.(newGeometry);
|
|
1546
|
+
}
|
|
1547
|
+
return newGeometry;
|
|
1548
|
+
});
|
|
1549
|
+
isInitialRender.current = false;
|
|
1550
|
+
};
|
|
1551
|
+
const handleResize = (entries) => {
|
|
1552
|
+
for (const entry of entries) {
|
|
1553
|
+
const rect = entry.target.getBoundingClientRect();
|
|
1554
|
+
updateGeometry(rect);
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
disconnectObserver();
|
|
1558
|
+
const observer = new ResizeObserver(handleResize);
|
|
1559
|
+
observer.observe(element);
|
|
1560
|
+
observerRef.current = observer;
|
|
1561
|
+
const initialRect = element.getBoundingClientRect();
|
|
1562
|
+
updateGeometry(initialRect);
|
|
1563
|
+
return disconnectObserver;
|
|
1564
|
+
}, [element, skip]);
|
|
1565
|
+
return geometry;
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Custom hook to measure the geometry of an element using a callback ref.
|
|
1570
|
+
* Measures the size and position of the element relative to the viewport.
|
|
1571
|
+
*
|
|
1572
|
+
* @returns {UseMeasureResult<HTMLElement>} An object containing `geometry`, `ref` callback, and `element`.
|
|
1573
|
+
* @template TElement extends HTMLElement
|
|
1574
|
+
* @example
|
|
1575
|
+
* ```tsx
|
|
1576
|
+
* const { geometry, ref } = useMeasure();
|
|
1577
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
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.
|
|
1594
|
+
*
|
|
1595
|
+
* @param ref - RefObject pointing to the element to measure
|
|
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>;
|
|
1602
|
+
* ```
|
|
1603
|
+
*/
|
|
1604
|
+
const useMeasureElement = (ref, { skip = false, onChange } = {}) => {
|
|
1605
|
+
return useMeasureShared(ref.current, { skip, onChange });
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1621
1608
|
/**
|
|
1622
1609
|
* Hook that returns true if any modifier key (Ctrl, Alt, Shift, Meta/Cmd) is pressed
|
|
1623
1610
|
*
|
|
@@ -2396,9 +2383,8 @@ const Collapse = ({ id, variant = "primary", initialExpanded = false, onToggle,
|
|
|
2396
2383
|
return (jsxRuntime.jsxs("div", { className: cvaCollapse({ variant: variant, className }), "data-testid": dataTestId, children: [jsxRuntime.jsx("div", { "aria-controls": id, "aria-expanded": expanded, className: cvaCollapseHeader({ expanded, variant, extraPadding, className: headerClassName }), onClick: handleClick, role: "button", children: jsxRuntime.jsxs("div", { className: cvaCollapseLabelContainer({ variant }), children: [jsxRuntime.jsx(Text, { className: cvaCollapseLabel({ variant }), id: LABEL_ID, size: variant === "secondary" ? "small" : "medium", type: "span", weight: "bold", children: label }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [headerAddon !== null && headerAddon !== undefined && variant !== "secondary" ? headerAddon : null, jsxRuntime.jsx(Icon, { ariaLabelledBy: LABEL_ID, className: cvaChevronIcon({ expanded }), name: "ChevronUp", size: variant === "secondary" ? "small" : "medium" })] })] }) }), jsxRuntime.jsx(Collapsible, { expanded: expanded, extraPadding: extraPadding, id: id, variant: variant, children: expanded || animate ? children : null })] }));
|
|
2397
2384
|
};
|
|
2398
2385
|
const Collapsible = ({ children, expanded, id, variant, extraPadding }) => {
|
|
2399
|
-
const ref =
|
|
2400
|
-
|
|
2401
|
-
return (jsxRuntime.jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? height || "auto" : "0" }, children: jsxRuntime.jsx("div", { ref: ref, children: jsxRuntime.jsx("div", { className: cvaCollapsible({ variant, extraPadding }), children: children }) }) }));
|
|
2386
|
+
const { geometry, ref } = useMeasure();
|
|
2387
|
+
return (jsxRuntime.jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? (geometry?.height ?? "auto") : "0" }, children: jsxRuntime.jsx("div", { ref: ref, children: jsxRuntime.jsx("div", { className: cvaCollapsible({ variant, extraPadding }), children: children }) }) }));
|
|
2402
2388
|
};
|
|
2403
2389
|
|
|
2404
2390
|
/**
|
|
@@ -2845,9 +2831,9 @@ const OverflowIndicator = ({ className, dataTestId, direction, onClickScroll, })
|
|
|
2845
2831
|
* @returns {Element} HorizontalOverflowScroller component
|
|
2846
2832
|
*/
|
|
2847
2833
|
const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollStateChange, }) => {
|
|
2848
|
-
const containerRef = react.useRef(null);
|
|
2849
2834
|
const childrenArray = react.Children.toArray(children);
|
|
2850
|
-
const
|
|
2835
|
+
const containerRef = react.useRef(null);
|
|
2836
|
+
const containerGeometry = useMeasureElement(containerRef);
|
|
2851
2837
|
const { isScrollable, isAtBeginning, isAtEnd } = useScrollDetection(containerRef, {
|
|
2852
2838
|
direction: "horizontal",
|
|
2853
2839
|
onScrollStateChange: onScrollStateChange
|
|
@@ -2859,20 +2845,18 @@ const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollS
|
|
|
2859
2845
|
: undefined,
|
|
2860
2846
|
});
|
|
2861
2847
|
const handleScrollLeft = () => {
|
|
2862
|
-
|
|
2863
|
-
if (!element || !containerWidth)
|
|
2848
|
+
if (!containerRef.current || containerGeometry?.width === undefined)
|
|
2864
2849
|
return;
|
|
2865
|
-
|
|
2866
|
-
left: -
|
|
2850
|
+
containerRef.current.scrollBy({
|
|
2851
|
+
left: -containerGeometry.width,
|
|
2867
2852
|
behavior: "smooth",
|
|
2868
2853
|
});
|
|
2869
2854
|
};
|
|
2870
2855
|
const handleScrollRight = () => {
|
|
2871
|
-
|
|
2872
|
-
if (!element || !containerWidth)
|
|
2856
|
+
if (!containerRef.current || containerGeometry?.width === undefined)
|
|
2873
2857
|
return;
|
|
2874
|
-
|
|
2875
|
-
left:
|
|
2858
|
+
containerRef.current.scrollBy({
|
|
2859
|
+
left: containerGeometry.width,
|
|
2876
2860
|
behavior: "smooth",
|
|
2877
2861
|
});
|
|
2878
2862
|
};
|
|
@@ -5477,7 +5461,6 @@ exports.useDebounce = useDebounce;
|
|
|
5477
5461
|
exports.useDevicePixelRatio = useDevicePixelRatio;
|
|
5478
5462
|
exports.useElevatedReducer = useElevatedReducer;
|
|
5479
5463
|
exports.useElevatedState = useElevatedState;
|
|
5480
|
-
exports.useGeometry = useGeometry;
|
|
5481
5464
|
exports.useHover = useHover;
|
|
5482
5465
|
exports.useInfiniteScroll = useInfiniteScroll;
|
|
5483
5466
|
exports.useIsFirstRender = useIsFirstRender;
|
|
@@ -5485,6 +5468,8 @@ exports.useIsFullscreen = useIsFullscreen;
|
|
|
5485
5468
|
exports.useIsTextTruncated = useIsTextTruncated;
|
|
5486
5469
|
exports.useList = useList;
|
|
5487
5470
|
exports.useListItemHeight = useListItemHeight;
|
|
5471
|
+
exports.useMeasure = useMeasure;
|
|
5472
|
+
exports.useMeasureElement = useMeasureElement;
|
|
5488
5473
|
exports.useModifierKey = useModifierKey;
|
|
5489
5474
|
exports.useOverflowItems = useOverflowItems;
|
|
5490
5475
|
exports.usePopoverContext = usePopoverContext;
|
package/index.esm.js
CHANGED
|
@@ -1323,109 +1323,6 @@ const useElevatedState = (initialState, customState) => {
|
|
|
1323
1323
|
return useMemo(() => customState ?? [fallbackValue, fallbackSetter], [customState, fallbackValue, fallbackSetter]);
|
|
1324
1324
|
};
|
|
1325
1325
|
|
|
1326
|
-
const UNINITIALIZED = Symbol("UNINITIALIZED");
|
|
1327
|
-
/**
|
|
1328
|
-
* Custom hook to get the geometry of an element.
|
|
1329
|
-
* Size and position of the element relative to the viewport.
|
|
1330
|
-
*/
|
|
1331
|
-
const useGeometry = (ref, { skip = false, onChange } = {}) => {
|
|
1332
|
-
const [geometry, setGeometry] = useState(() => {
|
|
1333
|
-
const rect = ref.current?.getBoundingClientRect();
|
|
1334
|
-
if (!rect) {
|
|
1335
|
-
return {
|
|
1336
|
-
width: 0,
|
|
1337
|
-
height: 0,
|
|
1338
|
-
top: 0,
|
|
1339
|
-
bottom: 0,
|
|
1340
|
-
left: 0,
|
|
1341
|
-
right: 0,
|
|
1342
|
-
x: 0,
|
|
1343
|
-
y: 0,
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
return {
|
|
1347
|
-
width: rect.width,
|
|
1348
|
-
height: rect.height,
|
|
1349
|
-
top: rect.top,
|
|
1350
|
-
bottom: rect.bottom,
|
|
1351
|
-
left: rect.left,
|
|
1352
|
-
right: rect.right,
|
|
1353
|
-
x: rect.x,
|
|
1354
|
-
y: rect.y,
|
|
1355
|
-
};
|
|
1356
|
-
});
|
|
1357
|
-
const resizeObserver = useRef(null);
|
|
1358
|
-
const [element, setElement] = useState(ref.current);
|
|
1359
|
-
const prevGeometry = useRef(UNINITIALIZED);
|
|
1360
|
-
// Track changes to ref.current on every render
|
|
1361
|
-
if (ref.current !== element) {
|
|
1362
|
-
setElement(ref.current);
|
|
1363
|
-
}
|
|
1364
|
-
useEffect(() => {
|
|
1365
|
-
if (skip || !element) {
|
|
1366
|
-
return;
|
|
1367
|
-
}
|
|
1368
|
-
// Update geometry immediately when element changes
|
|
1369
|
-
const elementRect = element.getBoundingClientRect();
|
|
1370
|
-
const newGeometry = {
|
|
1371
|
-
width: elementRect.width,
|
|
1372
|
-
height: elementRect.height,
|
|
1373
|
-
top: elementRect.top,
|
|
1374
|
-
bottom: elementRect.bottom,
|
|
1375
|
-
left: elementRect.left,
|
|
1376
|
-
right: elementRect.right,
|
|
1377
|
-
x: elementRect.x,
|
|
1378
|
-
y: elementRect.y,
|
|
1379
|
-
};
|
|
1380
|
-
const prev = prevGeometry.current;
|
|
1381
|
-
const hasChanged = prev === UNINITIALIZED ? false : !isEqual(newGeometry, prev);
|
|
1382
|
-
if (!hasChanged && prev !== UNINITIALIZED) {
|
|
1383
|
-
return;
|
|
1384
|
-
}
|
|
1385
|
-
setGeometry(newGeometry);
|
|
1386
|
-
if (hasChanged && prev !== UNINITIALIZED) {
|
|
1387
|
-
onChange?.(newGeometry);
|
|
1388
|
-
}
|
|
1389
|
-
prevGeometry.current = newGeometry;
|
|
1390
|
-
const observe = () => {
|
|
1391
|
-
if (!resizeObserver.current) {
|
|
1392
|
-
resizeObserver.current = new ResizeObserver(entries => {
|
|
1393
|
-
for (const entry of entries) {
|
|
1394
|
-
const entryRect = entry.target.getBoundingClientRect();
|
|
1395
|
-
const observedGeometry = {
|
|
1396
|
-
width: entryRect.width,
|
|
1397
|
-
height: entryRect.height,
|
|
1398
|
-
top: entryRect.top,
|
|
1399
|
-
bottom: entryRect.bottom,
|
|
1400
|
-
left: entryRect.left,
|
|
1401
|
-
right: entryRect.right,
|
|
1402
|
-
x: entryRect.x,
|
|
1403
|
-
y: entryRect.y,
|
|
1404
|
-
};
|
|
1405
|
-
const prevObserved = prevGeometry.current;
|
|
1406
|
-
const hasObservedChanged = prevObserved === UNINITIALIZED ? false : !isEqual(observedGeometry, prevObserved);
|
|
1407
|
-
if (hasObservedChanged || prevObserved === UNINITIALIZED) {
|
|
1408
|
-
setGeometry(observedGeometry);
|
|
1409
|
-
if (hasObservedChanged && prevObserved !== UNINITIALIZED) {
|
|
1410
|
-
onChange?.(observedGeometry);
|
|
1411
|
-
}
|
|
1412
|
-
prevGeometry.current = observedGeometry;
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
resizeObserver.current.observe(element);
|
|
1418
|
-
};
|
|
1419
|
-
observe();
|
|
1420
|
-
return () => {
|
|
1421
|
-
if (resizeObserver.current) {
|
|
1422
|
-
resizeObserver.current.disconnect();
|
|
1423
|
-
}
|
|
1424
|
-
};
|
|
1425
|
-
}, [element, onChange, skip]);
|
|
1426
|
-
return geometry;
|
|
1427
|
-
};
|
|
1428
|
-
|
|
1429
1326
|
/**
|
|
1430
1327
|
* The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
|
|
1431
1328
|
* The boolean will be true if the element is being hovered, and false if it is not.
|
|
@@ -1616,6 +1513,96 @@ const useIsTextTruncated = (text, { skip = false } = {}) => {
|
|
|
1616
1513
|
return useMemo(() => ({ ref, isTextTruncated }), [isTextTruncated]);
|
|
1617
1514
|
};
|
|
1618
1515
|
|
|
1516
|
+
/**
|
|
1517
|
+
* Shared measurement logic used by both useMeasure and useMeasureElement.
|
|
1518
|
+
* This hook observes an element and measures its geometry.
|
|
1519
|
+
*/
|
|
1520
|
+
const useMeasureShared = (element, { skip = false, onChange } = {}) => {
|
|
1521
|
+
const [geometry, setGeometry] = useState(undefined);
|
|
1522
|
+
const observerRef = useRef(null);
|
|
1523
|
+
const onChangeRef = useRef(onChange);
|
|
1524
|
+
const isInitialRender = useRef(true);
|
|
1525
|
+
onChangeRef.current = onChange;
|
|
1526
|
+
const disconnectObserver = () => {
|
|
1527
|
+
if (observerRef.current !== null) {
|
|
1528
|
+
observerRef.current.disconnect();
|
|
1529
|
+
observerRef.current = null;
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
useEffect(() => {
|
|
1533
|
+
if (skip || element === null) {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
const updateGeometry = (rect) => {
|
|
1537
|
+
const newGeometry = omit(rect, ["toJSON"]);
|
|
1538
|
+
setGeometry(prevGeometry => {
|
|
1539
|
+
if (isEqual(prevGeometry, newGeometry)) {
|
|
1540
|
+
return prevGeometry;
|
|
1541
|
+
}
|
|
1542
|
+
if (isInitialRender.current === false) {
|
|
1543
|
+
onChangeRef.current?.(newGeometry);
|
|
1544
|
+
}
|
|
1545
|
+
return newGeometry;
|
|
1546
|
+
});
|
|
1547
|
+
isInitialRender.current = false;
|
|
1548
|
+
};
|
|
1549
|
+
const handleResize = (entries) => {
|
|
1550
|
+
for (const entry of entries) {
|
|
1551
|
+
const rect = entry.target.getBoundingClientRect();
|
|
1552
|
+
updateGeometry(rect);
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
disconnectObserver();
|
|
1556
|
+
const observer = new ResizeObserver(handleResize);
|
|
1557
|
+
observer.observe(element);
|
|
1558
|
+
observerRef.current = observer;
|
|
1559
|
+
const initialRect = element.getBoundingClientRect();
|
|
1560
|
+
updateGeometry(initialRect);
|
|
1561
|
+
return disconnectObserver;
|
|
1562
|
+
}, [element, skip]);
|
|
1563
|
+
return geometry;
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* Custom hook to measure the geometry of an element using a callback ref.
|
|
1568
|
+
* Measures the size and position of the element relative to the viewport.
|
|
1569
|
+
*
|
|
1570
|
+
* @returns {UseMeasureResult<HTMLElement>} An object containing `geometry`, `ref` callback, and `element`.
|
|
1571
|
+
* @template TElement extends HTMLElement
|
|
1572
|
+
* @example
|
|
1573
|
+
* ```tsx
|
|
1574
|
+
* const { geometry, ref } = useMeasure();
|
|
1575
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
1576
|
+
* ```
|
|
1577
|
+
*/
|
|
1578
|
+
const useMeasure = ({ skip = false, onChange, } = {}) => {
|
|
1579
|
+
const [element, setElement] = useState(null);
|
|
1580
|
+
// Callback ref to track the element
|
|
1581
|
+
const ref = useCallback((node) => {
|
|
1582
|
+
setElement(node);
|
|
1583
|
+
}, []);
|
|
1584
|
+
const geometry = useMeasureShared(element, { skip, onChange });
|
|
1585
|
+
return { geometry, ref, element };
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Custom hook to measure the geometry of an element from a RefObject.
|
|
1590
|
+
* Use this when you already have a ref (e.g., from useRef or for composition with other hooks).
|
|
1591
|
+
* Measures the size and position of the element relative to the viewport.
|
|
1592
|
+
*
|
|
1593
|
+
* @param ref - RefObject pointing to the element to measure
|
|
1594
|
+
* @returns {Geometry} The geometry of the element
|
|
1595
|
+
* @example
|
|
1596
|
+
* ```tsx
|
|
1597
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
1598
|
+
* const geometry = useMeasureElement(ref);
|
|
1599
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
1600
|
+
* ```
|
|
1601
|
+
*/
|
|
1602
|
+
const useMeasureElement = (ref, { skip = false, onChange } = {}) => {
|
|
1603
|
+
return useMeasureShared(ref.current, { skip, onChange });
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1619
1606
|
/**
|
|
1620
1607
|
* Hook that returns true if any modifier key (Ctrl, Alt, Shift, Meta/Cmd) is pressed
|
|
1621
1608
|
*
|
|
@@ -2394,9 +2381,8 @@ const Collapse = ({ id, variant = "primary", initialExpanded = false, onToggle,
|
|
|
2394
2381
|
return (jsxs("div", { className: cvaCollapse({ variant: variant, className }), "data-testid": dataTestId, children: [jsx("div", { "aria-controls": id, "aria-expanded": expanded, className: cvaCollapseHeader({ expanded, variant, extraPadding, className: headerClassName }), onClick: handleClick, role: "button", children: jsxs("div", { className: cvaCollapseLabelContainer({ variant }), children: [jsx(Text, { className: cvaCollapseLabel({ variant }), id: LABEL_ID, size: variant === "secondary" ? "small" : "medium", type: "span", weight: "bold", children: label }), jsxs("div", { className: "flex items-center gap-2", children: [headerAddon !== null && headerAddon !== undefined && variant !== "secondary" ? headerAddon : null, jsx(Icon, { ariaLabelledBy: LABEL_ID, className: cvaChevronIcon({ expanded }), name: "ChevronUp", size: variant === "secondary" ? "small" : "medium" })] })] }) }), jsx(Collapsible, { expanded: expanded, extraPadding: extraPadding, id: id, variant: variant, children: expanded || animate ? children : null })] }));
|
|
2395
2382
|
};
|
|
2396
2383
|
const Collapsible = ({ children, expanded, id, variant, extraPadding }) => {
|
|
2397
|
-
const ref =
|
|
2398
|
-
|
|
2399
|
-
return (jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? height || "auto" : "0" }, children: jsx("div", { ref: ref, children: jsx("div", { className: cvaCollapsible({ variant, extraPadding }), children: children }) }) }));
|
|
2384
|
+
const { geometry, ref } = useMeasure();
|
|
2385
|
+
return (jsx("div", { className: cvaCollapseAnimated({ expanded }), id: id, style: { height: expanded ? (geometry?.height ?? "auto") : "0" }, children: jsx("div", { ref: ref, children: jsx("div", { className: cvaCollapsible({ variant, extraPadding }), children: children }) }) }));
|
|
2400
2386
|
};
|
|
2401
2387
|
|
|
2402
2388
|
/**
|
|
@@ -2843,9 +2829,9 @@ const OverflowIndicator = ({ className, dataTestId, direction, onClickScroll, })
|
|
|
2843
2829
|
* @returns {Element} HorizontalOverflowScroller component
|
|
2844
2830
|
*/
|
|
2845
2831
|
const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollStateChange, }) => {
|
|
2846
|
-
const containerRef = useRef(null);
|
|
2847
2832
|
const childrenArray = Children.toArray(children);
|
|
2848
|
-
const
|
|
2833
|
+
const containerRef = useRef(null);
|
|
2834
|
+
const containerGeometry = useMeasureElement(containerRef);
|
|
2849
2835
|
const { isScrollable, isAtBeginning, isAtEnd } = useScrollDetection(containerRef, {
|
|
2850
2836
|
direction: "horizontal",
|
|
2851
2837
|
onScrollStateChange: onScrollStateChange
|
|
@@ -2857,20 +2843,18 @@ const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollS
|
|
|
2857
2843
|
: undefined,
|
|
2858
2844
|
});
|
|
2859
2845
|
const handleScrollLeft = () => {
|
|
2860
|
-
|
|
2861
|
-
if (!element || !containerWidth)
|
|
2846
|
+
if (!containerRef.current || containerGeometry?.width === undefined)
|
|
2862
2847
|
return;
|
|
2863
|
-
|
|
2864
|
-
left: -
|
|
2848
|
+
containerRef.current.scrollBy({
|
|
2849
|
+
left: -containerGeometry.width,
|
|
2865
2850
|
behavior: "smooth",
|
|
2866
2851
|
});
|
|
2867
2852
|
};
|
|
2868
2853
|
const handleScrollRight = () => {
|
|
2869
|
-
|
|
2870
|
-
if (!element || !containerWidth)
|
|
2854
|
+
if (!containerRef.current || containerGeometry?.width === undefined)
|
|
2871
2855
|
return;
|
|
2872
|
-
|
|
2873
|
-
left:
|
|
2856
|
+
containerRef.current.scrollBy({
|
|
2857
|
+
left: containerGeometry.width,
|
|
2874
2858
|
behavior: "smooth",
|
|
2875
2859
|
});
|
|
2876
2860
|
};
|
|
@@ -5359,4 +5343,4 @@ const cvaClickable = cvaMerge([
|
|
|
5359
5343
|
},
|
|
5360
5344
|
});
|
|
5361
5345
|
|
|
5362
|
-
export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DetailsList, EmptyState, EmptyValue, ExternalLink, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, ValueBar, ZStack, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState,
|
|
5346
|
+
export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DetailsList, EmptyState, EmptyValue, ExternalLink, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, ValueBar, ZStack, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useMeasure, useMeasureElement, useModifierKey, useOverflowItems, usePopoverContext, usePrompt, useRelayPagination, useResize, useScrollDetection, useSelfUpdatingRef, useTimeout, useViewportBreakpoints, useWindowActivity };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.19",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"@floating-ui/react": "^0.26.25",
|
|
17
17
|
"string-ts": "^2.0.0",
|
|
18
18
|
"tailwind-merge": "^2.0.0",
|
|
19
|
-
"@trackunit/ui-design-tokens": "1.7.
|
|
20
|
-
"@trackunit/css-class-variance-utilities": "1.7.
|
|
21
|
-
"@trackunit/shared-utils": "1.9.
|
|
22
|
-
"@trackunit/ui-icons": "1.7.
|
|
23
|
-
"@trackunit/react-test-setup": "1.4.
|
|
19
|
+
"@trackunit/ui-design-tokens": "1.7.51",
|
|
20
|
+
"@trackunit/css-class-variance-utilities": "1.7.51",
|
|
21
|
+
"@trackunit/shared-utils": "1.9.51",
|
|
22
|
+
"@trackunit/ui-icons": "1.7.52",
|
|
23
|
+
"@trackunit/react-test-setup": "1.4.51",
|
|
24
24
|
"@tanstack/react-router": "1.114.29",
|
|
25
25
|
"es-toolkit": "^1.39.10",
|
|
26
26
|
"@tanstack/react-virtual": "3.13.12"
|
package/src/hooks/index.d.ts
CHANGED
|
@@ -6,12 +6,14 @@ export * from "./useDebounce";
|
|
|
6
6
|
export * from "./useDevicePixelRatio";
|
|
7
7
|
export * from "./useElevatedReducer";
|
|
8
8
|
export * from "./useElevatedState";
|
|
9
|
-
export * from "./useGeometry";
|
|
10
9
|
export * from "./useHover";
|
|
11
10
|
export * from "./useInfiniteScroll";
|
|
12
11
|
export * from "./useIsFirstRender";
|
|
13
12
|
export * from "./useIsFullScreen";
|
|
14
13
|
export * from "./useIsTextTruncated";
|
|
14
|
+
export * from "./useMeasure/useMeasure";
|
|
15
|
+
export * from "./useMeasure/useMeasureElement";
|
|
16
|
+
export type { Geometry } from "./useMeasure/useMeasureShared";
|
|
15
17
|
export * from "./useModifierKey";
|
|
16
18
|
export * from "./useRelayPagination";
|
|
17
19
|
export * from "./useResize";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { RefCallback } from "react";
|
|
2
|
+
import { Geometry, UseMeasureSharedOptions } from "./useMeasureShared";
|
|
3
|
+
type UseMeasureOptions = UseMeasureSharedOptions;
|
|
4
|
+
type UseMeasureResult<TElement extends HTMLElement = HTMLElement> = {
|
|
5
|
+
geometry: Geometry | undefined;
|
|
6
|
+
ref: RefCallback<TElement>;
|
|
7
|
+
element: TElement | null;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Custom hook to measure the geometry of an element using a callback ref.
|
|
11
|
+
* Measures the size and position of the element relative to the viewport.
|
|
12
|
+
*
|
|
13
|
+
* @returns {UseMeasureResult<HTMLElement>} An object containing `geometry`, `ref` callback, and `element`.
|
|
14
|
+
* @template TElement extends HTMLElement
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const { geometry, ref } = useMeasure();
|
|
18
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const useMeasure: <TElement extends HTMLElement = HTMLElement>({ skip, onChange, }?: UseMeasureOptions) => UseMeasureResult<TElement>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { Geometry, UseMeasureSharedOptions } from "./useMeasureShared";
|
|
3
|
+
type UseMeasureElementOptions = UseMeasureSharedOptions;
|
|
4
|
+
/**
|
|
5
|
+
* Custom hook to measure the geometry of an element from a RefObject.
|
|
6
|
+
* Use this when you already have a ref (e.g., from useRef or for composition with other hooks).
|
|
7
|
+
* Measures the size and position of the element relative to the viewport.
|
|
8
|
+
*
|
|
9
|
+
* @param ref - RefObject pointing to the element to measure
|
|
10
|
+
* @returns {Geometry} The geometry of the element
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
14
|
+
* const geometry = useMeasureElement(ref);
|
|
15
|
+
* return <div ref={ref}>Width: {geometry.width}</div>;
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare const useMeasureElement: <TElement extends HTMLElement = HTMLElement>(ref: RefObject<TElement | null>, { skip, onChange }?: UseMeasureElementOptions) => Geometry | undefined;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Geometry = {
|
|
2
|
+
readonly height: number;
|
|
3
|
+
readonly width: number;
|
|
4
|
+
readonly x: number;
|
|
5
|
+
readonly y: number;
|
|
6
|
+
readonly bottom: number;
|
|
7
|
+
readonly left: number;
|
|
8
|
+
readonly right: number;
|
|
9
|
+
readonly top: number;
|
|
10
|
+
};
|
|
11
|
+
export type UseMeasureSharedOptions = {
|
|
12
|
+
skip?: boolean;
|
|
13
|
+
onChange?: (geometry: Geometry) => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Shared measurement logic used by both useMeasure and useMeasureElement.
|
|
17
|
+
* This hook observes an element and measures its geometry.
|
|
18
|
+
*/
|
|
19
|
+
export declare const useMeasureShared: <TElement extends HTMLElement>(element: TElement | null, { skip, onChange }?: UseMeasureSharedOptions) => Geometry | undefined;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { RefObject } from "react";
|
|
2
|
-
type Geometry = {
|
|
3
|
-
width: number;
|
|
4
|
-
height: number;
|
|
5
|
-
top: number;
|
|
6
|
-
bottom: number;
|
|
7
|
-
left: number;
|
|
8
|
-
right: number;
|
|
9
|
-
x: number;
|
|
10
|
-
y: number;
|
|
11
|
-
};
|
|
12
|
-
interface UseGeometryOptions {
|
|
13
|
-
skip?: boolean;
|
|
14
|
-
onChange?: (geometry: Geometry) => void;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Custom hook to get the geometry of an element.
|
|
18
|
-
* Size and position of the element relative to the viewport.
|
|
19
|
-
*/
|
|
20
|
-
export declare const useGeometry: (ref: RefObject<HTMLElement | null>, { skip, onChange }?: UseGeometryOptions) => Geometry;
|
|
21
|
-
export {};
|