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

components/component-outline/component-outline.sortable.js
(function () {
  'use strict';

  ((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