mercury_editor-2.0.x-dev/components/component-outline/src/component-outline.sortable.js

components/component-outline/src/component-outline.sortable.js
((Drupal, once, Sortable) => {
  /**
   * Returns a list of errors for the "accepts" Sortable callback, or an empty array if there are no errors.
   * @param {Element} settings The builder settings.
   * @param {Element} el The element being moved.
   * @param {Element} target The destination
   * @param {Element} source The source
   * @param {Element} sibling The next sibling element
   * @return {Array} An array of errors.
   */
  function acceptsErrors(settings, el, target, source, sibling) {
    return Drupal._lpbMoveErrors.accepts
      .map((validator) =>
        validator.apply(null, [settings, el, target, source, sibling]),
      )
      .filter((errors) => errors !== false && errors !== undefined);
  }

  /**
   * Returns a list of errors for the "moves" Sortable callback, or an empty array if there are no errors.
   * @param {Element} settings The builder settings.
   * @param {Element} el The element being moved.
   * @param {Element} source The source
   * @param {Element} handle The drag handle being grabbed
   * @return {Array} An array of errors.
   */
  function movesErrors(settings, el, source, handle) {
    const errors = Drupal._lpbMoveErrors.moves
      .map((validator) => validator.apply(null, [settings, el, source, handle]))
      .filter((errors) => errors !== false && errors !== undefined);
    return errors;
  }

  function onReorder(mercuryEditorEntityId, layoutId, componentUuid) {
    const order = Array.from(
      document.querySelectorAll('.me-component-outline__list .me-component-outline__component')
    ).map(
      (item) => {
        return {
          uuid: item.getAttribute('data-uuid'),
          parentUuid:
            item.parentNode
              .closest('[data-uuid]')
              ?.getAttribute('data-uuid') || null,
          region:
            item.closest('[data-region]')?.getAttribute('data-region') || null,
        };
      },
    );
    const reorderEvent = new CustomEvent('mercuryEditorComponentAction', {
      detail: {
        mercuryEditorEntityId: mercuryEditorEntityId,
        layoutParagraphsLayoutId: layoutId,
        action: 'reorder',
        components: order,
        componentUuid
      },
    });
    document.dispatchEvent(reorderEvent);
  }

  Drupal.behaviors.mercuryEditorComponentOutlineSortable = {
    attach: function attach(context, drupalSettings) {
      once('me-component-outline-sortable', '.me-component-outline__list, .me-component-outline__components', context).forEach((element) => {
        // Initialize SortableJS on the outline list.
        const layoutId = element.closest('[data-layout-id]').getAttribute('data-layout-id');
        const mercuryEditorEntityId = element.closest('[data-mercury-editor-id]').getAttribute('data-mercury-editor-id');
        const settings = drupalSettings.lpBuilder[layoutId] || {};
        Sortable.create(element, {
          draggable: '.me-component-outline__component',
          group: `components-${layoutId}`,
          direction: 'vertical',
          invertSwap: true,
          swapThreshold: .5,
          ghostClass: 'me-component-outline-sortable-ghost',
          filter: (event) =>
            movesErrors(settings, null, null, event.target).length > 0,
          onStart: (event) => {
            // Clone the element and add a class for styling.
            // This is the element that will be left in its original place to
            // retain original dimensions and prevent content reflow while
            // dragging.
            const original = event.item;
            const clone = original.cloneNode(true); // Clone the element
            original.closest('.me-component-outline__list').classList.add('me-is-sorting');
            clone.querySelector('.me-component-outline-drag-image')?.remove();
            clone.classList.add('me-component-outline-sortable-placeholder'); // Add a class for styling
            clone.classList.remove('me-component-outline-sortable-ghost');
            original.parentNode.insertBefore(clone, original); // Keep the clone in place
          },
          onMove: (event, originalEvent) => {
            // Item cannot be dragged into the destination.
            if (
              acceptsErrors(
                settings,
                event.dragged,
                event.to,
                event.from,
                event.related,
              ).length > 0
            ) {
              if (event.to) {
                event.to.classList.add('sortable-to--disallowed');
              }
              return false;
            }
            // Prevent dropping directly before the cloned placeholder.
            if (event.related && event.to) {
              const children = Array.from(event.to.children);
              const index = children.indexOf(event.related);
              const nextSibling = event.willInsertAfter
                ? children[index + 1]
                : children[index];
              if (
                nextSibling &&
                nextSibling.classList.contains('me-component-outline-sortable-placeholder')
              ) {
                return false;
              }
            }
          },
          onEnd: (event) => {
            // Remove the cloned placeholder element.
            document
              .querySelectorAll('.me-component-outline-sortable-placeholder')
              .forEach((placeholderElement) => placeholderElement.remove());
            const { item }  = event;
            item.closest('.me-component-outline__list').classList.remove('me-is-sorting');
            function getDepth(node) {
              let depth = 0;
              while (node = node.parentNode.closest('.me-component-outline__component')) {
                depth++;
              }
              return depth;
            }
            item.style.setProperty('--me-treeitem-depth', getDepth(item));
            item.querySelectorAll('.me-component-outline__region, .me-component-outline__component').forEach((child) => {
              child.style.setProperty('--me-treeitem-depth', getDepth(child));
            });
            const uuid = item.closest('[data-uuid]').getAttribute('data-uuid');
            onReorder(mercuryEditorEntityId, layoutId, uuid);
          },
        });
      });
    }
  };

  /**
   * Sets a custom drag image for components being dragged for better control of styling.
   */
  document.addEventListener('dragstart', (event) => {
    if (event.target.classList.contains('me-component-outline__component')) {
      const dragImage = event.target.cloneNode(true);
      dragImage.classList.add('me-component-outline-drag-image');
      // Remove all child nodes except the controls.
      dragImage.childNodes.forEach((child) => {
        if (child.classList && !child.classList.contains('me-component-outline__component-controls')) {
          child.remove();
        }
      });
      Object.assign(dragImage.style, {
        position: 'absolute',
        top: '-9999px',
        left: '-9999px',
        pointerEvents: 'none',
        width: `${event.target.offsetWidth}px`,
      });
      // Temporarily add the drag image to the DOM to ensure styles are applied.
      event.target.prepend(dragImage);

      // Calculate the mouse offset within the source element
      const srcRect = event.target.getBoundingClientRect();
      const mouseX = event.clientX;
      const mouseY = event.clientY;
      const rawOffsetX = mouseX - srcRect.left;
      const rawOffsetY = mouseY - srcRect.top;

      event.target.classList.add('me-component-outline-is-dragging');
      event.dataTransfer.setDragImage(dragImage, rawOffsetX, rawOffsetY);
      // Remove element once drag image is captured.
      setTimeout(() => {
        dragImage.remove();
      }, 0);
    }
  });

})(Drupal, once, Sortable);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc