Skip to content

Commit

Permalink
make hit system relative. move unselect out of core Calendar
Browse files Browse the repository at this point in the history
  • Loading branch information
arshaw committed Jan 27, 2019
1 parent 99baabd commit cb2cc6f
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 216 deletions.
55 changes: 6 additions & 49 deletions src/core/Calendar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { elementClosest } from './util/dom-manip'
import { listenBySelector } from './util/dom-event'
import { capitaliseFirstLetter, debounce } from './util/misc'
import { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
Expand Down Expand Up @@ -39,8 +38,7 @@ import DateClicking from './interactions/DateClicking'
import DateSelecting from './interactions/DateSelecting'
import EventDragging from './interactions/EventDragging'
import EventResizing from './interactions/EventResizing'
import PointerDragging from './dnd/PointerDragging'

import UnselectAuto from './interactions/UnselectAuto'

export interface DateClickApi extends DatePointApi {
dayEl: HTMLElement
Expand Down Expand Up @@ -90,10 +88,8 @@ export default class Calendar {
defaultTimedEventDuration: Duration

interactionsStore: { [componentUid: string]: Interaction[] } = {}

unselectAuto: UnselectAuto // TODO: kill
removeNavLinkListener: any
documentPointer: PointerDragging // for unfocusing
isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system

windowResizeProxy: any
isResizing: boolean
Expand Down Expand Up @@ -136,6 +132,8 @@ export default class Calendar {
this.handleOptions(this.optionsManager.computed)
this.publiclyTrigger('_init') // for tests
this.hydrate()

this.unselectAuto = new UnselectAuto(this)
}


Expand Down Expand Up @@ -165,6 +163,8 @@ export default class Calendar {
this.unbindHandlers()
this.component.destroy() // don't null-out. in case API needs access
this.component = null

this.unselectAuto.destroy()
}
}

Expand Down Expand Up @@ -196,11 +196,6 @@ export default class Calendar {
}
})

let documentPointer = this.documentPointer = new PointerDragging(document)
documentPointer.shouldIgnoreMove = true
documentPointer.shouldWatchScroll = false
documentPointer.emitter.on('pointerup', this.onDocumentPointerUp)

if (this.opt('handleWindowResize')) {
window.addEventListener('resize',
this.windowResizeProxy = debounce( // prevents rapid calls
Expand All @@ -214,8 +209,6 @@ export default class Calendar {
unbindHandlers() {
this.removeNavLinkListener()

this.documentPointer.destroy()

if (this.windowResizeProxy) {
window.removeEventListener('resize', this.windowResizeProxy)
this.windowResizeProxy = null
Expand Down Expand Up @@ -933,10 +926,6 @@ export default class Calendar {
arg.view = this.view

this.publiclyTrigger('select', [ arg ])

if (pev) {
this.isRecentPointerDateSelect = true
}
}


Expand Down Expand Up @@ -988,38 +977,6 @@ export default class Calendar {
}


// for unfocusing selections
onDocumentPointerUp = (pev: PointerDragEvent) => {
let { state, documentPointer } = this

// touch-scrolling should never unfocus any type of selection
if (!documentPointer.wasTouchScroll) {

if (
state.dateSelection && // an existing date selection?
!this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
) {
let unselectAuto = this.viewOpt('unselectAuto')
let unselectCancel = this.viewOpt('unselectCancel')

if (unselectAuto && (!unselectAuto || !elementClosest(documentPointer.downEl, unselectCancel))) {
this.unselect(pev)
}
}

if (
state.eventSelection && // an existing event selected?
!elementClosest(documentPointer.downEl, EventDragging.SELECTOR) // interaction DIDN'T start on an event
) {
this.dispatch({ type: 'UNSELECT_EVENT' })
}

}

this.isRecentPointerDateSelect = false
}


// Date Utils
// -----------------------------------------------------------------------------------------------------------------

Expand Down
17 changes: 5 additions & 12 deletions src/core/common/OffsetTracker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getClippingParents, computeRect } from '../util/dom-geom'
import { pointInsideRect } from '../util/geom'
import { pointInsideRect, Rect } from '../util/geom'
import { ElementScrollGeomCache } from '../common/scroll-geom-cache'

/*
Expand All @@ -13,17 +13,10 @@ and an determine if a given point is inside the combined clipping rectangle.
export default class OffsetTracker { // ElementOffsetTracker

scrollCaches: ElementScrollGeomCache[]
origLeft: number
origTop: number
origRight: number
origBottom: number // TODO: use rect?
origRect: Rect

constructor(el: HTMLElement) {
let rect = computeRect(el)
this.origLeft = rect.left
this.origTop = rect.top
this.origRight = rect.right
this.origBottom = rect.bottom
this.origRect = computeRect(el)

// will work fine for divs that have overflow:hidden
this.scrollCaches = getClippingParents(el).map(function(el) {
Expand All @@ -38,7 +31,7 @@ export default class OffsetTracker { // ElementOffsetTracker
}

computeLeft() {
let left = this.origLeft
let left = this.origRect.left

for (let scrollCache of this.scrollCaches) {
left += scrollCache.origScrollLeft - scrollCache.getScrollLeft()
Expand All @@ -48,7 +41,7 @@ export default class OffsetTracker { // ElementOffsetTracker
}

computeTop() {
let top = this.origTop
let top = this.origRect.top

for (let scrollCache of this.scrollCaches) {
top += scrollCache.origScrollTop - scrollCache.getScrollTop()
Expand Down
20 changes: 1 addition & 19 deletions src/core/component/DateComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
fillRenderer: FillRenderer

el: HTMLElement // passed in to constructor
needHitsDepth: number = 0


constructor(context: ComponentContext, el: HTMLElement, isView?: boolean) {
Expand Down Expand Up @@ -106,25 +105,8 @@ export default class DateComponent<PropsType> extends Component<PropsType> {
// Hit System
// -----------------------------------------------------------------------------------------------------------------

requestPrepareHits() {
if (!(this.needHitsDepth++)) {
this.prepareHits()
}
}

requestReleaseHits() {
if (!(--this.needHitsDepth)) {
this.releaseHits()
}
}

protected prepareHits() {
}

protected releaseHits() {
}

queryHit(leftOffset, topOffset): Hit | null {
queryHit(positionLeft: number, positionTop: number, elWidth: number, elHeight: number): Hit | null {
return null // this should be abstract
}

Expand Down
76 changes: 52 additions & 24 deletions src/core/interactions/HitDragging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { constrainPoint, intersectRects, getRectCenter, diffPoints, Point } from
import { rangeContainsRange } from '../datelib/date-range'
import { Hit } from './hit'
import { InteractionSettingsStore } from './interaction'
import OffsetTracker from '../common/OffsetTracker'
import { mapHash } from '../util/object'

/*
Tracks movement over multiple droppable areas (aka "hits")
Expand All @@ -32,6 +34,7 @@ export default class HitDragging {
requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events

// internal state
offsetTrackers: { [componentUid: string]: OffsetTracker }
initialHit: Hit | null = null
movingHit: Hit | null = null
finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove
Expand Down Expand Up @@ -81,7 +84,7 @@ export default class HitDragging {
adjustedPoint = constrainPoint(adjustedPoint, subjectRect)
}

let initialHit = this.initialHit = this.queryHit(adjustedPoint.left, adjustedPoint.top)
let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top)

if (initialHit) {
if (this.useSubjectCenter && subjectRect) {
Expand Down Expand Up @@ -123,7 +126,7 @@ export default class HitDragging {
}

handleMove(ev: PointerDragEvent, forceHandle?: boolean) {
let hit = this.queryHit(
let hit = this.queryHitForOffset(
ev.pageX + this.coordAdjust!.left,
ev.pageY + this.coordAdjust!.top
)
Expand All @@ -135,39 +138,64 @@ export default class HitDragging {
}

prepareHits() {
let { droppableStore } = this

for (let id in droppableStore) {
droppableStore[id].component.requestPrepareHits()
}
this.offsetTrackers = mapHash(this.droppableStore, function(interactionSettings) {
return new OffsetTracker(interactionSettings.el)
})
}

releaseHits() {
let { droppableStore } = this
let { offsetTrackers } = this

for (let id in droppableStore) {
droppableStore[id].component.requestReleaseHits()
for (let id in offsetTrackers) {
offsetTrackers[id].destroy()
}

this.offsetTrackers = {}
}

queryHit(x: number, y: number): Hit | null {
let { droppableStore } = this
queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null {
let { droppableStore, offsetTrackers } = this
let bestHit: Hit | null = null

for (let id in droppableStore) {
let component = droppableStore[id].component
let hit = component.queryHit(x, y)

if (
hit &&
(
// make sure the hit is within activeRange, meaning it's not a deal cell
!component.props.dateProfile || // hack for DayTile
rangeContainsRange(component.props.dateProfile.activeRange, hit.dateSpan.range)
) &&
(!bestHit || hit.layer > bestHit.layer)
) {
bestHit = hit
let offsetTracker = offsetTrackers[id]

if (offsetTracker.isWithinClipping(offsetLeft, offsetTop)) {
let originLeft = offsetTracker.computeLeft()
let originTop = offsetTracker.computeTop()
let positionLeft = offsetLeft - originLeft
let positionTop = offsetTop - originTop
let { origRect } = offsetTracker
let width = origRect.right - origRect.left
let height = origRect.bottom - origRect.top

if (
// must be within the element's bounds
positionLeft >= 0 && positionTop < width &&
positionTop >= 0 && positionTop < height
) {
let hit = component.queryHit(positionLeft, positionTop, width, height)

if (
hit &&
(
// make sure the hit is within activeRange, meaning it's not a deal cell
!component.props.dateProfile || // hack for DayTile
rangeContainsRange(component.props.dateProfile.activeRange, hit.dateSpan.range)
) &&
(!bestHit || hit.layer > bestHit.layer)
) {

// TODO: better way to re-orient rectangle
hit.rect.left += originLeft
hit.rect.right += originLeft
hit.rect.top += originTop
hit.rect.bottom += originTop

bestHit = hit
}
}
}
}

Expand Down
70 changes: 70 additions & 0 deletions src/core/interactions/UnselectAuto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Calendar, { DateSelectionApi } from '../Calendar'
import PointerDragging from '../dnd/PointerDragging'
import { PointerDragEvent } from './pointer'
import { elementClosest } from '../util/dom-manip'
import EventDragging from './EventDragging'

export default class UnselectAuto {

calendar: Calendar
documentPointer: PointerDragging // for unfocusing
isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system

constructor(calendar: Calendar) {
this.calendar = calendar

let documentPointer = this.documentPointer = new PointerDragging(document)
documentPointer.shouldIgnoreMove = true
documentPointer.shouldWatchScroll = false
documentPointer.emitter.on('pointerup', this.onDocumentPointerUp)

/*
TODO: better way to know about whether there was a selection with the pointer
*/
calendar.on('select', this.onSelect)
}

destroy() {
this.calendar.off('select', this.onSelect)
this.documentPointer.destroy()
}

onSelect = (selectInfo: DateSelectionApi) => {
if (selectInfo.jsEvent) {
this.isRecentPointerDateSelect = true
}
}

onDocumentPointerUp = (pev: PointerDragEvent) => {
let { calendar, documentPointer } = this
let { state } = calendar

// touch-scrolling should never unfocus any type of selection
if (!documentPointer.wasTouchScroll) {

if (
state.dateSelection && // an existing date selection?
!this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
) {
let unselectAuto = calendar.viewOpt('unselectAuto')
let unselectCancel = calendar.viewOpt('unselectCancel')

if (unselectAuto && (!unselectAuto || !elementClosest(documentPointer.downEl, unselectCancel))) {
calendar.unselect(pev)
}
}

if (
state.eventSelection && // an existing event selected?
!elementClosest(documentPointer.downEl, EventDragging.SELECTOR) // interaction DIDN'T start on an event
) {
calendar.dispatch({ type: 'UNSELECT_EVENT' })
}

}

this.isRecentPointerDateSelect = false
}

}

0 comments on commit cb2cc6f

Please sign in to comment.