handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
1,077 lines (1,030 loc) • 60.1 kB
JavaScript
"use strict";
exports.__esModule = true;
require("core-js/modules/es.error.cause.js");
require("core-js/modules/es.array.push.js");
require("core-js/modules/es.set.difference.v2.js");
require("core-js/modules/es.set.intersection.v2.js");
require("core-js/modules/es.set.is-disjoint-from.v2.js");
require("core-js/modules/es.set.is-subset-of.v2.js");
require("core-js/modules/es.set.is-superset-of.v2.js");
require("core-js/modules/es.set.symmetric-difference.v2.js");
require("core-js/modules/es.set.union.v2.js");
require("core-js/modules/esnext.iterator.constructor.js");
require("core-js/modules/esnext.iterator.for-each.js");
require("core-js/modules/esnext.iterator.map.js");
require("core-js/modules/esnext.iterator.some.js");
var _highlight = _interopRequireWildcard(require("./highlight/highlight"));
var _range = _interopRequireDefault(require("./range"));
var _object = require("./../helpers/object");
var _mixed = require("./../helpers/mixed");
var _number = require("./../helpers/number");
var _array = require("./../helpers/array");
var _localHooks = _interopRequireDefault(require("./../mixins/localHooks"));
var _transformation = require("./transformation");
var _utils = require("./utils");
var _templateLiteralTag = require("./../helpers/templateLiteralTag");
var _a11y = require("../helpers/a11y");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
/**
* @typedef {object} SelectionState
* @property {CellRange[]} ranges The array of all ranges.
* @property {CellRange} activeRange The active range.
* @property {number} activeSelectionLayer The active selection layer.
* @property {number[]} selectedByRowHeader The state of the selected row headers.
* @property {number[]} selectedByColumnHeader The state of the selected column headers.
* @property {boolean} disableHeadersHighlight The state of the disable headers highlight.
*/
/**
* @class Selection
* @util
*/
var _extenderTransformation = /*#__PURE__*/new WeakMap();
var _focusTransformation = /*#__PURE__*/new WeakMap();
var _isFocusSelectionChanged = /*#__PURE__*/new WeakMap();
var _disableHeadersHighlight = /*#__PURE__*/new WeakMap();
var _selectionSource = /*#__PURE__*/new WeakMap();
var _expectedLayersCount = /*#__PURE__*/new WeakMap();
var _activeSelectionLayer = /*#__PURE__*/new WeakMap();
class Selection {
constructor(settings, tableProps) {
var _this = this;
/**
* Handsontable settings instance.
*
* @type {GridSettings}
*/
_defineProperty(this, "settings", void 0);
/**
* An additional object with dynamically defined properties which describes table state.
*
* @type {object}
*/
_defineProperty(this, "tableProps", void 0);
/**
* The flag which determines if the selection is in progress.
*
* @type {boolean}
*/
_defineProperty(this, "inProgress", false);
/**
* Selection data layer (handle visual coordinates).
*
* @type {SelectionRange}
*/
_defineProperty(this, "selectedRange", new _range.default((highlight, from, to) => {
return this.tableProps.createCellRange(highlight, from, to);
}));
/**
* Visualization layer.
*
* @type {Highlight}
*/
_defineProperty(this, "highlight", void 0);
/**
* The module for modifying coordinates of the start and end selection.
*
* @type {ExtenderTransformation}
*/
_classPrivateFieldInitSpec(this, _extenderTransformation, void 0);
/**
* The module for modifying coordinates of the focus selection.
*
* @type {FocusTransformation}
*/
_classPrivateFieldInitSpec(this, _focusTransformation, void 0);
/**
* The collection of the selection layer levels where the whole row was selected using the row header or
* the corner header.
*
* @type {Set<number>}
*/
_defineProperty(this, "selectedByRowHeader", new Set());
/**
* The collection of the selection layer levels where the whole column was selected using the column header or
* the corner header.
*
* @type {Set<number>}
*/
_defineProperty(this, "selectedByColumnHeader", new Set());
/**
* The flag which determines if the focus selection was changed.
*
* @type {boolean}
*/
_classPrivateFieldInitSpec(this, _isFocusSelectionChanged, false);
/**
* When sets disable highlighting the headers even when the logical coordinates points on them.
*
* @type {boolean}
*/
_classPrivateFieldInitSpec(this, _disableHeadersHighlight, false);
/**
* The source of the selection. It can be one of the following values: `mouse`, `unknown` or any other string.
*
* @type {'mouse' | 'unknown' | string}
*/
_classPrivateFieldInitSpec(this, _selectionSource, 'unknown');
/**
* The number of expected layers. It is used mostly to track when the last selection layer of non-contiguous
* selection is applied, thus the viewport scroll is triggered.
*
* @param {number}
*/
_classPrivateFieldInitSpec(this, _expectedLayersCount, -1);
/**
* The index of the active range layer. Active range layer is the layer that has visible focus highlight.
* Focus highlight may jump between selection range layers.
*
* @type {number}
*/
_classPrivateFieldInitSpec(this, _activeSelectionLayer, 0);
this.settings = settings;
this.tableProps = tableProps;
this.highlight = new _highlight.default({
headerClassName: settings.currentHeaderClassName,
activeHeaderClassName: settings.activeHeaderClassName,
rowClassName: settings.currentRowClassName,
columnClassName: settings.currentColClassName,
cellAttributes: [(0, _a11y.A11Y_SELECTED)()],
rowIndexMapper: this.tableProps.rowIndexMapper,
columnIndexMapper: this.tableProps.columnIndexMapper,
disabledCellSelection: (row, column) => this.tableProps.isDisabledCellSelection(row, column),
cellCornerVisible: function () {
return _this.isCellCornerVisible(...arguments);
},
areaCornerVisible: function () {
return _this.isAreaCornerVisible(...arguments);
},
visualToRenderableCoords: coords => this.tableProps.visualToRenderableCoords(coords),
renderableToVisualCoords: coords => this.tableProps.renderableToVisualCoords(coords),
createCellCoords: (row, column) => this.tableProps.createCellCoords(row, column),
createCellRange: (highlight, from, to) => this.tableProps.createCellRange(highlight, from, to)
});
_classPrivateFieldSet(_extenderTransformation, this, new _transformation.ExtenderTransformation(this.selectedRange, {
...this.tableProps,
navigableHeaders: () => settings.navigableHeaders,
fixedRowsBottom: () => settings.fixedRowsBottom,
minSpareRows: () => settings.minSpareRows,
minSpareCols: () => settings.minSpareCols,
autoWrapRow: () => settings.autoWrapRow,
autoWrapCol: () => settings.autoWrapCol
}));
_classPrivateFieldSet(_focusTransformation, this, new _transformation.FocusTransformation(this.selectedRange, {
...this.tableProps,
navigableHeaders: () => settings.navigableHeaders,
fixedRowsBottom: () => 0,
minSpareRows: () => 0,
minSpareCols: () => 0,
autoWrapRow: () => true,
autoWrapCol: () => true
}));
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('beforeTransformStart', function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _this.runLocalHooks('beforeModifyTransformStart', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('afterTransformStart', function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _this.runLocalHooks('afterModifyTransformStart', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('beforeTransformEnd', function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return _this.runLocalHooks('beforeModifyTransformEnd', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('afterTransformEnd', function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return _this.runLocalHooks('afterModifyTransformEnd', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('insertRowRequire', function () {
for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
args[_key5] = arguments[_key5];
}
return _this.runLocalHooks('insertRowRequire', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('insertColRequire', function () {
for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
args[_key6] = arguments[_key6];
}
return _this.runLocalHooks('insertColRequire', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('beforeRowWrap', function () {
for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
args[_key7] = arguments[_key7];
}
return _this.runLocalHooks('beforeRowWrap', ...args);
});
_classPrivateFieldGet(_extenderTransformation, this).addLocalHook('beforeColumnWrap', function () {
for (var _len8 = arguments.length, args = new Array(_len8), _key8 = 0; _key8 < _len8; _key8++) {
args[_key8] = arguments[_key8];
}
return _this.runLocalHooks('beforeColumnWrap', ...args);
});
_classPrivateFieldGet(_focusTransformation, this).addLocalHook('beforeTransformStart', function () {
for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
args[_key9] = arguments[_key9];
}
return _this.runLocalHooks('beforeModifyTransformFocus', ...args);
});
_classPrivateFieldGet(_focusTransformation, this).addLocalHook('afterTransformStart', function () {
for (var _len0 = arguments.length, args = new Array(_len0), _key0 = 0; _key0 < _len0; _key0++) {
args[_key0] = arguments[_key0];
}
return _this.runLocalHooks('afterModifyTransformFocus', ...args);
});
}
/**
* Gets all selection range layers of the selection.
*
* @returns {SelectionRange}
*/
getSelectedRange() {
return this.selectedRange;
}
/**
* Gets the active selection range layer.
*
* @returns {CellRange}
*/
getActiveSelectedRange() {
return this.selectedRange.peekByIndex(_classPrivateFieldGet(_activeSelectionLayer, this));
}
/**
* Gets the index of the active selection range layer.
*
* @returns {number}
*/
getActiveSelectionLayerIndex() {
return _classPrivateFieldGet(_activeSelectionLayer, this);
}
/**
* Sets the index of the active selection range layer.
*
* @param {number} layerIndex The index of the active selection range layer.
*/
setActiveSelectionLayerIndex(layerIndex) {
_classPrivateFieldSet(_activeSelectionLayer, this, layerIndex);
}
/**
* Marks the source of the selection. It can be one of the following values: `mouse`, or any other string.
*
* @param {'mouse' | 'unknown' | string} sourceName The source name.
*/
markSource(sourceName) {
_classPrivateFieldSet(_selectionSource, this, sourceName);
}
/**
* Marks end of the selection source. It restores the selection source to default value which is 'unknown'.
*/
markEndSource() {
_classPrivateFieldSet(_selectionSource, this, 'unknown');
}
/**
* Returns the source of the selection.
*
* @returns {'mouse' | 'unknown' | string}
*/
getSelectionSource() {
return _classPrivateFieldGet(_selectionSource, this);
}
/**
* Set the number of expected layers. The method is not obligatory to call. It is used mostly internally
* to determine when the last selection layer of non-contiguous is applied, thus the viewport scroll is triggered.
*
* @param {number} layersCount The number of expected layers.
*/
setExpectedLayers(layersCount) {
_classPrivateFieldSet(_expectedLayersCount, this, layersCount);
}
/**
* Indicate that selection process began. It sets internally `.inProgress` property to `true`.
*/
begin() {
this.inProgress = true;
}
/**
* Indicate that selection process finished. It sets internally `.inProgress` property to `false`.
*/
finish() {
this.runLocalHooks('afterSelectionFinished', Array.from(this.selectedRange));
this.inProgress = false;
_classPrivateFieldSet(_expectedLayersCount, this, -1);
}
/**
* Check if the process of selecting the cell/cells is in progress.
*
* @returns {boolean}
*/
isInProgress() {
return this.inProgress;
}
/**
* Starts selection range on given coordinate object.
*
* @param {CellCoords} coords Visual coords.
* @param {boolean} [multipleSelection] If `true`, selection will be worked in 'multiple' mode. This option works
* only when 'selectionMode' is set as 'multiple'. If the argument is not defined
* the default trigger will be used.
* @param {boolean} [fragment=false] If `true`, the selection will be treated as a partial selection where the
* `setRangeEnd` method won't be called on every `setRangeStart` call.
* @param {CellCoords} [highlightCoords] If set, allows changing the coordinates of the highlight/focus cell.
*/
setRangeStart(coords, multipleSelection) {
let fragment = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
let highlightCoords = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : coords;
const isMultipleMode = this.settings.selectionMode === 'multiple';
const isMultipleSelection = (0, _mixed.isUndefined)(multipleSelection) ? this.tableProps.getShortcutManager().isCtrlPressed() : multipleSelection;
// We are creating copy. We would like to modify just the start of the selection by below hook. Then original coords
// should be handled by next methods.
const coordsClone = coords.clone();
_classPrivateFieldSet(_disableHeadersHighlight, this, false);
_classPrivateFieldSet(_isFocusSelectionChanged, this, false);
this.runLocalHooks(`beforeSetRangeStart${fragment ? 'Only' : ''}`, coordsClone);
if (!isMultipleMode || isMultipleMode && !isMultipleSelection && (0, _mixed.isUndefined)(multipleSelection)) {
this.selectedRange.clear();
(0, _array.arrayEach)(this.highlight.getAreas(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getLayeredAreas(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getRowHeaders(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getColumnHeaders(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getActiveRowHeaders(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getActiveColumnHeaders(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getActiveCornerHeaders(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getRowHighlights(), highlight => void highlight.clear());
(0, _array.arrayEach)(this.highlight.getColumnHighlights(), highlight => void highlight.clear());
}
this.selectedRange.add(coordsClone).current().setHighlight(highlightCoords.clone());
if (this.getLayerLevel() === 0) {
this.selectedByRowHeader.clear();
this.selectedByColumnHeader.clear();
}
if (!fragment) {
this.setRangeEnd(coords);
}
}
/**
* Starts selection range on given coordinate object.
*
* @param {CellCoords} coords Visual coords.
* @param {boolean} [multipleSelection] If `true`, selection will be worked in 'multiple' mode. This option works
* only when 'selectionMode' is set as 'multiple'. If the argument is not defined
* the default trigger will be used.
* @param {CellCoords} [highlightCoords] If set, allows changing the coordinates of the highlight/focus cell.
*/
setRangeStartOnly(coords, multipleSelection) {
let highlightCoords = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : coords;
this.setRangeStart(coords, multipleSelection, true, highlightCoords);
}
/**
* Ends selection range on given coordinate object.
*
* @param {CellCoords} coords Visual coords.
* @param {number} [layerIndex] The layer index to set the end on. If not provided, the current layer level is used.
*/
setRangeEnd(coords) {
let layerIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getLayerLevel();
if (this.selectedRange.isEmpty()) {
return;
}
this.setActiveSelectionLayerIndex(layerIndex);
const coordsClone = coords.clone();
const countRows = this.tableProps.countRows();
const countCols = this.tableProps.countCols();
const isSingle = this.getActiveSelectedRange().clone().setTo(coords).isSingleHeader();
// Ignore processing the end range when the header selection starts overlapping the corner and
// the selection is not a single header highlight.
if ((countRows > 0 || countCols > 0) && (countRows === 0 && coordsClone.col < 0 && !isSingle || countCols === 0 && coordsClone.row < 0 && !isSingle)) {
return;
}
this.runLocalHooks('beforeSetRangeEnd', coordsClone);
this.begin();
const cellRange = this.getActiveSelectedRange();
if (!this.settings.navigableHeaders) {
cellRange.highlight.normalize();
}
if (this.settings.selectionMode === 'single') {
cellRange.setFrom(cellRange.highlight);
cellRange.setTo(cellRange.highlight);
} else {
const horizontalDir = cellRange.getHorizontalDirection();
const verticalDir = cellRange.getVerticalDirection();
const isMultiple = this.isMultiple();
cellRange.setTo(coordsClone);
if (isMultiple && (horizontalDir !== cellRange.getHorizontalDirection() || cellRange.getWidth() === 1 && !cellRange.includes(cellRange.highlight))) {
cellRange.from.assign({
col: cellRange.highlight.col
});
}
if (isMultiple && (verticalDir !== cellRange.getVerticalDirection() || cellRange.getHeight() === 1 && !cellRange.includes(cellRange.highlight))) {
cellRange.from.assign({
row: cellRange.highlight.row
});
}
}
// Prevent creating "area" selection that overlaps headers.
if (countRows > 0 && countCols > 0) {
if (!this.settings.navigableHeaders || this.settings.navigableHeaders && !cellRange.isSingleHeader()) {
cellRange.to.normalize();
}
}
this.runLocalHooks('beforeHighlightSet');
this.setRangeFocus(this.getActiveSelectedRange().highlight, layerIndex);
this.applyAndCommit(this.getActiveSelectedRange(), layerIndex);
const isLastLayer = _classPrivateFieldGet(_expectedLayersCount, this) === -1 || this.selectedRange.size() === _classPrivateFieldGet(_expectedLayersCount, this);
this.runLocalHooks('afterSetRangeEnd', coords, isLastLayer);
}
/**
* Applies and commits the selection to all layers (using the Walkontable Selection API) based on the selection (CellRanges)
* collected in the `selectedRange` module.
*
* @param {CellRange} [cellRange] The cell range to apply. If not provided, the current selection is used.
* @param {number} [layerLevel] The layer level to apply. If not provided, the current layer level is used.
*/
applyAndCommit() {
let cellRange = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getActiveSelectedRange();
let layerLevel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getLayerLevel();
const countRows = this.tableProps.countRows();
const countCols = this.tableProps.countCols();
this.highlight.useLayerLevel(layerLevel);
const areaHighlight = this.highlight.createArea();
const layeredAreaHighlight = this.highlight.createLayeredArea();
const rowHeaderHighlight = this.highlight.createRowHeader();
const columnHeaderHighlight = this.highlight.createColumnHeader();
const activeRowHeaderHighlight = this.highlight.createActiveRowHeader();
const activeColumnHeaderHighlight = this.highlight.createActiveColumnHeader();
const activeCornerHeaderHighlight = this.highlight.createActiveCornerHeader();
const rowHighlight = this.highlight.createRowHighlight();
const columnHighlight = this.highlight.createColumnHighlight();
areaHighlight.clear();
layeredAreaHighlight.clear();
rowHeaderHighlight.clear();
columnHeaderHighlight.clear();
activeRowHeaderHighlight.clear();
activeColumnHeaderHighlight.clear();
activeCornerHeaderHighlight.clear();
rowHighlight.clear();
columnHighlight.clear();
if (this.highlight.isEnabledFor(_highlight.AREA_TYPE, cellRange.highlight) && (this.isMultiple() || layerLevel >= 1)) {
areaHighlight.add(cellRange.from).add(cellRange.to).commit();
layeredAreaHighlight.add(cellRange.from).add(cellRange.to).commit();
if (layerLevel === 1) {
// For single cell selection in the same layer, we do not create area selection to prevent blue background.
// When non-consecutive selection is performed we have to add that missing area selection to the previous layer
// based on previous coordinates. It only occurs when the previous selection wasn't select multiple cells.
const previousRange = this.selectedRange.peekByIndex(layerLevel - 1);
this.highlight.useLayerLevel(layerLevel - 1);
this.highlight.createArea().add(previousRange.from).commit()
// Range may start with hidden indexes. Commit would not found start point (as we add just the `from` coords).
.syncWith(previousRange);
this.highlight.createLayeredArea().add(previousRange.from).commit()
// Range may start with hidden indexes. Commit would not found start point (as we add just the `from` coords).
.syncWith(previousRange);
this.highlight.useLayerLevel(layerLevel);
}
}
if (this.highlight.isEnabledFor(_highlight.HEADER_TYPE, cellRange.highlight)) {
if (!cellRange.isSingleHeader()) {
const rowCoordsFrom = this.tableProps.createCellCoords(Math.max(cellRange.from.row, 0), -1);
const rowCoordsTo = this.tableProps.createCellCoords(cellRange.to.row, -1);
const columnCoordsFrom = this.tableProps.createCellCoords(-1, Math.max(cellRange.from.col, 0));
const columnCoordsTo = this.tableProps.createCellCoords(-1, cellRange.to.col);
if (this.settings.selectionMode === 'single') {
rowHeaderHighlight.add(rowCoordsFrom).commit();
columnHeaderHighlight.add(columnCoordsFrom).commit();
rowHighlight.add(rowCoordsFrom).commit();
columnHighlight.add(columnCoordsFrom).commit();
} else {
rowHeaderHighlight.add(rowCoordsFrom).add(rowCoordsTo).commit();
columnHeaderHighlight.add(columnCoordsFrom).add(columnCoordsTo).commit();
rowHighlight.add(rowCoordsFrom).add(rowCoordsTo).commit();
columnHighlight.add(columnCoordsFrom).add(columnCoordsTo).commit();
}
}
const highlightRowHeaders = !_classPrivateFieldGet(_disableHeadersHighlight, this) && this.isEntireRowSelected() && (countCols > 0 && countCols === cellRange.getWidth() || countCols === 0 && this.isSelectedByRowHeader());
const highlightColumnHeaders = !_classPrivateFieldGet(_disableHeadersHighlight, this) && this.isEntireColumnSelected() && (countRows > 0 && countRows === cellRange.getHeight() || countRows === 0 && this.isSelectedByColumnHeader());
if (highlightRowHeaders) {
activeRowHeaderHighlight.add(this.tableProps.createCellCoords(Math.max(cellRange.from.row, 0), Math.min(-this.tableProps.countRowHeaders(), -1))).add(this.tableProps.createCellCoords(Math.max(cellRange.to.row, 0), -1)).commit();
}
if (highlightColumnHeaders) {
activeColumnHeaderHighlight.add(this.tableProps.createCellCoords(Math.min(-this.tableProps.countColHeaders(), -1), Math.max(cellRange.from.col, 0))).add(this.tableProps.createCellCoords(-1, Math.max(cellRange.to.col, 0))).commit();
}
if (highlightRowHeaders && highlightColumnHeaders) {
activeCornerHeaderHighlight.add(this.tableProps.createCellCoords(-this.tableProps.countColHeaders(), -this.tableProps.countRowHeaders())).add(this.tableProps.createCellCoords(-1, -1)).commit();
}
}
}
/**
* Sets the selection focus position at the specified coordinates.
*
* @param {CellCoords} coords The CellCoords instance with defined visual coordinates.
* @param {number} [layerIndex] The layer index to set the focus on.
*/
setRangeFocus(coords) {
let layerIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getLayerLevel();
if (this.selectedRange.isEmpty()) {
return;
}
this.setActiveSelectionLayerIndex(layerIndex);
_classPrivateFieldGet(_extenderTransformation, this).setActiveLayerIndex(layerIndex);
_classPrivateFieldGet(_focusTransformation, this).setActiveLayerIndex(layerIndex);
const cellRange = this.getActiveSelectedRange();
if (!this.inProgress) {
this.runLocalHooks('beforeSetFocus', coords);
}
const focusHighlight = this.highlight.getFocus();
focusHighlight.clear();
cellRange.setHighlight(coords);
if (!this.inProgress) {
this.runLocalHooks('beforeHighlightSet');
}
if (this.highlight.isEnabledFor(_highlight.FOCUS_TYPE, cellRange.highlight)) {
focusHighlight.add(cellRange.highlight).commit().syncWith(cellRange);
}
if (!this.inProgress) {
_classPrivateFieldSet(_isFocusSelectionChanged, this, true);
this.runLocalHooks('afterSetFocus', cellRange.highlight);
}
}
/**
* Selects cell relative to the current cell (if possible).
*
* @param {number} rowDelta Rows number to move, value can be passed as negative number.
* @param {number} colDelta Columns number to move, value can be passed as negative number.
* @param {boolean} [createMissingRecords=false] If `true` the new rows/columns will be created if necessary.
* Otherwise, row/column will be created according to `minSpareRows/minSpareCols` settings of Handsontable.
*/
transformStart(rowDelta, colDelta) {
let createMissingRecords = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
const {
visualCoords
} = _classPrivateFieldGet(_extenderTransformation, this).transformStart(rowDelta, colDelta, createMissingRecords);
this.setRangeStart(visualCoords);
}
/**
* Sets selection end cell relative to the current selection end cell (if possible).
*
* @param {number} rowDelta Rows number to move, value can be passed as negative number.
* @param {number} colDelta Columns number to move, value can be passed as negative number.
*/
transformEnd(rowDelta, colDelta) {
const {
visualCoords,
selectionLayer
} = _classPrivateFieldGet(_extenderTransformation, this).transformEnd(rowDelta, colDelta);
this.setRangeEnd(visualCoords, selectionLayer);
}
/**
* Transforms the focus cell selection relative to the current focus position.
*
* @param {number} rowDelta Rows number to move, value can be passed as negative number.
* @param {number} colDelta Columns number to move, value can be passed as negative number.
*/
transformFocus(rowDelta, colDelta) {
const {
selectionLayer,
visualCoords
} = _classPrivateFieldGet(_focusTransformation, this).transformStart(rowDelta, colDelta);
this.setRangeFocus(visualCoords.normalize(), selectionLayer);
}
/**
* Transforms the last selection layer down or up by the index count.
*
* @param {number} visualRowIndex Visual row index from which the selection will be shifted.
* @param {number} amount The number of rows to shift the selection.
*/
shiftRows(visualRowIndex, amount) {
if (!this.isSelected()) {
return;
}
const range = this.getActiveSelectedRange();
if (this.isSelectedByCorner()) {
this.selectAll(true, true, {
disableHeadersHighlight: true
});
} else if (this.isSelectedByColumnHeader() || range.getOuterTopStartCorner().row >= visualRowIndex) {
const {
from,
to,
highlight
} = range;
const countRows = this.tableProps.countRows();
const isSelectedByRowHeader = this.isSelectedByRowHeader();
const isSelectedByColumnHeader = this.isSelectedByColumnHeader();
const minRow = isSelectedByColumnHeader ? -1 : 0;
const coordsStartAmount = isSelectedByColumnHeader ? 0 : amount;
// Remove from the stack the last added selection as that selection below will be
// replaced by new transformed selection.
this.getSelectedRange().pop();
const coordsStart = this.tableProps.createCellCoords((0, _number.clamp)(from.row + coordsStartAmount, minRow, countRows - 1), from.col);
const coordsEnd = this.tableProps.createCellCoords((0, _number.clamp)(to.row + amount, minRow, countRows - 1), to.col);
this.markSource('shift');
if (highlight.row >= visualRowIndex) {
this.setRangeStartOnly(coordsStart, true, this.tableProps.createCellCoords((0, _number.clamp)(highlight.row + amount, 0, countRows - 1), highlight.col));
} else {
this.setRangeStartOnly(coordsStart, true);
}
if (isSelectedByRowHeader) {
this.selectedByRowHeader.add(this.getLayerLevel());
}
if (isSelectedByColumnHeader) {
this.selectedByColumnHeader.add(this.getLayerLevel());
}
this.setRangeEnd(coordsEnd);
this.markEndSource();
}
}
/**
* Transforms the last selection layer left or right by the index count.
*
* @param {number} visualColumnIndex Visual column index from which the selection will be shifted.
* @param {number} amount The number of columns to shift the selection.
*/
shiftColumns(visualColumnIndex, amount) {
if (!this.isSelected()) {
return;
}
const range = this.getActiveSelectedRange();
if (this.isSelectedByCorner()) {
this.selectAll(true, true, {
disableHeadersHighlight: true
});
} else if (this.isSelectedByRowHeader() || range.getOuterTopStartCorner().col >= visualColumnIndex) {
const {
from,
to,
highlight
} = range;
const countCols = this.tableProps.countCols();
const isSelectedByRowHeader = this.isSelectedByRowHeader();
const isSelectedByColumnHeader = this.isSelectedByColumnHeader();
const minColumn = isSelectedByRowHeader ? -1 : 0;
const coordsStartAmount = isSelectedByRowHeader ? 0 : amount;
// Remove from the stack the last added selection as that selection below will be
// replaced by new transformed selection.
this.getSelectedRange().pop();
const coordsStart = this.tableProps.createCellCoords(from.row, (0, _number.clamp)(from.col + coordsStartAmount, minColumn, countCols - 1));
const coordsEnd = this.tableProps.createCellCoords(to.row, (0, _number.clamp)(to.col + amount, minColumn, countCols - 1));
this.markSource('shift');
if (highlight.col >= visualColumnIndex) {
this.setRangeStartOnly(coordsStart, true, this.tableProps.createCellCoords(highlight.row, (0, _number.clamp)(highlight.col + amount, 0, countCols - 1)));
} else {
this.setRangeStartOnly(coordsStart, true);
}
if (isSelectedByRowHeader) {
this.selectedByRowHeader.add(this.getLayerLevel());
}
if (isSelectedByColumnHeader) {
this.selectedByColumnHeader.add(this.getLayerLevel());
}
this.setRangeEnd(coordsEnd);
this.markEndSource();
}
}
/**
* Returns currently used layer level.
*
* @returns {number} Returns layer level starting from 0. If no selection was added to the table -1 is returned.
*/
getLayerLevel() {
return this.selectedRange.size() - 1;
}
/**
* Returns `true` if currently there is a selection on the screen, `false` otherwise.
*
* @returns {boolean}
*/
isSelected() {
return !this.selectedRange.isEmpty();
}
/**
* Returns information if we have a multi-selection. This method check multi-selection only on the latest layer of
* the selection.
*
* @param {CellRange} [cellRange] The cell range to check. If not provided, the latest selection layer is used.
* @returns {boolean}
*/
isMultiple() {
let cellRange = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getActiveSelectedRange();
if (!this.isSelected()) {
return false;
}
const isMultipleListener = (0, _object.createObjectPropListener)(!cellRange.isSingle());
this.runLocalHooks('afterIsMultipleSelection', isMultipleListener);
return isMultipleListener.value;
}
/**
* Checks if the last selection involves changing the focus cell position only.
*
* @returns {boolean}
*/
isFocusSelectionChanged() {
return this.isSelected() && _classPrivateFieldGet(_isFocusSelectionChanged, this);
}
/**
* Returns `true` if the selection was applied by clicking to the row header. If the `layerLevel`
* argument is passed then only that layer will be checked. Otherwise, it checks if any row header
* was clicked on any selection layer level.
*
* @param {number} [layerLevel=this.getLayerLevel()] Selection layer level to check.
* @returns {boolean}
*/
isSelectedByRowHeader() {
let layerLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getLayerLevel();
return !this.isSelectedByCorner(layerLevel) && (layerLevel === -1 ? this.selectedByRowHeader.size > 0 : this.selectedByRowHeader.has(layerLevel));
}
/**
* Returns `true` if the selection consists of entire rows (including their headers). If the `layerLevel`
* argument is passed then only that layer will be checked. Otherwise, it checks the selection for all layers.
*
* @param {number} [layerLevel=this.getLayerLevel()] Selection layer level to check.
* @returns {boolean}
*/
isEntireRowSelected() {
let layerLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getLayerLevel();
const tester = range => {
const {
col
} = range.getOuterTopStartCorner();
const rowHeaders = this.tableProps.countRowHeaders();
const countCols = this.tableProps.countCols();
return (rowHeaders > 0 && col < 0 || rowHeaders === 0) && range.getWidth() === countCols;
};
if (layerLevel === -1) {
return Array.from(this.selectedRange).some(range => tester(range));
}
const range = this.selectedRange.peekByIndex(layerLevel);
return range ? tester(range) : false;
}
/**
* Returns `true` if the selection was applied by clicking to the column header. If the `layerLevel`
* argument is passed then only that layer will be checked. Otherwise, it checks if any column header
* was clicked on any selection layer level.
*
* @param {number} [layerLevel=this.getLayerLevel()] Selection layer level to check.
* @returns {boolean}
*/
isSelectedByColumnHeader() {
let layerLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getLayerLevel();
return !this.isSelectedByCorner() && (layerLevel === -1 ? this.selectedByColumnHeader.size > 0 : this.selectedByColumnHeader.has(layerLevel));
}
/**
* Returns `true` if the selection consists of entire columns (including their headers). If the `layerLevel`
* argument is passed then only that layer will be checked. Otherwise, it checks the selection for all layers.
*
* @param {number} [layerLevel=this.getLayerLevel()] Selection layer level to check.
* @returns {boolean}
*/
isEntireColumnSelected() {
let layerLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getLayerLevel();
const tester = range => {
const {
row
} = range.getOuterTopStartCorner();
const colHeaders = this.tableProps.countColHeaders();
const countRows = this.tableProps.countRows();
return (colHeaders > 0 && row < 0 || colHeaders === 0) && range.getHeight() === countRows;
};
if (layerLevel === -1) {
return Array.from(this.selectedRange).some(range => tester(range));
}
const range = this.selectedRange.peekByIndex(layerLevel);
return range ? tester(range) : false;
}
/**
* Returns `true` if the selection was applied by clicking on the row or column header on any layer level.
*
* @returns {boolean}
*/
isSelectedByAnyHeader() {
return this.isSelectedByRowHeader(-1) || this.isSelectedByColumnHeader(-1) || this.isSelectedByCorner();
}
/**
* Returns `true` if the selection was applied by clicking on the left-top corner overlay.
*
* @returns {boolean}
*/
isSelectedByCorner() {
return this.selectedByColumnHeader.has(this.getLayerLevel()) && this.selectedByRowHeader.has(this.getLayerLevel());
}
/**
* Returns `true` if coords is within selection coords. This method iterates through all selection layers to check if
* the coords object is within selection range.
*
* @param {CellCoords} coords The CellCoords instance with defined visual coordinates.
* @returns {boolean}
*/
inInSelection(coords) {
return this.selectedRange.includes(coords);
}
/**
* Returns `true` if the cell corner should be visible.
*
* @private
* @returns {boolean} `true` if the corner element has to be visible, `false` otherwise.
*/
isCellCornerVisible() {
return this.settings.fillHandle && !this.tableProps.isEditorOpened() && !this.isMultiple();
}
/**
* Returns `true` if the cell coordinates are visible (renderable).
*
* @private
* @param {CellCoords} coords The cell coordinates to check.
* @returns {boolean}
*/
isCellVisible(coords) {
const renderableCoords = this.tableProps.visualToRenderableCoords(coords);
return renderableCoords.row !== null && renderableCoords.col !== null;
}
/**
* Returns `true` if the area corner should be visible.
*
* @param {number} layerLevel The layer level.
* @returns {boolean} `true` if the corner element has to be visible, `false` otherwise.
*/
isAreaCornerVisible(layerLevel) {
if (Number.isInteger(layerLevel) && layerLevel !== this.getLayerLevel()) {
return false;
}
return this.settings.fillHandle && !this.tableProps.isEditorOpened() && this.isMultiple();
}
/**
* Clear the selection by resetting the collected ranges and highlights.
*/
clear() {
// TODO: collections selectedByColumnHeader and selectedByRowHeader should be clear too.
this.selectedRange.clear();
this.highlight.clear();
}
/**
* Deselects all selected cells.
*/
deselect() {
if (!this.isSelected()) {
return;
}
this.inProgress = false;
this.clear();
this.runLocalHooks('afterDeselect');
}
/**
* Selects all cells and headers.
*
* @param {boolean} [includeRowHeaders=false] `true` If the selection should include the row headers,
* `false` otherwise.
* @param {boolean} [includeColumnHeaders=false] `true` If the selection should include the column
* headers, `false` otherwise.
* @param {object} [options] Additional object with options.
* @param {{row: number, col: number} | boolean} [options.focusPosition] The argument allows changing the cell/header
* focus position. The value takes an object with a `row` and `col` properties from -N to N, where
* negative values point to the headers and positive values point to the cell range. If `false`, the focus
* position won't be changed.
* @param {boolean} [options.disableHeadersHighlight] If `true`, disables highlighting the headers even when
* the logical coordinates points on them.
*/
selectAll() {
var _this$getActiveSelect;
let includeRowHeaders = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let includeColumnHeaders = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
focusPosition: false,
disableHeadersHighlight: false
};
const nrOfRows = this.tableProps.countRows();
const nrOfColumns = this.tableProps.countCols();
const countRowHeaders = this.tableProps.countRowHeaders();
const countColHeaders = this.tableProps.countColHeaders();
const rowFrom = includeColumnHeaders ? -countColHeaders : 0;
const columnFrom = includeRowHeaders ? -countRowHeaders : 0;
// We can't select cells when there is no data.
if (rowFrom === 0 && columnFrom === 0 && (nrOfRows === 0 || nrOfColumns === 0)) {
return;
}
let highlight = (_this$getActiveSelect = this.getActiveSelectedRange()) === null || _this$getActiveSelect === void 0 ? void 0 : _this$getActiveSelect.highlight;
const {
focusPosition,
disableHeadersHighlight
} = options;
if (focusPosition && Number.isInteger(focusPosition === null || focusPosition === void 0 ? void 0 : focusPosition.row) && Number.isInteger(focusPosition === null || focusPosition === void 0 ? void 0 : focusPosition.col)) {
highlight = this.tableProps.createCellCoords((0, _number.clamp)(focusPosition.row, rowFrom, nrOfRows - 1), (0, _number.clamp)(focusPosition.col, columnFrom, nrOfColumns - 1));
}
const startCoords = this.tableProps.createCellCoords(rowFrom, columnFrom);
const endCoords = this.tableProps.createCellCoords(nrOfRows - 1, nrOfColumns - 1);
this.clear();
this.runLocalHooks('beforeSelectAll', startCoords, endCoords, highlight);
this.setRangeStartOnly(startCoords, undefined, highlight);
_classPrivateFieldSet(_disableHeadersHighlight, this, disableHeadersHighlight);
if (columnFrom < 0) {
this.selectedByRowHeader.add(this.getLayerLevel());
}
if (rowFrom < 0) {
this.selectedByColumnHeader.add(this.getLayerLevel());
}
this.setRangeEnd(endCoords);
this.runLocalHooks('afterSelectAll', startCoords, endCoords, highlight);
this.finish();
}
/**
* Make multiple, non-contiguous selection specified by `row` and `column` values or a range of cells
* finishing at `endRow`, `endColumn`. The method supports two input formats, first as an array of arrays such
* as `[[rowStart, columnStart, rowEnd, columnEnd]]` and second format as an array of CellRange objects.
* If the passed ranges have another format the exception will be thrown.
*
* @param {Array[]|CellRange[]} selectionRanges The coordinates which define what the cells should be selected.
* @returns {boolean} Returns `true` if selection was successful, `false` otherwise.
*/
selectCells(selectionRanges) {
var _this2 = this;
const selectionType = (0, _utils.detectSelectionType)(selectionRanges);
if (selectionType === _utils.SELECTION_TYPE_EMPTY) {
return false;
} else if (selectionType === _utils.SELECTION_TYPE_UNRECOGNIZED) {
throw new Error((0, _templateLiteralTag.toSingleLine)`Unsupported format of the selection ranges was passed. To select cells pass\x20
the coordinates as an array of arrays ([[rowStart, columnStart/columnPropStart, rowEnd,\x20
columnEnd/columnPropEnd]]) or as an array of CellRange objects.`);
}
const selectionSchemaNormalizer = (0, _utils.normalizeSelectionFactory)(selectionType, {
createCellCoords: function () {
return _this2.tableProps.createCellCoords(...arguments);
},
createCellRange: function () {
return _this2.tableProps.createCellRange(...arguments);
},
propToCol: prop => this.tableProps.propToCol(prop),
keepDirection: true
});
const navigableHeaders = this.settings.navigableHeaders;
const tableParams = {
countRows: this.tableProps.countRows(),
countCols: this.tableProps.countCols(),
countRowHeaders: navigableHeaders ? this.tableProps.countRowHeaders() : 0,
countColHeaders: navigableHeaders ? this.tableProps.countColHeaders() : 0
};
// Check if every layer of the coordinates are valid.
const isValid = !selectionRanges.some(selection => {
const cellRange = selectionSchemaNormalizer(selection);
const rangeValidity = cellRange.isValid(tableParams);
return !(rangeValidity && !cellRange.containsHeaders() || rangeValidity && cellRange.containsHeaders() && cellRange.isSingleHeader());
});
if (isValid) {
this.clear();
this.setExpectedLayers(selectionRanges.length);
(0, _array.arrayEach)(selectionRanges, selection => {
const {
from,
to
} = selectionSchemaNormalizer(selection);
this.setRangeStartOnly(from.clone(), false);
this.setRangeEnd(to.clone());
});
this.finish();
}
return isValid;
}
/**
* Select column specified by `startColumn` visual index or column property or a range of columns finishing at
* `endColumn`.
*
* @param {number|string} startColumn Visual column index or column property from which the selection starts.
* @param {number|string} [endColumn] Visual column index or column property from to the selection finishes.
* @param {number | { row: number, col: number }} [focusPosition=0] The argument allows changing the cell/header focus
* position. The value can take visual row index from -N to N, where negative values point to the headers and positive
* values point to the cell range. An object with `row` and `col` properties also can be passed to change the focus
* position horizontally.
* @returns {boolean} Returns `true` if selection was successful, `false` otherwise.
*/
selectColumns(startColumn) {
let endColumn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startColumn;
let focusPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
const start = typeof startColumn === 'string' ? this.tableProps.propToCol(startColumn) : startColumn;
const end = typeof endColumn === 'string' ? this.tableProps.propToCol(endColumn) : endColumn;
const countRows = this.tableProps.countRows();
const countCols = this.tableProps.countCols();
const countColHeaders = this.tableProps.countColHeaders();
const columnHeaderLastIndex = countColHeaders === 0 ? 0 : -countColHeaders;
const fromCoords = this.tableProps.createCellCoords(columnHeaderLastIndex, start);
const toCoords = this.tableProps.createCellCoords(countRows - 1, end);
const isValid = this.tableProps.createCellRange(fromCoords, fromCoords, toCoords).isValid({
countRows,
countCols,
countRowHeaders: 0,
countColHeaders
});
if (isValid) {
let highlightRow = 0;
let highlightColumn = 0;
if (Number.isInteger(focusPosition === null || focusPosition === void 0 ? void 0 : focusPosition.row) && Number.isInteger(focusPosition === null || focusPosition === void 0 ? void 0 : focusPositio