Browse Source

improve autoscroll, mirror

Adam Shaw 7 years ago
parent
commit
b1d2db7c44

+ 14 - 15
src/agenda/TimeGrid.ts

@@ -62,8 +62,7 @@ export default class TimeGrid extends DateComponent {
 
   colPositions: CoordCache
   slatPositions: CoordCache
-  colOffsets: OffsetCoordCache
-  slatOffsets: OffsetCoordCache
+  offsetTracker: OffsetCoordCache
 
   rootBgContainerEl: HTMLElement
   bottomRuleEl: HTMLElement // hidden by default
@@ -238,7 +237,7 @@ export default class TimeGrid extends DateComponent {
     this.slatEls = findElements(this.slatContainerEl, 'tr')
 
     this.slatPositions = new CoordCache({
-      originEl: this.slatContainerEl,
+      originEl: this.el,
       els: this.slatEls,
       isVertical: true
     })
@@ -316,7 +315,7 @@ export default class TimeGrid extends DateComponent {
     this.colEls = findElements(this.el, '.fc-day, .fc-disabled-day')
 
     this.colPositions = new CoordCache({
-      originEl: this.rootBgContainerEl,
+      originEl: this.el,
       els: this.colEls,
       isHorizontal: true
     })
@@ -579,26 +578,26 @@ export default class TimeGrid extends DateComponent {
 
 
   prepareHits() {
-    this.colOffsets = new OffsetCoordCache(this.colPositions)
-    this.slatOffsets = new OffsetCoordCache(this.slatPositions)
+    this.offsetTracker = new OffsetCoordCache(this.el)
   }
 
 
   releaseHits() {
-    this.colOffsets.destroy()
-    this.slatOffsets.destroy()
+    this.offsetTracker.destroy()
   }
 
 
   queryHit(leftOffset, topOffset): Hit {
-    let { snapsPerSlot, slatPositions, colOffsets, slatOffsets } = this
+    let { snapsPerSlot, slatPositions, colPositions, offsetTracker } = this
 
-    if (colOffsets.isInBounds(leftOffset, topOffset)) {
-      let colIndex = colOffsets.leftOffsetToIndex(leftOffset)
-      let slatIndex = slatOffsets.topOffsetToIndex(topOffset)
+    if (offsetTracker.isWithinClipping(leftOffset, topOffset)) {
+      let leftOrigin = offsetTracker.getLeftAdjust()
+      let topOrigin = offsetTracker.getTopAdjust()
+      let colIndex = colPositions.leftPositionToIndex(leftOffset - leftOrigin)
+      let slatIndex = slatPositions.topPositionToIndex(topOffset - topOrigin)
 
       if (colIndex != null && slatIndex != null) {
-        let slatTop = slatOffsets.indexToTopOffset(slatIndex)
+        let slatTop = slatPositions.indexToTopPosition(slatIndex) + topOrigin
         let slatHeight = slatPositions.getHeight(slatIndex)
         let partial = (topOffset - slatTop) / slatHeight // floating point number between 0 and 1
         let localSnapIndex = Math.floor(partial * snapsPerSlot) // the snap # relative to start of slat
@@ -622,8 +621,8 @@ export default class TimeGrid extends DateComponent {
           },
           dayEl: this.colEls[colIndex],
           rect: {
-            left: colOffsets.indexToLeftOffset(colIndex),
-            right: colOffsets.indexToRightOffset(colIndex),
+            left: colPositions.indexToLeftPosition(colIndex) + leftOrigin,
+            right: colPositions.indexToRightPosition(colIndex) + leftOrigin,
             top: slatTop,
             bottom: slatTop + slatHeight
           }

+ 14 - 16
src/basic/DayGrid.ts

@@ -63,8 +63,7 @@ export default class DayGrid extends DateComponent {
 
   rowPositions: CoordCache
   colPositions: CoordCache
-  rowOffsets: OffsetCoordCache
-  colOffsets: OffsetCoordCache
+  offsetTracker: OffsetCoordCache
 
   // isRigid determines whether the individual rows should ignore the contents and be a constant height.
   // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
@@ -308,23 +307,23 @@ export default class DayGrid extends DateComponent {
 
 
   prepareHits() {
-    this.colOffsets = new OffsetCoordCache(this.colPositions)
-    this.rowOffsets = new OffsetCoordCache(this.rowPositions)
+    this.offsetTracker = new OffsetCoordCache(this.el)
   }
 
 
   releaseHits() {
-    this.colOffsets.destroy()
-    this.rowOffsets.destroy()
+    this.offsetTracker.destroy()
   }
 
 
   queryHit(leftOffset, topOffset): Hit {
-    let { colOffsets, rowOffsets } = this
+    let { colPositions, rowPositions, offsetTracker } = this
 
-    if (colOffsets.isInBounds(leftOffset, topOffset)) {
-      let col = colOffsets.leftOffsetToIndex(leftOffset)
-      let row = rowOffsets.topOffsetToIndex(topOffset)
+    if (offsetTracker.isWithinClipping(leftOffset, topOffset)) {
+      let leftOrigin = offsetTracker.getLeftAdjust()
+      let topOrigin = offsetTracker.getTopAdjust()
+      let col = colPositions.leftPositionToIndex(leftOffset - leftOrigin)
+      let row = rowPositions.topPositionToIndex(topOffset - topOrigin)
 
       if (row != null && col != null) {
         return {
@@ -335,10 +334,10 @@ export default class DayGrid extends DateComponent {
           },
           dayEl: this.getCellEl(row, col),
           rect: {
-            left: colOffsets.indexToLeftOffset(col),
-            right: colOffsets.indexToRightOffset(col),
-            top: rowOffsets.indexToTopOffset(row),
-            bottom: rowOffsets.indexToBottomOffset(row)
+            left: colPositions.indexToLeftPosition(col) + leftOrigin,
+            right: colPositions.indexToRightPosition(col) + leftOrigin,
+            top: rowPositions.indexToTopPosition(row) + topOrigin,
+            bottom: rowPositions.indexToBottomPosition(row) + topOrigin
           }
         }
       }
@@ -635,10 +634,9 @@ export default class DayGrid extends DateComponent {
 
     options = {
       className: 'fc-more-popover ' + view.calendar.theme.getClass('popover'),
-      parentEl: view.el, // attach to root of view. guarantees outside of scrollbars.
+      parentEl: this.el,
       top: computeRect(topEl).top,
       autoHide: true, // when the user clicks elsewhere, hide the popover
-      viewportConstrain: this.opt('popoverViewportConstrain'),
       content: (el) => {
         this.segPopoverTile.setElement(el)
 

+ 39 - 32
src/common/OffsetCoordCache.ts

@@ -1,55 +1,62 @@
-import CoordCache from './CoordCache'
-import { computeInnerRect, getScrollParent, computeRect } from '../util/dom-geom'
-import { Rect, pointInsideRect } from '../util/geom'
+import { getClippingParents, computeRect } from '../util/dom-geom'
+import { pointInsideRect } from '../util/geom'
+import { ElScrollControllerCache, ElScrollController } from '../dnd/scroll'
 
-export default class OffsetCoordCache {
+export default class OffsetCoordCache { // TODO: rename to OffsetTracker?
 
-  coordCache: CoordCache
-  boundingRect: Rect
+  scrollCaches: ElScrollControllerCache[]
   originOffsetLeft: number
   originOffsetTop: number
 
-  constructor(coordCache: CoordCache) {
-    this.coordCache = coordCache
-
-    let scrollParent = getScrollParent(coordCache.originEl)
-    this.boundingRect = scrollParent ? computeInnerRect(scrollParent) : null
-
-    let rect = computeRect(coordCache.originEl)
+  constructor(el: HTMLElement) {
+    let rect = computeRect(el)
     this.originOffsetLeft = rect.left
     this.originOffsetTop = rect.top
+
+    this.scrollCaches = getClippingParents(el).map(function(el) {
+      return new ElScrollControllerCache(
+        new ElScrollController(el),
+        true // listens to element for scrolling
+      )
+    })
   }
 
   destroy() {
-    // console.log('OffsetCoordCache::destroy')
+    for (let scrollCache of this.scrollCaches) {
+      scrollCache.destroy()
+    }
   }
 
-  isInBounds(pageX, pageY): boolean {
-    return !this.boundingRect || pointInsideRect({ left: pageX, top: pageY }, this.boundingRect)
-  }
+  isWithinClipping(pageX, pageY): boolean {
+    let point = { left: pageX, top: pageY }
 
-  leftOffsetToIndex(leftOffset): number {
-    return this.coordCache.leftPositionToIndex(leftOffset - this.originOffsetLeft)
-  }
+    for (let scrollCache of this.scrollCaches) {
+      if (!pointInsideRect(point, scrollCache.rect)) {
+        return false
+      }
+    }
 
-  topOffsetToIndex(topOffset): number {
-    return this.coordCache.topPositionToIndex(topOffset - this.originOffsetTop)
+    return true
   }
 
-  indexToLeftOffset(index): number {
-    return this.coordCache.indexToLeftPosition(index) + this.originOffsetLeft
-  }
+  getLeftAdjust() {
+    let left = this.originOffsetLeft
 
-  indexToTopOffset(index): number {
-    return this.coordCache.indexToTopPosition(index) + this.originOffsetTop
-  }
+    for (let scrollCache of this.scrollCaches) {
+      left += scrollCache.scrollLeft - scrollCache.origScrollLeft
+    }
 
-  indexToRightOffset(index): number {
-    return this.coordCache.indexToRightPosition(index) + this.originOffsetLeft
+    return left
   }
 
-  indexToBottomOffset(index): number {
-    return this.coordCache.indexToBottomPosition(index) + this.originOffsetTop
+  getTopAdjust() {
+    let top = this.originOffsetTop
+
+    for (let scrollCache of this.scrollCaches) {
+      top += scrollCache.origScrollTop - scrollCache.scrollTop
+    }
+
+    return top
   }
 
 }

+ 6 - 16
src/common/Popover.ts

@@ -15,8 +15,7 @@ Options:
 
 import { removeElement, createElement, applyStyle } from '../util/dom-manip'
 import { listenBySelector } from '../util/dom-event'
-import { getScrollParent, computeRect, computeViewportRect } from '../util/dom-geom'
-import { Rect } from '../util/geom'
+import { computeClippingRect, computeRect } from '../util/dom-geom'
 
 export interface PopoverOptions {
   className?: string
@@ -122,8 +121,7 @@ export default class Popover {
     let el = this.el
     let elDims = el.getBoundingClientRect() // only used for width,height
     let origin = computeRect(el.offsetParent)
-    let scrollEl = getScrollParent(el)
-    let viewportRect: Rect
+    let clippingRect = computeClippingRect(options.parentEl)
     let top // the "position" (not "offset") values for the popover
     let left //
 
@@ -137,19 +135,11 @@ export default class Popover {
       left = 0
     }
 
-    if (scrollEl) {
-      viewportRect = computeRect(scrollEl)
-    } else {
-      viewportRect = computeViewportRect()
-    }
-
     // constrain to the view port. if constrained by two edges, give precedence to top/left
-    if (options.viewportConstrain !== false) {
-      top = Math.min(top, viewportRect.bottom - elDims.height - this.margin)
-      top = Math.max(top, viewportRect.top + this.margin)
-      left = Math.min(left, viewportRect.right - elDims.width - this.margin)
-      left = Math.max(left, viewportRect.left + this.margin)
-    }
+    top = Math.min(top, clippingRect.bottom - elDims.height - this.margin)
+    top = Math.max(top, clippingRect.top + this.margin)
+    left = Math.min(left, clippingRect.right - elDims.width - this.margin)
+    left = Math.max(left, clippingRect.left + this.margin)
 
     applyStyle(el, {
       top: top - origin.top,

+ 75 - 48
src/dnd/AutoScroller.ts

@@ -1,4 +1,10 @@
-import { ScrollControllerCache, ElScrollControllerCache, ElScrollController, WindowScrollControllerCache } from './scroll'
+import {
+  ScrollControllerCache,
+  ElScrollControllerCache,
+  ElScrollController,
+  WindowScrollControllerCache,
+  WindowScrollController
+} from './scroll'
 
 interface Side { // rename to Edge?
   controller: ScrollControllerCache
@@ -11,55 +17,74 @@ interface Side { // rename to Edge?
 // https://developer.mozilla.org/en-US/docs/Web/API/Performance
 const getTime = typeof performance === 'function' ? (performance as any).now : Date.now
 
-let uid = 0
-
 export default class AutoScroller {
 
-  id: string
-
-  constructor() {
-    this.id = String(uid++)
-  }
-
   // options that can be set by caller
+  isEnabled: boolean = true
   scrollerQuery: (Window | string)[] = [ window, '.fc-scroller' ]
   edge: number = 50
-  maxSpeed: number = 300 // pixels per second
-  isAnimating: boolean = false
+  maxVelocity: number = 300 // pixels per second
+
   pointerScreenX: number
   pointerScreenY: number
+  isAnimating: boolean = false
+  everMovedUp: boolean = false
+  everMovedDown: boolean = false
+  everMovedLeft: boolean = false
+  everMovedRight: boolean = false
 
-  private windowController: WindowScrollControllerCache
   private controllers: ScrollControllerCache[] // rename to caches?
   private msSinceRequest: number
 
-  start(pageX: number, pageY: number, windowController: WindowScrollControllerCache) { // TODO: pass windowscrollcontrollercache
-    this.windowController = windowController
-    this.controllers = this.buildControllers()
-    this.handleMove(pageX, pageY)
+  start(pageX: number, pageY: number) {
+    if (this.isEnabled) {
+      this.controllers = this.buildControllers()
+
+      this.pointerScreenX = null
+      this.pointerScreenY = null
+      this.everMovedUp = false
+      this.everMovedDown = false
+      this.everMovedLeft = false
+      this.everMovedRight = false
+
+      this.handleMove(pageX, pageY)
+    }
   }
 
   handleMove(pageX: number, pageY: number) {
-    this.pointerScreenX = pageX - this.windowController.getScrollLeft() // audit all ordering
-    this.pointerScreenY = pageY - this.windowController.getScrollTop()
+    if (this.isEnabled) {
+      let pointerScreenX = pageX - window.scrollX //this.windowController.getScrollLeft() // audit all ordering
+      let pointerScreenY = pageY - window.scrollY // this.windowController.getScrollTop()
+
+      let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY
+      let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX
+
+      if (yDelta < 0) { this.everMovedUp = true }
+      else if (yDelta > 0) { this.everMovedDown = true }
 
-    if (!this.isAnimating) {
-      this.isAnimating = true
-      this.requestAnimation(getTime())
+      if (xDelta < 0) { this.everMovedLeft = true }
+      else if (yDelta > 0) { this.everMovedRight = true }
+
+      this.pointerScreenX = pointerScreenX
+      this.pointerScreenY = pointerScreenY
+
+      if (!this.isAnimating) {
+        this.isAnimating = true
+        this.requestAnimation(getTime())
+      }
     }
   }
 
   stop() {
-    this.isAnimating = false // will stop animation
+    if (this.isEnabled) {
+      this.isAnimating = false
 
-    for (let controller of this.controllers) {
-      if (controller !== this.windowController) { // because window controller isnt our responsbility. TODO: rethink
+      for (let controller of this.controllers) {
         controller.destroy()
       }
-    }
-    this.controllers = null
 
-    this.windowController = null
+      this.controllers = null
+    }
   }
 
   requestAnimation(now) {
@@ -68,10 +93,10 @@ export default class AutoScroller {
   }
 
   private animate = () => {
-    if (this.isAnimating) { // wasn't cancelled
+    if (this.isAnimating) { // wasn't cancelled between animation calls
       let side = this.computeBestSide(
-        this.pointerScreenX + this.windowController.getScrollLeft(),
-        this.pointerScreenY + this.windowController.getScrollTop()
+        this.pointerScreenX + window.scrollX,
+        this.pointerScreenY + window.scrollY
       )
 
       if (side) {
@@ -85,28 +110,24 @@ export default class AutoScroller {
   }
 
   private handleSide(side: Side, seconds: number) {
-    let { edge } = this
     let { controller } = side
-    let invEdge = edge - side.distance
-    let speed = ((invEdge * invEdge) / (edge * edge)) * // quadratic
-      this.maxSpeed * seconds
+    let { edge } = this
+    let invDistance = edge - side.distance
+    let velocity = (invDistance * invDistance) / (edge * edge) * this.maxVelocity * seconds // quadratic
+    let sign = 1
 
     switch (side.name) {
 
       case 'left':
-        controller.setScrollLeft(controller.getScrollLeft() - speed)
-        break
-
+        sign = -1
       case 'right':
-        controller.setScrollLeft(controller.getScrollLeft() + speed)
+        controller.setScrollLeft(controller.getScrollLeft() + velocity * sign)
         break
 
       case 'top':
-        controller.setScrollTop(controller.getScrollTop() - speed)
-        break
-
+        sign = -1
       case 'bottom':
-        controller.setScrollTop(controller.getScrollTop() + speed)
+        controller.setScrollTop(controller.getScrollTop() + velocity * sign)
         break
     }
   }
@@ -126,19 +147,19 @@ export default class AutoScroller {
       // completely within the rect?
       if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) {
 
-        if (topDist <= edge && controller.canScrollUp() && (!bestSide || bestSide.distance > topDist)) {
+        if (topDist <= edge && this.everMovedUp && controller.canScrollUp() && (!bestSide || bestSide.distance > topDist)) {
           bestSide = { controller, name: 'top', distance: topDist }
         }
 
-        if (bottomDist <= edge && controller.canScrollDown() && (!bestSide || bestSide.distance > bottomDist)) {
+        if (bottomDist <= edge && this.everMovedDown && controller.canScrollDown() && (!bestSide || bestSide.distance > bottomDist)) {
           bestSide = { controller, name: 'bottom', distance: bottomDist }
         }
 
-        if (leftDist <= edge && controller.canScrollLeft() && (!bestSide || bestSide.distance > leftDist)) {
+        if (leftDist <= edge && this.everMovedLeft && controller.canScrollLeft() && (!bestSide || bestSide.distance > leftDist)) {
           bestSide = { controller, name: 'left', distance: leftDist }
         }
 
-        if (rightDist <= edge && controller.canScrollRight() && (!bestSide || bestSide.distance > rightDist)) {
+        if (rightDist <= edge && this.everMovedRight && controller.canScrollRight() && (!bestSide || bestSide.distance > rightDist)) {
           bestSide = { controller, name: 'right', distance: rightDist }
         }
       }
@@ -150,9 +171,15 @@ export default class AutoScroller {
   private buildControllers() {
     return this.queryScrollerEls().map((el) => {
       if (el === window) {
-        return this.windowController
+        return new WindowScrollControllerCache(
+          new WindowScrollController(),
+          false // don't listen to user-generated scrolls
+        )
       } else {
-        return new ElScrollControllerCache(new ElScrollController(el as HTMLElement))
+        return new ElScrollControllerCache(
+          new ElScrollController(el as HTMLElement),
+          false // don't listen to user-generated scrolls
+        )
       }
     })
   }

+ 9 - 17
src/dnd/ElementMirror.ts

@@ -1,7 +1,6 @@
 import { removeElement, applyStyle } from '../util/dom-manip'
 import { whenTransitionDone } from '../util/dom-event'
 import { Rect } from '../util/geom'
-import { WindowScrollControllerCache } from './scroll'
 
 /*
 An effect in which an element follows the movement of a pointer across the screen.
@@ -17,7 +16,6 @@ export default class ElementMirror {
   deltaY?: number
   sourceEl: HTMLElement | null = null
   mirrorEl: HTMLElement | null = null
-  windowController: WindowScrollControllerCache
   sourceElRect: Rect | null = null // screen coords relative to viewport
 
   // options that can be set directly by caller
@@ -26,19 +24,19 @@ export default class ElementMirror {
   zIndex: number = 9999
   revertDuration: number = 0
 
-  start(sourceEl: HTMLElement, pageX: number, pageY: number, windowController: WindowScrollControllerCache) {
+  start(sourceEl: HTMLElement, pageX: number, pageY: number) {
     this.sourceEl = sourceEl
-    this.origScreenX = pageX - windowController.getScrollLeft()
-    this.origScreenY = pageY - windowController.getScrollTop()
+    this.sourceElRect = this.sourceEl.getBoundingClientRect()
+    this.origScreenX = pageX - window.scrollX
+    this.origScreenY = pageY - window.scrollY
     this.deltaX = 0
     this.deltaY = 0
-    this.windowController = windowController
     this.updateElPosition()
   }
 
   handleMove(pageX: number, pageY: number) {
-    this.deltaX = (pageX - this.windowController.getScrollLeft()) - this.origScreenX!
-    this.deltaY = (pageY - this.windowController.getScrollTop()) - this.origScreenY!
+    this.deltaX = (pageX - window.scrollX) - this.origScreenX!
+    this.deltaY = (pageY - window.scrollY) - this.origScreenY!
     this.updateElPosition()
   }
 
@@ -86,14 +84,15 @@ export default class ElementMirror {
 
   doRevertAnimation(callback: () => void, revertDuration: number) {
     let mirrorEl = this.mirrorEl!
+    let finalSourceElRect = this.sourceEl.getBoundingClientRect() // because autoscrolling might have happened
 
     mirrorEl.style.transition =
       'top ' + revertDuration + 'ms,' +
       'left ' + revertDuration + 'ms'
 
     applyStyle(mirrorEl, {
-      left: this.sourceElRect!.left,
-      top: this.sourceElRect!.top
+      left: finalSourceElRect.left,
+      top: finalSourceElRect.top
     })
 
     whenTransitionDone(mirrorEl, () => {
@@ -109,17 +108,10 @@ export default class ElementMirror {
     }
 
     this.sourceEl = null
-    this.sourceElRect = null // so knows to recompute next time
   }
 
   updateElPosition() {
     if (this.sourceEl && this.isVisible) {
-
-      if (!this.sourceElRect) {
-        // relative to viewport, which is what we want, since mirror el is position: fixed
-        this.sourceElRect = this.sourceEl.getBoundingClientRect()
-      }
-
       applyStyle(this.getMirrorEl(), {
         left: this.sourceElRect.left + this.deltaX!,
         top: this.sourceElRect.top + this.deltaY!

+ 6 - 9
src/dnd/FeaturefulElementDragging.ts

@@ -3,7 +3,6 @@ import { preventSelection, allowSelection, preventContextMenu, allowContextMenu
 import ElementMirror from './ElementMirror'
 import ElementDragging from './ElementDragging'
 import AutoScroller from './AutoScroller'
-import { WindowScrollControllerCache, WindowScrollController } from './scroll'
 
 /*
 Monitors dragging on an element. Has a number of high-level features:
@@ -14,7 +13,6 @@ Monitors dragging on an element. Has a number of high-level features:
 export default class FeaturefulElementDragging extends ElementDragging {
 
   pointer: PointerDragging
-  windowScrollController: WindowScrollControllerCache
   mirror: ElementMirror
   autoScroller: AutoScroller
 
@@ -67,9 +65,8 @@ export default class FeaturefulElementDragging extends ElementDragging {
         this.origX = ev.pageX
         this.origY = ev.pageY
 
-        this.windowScrollController = new WindowScrollControllerCache(new WindowScrollController())
-        this.mirror.start(ev.subjectEl as HTMLElement, ev.pageX, ev.pageY, this.windowScrollController)
-        this.autoScroller.start(ev.pageX, ev.pageY, this.windowScrollController)
+        this.mirror.start(ev.subjectEl as HTMLElement, ev.pageX, ev.pageY)
+        this.autoScroller.start(ev.pageX, ev.pageY)
 
         this.startDelay(ev)
 
@@ -85,8 +82,10 @@ export default class FeaturefulElementDragging extends ElementDragging {
 
       this.emitter.trigger('pointermove', ev)
 
-      this.mirror.handleMove(ev.pageX, ev.pageY)
-      this.autoScroller.handleMove(ev.pageX, ev.pageY)
+      if (ev.origEvent.type !== 'scroll') {
+        this.mirror.handleMove(ev.pageX, ev.pageY)
+        this.autoScroller.handleMove(ev.pageX, ev.pageY)
+      }
 
       if (!this.isDistanceSurpassed) {
         let dx = ev.pageX - this.origX!
@@ -117,8 +116,6 @@ export default class FeaturefulElementDragging extends ElementDragging {
 
       if (!this.pointer.shouldIgnoreMove) { // because these things wouldn't have been started otherwise
         this.autoScroller.stop()
-        this.windowScrollController.destroy()
-        this.windowScrollController = null
       }
 
       if (this.isDragging) {

+ 64 - 33
src/dnd/scroll.ts

@@ -83,11 +83,7 @@ export class ElScrollController extends ScrollController {
 
 }
 
-export class WindowScrollController extends ElScrollController {
-
-  constructor() {
-    super(document.documentElement)
-  }
+export class WindowScrollController extends ScrollController {
 
   getScrollTop() {
     return window.scrollY
@@ -105,48 +101,74 @@ export class WindowScrollController extends ElScrollController {
     window.scroll(n, window.scrollY)
   }
 
+  getScrollWidth() {
+    return document.documentElement.scrollWidth
+  }
+
+  getScrollHeight() {
+    return document.documentElement.scrollHeight
+  }
+
+  getClientHeight() {
+    return document.documentElement.clientHeight
+  }
+
+  getClientWidth() {
+    return document.documentElement.clientWidth
+  }
+
 }
 
+// TODO: more of a "dimensions" cache
 export abstract class ScrollControllerCache extends ScrollController {
 
   rect: Rect
   scrollController: ScrollController
 
-  protected scrollTop: number
-  protected scrollLeft: number
+  doesListening: boolean
+
+  origScrollTop: number
+  origScrollLeft: number
+
+  scrollTop: number
+  scrollLeft: number
   protected scrollWidth: number
   protected scrollHeight: number
   protected clientWidth: number
   protected clientHeight: number
 
-  constructor(scrollController: ScrollController) {
+  constructor(scrollController: ScrollController, doesListening) {
     super()
     this.scrollController = scrollController
-    this.scrollTop = scrollController.getScrollTop()
-    this.scrollLeft = scrollController.getScrollLeft()
+    this.doesListening = doesListening
+    this.scrollTop = this.origScrollTop = scrollController.getScrollTop()
+    this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft()
     this.scrollWidth = scrollController.getScrollWidth()
     this.scrollHeight = scrollController.getScrollHeight()
     this.clientWidth = scrollController.getClientWidth()
     this.clientHeight = scrollController.getClientHeight()
     this.rect = this.computeRect() // do last in case it needs cached values
-    this.getEventTarget().addEventListener('scroll', this.handleScroll)
+
+    if (this.doesListening) {
+      this.getEventTarget().addEventListener('scroll', this.handleScroll)
+    }
   }
 
+  abstract getEventTarget(): EventTarget
+  abstract computeRect(): Rect
+
   destroy() {
-    this.getEventTarget().removeEventListener('scroll', this.handleScroll)
+    if (this.doesListening) {
+      this.getEventTarget().removeEventListener('scroll', this.handleScroll)
+    }
   }
 
   handleScroll = () => {
     this.scrollTop = this.scrollController.getScrollTop()
     this.scrollLeft = this.scrollController.getScrollLeft()
-    this._handleScroll()
+    this.handleScrollChange()
   }
 
-  _handleScroll() { }
-
-  abstract computeRect(): Rect
-  abstract getEventTarget(): EventTarget
-
   getScrollTop() {
     return this.scrollTop
   }
@@ -157,14 +179,20 @@ export abstract class ScrollControllerCache extends ScrollController {
 
   setScrollTop(n) {
     this.scrollController.setScrollTop(n)
-    this.scrollTop = Math.max(Math.min(n, this.getMaxScrollTop()), 0) // in meantime before handleScroll
-    this._handleScroll()
+
+    if (!this.doesListening) {
+      this.scrollTop = Math.max(Math.min(n, this.getMaxScrollTop()), 0)
+      this.handleScrollChange()
+    }
   }
 
   setScrollLeft(n) {
     this.scrollController.setScrollLeft(n)
-    this.scrollLeft = Math.max(Math.min(n, this.getMaxScrollLeft()), 0) // in meantime before handleScroll
-    this._handleScroll()
+
+    if (!this.doesListening) {
+      this.scrollLeft = Math.max(Math.min(n, this.getMaxScrollLeft()), 0)
+      this.handleScrollChange()
+    }
   }
 
   getClientWidth() {
@@ -183,40 +211,43 @@ export abstract class ScrollControllerCache extends ScrollController {
     return this.scrollHeight
   }
 
+  handleScrollChange() {
+  }
+
 }
 
 export class ElScrollControllerCache extends ScrollControllerCache {
 
   scrollController: ElScrollController
 
-  computeRect() {
-    return computeRect(this.scrollController.el)
-  }
-
   getEventTarget(): EventTarget {
     return this.scrollController.el
   }
 
+  computeRect() {
+    return computeRect(this.scrollController.el)
+  }
+
 }
 
 export class WindowScrollControllerCache extends ScrollControllerCache {
 
   scrollController: WindowScrollController
 
+  getEventTarget(): EventTarget {
+    return window
+  }
+
   computeRect(): Rect {
     return { // computeViewportRect needed anymore?
       left: this.scrollLeft,
-      right: this.scrollLeft + this.clientWidth, // clientWidth best?
+      right: this.scrollLeft + this.clientWidth,
       top: this.scrollTop,
-      bottom: this.scrollTop + this.clientHeight // clientHeight best?
+      bottom: this.scrollTop + this.clientHeight
     }
   }
 
-  getEventTarget(): EventTarget {
-    return window
-  }
-
-  _handleScroll() {
+  handleScrollChange() {
     this.rect = this.computeRect()
   }
 

+ 1 - 0
src/interactions/DateClicking.ts

@@ -18,6 +18,7 @@ export default class DateClicking {
 
     // we DO want to watch pointer moves because otherwise finalHit won't get populated
     this.dragging = new FeaturefulElementDragging(component.el)
+    this.dragging.autoScroller.isEnabled = false
 
     let hitDragging = this.hitDragging = new HitDragging(this.dragging, component)
     hitDragging.emitter.on('pointerdown', this.handlePointerDown)

+ 17 - 5
src/util/dom-geom.ts

@@ -1,5 +1,5 @@
 import { createElement, removeElement } from './dom-manip'
-import { Rect } from './geom'
+import { Rect, intersectRects } from './geom'
 
 export interface EdgeInfo {
   borderLeft: number
@@ -102,8 +102,8 @@ export function computeHeightAndMargins(el: HTMLElement) {
 }
 
 
-// will return null of no scroll parent. will NOT return window/body
-export function getScrollParent(el: HTMLElement): HTMLElement | null {
+export function getClippingParents(el: HTMLElement): HTMLElement[] {
+  let parents: HTMLElement[] = []
 
   while (el instanceof HTMLElement) { // will stop when gets to document or null
     let computedStyle = window.getComputedStyle(el)
@@ -113,13 +113,25 @@ export function getScrollParent(el: HTMLElement): HTMLElement | null {
     }
 
     if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) {
-      return el
+      parents.push(el)
     }
 
     el = el.parentNode as HTMLElement
   }
 
-  return null
+  return parents
+}
+
+
+export function computeClippingRect(el: HTMLElement): Rect {
+  return getClippingParents(el)
+    .map(function(el) {
+      return computeInnerRect(el)
+    })
+    .concat(computeViewportRect())
+    .reduce(function(rect0, rect1) {
+      return intersectRects(rect0, rect1) || rect1 // should always intersect
+    })
 }