claro-8.x-1.x-dev/js/tabledrag.es6.js

js/tabledrag.es6.js
/**
 * @file
 * Overrides tabledrag.js that provides dragging capabilities.
 *
 * - New Drupal.theme.tableDragHandle() function for tabledrag handle markup
 * - New Drupal.theme.tableDragToggle() function for tabledrag toggle markup
 * - New Drupal.theme.tableDragToggleWrapper() function for the wrapper of the
 *   tabledrag toggle (@todo: https://www.drupal.org/node/3084916).
 * - Tabledrag functionality can be disabled
 * - The initial content of the tabledrag-cell is wrapped into a new DOM element
 *   ".tabledrag-cell-content__item". This new element is moved into an another
 *   ".tabledrag-cell-content" division that contains the drag handle, the
 *   identation elements and the tabledrag changed mark as well.
 *   This is needed to keep all of these element in a single line
 *   Claro introduced two theme functions for these:
 *   - Drupal.theme.tableDragCellContentWrapper() provides the output of the
 *     original content of the first table cell.
 *   - Drupal.theme.tableDragCellItemsWrapper() provides the markup of the
 *     common wrapper for every tabledrag cell elements including the
 *     indentation(s), the drag-handle, the original content and the tabledrag
 *     changed marker.
 * - Fixes the RTL bug of the original tabledrag.js
 * - Tabledrag changed mark is added next to the drag-handle, and not after the
 *   last item. (@todo: https://www.drupal.org/node/3084910).
 *
 * The '_slicedToArray' shim added for handling destructured arrays breaks IE11,
 * that is why the 'prefer-destructuring' rule is disabled.
 *
 * @todo Refactor after https://www.drupal.org/node/3077938,
 */
 
/**
 * Triggers when weights columns are toggled.
 *
 * @event columnschange
 */
 
/* eslint-disable default-case, new-cap, prefer-destructuring */
(($, Drupal, drupalSettings) => {
  /**
   * Store the state of weight columns display for all tables.
   *
   * Default value is to hide weight columns.
   */
  let showWeight = JSON.parse(
    localStorage.getItem("Drupal.tableDrag.showWeight")
  );
 
  /**
   * Drag and drop table rows with field manipulation.
   *
   * Using the drupal_attach_tabledrag() function, any table with weights or
   * parent relationships may be made into draggable tables. Columns containing
   * a field may optionally be hidden, providing a better user experience.
   *
   * Created tableDrag instances may be modified with custom behaviors by
   * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
   * See blocks.js for an example of adding additional functionality to
   * tableDrag.
   *
   * @type {Drupal~behavior}
   */
  Drupal.behaviors.tableDrag = {
    attach(context, settings) {
      function initTableDrag(table, base) {
        if (table.length) {
          // Create the new tableDrag instance. Save in the Drupal variable
          // to allow other scripts access to the object.
          Drupal.tableDrag[base] = new Drupal.tableDrag(
            table[0],
            settings.tableDrag[base]
          );
        }
      }
 
      Object.keys(settings.tableDrag || {}).forEach(base => {
        initTableDrag(
          $(context)
            .find(`#${base}`)
            .once("tabledrag"),
          base
        );
      });
    }
  };
 
  /**
   * Provides table and field manipulation.
   *
   * @constructor
   *
   * @param {HTMLElement} table
   *   DOM object for the table to be made draggable.
   * @param {object} tableSettings
   *   Settings for the table added via drupal_add_dragtable().
   */
  Drupal.tableDrag = function init(table, tableSettings) {
    const self = this;
    const $table = $(table);
 
    /**
     * @type {jQuery}
     */
    this.$table = $(table);
 
    /**
     *
     * @type {HTMLElement}
     */
    this.table = table;
 
    /**
     * @type {object}
     */
    this.tableSettings = tableSettings;
 
    /**
     * Used to hold information about a current drag operation.
     *
     * @type {?HTMLElement}
     */
    this.dragObject = null;
 
    /**
     * Provides operations for row manipulation.
     *
     * @type {?HTMLElement}
     */
    this.rowObject = null;
 
    /**
     * Remember the previous element.
     *
     * @type {?HTMLElement}
     */
    this.oldRowElement = null;
 
    /**
     * Used to determine up or down direction from last mouse move.
     *
     * @type {number}
     */
    this.oldY = 0;
 
    /**
     * Whether anything in the entire table has changed.
     *
     * @type {bool}
     */
    this.changed = false;
 
    /**
     * Maximum amount of allowed parenting.
     *
     * @type {number}
     */
    this.maxDepth = 0;
 
    /**
     * Direction of the table.
     *
     * @type {number}
     */
    this.rtl = $(this.table).css("direction") === "rtl" ? -1 : 1;
 
    /**
     *
     * @type {bool}
     */
    this.striping = $(this.table).data("striping") === 1;
 
    /**
     * Configure the scroll settings.
     *
     * @type {object}
     *
     * @prop {number} amount
     * @prop {number} interval
     * @prop {number} trigger
     */
    this.scrollSettings = { amount: 4, interval: 50, trigger: 70 };
 
    /**
     *
     * @type {?number}
     */
    this.scrollInterval = null;
 
    /**
     *
     * @type {number}
     */
    this.scrollY = 0;
 
    /**
     *
     * @type {number}
     */
    this.windowHeight = 0;
 
    /**
     * Check this table's settings for parent relationships.
     *
     * For efficiency, large sections of code can be skipped if we don't need to
     * track horizontal movement and indentations.
     *
     * @type {bool}
     */
    this.indentEnabled = false;
    Object.keys(tableSettings || {}).forEach(group => {
      Object.keys(tableSettings[group] || {}).forEach(n => {
        if (tableSettings[group][n].relationship === "parent") {
          this.indentEnabled = true;
        }
        if (tableSettings[group][n].limit > 0) {
          this.maxDepth = tableSettings[group][n].limit;
        }
      });
    });
    if (this.indentEnabled) {
      /**
       * Total width of indents, set in makeDraggable.
       *
       * @type {number}
       */
      this.indentCount = 1;
      // Find the width of indentations to measure mouse movements against.
      // Because the table doesn't need to start with any indentations, we
      // manually append 2 indentations in the first draggable row, measure
      // the offset, then remove.
      const indent = Drupal.theme("tableDragIndentation");
      const testRow = $("<tr/>")
        .addClass("draggable")
        .appendTo(table);
      const testCell = $("<td/>")
        .appendTo(testRow)
        .prepend(indent)
        .prepend(indent);
      const $indentation = testCell.find(".js-indentation");
 
      /**
       * @type {number}
       */
      this.indentAmount =
        $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft;
      testRow.remove();
    }
 
    // Make each applicable row draggable.
    // Match immediate children of the parent element to allow nesting.
    $table
      .find("> tr.draggable, > tbody > tr.draggable")
      .each(function initDraggable() {
        self.makeDraggable(this);
      });
 
    // Add the toggle link wrapper before the table that will contain the toggle
    // for users to show or hide weight columns.
    $table.before(
      $(Drupal.theme("tableDragToggleWrapper"))
        .addClass("js-tabledrag-toggle-weight-wrapper")
        .on(
          "click",
          ".js-tabledrag-toggle-weight",
          $.proxy(function toggleColumns(event) {
            event.preventDefault();
            this.toggleColumns();
          }, this)
        )
    );
 
    // Initialize the specified columns (for example, weight or parent columns)
    // to show or hide according to user preference. This aids accessibility
    // so that, e.g., screen reader users can choose to enter weight values and
    // manipulate form elements directly, rather than using drag-and-drop..
    self.initColumns();
 
    // Add event bindings to the document. The self variable is passed along
    // as event handlers do not have direct access to the tableDrag object.
    $(document).on("touchmove", event =>
      self.dragRow(event.originalEvent.touches[0], self)
    );
    $(document).on("touchend", event =>
      self.dropRow(event.originalEvent.touches[0], self)
    );
    $(document).on("mousemove pointermove", event => self.dragRow(event, self));
    $(document).on("mouseup pointerup", event => self.dropRow(event, self));
 
    // React to localStorage event showing or hiding weight columns.
    $(window).on(
      "storage",
      $.proxy(function weightColumnDisplayChange(event) {
        // Only react to 'Drupal.tableDrag.showWeight' value change.
        if (event.originalEvent.key === "Drupal.tableDrag.showWeight") {
          // This was changed in another window, get the new value for this
          // window.
          showWeight = JSON.parse(event.originalEvent.newValue);
          this.displayColumns(showWeight);
        }
      }, this)
    );
  };
 
  $.extend(Drupal.tableDrag.prototype, {
    /**
     * Initialize columns containing form elements to be hidden by default.
     *
     * Identify and mark each cell with a CSS class so we can easily toggle
     * show/hide it. Finally, hide columns if user does not have a
     * 'Drupal.tableDrag.showWeight' localStorage value.
     */
    initColumns() {
      const { $table } = this;
      let hidden;
      let cell;
      let columnIndex;
      Object.keys(this.tableSettings || {}).forEach(group => {
        // Find the first field in this group.
        Object.keys(this.tableSettings[group]).some(tableSetting => {
          const field = $table
            .find(`.${this.tableSettings[group][tableSetting].target}`)
            .eq(0);
          if (field.length && this.tableSettings[group][tableSetting].hidden) {
            hidden = this.tableSettings[group][tableSetting].hidden;
            cell = field.closest("td");
            return true;
          }
          return false;
        });
 
        // Mark the column containing this field so it can be hidden.
        if (hidden && cell[0]) {
          // Add 1 to our indexes. The nth-child selector is 1 based, not 0
          // based. Match immediate children of the parent element to allow
          // nesting.
          columnIndex =
            cell
              .parent()
              .find("> td")
              .index(cell.get(0)) + 1;
          $table
            .find("> thead > tr, > tbody > tr, > tr")
            .each(this.addColspanClass(columnIndex));
        }
      });
      this.displayColumns(showWeight);
    },
 
    /**
     * Mark cells that have colspan.
     *
     * In order to adjust the colspan instead of hiding them altogether.
     *
     * @param {number} columnIndex
     *   The column index to add colspan class to.
     *
     * @return {function}
     *   Function to add colspan class.
     */
    addColspanClass(columnIndex) {
      return function addColspanClass() {
        // Get the columnIndex and adjust for any colspans in this row.
        const $row = $(this);
        let index = columnIndex;
        const cells = $row.children();
        let cell;
        cells.each(function checkColspan(n) {
          if (n < index && this.colSpan && this.colSpan > 1) {
            index -= this.colSpan - 1;
          }
        });
        if (index > 0) {
          cell = cells.filter(`:nth-child(${index})`);
          if (cell[0].colSpan && cell[0].colSpan > 1) {
            // If this cell has a colspan, mark it so we can reduce the colspan.
            cell.addClass("tabledrag-has-colspan");
          } else {
            // Mark this cell so we can hide it.
            cell.addClass("tabledrag-hide");
          }
        }
      };
    },
 
    /**
     * Hide or display weight columns. Triggers an event on change.
     *
     * @fires event:columnschange
     *
     * @param {bool} displayWeight
     *   'true' will show weight columns.
     */
    displayColumns(displayWeight) {
      if (displayWeight) {
        this.showColumns();
      }
      // Default action is to hide columns.
      else {
        this.hideColumns();
      }
      // Trigger an event to allow other scripts to react to this display change.
      // Force the extra parameter as a bool.
      $("table")
        .findOnce("tabledrag")
        .trigger("columnschange", !!displayWeight);
    },
 
    /**
     * Toggle the weight column depending on 'showWeight' value.
     *
     * Store only default override.
     */
    toggleColumns() {
      showWeight = !showWeight;
      this.displayColumns(showWeight);
      if (showWeight) {
        // Save default override.
        localStorage.setItem("Drupal.tableDrag.showWeight", showWeight);
      } else {
        // Reset the value to its default.
        localStorage.removeItem("Drupal.tableDrag.showWeight");
      }
    },
 
    /**
     * Hide the columns containing weight/parent form elements.
     *
     * Undo showColumns().
     */
    hideColumns() {
      const $tables = $("table").findOnce("tabledrag");
      // Hide weight/parent cells and headers.
      $tables.find(".tabledrag-hide").css("display", "none");
      // Show TableDrag handles.
      $tables.find(".js-tabledrag-handle").css("display", "");
      // Reduce the colspan of any effected multi-span columns.
      $tables.find(".tabledrag-has-colspan").each(function decreaseColspan() {
        this.colSpan = this.colSpan - 1;
      });
      // Change link text.
      $(".js-tabledrag-toggle-weight-wrapper").each(
        function addShowWeightToggle() {
          const $wrapper = $(this);
          const toggleWasFocused = $wrapper.find(
            ".js-tabledrag-toggle-weight:focus"
          ).length;
          $wrapper
            .empty()
            .append(
              $(
                Drupal.theme(
                  "tableDragToggle",
                  "show",
                  Drupal.t("Show row weights")
                )
              ).addClass("js-tabledrag-toggle-weight")
            );
          if (toggleWasFocused) {
            $wrapper.find(".js-tabledrag-toggle-weight").trigger("focus");
          }
        }
      );
    },
 
    /**
     * Show the columns containing weight/parent form elements.
     *
     * Undo hideColumns().
     */
    showColumns() {
      const $tables = $("table").findOnce("tabledrag");
      // Show weight/parent cells and headers.
      $tables.find(".tabledrag-hide").css("display", "");
      // Hide TableDrag handles.
      $tables.find(".js-tabledrag-handle").css("display", "none");
      // Increase the colspan for any columns where it was previously reduced.
      $tables.find(".tabledrag-has-colspan").each(function increaseColspan() {
        this.colSpan = this.colSpan + 1;
      });
      // Change link text.
      $(".js-tabledrag-toggle-weight-wrapper").each(
        function addHideWeightToggle() {
          const $wrapper = $(this);
          const toggleWasFocused = $wrapper.find(
            ".js-tabledrag-toggle-weight:focus"
          ).length;
          $wrapper
            .empty()
            .append(
              $(
                Drupal.theme(
                  "tableDragToggle",
                  "hide",
                  Drupal.t("Hide row weights")
                )
              ).addClass("js-tabledrag-toggle-weight")
            );
          if (toggleWasFocused) {
            $wrapper.find(".js-tabledrag-toggle-weight").trigger("focus");
          }
        }
      );
    },
 
    /**
     * Find the target used within a particular row and group.
     *
     * @param {string} group
     *   Group selector.
     * @param {HTMLElement} row
     *   The row HTML element.
     *
     * @return {object}
     *   The table row settings.
     */
    rowSettings(group, row) {
      const field = $(row).find(`.${group}`);
      const tableSettingsGroup = this.tableSettings[group];
      return Object.keys(tableSettingsGroup)
        .map(delta => {
          const targetClass = tableSettingsGroup[delta].target;
          let rowSettings;
          if (field.is(`.${targetClass}`)) {
            // Return a copy of the row settings.
            rowSettings = {};
            Object.keys(tableSettingsGroup[delta]).forEach(n => {
              rowSettings[n] = tableSettingsGroup[delta][n];
            });
          }
          return rowSettings;
        })
        .filter(rowSetting => rowSetting)[0];
    },
 
    /**
     * Take an item and add event handlers to make it become draggable.
     *
     * @param {HTMLElement} item
     *   The item to add event handlers to.
     */
    makeDraggable(item) {
      const self = this;
      const $item = $(item);
      const $firstCell = $item
        .find("td:first-of-type")
        .wrapInner(Drupal.theme.tableDragCellContentWrapper())
        .wrapInner(
          $(Drupal.theme("tableDragCellItemsWrapper")).addClass(
            "js-tabledrag-cell-content"
          )
        );
      const $targetElem = $firstCell.find(".js-tabledrag-cell-content").length
        ? $firstCell.find(".js-tabledrag-cell-content")
        : $firstCell.addClass("js-tabledrag-cell-content");
 
      // Move indentations into the '.js-tabledrag-cell-content' target.
      $targetElem
        .find(".js-indentation")
        .detach()
        .prependTo($targetElem);
 
      // Add a class to the title link.
      $targetElem.find("a").addClass("menu-item__link");
      // Create the handle.
      const handle = $(Drupal.theme.tableDragHandle())
        .addClass("js-tabledrag-handle")
        .attr("title", Drupal.t("Drag to re-order"));
      // Insert the handle after indentations (if any).
      const $indentationLast = $targetElem.find(".js-indentation").eq(-1);
      if ($indentationLast.length) {
        $indentationLast.after(handle);
        // Update the total width of indentation in this entire table.
        self.indentCount = Math.max(
          $item.find(".js-indentation").length,
          self.indentCount
        );
      } else {
        $targetElem.prepend(handle);
      }
 
      // Prevent the anchor tag from jumping us to the top of the page.
      handle.on("click", event => {
        event.preventDefault();
      });
 
      // Don't do anything if tabledrag is disabled.
      if (handle.closest(".js-tabledrag-disabled").length) {
        return;
      }
 
      handle.on("mousedown touchstart pointerdown", event => {
        event.preventDefault();
        if (event.originalEvent.type === "touchstart") {
          event = event.originalEvent.touches[0];
        }
        self.dragStart(event, self, item);
      });
 
      // Set blur cleanup when a handle is focused.
      handle.on("focus", () => {
        self.safeBlur = true;
      });
 
      // On blur, fire the same function as a touchend/mouseup. This is used to
      // update values after a row has been moved through the keyboard support.
      handle.on("blur", event => {
        if (self.rowObject && self.safeBlur) {
          self.dropRow(event, self);
        }
      });
 
      // Add arrow-key support to the handle.
      handle.on("keydown", event => {
        // If a rowObject doesn't yet exist and this isn't the tab key.
        if (event.keyCode !== 9 && !self.rowObject) {
          self.rowObject = new self.row(
            item,
            "keyboard",
            self.indentEnabled,
            self.maxDepth,
            true
          );
        }
 
        let keyChange = false;
        let groupHeight;
 
        /* eslint-disable no-fallthrough */
 
        switch (event.keyCode) {
          // Left arrow.
          case 37:
          // Safari left arrow.
          case 63234:
            keyChange = true;
            self.rowObject.indent(-1 * self.rtl);
            break;
 
          // Up arrow.
          case 38:
          // Safari up arrow.
          case 63232: {
            let $previousRow = $(self.rowObject.element)
              .prev("tr")
              .eq(0);
            let previousRow = $previousRow.get(0);
            while (previousRow && $previousRow.is(":hidden")) {
              $previousRow = $(previousRow)
                .prev("tr")
                .eq(0);
              previousRow = $previousRow.get(0);
            }
            if (previousRow) {
              // Do not allow the onBlur cleanup.
              self.safeBlur = false;
              self.rowObject.direction = "up";
              keyChange = true;
 
              if ($(item).is(".tabledrag-root")) {
                // Swap with the previous top-level row.
                groupHeight = 0;
                while (
                  previousRow &&
                  $previousRow.find(".js-indentation").length
                ) {
                  $previousRow = $(previousRow)
                    .prev("tr")
                    .eq(0);
                  previousRow = $previousRow.get(0);
                  groupHeight += $previousRow.is(":hidden")
                    ? 0
                    : previousRow.offsetHeight;
                }
                if (previousRow) {
                  self.rowObject.swap("before", previousRow);
                  // No need to check for indentation, 0 is the only valid one.
                  window.scrollBy(0, -groupHeight);
                }
              } else if (
                self.table.tBodies[0].rows[0] !== previousRow ||
                $previousRow.is(".draggable")
              ) {
                // Swap with the previous row (unless previous row is the first
                // one and undraggable).
                self.rowObject.swap("before", previousRow);
                self.rowObject.interval = null;
                self.rowObject.indent(0);
                window.scrollBy(0, -parseInt(item.offsetHeight, 10));
              }
              // Regain focus after the DOM manipulation.
              handle.trigger("focus");
            }
            break;
          }
          // Right arrow.
          case 39:
          // Safari right arrow.
          case 63235:
            keyChange = true;
            self.rowObject.indent(self.rtl);
            break;
 
          // Down arrow.
          case 40:
          // Safari down arrow.
          case 63233: {
            let $nextRow = $(self.rowObject.group)
              .eq(-1)
              .next("tr")
              .eq(0);
            let nextRow = $nextRow.get(0);
            while (nextRow && $nextRow.is(":hidden")) {
              $nextRow = $(nextRow)
                .next("tr")
                .eq(0);
              nextRow = $nextRow.get(0);
            }
            if (nextRow) {
              // Do not allow the onBlur cleanup.
              self.safeBlur = false;
              self.rowObject.direction = "down";
              keyChange = true;
 
              if ($(item).is(".tabledrag-root")) {
                // Swap with the next group (necessarily a top-level one).
                groupHeight = 0;
                const nextGroup = new self.row(
                  nextRow,
                  "keyboard",
                  self.indentEnabled,
                  self.maxDepth,
                  false
                );
                if (nextGroup) {
                  $(nextGroup.group).each(function groupIterator() {
                    groupHeight += $(this).is(":hidden")
                      ? 0
                      : this.offsetHeight;
                  });
                  const nextGroupRow = $(nextGroup.group)
                    .eq(-1)
                    .get(0);
                  self.rowObject.swap("after", nextGroupRow);
                  // No need to check for indentation, 0 is the only valid one.
                  window.scrollBy(0, parseInt(groupHeight, 10));
                }
              } else {
                // Swap with the next row.
                self.rowObject.swap("after", nextRow);
                self.rowObject.interval = null;
                self.rowObject.indent(0);
                window.scrollBy(0, parseInt(item.offsetHeight, 10));
              }
              // Regain focus after the DOM manipulation.
              handle.trigger("focus");
            }
            break;
          }
        }
 
        /* eslint-enable no-fallthrough */
 
        if (self.rowObject && self.rowObject.changed === true) {
          $(item).addClass("drag");
          if (self.oldRowElement) {
            $(self.oldRowElement).removeClass("drag-previous");
          }
          self.oldRowElement = item;
          if (self.striping === true) {
            self.restripeTable();
          }
          self.onDrag();
        }
 
        // Returning false if we have an arrow key to prevent scrolling.
        if (keyChange) {
          return false;
        }
      });
 
      // Compatibility addition, return false on keypress to prevent unwanted
      // scrolling. IE and Safari will suppress scrolling on keydown, but all
      // other browsers need to return false on keypress.
      handle.on("keypress", event => {
        /* eslint-disable no-fallthrough */
 
        switch (event.keyCode) {
          // Left arrow.
          case 37:
          // Up arrow.
          case 38:
          // Right arrow.
          case 39:
          // Down arrow.
          case 40:
            return false;
        }
 
        /* eslint-enable no-fallthrough */
      });
    },
 
    /**
     * Pointer event initiator, creates drag object and information.
     *
     * @param {jQuery.Event} event
     *   The event object that trigger the drag.
     * @param {Drupal.tableDrag} self
     *   The drag handle.
     * @param {HTMLElement} item
     *   The item that that is being dragged.
     */
    dragStart(event, self, item) {
      // Create a new dragObject recording the pointer information.
      self.dragObject = {};
      self.dragObject.initOffset = self.getPointerOffset(item, event);
      self.dragObject.initPointerCoords = self.pointerCoords(event);
      if (self.indentEnabled) {
        self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;
      }
 
      // If there's a lingering row object from the keyboard, remove its focus.
      if (self.rowObject) {
        $(self.rowObject.element)
          .find(".js-tabledrag-handle")
          .trigger("blur");
      }
 
      // Create a new rowObject for manipulation of this row.
      self.rowObject = new self.row(
        item,
        "pointer",
        self.indentEnabled,
        self.maxDepth,
        true
      );
 
      // Save the position of the table.
      self.table.topY = $(self.table).offset().top;
      self.table.bottomY = self.table.topY + self.table.offsetHeight;
 
      // Add classes to the handle and row.
      $(item).addClass("drag");
 
      // Set the document to use the move cursor during drag.
      $("body").addClass("drag");
      if (self.oldRowElement) {
        $(self.oldRowElement).removeClass("drag-previous");
      }
    },
 
    /**
     * Pointer movement handler, bound to document.
     *
     * @param {jQuery.Event} event
     *   The pointer event.
     * @param {Drupal.tableDrag} self
     *   The tableDrag instance.
     *
     * @return {bool|undefined}
     *   Undefined if no dragObject is defined, false otherwise.
     */
    dragRow(event, self) {
      if (self.dragObject) {
        self.currentPointerCoords = self.pointerCoords(event);
        const y = self.currentPointerCoords.y - self.dragObject.initOffset.y;
        const x = self.currentPointerCoords.x - self.dragObject.initOffset.x;
 
        // Check for row swapping and vertical scrolling.
        if (y !== self.oldY) {
          self.rowObject.direction = y > self.oldY ? "down" : "up";
          // Update the old value.
          self.oldY = y;
          // Check if the window should be scrolled (and how fast).
          const scrollAmount = self.checkScroll(self.currentPointerCoords.y);
          // Stop any current scrolling.
          clearInterval(self.scrollInterval);
          // Continue scrolling if the mouse has moved in the scroll direction.
          if (
            (scrollAmount > 0 && self.rowObject.direction === "down") ||
            (scrollAmount < 0 && self.rowObject.direction === "up")
          ) {
            self.setScroll(scrollAmount);
          }
 
          // If we have a valid target, perform the swap and restripe the table.
          const currentRow = self.findDropTargetRow(x, y);
          if (currentRow) {
            if (self.rowObject.direction === "down") {
              self.rowObject.swap("after", currentRow, self);
            } else {
              self.rowObject.swap("before", currentRow, self);
            }
            if (self.striping === true) {
              self.restripeTable();
            }
          }
        }
 
        // Similar to row swapping, handle indentations.
        if (self.indentEnabled) {
          const xDiff =
            self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;
          // Set the number of indentations the pointer has been moved left or
          // right.
          const indentDiff = Math.round(xDiff / self.indentAmount);
          // Indent the row with our estimated diff, which may be further
          // restricted according to the rows around this row.
          const indentChange = self.rowObject.indent(indentDiff);
          // Update table and pointer indentations.
          self.dragObject.indentPointerPos.x +=
            self.indentAmount * indentChange;
          self.indentCount = Math.max(self.indentCount, self.rowObject.indents);
        }
 
        return false;
      }
    },
 
    /**
     * Pointerup behavior.
     *
     * @param {jQuery.Event} event
     *   The pointer event.
     * @param {Drupal.tableDrag} self
     *   The tableDrag instance.
     */
    dropRow(event, self) {
      let droppedRow;
      let $droppedRow;
 
      // Drop row functionality.
      if (self.rowObject !== null) {
        droppedRow = self.rowObject.element;
        $droppedRow = $(droppedRow);
        // The row is already in the right place so we just release it.
        if (self.rowObject.changed === true) {
          // Update the fields in the dropped row.
          self.updateFields(droppedRow);
 
          // If a setting exists for affecting the entire group, update all the
          // fields in the entire dragged group.
          Object.keys(self.tableSettings || {}).forEach(group => {
            const rowSettings = self.rowSettings(group, droppedRow);
            if (rowSettings.relationship === "group") {
              Object.keys(self.rowObject.children || {}).forEach(n => {
                self.updateField(self.rowObject.children[n], group);
              });
            }
          });
 
          self.rowObject.markChanged();
          if (self.changed === false) {
            const $messageTarget = $(self.table).prevAll(
              ".js-tabledrag-toggle-weight-wrapper"
            ).length
              ? $(self.table)
                  .prevAll(".js-tabledrag-toggle-weight-wrapper")
                  .last()
              : self.table;
            $(Drupal.theme("tableDragChangedWarning"))
              .insertBefore($messageTarget)
              .hide()
              .fadeIn("slow");
            self.changed = true;
          }
        }
 
        if (self.indentEnabled) {
          self.rowObject.removeIndentClasses();
        }
        if (self.oldRowElement) {
          $(self.oldRowElement).removeClass("drag-previous");
        }
        $droppedRow.removeClass("drag").addClass("drag-previous");
        self.oldRowElement = droppedRow;
        self.onDrop();
        self.rowObject = null;
      }
 
      // Functionality specific only to pointerup events.
      if (self.dragObject !== null) {
        self.dragObject = null;
        $("body").removeClass("drag");
        clearInterval(self.scrollInterval);
      }
    },
 
    /**
     * Get the coordinates from the event (allowing for browser differences).
     *
     * @param {jQuery.Event} event
     *   The pointer event.
     *
     * @return {object}
     *   An object with `x` and `y` keys indicating the position.
     */
    pointerCoords(event) {
      if (event.pageX || event.pageY) {
        return { x: event.pageX, y: event.pageY };
      }
      return {
        x:
          event.clientX + (document.body.scrollLeft - document.body.clientLeft),
        y: event.clientY + (document.body.scrollTop - document.body.clientTop)
      };
    },
 
    /**
     * Get the event offset from the target element.
     *
     * Given a target element and a pointer event, get the event offset from that
     * element. To do this we need the element's position and the target position.
     *
     * @param {HTMLElement} target
     *   The target HTML element.
     * @param {jQuery.Event} event
     *   The pointer event.
     *
     * @return {object}
     *   An object with `x` and `y` keys indicating the position.
     */
    getPointerOffset(target, event) {
      const docPos = $(target).offset();
      const pointerPos = this.pointerCoords(event);
      return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top };
    },
 
    /**
     * Find the row the mouse is currently over.
     *
     * This row is then taken and swapped with the one being dragged.
     *
     * @param {number} x
     *   The x coordinate of the mouse on the page (not the screen).
     * @param {number} y
     *   The y coordinate of the mouse on the page (not the screen).
     *
     * @return {*}
     *   The drop target row, if found.
     */
    findDropTargetRow(x, y) {
      const rows = $(this.table.tBodies[0].rows).not(":hidden");
      for (let n = 0; n < rows.length; n++) {
        let row = rows[n];
        let $row = $(row);
        const rowY = $row.offset().top;
        let rowHeight;
        // Because Safari does not report offsetHeight on table rows, but does on
        // table cells, grab the firstChild of the row and use that instead.
        if (row.offsetHeight === 0) {
          rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2;
        }
        // Other browsers.
        else {
          rowHeight = parseInt(row.offsetHeight, 10) / 2;
        }
 
        // Because we always insert before, we need to offset the height a bit.
        if (y > rowY - rowHeight && y < rowY + rowHeight) {
          if (this.indentEnabled) {
            // Check that this row is not a child of the row being dragged.
            if (
              Object.keys(this.rowObject.group).some(
                o => this.rowObject.group[o] === row
              )
            ) {
              return null;
            }
          }
          // Do not allow a row to be swapped with itself.
          else if (row === this.rowObject.element) {
            return null;
          }
 
          // Check that swapping with this row is allowed.
          if (!this.rowObject.isValidSwap(row)) {
            return null;
          }
 
          // We may have found the row the mouse just passed over, but it doesn't
          // take into account hidden rows. Skip backwards until we find a
          // draggable row.
          while ($row.is(":hidden") && $row.prev("tr").is(":hidden")) {
            $row = $row.prev("tr:first-of-type");
            row = $row.get(0);
          }
          return row;
        }
      }
      return null;
    },
 
    /**
     * After the row is dropped, update the table fields.
     *
     * @param {HTMLElement} changedRow
     *   DOM object for the row that was just dropped.
     */
    updateFields(changedRow) {
      Object.keys(this.tableSettings || {}).forEach(group => {
        // Each group may have a different setting for relationship, so we find
        // the source rows for each separately.
        this.updateField(changedRow, group);
      });
    },
 
    /**
     * After the row is dropped, update a single table field.
     *
     * @param {HTMLElement} changedRow
     *   DOM object for the row that was just dropped.
     * @param {string} group
     *   The settings group on which field updates will occur.
     */
    updateField(changedRow, group) {
      let rowSettings = this.rowSettings(group, changedRow);
      const $changedRow = $(changedRow);
      let sourceRow;
      let $previousRow;
      let previousRow;
      let useSibling;
      // Set the row as its own target.
      if (
        rowSettings.relationship === "self" ||
        rowSettings.relationship === "group"
      ) {
        sourceRow = changedRow;
      }
      // Siblings are easy, check previous and next rows.
      else if (rowSettings.relationship === "sibling") {
        $previousRow = $changedRow.prev("tr:first-of-type");
        previousRow = $previousRow.get(0);
        const $nextRow = $changedRow.next("tr:first-of-type");
        const nextRow = $nextRow.get(0);
        sourceRow = changedRow;
        if (
          $previousRow.is(".draggable") &&
          $previousRow.find(`.${group}`).length
        ) {
          if (this.indentEnabled) {
            if (
              $previousRow.find(".js-indentations").length ===
              $changedRow.find(".js-indentations").length
            ) {
              sourceRow = previousRow;
            }
          } else {
            sourceRow = previousRow;
          }
        } else if (
          $nextRow.is(".draggable") &&
          $nextRow.find(`.${group}`).length
        ) {
          if (this.indentEnabled) {
            if (
              $nextRow.find(".js-indentations").length ===
              $changedRow.find(".js-indentations").length
            ) {
              sourceRow = nextRow;
            }
          } else {
            sourceRow = nextRow;
          }
        }
      }
      // Parents, look up the tree until we find a field not in this group.
      // Go up as many parents as indentations in the changed row.
      else if (rowSettings.relationship === "parent") {
        $previousRow = $changedRow.prev("tr");
        previousRow = $previousRow;
        while (
          $previousRow.length &&
          $previousRow.find(".js-indentation").length >= this.rowObject.indents
        ) {
          $previousRow = $previousRow.prev("tr");
          previousRow = $previousRow;
        }
        // If we found a row.
        if ($previousRow.length) {
          sourceRow = $previousRow.get(0);
        }
        // Otherwise we went all the way to the left of the table without finding
        // a parent, meaning this item has been placed at the root level.
        else {
          // Use the first row in the table as source, because it's guaranteed to
          // be at the root level. Find the first item, then compare this row
          // against it as a sibling.
          sourceRow = $(this.table)
            .find("tr.draggable:first-of-type")
            .get(0);
          if (sourceRow === this.rowObject.element) {
            sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1])
              .next("tr.draggable")
              .get(0);
          }
          useSibling = true;
        }
      }
 
      // Because we may have moved the row from one category to another,
      // take a look at our sibling and borrow its sources and targets.
      this.copyDragClasses(sourceRow, changedRow, group);
      rowSettings = this.rowSettings(group, changedRow);
 
      // In the case that we're looking for a parent, but the row is at the top
      // of the tree, copy our sibling's values.
      if (useSibling) {
        rowSettings.relationship = "sibling";
        rowSettings.source = rowSettings.target;
      }
 
      const targetClass = `.${rowSettings.target}`;
      const targetElement = $changedRow.find(targetClass).get(0);
 
      // Check if a target element exists in this row.
      if (targetElement) {
        const sourceClass = `.${rowSettings.source}`;
        const sourceElement = $(sourceClass, sourceRow).get(0);
        switch (rowSettings.action) {
          case "depth":
            // Get the depth of the target row.
            targetElement.value = $(sourceElement)
              .closest("tr")
              .find(".js-indentation").length;
            break;
 
          case "match":
            // Update the value.
            targetElement.value = sourceElement.value;
            break;
 
          case "order": {
            const siblings = this.rowObject.findSiblings(rowSettings);
            if ($(targetElement).is("select")) {
              // Get a list of acceptable values.
              const values = [];
              $(targetElement)
                .find("option")
                .each(function collectValues() {
                  values.push(this.value);
                });
              const maxVal = values[values.length - 1];
              // Populate the values in the siblings.
              $(siblings)
                .find(targetClass)
                .each(function assignValues() {
                  // If there are more items than possible values, assign the
                  // maximum value to the row.
                  if (values.length > 0) {
                    this.value = values.shift();
                  } else {
                    this.value = maxVal;
                  }
                });
            } else {
              // Assume a numeric input field.
              let weight =
                parseInt(
                  $(siblings[0])
                    .find(targetClass)
                    .val(),
                  10
                ) || 0;
              $(siblings)
                .find(targetClass)
                .each(function assignWeight() {
                  this.value = weight;
                  weight += 1;
                });
            }
            break;
          }
        }
      }
    },
 
    /**
     * Copy all tableDrag related classes from one row to another.
     *
     * Copy all special tableDrag classes from one row's form elements to a
     * different one, removing any special classes that the destination row
     * may have had.
     *
     * @param {HTMLElement} sourceRow
     *   The element for the source row.
     * @param {HTMLElement} targetRow
     *   The element for the target row.
     * @param {string} group
     *   The group selector.
     */
    copyDragClasses(sourceRow, targetRow, group) {
      const sourceElement = $(sourceRow).find(`.${group}`);
      const targetElement = $(targetRow).find(`.${group}`);
      if (sourceElement.length && targetElement.length) {
        targetElement[0].className = sourceElement[0].className;
      }
    },
 
    /**
     * Check the suggested scroll of the table.
     *
     * @param {number} cursorY
     *   The Y position of the cursor.
     *
     * @return {number}
     *   The suggested scroll.
     */
    checkScroll(cursorY) {
      const de = document.documentElement;
      const b = document.body;
      const windowHeight =
        window.innerHeight ||
        (de.clientHeight && de.clientWidth !== 0
          ? de.clientHeight
          : b.offsetHeight);
      this.windowHeight = windowHeight;
      let scrollY;
      if (document.all) {
        scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop;
      } else {
        scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY;
      }
      this.scrollY = scrollY;
      const { trigger } = this.scrollSettings;
      let delta = 0;
 
      // Return a scroll speed relative to the edge of the screen.
      if (cursorY - scrollY > windowHeight - trigger) {
        delta = trigger / (windowHeight + (scrollY - cursorY));
        delta = delta > 0 && delta < trigger ? delta : trigger;
        return delta * this.scrollSettings.amount;
      }
      if (cursorY - scrollY < trigger) {
        delta = trigger / (cursorY - scrollY);
        delta = delta > 0 && delta < trigger ? delta : trigger;
        return -delta * this.scrollSettings.amount;
      }
    },
 
    /**
     * Set the scroll for the table.
     *
     * @param {number} scrollAmount
     *   The amount of scroll to apply to the window.
     */
    setScroll(scrollAmount) {
      const self = this;
 
      this.scrollInterval = setInterval(() => {
        // Update the scroll values stored in the object.
        self.checkScroll(self.currentPointerCoords.y);
        const aboveTable = self.scrollY > self.table.topY;
        const belowTable =
          self.scrollY + self.windowHeight < self.table.bottomY;
        if (
          (scrollAmount > 0 && belowTable) ||
          (scrollAmount < 0 && aboveTable)
        ) {
          window.scrollBy(0, scrollAmount);
        }
      }, this.scrollSettings.interval);
    },
 
    /**
     * Command to restripe table properly.
     */
    restripeTable() {
      // :even and :odd are reversed because jQuery counts from 0 and
      // we count from 1, so we're out of sync.
      // Match immediate children of the parent element to allow nesting.
      $(this.table)
        .find("> tbody > tr.draggable, > tr.draggable")
        .filter(":visible")
        .filter(":odd")
        .removeClass("odd")
        .addClass("even")
        .end()
        .filter(":even")
        .removeClass("even")
        .addClass("odd");
    },
 
    /**
     * Stub function. Allows a custom handler when a row begins dragging.
     *
     * @return {null}
     *   Returns null when the stub function is used.
     */
    onDrag() {
      return null;
    },
 
    /**
     * Stub function. Allows a custom handler when a row is dropped.
     *
     * @return {null}
     *   Returns null when the stub function is used.
     */
    onDrop() {
      return null;
    },
 
    /**
     * Constructor to make a new object to manipulate a table row.
     *
     * @param {HTMLElement} tableRow
     *   The DOM element for the table row we will be manipulating.
     * @param {string} method
     *   The method in which this row is being moved. Either 'keyboard' or
     *   'mouse'.
     * @param {bool} indentEnabled
     *   Whether the containing table uses indentations. Used for optimizations.
     * @param {number} maxDepth
     *   The maximum amount of indentations this row may contain.
     * @param {bool} addClasses
     *   Whether we want to add classes to this row to indicate child
     *   relationships.
     */
    row(tableRow, method, indentEnabled, maxDepth, addClasses) {
      const $tableRow = $(tableRow);
 
      this.element = tableRow;
      this.method = method;
      this.group = [tableRow];
      this.groupDepth = $tableRow.find(".js-indentation").length;
      this.changed = false;
      this.table = $tableRow.closest("table")[0];
      this.indentEnabled = indentEnabled;
      this.maxDepth = maxDepth;
      // Direction the row is being moved.
      this.direction = "";
      if (this.indentEnabled) {
        this.indents = $tableRow.find(".js-indentation").length;
        this.children = this.findChildren(addClasses);
        this.group = $.merge(this.group, this.children);
        // Find the depth of this entire group.
        for (let n = 0; n < this.group.length; n++) {
          this.groupDepth = Math.max(
            $(this.group[n]).find(".js-indentation").length,
            this.groupDepth
          );
        }
      }
    }
  });
 
  $.extend(Drupal.tableDrag.prototype.row.prototype, {
    /**
     * Find all children of rowObject by indentation.
     *
     * @param {bool} addClasses
     *   Whether we want to add classes to this row to indicate child
     *   relationships.
     *
     * @return {Array}
     *   An array of children of the row.
     */
    findChildren(addClasses) {
      const parentIndentation = this.indents;
      let currentRow = $(this.element, this.table).next("tr.draggable");
      const rows = [];
      let child = 0;
 
      function rowIndentation(indentNum, el) {
        const self = $(el);
        if (child === 1 && indentNum === parentIndentation) {
          self.addClass("tree-child-first");
        }
        if (indentNum === parentIndentation) {
          self.addClass("tree-child");
        } else if (indentNum > parentIndentation) {
          self.addClass("tree-child-horizontal");
        }
      }
 
      while (currentRow.length) {
        // A greater indentation indicates this is a child.
        if (currentRow.find(".js-indentation").length > parentIndentation) {
          child += 1;
          rows.push(currentRow[0]);
          if (addClasses) {
            currentRow.find(".js-indentation").each(rowIndentation);
          }
        } else {
          break;
        }
        currentRow = currentRow.next("tr.draggable");
      }
      if (addClasses && rows.length) {
        $(rows[rows.length - 1])
          .find(`.js-indentation:nth-child(${parentIndentation + 1})`)
          .addClass("tree-child-last");
      }
      return rows;
    },
 
    /**
     * Ensure that two rows are allowed to be swapped.
     *
     * @param {HTMLElement} row
     *   DOM object for the row being considered for swapping.
     *
     * @return {bool}
     *   Whether the swap is a valid swap or not.
     */
    isValidSwap(row) {
      const $row = $(row);
      if (this.indentEnabled) {
        let prevRow;
        let nextRow;
        if (this.direction === "down") {
          prevRow = row;
          nextRow = $row.next("tr").get(0);
        } else {
          prevRow = $row.prev("tr").get(0);
          nextRow = row;
        }
        this.interval = this.validIndentInterval(prevRow, nextRow);
 
        // We have an invalid swap if the valid indentations interval is empty.
        if (this.interval.min > this.interval.max) {
          return false;
        }
      }
 
      // Do not let an un-draggable first row have anything put before it.
      if (
        this.table.tBodies[0].rows[0] === row &&
        $row.is(":not(.draggable)")
      ) {
        return false;
      }
 
      return true;
    },
 
    /**
     * Perform the swap between two rows.
     *
     * @param {string} position
     *   Whether the swap will occur 'before' or 'after' the given row.
     * @param {HTMLElement} row
     *   DOM element what will be swapped with the row group.
     */
    swap(position, row) {
      // Makes sure only DOM object are passed to Drupal.detachBehaviors().
      this.group.forEach(detachedRow => {
        Drupal.detachBehaviors(detachedRow, drupalSettings, "move");
      });
      $(row)[position](this.group);
      // Makes sure only DOM object are passed to Drupal.attachBehaviors()s.
      this.group.forEach(attachedRow => {
        Drupal.attachBehaviors(attachedRow, drupalSettings);
      });
      this.changed = true;
      this.onSwap(row);
    },
 
    /**
     * Determine the valid indentations interval for the row at a given position.
     *
     * @param {?HTMLElement} prevRow
     *   DOM object for the row before the tested position
     *   (or null for first position in the table).
     * @param {?HTMLElement} nextRow
     *   DOM object for the row after the tested position
     *   (or null for last position in the table).
     *
     * @return {object}
     *   An object with the keys `min` and `max` to indicate the valid indent
     *   interval.
     */
    validIndentInterval(prevRow, nextRow) {
      const $prevRow = $(prevRow);
      let maxIndent;
 
      // Minimum indentation:
      // Do not orphan the next row.
      const minIndent = nextRow ? $(nextRow).find(".js-indentation").length : 0;
 
      // Maximum indentation:
      if (
        !prevRow ||
        $prevRow.is(":not(.draggable)") ||
        $(this.element).is(".tabledrag-root")
      ) {
        // Do not indent:
        // - the first row in the table,
        // - rows dragged below a non-draggable row,
        // - 'root' rows.
        maxIndent = 0;
      } else {
        // Do not go deeper than as a child of the previous row.
        maxIndent =
          $prevRow.find(".js-indentation").length +
          ($prevRow.is(".tabledrag-leaf") ? 0 : 1);
        // Limit by the maximum allowed depth for the table.
        if (this.maxDepth) {
          maxIndent = Math.min(
            maxIndent,
            this.maxDepth - (this.groupDepth - this.indents)
          );
        }
      }
 
      return { min: minIndent, max: maxIndent };
    },
 
    /**
     * Indent a row within the legal bounds of the table.
     *
     * @param {number} indentDiff
     *   The number of additional indentations proposed for the row (can be
     *   positive or negative). This number will be adjusted to nearest valid
     *   indentation level for the row.
     *
     * @return {number}
     *   The number of indentations applied.
     */
    indent(indentDiff) {
      const $group = $(this.group);
      // Determine the valid indentations interval if not available yet.
      if (!this.interval) {
        const prevRow = $(this.element)
          .prev("tr")
          .get(0);
        const nextRow = $group
          .eq(-1)
          .next("tr")
          .get(0);
        this.interval = this.validIndentInterval(prevRow, nextRow);
      }
 
      // Adjust to the nearest valid indentation.
      let indent = this.indents + indentDiff;
      indent = Math.max(indent, this.interval.min);
      indent = Math.min(indent, this.interval.max);
      indentDiff = indent - this.indents;
 
      for (let n = 1; n <= Math.abs(indentDiff); n++) {
        // Add or remove indentations.
        if (indentDiff < 0) {
          $group.find(".js-indentation:first-of-type").remove();
          this.indents -= 1;
        } else {
          $group
            .find(".js-tabledrag-cell-content")
            .prepend(Drupal.theme("tableDragIndentation"));
          this.indents += 1;
        }
      }
      if (indentDiff) {
        // Update indentation for this row.
        this.changed = true;
        this.groupDepth += indentDiff;
        this.onIndent();
      }
 
      return indentDiff;
    },
 
    /**
     * Find all siblings for a row.
     *
     * According to its subgroup or indentation. Note that the passed-in row is
     * included in the list of siblings.
     *
     * @param {object} rowSettings
     *   The field settings we're using to identify what constitutes a sibling.
     *
     * @return {Array}
     *   An array of siblings.
     */
    findSiblings(rowSettings) {
      const siblings = [];
      const directions = ["prev", "next"];
      const rowIndentation = this.indents;
      let checkRowIndentation;
      for (let d = 0; d < directions.length; d++) {
        let checkRow = $(this.element)[directions[d]]();
        while (checkRow.length) {
          // Check that the sibling contains a similar target field.
          if (checkRow.find(`.${rowSettings.target}`)) {
            // Either add immediately if this is a flat table, or check to
            // ensure that this row has the same level of indentation.
            if (this.indentEnabled) {
              checkRowIndentation = checkRow.find(".js-indentation").length;
            }
 
            if (!this.indentEnabled || checkRowIndentation === rowIndentation) {
              siblings.push(checkRow[0]);
            } else if (checkRowIndentation < rowIndentation) {
              // No need to keep looking for siblings when we get to a parent.
              break;
            }
          } else {
            break;
          }
          checkRow = checkRow[directions[d]]();
        }
        // Since siblings are added in reverse order for previous, reverse the
        // completed list of previous siblings. Add the current row and
        // continue.
        if (directions[d] === "prev") {
          siblings.reverse();
          siblings.push(this.element);
        }
      }
      return siblings;
    },
 
    /**
     * Remove indentation helper classes from the current row group.
     */
    removeIndentClasses() {
      Object.keys(this.children || {}).forEach(n => {
        $(this.children[n])
          .find(".js-indentation")
          .removeClass("tree-child")
          .removeClass("tree-child-first")
          .removeClass("tree-child-last")
          .removeClass("tree-child-horizontal");
      });
    },
 
    /**
     * Add an asterisk or other marker to the changed row.
     */
    markChanged() {
      const marker = $(Drupal.theme("tableDragChangedMarker")).addClass(
        "js-tabledrag-changed-marker"
      );
      const cell = $(this.element).find("td:first-of-type");
      if (cell.find(".js-tabledrag-changed-marker").length === 0) {
        cell.find(".js-tabledrag-handle").after(marker);
      }
    },
 
    /**
     * Stub function. Allows a custom handler when a row is indented.
     *
     * @return {null}
     *   Returns null when the stub function is used.
     */
    onIndent() {
      return null;
    },
 
    /**
     * Stub function. Allows a custom handler when a row is swapped.
     *
     * @param {HTMLElement} swappedRow
     *   The element for the swapped row.
     *
     * @return {null}
     *   Returns null when the stub function is used.
     */
    // eslint-disable-next-line no-unused-vars
    onSwap(swappedRow) {
      return null;
    }
  });
 
  $.extend(
    Drupal.theme,
    /** @lends Drupal.theme */ {
      /**
       * @return {string}
       *  Markup for the marker.
       */
      tableDragChangedMarker() {
        return `<abbr class="warning tabledrag-changed" title="${Drupal.t(
          "Changed"
        )}">*</abbr>`;
      },
 
      /**
       * @return {string}
       *   Markup for the indentation.
       */
      tableDragIndentation() {
        return '<div class="js-indentation indentation"><svg xmlns="http://www.w3.org/2000/svg" class="tree" width="25" height="25" viewBox="0 0 25 25"><path class="tree__item tree__item-child-ltr tree__item-child-last-ltr tree__item-horizontal tree__item-horizontal-right" d="M12,12.5 H25" stroke="#888"/><path class="tree__item tree__item-child-rtl tree__item-child-last-rtl tree__item-horizontal tree__horizontal-left" d="M0,12.5 H13" stroke="#888"/><path class="tree__item tree__item-child-ltr tree__item-child-rtl tree__item-child-last-ltr tree__item-child-last-rtl tree__vertical tree__vertical-top" d="M12.5,12 v-99" stroke="#888"/><path class="tree__item tree__item-child-ltr tree__item-child-rtl tree__vertical tree__vertical-bottom" d="M12.5,12 v99" stroke="#888"/></svg></div>';
      },
 
      /**
       * @return {string}
       *   Markup for the warning.
       */
      tableDragChangedWarning() {
        return `<div class="tabledrag-changed-warning messages messages--warning" role="alert">${Drupal.theme(
          "tableDragChangedMarker"
        )} ${Drupal.t("You have unsaved changes.")}</div>`;
      },
 
      /**
       * Constucts the table drag handle.
       *
       * @return {string}
       *   A string representing a DOM fragment.
       */
      tableDragHandle() {
        return '<a href="#" class="tabledrag-handle"></a>';
      },
 
      /**
       * Constructs the wrapper for the whole table drag cell.
       *
       * @return {string}
       *   A string representing a DOM fragment.
       */
      tableDragCellItemsWrapper() {
        return '<div class="tabledrag-cell-content"/>';
      },
 
      /**
       * Constructs the wrapper for the initial content of the drag cell.
       *
       * @return {string}
       *   A string representing a DOM fragment.
       */
      tableDragCellContentWrapper() {
        return '<div class="tabledrag-cell-content__item"/>';
      },
 
      /**
       * Constructs the weight column toggle.
       *
       * The 'tabledrag-toggle-weight' CSS class should be kept since it is used
       * elsewhere as well (e.g. in tests).
       *
       * @param {string} action
       *   The action the toggle will perform.
       * @param {string} text
       *   The text content of the toggle.
       *
       * @return {string}
       *   A string representing a DOM fragment.
       */
      tableDragToggle(action, text) {
        const classes = [
          "action-link",
          "action-link--extrasmall",
          "tabledrag-toggle-weight"
        ];
        switch (action) {
          case "show":
            classes.push("action-link--icon-show");
            break;
 
          default:
            classes.push("action-link--icon-hide");
            break;
        }
 
        return `<a href="#" class="${classes.join(" ")}">${text}</a>`;
      },
 
      /**
       * Constructs the wrapper of the weight column toggle.
       *
       * The 'tabledrag-toggle-weight-wrapper' CSS class should be kept since it is used
       * by Views UI and inside off-canvas dialogs.
       *
       * @return {string}
       *   A string representing a DOM fragment.
       */
      tableDragToggleWrapper() {
        return '<div class="tabledrag-toggle-weight-wrapper"></div>';
      }
    }
  );
})(jQuery, Drupal, drupalSettings);

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

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