DateSelecting.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import {
  2. compareNumbers, enableCursor, disableCursor, DateComponent, Hit,
  3. DateSpan, PointerDragEvent, dateSelectionJoinTransformer,
  4. Interaction, InteractionSettings, interactionSettingsToStore,
  5. triggerDateSelect, isDateSelectionValid,
  6. } from '@fullcalendar/core/internal'
  7. import { HitDragging } from './HitDragging.js'
  8. import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging.js'
  9. /*
  10. Tracks when the user selects a portion of time of a component,
  11. constituted by a drag over date cells, with a possible delay at the beginning of the drag.
  12. */
  13. export class DateSelecting extends Interaction {
  14. dragging: FeaturefulElementDragging
  15. hitDragging: HitDragging
  16. dragSelection: DateSpan | null = null
  17. constructor(settings: InteractionSettings) {
  18. super(settings)
  19. let { component } = settings
  20. let { options } = component.context
  21. let dragging = this.dragging = new FeaturefulElementDragging(settings.el)
  22. dragging.touchScrollAllowed = false
  23. dragging.minDistance = options.selectMinDistance || 0
  24. dragging.autoScroller.isEnabled = options.dragScroll
  25. let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings))
  26. hitDragging.emitter.on('pointerdown', this.handlePointerDown)
  27. hitDragging.emitter.on('dragstart', this.handleDragStart)
  28. hitDragging.emitter.on('hitupdate', this.handleHitUpdate)
  29. hitDragging.emitter.on('pointerup', this.handlePointerUp)
  30. }
  31. destroy() {
  32. this.dragging.destroy()
  33. }
  34. handlePointerDown = (ev: PointerDragEvent) => {
  35. let { component, dragging } = this
  36. let { options } = component.context
  37. let canSelect = options.selectable &&
  38. component.isValidDateDownEl(ev.origEvent.target as HTMLElement)
  39. // don't bother to watch expensive moves if component won't do selection
  40. dragging.setIgnoreMove(!canSelect)
  41. // if touch, require user to hold down
  42. dragging.delay = ev.isTouch ? getComponentTouchDelay(component) : null
  43. }
  44. handleDragStart = (ev: PointerDragEvent) => {
  45. this.component.context.calendarApi.unselect(ev) // unselect previous selections
  46. }
  47. handleHitUpdate = (hit: Hit | null, isFinal: boolean) => {
  48. let { context } = this.component
  49. let dragSelection: DateSpan | null = null
  50. let isInvalid = false
  51. if (hit) {
  52. let initialHit = this.hitDragging.initialHit!
  53. let disallowed = hit.componentId === initialHit.componentId
  54. && this.isHitComboAllowed
  55. && !this.isHitComboAllowed(initialHit, hit)
  56. if (!disallowed) {
  57. dragSelection = joinHitsIntoSelection(
  58. initialHit,
  59. hit,
  60. context.pluginHooks.dateSelectionTransformers,
  61. )
  62. }
  63. if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
  64. isInvalid = true
  65. dragSelection = null
  66. }
  67. }
  68. if (dragSelection) {
  69. context.dispatch({ type: 'SELECT_DATES', selection: dragSelection })
  70. } else if (!isFinal) { // only unselect if moved away while dragging
  71. context.dispatch({ type: 'UNSELECT_DATES' })
  72. }
  73. if (!isInvalid) {
  74. enableCursor()
  75. } else {
  76. disableCursor()
  77. }
  78. if (!isFinal) {
  79. this.dragSelection = dragSelection // only clear if moved away from all hits while dragging
  80. }
  81. }
  82. handlePointerUp = (pev: PointerDragEvent) => {
  83. if (this.dragSelection) {
  84. // selection is already rendered, so just need to report selection
  85. triggerDateSelect(this.dragSelection, pev, this.component.context)
  86. this.dragSelection = null
  87. }
  88. }
  89. }
  90. function getComponentTouchDelay(component: DateComponent<any>): number {
  91. let { options } = component.context
  92. let delay = options.selectLongPressDelay
  93. if (delay == null) {
  94. delay = options.longPressDelay
  95. }
  96. return delay
  97. }
  98. function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan {
  99. let dateSpan0 = hit0.dateSpan
  100. let dateSpan1 = hit1.dateSpan
  101. let ms = [
  102. dateSpan0.range.start,
  103. dateSpan0.range.end,
  104. dateSpan1.range.start,
  105. dateSpan1.range.end,
  106. ]
  107. ms.sort(compareNumbers)
  108. let props = {} as DateSpan
  109. for (let transformer of dateSelectionTransformers) {
  110. let res = transformer(hit0, hit1)
  111. if (res === false) {
  112. return null
  113. }
  114. if (res) {
  115. Object.assign(props, res)
  116. }
  117. }
  118. props.range = { start: ms[0], end: ms[3] }
  119. props.allDay = dateSpan0.allDay
  120. return props
  121. }