@trackunit/react-components 1.14.29 → 1.14.30

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
@@ -7697,6 +7697,9 @@ const getWindowSize = () => {
7697
7697
 
7698
7698
  /**
7699
7699
  * Blocks scrolling on the document and compensates for scrollbar width to prevent layout shift.
7700
+ * Uses scrollbar-gutter: stable to reserve space for the scrollbar when hiding overflow,
7701
+ * but only if a scrollbar was actually visible before blocking.
7702
+ * This only has an effect with classic scrollbars - overlay scrollbars (macOS default) are unaffected.
7700
7703
  * Returns the original styles so they can be restored later.
7701
7704
  *
7702
7705
  * @returns {OriginalStyles} The original styles before blocking
@@ -7704,6 +7707,18 @@ const getWindowSize = () => {
7704
7707
  const blockDocumentScroll = () => {
7705
7708
  const { body } = document;
7706
7709
  const html = document.documentElement;
7710
+ // Check if there's a visible scrollbar before we hide it
7711
+ // The scrollbar can be on either <html> or <body> depending on CSS setup
7712
+ // (e.g., Storybook sets overflow:hidden on html and overflow:auto on body)
7713
+ // Check html scrollbar: window.innerWidth includes scrollbar, clientWidth doesn't
7714
+ const htmlScrollbarWidth = window.innerWidth - html.clientWidth;
7715
+ // Check body scrollbar: offsetWidth includes border+scrollbar, clientWidth excludes both
7716
+ const bodyStyle = window.getComputedStyle(body);
7717
+ const bodyBorderLeft = parseInt(bodyStyle.borderLeftWidth) || 0;
7718
+ const bodyBorderRight = parseInt(bodyStyle.borderRightWidth) || 0;
7719
+ const bodyScrollbarWidth = body.offsetWidth - bodyBorderLeft - bodyBorderRight - body.clientWidth;
7720
+ // Use whichever scrollbar is present
7721
+ const hasVisibleScrollbar = htmlScrollbarWidth > 0 || bodyScrollbarWidth > 0;
7707
7722
  // Store original values before modifying
7708
7723
  const originalStyles = {
7709
7724
  html: {
@@ -7713,19 +7728,20 @@ const blockDocumentScroll = () => {
7713
7728
  body: {
7714
7729
  position: body.style.position,
7715
7730
  overflow: body.style.overflow,
7716
- paddingRight: body.style.paddingRight,
7731
+ scrollbarGutter: body.style.scrollbarGutter,
7717
7732
  },
7718
7733
  };
7719
- // Calculate scrollbar width to prevent layout shift when hiding scrollbar
7720
- const scrollBarWidth = window.innerWidth - (html.clientWidth || 0);
7721
- const bodyPaddingRight = parseInt(window.getComputedStyle(body).getPropertyValue("padding-right")) || 0;
7722
7734
  // Block scroll on both html and body for cross-browser compatibility
7723
7735
  html.style.position = "relative";
7724
7736
  html.style.overflow = "hidden";
7725
7737
  body.style.position = "relative";
7726
7738
  body.style.overflow = "hidden";
7727
- // Preserve existing padding and add scrollbar width to prevent layout shift
7728
- body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;
7739
+ // Apply scrollbar-gutter on body (where the scrollbar typically lives when content scrolls)
7740
+ // This reserves space for the scrollbar even when overflow is hidden, preventing layout shift.
7741
+ // Only has effect with classic scrollbars - overlay scrollbars (macOS default) are unaffected.
7742
+ if (hasVisibleScrollbar) {
7743
+ body.style.scrollbarGutter = "stable";
7744
+ }
7729
7745
  return originalStyles;
7730
7746
  };
7731
7747
  /**
@@ -7744,23 +7760,40 @@ const restoreDocumentScroll = (originalStyles) => {
7744
7760
  if (originalStyles.body) {
7745
7761
  body.style.position = originalStyles.body.position;
7746
7762
  body.style.overflow = originalStyles.body.overflow;
7747
- body.style.paddingRight = originalStyles.body.paddingRight;
7763
+ body.style.scrollbarGutter = originalStyles.body.scrollbarGutter;
7748
7764
  }
7749
7765
  };
7750
7766
  /**
7751
7767
  * Blocks scrolling on a custom container element.
7768
+ * Uses scrollbar-gutter: stable to reserve space for the scrollbar when hiding overflow,
7769
+ * but only if a scrollbar was actually visible before blocking.
7770
+ * This only has an effect with classic scrollbars - overlay scrollbars (macOS default) are unaffected.
7752
7771
  * Returns the original styles so they can be restored later.
7753
7772
  *
7754
7773
  * @param container - The container element to block scroll on
7755
7774
  * @returns {OriginalStyles} The original styles before blocking
7756
7775
  */
7757
7776
  const blockContainerScroll = (container) => {
7777
+ // Check if there's a visible scrollbar before we hide it
7778
+ // offsetWidth includes border + scrollbar, clientWidth excludes both
7779
+ // We need to subtract borders to isolate the scrollbar width
7780
+ const style = window.getComputedStyle(container);
7781
+ const borderLeft = parseInt(style.borderLeftWidth) || 0;
7782
+ const borderRight = parseInt(style.borderRightWidth) || 0;
7783
+ const scrollbarWidth = container.offsetWidth - borderLeft - borderRight - container.clientWidth;
7784
+ const hasVisibleScrollbar = scrollbarWidth > 0;
7758
7785
  const originalStyles = {
7759
7786
  container: {
7760
7787
  overflow: container.style.overflow,
7788
+ scrollbarGutter: container.style.scrollbarGutter,
7761
7789
  },
7762
7790
  };
7763
7791
  container.style.overflow = "hidden";
7792
+ // Only add scrollbar-gutter if there was a visible scrollbar to preserve space for
7793
+ // This prevents adding unnecessary space when content doesn't overflow
7794
+ if (hasVisibleScrollbar) {
7795
+ container.style.scrollbarGutter = "stable";
7796
+ }
7764
7797
  return originalStyles;
7765
7798
  };
7766
7799
  /**
@@ -7772,6 +7805,7 @@ const blockContainerScroll = (container) => {
7772
7805
  const restoreContainerScroll = (container, originalStyles) => {
7773
7806
  if (originalStyles.container) {
7774
7807
  container.style.overflow = originalStyles.container.overflow;
7808
+ container.style.scrollbarGutter = originalStyles.container.scrollbarGutter;
7775
7809
  }
7776
7810
  };
7777
7811
  /**
package/index.esm.js CHANGED
@@ -7695,6 +7695,9 @@ const getWindowSize = () => {
7695
7695
 
7696
7696
  /**
7697
7697
  * Blocks scrolling on the document and compensates for scrollbar width to prevent layout shift.
7698
+ * Uses scrollbar-gutter: stable to reserve space for the scrollbar when hiding overflow,
7699
+ * but only if a scrollbar was actually visible before blocking.
7700
+ * This only has an effect with classic scrollbars - overlay scrollbars (macOS default) are unaffected.
7698
7701
  * Returns the original styles so they can be restored later.
7699
7702
  *
7700
7703
  * @returns {OriginalStyles} The original styles before blocking
@@ -7702,6 +7705,18 @@ const getWindowSize = () => {
7702
7705
  const blockDocumentScroll = () => {
7703
7706
  const { body } = document;
7704
7707
  const html = document.documentElement;
7708
+ // Check if there's a visible scrollbar before we hide it
7709
+ // The scrollbar can be on either <html> or <body> depending on CSS setup
7710
+ // (e.g., Storybook sets overflow:hidden on html and overflow:auto on body)
7711
+ // Check html scrollbar: window.innerWidth includes scrollbar, clientWidth doesn't
7712
+ const htmlScrollbarWidth = window.innerWidth - html.clientWidth;
7713
+ // Check body scrollbar: offsetWidth includes border+scrollbar, clientWidth excludes both
7714
+ const bodyStyle = window.getComputedStyle(body);
7715
+ const bodyBorderLeft = parseInt(bodyStyle.borderLeftWidth) || 0;
7716
+ const bodyBorderRight = parseInt(bodyStyle.borderRightWidth) || 0;
7717
+ const bodyScrollbarWidth = body.offsetWidth - bodyBorderLeft - bodyBorderRight - body.clientWidth;
7718
+ // Use whichever scrollbar is present
7719
+ const hasVisibleScrollbar = htmlScrollbarWidth > 0 || bodyScrollbarWidth > 0;
7705
7720
  // Store original values before modifying
7706
7721
  const originalStyles = {
7707
7722
  html: {
@@ -7711,19 +7726,20 @@ const blockDocumentScroll = () => {
7711
7726
  body: {
7712
7727
  position: body.style.position,
7713
7728
  overflow: body.style.overflow,
7714
- paddingRight: body.style.paddingRight,
7729
+ scrollbarGutter: body.style.scrollbarGutter,
7715
7730
  },
7716
7731
  };
7717
- // Calculate scrollbar width to prevent layout shift when hiding scrollbar
7718
- const scrollBarWidth = window.innerWidth - (html.clientWidth || 0);
7719
- const bodyPaddingRight = parseInt(window.getComputedStyle(body).getPropertyValue("padding-right")) || 0;
7720
7732
  // Block scroll on both html and body for cross-browser compatibility
7721
7733
  html.style.position = "relative";
7722
7734
  html.style.overflow = "hidden";
7723
7735
  body.style.position = "relative";
7724
7736
  body.style.overflow = "hidden";
7725
- // Preserve existing padding and add scrollbar width to prevent layout shift
7726
- body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;
7737
+ // Apply scrollbar-gutter on body (where the scrollbar typically lives when content scrolls)
7738
+ // This reserves space for the scrollbar even when overflow is hidden, preventing layout shift.
7739
+ // Only has effect with classic scrollbars - overlay scrollbars (macOS default) are unaffected.
7740
+ if (hasVisibleScrollbar) {
7741
+ body.style.scrollbarGutter = "stable";
7742
+ }
7727
7743
  return originalStyles;
7728
7744
  };
7729
7745
  /**
@@ -7742,23 +7758,40 @@ const restoreDocumentScroll = (originalStyles) => {
7742
7758
  if (originalStyles.body) {
7743
7759
  body.style.position = originalStyles.body.position;
7744
7760
  body.style.overflow = originalStyles.body.overflow;
7745
- body.style.paddingRight = originalStyles.body.paddingRight;
7761
+ body.style.scrollbarGutter = originalStyles.body.scrollbarGutter;
7746
7762
  }
7747
7763
  };
7748
7764
  /**
7749
7765
  * Blocks scrolling on a custom container element.
7766
+ * Uses scrollbar-gutter: stable to reserve space for the scrollbar when hiding overflow,
7767
+ * but only if a scrollbar was actually visible before blocking.
7768
+ * This only has an effect with classic scrollbars - overlay scrollbars (macOS default) are unaffected.
7750
7769
  * Returns the original styles so they can be restored later.
7751
7770
  *
7752
7771
  * @param container - The container element to block scroll on
7753
7772
  * @returns {OriginalStyles} The original styles before blocking
7754
7773
  */
7755
7774
  const blockContainerScroll = (container) => {
7775
+ // Check if there's a visible scrollbar before we hide it
7776
+ // offsetWidth includes border + scrollbar, clientWidth excludes both
7777
+ // We need to subtract borders to isolate the scrollbar width
7778
+ const style = window.getComputedStyle(container);
7779
+ const borderLeft = parseInt(style.borderLeftWidth) || 0;
7780
+ const borderRight = parseInt(style.borderRightWidth) || 0;
7781
+ const scrollbarWidth = container.offsetWidth - borderLeft - borderRight - container.clientWidth;
7782
+ const hasVisibleScrollbar = scrollbarWidth > 0;
7756
7783
  const originalStyles = {
7757
7784
  container: {
7758
7785
  overflow: container.style.overflow,
7786
+ scrollbarGutter: container.style.scrollbarGutter,
7759
7787
  },
7760
7788
  };
7761
7789
  container.style.overflow = "hidden";
7790
+ // Only add scrollbar-gutter if there was a visible scrollbar to preserve space for
7791
+ // This prevents adding unnecessary space when content doesn't overflow
7792
+ if (hasVisibleScrollbar) {
7793
+ container.style.scrollbarGutter = "stable";
7794
+ }
7762
7795
  return originalStyles;
7763
7796
  };
7764
7797
  /**
@@ -7770,6 +7803,7 @@ const blockContainerScroll = (container) => {
7770
7803
  const restoreContainerScroll = (container, originalStyles) => {
7771
7804
  if (originalStyles.container) {
7772
7805
  container.style.overflow = originalStyles.container.overflow;
7806
+ container.style.scrollbarGutter = originalStyles.container.scrollbarGutter;
7773
7807
  }
7774
7808
  };
7775
7809
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.14.29",
3
+ "version": "1.14.30",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {