public subscribe()

in packages/react/src/utilities/dragdrop/DragDropHelper.tsx [57:247]


  public subscribe(
    root: HTMLElement,
    events: EventGroup,
    dragDropOptions: IDragDropOptions,
  ): {
    key: string;
    dispose(): void;
  } {
    if (!this._initialized) {
      this._events = new EventGroup(this);

      const doc = getDocument();

      // clear drag data when mouse up, use capture event to ensure it will be run
      if (doc) {
        this._events.on(doc.body, 'mouseup', this._onMouseUp.bind(this), true);
        this._events.on(doc, 'mouseup', this._onDocumentMouseUp.bind(this), true);
      }

      this._initialized = true;
    }

    const { key = `${++this._lastId}` } = dragDropOptions;

    const handlers: {
      callback: (context: IDragDropContext, event?: any) => void;
      eventName: string;
    }[] = [];

    let onDragStart: (event: DragEvent) => void;
    let onDragLeave: (event: DragEvent) => void;
    let onDragEnter: (event: DragEvent) => void;
    let onDragEnd: (event: DragEvent) => void;
    let onDrop: (event: DragEvent) => void;
    let onDragOver: (event: DragEvent) => void;
    let onMouseDown: (event: MouseEvent) => void;

    let isDraggable: boolean;
    let isDroppable: boolean;

    let activeTarget: {
      target: IDragDropTarget;
      dispose: () => void;
    };

    if (dragDropOptions && root) {
      const { eventMap, context, updateDropState } = dragDropOptions;

      const dragDropTarget: IDragDropTarget = {
        root: root,
        options: dragDropOptions,
        key: key,
      };

      isDraggable = this._isDraggable(dragDropTarget);
      isDroppable = this._isDroppable(dragDropTarget);

      if (isDraggable || isDroppable) {
        if (eventMap) {
          for (const event of eventMap) {
            const handler = {
              callback: event.callback.bind(null, context),
              eventName: event.eventName,
            };

            handlers.push(handler);

            this._events.on(root, handler.eventName, handler.callback);
          }
        }
      }

      if (isDroppable) {
        // If the target is droppable, wire up global event listeners to track drop-related events.
        onDragLeave = (event: DragEvent) => {
          if (!(event as IDragDropEvent).isHandled) {
            (event as IDragDropEvent).isHandled = true;
            this._dragEnterCounts[key]--;
            if (this._dragEnterCounts[key] === 0) {
              updateDropState(false /* isDropping */, event);
            }
          }
        };

        onDragEnter = (event: DragEvent) => {
          event.preventDefault(); // needed for IE
          if (!(event as IDragDropEvent).isHandled) {
            (event as IDragDropEvent).isHandled = true;
            this._dragEnterCounts[key]++;
            if (this._dragEnterCounts[key] === 1) {
              updateDropState(true /* isDropping */, event);
            }
          }
        };

        onDragEnd = (event: DragEvent) => {
          this._dragEnterCounts[key] = 0;
          updateDropState(false /* isDropping */, event);
        };

        onDrop = (event: DragEvent) => {
          this._dragEnterCounts[key] = 0;
          updateDropState(false /* isDropping */, event);

          if (dragDropOptions.onDrop) {
            dragDropOptions.onDrop(dragDropOptions.context.data, event);
          }
        };

        onDragOver = (event: DragEvent) => {
          event.preventDefault();
          if (dragDropOptions.onDragOver) {
            dragDropOptions.onDragOver(dragDropOptions.context.data, event);
          }
        };

        this._dragEnterCounts[key] = 0;

        // dragenter and dragleave will be fired when hover to the child element
        // but we only want to change state when enter or leave the current element
        // use the count to ensure it.
        events.on(root, 'dragenter', onDragEnter);
        events.on(root, 'dragleave', onDragLeave);
        events.on(root, 'dragend', onDragEnd);
        events.on(root, 'drop', onDrop);
        events.on(root, 'dragover', onDragOver);
      }

      if (isDraggable) {
        // If the target is draggable, wire up local event listeners for mouse events.
        onMouseDown = this._onMouseDown.bind(this, dragDropTarget);
        onDragEnd = this._onDragEnd.bind(this, dragDropTarget);

        // We need to add in data so that on Firefox we show the ghost element when dragging
        onDragStart = (event: DragEvent) => {
          const options = dragDropOptions;
          if (options && options.onDragStart) {
            options.onDragStart(options.context.data, options.context.index, this._selection.getSelection(), event);
          }
          this._isDragging = true;
          if (event.dataTransfer) {
            event.dataTransfer.setData('id', root.id);
          }
        };

        events.on(root, 'dragstart', onDragStart);
        events.on(root, 'mousedown', onMouseDown);
        events.on(root, 'dragend', onDragEnd);
      }

      activeTarget = {
        target: dragDropTarget,
        dispose: () => {
          if (this._activeTargets[key] === activeTarget) {
            delete this._activeTargets[key];
          }

          if (root) {
            for (const handler of handlers) {
              this._events.off(root, handler.eventName, handler.callback);
            }

            if (isDroppable) {
              events.off(root, 'dragenter', onDragEnter);
              events.off(root, 'dragleave', onDragLeave);
              events.off(root, 'dragend', onDragEnd);
              events.off(root, 'dragover', onDragOver);
              events.off(root, 'drop', onDrop);
            }

            if (isDraggable) {
              events.off(root, 'dragstart', onDragStart);
              events.off(root, 'mousedown', onMouseDown);
              events.off(root, 'dragend', onDragEnd);
            }
          }
        },
      };

      this._activeTargets[key] = activeTarget;
    }

    return {
      key: key,
      dispose: () => {
        if (activeTarget) {
          activeTarget.dispose();
        }
      },
    };
  }