All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.router.transition.hook-builder.js Maven / Gradle / Ivy

import { assertPredicate, unnestR } from "../../shared/common";
import { TransitionHookPhase, TransitionHookScope } from "./interface";
import { TransitionHook } from "./transition-hook";
/**
 * This class returns applicable TransitionHooks for a specific Transition instance.
 *
 * Hooks ([[RegisteredHook]]) may be registered globally, e.g., $transitions.onEnter(...), or locally, e.g.
 * myTransition.onEnter(...).  The HookBuilder finds matching RegisteredHooks (where the match criteria is
 * determined by the type of hook)
 *
 * The HookBuilder also converts RegisteredHooks objects to TransitionHook objects, which are used to run a Transition.
 *
 * The HookBuilder constructor is given the $transitions service and a Transition instance.  Thus, a HookBuilder
 * instance may only be used for one specific Transition object. (side note: the _treeChanges accessor is private
 * in the Transition class, so we must also provide the Transition's _treeChanges)
 */
export class HookBuilder {
  constructor(transition) {
    this.transition = transition;
  }
  buildHooksForPhase(phase) {
    const $transitions = this.transition.transitionService;
    return $transitions._pluginapi
      ._getEvents(phase)
      .map((type) => this.buildHooks(type))
      .reduce(unnestR, [])
      .filter(Boolean);
  }
  /**
   * Returns an array of newly built TransitionHook objects.
   *
   * - Finds all RegisteredHooks registered for the given `hookType` which matched the transition's [[TreeChanges]].
   * - Finds [[PathNode]] (or `PathNode[]`) to use as the TransitionHook context(s)
   * - For each of the [[PathNode]]s, creates a TransitionHook
   *
   * @param hookType the type of the hook registration function, e.g., 'onEnter', 'onFinish'.
   */
  buildHooks(hookType) {
    const transition = this.transition;
    const treeChanges = transition.treeChanges();
    // Find all the matching registered hooks for a given hook type
    const matchingHooks = this.getMatchingHooks(
      hookType,
      treeChanges,
      transition,
    );
    if (!matchingHooks) return [];
    const baseHookOptions = {
      transition: transition,
      current: transition.options().current,
    };
    const makeTransitionHooks = (hook) => {
      // Fetch the Nodes that caused this hook to match.
      const matches = hook.matches(treeChanges, transition);
      // Select the PathNode[] that will be used as TransitionHook context objects
      const matchingNodes = matches[hookType.criteriaMatchPath.name];
      // Return an array of HookTuples
      return matchingNodes.map((node) => {
        const _options = Object.assign(
          {
            bind: hook.bind,
            traceData: { hookType: hookType.name, context: node },
          },
          baseHookOptions,
        );
        const state =
          hookType.criteriaMatchPath.scope === TransitionHookScope.STATE
            ? node.state.self
            : null;
        const transitionHook = new TransitionHook(
          transition,
          state,
          hook,
          _options,
        );
        return { hook, node, transitionHook };
      });
    };
    return matchingHooks
      .map(makeTransitionHooks)
      .reduce(unnestR, [])
      .sort(tupleSort(hookType.reverseSort))
      .map((tuple) => tuple.transitionHook);
  }
  /**
   * Finds all RegisteredHooks from:
   * - The Transition object instance hook registry
   * - The TransitionService ($transitions) global hook registry
   *
   * which matched:
   * - the eventType
   * - the matchCriteria (to, from, exiting, retained, entering)
   *
   * @returns an array of matched [[RegisteredHook]]s
   */
  getMatchingHooks(hookType, treeChanges, transition) {
    const isCreate = hookType.hookPhase === TransitionHookPhase.CREATE;
    // Instance and Global hook registries
    const $transitions = this.transition.transitionService;
    const registries = isCreate
      ? [$transitions]
      : [this.transition, $transitions];
    return registries
      .map((reg) => reg.getHooks(hookType.name)) // Get named hooks from registries
      .filter(
        assertPredicate(Array.isArray, `broken event named: ${hookType.name}`),
      ) // Sanity check
      .reduce(unnestR, []) // Un-nest RegisteredHook[][] to RegisteredHook[] array
      .filter((hook) => hook.matches(treeChanges, transition)); // Only those satisfying matchCriteria
  }
}
/**
 * A factory for a sort function for HookTuples.
 *
 * The sort function first compares the PathNode depth (how deep in the state tree a node is), then compares
 * the EventHook priority.
 *
 * @param reverseDepthSort a boolean, when true, reverses the sort order for the node depth
 * @returns a tuple sort function
 */
function tupleSort(reverseDepthSort = false) {
  return function nodeDepthThenPriority(l, r) {
    const factor = reverseDepthSort ? -1 : 1;
    const depthDelta =
      (l.node.state.path.length - r.node.state.path.length) * factor;
    return depthDelta !== 0 ? depthDelta : r.hook.priority - l.hook.priority;
  };
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy