svelte-dnd-action 0.9.39 → 0.9.41

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.mjs CHANGED
@@ -495,33 +495,13 @@ function calcDistanceBetweenCenters(elA, elB) {
495
495
 
496
496
  /**
497
497
  * @param {HTMLElement} el - the element to check
498
- * @returns {boolean} - true if the element in its entirety is off screen including the scrollable area (the normal dom events look at the mouse rather than the element)
498
+ * @returns {boolean} - true if the element in its entirety is off-screen including the scrollable area (the normal dom events look at the mouse rather than the element)
499
499
  */
500
500
  function isElementOffDocument(el) {
501
501
  var rect = getAbsoluteRect(el);
502
502
  return rect.right < 0 || rect.left > document.documentElement.scrollWidth || rect.bottom < 0 || rect.top > document.documentElement.scrollHeight;
503
503
  }
504
504
 
505
- /**
506
- * If the point is inside the element returns its distances from the sides, otherwise returns null
507
- * @param {Point} point
508
- * @param {HTMLElement} el
509
- * @return {null|{top: number, left: number, bottom: number, right: number}}
510
- */
511
- function calcInnerDistancesBetweenPointAndSidesOfElement(point, el) {
512
- var rect = getAbsoluteRect(el);
513
- if (!isPointInsideRect(point, rect)) {
514
- return null;
515
- }
516
- return {
517
- top: point.y - rect.top,
518
- bottom: rect.bottom - point.y,
519
- left: point.x - rect.left,
520
- // TODO - figure out what is so special about right (why the rect is too big)
521
- right: Math.min(rect.right, document.documentElement.clientWidth) - point.x
522
- };
523
- }
524
-
525
505
  var dzToShadowIndexToRect;
526
506
 
527
507
  /**
@@ -535,17 +515,6 @@ function resetIndexesCache() {
535
515
  }
536
516
  resetIndexesCache();
537
517
 
538
- /**
539
- * Resets the cache that allows for smarter "would be index" resolution for a specific dropzone, should be called after the zone was scrolled
540
- * @param {HTMLElement} dz
541
- */
542
- function resetIndexesCacheForDz(dz) {
543
- printDebug(function () {
544
- return "resetting indexes cache for dz";
545
- });
546
- dzToShadowIndexToRect["delete"](dz);
547
- }
548
-
549
518
  /**
550
519
  * Caches the coordinates of the shadow element when it's in a certain index in a certain dropzone.
551
520
  * Helpful in order to determine "would be index" more effectively
@@ -628,102 +597,6 @@ function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
628
597
  };
629
598
  }
630
599
 
631
- var SCROLL_ZONE_PX = 25;
632
- function makeScroller() {
633
- var scrollingInfo;
634
- function resetScrolling() {
635
- scrollingInfo = {
636
- directionObj: undefined,
637
- stepPx: 0
638
- };
639
- }
640
- resetScrolling();
641
- // directionObj {x: 0|1|-1, y:0|1|-1} - 1 means down in y and right in x
642
- function scrollContainer(containerEl) {
643
- var _scrollingInfo = scrollingInfo,
644
- directionObj = _scrollingInfo.directionObj,
645
- stepPx = _scrollingInfo.stepPx;
646
- if (directionObj) {
647
- containerEl.scrollBy(directionObj.x * stepPx, directionObj.y * stepPx);
648
- window.requestAnimationFrame(function () {
649
- return scrollContainer(containerEl);
650
- });
651
- }
652
- }
653
- function calcScrollStepPx(distancePx) {
654
- return SCROLL_ZONE_PX - distancePx;
655
- }
656
-
657
- /**
658
- * If the pointer is next to the sides of the element to scroll, will trigger scrolling
659
- * Can be called repeatedly with updated pointer and elementToScroll values without issues
660
- * @return {boolean} - true if scrolling was needed
661
- */
662
- function scrollIfNeeded(pointer, elementToScroll) {
663
- if (!elementToScroll) {
664
- return false;
665
- }
666
- var distances = calcInnerDistancesBetweenPointAndSidesOfElement(pointer, elementToScroll);
667
- if (distances === null) {
668
- resetScrolling();
669
- return false;
670
- }
671
- var isAlreadyScrolling = !!scrollingInfo.directionObj;
672
- var scrollingVertically = false,
673
- scrollingHorizontally = false;
674
- // vertical
675
- if (elementToScroll.scrollHeight > elementToScroll.clientHeight) {
676
- if (distances.bottom < SCROLL_ZONE_PX) {
677
- scrollingVertically = true;
678
- scrollingInfo.directionObj = {
679
- x: 0,
680
- y: 1
681
- };
682
- scrollingInfo.stepPx = calcScrollStepPx(distances.bottom);
683
- } else if (distances.top < SCROLL_ZONE_PX) {
684
- scrollingVertically = true;
685
- scrollingInfo.directionObj = {
686
- x: 0,
687
- y: -1
688
- };
689
- scrollingInfo.stepPx = calcScrollStepPx(distances.top);
690
- }
691
- if (!isAlreadyScrolling && scrollingVertically) {
692
- scrollContainer(elementToScroll);
693
- return true;
694
- }
695
- }
696
- // horizontal
697
- if (elementToScroll.scrollWidth > elementToScroll.clientWidth) {
698
- if (distances.right < SCROLL_ZONE_PX) {
699
- scrollingHorizontally = true;
700
- scrollingInfo.directionObj = {
701
- x: 1,
702
- y: 0
703
- };
704
- scrollingInfo.stepPx = calcScrollStepPx(distances.right);
705
- } else if (distances.left < SCROLL_ZONE_PX) {
706
- scrollingHorizontally = true;
707
- scrollingInfo.directionObj = {
708
- x: -1,
709
- y: 0
710
- };
711
- scrollingInfo.stepPx = calcScrollStepPx(distances.left);
712
- }
713
- if (!isAlreadyScrolling && scrollingHorizontally) {
714
- scrollContainer(elementToScroll);
715
- return true;
716
- }
717
- }
718
- resetScrolling();
719
- return false;
720
- }
721
- return {
722
- scrollIfNeeded: scrollIfNeeded,
723
- resetScrolling: resetScrolling
724
- };
725
- }
726
-
727
600
  /**
728
601
  * @param {Object} object
729
602
  * @return {string}
@@ -789,9 +662,6 @@ function areArraysShallowEqualSameOrder(arrA, arrB) {
789
662
 
790
663
  var INTERVAL_MS = 200;
791
664
  var TOLERANCE_PX = 10;
792
- var _makeScroller = makeScroller(),
793
- scrollIfNeeded = _makeScroller.scrollIfNeeded,
794
- resetScrolling = _makeScroller.resetScrolling;
795
665
  var next;
796
666
 
797
667
  /**
@@ -799,9 +669,11 @@ var next;
799
669
  * @param {Set<HTMLElement>} dropZones
800
670
  * @param {HTMLElement} draggedEl
801
671
  * @param {number} [intervalMs = INTERVAL_MS]
672
+ * @param {MultiScroller} multiScroller
802
673
  */
803
674
  function observe(draggedEl, dropZones) {
804
675
  var intervalMs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INTERVAL_MS;
676
+ var multiScroller = arguments.length > 3 ? arguments[3] : undefined;
805
677
  // initialization
806
678
  var lastDropZoneFound;
807
679
  var lastIndexFound;
@@ -817,7 +689,7 @@ function observe(draggedEl, dropZones) {
817
689
  */
818
690
  function andNow() {
819
691
  var currentCenterOfDragged = findCenterOfElement(draggedEl);
820
- var scrolled = scrollIfNeeded(currentCenterOfDragged, lastDropZoneFound);
692
+ var scrolled = multiScroller.multiScrollIfNeeded();
821
693
  // we only want to make a new decision after the element was moved a bit to prevent flickering
822
694
  if (!scrolled && lastCentrePositionOfDragged && Math.abs(lastCentrePositionOfDragged.x - currentCenterOfDragged.x) < TOLERANCE_PX && Math.abs(lastCentrePositionOfDragged.y - currentCenterOfDragged.y) < TOLERANCE_PX) {
823
695
  next = window.setTimeout(andNow, intervalMs);
@@ -838,7 +710,7 @@ function observe(draggedEl, dropZones) {
838
710
  try {
839
711
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
840
712
  var dz = _step.value;
841
- if (scrolled) resetIndexesCacheForDz(lastDropZoneFound);
713
+ if (scrolled) resetIndexesCache();
842
714
  var indexObj = findWouldBeIndex(draggedEl, dz);
843
715
  if (indexObj === null) {
844
716
  // it is not inside
@@ -883,61 +755,225 @@ function unobserve() {
883
755
  return "unobserving";
884
756
  });
885
757
  clearTimeout(next);
886
- resetScrolling();
887
758
  resetIndexesCache();
888
759
  }
889
760
 
890
- var INTERVAL_MS$1 = 300;
891
- var mousePosition;
761
+ var SCROLL_ZONE_PX = 30;
892
762
 
893
763
  /**
894
- * Do not use this! it is visible for testing only until we get over the issue Cypress not triggering the mousemove listeners
895
- * // TODO - make private (remove export)
896
- * @param {{clientX: number, clientY: number}} e
764
+ * Will make a scroller that can scroll any element given to it in any direction
765
+ * @returns {{scrollIfNeeded: function(Point, HTMLElement): boolean, resetScrolling: function(void):void}}
897
766
  */
898
- function updateMousePosition(e) {
899
- var c = e.touches ? e.touches[0] : e;
900
- mousePosition = {
901
- x: c.clientX,
902
- y: c.clientY
903
- };
904
- }
905
- var _makeScroller$1 = makeScroller(),
906
- scrollIfNeeded$1 = _makeScroller$1.scrollIfNeeded,
907
- resetScrolling$1 = _makeScroller$1.resetScrolling;
908
- var next$1;
909
- function loop() {
910
- if (mousePosition) {
911
- var scrolled = scrollIfNeeded$1(mousePosition, document.documentElement);
912
- if (scrolled) resetIndexesCache();
767
+ function makeScroller() {
768
+ var scrollingInfo;
769
+ function resetScrolling() {
770
+ scrollingInfo = {
771
+ directionObj: undefined,
772
+ stepPx: 0
773
+ };
913
774
  }
914
- next$1 = window.setTimeout(loop, INTERVAL_MS$1);
775
+ resetScrolling();
776
+ // directionObj {x: 0|1|-1, y:0|1|-1} - 1 means down in y and right in x
777
+ function scrollContainer(containerEl) {
778
+ var _scrollingInfo = scrollingInfo,
779
+ directionObj = _scrollingInfo.directionObj,
780
+ stepPx = _scrollingInfo.stepPx;
781
+ if (directionObj) {
782
+ containerEl.scrollBy(directionObj.x * stepPx, directionObj.y * stepPx);
783
+ window.requestAnimationFrame(function () {
784
+ return scrollContainer(containerEl);
785
+ });
786
+ }
787
+ }
788
+ function calcScrollStepPx(distancePx) {
789
+ return SCROLL_ZONE_PX - distancePx;
790
+ }
791
+
792
+ /**
793
+ * @param {Point} pointer - the pointer will be used to decide in which direction to scroll
794
+ * @param {HTMLElement} elementToScroll - the scroll container
795
+ * If the pointer is next to the sides of the element to scroll, will trigger scrolling
796
+ * Can be called repeatedly with updated pointer and elementToScroll values without issues
797
+ * @return {boolean} - true if scrolling was needed
798
+ */
799
+ function scrollIfNeeded(pointer, elementToScroll) {
800
+ if (!elementToScroll) {
801
+ return false;
802
+ }
803
+ var distances = calcInnerDistancesBetweenPointAndSidesOfElement(pointer, elementToScroll);
804
+ if (distances === null) {
805
+ resetScrolling();
806
+ return false;
807
+ }
808
+ var isAlreadyScrolling = !!scrollingInfo.directionObj;
809
+ var scrollingVertically = false,
810
+ scrollingHorizontally = false;
811
+ // vertical
812
+ if (elementToScroll.scrollHeight > elementToScroll.clientHeight) {
813
+ if (distances.bottom < SCROLL_ZONE_PX) {
814
+ scrollingVertically = true;
815
+ scrollingInfo.directionObj = {
816
+ x: 0,
817
+ y: 1
818
+ };
819
+ scrollingInfo.stepPx = calcScrollStepPx(distances.bottom);
820
+ } else if (distances.top < SCROLL_ZONE_PX) {
821
+ scrollingVertically = true;
822
+ scrollingInfo.directionObj = {
823
+ x: 0,
824
+ y: -1
825
+ };
826
+ scrollingInfo.stepPx = calcScrollStepPx(distances.top);
827
+ }
828
+ if (!isAlreadyScrolling && scrollingVertically) {
829
+ scrollContainer(elementToScroll);
830
+ return true;
831
+ }
832
+ }
833
+ // horizontal
834
+ if (elementToScroll.scrollWidth > elementToScroll.clientWidth) {
835
+ if (distances.right < SCROLL_ZONE_PX) {
836
+ scrollingHorizontally = true;
837
+ scrollingInfo.directionObj = {
838
+ x: 1,
839
+ y: 0
840
+ };
841
+ scrollingInfo.stepPx = calcScrollStepPx(distances.right);
842
+ } else if (distances.left < SCROLL_ZONE_PX) {
843
+ scrollingHorizontally = true;
844
+ scrollingInfo.directionObj = {
845
+ x: -1,
846
+ y: 0
847
+ };
848
+ scrollingInfo.stepPx = calcScrollStepPx(distances.left);
849
+ }
850
+ if (!isAlreadyScrolling && scrollingHorizontally) {
851
+ scrollContainer(elementToScroll);
852
+ return true;
853
+ }
854
+ }
855
+ resetScrolling();
856
+ return false;
857
+ }
858
+ return {
859
+ scrollIfNeeded: scrollIfNeeded,
860
+ resetScrolling: resetScrolling
861
+ };
915
862
  }
916
863
 
917
864
  /**
918
- * will start watching the mouse pointer and scroll the window if it goes next to the edges
865
+ * If the point is inside the element returns its distances from the sides, otherwise returns null
866
+ * @param {Point} point
867
+ * @param {HTMLElement} el
868
+ * @return {null|{top: number, left: number, bottom: number, right: number}}
919
869
  */
920
- function armWindowScroller() {
921
- printDebug(function () {
922
- return "arming window scroller";
923
- });
924
- window.addEventListener("mousemove", updateMousePosition);
925
- window.addEventListener("touchmove", updateMousePosition);
926
- loop();
870
+ function calcInnerDistancesBetweenPointAndSidesOfElement(point, el) {
871
+ // Even if the scrolling element is small it acts as a scroller for the viewport
872
+ var rect = el === document.scrollingElement ? {
873
+ top: 0,
874
+ bottom: window.innerHeight,
875
+ left: 0,
876
+ right: window.innerWidth
877
+ } : el.getBoundingClientRect();
878
+ if (!isPointInsideRect(point, rect)) {
879
+ return null;
880
+ }
881
+ return {
882
+ top: point.y - rect.top,
883
+ bottom: rect.bottom - point.y,
884
+ left: point.x - rect.left,
885
+ right: rect.right - point.x
886
+ };
927
887
  }
928
888
 
929
889
  /**
930
- * will stop watching the mouse pointer and won't scroll the window anymore
890
+ @typedef {Object} MultiScroller
891
+ @property {function():boolean} multiScrollIfNeeded - call this on every "tick" to scroll containers if needed, returns true if anything was scrolled
892
+ /**
893
+ * Creates a scroller than can scroll any of the provided containers or any of their scrollable parents (including the document's scrolling element)
894
+ * @param {HTMLElement[]} baseElementsForScrolling
895
+ * @param {function():Point} getPointerPosition
896
+ * @return {MultiScroller}
931
897
  */
932
- function disarmWindowScroller() {
898
+ function createMultiScroller() {
899
+ var baseElementsForScrolling = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
900
+ var getPointerPosition = arguments.length > 1 ? arguments[1] : undefined;
933
901
  printDebug(function () {
934
- return "disarming window scroller";
902
+ return "creating multi-scroller";
903
+ });
904
+ var scrollingContainersSet = findRelevantScrollContainers(baseElementsForScrolling);
905
+ var scrollingContainersDeepToShallow = Array.from(scrollingContainersSet).sort(function (dz1, dz2) {
906
+ return getDepth(dz2) - getDepth(dz1);
935
907
  });
936
- window.removeEventListener("mousemove", updateMousePosition);
937
- window.removeEventListener("touchmove", updateMousePosition);
938
- mousePosition = undefined;
939
- window.clearTimeout(next$1);
940
- resetScrolling$1();
908
+ var _makeScroller = makeScroller(),
909
+ scrollIfNeeded = _makeScroller.scrollIfNeeded;
910
+
911
+ /**
912
+ * @return {boolean} - was any container scrolled
913
+ */
914
+ function tick() {
915
+ var mousePosition = getPointerPosition();
916
+ if (!mousePosition || !scrollingContainersDeepToShallow) {
917
+ return false;
918
+ }
919
+ var scrollContainersUnderCursor = scrollingContainersDeepToShallow.filter(function (el) {
920
+ return isPointInsideRect(mousePosition, el.getBoundingClientRect()) || el === document.scrollingElement;
921
+ });
922
+ for (var i = 0; i < scrollContainersUnderCursor.length; i++) {
923
+ var scrolled = scrollIfNeeded(mousePosition, scrollContainersUnderCursor[i]);
924
+ if (scrolled) {
925
+ return true;
926
+ }
927
+ }
928
+ return false;
929
+ }
930
+ return {
931
+ multiScrollIfNeeded: scrollingContainersSet.size > 0 ? tick : function () {
932
+ return false;
933
+ }
934
+ };
935
+ }
936
+
937
+ // internal utils
938
+ function findScrollableParents(element) {
939
+ if (!element) {
940
+ return [];
941
+ }
942
+ var scrollableContainers = [];
943
+ var parent = element;
944
+ while (parent) {
945
+ var _window$getComputedSt = window.getComputedStyle(parent),
946
+ overflow = _window$getComputedSt.overflow;
947
+ if (overflow.split(" ").some(function (o) {
948
+ return o.includes("auto") || o.includes("scroll");
949
+ })) {
950
+ scrollableContainers.push(parent);
951
+ }
952
+ parent = parent.parentElement;
953
+ }
954
+ return scrollableContainers;
955
+ }
956
+ function findRelevantScrollContainers(dropZones) {
957
+ var scrollingContainers = new Set();
958
+ var _iterator = _createForOfIteratorHelper(dropZones),
959
+ _step;
960
+ try {
961
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
962
+ var dz = _step.value;
963
+ findScrollableParents(dz).forEach(function (container) {
964
+ return scrollingContainers.add(container);
965
+ });
966
+ }
967
+ // The scrolling element might have overflow visible and still be scrollable
968
+ } catch (err) {
969
+ _iterator.e(err);
970
+ } finally {
971
+ _iterator.f();
972
+ }
973
+ if (document.scrollingElement.scrollHeight > document.scrollingElement.clientHeight || document.scrollingElement.scrollWidth > document.scrollingElement.clientHeight) {
974
+ scrollingContainers.add(document.scrollingElement);
975
+ }
976
+ return scrollingContainers;
941
977
  }
942
978
 
943
979
  /**
@@ -1286,7 +1322,6 @@ function watchDraggedElement() {
1286
1322
  printDebug(function () {
1287
1323
  return "watching dragged element";
1288
1324
  });
1289
- armWindowScroller();
1290
1325
  var dropZones = typeToDropZones.get(draggedElType);
1291
1326
  var _iterator = _createForOfIteratorHelper(dropZones),
1292
1327
  _step;
@@ -1303,18 +1338,21 @@ function watchDraggedElement() {
1303
1338
  _iterator.f();
1304
1339
  }
1305
1340
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop);
1341
+
1306
1342
  // it is important that we don't have an interval that is faster than the flip duration because it can cause elements to jump bach and forth
1307
1343
  var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(dropZones.keys()).map(function (dz) {
1308
1344
  return dzToConfig.get(dz).dropAnimationDurationMs;
1309
1345
  })));
1310
- var observationIntervalMs = setIntervalMs === 0 ? DISABLED_OBSERVATION_INTERVAL_MS : Math.max(setIntervalMs, MIN_OBSERVATION_INTERVAL_MS); //if setintervalms is 0 it goes to 20, otherwise it is max between it and min observation.
1311
- observe(draggedEl, dropZones, observationIntervalMs * 1.07);
1346
+ var observationIntervalMs = setIntervalMs === 0 ? DISABLED_OBSERVATION_INTERVAL_MS : Math.max(setIntervalMs, MIN_OBSERVATION_INTERVAL_MS); // if setIntervalMs is 0 it goes to 20, otherwise it is max between it and min observation.
1347
+ var multiScroller = createMultiScroller(dropZones, function () {
1348
+ return currentMousePosition;
1349
+ });
1350
+ observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller);
1312
1351
  }
1313
1352
  function unWatchDraggedElement() {
1314
1353
  printDebug(function () {
1315
1354
  return "unwatching dragged element";
1316
1355
  });
1317
- disarmWindowScroller();
1318
1356
  var dropZones = typeToDropZones.get(draggedElType);
1319
1357
  var _iterator2 = _createForOfIteratorHelper(dropZones),
1320
1358
  _step2;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svelte-dnd-action",
3
3
  "description": "*An awesome drag and drop library for Svelte 3 and 4 (not using the browser's built-in dnd, thanks god): Rich animations, nested containers, touch support and more *",
4
- "version": "0.9.39",
4
+ "version": "0.9.41",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
@@ -150,29 +150,9 @@ export function calcDistanceBetweenCenters(elA, elB) {
150
150
 
151
151
  /**
152
152
  * @param {HTMLElement} el - the element to check
153
- * @returns {boolean} - true if the element in its entirety is off screen including the scrollable area (the normal dom events look at the mouse rather than the element)
153
+ * @returns {boolean} - true if the element in its entirety is off-screen including the scrollable area (the normal dom events look at the mouse rather than the element)
154
154
  */
155
155
  export function isElementOffDocument(el) {
156
156
  const rect = getAbsoluteRect(el);
157
157
  return rect.right < 0 || rect.left > document.documentElement.scrollWidth || rect.bottom < 0 || rect.top > document.documentElement.scrollHeight;
158
158
  }
159
-
160
- /**
161
- * If the point is inside the element returns its distances from the sides, otherwise returns null
162
- * @param {Point} point
163
- * @param {HTMLElement} el
164
- * @return {null|{top: number, left: number, bottom: number, right: number}}
165
- */
166
- export function calcInnerDistancesBetweenPointAndSidesOfElement(point, el) {
167
- const rect = getAbsoluteRect(el);
168
- if (!isPointInsideRect(point, rect)) {
169
- return null;
170
- }
171
- return {
172
- top: point.y - rect.top,
173
- bottom: rect.bottom - point.y,
174
- left: point.x - rect.left,
175
- // TODO - figure out what is so special about right (why the rect is too big)
176
- right: Math.min(rect.right, document.documentElement.clientWidth) - point.x
177
- };
178
- }
@@ -12,15 +12,6 @@ export function resetIndexesCache() {
12
12
  }
13
13
  resetIndexesCache();
14
14
 
15
- /**
16
- * Resets the cache that allows for smarter "would be index" resolution for a specific dropzone, should be called after the zone was scrolled
17
- * @param {HTMLElement} dz
18
- */
19
- export function resetIndexesCacheForDz(dz) {
20
- printDebug(() => "resetting indexes cache for dz");
21
- dzToShadowIndexToRect.delete(dz);
22
- }
23
-
24
15
  /**
25
16
  * Caches the coordinates of the shadow element when it's in a certain index in a certain dropzone.
26
17
  * Helpful in order to determine "would be index" more effectively
@@ -0,0 +1,74 @@
1
+ import {makeScroller} from "./scroller";
2
+ import {printDebug} from "../constants";
3
+ import {getDepth} from "./util";
4
+ import {isPointInsideRect} from "./intersection";
5
+
6
+ /**
7
+ @typedef {Object} MultiScroller
8
+ @property {function():boolean} multiScrollIfNeeded - call this on every "tick" to scroll containers if needed, returns true if anything was scrolled
9
+ /**
10
+ * Creates a scroller than can scroll any of the provided containers or any of their scrollable parents (including the document's scrolling element)
11
+ * @param {HTMLElement[]} baseElementsForScrolling
12
+ * @param {function():Point} getPointerPosition
13
+ * @return {MultiScroller}
14
+ */
15
+ export function createMultiScroller(baseElementsForScrolling = [], getPointerPosition) {
16
+ printDebug(() => "creating multi-scroller");
17
+ const scrollingContainersSet = findRelevantScrollContainers(baseElementsForScrolling);
18
+ const scrollingContainersDeepToShallow = Array.from(scrollingContainersSet).sort((dz1, dz2) => getDepth(dz2) - getDepth(dz1));
19
+ const {scrollIfNeeded} = makeScroller();
20
+
21
+ /**
22
+ * @return {boolean} - was any container scrolled
23
+ */
24
+ function tick() {
25
+ const mousePosition = getPointerPosition();
26
+ if (!mousePosition || !scrollingContainersDeepToShallow) {
27
+ return false;
28
+ }
29
+ const scrollContainersUnderCursor = scrollingContainersDeepToShallow.filter(
30
+ el => isPointInsideRect(mousePosition, el.getBoundingClientRect()) || el === document.scrollingElement
31
+ );
32
+ for (let i = 0; i < scrollContainersUnderCursor.length; i++) {
33
+ const scrolled = scrollIfNeeded(mousePosition, scrollContainersUnderCursor[i]);
34
+ if (scrolled) {
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+ return {
41
+ multiScrollIfNeeded: scrollingContainersSet.size > 0 ? tick : () => false
42
+ };
43
+ }
44
+
45
+ // internal utils
46
+ function findScrollableParents(element) {
47
+ if (!element) {
48
+ return [];
49
+ }
50
+ const scrollableContainers = [];
51
+ let parent = element;
52
+ while (parent) {
53
+ const {overflow} = window.getComputedStyle(parent);
54
+ if (overflow.split(" ").some(o => o.includes("auto") || o.includes("scroll"))) {
55
+ scrollableContainers.push(parent);
56
+ }
57
+ parent = parent.parentElement;
58
+ }
59
+ return scrollableContainers;
60
+ }
61
+ function findRelevantScrollContainers(dropZones) {
62
+ const scrollingContainers = new Set();
63
+ for (let dz of dropZones) {
64
+ findScrollableParents(dz).forEach(container => scrollingContainers.add(container));
65
+ }
66
+ // The scrolling element might have overflow visible and still be scrollable
67
+ if (
68
+ document.scrollingElement.scrollHeight > document.scrollingElement.clientHeight ||
69
+ document.scrollingElement.scrollWidth > document.scrollingElement.clientHeight
70
+ ) {
71
+ scrollingContainers.add(document.scrollingElement);
72
+ }
73
+ return scrollingContainers;
74
+ }
@@ -1,4 +1,4 @@
1
- import {findWouldBeIndex, resetIndexesCache, resetIndexesCacheForDz} from "./listUtil";
1
+ import {findWouldBeIndex, resetIndexesCache} from "./listUtil";
2
2
  import {findCenterOfElement, isElementOffDocument} from "./intersection";
3
3
  import {
4
4
  dispatchDraggedElementEnteredContainer,
@@ -7,13 +7,11 @@ import {
7
7
  dispatchDraggedLeftDocument,
8
8
  dispatchDraggedElementIsOverIndex
9
9
  } from "./dispatcher";
10
- import {makeScroller} from "./scroller";
11
10
  import {getDepth} from "./util";
12
11
  import {printDebug} from "../constants";
13
12
 
14
13
  const INTERVAL_MS = 200;
15
14
  const TOLERANCE_PX = 10;
16
- const {scrollIfNeeded, resetScrolling} = makeScroller();
17
15
  let next;
18
16
 
19
17
  /**
@@ -21,8 +19,9 @@ let next;
21
19
  * @param {Set<HTMLElement>} dropZones
22
20
  * @param {HTMLElement} draggedEl
23
21
  * @param {number} [intervalMs = INTERVAL_MS]
22
+ * @param {MultiScroller} multiScroller
24
23
  */
25
- export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS) {
24
+ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS, multiScroller) {
26
25
  // initialization
27
26
  let lastDropZoneFound;
28
27
  let lastIndexFound;
@@ -36,7 +35,7 @@ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS) {
36
35
  */
37
36
  function andNow() {
38
37
  const currentCenterOfDragged = findCenterOfElement(draggedEl);
39
- const scrolled = scrollIfNeeded(currentCenterOfDragged, lastDropZoneFound);
38
+ const scrolled = multiScroller.multiScrollIfNeeded();
40
39
  // we only want to make a new decision after the element was moved a bit to prevent flickering
41
40
  if (
42
41
  !scrolled &&
@@ -57,7 +56,7 @@ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS) {
57
56
  // this is a simple algorithm, potential improvement: first look at lastDropZoneFound
58
57
  let isDraggedInADropZone = false;
59
58
  for (const dz of dropZonesFromDeepToShallow) {
60
- if (scrolled) resetIndexesCacheForDz(lastDropZoneFound);
59
+ if (scrolled) resetIndexesCache();
61
60
  const indexObj = findWouldBeIndex(draggedEl, dz);
62
61
  if (indexObj === null) {
63
62
  // it is not inside
@@ -95,6 +94,5 @@ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS) {
95
94
  export function unobserve() {
96
95
  printDebug(() => "unobserving");
97
96
  clearTimeout(next);
98
- resetScrolling();
99
97
  resetIndexesCache();
100
98
  }