Adam Shaw 4 роки тому
батько
коміт
d1eee288f0

+ 1 - 1
packages-premium

@@ -1 +1 @@
-Subproject commit ab2791f615dcb53bc6b779dd2025cd715278fc7b
+Subproject commit 501f3f2c2dff395eee152c12c8a13b504e7f1234

+ 0 - 42
packages/common/src/component/DateComponent.ts

@@ -1,12 +1,8 @@
 import { BaseComponent } from '../vdom-util'
 import { EventRenderRange } from './event-rendering'
-import { DateSpan } from '../structs/date-span'
 import { EventInstanceHash } from '../structs/event-instance'
-import { rangeContainsRange } from '../datelib/date-range'
 import { Hit } from '../interactions/hit'
 import { elementClosest } from '../util/dom-manip'
-import { isDateSelectionValid, isInteractionValid } from '../validation'
-import { EventInteractionState } from '../interactions/event-interaction-state'
 import { guid } from '../util/misc'
 import { Dictionary } from '../options'
 
@@ -40,13 +36,6 @@ PURPOSES:
 export abstract class DateComponent<Props=Dictionary, State=Dictionary> extends BaseComponent<Props, State> {
   uid = guid()
 
-  // IN SCHEDULER: allowAcrossResources
-
-  // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
-  // of the date areas. if not defined, assumes to be day and time granularity.
-  // TODO: port isTimeScale into same system?
-  largeUnit: any
-
   // Hit System
   // -----------------------------------------------------------------------------------------------------------------
 
@@ -57,37 +46,6 @@ export abstract class DateComponent<Props=Dictionary, State=Dictionary> extends
     return null // this should be abstract
   }
 
-  // Validation
-  // -----------------------------------------------------------------------------------------------------------------
-
-  isInteractionValid(interaction: EventInteractionState) {
-    let { dateProfile } = this.props as any // HACK
-    let { instances } = interaction.mutatedEvents
-
-    if (dateProfile) { // HACK for MorePopover
-      for (let instanceId in instances) {
-        if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
-          return false
-        }
-      }
-    }
-
-    return isInteractionValid(interaction, this.context)
-  }
-
-  isDateSelectionValid(selection: DateSpan): boolean {
-    let { dateProfile } = this.props as any // HACK
-
-    if (
-      dateProfile && // HACK for MorePopover
-      !rangeContainsRange(dateProfile.validRange, selection.range)
-    ) {
-      return false
-    }
-
-    return isDateSelectionValid(selection, this.context)
-  }
-
   // Pointer Interaction Utils
   // -----------------------------------------------------------------------------------------------------------------
 

+ 0 - 4
packages/common/src/interactions/event-resizing.ts

@@ -1,4 +0,0 @@
-import { Hit } from './hit'
-import { Dictionary } from '../options'
-
-export type EventResizeJoinTransforms = (hit0: Hit, hit1: Hit) => false | Dictionary

+ 6 - 2
packages/common/src/interactions/hit.ts

@@ -1,11 +1,15 @@
-import { DateComponent } from '../component/DateComponent'
+import { DateProfile } from '../DateProfileGenerator'
 import { DateSpan } from '../structs/date-span'
 import { Rect } from '../util/geom'
+import { ViewContext } from '../ViewContext'
 
 export interface Hit {
-  component: DateComponent<any>
+  componentId?: string // will be set by HitDragging
+  context?: ViewContext // will be set by HitDragging
+  dateProfile: DateProfile
   dateSpan: DateSpan
   dayEl: HTMLElement
   rect: Rect
   layer: number
+  largeUnit?: string // TODO: have timeline set this!
 }

+ 6 - 1
packages/common/src/interactions/interaction.ts

@@ -1,10 +1,13 @@
 import { DateComponent } from '../component/DateComponent'
+import { Hit } from './hit'
 
 export abstract class Interaction {
   component: DateComponent<any>
+  isHitComboAllowed: (hit0: Hit, hit1: Hit) => boolean
 
   constructor(settings: InteractionSettings) {
     this.component = settings.component
+    this.isHitComboAllowed = settings.isHitComboAllowed || null
   }
 
   destroy() {
@@ -16,13 +19,14 @@ export type InteractionClass = { new(settings: InteractionSettings): Interaction
 export interface InteractionSettingsInput {
   el: HTMLElement
   useEventCenter?: boolean
-  // TODO: add largeUnit
+  isHitComboAllowed?: (hit0: Hit, hit1: Hit) => boolean
 }
 
 export interface InteractionSettings {
   component: DateComponent<any>
   el: HTMLElement
   useEventCenter: boolean
+  isHitComboAllowed: ((hit0: Hit, hit1: Hit) => boolean) | null
 }
 
 export type InteractionSettingsStore = { [componenUid: string]: InteractionSettings }
@@ -32,6 +36,7 @@ export function parseInteractionSettings(component: DateComponent<any>, input: I
     component,
     el: input.el,
     useEventCenter: input.useEventCenter != null ? input.useEventCenter : true,
+    isHitComboAllowed: input.isHitComboAllowed || null,
   }
 }
 

+ 0 - 1
packages/common/src/main.ts

@@ -178,7 +178,6 @@ export { PointerDragEvent } from './interactions/pointer'
 export { Hit } from './interactions/hit'
 export { dateSelectionJoinTransformer } from './interactions/date-selecting'
 export { eventDragMutationMassager, EventDropTransformers } from './interactions/event-dragging'
-export { EventResizeJoinTransforms } from './interactions/event-resizing'
 export { ElementDragging } from './interactions/ElementDragging'
 
 export { config } from './global-config'

+ 0 - 3
packages/common/src/plugin-system-struct.ts

@@ -9,7 +9,6 @@ import { CalendarContext } from './CalendarContext'
 import { isPropsValidTester } from './structs/constraint'
 import { eventDragMutationMassager, eventIsDraggableTransformer, EventDropTransformers } from './interactions/event-dragging'
 import { dateSelectionJoinTransformer } from './interactions/date-selecting'
-import { EventResizeJoinTransforms } from './interactions/event-resizing'
 import { ExternalDefTransform } from './interactions/external-element-dragging'
 import { InteractionClass } from './interactions/interaction'
 import { ThemeClass } from './theme/Theme'
@@ -44,7 +43,6 @@ export interface PluginDefInput {
   viewPropsTransformers?: ViewPropsTransformerClass[]
   isPropsValid?: isPropsValidTester
   externalDefTransforms?: ExternalDefTransform[]
-  eventResizeJoinTransforms?: EventResizeJoinTransforms[]
   viewContainerAppends?: ViewContainerAppend[]
   eventDropTransformers?: EventDropTransformers[]
   componentInteractions?: InteractionClass[]
@@ -81,7 +79,6 @@ export interface PluginHooks {
   viewPropsTransformers: ViewPropsTransformerClass[]
   isPropsValid: isPropsValidTester | null
   externalDefTransforms: ExternalDefTransform[]
-  eventResizeJoinTransforms: EventResizeJoinTransforms[]
   viewContainerAppends: ViewContainerAppend[]
   eventDropTransformers: EventDropTransformers[]
   componentInteractions: InteractionClass[]

+ 0 - 3
packages/common/src/plugin-system.ts

@@ -24,7 +24,6 @@ export function createPlugin(input: PluginDefInput): PluginDef {
     viewPropsTransformers: input.viewPropsTransformers || [],
     isPropsValid: input.isPropsValid || null,
     externalDefTransforms: input.externalDefTransforms || [],
-    eventResizeJoinTransforms: input.eventResizeJoinTransforms || [],
     viewContainerAppends: input.viewContainerAppends || [],
     eventDropTransformers: input.eventDropTransformers || [],
     componentInteractions: input.componentInteractions || [],
@@ -64,7 +63,6 @@ function buildPluginHooks(pluginDefs: PluginDef[], globalDefs: PluginDef[]): Plu
     viewPropsTransformers: [],
     isPropsValid: null,
     externalDefTransforms: [],
-    eventResizeJoinTransforms: [],
     viewContainerAppends: [],
     eventDropTransformers: [],
     componentInteractions: [],
@@ -136,7 +134,6 @@ function combineHooks(hooks0: PluginHooks, hooks1: PluginHooks): PluginHooks {
     viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
     isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
     externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
-    eventResizeJoinTransforms: hooks0.eventResizeJoinTransforms.concat(hooks1.eventResizeJoinTransforms),
     viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
     eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
     calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),

+ 20 - 2
packages/common/src/validation.ts

@@ -11,15 +11,33 @@ import { CalendarContext } from './CalendarContext'
 import { buildDateSpanApiWithContext } from './calendar-utils'
 import { Constraint } from './structs/constraint'
 import { expandRecurring } from './structs/recurring-event'
+import { DateProfile } from './DateProfileGenerator'
 
 // high-level segmenting-aware tester functions
 // ------------------------------------------------------------------------------------------------------------------------
 
-export function isInteractionValid(interaction: EventInteractionState, context: CalendarContext) {
+export function isInteractionValid(
+  interaction: EventInteractionState,
+  dateProfile: DateProfile,
+  context: CalendarContext
+) {
+  let { instances } = interaction.mutatedEvents
+  for (let instanceId in instances) {
+    if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
+      return false
+    }
+  }
   return isNewPropsValid({ eventDrag: interaction }, context) // HACK: the eventDrag props is used for ALL interactions
 }
 
-export function isDateSelectionValid(dateSelection: DateSpan, context: CalendarContext) {
+export function isDateSelectionValid(
+  dateSelection: DateSpan,
+  dateProfile: DateProfile,
+  context: CalendarContext,
+) {
+  if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) {
+    return false
+  }
   return isNewPropsValid({ dateSelection }, context)
 }
 

+ 1 - 1
packages/daygrid/src/DayTable.tsx

@@ -86,7 +86,7 @@ export class DayTable extends DateComponent<DayTableProps, ViewContext> {
 
     if (rawHit) {
       return {
-        component: this,
+        dateProfile: this.props.dateProfile,
         dateSpan: rawHit.dateSpan,
         dayEl: rawHit.dayEl,
         rect: {

+ 23 - 24
packages/daygrid/src/MorePopover.tsx

@@ -9,7 +9,7 @@ import {
   DayCellRoot,
   DayCellContent,
   DateProfile,
-  createRef,
+  Hit,
 } from '@fullcalendar/common'
 import { TableSeg } from './TableSeg'
 import { TableBlockEvent } from './TableBlockEvent'
@@ -30,7 +30,7 @@ export interface MorePopoverProps {
 }
 
 export class MorePopover extends DateComponent<MorePopoverProps> {
-  private rootElRef = createRef<HTMLElement>()
+  rootEl: HTMLElement
 
   render() {
     let { options, dateEnv } = this.context
@@ -39,7 +39,7 @@ export class MorePopover extends DateComponent<MorePopoverProps> {
     let title = dateEnv.format(date, options.dayPopoverFormat)
 
     return (
-      <DayCellRoot date={date} dateProfile={dateProfile} todayRange={todayRange} elRef={this.rootElRef}>
+      <DayCellRoot date={date} dateProfile={dateProfile} todayRange={todayRange} elRef={this.handleRootEl}>
         {(rootElRef, dayClassNames, dataAttrs) => (
           <Popover
             elRef={rootElRef}
@@ -58,7 +58,6 @@ export class MorePopover extends DateComponent<MorePopoverProps> {
             </DayCellContent>
             {props.segs.map((seg) => {
               let instanceId = seg.eventRange.instance.instanceId
-
               return (
                 <div
                   className="fc-daygrid-event-harness"
@@ -95,36 +94,36 @@ export class MorePopover extends DateComponent<MorePopoverProps> {
     )
   }
 
-  positionToHit(positionLeft: number, positionTop: number, originEl: HTMLElement) {
-    let rootEl = this.rootElRef.current
-
-    if (!originEl || !rootEl) { // why?
-      return null
+  handleRootEl = (rootEl: HTMLDivElement | null) => {
+    this.rootEl = rootEl
+    if (rootEl) {
+      this.context.registerInteractiveComponent(this, { el: rootEl })
+    } else {
+      this.context.unregisterInteractiveComponent(this)
     }
+  }
 
-    let originRect = originEl.getBoundingClientRect()
-    let elRect = rootEl.getBoundingClientRect()
-    let newOriginLeft = elRect.left - originRect.left
-    let newOriginTop = elRect.top - originRect.top
-    let localLeft = positionLeft - newOriginLeft
-    let localTop = positionTop - newOriginTop
-    let date = this.props.date
+  queryHit(positionLeft: number, positionTop: number, elWidth: number, elHeight: number): Hit {
+    let { rootEl, props } = this
+    let { date } = props
 
-    if ( // ugly way to detect intersection
-      localLeft >= 0 && localLeft < elRect.width &&
-      localTop >= 0 && localTop < elRect.height
+    if (
+      positionLeft >= 0 && positionLeft < elWidth &&
+      positionTop >= 0 && positionTop < elHeight
     ) {
       return {
+        dateProfile: props.dateProfile,
         dateSpan: {
+          resourceId: '',
           allDay: true,
           range: { start: date, end: addDays(date, 1) },
         },
         dayEl: rootEl,
-        relativeRect: {
-          left: newOriginLeft,
-          top: newOriginTop,
-          right: elRect.width,
-          bottom: elRect.height,
+        rect: {
+          left: 0,
+          top: 0,
+          right: elWidth,
+          bottom: elHeight,
         },
         layer: 1, // important when comparing with hits from other components
       }

+ 3 - 3
packages/interaction/src/interactions-external/ExternalElementDragging.ts

@@ -78,7 +78,7 @@ export class ExternalElementDragging {
     }
 
     if (hit) {
-      receivingContext = hit.component.context
+      receivingContext = hit.context
 
       if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, receivingContext)) {
         droppableEvent = computeEventForDateSpan(
@@ -88,7 +88,7 @@ export class ExternalElementDragging {
         )
 
         interaction.mutatedEvents = eventTupleToStore(droppableEvent)
-        isInvalid = !isInteractionValid(interaction, receivingContext)
+        isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext)
 
         if (isInvalid) {
           interaction.mutatedEvents = createEmptyEventStore()
@@ -126,7 +126,7 @@ export class ExternalElementDragging {
 
     if (receivingContext && droppableEvent) {
       let finalHit = this.hitDragging.finalHit!
-      let finalView = finalHit.component.context.viewApi
+      let finalView = finalHit.context.viewApi
       let dragMeta = this.dragMeta!
 
       receivingContext.emitter.trigger('drop', {

+ 14 - 6
packages/interaction/src/interactions/DateSelecting.ts

@@ -7,6 +7,7 @@ import {
 import { __assign } from 'tslib'
 import { HitDragging } from './HitDragging'
 import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging'
+import { isDateSelectionValid } from '@fullcalendar/common/tsc/validation'
 
 /*
 Tracks when the user selects a portion of time of a component,
@@ -62,13 +63,20 @@ export class DateSelecting extends Interaction {
     let isInvalid = false
 
     if (hit) {
-      dragSelection = joinHitsIntoSelection(
-        this.hitDragging.initialHit!,
-        hit,
-        context.pluginHooks.dateSelectionTransformers,
-      )
+      let initialHit = this.hitDragging.initialHit!
+      let disallowed = hit.componentId === initialHit.componentId
+        && this.isHitComboAllowed
+        && !this.isHitComboAllowed(initialHit, hit)
+
+      if (!disallowed) {
+        dragSelection = joinHitsIntoSelection(
+          initialHit,
+          hit,
+          context.pluginHooks.dateSelectionTransformers,
+        )
+      }
 
-      if (!dragSelection || !this.component.isDateSelectionValid(dragSelection)) {
+      if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
         isInvalid = true
         dragSelection = null
       }

+ 9 - 10
packages/interaction/src/interactions/EventDragging.ts

@@ -18,6 +18,7 @@ import {
   buildEventApis,
   EventAddArg,
   EventRemoveArg,
+  isInteractionValid,
 } from '@fullcalendar/common'
 import { __assign } from 'tslib'
 import { HitDragging, isHitsEqual } from './HitDragging'
@@ -165,8 +166,7 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
     }
 
     if (hit) {
-      let receivingComponent = hit.component
-      receivingContext = receivingComponent.context
+      receivingContext = hit.context
       let receivingOptions = receivingContext.options
 
       if (
@@ -184,10 +184,9 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
           )
           interaction.mutatedEvents = mutatedRelevantEvents
 
-          if (!receivingComponent.isInteractionValid(interaction)) {
+          if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) {
             isInvalid = true
             mutation = null
-
             mutatedRelevantEvents = null
             interaction.mutatedEvents = createEmptyEventStore()
           }
@@ -356,13 +355,13 @@ export class EventDragging extends Interaction { // TODO: rename to EventSelecti
             ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext),
             draggedEl: ev.subjectEl as HTMLElement,
             jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655
-            view: finalHit.component.context.viewApi,
+            view: finalHit.context.viewApi,
           })
 
           receivingContext.emitter.trigger('eventReceive', {
             ...eventAddArg,
             draggedEl: ev.subjectEl as HTMLElement,
-            view: finalHit.component.context.viewApi,
+            view: finalHit.context.viewApi,
           })
         }
       } else {
@@ -437,7 +436,7 @@ function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutation
 
   if (dateSpan0.allDay !== dateSpan1.allDay) {
     standardProps.allDay = dateSpan1.allDay
-    standardProps.hasEnd = hit1.component.context.options.allDayMaintainDuration
+    standardProps.hasEnd = hit1.context.options.allDayMaintainDuration
 
     if (dateSpan1.allDay) {
       // means date1 is already start-of-day,
@@ -448,9 +447,9 @@ function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutation
 
   let delta = diffDates(
     date0, date1,
-    hit0.component.context.dateEnv,
-    hit0.component === hit1.component ?
-      hit0.component.largeUnit :
+    hit0.context.dateEnv,
+    hit0.componentId === hit1.componentId ?
+      hit0.largeUnit :
       null,
   )
 

+ 18 - 32
packages/interaction/src/interactions/EventResizing.ts

@@ -10,8 +10,7 @@ import {
   EventRenderRange, getElSeg,
   createDuration,
   EventInteractionState,
-  EventResizeJoinTransforms,
-  Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis,
+  Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid,
 } from '@fullcalendar/common'
 import { __assign } from 'tslib'
 import { HitDragging, isHitsEqual } from './HitDragging'
@@ -119,23 +118,27 @@ export class EventResizing extends Interaction {
     }
 
     if (hit) {
-      mutation = computeMutation(
-        initialHit,
-        hit,
-        (ev.subjectEl as HTMLElement).classList.contains('fc-event-resizer-start'),
-        eventInstance.range,
-        context.pluginHooks.eventResizeJoinTransforms,
-      )
+      let disallowed = hit.componentId === initialHit.componentId
+        && this.isHitComboAllowed
+        && !this.isHitComboAllowed(initialHit, hit)
+
+      if (!disallowed) {
+        mutation = computeMutation(
+          initialHit,
+          hit,
+          (ev.subjectEl as HTMLElement).classList.contains('fc-event-resizer-start'),
+          eventInstance.range,
+        )
+      }
     }
 
     if (mutation) {
       mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context)
       interaction.mutatedEvents = mutatedRelevantEvents
 
-      if (!this.component.isInteractionValid(interaction)) {
+      if (!isInteractionValid(interaction, hit.dateProfile, context)) {
         isInvalid = true
         mutation = null
-
         mutatedRelevantEvents = null
         interaction.mutatedEvents = null
       }
@@ -237,40 +240,23 @@ function computeMutation(
   hit1: Hit,
   isFromStart: boolean,
   instanceRange: DateRange,
-  transforms: EventResizeJoinTransforms[],
 ): EventMutation | null {
-  let dateEnv = hit0.component.context.dateEnv
+  let dateEnv = hit0.context.dateEnv
   let date0 = hit0.dateSpan.range.start
   let date1 = hit1.dateSpan.range.start
 
   let delta = diffDates(
     date0, date1,
     dateEnv,
-    hit0.component.largeUnit,
+    hit0.largeUnit,
   )
 
-  let props = {} as EventMutation
-
-  for (let transform of transforms) {
-    let res = transform(hit0, hit1)
-
-    if (res === false) {
-      return null
-    }
-
-    if (res) {
-      __assign(props, res)
-    }
-  }
-
   if (isFromStart) {
     if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) {
-      props.startDelta = delta
-      return props
+      return { startDelta: delta }
     }
   } else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) {
-    props.endDelta = delta
-    return props
+    return { endDelta: delta }
   }
 
   return null

+ 6 - 8
packages/interaction/src/interactions/HitDragging.ts

@@ -87,7 +87,6 @@ export class HitDragging {
     }
 
     let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top)
-
     if (initialHit) {
       if (this.useSubjectCenter && subjectRect) {
         let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect)
@@ -142,7 +141,6 @@ export class HitDragging {
   prepareHits() {
     this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => {
       interactionSettings.component.prepareHits()
-
       return new OffsetTracker(interactionSettings.el)
     })
   }
@@ -183,16 +181,16 @@ export class HitDragging {
           positionTop >= 0 && positionTop < height
         ) {
           let hit = component.queryHit(positionLeft, positionTop, width, height)
-          let dateProfile = component.context.getCurrentData().dateProfile
-
           if (
-            hit &&
-            (
-              // make sure the hit is within activeRange, meaning it's not a deal cell
-              rangeContainsRange(dateProfile.activeRange, hit.dateSpan.range)
+            hit && (
+              // make sure the hit is within activeRange, meaning it's not a dead cell
+              rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)
             ) &&
             (!bestHit || hit.layer > bestHit.layer)
           ) {
+            hit.componentId = id
+            hit.context = component.context
+
             // TODO: better way to re-orient rectangle
             hit.rect.left += originLeft
             hit.rect.right += originLeft

+ 1 - 1
packages/timegrid/src/DayTimeCols.tsx

@@ -102,7 +102,7 @@ export class DayTimeCols extends DateComponent<DayTimeColsProps> {
 
     if (rawHit) {
       return {
-        component: this,
+        dateProfile: this.props.dateProfile,
         dateSpan: rawHit.dateSpan,
         dayEl: rawHit.dayEl,
         rect: {