@lumx/react 4.0.1-alpha.0 → 4.0.1-alpha.2

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.
Files changed (3) hide show
  1. package/index.js +75 -17
  2. package/index.js.map +1 -1
  3. package/package.json +3 -3
package/index.js CHANGED
@@ -43,7 +43,6 @@ import range from 'lodash/range';
43
43
  import { mdiPlayCircleOutline } from '@lumx/icons/esm/play-circle-outline';
44
44
  import { mdiPauseCircleOutline } from '@lumx/icons/esm/pause-circle-outline';
45
45
  import chunk from 'lodash/chunk';
46
- import debounce$1 from 'lodash/debounce';
47
46
  import ReactDOM from 'react-dom';
48
47
  import take from 'lodash/take';
49
48
  import isObject from 'lodash/isObject';
@@ -11565,9 +11564,43 @@ const SlideshowControls = Object.assign(InternalSlideshowControls, {
11565
11564
  useSlideshowControlsDefaultOptions: DEFAULT_OPTIONS
11566
11565
  });
11567
11566
 
11567
+ /**
11568
+ * Polyfill-like helper for the scrollend event.
11569
+ * Uses native scrollend if available, otherwise falls back to a timeout.
11570
+ *
11571
+ * @param element The element to listen to.
11572
+ * @param callback The callback to execute when scrolling ends.
11573
+ * @param options Options for the listener (timeout and AbortSignal).
11574
+ */
11575
+ function onScrollEnd(element, callback, options = {}) {
11576
+ const {
11577
+ timeout = 150,
11578
+ signal
11579
+ } = options;
11580
+
11581
+ // Native scrollend
11582
+ if ('onscrollend' in window) {
11583
+ element.addEventListener('scrollend', callback, {
11584
+ signal
11585
+ });
11586
+ return;
11587
+ }
11588
+
11589
+ // Fallback for browsers that don't support scrollend
11590
+ let timer;
11591
+ const handleScroll = () => {
11592
+ clearTimeout(timer);
11593
+ timer = setTimeout(callback, timeout);
11594
+ };
11595
+ element.addEventListener('scroll', handleScroll, {
11596
+ signal
11597
+ });
11598
+ signal?.addEventListener('abort', () => clearTimeout(timer));
11599
+ }
11600
+
11568
11601
  /**
11569
11602
  * Hook to handle scroll synchronization for the Slideshow component.
11570
- * It syncs the scroll position with the active index and vice-versa.
11603
+ * It syncs the scroll position with the active index and vice versa.
11571
11604
  */
11572
11605
  const useSlideScroll = ({
11573
11606
  enabled,
@@ -11576,43 +11609,68 @@ const useSlideScroll = ({
11576
11609
  onChange
11577
11610
  }) => {
11578
11611
  const isAutoScrollRef = React__default.useRef(false);
11612
+ const isFromScrollRef = React__default.useRef(false);
11579
11613
 
11580
11614
  // Sync State -> DOM (Programmatic Navigation)
11581
11615
  React__default.useEffect(() => {
11582
- if (!enabled || !wrapperRef.current) return;
11583
11616
  const wrapper = wrapperRef.current;
11617
+ if (!enabled || !wrapper) {
11618
+ return;
11619
+ }
11620
+
11621
+ // Skip if currently scrolling
11622
+ if (isFromScrollRef.current) {
11623
+ return;
11624
+ }
11584
11625
  const targetElement = wrapper.children[activeIndex];
11585
11626
  if (!targetElement) return;
11627
+ let newScrollLeft = targetElement.offsetLeft;
11628
+ if (targetElement.offsetParent !== wrapper) {
11629
+ newScrollLeft -= wrapper.offsetLeft + wrapper.clientLeft;
11630
+ }
11631
+ if (Math.abs(wrapper.scrollLeft - newScrollLeft) < 1) {
11632
+ return;
11633
+ }
11586
11634
  isAutoScrollRef.current = true;
11587
11635
  wrapper.scrollTo({
11588
- left: targetElement.offsetLeft,
11636
+ left: newScrollLeft,
11589
11637
  behavior: !isReducedMotion() ? 'smooth' : undefined
11590
11638
  });
11591
11639
  }, [activeIndex, enabled, wrapperRef]);
11592
11640
 
11593
11641
  // Sync DOM -> State (User Interaction)
11594
11642
  React__default.useEffect(() => {
11595
- if (!enabled || !wrapperRef.current) return undefined;
11596
11643
  const wrapper = wrapperRef.current;
11597
- const handleScroll = debounce$1(() => {
11644
+ if (!enabled || !wrapper) return undefined;
11645
+ const controller = new AbortController();
11646
+ const {
11647
+ signal
11648
+ } = controller;
11649
+ const handleScroll = () => {
11650
+ // Skip if currently scrolling programmatically
11651
+ if (isAutoScrollRef.current) {
11652
+ return;
11653
+ }
11598
11654
  const {
11599
11655
  scrollLeft,
11600
11656
  clientWidth
11601
11657
  } = wrapper;
11602
11658
  const newIndex = Math.round(scrollLeft / clientWidth);
11603
-
11604
- // Skip onChange when scroll triggered in previous useEffect
11605
- if (!isAutoScrollRef.current) {
11606
- onChange?.(newIndex);
11607
- }
11659
+ isFromScrollRef.current = true;
11660
+ onChange?.(newIndex);
11661
+ };
11662
+ const reset = () => {
11608
11663
  isAutoScrollRef.current = false;
11609
- }, 100);
11610
- wrapper.addEventListener('scroll', handleScroll);
11611
- return () => {
11612
- wrapper.removeEventListener('scroll', handleScroll);
11613
- handleScroll.cancel();
11664
+ isFromScrollRef.current = false;
11614
11665
  };
11615
- }, [enabled, onChange, wrapperRef, activeIndex]);
11666
+ wrapper.addEventListener('scroll', handleScroll, {
11667
+ signal
11668
+ });
11669
+ onScrollEnd(wrapper, reset, {
11670
+ signal
11671
+ });
11672
+ return () => controller.abort();
11673
+ }, [enabled, onChange, wrapperRef]);
11616
11674
  };
11617
11675
 
11618
11676
  /**