@trackunit/react-components 1.21.8 → 1.21.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs.js CHANGED
@@ -1341,6 +1341,15 @@ const createBreakpointState = ({ width }) => {
1341
1341
  }), { ...defaultBreakpointState });
1342
1342
  };
1343
1343
 
1344
+ const canUseMatchMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function";
1345
+ const getViewportBreakpointState = () => {
1346
+ if (!canUseMatchMedia())
1347
+ return { ...defaultBreakpointState };
1348
+ return sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1349
+ ...acc,
1350
+ [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1351
+ }), { ...defaultBreakpointState });
1352
+ };
1344
1353
  /**
1345
1354
  * A custom React hook that provides real-time information about the current viewport size.
1346
1355
  * ! Consider using `useContainerBreakpoints` instead, and only use this when you need to actually react to the viewport size, not the container size.
@@ -1364,36 +1373,23 @@ const createBreakpointState = ({ width }) => {
1364
1373
  */
1365
1374
  const useViewportBreakpoints = (options = {}) => {
1366
1375
  const { skip = false } = options;
1367
- const [viewportSize, setViewportSize] = react.useState(() => {
1368
- const newViewportSize = sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1369
- ...acc,
1370
- [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1371
- }), { ...defaultBreakpointState });
1372
- return newViewportSize;
1373
- });
1376
+ const [viewportSize, setViewportSize] = react.useState(getViewportBreakpointState);
1374
1377
  const updateViewportSize = react.useCallback(() => {
1375
- const newViewportSize = sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1376
- ...acc,
1377
- [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1378
- }), { ...defaultBreakpointState });
1379
- setViewportSize(newViewportSize);
1378
+ setViewportSize(getViewportBreakpointState());
1380
1379
  }, []);
1381
1380
  const updateViewportSizeRef = react.useRef(updateViewportSize);
1382
1381
  react.useEffect(() => {
1383
1382
  updateViewportSizeRef.current = updateViewportSize;
1384
1383
  }, [updateViewportSize]);
1385
1384
  react.useEffect(() => {
1386
- if (skip) {
1385
+ if (skip || !canUseMatchMedia()) {
1387
1386
  return;
1388
1387
  }
1389
- // Initial check
1390
1388
  updateViewportSizeRef.current();
1391
- // Set up listeners for each breakpoint
1392
1389
  const mediaQueryLists = sharedUtils.objectEntries(uiDesignTokens.themeScreenSizeAsNumber).map(([_, minWidth]) => window.matchMedia(`(min-width: ${minWidth}px)`));
1393
1390
  mediaQueryLists.forEach(mql => {
1394
1391
  mql.addEventListener("change", updateViewportSizeRef.current);
1395
1392
  });
1396
- // Cleanup
1397
1393
  return () => {
1398
1394
  mediaQueryLists.forEach(mql => {
1399
1395
  mql.removeEventListener("change", updateViewportSizeRef.current);
@@ -9812,6 +9808,112 @@ const useModifierKey = ({ exclude = [] } = {}) => {
9812
9808
  return isModifierPressed;
9813
9809
  };
9814
9810
 
9811
+ const resolveRootEl = (refLike) => {
9812
+ if (refLike && typeof refLike === "object" && "current" in refLike) {
9813
+ return refLike.current;
9814
+ }
9815
+ return refLike;
9816
+ };
9817
+ /**
9818
+ * Observes a scroll area within the given root element and toggles CSS
9819
+ * class(es) on a target element when the scroll area overflows vertically.
9820
+ *
9821
+ * Behavior:
9822
+ * - Locates elements via CSS selectors within the root.
9823
+ * - Recomputes on resize, DOM mutations within the scroll area, scroll
9824
+ * events, and image load/error events.
9825
+ * - Cleans up all observers/listeners on unmount or dependency change.
9826
+ *
9827
+ * Edge cases:
9828
+ * - If either element is missing, retries via requestAnimationFrame
9829
+ * (up to 20 attempts) to handle deferred DOM mounting (e.g. portals).
9830
+ * - No DOM mutations occur when `enabled` is false.
9831
+ *
9832
+ * @param rootRef Root element that contains both the scroll area and the target.
9833
+ * @param options Configuration.
9834
+ * @param options.scrollAreaSelector CSS selector to locate the scrollable element.
9835
+ * @param options.targetSelector CSS selector to locate the element that receives the class toggle.
9836
+ * @param options.toggleClass CSS class(es) toggled on the target when the scroll area overflows. Accepts string or string[].
9837
+ * @param options.enabled Whether the hook is active. Defaults to true.
9838
+ */
9839
+ const useOverflowBorder = (rootRef, { scrollAreaSelector, targetSelector, toggleClass, enabled = true }) => {
9840
+ react.useLayoutEffect(() => {
9841
+ if (!enabled)
9842
+ return;
9843
+ let cleanup;
9844
+ let cancelled = false;
9845
+ const classes = Array.isArray(toggleClass)
9846
+ ? toggleClass.filter(Boolean)
9847
+ : String(toggleClass).split(/\s+/).filter(Boolean);
9848
+ let attempts = 0;
9849
+ const MAX_ATTEMPTS = 20;
9850
+ const tryInit = () => {
9851
+ if (cancelled)
9852
+ return;
9853
+ const root = resolveRootEl(rootRef);
9854
+ if (!root) {
9855
+ if (attempts++ < MAX_ATTEMPTS)
9856
+ requestAnimationFrame(tryInit);
9857
+ return;
9858
+ }
9859
+ const scrollAreaEl = root.querySelector(scrollAreaSelector);
9860
+ const targetEl = root.querySelector(targetSelector);
9861
+ if (!scrollAreaEl || !targetEl) {
9862
+ if (attempts++ < MAX_ATTEMPTS)
9863
+ requestAnimationFrame(tryInit);
9864
+ return;
9865
+ }
9866
+ const update = () => {
9867
+ const hasOverflow = scrollAreaEl.scrollHeight > scrollAreaEl.clientHeight;
9868
+ classes.forEach(cls => targetEl.classList.toggle(cls, hasOverflow));
9869
+ };
9870
+ update();
9871
+ const ro = new ResizeObserver(update);
9872
+ ro.observe(scrollAreaEl);
9873
+ const attachImgListeners = (node) => {
9874
+ node.querySelectorAll("img").forEach(img => {
9875
+ if (!img.complete) {
9876
+ const once = () => update();
9877
+ img.addEventListener("load", once, { once: true });
9878
+ img.addEventListener("error", once, { once: true });
9879
+ }
9880
+ });
9881
+ };
9882
+ attachImgListeners(scrollAreaEl);
9883
+ const mo = new MutationObserver(muts => {
9884
+ update();
9885
+ for (const m of muts) {
9886
+ m.addedNodes.forEach(n => {
9887
+ if (n instanceof HTMLImageElement) {
9888
+ if (!n.complete) {
9889
+ const once = () => update();
9890
+ n.addEventListener("load", once, { once: true });
9891
+ n.addEventListener("error", once, { once: true });
9892
+ }
9893
+ }
9894
+ else if (n instanceof HTMLElement) {
9895
+ attachImgListeners(n);
9896
+ }
9897
+ });
9898
+ }
9899
+ });
9900
+ mo.observe(scrollAreaEl, { childList: true, subtree: true, characterData: true });
9901
+ scrollAreaEl.addEventListener("scroll", update, { passive: true });
9902
+ cleanup = () => {
9903
+ ro.disconnect();
9904
+ mo.disconnect();
9905
+ scrollAreaEl.removeEventListener("scroll", update);
9906
+ classes.forEach(cls => targetEl.classList.remove(cls));
9907
+ };
9908
+ };
9909
+ requestAnimationFrame(tryInit);
9910
+ return () => {
9911
+ cancelled = true;
9912
+ cleanup?.();
9913
+ };
9914
+ }, [enabled, toggleClass, scrollAreaSelector, targetSelector, rootRef]);
9915
+ };
9916
+
9815
9917
  /**
9816
9918
  * The usePrevious hook is a useful tool for tracking the previous value of a variable in a functional component. This can be particularly handy in scenarios where it is necessary to compare the current value with the previous one, such as triggering actions or rendering based on changes.
9817
9919
  *
@@ -10329,6 +10431,7 @@ exports.useLocalStorageReducer = useLocalStorageReducer;
10329
10431
  exports.useMeasure = useMeasure;
10330
10432
  exports.useMergeRefs = useMergeRefs;
10331
10433
  exports.useModifierKey = useModifierKey;
10434
+ exports.useOverflowBorder = useOverflowBorder;
10332
10435
  exports.useOverflowItems = useOverflowItems;
10333
10436
  exports.usePopoverContext = usePopoverContext;
10334
10437
  exports.usePrevious = usePrevious;
package/index.esm.js CHANGED
@@ -1339,6 +1339,15 @@ const createBreakpointState = ({ width }) => {
1339
1339
  }), { ...defaultBreakpointState });
1340
1340
  };
1341
1341
 
1342
+ const canUseMatchMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function";
1343
+ const getViewportBreakpointState = () => {
1344
+ if (!canUseMatchMedia())
1345
+ return { ...defaultBreakpointState };
1346
+ return objectEntries(themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1347
+ ...acc,
1348
+ [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1349
+ }), { ...defaultBreakpointState });
1350
+ };
1342
1351
  /**
1343
1352
  * A custom React hook that provides real-time information about the current viewport size.
1344
1353
  * ! Consider using `useContainerBreakpoints` instead, and only use this when you need to actually react to the viewport size, not the container size.
@@ -1362,36 +1371,23 @@ const createBreakpointState = ({ width }) => {
1362
1371
  */
1363
1372
  const useViewportBreakpoints = (options = {}) => {
1364
1373
  const { skip = false } = options;
1365
- const [viewportSize, setViewportSize] = useState(() => {
1366
- const newViewportSize = objectEntries(themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1367
- ...acc,
1368
- [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1369
- }), { ...defaultBreakpointState });
1370
- return newViewportSize;
1371
- });
1374
+ const [viewportSize, setViewportSize] = useState(getViewportBreakpointState);
1372
1375
  const updateViewportSize = useCallback(() => {
1373
- const newViewportSize = objectEntries(themeScreenSizeAsNumber).reduce((acc, [size, minWidth]) => ({
1374
- ...acc,
1375
- [breakpointPropsMap[size]]: window.matchMedia(`(min-width: ${minWidth}px)`).matches,
1376
- }), { ...defaultBreakpointState });
1377
- setViewportSize(newViewportSize);
1376
+ setViewportSize(getViewportBreakpointState());
1378
1377
  }, []);
1379
1378
  const updateViewportSizeRef = useRef(updateViewportSize);
1380
1379
  useEffect(() => {
1381
1380
  updateViewportSizeRef.current = updateViewportSize;
1382
1381
  }, [updateViewportSize]);
1383
1382
  useEffect(() => {
1384
- if (skip) {
1383
+ if (skip || !canUseMatchMedia()) {
1385
1384
  return;
1386
1385
  }
1387
- // Initial check
1388
1386
  updateViewportSizeRef.current();
1389
- // Set up listeners for each breakpoint
1390
1387
  const mediaQueryLists = objectEntries(themeScreenSizeAsNumber).map(([_, minWidth]) => window.matchMedia(`(min-width: ${minWidth}px)`));
1391
1388
  mediaQueryLists.forEach(mql => {
1392
1389
  mql.addEventListener("change", updateViewportSizeRef.current);
1393
1390
  });
1394
- // Cleanup
1395
1391
  return () => {
1396
1392
  mediaQueryLists.forEach(mql => {
1397
1393
  mql.removeEventListener("change", updateViewportSizeRef.current);
@@ -9810,6 +9806,112 @@ const useModifierKey = ({ exclude = [] } = {}) => {
9810
9806
  return isModifierPressed;
9811
9807
  };
9812
9808
 
9809
+ const resolveRootEl = (refLike) => {
9810
+ if (refLike && typeof refLike === "object" && "current" in refLike) {
9811
+ return refLike.current;
9812
+ }
9813
+ return refLike;
9814
+ };
9815
+ /**
9816
+ * Observes a scroll area within the given root element and toggles CSS
9817
+ * class(es) on a target element when the scroll area overflows vertically.
9818
+ *
9819
+ * Behavior:
9820
+ * - Locates elements via CSS selectors within the root.
9821
+ * - Recomputes on resize, DOM mutations within the scroll area, scroll
9822
+ * events, and image load/error events.
9823
+ * - Cleans up all observers/listeners on unmount or dependency change.
9824
+ *
9825
+ * Edge cases:
9826
+ * - If either element is missing, retries via requestAnimationFrame
9827
+ * (up to 20 attempts) to handle deferred DOM mounting (e.g. portals).
9828
+ * - No DOM mutations occur when `enabled` is false.
9829
+ *
9830
+ * @param rootRef Root element that contains both the scroll area and the target.
9831
+ * @param options Configuration.
9832
+ * @param options.scrollAreaSelector CSS selector to locate the scrollable element.
9833
+ * @param options.targetSelector CSS selector to locate the element that receives the class toggle.
9834
+ * @param options.toggleClass CSS class(es) toggled on the target when the scroll area overflows. Accepts string or string[].
9835
+ * @param options.enabled Whether the hook is active. Defaults to true.
9836
+ */
9837
+ const useOverflowBorder = (rootRef, { scrollAreaSelector, targetSelector, toggleClass, enabled = true }) => {
9838
+ useLayoutEffect(() => {
9839
+ if (!enabled)
9840
+ return;
9841
+ let cleanup;
9842
+ let cancelled = false;
9843
+ const classes = Array.isArray(toggleClass)
9844
+ ? toggleClass.filter(Boolean)
9845
+ : String(toggleClass).split(/\s+/).filter(Boolean);
9846
+ let attempts = 0;
9847
+ const MAX_ATTEMPTS = 20;
9848
+ const tryInit = () => {
9849
+ if (cancelled)
9850
+ return;
9851
+ const root = resolveRootEl(rootRef);
9852
+ if (!root) {
9853
+ if (attempts++ < MAX_ATTEMPTS)
9854
+ requestAnimationFrame(tryInit);
9855
+ return;
9856
+ }
9857
+ const scrollAreaEl = root.querySelector(scrollAreaSelector);
9858
+ const targetEl = root.querySelector(targetSelector);
9859
+ if (!scrollAreaEl || !targetEl) {
9860
+ if (attempts++ < MAX_ATTEMPTS)
9861
+ requestAnimationFrame(tryInit);
9862
+ return;
9863
+ }
9864
+ const update = () => {
9865
+ const hasOverflow = scrollAreaEl.scrollHeight > scrollAreaEl.clientHeight;
9866
+ classes.forEach(cls => targetEl.classList.toggle(cls, hasOverflow));
9867
+ };
9868
+ update();
9869
+ const ro = new ResizeObserver(update);
9870
+ ro.observe(scrollAreaEl);
9871
+ const attachImgListeners = (node) => {
9872
+ node.querySelectorAll("img").forEach(img => {
9873
+ if (!img.complete) {
9874
+ const once = () => update();
9875
+ img.addEventListener("load", once, { once: true });
9876
+ img.addEventListener("error", once, { once: true });
9877
+ }
9878
+ });
9879
+ };
9880
+ attachImgListeners(scrollAreaEl);
9881
+ const mo = new MutationObserver(muts => {
9882
+ update();
9883
+ for (const m of muts) {
9884
+ m.addedNodes.forEach(n => {
9885
+ if (n instanceof HTMLImageElement) {
9886
+ if (!n.complete) {
9887
+ const once = () => update();
9888
+ n.addEventListener("load", once, { once: true });
9889
+ n.addEventListener("error", once, { once: true });
9890
+ }
9891
+ }
9892
+ else if (n instanceof HTMLElement) {
9893
+ attachImgListeners(n);
9894
+ }
9895
+ });
9896
+ }
9897
+ });
9898
+ mo.observe(scrollAreaEl, { childList: true, subtree: true, characterData: true });
9899
+ scrollAreaEl.addEventListener("scroll", update, { passive: true });
9900
+ cleanup = () => {
9901
+ ro.disconnect();
9902
+ mo.disconnect();
9903
+ scrollAreaEl.removeEventListener("scroll", update);
9904
+ classes.forEach(cls => targetEl.classList.remove(cls));
9905
+ };
9906
+ };
9907
+ requestAnimationFrame(tryInit);
9908
+ return () => {
9909
+ cancelled = true;
9910
+ cleanup?.();
9911
+ };
9912
+ }, [enabled, toggleClass, scrollAreaSelector, targetSelector, rootRef]);
9913
+ };
9914
+
9813
9915
  /**
9814
9916
  * The usePrevious hook is a useful tool for tracking the previous value of a variable in a functional component. This can be particularly handy in scenarios where it is necessary to compare the current value with the previous one, such as triggering actions or rendering based on changes.
9815
9917
  *
@@ -10174,4 +10276,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
10174
10276
  return useMemo(() => ({ focused }), [focused]);
10175
10277
  };
10176
10278
 
10177
- export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SectionHeader, SegmentedValueBar, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
10279
+ export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SectionHeader, SegmentedValueBar, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowBorder, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.21.8",
3
+ "version": "1.21.12",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -13,10 +13,10 @@
13
13
  "@floating-ui/react": "^0.26.25",
14
14
  "string-ts": "^2.0.0",
15
15
  "tailwind-merge": "^2.0.0",
16
- "@trackunit/ui-design-tokens": "1.11.89",
17
- "@trackunit/css-class-variance-utilities": "1.11.92",
18
- "@trackunit/shared-utils": "1.13.92",
19
- "@trackunit/ui-icons": "1.11.88",
16
+ "@trackunit/ui-design-tokens": "1.11.92",
17
+ "@trackunit/css-class-variance-utilities": "1.11.95",
18
+ "@trackunit/shared-utils": "1.13.95",
19
+ "@trackunit/ui-icons": "1.11.91",
20
20
  "es-toolkit": "^1.39.10",
21
21
  "@tanstack/react-virtual": "3.13.12",
22
22
  "fflate": "^0.8.2",
@@ -0,0 +1,33 @@
1
+ import { type RefObject } from "react";
2
+ type OverflowBorderOptions = {
3
+ readonly scrollAreaSelector: string;
4
+ readonly targetSelector: string;
5
+ readonly toggleClass: string | Array<string>;
6
+ readonly enabled?: boolean;
7
+ };
8
+ type ElementRef = HTMLElement | null;
9
+ type ElementRefLike = ElementRef | RefObject<ElementRef>;
10
+ /**
11
+ * Observes a scroll area within the given root element and toggles CSS
12
+ * class(es) on a target element when the scroll area overflows vertically.
13
+ *
14
+ * Behavior:
15
+ * - Locates elements via CSS selectors within the root.
16
+ * - Recomputes on resize, DOM mutations within the scroll area, scroll
17
+ * events, and image load/error events.
18
+ * - Cleans up all observers/listeners on unmount or dependency change.
19
+ *
20
+ * Edge cases:
21
+ * - If either element is missing, retries via requestAnimationFrame
22
+ * (up to 20 attempts) to handle deferred DOM mounting (e.g. portals).
23
+ * - No DOM mutations occur when `enabled` is false.
24
+ *
25
+ * @param rootRef Root element that contains both the scroll area and the target.
26
+ * @param options Configuration.
27
+ * @param options.scrollAreaSelector CSS selector to locate the scrollable element.
28
+ * @param options.targetSelector CSS selector to locate the element that receives the class toggle.
29
+ * @param options.toggleClass CSS class(es) toggled on the target when the scroll area overflows. Accepts string or string[].
30
+ * @param options.enabled Whether the hook is active. Defaults to true.
31
+ */
32
+ export declare const useOverflowBorder: (rootRef: ElementRefLike, { scrollAreaSelector, targetSelector, toggleClass, enabled }: OverflowBorderOptions) => void;
33
+ export {};
package/src/index.d.ts CHANGED
@@ -130,6 +130,7 @@ export { useKeyboardShortcut } from "./hooks/useKeyboardShortcut/useKeyboardShor
130
130
  export * from "./hooks/useMeasure";
131
131
  export * from "./hooks/useMergeRefs";
132
132
  export * from "./hooks/useModifierKey";
133
+ export * from "./hooks/useOverflowBorder";
133
134
  export * from "./hooks/usePrevious";
134
135
  export * from "./hooks/useRelayPagination";
135
136
  export * from "./hooks/useResize";
@@ -141,3 +142,4 @@ export * from "./hooks/useTimeout";
141
142
  export * from "./hooks/useViewportBreakpoints";
142
143
  export * from "./hooks/useWatch";
143
144
  export * from "./hooks/useWindowActivity";
145
+ export type { CloseReason, DismissOptions, OnBeforeCloseFn, OnCloseFn, OnOpenChangeFn, OnOpenFn, UseOverlayDismissibleProps, UseOverlayDismissibleReturn, } from "./overlay-dismissible/types";
@@ -0,0 +1,52 @@
1
+ import type { RefObject } from "react";
2
+ /**
3
+ * Reasons why an overlay (Modal/Sheet) closed.
4
+ * - `escape-key`: User pressed the ESC key
5
+ * - `outside-press`: User clicked outside / on overlay
6
+ * - `programmatic`: The close() function was called (e.g., from a close button)
7
+ * - `gesture`: User swiped down to close (Sheet only; Modal-in-sheet-mode)
8
+ */
9
+ export type CloseReason = "escape-key" | "outside-press" | "programmatic" | "gesture";
10
+ /**
11
+ * Config for which user gestures trigger close.
12
+ * Shared by Modal and Sheet.
13
+ */
14
+ export type DismissOptions = {
15
+ /** Whether ESC key closes. @default true */
16
+ readonly escapeKey?: boolean;
17
+ /** Whether overlay/outside click closes. @default true */
18
+ readonly outsidePress?: boolean;
19
+ /** Whether swipe-down gesture closes (Sheet only; Modal-in-sheet-mode). @default true */
20
+ readonly gesture?: boolean;
21
+ };
22
+ /** Callback fired when the overlay closes. */
23
+ export type OnCloseFn = (event?: Event, reason?: CloseReason) => void;
24
+ /** Callback fired when the overlay opens. */
25
+ export type OnOpenFn = (event?: Event) => void;
26
+ /** Callback fired when the overlay open state changes. */
27
+ export type OnOpenChangeFn = (open: boolean, event?: Event, reason?: CloseReason) => void;
28
+ /** Callback fired BEFORE the overlay closes. Return false to prevent closing. */
29
+ export type OnBeforeCloseFn = (event?: Event, reason?: CloseReason) => boolean | Promise<boolean>;
30
+ /** Base props shared by useModal and useSheet. */
31
+ export type UseOverlayDismissibleProps = {
32
+ readonly isOpen?: boolean;
33
+ readonly defaultOpen?: boolean;
34
+ readonly onClose?: OnCloseFn;
35
+ readonly onOpen?: OnOpenFn;
36
+ readonly onOpenChange?: OnOpenChangeFn;
37
+ readonly onBeforeClose?: OnBeforeCloseFn;
38
+ /** Config for which user gestures trigger close. Defaults: all true. */
39
+ readonly dismiss?: DismissOptions;
40
+ };
41
+ /** Base return value shared by useModal and useSheet. */
42
+ export type UseOverlayDismissibleReturn = {
43
+ readonly isOpen: boolean;
44
+ readonly open: () => void;
45
+ readonly close: () => void;
46
+ readonly toggle: () => void;
47
+ readonly ref: RefObject<HTMLDivElement | null>;
48
+ };
49
+ /** Resolves DismissOptions with defaults (all true when omitted). */
50
+ export declare const DEFAULT_DISMISS_OPTIONS: Required<DismissOptions>;
51
+ /** Merges caller-supplied DismissOptions with defaults, returning a fully-resolved options object. */
52
+ export declare function resolveDismissOptions(dismiss?: DismissOptions): Required<DismissOptions>;