reflow/index.js

import { newInteraction } from '@interactjs/core/interactions';
import {
  arr,
  is,
  extend,
  rect as rectUtils,
  pointer as pointerUtils,
  win,
} from '@interactjs/utils';

export function init (scope) {
  const {
    actions,
    interactions,
    /** @lends Interactable */
    Interactable,
  } = scope;

  // add action reflow event types
  for (const actionName of actions.names) {
    actions.eventTypes.push(`${actionName}reflow`);
  }

  // remove completed reflow interactions
  interactions.signals.on('stop', ({ interaction }) => {
    if (interaction.pointerType === 'reflow') {
      interaction._reflowResolve();
      arr.remove(scope.interactions.list, interaction);
    }
  });

  /**
   * ```js
   * const interactable = interact(target);
   * const drag = { name: drag, axis: 'x' };
   * const resize = { name: resize, edges: { left: true, bottom: true };
   *
   * interactable.reflow(drag);
   * interactable.reflow(resize);
   * ```
   *
   * Start an action sequence to re-apply modifiers, check drops, etc.
   *
   * @param { Object } action The action to begin
   * @param { string } action.name The name of the action
   * @returns { Promise<Interactable> }
   */
  Interactable.prototype.reflow = function (action) {
    return reflow(this, action, scope);
  };
}

function reflow (interactable, action, scope) {
  const elements = is.string(interactable.target)
    ? arr.from(interactable._context.querySelectorAll(interactable.target))
    : [interactable.target];

  const Promise = win.window.Promise;
  const promises = Promise ? [] : null;

  for (const element of elements) {
    const rect = interactable.getRect(element);

    if (!rect) { break; }

    const runningInteraction = arr.find(
      scope.interactions.list,
      interaction => {
        return interaction.interacting() &&
          interaction.target === interactable &&
          interaction.element === element &&
          interaction.prepared.name === action.name;
      });
    let reflowPromise;

    if (runningInteraction) {
      runningInteraction.move();

      reflowPromise = runningInteraction._reflowPromise || new Promise(resolve => {
        runningInteraction._reflowResolve = resolve;
      });
    }
    else {
      const xywh = rectUtils.tlbrToXywh(rect);
      const coords = {
        page     : { x: xywh.x, y: xywh.y },
        client   : { x: xywh.x, y: xywh.y },
        timeStamp: Date.now(),
      };

      const event = pointerUtils.coordsToEvent(coords);
      reflowPromise = startReflow(scope, interactable, element, action, event);
    }

    if (promises) {
      promises.push(reflowPromise);
    }
  }

  return promises && Promise.all(promises).then(() => interactable);
}

function startReflow (scope, interactable, element, action, event) {
  const interaction = newInteraction({ pointerType: 'reflow' }, scope);
  const signalArg = {
    interaction,
    event,
    pointer: event,
    eventTarget: element,
    phase: 'reflow',
  };

  interaction.target = interactable;
  interaction.element = element;
  interaction.prepared = extend({}, action);
  interaction.prevEvent = event;
  interaction.updatePointer(event, event, element, true);

  interaction._doPhase(signalArg);

  const reflowPromise = win.window.Promise
    ? new win.window.Promise((resolve) => {
      interaction._reflowResolve = resolve;
    })
    : null;

  signalArg.phase = 'start';
  interaction._reflowPromise = reflowPromise;
  interaction._interacting = interaction._doPhase(signalArg);

  if (interaction._interacting) {
    interaction.move(signalArg);
    interaction.end(event);
  }
  else {
    interaction.stop();
  }

  interaction.removePointer(event, event);
  interaction.pointerIsDown = false;

  return reflowPromise;
}

export default { init };