@trackunit/react-components 1.10.33 → 1.10.40

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
@@ -1726,6 +1726,150 @@ const getWindowSize = () => {
1726
1726
  }
1727
1727
  };
1728
1728
 
1729
+ /**
1730
+ * Blocks scrolling on the document and compensates for scrollbar width to prevent layout shift.
1731
+ * Returns the original styles so they can be restored later.
1732
+ *
1733
+ * @returns {OriginalStyles} The original styles before blocking
1734
+ */
1735
+ const blockDocumentScroll = () => {
1736
+ const { body } = document;
1737
+ const html = document.documentElement;
1738
+ // Store original values before modifying
1739
+ const originalStyles = {
1740
+ html: {
1741
+ position: html.style.position,
1742
+ overflow: html.style.overflow,
1743
+ },
1744
+ body: {
1745
+ position: body.style.position,
1746
+ overflow: body.style.overflow,
1747
+ paddingRight: body.style.paddingRight,
1748
+ },
1749
+ };
1750
+ // Calculate scrollbar width to prevent layout shift when hiding scrollbar
1751
+ const scrollBarWidth = window.innerWidth - (html.clientWidth || 0);
1752
+ const bodyPaddingRight = parseInt(window.getComputedStyle(body).getPropertyValue("padding-right")) || 0;
1753
+ // Block scroll on both html and body for cross-browser compatibility
1754
+ html.style.position = "relative";
1755
+ html.style.overflow = "hidden";
1756
+ body.style.position = "relative";
1757
+ body.style.overflow = "hidden";
1758
+ // Preserve existing padding and add scrollbar width to prevent layout shift
1759
+ body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;
1760
+ return originalStyles;
1761
+ };
1762
+ /**
1763
+ * Restores document scrolling by restoring the provided original styles.
1764
+ *
1765
+ * @param originalStyles - The original styles to restore
1766
+ */
1767
+ const restoreDocumentScroll = (originalStyles) => {
1768
+ const { body } = document;
1769
+ const html = document.documentElement;
1770
+ // Restore original values instead of just clearing
1771
+ if (originalStyles.html) {
1772
+ html.style.position = originalStyles.html.position;
1773
+ html.style.overflow = originalStyles.html.overflow;
1774
+ }
1775
+ if (originalStyles.body) {
1776
+ body.style.position = originalStyles.body.position;
1777
+ body.style.overflow = originalStyles.body.overflow;
1778
+ body.style.paddingRight = originalStyles.body.paddingRight;
1779
+ }
1780
+ };
1781
+ /**
1782
+ * Blocks scrolling on a custom container element.
1783
+ * Returns the original styles so they can be restored later.
1784
+ *
1785
+ * @param container - The container element to block scroll on
1786
+ * @returns {OriginalStyles} The original styles before blocking
1787
+ */
1788
+ const blockContainerScroll = (container) => {
1789
+ const originalStyles = {
1790
+ container: {
1791
+ overflow: container.style.overflow,
1792
+ },
1793
+ };
1794
+ container.style.overflow = "hidden";
1795
+ return originalStyles;
1796
+ };
1797
+ /**
1798
+ * Restores container scrolling by restoring the provided original styles.
1799
+ *
1800
+ * @param container - The container element to restore scroll on
1801
+ * @param originalStyles - The original styles to restore
1802
+ */
1803
+ const restoreContainerScroll = (container, originalStyles) => {
1804
+ if (originalStyles.container) {
1805
+ container.style.overflow = originalStyles.container.overflow;
1806
+ }
1807
+ };
1808
+ /**
1809
+ * Hook that provides scroll blocking functionality.
1810
+ * This properly accounts for existing body padding to prevent layout shifts.
1811
+ *
1812
+ * Each instance gets its own stored original styles via refs, preventing
1813
+ * conflicts when multiple components are used on the same page. The hook also ensures
1814
+ * cleanup on unmount if scroll is still blocked.
1815
+ *
1816
+ * @param scrollContainer - The DOM element whose scroll should be blocked. Defaults to document.body.
1817
+ * @returns {{blockScroll: () => void, restoreScroll: () => void}} Object containing blockScroll and restoreScroll functions
1818
+ */
1819
+ const useScrollBlock = (scrollContainer = typeof document !== "undefined" ? document.body : null // default to document.body if no scroll container is provided
1820
+ ) => {
1821
+ const originalStylesRef = react.useRef(null);
1822
+ const isBlockedRef = react.useRef(false);
1823
+ /**
1824
+ * Blocks scrolling and stores original styles for restoration.
1825
+ * Blocks the document (body/html) if scroll container is the document body,
1826
+ * or blocks the custom container if a custom container is provided.
1827
+ */
1828
+ const blockScroll = react.useCallback(() => {
1829
+ if (isBlockedRef.current || !scrollContainer) {
1830
+ return; // Already blocked or no scroll container
1831
+ }
1832
+ if (scrollContainer === document.body) {
1833
+ originalStylesRef.current = blockDocumentScroll();
1834
+ }
1835
+ else {
1836
+ originalStylesRef.current = blockContainerScroll(scrollContainer);
1837
+ }
1838
+ isBlockedRef.current = true;
1839
+ }, [scrollContainer]);
1840
+ /**
1841
+ * Restores scrolling using the previously stored original styles.
1842
+ */
1843
+ const restoreScroll = react.useCallback(() => {
1844
+ if (!isBlockedRef.current || !scrollContainer || !originalStylesRef.current) {
1845
+ return;
1846
+ }
1847
+ if (scrollContainer === document.body) {
1848
+ restoreDocumentScroll(originalStylesRef.current);
1849
+ }
1850
+ else {
1851
+ restoreContainerScroll(scrollContainer, originalStylesRef.current);
1852
+ }
1853
+ originalStylesRef.current = null;
1854
+ isBlockedRef.current = false;
1855
+ }, [scrollContainer]);
1856
+ // Cleanup: restore scroll if component unmounts while scroll is blocked
1857
+ react.useEffect(() => {
1858
+ return () => {
1859
+ if (isBlockedRef.current && scrollContainer && originalStylesRef.current) {
1860
+ if (scrollContainer === document.body) {
1861
+ restoreDocumentScroll(originalStylesRef.current);
1862
+ }
1863
+ else {
1864
+ restoreContainerScroll(scrollContainer, originalStylesRef.current);
1865
+ }
1866
+ isBlockedRef.current = false;
1867
+ }
1868
+ };
1869
+ }, [scrollContainer]);
1870
+ return react.useMemo(() => ({ blockScroll, restoreScroll }), [blockScroll, restoreScroll]);
1871
+ };
1872
+
1729
1873
  const SCROLL_DEBOUNCE_TIME = 50;
1730
1874
  /**
1731
1875
  * Hook for detecting scroll values in horizontal or vertical direction.
@@ -5476,6 +5620,7 @@ exports.usePopoverContext = usePopoverContext;
5476
5620
  exports.usePrompt = usePrompt;
5477
5621
  exports.useRelayPagination = useRelayPagination;
5478
5622
  exports.useResize = useResize;
5623
+ exports.useScrollBlock = useScrollBlock;
5479
5624
  exports.useScrollDetection = useScrollDetection;
5480
5625
  exports.useSelfUpdatingRef = useSelfUpdatingRef;
5481
5626
  exports.useTimeout = useTimeout;
package/index.esm.js CHANGED
@@ -1724,6 +1724,150 @@ const getWindowSize = () => {
1724
1724
  }
1725
1725
  };
1726
1726
 
1727
+ /**
1728
+ * Blocks scrolling on the document and compensates for scrollbar width to prevent layout shift.
1729
+ * Returns the original styles so they can be restored later.
1730
+ *
1731
+ * @returns {OriginalStyles} The original styles before blocking
1732
+ */
1733
+ const blockDocumentScroll = () => {
1734
+ const { body } = document;
1735
+ const html = document.documentElement;
1736
+ // Store original values before modifying
1737
+ const originalStyles = {
1738
+ html: {
1739
+ position: html.style.position,
1740
+ overflow: html.style.overflow,
1741
+ },
1742
+ body: {
1743
+ position: body.style.position,
1744
+ overflow: body.style.overflow,
1745
+ paddingRight: body.style.paddingRight,
1746
+ },
1747
+ };
1748
+ // Calculate scrollbar width to prevent layout shift when hiding scrollbar
1749
+ const scrollBarWidth = window.innerWidth - (html.clientWidth || 0);
1750
+ const bodyPaddingRight = parseInt(window.getComputedStyle(body).getPropertyValue("padding-right")) || 0;
1751
+ // Block scroll on both html and body for cross-browser compatibility
1752
+ html.style.position = "relative";
1753
+ html.style.overflow = "hidden";
1754
+ body.style.position = "relative";
1755
+ body.style.overflow = "hidden";
1756
+ // Preserve existing padding and add scrollbar width to prevent layout shift
1757
+ body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;
1758
+ return originalStyles;
1759
+ };
1760
+ /**
1761
+ * Restores document scrolling by restoring the provided original styles.
1762
+ *
1763
+ * @param originalStyles - The original styles to restore
1764
+ */
1765
+ const restoreDocumentScroll = (originalStyles) => {
1766
+ const { body } = document;
1767
+ const html = document.documentElement;
1768
+ // Restore original values instead of just clearing
1769
+ if (originalStyles.html) {
1770
+ html.style.position = originalStyles.html.position;
1771
+ html.style.overflow = originalStyles.html.overflow;
1772
+ }
1773
+ if (originalStyles.body) {
1774
+ body.style.position = originalStyles.body.position;
1775
+ body.style.overflow = originalStyles.body.overflow;
1776
+ body.style.paddingRight = originalStyles.body.paddingRight;
1777
+ }
1778
+ };
1779
+ /**
1780
+ * Blocks scrolling on a custom container element.
1781
+ * Returns the original styles so they can be restored later.
1782
+ *
1783
+ * @param container - The container element to block scroll on
1784
+ * @returns {OriginalStyles} The original styles before blocking
1785
+ */
1786
+ const blockContainerScroll = (container) => {
1787
+ const originalStyles = {
1788
+ container: {
1789
+ overflow: container.style.overflow,
1790
+ },
1791
+ };
1792
+ container.style.overflow = "hidden";
1793
+ return originalStyles;
1794
+ };
1795
+ /**
1796
+ * Restores container scrolling by restoring the provided original styles.
1797
+ *
1798
+ * @param container - The container element to restore scroll on
1799
+ * @param originalStyles - The original styles to restore
1800
+ */
1801
+ const restoreContainerScroll = (container, originalStyles) => {
1802
+ if (originalStyles.container) {
1803
+ container.style.overflow = originalStyles.container.overflow;
1804
+ }
1805
+ };
1806
+ /**
1807
+ * Hook that provides scroll blocking functionality.
1808
+ * This properly accounts for existing body padding to prevent layout shifts.
1809
+ *
1810
+ * Each instance gets its own stored original styles via refs, preventing
1811
+ * conflicts when multiple components are used on the same page. The hook also ensures
1812
+ * cleanup on unmount if scroll is still blocked.
1813
+ *
1814
+ * @param scrollContainer - The DOM element whose scroll should be blocked. Defaults to document.body.
1815
+ * @returns {{blockScroll: () => void, restoreScroll: () => void}} Object containing blockScroll and restoreScroll functions
1816
+ */
1817
+ const useScrollBlock = (scrollContainer = typeof document !== "undefined" ? document.body : null // default to document.body if no scroll container is provided
1818
+ ) => {
1819
+ const originalStylesRef = useRef(null);
1820
+ const isBlockedRef = useRef(false);
1821
+ /**
1822
+ * Blocks scrolling and stores original styles for restoration.
1823
+ * Blocks the document (body/html) if scroll container is the document body,
1824
+ * or blocks the custom container if a custom container is provided.
1825
+ */
1826
+ const blockScroll = useCallback(() => {
1827
+ if (isBlockedRef.current || !scrollContainer) {
1828
+ return; // Already blocked or no scroll container
1829
+ }
1830
+ if (scrollContainer === document.body) {
1831
+ originalStylesRef.current = blockDocumentScroll();
1832
+ }
1833
+ else {
1834
+ originalStylesRef.current = blockContainerScroll(scrollContainer);
1835
+ }
1836
+ isBlockedRef.current = true;
1837
+ }, [scrollContainer]);
1838
+ /**
1839
+ * Restores scrolling using the previously stored original styles.
1840
+ */
1841
+ const restoreScroll = useCallback(() => {
1842
+ if (!isBlockedRef.current || !scrollContainer || !originalStylesRef.current) {
1843
+ return;
1844
+ }
1845
+ if (scrollContainer === document.body) {
1846
+ restoreDocumentScroll(originalStylesRef.current);
1847
+ }
1848
+ else {
1849
+ restoreContainerScroll(scrollContainer, originalStylesRef.current);
1850
+ }
1851
+ originalStylesRef.current = null;
1852
+ isBlockedRef.current = false;
1853
+ }, [scrollContainer]);
1854
+ // Cleanup: restore scroll if component unmounts while scroll is blocked
1855
+ useEffect(() => {
1856
+ return () => {
1857
+ if (isBlockedRef.current && scrollContainer && originalStylesRef.current) {
1858
+ if (scrollContainer === document.body) {
1859
+ restoreDocumentScroll(originalStylesRef.current);
1860
+ }
1861
+ else {
1862
+ restoreContainerScroll(scrollContainer, originalStylesRef.current);
1863
+ }
1864
+ isBlockedRef.current = false;
1865
+ }
1866
+ };
1867
+ }, [scrollContainer]);
1868
+ return useMemo(() => ({ blockScroll, restoreScroll }), [blockScroll, restoreScroll]);
1869
+ };
1870
+
1727
1871
  const SCROLL_DEBOUNCE_TIME = 50;
1728
1872
  /**
1729
1873
  * Hook for detecting scroll values in horizontal or vertical direction.
@@ -5343,4 +5487,4 @@ const cvaClickable = cvaMerge([
5343
5487
  },
5344
5488
  });
5345
5489
 
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 };
5490
+ 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, useScrollBlock, 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.33",
3
+ "version": "1.10.40",
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.64",
20
- "@trackunit/css-class-variance-utilities": "1.7.64",
21
- "@trackunit/shared-utils": "1.9.64",
22
- "@trackunit/ui-icons": "1.7.65",
23
- "@trackunit/react-test-setup": "1.4.64",
19
+ "@trackunit/ui-design-tokens": "1.7.70",
20
+ "@trackunit/css-class-variance-utilities": "1.7.70",
21
+ "@trackunit/shared-utils": "1.9.70",
22
+ "@trackunit/ui-icons": "1.7.71",
23
+ "@trackunit/react-test-setup": "1.4.70",
24
24
  "@tanstack/react-router": "1.114.29",
25
25
  "es-toolkit": "^1.39.10",
26
26
  "@tanstack/react-virtual": "3.13.12"
@@ -17,6 +17,7 @@ export type { Geometry } from "./useMeasure/useMeasureShared";
17
17
  export * from "./useModifierKey";
18
18
  export * from "./useRelayPagination";
19
19
  export * from "./useResize";
20
+ export * from "./useScrollBlock";
20
21
  export * from "./useScrollDetection";
21
22
  export * from "./useSelfUpdatingRef";
22
23
  export * from "./useTimeout";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Hook that provides scroll blocking functionality.
3
+ * This properly accounts for existing body padding to prevent layout shifts.
4
+ *
5
+ * Each instance gets its own stored original styles via refs, preventing
6
+ * conflicts when multiple components are used on the same page. The hook also ensures
7
+ * cleanup on unmount if scroll is still blocked.
8
+ *
9
+ * @param scrollContainer - The DOM element whose scroll should be blocked. Defaults to document.body.
10
+ * @returns {{blockScroll: () => void, restoreScroll: () => void}} Object containing blockScroll and restoreScroll functions
11
+ */
12
+ export declare const useScrollBlock: (scrollContainer?: HTMLElement | null) => {
13
+ blockScroll: () => void;
14
+ restoreScroll: () => void;
15
+ };