svelte-dnd-action 0.9.40 → 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/README.md CHANGED
@@ -22,7 +22,7 @@ It is being actively maintained.
22
22
  - Rich animations (can be opted out of)
23
23
  - Touch support
24
24
  - Define what can be dropped where (dnd-zones optionally have a "type")
25
- - Scroll dnd-zones and/or the window horizontally or vertically by placing the dragged element next to the edge
25
+ - Scroll dnd-zones (of the relevant "type"), parent containers that contains them and/or the window horizontally or vertically by placing the dragged element next to the edge
26
26
  - Supports advanced use-cases such as various flavours of copy-on-drag and custom drag handles (see examples below)
27
27
  - Performant and small footprint (no external dependencies, no fluff code)
28
28
  - Fully accessible (beta) - keyboard support, aria attributes and assistive instructions for screen readers
@@ -82,7 +82,7 @@ npm install --save-dev svelte-dnd-action
82
82
  width: 50%;
83
83
  padding: 0.3em;
84
84
  border: 1px solid black;
85
- /* this will allow the dragged element to scroll the list */
85
+ /* this will allow the dragged element to scroll the list although starting in version 0.9.41 the lib would detect any scrollable parent*/
86
86
  overflow: scroll;
87
87
  height: 200px;
88
88
  }
@@ -207,7 +207,6 @@ If you want to implement your own custom screen-reader alerts, roles and instruc
207
207
  - Any data that should "survive" when the items are dragged around and dropped should be included in the `items` array that is passed in.
208
208
  - The host component must refresh the items that are passed in to the custom-action when receiving consider and finalize events (do not omit any handler).
209
209
  - FYI, the library assumes it is okay to add a temporary item to the items list in any of the dnd-zones while an element is dragged around.
210
- - If you want dragged items to be able to scroll the container, make sure the scroll-container (the element with overflow:scroll) is the dnd-zone (the element decorated with this custom action)
211
210
  - Svelte's built-in transitions might not play nice with this library. Luckily, it is an easy issue to work around. There are examples above.
212
211
 
213
212
  ### Overriding the item id key name
package/dist/index.js CHANGED
@@ -501,33 +501,13 @@
501
501
 
502
502
  /**
503
503
  * @param {HTMLElement} el - the element to check
504
- * @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)
504
+ * @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)
505
505
  */
506
506
  function isElementOffDocument(el) {
507
507
  var rect = getAbsoluteRect(el);
508
508
  return rect.right < 0 || rect.left > document.documentElement.scrollWidth || rect.bottom < 0 || rect.top > document.documentElement.scrollHeight;
509
509
  }
510
510
 
511
- /**
512
- * If the point is inside the element returns its distances from the sides, otherwise returns null
513
- * @param {Point} point
514
- * @param {HTMLElement} el
515
- * @return {null|{top: number, left: number, bottom: number, right: number}}
516
- */
517
- function calcInnerDistancesBetweenPointAndSidesOfElement(point, el) {
518
- var rect = getAbsoluteRect(el);
519
- if (!isPointInsideRect(point, rect)) {
520
- return null;
521
- }
522
- return {
523
- top: point.y - rect.top,
524
- bottom: rect.bottom - point.y,
525
- left: point.x - rect.left,
526
- // TODO - figure out what is so special about right (why the rect is too big)
527
- right: Math.min(rect.right, document.documentElement.clientWidth) - point.x
528
- };
529
- }
530
-
531
511
  var dzToShadowIndexToRect;
532
512
 
533
513
  /**
@@ -541,17 +521,6 @@
541
521
  }
542
522
  resetIndexesCache();
543
523
 
544
- /**
545
- * Resets the cache that allows for smarter "would be index" resolution for a specific dropzone, should be called after the zone was scrolled
546
- * @param {HTMLElement} dz
547
- */
548
- function resetIndexesCacheForDz(dz) {
549
- printDebug(function () {
550
- return "resetting indexes cache for dz";
551
- });
552
- dzToShadowIndexToRect["delete"](dz);
553
- }
554
-
555
524
  /**
556
525
  * Caches the coordinates of the shadow element when it's in a certain index in a certain dropzone.
557
526
  * Helpful in order to determine "would be index" more effectively
@@ -634,102 +603,6 @@
634
603
  };
635
604
  }
636
605
 
637
- var SCROLL_ZONE_PX = 25;
638
- function makeScroller() {
639
- var scrollingInfo;
640
- function resetScrolling() {
641
- scrollingInfo = {
642
- directionObj: undefined,
643
- stepPx: 0
644
- };
645
- }
646
- resetScrolling();
647
- // directionObj {x: 0|1|-1, y:0|1|-1} - 1 means down in y and right in x
648
- function scrollContainer(containerEl) {
649
- var _scrollingInfo = scrollingInfo,
650
- directionObj = _scrollingInfo.directionObj,
651
- stepPx = _scrollingInfo.stepPx;
652
- if (directionObj) {
653
- containerEl.scrollBy(directionObj.x * stepPx, directionObj.y * stepPx);
654
- window.requestAnimationFrame(function () {
655
- return scrollContainer(containerEl);
656
- });
657
- }
658
- }
659
- function calcScrollStepPx(distancePx) {
660
- return SCROLL_ZONE_PX - distancePx;
661
- }
662
-
663
- /**
664
- * If the pointer is next to the sides of the element to scroll, will trigger scrolling
665
- * Can be called repeatedly with updated pointer and elementToScroll values without issues
666
- * @return {boolean} - true if scrolling was needed
667
- */
668
- function scrollIfNeeded(pointer, elementToScroll) {
669
- if (!elementToScroll) {
670
- return false;
671
- }
672
- var distances = calcInnerDistancesBetweenPointAndSidesOfElement(pointer, elementToScroll);
673
- if (distances === null) {
674
- resetScrolling();
675
- return false;
676
- }
677
- var isAlreadyScrolling = !!scrollingInfo.directionObj;
678
- var scrollingVertically = false,
679
- scrollingHorizontally = false;
680
- // vertical
681
- if (elementToScroll.scrollHeight > elementToScroll.clientHeight) {
682
- if (distances.bottom < SCROLL_ZONE_PX) {
683
- scrollingVertically = true;
684
- scrollingInfo.directionObj = {
685
- x: 0,
686
- y: 1
687
- };
688
- scrollingInfo.stepPx = calcScrollStepPx(distances.bottom);
689
- } else if (distances.top < SCROLL_ZONE_PX) {
690
- scrollingVertically = true;
691
- scrollingInfo.directionObj = {
692
- x: 0,
693
- y: -1
694
- };
695
- scrollingInfo.stepPx = calcScrollStepPx(distances.top);
696
- }
697
- if (!isAlreadyScrolling && scrollingVertically) {
698
- scrollContainer(elementToScroll);
699
- return true;
700
- }
701
- }
702
- // horizontal
703
- if (elementToScroll.scrollWidth > elementToScroll.clientWidth) {
704
- if (distances.right < SCROLL_ZONE_PX) {
705
- scrollingHorizontally = true;
706
- scrollingInfo.directionObj = {
707
- x: 1,
708
- y: 0
709
- };
710
- scrollingInfo.stepPx = calcScrollStepPx(distances.right);
711
- } else if (distances.left < SCROLL_ZONE_PX) {
712
- scrollingHorizontally = true;
713
- scrollingInfo.directionObj = {
714
- x: -1,
715
- y: 0
716
- };
717
- scrollingInfo.stepPx = calcScrollStepPx(distances.left);
718
- }
719
- if (!isAlreadyScrolling && scrollingHorizontally) {
720
- scrollContainer(elementToScroll);
721
- return true;
722
- }
723
- }
724
- resetScrolling();
725
- return false;
726
- }
727
- return {
728
- scrollIfNeeded: scrollIfNeeded,
729
- resetScrolling: resetScrolling
730
- };
731
- }
732
-
733
606
  /**
734
607
  * @param {Object} object
735
608
  * @return {string}
@@ -795,9 +668,6 @@
795
668
 
796
669
  var INTERVAL_MS = 200;
797
670
  var TOLERANCE_PX = 10;
798
- var _makeScroller = makeScroller(),
799
- scrollIfNeeded = _makeScroller.scrollIfNeeded,
800
- resetScrolling = _makeScroller.resetScrolling;
801
671
  var next;
802
672
 
803
673
  /**
@@ -805,9 +675,11 @@
805
675
  * @param {Set<HTMLElement>} dropZones
806
676
  * @param {HTMLElement} draggedEl
807
677
  * @param {number} [intervalMs = INTERVAL_MS]
678
+ * @param {MultiScroller} multiScroller
808
679
  */
809
680
  function observe(draggedEl, dropZones) {
810
681
  var intervalMs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INTERVAL_MS;
682
+ var multiScroller = arguments.length > 3 ? arguments[3] : undefined;
811
683
  // initialization
812
684
  var lastDropZoneFound;
813
685
  var lastIndexFound;
@@ -823,7 +695,7 @@
823
695
  */
824
696
  function andNow() {
825
697
  var currentCenterOfDragged = findCenterOfElement(draggedEl);
826
- var scrolled = scrollIfNeeded(currentCenterOfDragged, lastDropZoneFound);
698
+ var scrolled = multiScroller.multiScrollIfNeeded();
827
699
  // we only want to make a new decision after the element was moved a bit to prevent flickering
828
700
  if (!scrolled && lastCentrePositionOfDragged && Math.abs(lastCentrePositionOfDragged.x - currentCenterOfDragged.x) < TOLERANCE_PX && Math.abs(lastCentrePositionOfDragged.y - currentCenterOfDragged.y) < TOLERANCE_PX) {
829
701
  next = window.setTimeout(andNow, intervalMs);
@@ -844,7 +716,7 @@
844
716
  try {
845
717
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
846
718
  var dz = _step.value;
847
- if (scrolled) resetIndexesCacheForDz(lastDropZoneFound);
719
+ if (scrolled) resetIndexesCache();
848
720
  var indexObj = findWouldBeIndex(draggedEl, dz);
849
721
  if (indexObj === null) {
850
722
  // it is not inside
@@ -889,61 +761,225 @@
889
761
  return "unobserving";
890
762
  });
891
763
  clearTimeout(next);
892
- resetScrolling();
893
764
  resetIndexesCache();
894
765
  }
895
766
 
896
- var INTERVAL_MS$1 = 300;
897
- var mousePosition;
767
+ var SCROLL_ZONE_PX = 30;
898
768
 
899
769
  /**
900
- * Do not use this! it is visible for testing only until we get over the issue Cypress not triggering the mousemove listeners
901
- * // TODO - make private (remove export)
902
- * @param {{clientX: number, clientY: number}} e
770
+ * Will make a scroller that can scroll any element given to it in any direction
771
+ * @returns {{scrollIfNeeded: function(Point, HTMLElement): boolean, resetScrolling: function(void):void}}
903
772
  */
904
- function updateMousePosition(e) {
905
- var c = e.touches ? e.touches[0] : e;
906
- mousePosition = {
907
- x: c.clientX,
908
- y: c.clientY
909
- };
910
- }
911
- var _makeScroller$1 = makeScroller(),
912
- scrollIfNeeded$1 = _makeScroller$1.scrollIfNeeded,
913
- resetScrolling$1 = _makeScroller$1.resetScrolling;
914
- var next$1;
915
- function loop() {
916
- if (mousePosition) {
917
- var scrolled = scrollIfNeeded$1(mousePosition, document.documentElement);
918
- if (scrolled) resetIndexesCache();
773
+ function makeScroller() {
774
+ var scrollingInfo;
775
+ function resetScrolling() {
776
+ scrollingInfo = {
777
+ directionObj: undefined,
778
+ stepPx: 0
779
+ };
919
780
  }
920
- next$1 = window.setTimeout(loop, INTERVAL_MS$1);
781
+ resetScrolling();
782
+ // directionObj {x: 0|1|-1, y:0|1|-1} - 1 means down in y and right in x
783
+ function scrollContainer(containerEl) {
784
+ var _scrollingInfo = scrollingInfo,
785
+ directionObj = _scrollingInfo.directionObj,
786
+ stepPx = _scrollingInfo.stepPx;
787
+ if (directionObj) {
788
+ containerEl.scrollBy(directionObj.x * stepPx, directionObj.y * stepPx);
789
+ window.requestAnimationFrame(function () {
790
+ return scrollContainer(containerEl);
791
+ });
792
+ }
793
+ }
794
+ function calcScrollStepPx(distancePx) {
795
+ return SCROLL_ZONE_PX - distancePx;
796
+ }
797
+
798
+ /**
799
+ * @param {Point} pointer - the pointer will be used to decide in which direction to scroll
800
+ * @param {HTMLElement} elementToScroll - the scroll container
801
+ * If the pointer is next to the sides of the element to scroll, will trigger scrolling
802
+ * Can be called repeatedly with updated pointer and elementToScroll values without issues
803
+ * @return {boolean} - true if scrolling was needed
804
+ */
805
+ function scrollIfNeeded(pointer, elementToScroll) {
806
+ if (!elementToScroll) {
807
+ return false;
808
+ }
809
+ var distances = calcInnerDistancesBetweenPointAndSidesOfElement(pointer, elementToScroll);
810
+ if (distances === null) {
811
+ resetScrolling();
812
+ return false;
813
+ }
814
+ var isAlreadyScrolling = !!scrollingInfo.directionObj;
815
+ var scrollingVertically = false,
816
+ scrollingHorizontally = false;
817
+ // vertical
818
+ if (elementToScroll.scrollHeight > elementToScroll.clientHeight) {
819
+ if (distances.bottom < SCROLL_ZONE_PX) {
820
+ scrollingVertically = true;
821
+ scrollingInfo.directionObj = {
822
+ x: 0,
823
+ y: 1
824
+ };
825
+ scrollingInfo.stepPx = calcScrollStepPx(distances.bottom);
826
+ } else if (distances.top < SCROLL_ZONE_PX) {
827
+ scrollingVertically = true;
828
+ scrollingInfo.directionObj = {
829
+ x: 0,
830
+ y: -1
831
+ };
832
+ scrollingInfo.stepPx = calcScrollStepPx(distances.top);
833
+ }
834
+ if (!isAlreadyScrolling && scrollingVertically) {
835
+ scrollContainer(elementToScroll);
836
+ return true;
837
+ }
838
+ }
839
+ // horizontal
840
+ if (elementToScroll.scrollWidth > elementToScroll.clientWidth) {
841
+ if (distances.right < SCROLL_ZONE_PX) {
842
+ scrollingHorizontally = true;
843
+ scrollingInfo.directionObj = {
844
+ x: 1,
845
+ y: 0
846
+ };
847
+ scrollingInfo.stepPx = calcScrollStepPx(distances.right);
848
+ } else if (distances.left < SCROLL_ZONE_PX) {
849
+ scrollingHorizontally = true;
850
+ scrollingInfo.directionObj = {
851
+ x: -1,
852
+ y: 0
853
+ };
854
+ scrollingInfo.stepPx = calcScrollStepPx(distances.left);
855
+ }
856
+ if (!isAlreadyScrolling && scrollingHorizontally) {
857
+ scrollContainer(elementToScroll);
858
+ return true;
859
+ }
860
+ }
861
+ resetScrolling();
862
+ return false;
863
+ }
864
+ return {
865
+ scrollIfNeeded: scrollIfNeeded,
866
+ resetScrolling: resetScrolling
867
+ };
921
868
  }
922
869
 
923
870
  /**
924
- * will start watching the mouse pointer and scroll the window if it goes next to the edges
871
+ * If the point is inside the element returns its distances from the sides, otherwise returns null
872
+ * @param {Point} point
873
+ * @param {HTMLElement} el
874
+ * @return {null|{top: number, left: number, bottom: number, right: number}}
925
875
  */
926
- function armWindowScroller() {
927
- printDebug(function () {
928
- return "arming window scroller";
929
- });
930
- window.addEventListener("mousemove", updateMousePosition);
931
- window.addEventListener("touchmove", updateMousePosition);
932
- loop();
876
+ function calcInnerDistancesBetweenPointAndSidesOfElement(point, el) {
877
+ // Even if the scrolling element is small it acts as a scroller for the viewport
878
+ var rect = el === document.scrollingElement ? {
879
+ top: 0,
880
+ bottom: window.innerHeight,
881
+ left: 0,
882
+ right: window.innerWidth
883
+ } : el.getBoundingClientRect();
884
+ if (!isPointInsideRect(point, rect)) {
885
+ return null;
886
+ }
887
+ return {
888
+ top: point.y - rect.top,
889
+ bottom: rect.bottom - point.y,
890
+ left: point.x - rect.left,
891
+ right: rect.right - point.x
892
+ };
933
893
  }
934
894
 
935
895
  /**
936
- * will stop watching the mouse pointer and won't scroll the window anymore
896
+ @typedef {Object} MultiScroller
897
+ @property {function():boolean} multiScrollIfNeeded - call this on every "tick" to scroll containers if needed, returns true if anything was scrolled
898
+ /**
899
+ * Creates a scroller than can scroll any of the provided containers or any of their scrollable parents (including the document's scrolling element)
900
+ * @param {HTMLElement[]} baseElementsForScrolling
901
+ * @param {function():Point} getPointerPosition
902
+ * @return {MultiScroller}
937
903
  */
938
- function disarmWindowScroller() {
904
+ function createMultiScroller() {
905
+ var baseElementsForScrolling = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
906
+ var getPointerPosition = arguments.length > 1 ? arguments[1] : undefined;
939
907
  printDebug(function () {
940
- return "disarming window scroller";
908
+ return "creating multi-scroller";
909
+ });
910
+ var scrollingContainersSet = findRelevantScrollContainers(baseElementsForScrolling);
911
+ var scrollingContainersDeepToShallow = Array.from(scrollingContainersSet).sort(function (dz1, dz2) {
912
+ return getDepth(dz2) - getDepth(dz1);
941
913
  });
942
- window.removeEventListener("mousemove", updateMousePosition);
943
- window.removeEventListener("touchmove", updateMousePosition);
944
- mousePosition = undefined;
945
- window.clearTimeout(next$1);
946
- resetScrolling$1();
914
+ var _makeScroller = makeScroller(),
915
+ scrollIfNeeded = _makeScroller.scrollIfNeeded;
916
+
917
+ /**
918
+ * @return {boolean} - was any container scrolled
919
+ */
920
+ function tick() {
921
+ var mousePosition = getPointerPosition();
922
+ if (!mousePosition || !scrollingContainersDeepToShallow) {
923
+ return false;
924
+ }
925
+ var scrollContainersUnderCursor = scrollingContainersDeepToShallow.filter(function (el) {
926
+ return isPointInsideRect(mousePosition, el.getBoundingClientRect()) || el === document.scrollingElement;
927
+ });
928
+ for (var i = 0; i < scrollContainersUnderCursor.length; i++) {
929
+ var scrolled = scrollIfNeeded(mousePosition, scrollContainersUnderCursor[i]);
930
+ if (scrolled) {
931
+ return true;
932
+ }
933
+ }
934
+ return false;
935
+ }
936
+ return {
937
+ multiScrollIfNeeded: scrollingContainersSet.size > 0 ? tick : function () {
938
+ return false;
939
+ }
940
+ };
941
+ }
942
+
943
+ // internal utils
944
+ function findScrollableParents(element) {
945
+ if (!element) {
946
+ return [];
947
+ }
948
+ var scrollableContainers = [];
949
+ var parent = element;
950
+ while (parent) {
951
+ var _window$getComputedSt = window.getComputedStyle(parent),
952
+ overflow = _window$getComputedSt.overflow;
953
+ if (overflow.split(" ").some(function (o) {
954
+ return o.includes("auto") || o.includes("scroll");
955
+ })) {
956
+ scrollableContainers.push(parent);
957
+ }
958
+ parent = parent.parentElement;
959
+ }
960
+ return scrollableContainers;
961
+ }
962
+ function findRelevantScrollContainers(dropZones) {
963
+ var scrollingContainers = new Set();
964
+ var _iterator = _createForOfIteratorHelper(dropZones),
965
+ _step;
966
+ try {
967
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
968
+ var dz = _step.value;
969
+ findScrollableParents(dz).forEach(function (container) {
970
+ return scrollingContainers.add(container);
971
+ });
972
+ }
973
+ // The scrolling element might have overflow visible and still be scrollable
974
+ } catch (err) {
975
+ _iterator.e(err);
976
+ } finally {
977
+ _iterator.f();
978
+ }
979
+ if (document.scrollingElement.scrollHeight > document.scrollingElement.clientHeight || document.scrollingElement.scrollWidth > document.scrollingElement.clientHeight) {
980
+ scrollingContainers.add(document.scrollingElement);
981
+ }
982
+ return scrollingContainers;
947
983
  }
948
984
 
949
985
  /**
@@ -1292,7 +1328,6 @@
1292
1328
  printDebug(function () {
1293
1329
  return "watching dragged element";
1294
1330
  });
1295
- armWindowScroller();
1296
1331
  var dropZones = typeToDropZones.get(draggedElType);
1297
1332
  var _iterator = _createForOfIteratorHelper(dropZones),
1298
1333
  _step;
@@ -1309,18 +1344,21 @@
1309
1344
  _iterator.f();
1310
1345
  }
1311
1346
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop);
1347
+
1312
1348
  // 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
1313
1349
  var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(dropZones.keys()).map(function (dz) {
1314
1350
  return dzToConfig.get(dz).dropAnimationDurationMs;
1315
1351
  })));
1316
- 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.
1317
- observe(draggedEl, dropZones, observationIntervalMs * 1.07);
1352
+ 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.
1353
+ var multiScroller = createMultiScroller(dropZones, function () {
1354
+ return currentMousePosition;
1355
+ });
1356
+ observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller);
1318
1357
  }
1319
1358
  function unWatchDraggedElement() {
1320
1359
  printDebug(function () {
1321
1360
  return "unwatching dragged element";
1322
1361
  });
1323
- disarmWindowScroller();
1324
1362
  var dropZones = typeToDropZones.get(draggedElType);
1325
1363
  var _iterator2 = _createForOfIteratorHelper(dropZones),
1326
1364
  _step2;
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.40",
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
  }
@@ -1,6 +1,10 @@
1
- import {calcInnerDistancesBetweenPointAndSidesOfElement} from "./intersection";
2
- const SCROLL_ZONE_PX = 25;
1
+ import {isPointInsideRect} from "./intersection";
2
+ const SCROLL_ZONE_PX = 30;
3
3
 
4
+ /**
5
+ * Will make a scroller that can scroll any element given to it in any direction
6
+ * @returns {{scrollIfNeeded: function(Point, HTMLElement): boolean, resetScrolling: function(void):void}}
7
+ */
4
8
  export function makeScroller() {
5
9
  let scrollingInfo;
6
10
  function resetScrolling() {
@@ -20,6 +24,8 @@ export function makeScroller() {
20
24
  }
21
25
 
22
26
  /**
27
+ * @param {Point} pointer - the pointer will be used to decide in which direction to scroll
28
+ * @param {HTMLElement} elementToScroll - the scroll container
23
29
  * If the pointer is next to the sides of the element to scroll, will trigger scrolling
24
30
  * Can be called repeatedly with updated pointer and elementToScroll values without issues
25
31
  * @return {boolean} - true if scrolling was needed
@@ -76,3 +82,31 @@ export function makeScroller() {
76
82
  resetScrolling
77
83
  };
78
84
  }
85
+
86
+ /**
87
+ * If the point is inside the element returns its distances from the sides, otherwise returns null
88
+ * @param {Point} point
89
+ * @param {HTMLElement} el
90
+ * @return {null|{top: number, left: number, bottom: number, right: number}}
91
+ */
92
+ function calcInnerDistancesBetweenPointAndSidesOfElement(point, el) {
93
+ // Even if the scrolling element is small it acts as a scroller for the viewport
94
+ const rect =
95
+ el === document.scrollingElement
96
+ ? {
97
+ top: 0,
98
+ bottom: window.innerHeight,
99
+ left: 0,
100
+ right: window.innerWidth
101
+ }
102
+ : el.getBoundingClientRect();
103
+ if (!isPointInsideRect(point, rect)) {
104
+ return null;
105
+ }
106
+ return {
107
+ top: point.y - rect.top,
108
+ bottom: rect.bottom - point.y,
109
+ left: point.x - rect.left,
110
+ right: rect.right - point.x
111
+ };
112
+ }
@@ -9,7 +9,7 @@ import {
9
9
  TRIGGERS
10
10
  } from "./constants";
11
11
  import {observe, unobserve} from "./helpers/observer";
12
- import {armWindowScroller, disarmWindowScroller} from "./helpers/windowScroller";
12
+ import {createMultiScroller} from "./helpers/multiScroller";
13
13
  import {
14
14
  createDraggedElementFrom,
15
15
  decorateShadowEl,
@@ -88,22 +88,23 @@ function unregisterDropZone(dropZoneEl, type) {
88
88
  /* functions to manage observing the dragged element and trigger custom drag-events */
89
89
  function watchDraggedElement() {
90
90
  printDebug(() => "watching dragged element");
91
- armWindowScroller();
92
91
  const dropZones = typeToDropZones.get(draggedElType);
92
+
93
93
  for (const dz of dropZones) {
94
94
  dz.addEventListener(DRAGGED_ENTERED_EVENT_NAME, handleDraggedEntered);
95
95
  dz.addEventListener(DRAGGED_LEFT_EVENT_NAME, handleDraggedLeft);
96
96
  dz.addEventListener(DRAGGED_OVER_INDEX_EVENT_NAME, handleDraggedIsOverIndex);
97
97
  }
98
98
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop);
99
+
99
100
  // 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
100
101
  const setIntervalMs = Math.max(...Array.from(dropZones.keys()).map(dz => dzToConfig.get(dz).dropAnimationDurationMs));
101
- const 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.
102
- observe(draggedEl, dropZones, observationIntervalMs * 1.07);
102
+ const 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.
103
+ const multiScroller = createMultiScroller(dropZones, () => currentMousePosition);
104
+ observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller);
103
105
  }
104
106
  function unWatchDraggedElement() {
105
107
  printDebug(() => "unwatching dragged element");
106
- disarmWindowScroller();
107
108
  const dropZones = typeToDropZones.get(draggedElType);
108
109
  for (const dz of dropZones) {
109
110
  dz.removeEventListener(DRAGGED_ENTERED_EVENT_NAME, handleDraggedEntered);
@@ -1,48 +0,0 @@
1
- import {makeScroller} from "./scroller";
2
- import {printDebug} from "../constants";
3
- import {resetIndexesCache} from "./listUtil";
4
-
5
- const INTERVAL_MS = 300;
6
- let mousePosition;
7
-
8
- /**
9
- * Do not use this! it is visible for testing only until we get over the issue Cypress not triggering the mousemove listeners
10
- * // TODO - make private (remove export)
11
- * @param {{clientX: number, clientY: number}} e
12
- */
13
- export function updateMousePosition(e) {
14
- const c = e.touches ? e.touches[0] : e;
15
- mousePosition = {x: c.clientX, y: c.clientY};
16
- }
17
- const {scrollIfNeeded, resetScrolling} = makeScroller();
18
- let next;
19
-
20
- function loop() {
21
- if (mousePosition) {
22
- const scrolled = scrollIfNeeded(mousePosition, document.documentElement);
23
- if (scrolled) resetIndexesCache();
24
- }
25
- next = window.setTimeout(loop, INTERVAL_MS);
26
- }
27
-
28
- /**
29
- * will start watching the mouse pointer and scroll the window if it goes next to the edges
30
- */
31
- export function armWindowScroller() {
32
- printDebug(() => "arming window scroller");
33
- window.addEventListener("mousemove", updateMousePosition);
34
- window.addEventListener("touchmove", updateMousePosition);
35
- loop();
36
- }
37
-
38
- /**
39
- * will stop watching the mouse pointer and won't scroll the window anymore
40
- */
41
- export function disarmWindowScroller() {
42
- printDebug(() => "disarming window scroller");
43
- window.removeEventListener("mousemove", updateMousePosition);
44
- window.removeEventListener("touchmove", updateMousePosition);
45
- mousePosition = undefined;
46
- window.clearTimeout(next);
47
- resetScrolling();
48
- }