@diabolic/hangover 0.1.6 → 0.1.7

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/dist/index.cjs.js CHANGED
@@ -737,12 +737,16 @@ function DropdownContent({
737
737
  if (displayMode !== 'scroll') return;
738
738
  const list = contentRef.current;
739
739
  if (!list) return;
740
- function updateBottomSpace() {
740
+ let rafId = null;
741
+ function computeBottomSpace() {
742
+ rafId = null;
741
743
  const sections = list.querySelectorAll('[data-section-for]');
742
744
  const lastSection = sections[sections.length - 1];
743
745
  if (!lastSection) {
744
- list.style.paddingBottom = '';
745
- bottomPadRef.current = 0;
746
+ if (bottomPadRef.current !== 0) {
747
+ list.style.paddingBottom = '';
748
+ bottomPadRef.current = 0;
749
+ }
746
750
  return;
747
751
  }
748
752
 
@@ -755,20 +759,34 @@ function DropdownContent({
755
759
  const listTop = list.getBoundingClientRect().top;
756
760
  const lastTopWithinContent = lastSection.getBoundingClientRect().top - listTop + list.scrollTop;
757
761
  const spaceBelowLastTop = naturalScrollHeight - lastTopWithinContent;
758
- const pad = Math.max(0, available - spaceBelowLastTop);
762
+ const pad = Math.max(0, Math.round(available - spaceBelowLastTop));
763
+
764
+ // Only touch the DOM when the reserved space actually changes by a whole
765
+ // pixel — this ignores sub-pixel/floating jitter from the observer and
766
+ // keeps the value stable.
767
+ if (Math.abs(pad - bottomPadRef.current) < 1) return;
759
768
  bottomPadRef.current = pad;
760
769
  list.style.paddingBottom = `${pad}px`;
761
770
  }
762
- updateBottomSpace();
771
+
772
+ // Coalesce bursts of observer callbacks into a single measurement per frame.
773
+ function scheduleBottomSpace() {
774
+ if (rafId !== null) return;
775
+ rafId = requestAnimationFrame(computeBottomSpace);
776
+ }
777
+ scheduleBottomSpace();
763
778
 
764
779
  // Observe both the container (viewport resize) and every section (group
765
780
  // expand/collapse, late reflows) so the reserved space stays accurate.
766
- const observer = new ResizeObserver(updateBottomSpace);
781
+ const observer = new ResizeObserver(scheduleBottomSpace);
767
782
  observer.observe(list);
768
783
  list.querySelectorAll('[data-section-for]').forEach(section => {
769
784
  observer.observe(section);
770
785
  });
771
- return () => observer.disconnect();
786
+ return () => {
787
+ if (rafId !== null) cancelAnimationFrame(rafId);
788
+ observer.disconnect();
789
+ };
772
790
  }, [displayMode, contentRef, children, searchQuery]);
773
791
  function handleSearch(e) {
774
792
  fireEvent('search', {
package/dist/index.esm.js CHANGED
@@ -733,12 +733,16 @@ function DropdownContent({
733
733
  if (displayMode !== 'scroll') return;
734
734
  const list = contentRef.current;
735
735
  if (!list) return;
736
- function updateBottomSpace() {
736
+ let rafId = null;
737
+ function computeBottomSpace() {
738
+ rafId = null;
737
739
  const sections = list.querySelectorAll('[data-section-for]');
738
740
  const lastSection = sections[sections.length - 1];
739
741
  if (!lastSection) {
740
- list.style.paddingBottom = '';
741
- bottomPadRef.current = 0;
742
+ if (bottomPadRef.current !== 0) {
743
+ list.style.paddingBottom = '';
744
+ bottomPadRef.current = 0;
745
+ }
742
746
  return;
743
747
  }
744
748
 
@@ -751,20 +755,34 @@ function DropdownContent({
751
755
  const listTop = list.getBoundingClientRect().top;
752
756
  const lastTopWithinContent = lastSection.getBoundingClientRect().top - listTop + list.scrollTop;
753
757
  const spaceBelowLastTop = naturalScrollHeight - lastTopWithinContent;
754
- const pad = Math.max(0, available - spaceBelowLastTop);
758
+ const pad = Math.max(0, Math.round(available - spaceBelowLastTop));
759
+
760
+ // Only touch the DOM when the reserved space actually changes by a whole
761
+ // pixel — this ignores sub-pixel/floating jitter from the observer and
762
+ // keeps the value stable.
763
+ if (Math.abs(pad - bottomPadRef.current) < 1) return;
755
764
  bottomPadRef.current = pad;
756
765
  list.style.paddingBottom = `${pad}px`;
757
766
  }
758
- updateBottomSpace();
767
+
768
+ // Coalesce bursts of observer callbacks into a single measurement per frame.
769
+ function scheduleBottomSpace() {
770
+ if (rafId !== null) return;
771
+ rafId = requestAnimationFrame(computeBottomSpace);
772
+ }
773
+ scheduleBottomSpace();
759
774
 
760
775
  // Observe both the container (viewport resize) and every section (group
761
776
  // expand/collapse, late reflows) so the reserved space stays accurate.
762
- const observer = new ResizeObserver(updateBottomSpace);
777
+ const observer = new ResizeObserver(scheduleBottomSpace);
763
778
  observer.observe(list);
764
779
  list.querySelectorAll('[data-section-for]').forEach(section => {
765
780
  observer.observe(section);
766
781
  });
767
- return () => observer.disconnect();
782
+ return () => {
783
+ if (rafId !== null) cancelAnimationFrame(rafId);
784
+ observer.disconnect();
785
+ };
768
786
  }, [displayMode, contentRef, children, searchQuery]);
769
787
  function handleSearch(e) {
770
788
  fireEvent('search', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diabolic/hangover",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "A headless-style, compound React dropdown/field-picker component library",
5
5
  "license": "MIT",
6
6
  "author": "bugrakaan",