Files
railwaysystem/RuoYi-Vue/ruoyi-ui/node_modules1/handsontable/plugins/manualRowMove/manualRowMove.js
T

673 lines
23 KiB
JavaScript
Raw Normal View History

2026-09-10 15:40:10 +08:00
"use strict";
exports.__esModule = true;
require("core-js/modules/es.array.push.js");
var _base = require("../base");
var _pluginHooks = _interopRequireDefault(require("../../pluginHooks"));
var _array = require("../../helpers/array");
var _element = require("../../helpers/dom/element");
var _number = require("../../helpers/number");
var _eventManager = _interopRequireDefault(require("../../eventManager"));
var _backlight = _interopRequireDefault(require("./ui/backlight"));
var _guideline = _interopRequireDefault(require("./ui/guideline"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_pluginHooks.default.getSingleton().register('beforeRowMove');
_pluginHooks.default.getSingleton().register('afterRowMove');
const PLUGIN_KEY = 'manualRowMove';
exports.PLUGIN_KEY = PLUGIN_KEY;
const PLUGIN_PRIORITY = 140;
exports.PLUGIN_PRIORITY = PLUGIN_PRIORITY;
const privatePool = new WeakMap();
const CSS_PLUGIN = 'ht__manualRowMove';
const CSS_SHOW_UI = 'show-ui';
const CSS_ON_MOVING = 'on-moving--rows';
const CSS_AFTER_SELECTION = 'after-selection--rows';
/* eslint-disable jsdoc/require-description-complete-sentence */
/**
* @plugin ManualRowMove
* @class ManualRowMove
*
* @description
* This plugin allows to change rows order. To make rows order persistent the {@link Options#persistentState}
* plugin should be enabled.
*
* API:
* - `moveRow` - move single row to the new position.
* - `moveRows` - move many rows (as an array of indexes) to the new position.
* - `dragRow` - drag single row to the new position.
* - `dragRows` - drag many rows (as an array of indexes) to the new position.
*
* [Documentation](@/guides/rows/row-moving.md) explain differences between drag and move actions. Please keep in mind that if you want apply visual changes,
* you have to call manually the `render` method on the instance of Handsontable.
*
* The plugin creates additional components to make moving possibly using user interface:
* - backlight - highlight of selected rows.
* - guideline - line which shows where rows has been moved.
*
* @class ManualRowMove
* @plugin ManualRowMove
*/
class ManualRowMove extends _base.BasePlugin {
static get PLUGIN_KEY() {
return PLUGIN_KEY;
}
static get PLUGIN_PRIORITY() {
return PLUGIN_PRIORITY;
}
constructor(hotInstance) {
super(hotInstance);
/**
* Set up WeakMap of plugin to sharing private parameters;.
*/
privatePool.set(this, {
rowsToMove: [],
pressed: void 0,
target: {
eventPageY: void 0,
coords: void 0,
TD: void 0,
row: void 0
},
cachedDropIndex: void 0
});
/**
* Event Manager object.
*
* @private
* @type {object}
*/
this.eventManager = new _eventManager.default(this);
/**
* Backlight UI object.
*
* @private
* @type {object}
*/
this.backlight = new _backlight.default(hotInstance);
/**
* Guideline UI object.
*
* @private
* @type {object}
*/
this.guideline = new _guideline.default(hotInstance);
}
/**
* Checks if the plugin is enabled in the handsontable settings. This method is executed in {@link Hooks#beforeInit}
* hook and if it returns `true` then the {@link ManualRowMove#enablePlugin} method is called.
*
* @returns {boolean}
*/
isEnabled() {
return !!this.hot.getSettings()[PLUGIN_KEY];
}
/**
* Enables the plugin functionality for this Handsontable instance.
*/
enablePlugin() {
var _this = this;
if (this.enabled) {
return;
}
this.addHook('beforeOnCellMouseDown', function () {
return _this.onBeforeOnCellMouseDown(...arguments);
});
this.addHook('beforeOnCellMouseOver', function () {
return _this.onBeforeOnCellMouseOver(...arguments);
});
this.addHook('afterScrollHorizontally', () => this.onAfterScrollHorizontally());
this.addHook('afterLoadData', function () {
return _this.onAfterLoadData(...arguments);
});
this.buildPluginUI();
this.registerEvents();
// TODO: move adding plugin classname to BasePlugin.
(0, _element.addClass)(this.hot.rootElement, CSS_PLUGIN);
super.enablePlugin();
}
/**
* Updates the plugin's state.
*
* This method is executed when [`updateSettings()`](@/api/core.md#updatesettings) is invoked with any of the following configuration options:
* - [`manualRowMove`](@/api/options.md#manualrowmove)
*/
updatePlugin() {
this.disablePlugin();
this.enablePlugin();
this.moveBySettingsOrLoad();
super.updatePlugin();
}
/**
* Disables the plugin functionality for this Handsontable instance.
*/
disablePlugin() {
(0, _element.removeClass)(this.hot.rootElement, CSS_PLUGIN);
this.unregisterEvents();
this.backlight.destroy();
this.guideline.destroy();
super.disablePlugin();
}
/**
* Moves a single row.
*
* To see the outcome, rerender your grid by calling [`render()`](@/api/core.md#render).
*
* @param {number} row Visual row index to be moved.
* @param {number} finalIndex Visual row index, being a start index for the moved rows. Points to where the elements will be placed after the moving action.
* To check the visualization of the final index, please take a look at [documentation](@/guides/rows/row-moving.md#drag-and-move-actions-of-manualrowmove-plugin).
* @fires Hooks#beforeRowMove
* @fires Hooks#afterRowMove
* @returns {boolean}
*/
moveRow(row, finalIndex) {
return this.moveRows([row], finalIndex);
}
/**
* Moves multiple rows.
*
* To see the outcome, rerender your grid by calling [`render()`](@/api/core.md#render).
*
* @param {Array} rows Array of visual row indexes to be moved.
* @param {number} finalIndex Visual row index, being a start index for the moved rows. Points to where the elements will be placed after the moving action.
* To check the visualization of the final index, please take a look at [documentation](@/guides/rows/row-moving.md#drag-and-move-actions-of-manualrowmove-plugin).
* @fires Hooks#beforeRowMove
* @fires Hooks#afterRowMove
* @returns {boolean}
*/
moveRows(rows, finalIndex) {
const priv = privatePool.get(this);
const dropIndex = priv.cachedDropIndex;
const movePossible = this.isMovePossible(rows, finalIndex);
const beforeMoveHook = this.hot.runHooks('beforeRowMove', rows, finalIndex, dropIndex, movePossible);
priv.cachedDropIndex = void 0;
if (beforeMoveHook === false) {
return;
}
if (movePossible) {
this.hot.rowIndexMapper.moveIndexes(rows, finalIndex);
}
const movePerformed = movePossible && this.isRowOrderChanged(rows, finalIndex);
this.hot.runHooks('afterRowMove', rows, finalIndex, dropIndex, movePossible, movePerformed);
return movePerformed;
}
/**
* Drag a single row to drop index position.
*
* @param {number} row Visual row index to be dragged.
* @param {number} dropIndex Visual row index, being a drop index for the moved rows. Points to where we are going to drop the moved elements.
* To check visualization of drop index please take a look at [documentation](@/guides/rows/row-moving.md#drag-and-move-actions-of-manualrowmove-plugin).
* @fires Hooks#beforeRowMove
* @fires Hooks#afterRowMove
* @returns {boolean}
*/
dragRow(row, dropIndex) {
return this.dragRows([row], dropIndex);
}
/**
* Drag multiple rows to drop index position.
*
* @param {Array} rows Array of visual row indexes to be dragged.
* @param {number} dropIndex Visual row index, being a drop index for the moved rows. Points to where we are going to drop the moved elements.
* To check visualization of drop index please take a look at [documentation](@/guides/rows/row-moving.md#drag-and-move-actions-of-manualrowmove-plugin).
* @fires Hooks#beforeRowMove
* @fires Hooks#afterRowMove
* @returns {boolean}
*/
dragRows(rows, dropIndex) {
const finalIndex = this.countFinalIndex(rows, dropIndex);
const priv = privatePool.get(this);
priv.cachedDropIndex = dropIndex;
return this.moveRows(rows, finalIndex);
}
/**
* Indicates if it's possible to move rows to the desired position. Some of the actions aren't possible, i.e. You can’t move more than one element to the last position.
*
* @param {Array} movedRows Array of visual row indexes to be moved.
* @param {number} finalIndex Visual row index, being a start index for the moved rows. Points to where the elements will be placed after the moving action.
* To check the visualization of the final index, please take a look at [documentation](@/guides/rows/row-moving.md#drag-and-move-actions-of-manualrowmove-plugin).
* @returns {boolean}
*/
isMovePossible(movedRows, finalIndex) {
const length = this.hot.rowIndexMapper.getNotTrimmedIndexesLength();
// An attempt to transfer more rows to start destination than is possible (only when moving from the top to the bottom).
const tooHighDestinationIndex = movedRows.length + finalIndex > length;
const tooLowDestinationIndex = finalIndex < 0;
const tooLowMovedRowIndex = movedRows.some(movedRow => movedRow < 0);
const tooHighMovedRowIndex = movedRows.some(movedRow => movedRow >= length);
if (tooHighDestinationIndex || tooLowDestinationIndex || tooLowMovedRowIndex || tooHighMovedRowIndex) {
return false;
}
return true;
}
/**
* Indicates if order of rows was changed.
*
* @private
* @param {Array} movedRows Array of visual row indexes to be moved.
* @param {number} finalIndex Visual row index, being a start index for the moved rows. Points to where the elements will be placed after the moving action.
* To check the visualization of the final index, please take a look at [documentation](@/guides/rows/row-moving.md#drag-and-move-actions-of-manualrowmove-plugin).
* @returns {boolean}
*/
isRowOrderChanged(movedRows, finalIndex) {
return movedRows.some((row, nrOfMovedElement) => row - nrOfMovedElement !== finalIndex);
}
/**
* Count the final row index from the drop index.
*
* @private
* @param {Array} movedRows Array of visual row indexes to be moved.
* @param {number} dropIndex Visual row index, being a drop index for the moved rows.
* @returns {number} Visual row index, being a start index for the moved rows.
*/
countFinalIndex(movedRows, dropIndex) {
const numberOfRowsLowerThanDropIndex = (0, _array.arrayReduce)(movedRows, (numberOfRows, currentRowIndex) => {
if (currentRowIndex < dropIndex) {
numberOfRows += 1;
}
return numberOfRows;
}, 0);
return dropIndex - numberOfRowsLowerThanDropIndex;
}
/**
* Gets the sum of the heights of rows in the provided range.
*
* @private
* @param {number} fromRow Visual row index.
* @param {number} toRow Visual row index.
* @returns {number}
*/
getRowsHeight(fromRow, toRow) {
const rowMapper = this.hot.rowIndexMapper;
let rowsHeight = 0;
for (let visualRowIndex = fromRow; visualRowIndex <= toRow; visualRowIndex++) {
const renderableIndex = rowMapper.getRenderableFromVisualIndex(visualRowIndex);
if (renderableIndex !== null) {
rowsHeight += this.hot.view._wt.wtTable.getRowHeight(renderableIndex) || 23;
}
}
return rowsHeight;
}
/**
* Loads initial settings when persistent state is saved or when plugin was initialized as an array.
*
* @private
*/
moveBySettingsOrLoad() {
const pluginSettings = this.hot.getSettings()[PLUGIN_KEY];
if (Array.isArray(pluginSettings)) {
this.moveRows(pluginSettings, 0);
} else if (pluginSettings !== void 0) {
const persistentState = this.persistentStateLoad();
if (persistentState.length) {
this.moveRows(persistentState, 0);
}
}
}
/**
* Checks if the provided row is in the fixedRowsTop section.
*
* @private
* @param {number} row Visual row index to check.
* @returns {boolean}
*/
isFixedRowTop(row) {
return row < this.hot.getSettings().fixedRowsTop;
}
/**
* Checks if the provided row is in the fixedRowsBottom section.
*
* @private
* @param {number} row Visual row index to check.
* @returns {boolean}
*/
isFixedRowBottom(row) {
return row > this.hot.countRows() - 1 - this.hot.getSettings().fixedRowsBottom;
}
/**
* Saves the manual row positions to the persistent state (the {@link Options#persistentState} option has to be enabled).
*
* @private
* @fires Hooks#persistentStateSave
*/
persistentStateSave() {
// The `PersistentState` plugin should be refactored.
this.hot.runHooks('persistentStateSave', 'manualRowMove', this.hot.rowIndexMapper.getIndexesSequence());
}
/**
* Loads the manual row positions from the persistent state (the {@link Options#persistentState} option has to be enabled).
*
* @private
* @fires Hooks#persistentStateLoad
* @returns {Array} Stored state.
*/
persistentStateLoad() {
const storedState = {};
this.hot.runHooks('persistentStateLoad', 'manualRowMove', storedState);
return storedState.value ? storedState.value : [];
}
/**
* Prepares an array of indexes based on actual selection.
*
* @private
* @returns {Array}
*/
prepareRowsToMoving() {
const selection = this.hot.getSelectedRangeLast();
const selectedRows = [];
if (!selection) {
return selectedRows;
}
const {
from,
to
} = selection;
const start = Math.min(from.row, to.row);
const end = Math.max(from.row, to.row);
(0, _number.rangeEach)(start, end, i => {
selectedRows.push(i);
});
return selectedRows;
}
/**
* Update the UI visual position.
*
* @private
*/
refreshPositions() {
const priv = privatePool.get(this);
const coords = priv.target.coords;
const firstVisible = this.hot.view.getFirstFullyVisibleRow();
const lastVisible = this.hot.view.getLastFullyVisibleRow();
const countRows = this.hot.countRows();
if (this.isFixedRowTop(coords.row) && firstVisible > 0) {
this.hot.scrollViewportTo(this.hot.rowIndexMapper.getNearestNotHiddenIndex(firstVisible - 1, -1));
}
if (this.isFixedRowBottom(coords.row) && lastVisible < countRows) {
this.hot.scrollViewportTo(this.hot.rowIndexMapper.getNearestNotHiddenIndex(lastVisible + 1, 1), undefined, true);
}
const wtTable = this.hot.view._wt.wtTable;
const TD = priv.target.TD;
const rootElement = this.hot.rootElement;
const rootElementOffset = (0, _element.offset)(rootElement);
const trimmingContainer = (0, _element.getTrimmingContainer)(rootElement);
const tableScroll = wtTable.holder.scrollTop;
let trimmingContainerScroll;
// Trimming container is the `window` element.
if (this.hot.rootWindow === trimmingContainer) {
trimmingContainerScroll = trimmingContainer.scrollY;
} else {
trimmingContainerScroll = trimmingContainer.scrollTop;
}
const pixelsAbove = rootElementOffset.top - trimmingContainerScroll;
const pixelsRelToTableStart = priv.target.eventPageY - pixelsAbove + tableScroll;
const hiderHeight = wtTable.hider.offsetHeight;
const tbodyOffsetTop = wtTable.TBODY.offsetTop;
const backlightElemMarginTop = this.backlight.getOffset().top;
const backlightElemHeight = this.backlight.getSize().height;
const tdMiddle = TD.offsetHeight / 2;
const tdHeight = TD.offsetHeight;
let tdStartPixel = this.hot.view.THEAD.offsetHeight + this.getRowsHeight(0, coords.row - 1);
const isBelowTable = pixelsRelToTableStart >= tdStartPixel + tdMiddle;
if (this.isFixedRowTop(coords.row)) {
tdStartPixel += wtTable.holder.scrollTop;
}
if (coords.row < 0) {
// if hover on colHeader
priv.target.row = firstVisible > 0 ? firstVisible - 1 : firstVisible;
} else if (isBelowTable) {
// if hover on lower part of TD
priv.target.row = coords.row + 1;
// unfortunately first row is bigger than rest
tdStartPixel += coords.row === 0 ? tdHeight - 1 : tdHeight;
} else {
// elsewhere on table
priv.target.row = coords.row;
}
let backlightTop = pixelsRelToTableStart;
let guidelineTop = tdStartPixel;
if (pixelsRelToTableStart + backlightElemHeight + backlightElemMarginTop >= hiderHeight) {
// prevent display backlight below table
backlightTop = hiderHeight - backlightElemHeight - backlightElemMarginTop;
} else if (pixelsRelToTableStart + backlightElemMarginTop < tbodyOffsetTop) {
// prevent display above below table
backlightTop = tbodyOffsetTop + Math.abs(backlightElemMarginTop);
}
if (tdStartPixel >= hiderHeight - 1) {
// prevent display guideline below table
guidelineTop = hiderHeight - 1;
}
this.backlight.setPosition(backlightTop);
this.guideline.setPosition(guidelineTop);
}
/**
* Binds the events used by the plugin.
*
* @private
*/
registerEvents() {
const {
documentElement
} = this.hot.rootDocument;
this.eventManager.addEventListener(documentElement, 'mousemove', event => this.onMouseMove(event));
this.eventManager.addEventListener(documentElement, 'mouseup', () => this.onMouseUp());
}
/**
* Unbinds the events used by the plugin.
*
* @private
*/
unregisterEvents() {
this.eventManager.clear();
}
/**
* Change the behavior of selection / dragging.
*
* @private
* @param {MouseEvent} event `mousedown` event properties.
* @param {CellCoords} coords Visual cell coordinates where was fired event.
* @param {HTMLElement} TD Cell represented as HTMLElement.
* @param {object} controller An object with properties `row`, `column` and `cell`. Each property contains
* a boolean value that allows or disallows changing the selection for that particular area.
*/
onBeforeOnCellMouseDown(event, coords, TD, controller) {
const {
wtTable,
wtViewport
} = this.hot.view._wt;
const isHeaderSelection = this.hot.selection.isSelectedByRowHeader();
const selection = this.hot.getSelectedRangeLast();
const priv = privatePool.get(this);
if (!selection || !isHeaderSelection || priv.pressed || event.button !== 0) {
priv.pressed = false;
priv.rowsToMove.length = 0;
(0, _element.removeClass)(this.hot.rootElement, [CSS_ON_MOVING, CSS_SHOW_UI]);
return;
}
const guidelineIsNotReady = this.guideline.isBuilt() && !this.guideline.isAppended();
const backlightIsNotReady = this.backlight.isBuilt() && !this.backlight.isAppended();
if (guidelineIsNotReady && backlightIsNotReady) {
this.guideline.appendTo(wtTable.hider);
this.backlight.appendTo(wtTable.hider);
}
const {
from,
to
} = selection;
const start = Math.min(from.row, to.row);
const end = Math.max(from.row, to.row);
if (coords.col < 0 && coords.row >= start && coords.row <= end) {
controller.row = true;
priv.pressed = true;
priv.target.eventPageY = event.pageY;
priv.target.coords = coords;
priv.target.TD = TD;
priv.rowsToMove = this.prepareRowsToMoving();
const leftPos = wtTable.holder.scrollLeft + wtViewport.getRowHeaderWidth();
this.backlight.setPosition(null, leftPos);
this.backlight.setSize(wtTable.hider.offsetWidth - leftPos, this.getRowsHeight(start, end));
this.backlight.setOffset((this.getRowsHeight(start, coords.row - 1) + event.offsetY) * -1, null);
(0, _element.addClass)(this.hot.rootElement, CSS_ON_MOVING);
this.refreshPositions();
} else {
(0, _element.removeClass)(this.hot.rootElement, CSS_AFTER_SELECTION);
priv.pressed = false;
priv.rowsToMove.length = 0;
}
}
/**
* 'mouseMove' event callback. Fired when pointer move on document.documentElement.
*
* @private
* @param {MouseEvent} event `mousemove` event properties.
*/
onMouseMove(event) {
const priv = privatePool.get(this);
if (!priv.pressed) {
return;
}
// callback for browser which doesn't supports CSS pointer-event: none
if (event.target === this.backlight.element) {
const height = this.backlight.getSize().height;
this.backlight.setSize(null, 0);
setTimeout(function () {
this.backlight.setPosition(null, height);
});
}
priv.target.eventPageY = event.pageY;
this.refreshPositions();
}
/**
* 'beforeOnCellMouseOver' hook callback. Fired when pointer was over cell.
*
* @private
* @param {MouseEvent} event `mouseover` event properties.
* @param {CellCoords} coords Visual cell coordinates where was fired event.
* @param {HTMLElement} TD Cell represented as HTMLElement.
* @param {object} controller An object with properties `row`, `column` and `cell`. Each property contains
* a boolean value that allows or disallows changing the selection for that particular area.
*/
onBeforeOnCellMouseOver(event, coords, TD, controller) {
const selectedRange = this.hot.getSelectedRangeLast();
const priv = privatePool.get(this);
if (!selectedRange || !priv.pressed) {
return;
}
if (priv.rowsToMove.indexOf(coords.row) > -1) {
(0, _element.removeClass)(this.hot.rootElement, CSS_SHOW_UI);
} else {
(0, _element.addClass)(this.hot.rootElement, CSS_SHOW_UI);
}
controller.row = true;
controller.column = true;
controller.cell = true;
priv.target.coords = coords;
priv.target.TD = TD;
}
/**
* `onMouseUp` hook callback.
*
* @private
*/
onMouseUp() {
const priv = privatePool.get(this);
const target = priv.target.row;
const rowsLen = priv.rowsToMove.length;
priv.pressed = false;
priv.backlightHeight = 0;
(0, _element.removeClass)(this.hot.rootElement, [CSS_ON_MOVING, CSS_SHOW_UI, CSS_AFTER_SELECTION]);
if (this.hot.selection.isSelectedByRowHeader()) {
(0, _element.addClass)(this.hot.rootElement, CSS_AFTER_SELECTION);
}
if (rowsLen < 1 || target === void 0) {
return;
}
const firstMovedVisualRow = priv.rowsToMove[0];
const firstMovedPhysicalRow = this.hot.toPhysicalRow(firstMovedVisualRow);
const movePerformed = this.dragRows(priv.rowsToMove, target);
priv.rowsToMove.length = 0;
if (movePerformed === true) {
this.persistentStateSave();
this.hot.render();
this.hot.view.adjustElementsSize(true);
const selectionStart = this.hot.toVisualRow(firstMovedPhysicalRow);
const selectionEnd = selectionStart + rowsLen - 1;
this.hot.selectRows(selectionStart, selectionEnd);
}
}
/**
* `afterScrollHorizontally` hook callback. Fired the table was scrolled horizontally.
*
* @private
*/
onAfterScrollHorizontally() {
const wtTable = this.hot.view._wt.wtTable;
const headerWidth = this.hot.view._wt.wtViewport.getRowHeaderWidth();
const scrollLeft = wtTable.holder.scrollLeft;
const posLeft = headerWidth + scrollLeft;
this.backlight.setPosition(null, posLeft);
this.backlight.setSize(wtTable.hider.offsetWidth - posLeft);
}
/**
* Builds the plugin's UI.
*
* @private
*/
buildPluginUI() {
this.backlight.build();
this.guideline.build();
}
/**
* Callback for the `afterLoadData` hook.
*
* @private
*/
onAfterLoadData() {
this.moveBySettingsOrLoad();
}
/**
* Destroys the plugin instance.
*/
destroy() {
this.backlight.destroy();
this.guideline.destroy();
super.destroy();
}
}
exports.ManualRowMove = ManualRowMove;