UNPKG

187 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports.default = Core;
5require("core-js/modules/es.error.cause.js");
6require("core-js/modules/es.array.push.js");
7require("core-js/modules/es.set.difference.v2.js");
8require("core-js/modules/es.set.intersection.v2.js");
9require("core-js/modules/es.set.is-disjoint-from.v2.js");
10require("core-js/modules/es.set.is-subset-of.v2.js");
11require("core-js/modules/es.set.is-superset-of.v2.js");
12require("core-js/modules/es.set.symmetric-difference.v2.js");
13require("core-js/modules/es.set.union.v2.js");
14require("core-js/modules/web.immediate.js");
15var _element = require("./helpers/dom/element");
16var _function = require("./helpers/function");
17var _mixed = require("./helpers/mixed");
18var _browser = require("./helpers/browser");
19var _editorManager = _interopRequireDefault(require("./editorManager"));
20var _eventManager = _interopRequireDefault(require("./eventManager"));
21var _object = require("./helpers/object");
22var _focusManager = require("./focusManager");
23var _array = require("./helpers/array");
24var _parseTable = require("./utils/parseTable");
25var _registry = require("./plugins/registry");
26var _registry2 = require("./renderers/registry");
27var _registry3 = require("./editors/registry");
28var _registry4 = require("./validators/registry");
29var _string = require("./helpers/string");
30var _number = require("./helpers/number");
31var _tableView = _interopRequireDefault(require("./tableView"));
32var _dataSource = _interopRequireDefault(require("./dataMap/dataSource"));
33var _data = require("./helpers/data");
34var _translations = require("./translations");
35var _rootInstance = require("./utils/rootInstance");
36var _src = require("./3rdparty/walkontable/src");
37var _pluginHooks = _interopRequireDefault(require("./pluginHooks"));
38var _registry5 = require("./i18n/registry");
39var _utils = require("./i18n/utils");
40var _selection = require("./selection");
41var _dataMap = require("./dataMap");
42var _index3 = require("./core/index");
43var _uniqueMap = require("./utils/dataStructures/uniqueMap");
44var _shortcuts = require("./shortcuts");
45var _shortcutContexts = require("./shortcutContexts");
46function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
47let activeGuid = null;
48
49/**
50 * Keeps the collection of the all Handsontable instances created on the same page. The
51 * list is then used to trigger the "afterUnlisten" hook when the "listen()" method was
52 * called on another instance.
53 *
54 * @type {Map<string, Core>}
55 */
56const foreignHotInstances = new Map();
57
58/**
59 * A set of deprecated feature names.
60 *
61 * @type {Set<string>}
62 */
63// eslint-disable-next-line no-unused-vars
64const deprecationWarns = new Set();
65
66/* eslint-disable jsdoc/require-description-complete-sentence */
67/**
68 * Handsontable constructor.
69 *
70 * @core
71 * @class Core
72 * @description
73 *
74 * The `Handsontable` class (known as the `Core`) lets you modify the grid's behavior by using Handsontable's public API methods.
75 *
76 * ::: only-for react
77 * To use these methods, associate a Handsontable instance with your instance
78 * of the [`HotTable` component](@/guides/getting-started/installation/installation.md#_4-use-the-hottable-component),
79 * by using React's `ref` feature (read more on the [Instance methods](@/guides/getting-started/react-methods/react-methods.md) page).
80 * :::
81 *
82 * ## How to call a method
83 *
84 * ::: only-for javascript
85 * ```js
86 * // create a Handsontable instance
87 * const hot = new Handsontable(document.getElementById('example'), options);
88 *
89 * // call a method
90 * hot.setDataAtCell(0, 0, 'new value');
91 * ```
92 * :::
93 *
94 * ::: only-for react
95 * ```jsx
96 * import { useRef } from 'react';
97 *
98 * const hotTableComponent = useRef(null);
99 *
100 * <HotTable
101 * // associate your `HotTable` component with a Handsontable instance
102 * ref={hotTableComponent}
103 * settings={options}
104 * />
105 *
106 * // access the Handsontable instance, under the `.current.hotInstance` property
107 * // call a method
108 * hotTableComponent.current.hotInstance.setDataAtCell(0, 0, 'new value');
109 * ```
110 * :::
111 *
112 * @param {HTMLElement} rootElement The element to which the Handsontable instance is injected.
113 * @param {object} userSettings The user defined options.
114 * @param {boolean} [rootInstanceSymbol=false] Indicates if the instance is root of all later instances created.
115 */
116function Core(rootElement, userSettings) {
117 var _userSettings$layoutD,
118 _this = this;
119 let rootInstanceSymbol = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
120 let instance = this;
121 const eventManager = new _eventManager.default(instance);
122 let datamap;
123 let dataSource;
124 let grid;
125 let editorManager;
126 let focusManager;
127 let viewportScroller;
128 let firstRun = true;
129 if ((0, _rootInstance.hasValidParameter)(rootInstanceSymbol)) {
130 (0, _rootInstance.registerAsRootInstance)(this);
131 }
132
133 // TODO: check if references to DOM elements should be move to UI layer (Walkontable)
134 /**
135 * Reference to the container element.
136 *
137 * @private
138 * @type {HTMLElement}
139 */
140 this.rootElement = rootElement;
141 /**
142 * The nearest document over container.
143 *
144 * @private
145 * @type {Document}
146 */
147 this.rootDocument = rootElement.ownerDocument;
148 /**
149 * Window object over container's document.
150 *
151 * @private
152 * @type {Window}
153 */
154 this.rootWindow = this.rootDocument.defaultView;
155 /**
156 * A boolean to tell if the Handsontable has been fully destroyed. This is set to `true`
157 * after `afterDestroy` hook is called.
158 *
159 * @memberof Core#
160 * @member isDestroyed
161 * @type {boolean}
162 */
163 this.isDestroyed = false;
164 /**
165 * The counter determines how many times the render suspending was called. It allows
166 * tracking the nested suspending calls. For each render suspend resuming call the
167 * counter is decremented. The value equal to 0 means the render suspending feature
168 * is disabled.
169 *
170 * @private
171 * @type {number}
172 */
173 this.renderSuspendedCounter = 0;
174 /**
175 * The counter determines how many times the execution suspending was called. It allows
176 * tracking the nested suspending calls. For each execution suspend resuming call the
177 * counter is decremented. The value equal to 0 means the execution suspending feature
178 * is disabled.
179 *
180 * @private
181 * @type {number}
182 */
183 this.executionSuspendedCounter = 0;
184 const layoutDirection = (_userSettings$layoutD = userSettings === null || userSettings === void 0 ? void 0 : userSettings.layoutDirection) !== null && _userSettings$layoutD !== void 0 ? _userSettings$layoutD : 'inherit';
185 const rootElementDirection = ['rtl', 'ltr'].includes(layoutDirection) ? layoutDirection : this.rootWindow.getComputedStyle(this.rootElement).direction;
186 this.rootElement.setAttribute('dir', rootElementDirection);
187
188 /**
189 * Checks if the grid is rendered using the right-to-left layout direction.
190 *
191 * @since 12.0.0
192 * @memberof Core#
193 * @function isRtl
194 * @returns {boolean} True if RTL.
195 */
196 this.isRtl = function () {
197 return rootElementDirection === 'rtl';
198 };
199
200 /**
201 * Checks if the grid is rendered using the left-to-right layout direction.
202 *
203 * @since 12.0.0
204 * @memberof Core#
205 * @function isLtr
206 * @returns {boolean} True if LTR.
207 */
208 this.isLtr = function () {
209 return !instance.isRtl();
210 };
211
212 /**
213 * Returns 1 for LTR; -1 for RTL. Useful for calculations.
214 *
215 * @since 12.0.0
216 * @memberof Core#
217 * @function getDirectionFactor
218 * @returns {number} Returns 1 for LTR; -1 for RTL.
219 */
220 this.getDirectionFactor = function () {
221 return instance.isLtr() ? 1 : -1;
222 };
223 userSettings.language = (0, _registry5.getValidLanguageCode)(userSettings.language);
224 const metaManager = new _dataMap.MetaManager(instance, userSettings, [_dataMap.DynamicCellMetaMod, _dataMap.ExtendMetaPropertiesMod]);
225 const tableMeta = metaManager.getTableMeta();
226 const globalMeta = metaManager.getGlobalMeta();
227 const pluginsRegistry = (0, _uniqueMap.createUniqueMap)();
228 this.container = this.rootDocument.createElement('div');
229 this.renderCall = false;
230 rootElement.insertBefore(this.container, rootElement.firstChild);
231 if ((0, _rootInstance.isRootInstance)(this)) {
232 (0, _mixed._injectProductInfo)(userSettings.licenseKey, rootElement);
233 }
234 this.guid = `ht_${(0, _string.randomString)()}`; // this is the namespace for global events
235
236 foreignHotInstances.set(this.guid, this);
237
238 /**
239 * Instance of index mapper which is responsible for managing the column indexes.
240 *
241 * @memberof Core#
242 * @member columnIndexMapper
243 * @type {IndexMapper}
244 */
245 this.columnIndexMapper = new _translations.IndexMapper();
246 /**
247 * Instance of index mapper which is responsible for managing the row indexes.
248 *
249 * @memberof Core#
250 * @member rowIndexMapper
251 * @type {IndexMapper}
252 */
253 this.rowIndexMapper = new _translations.IndexMapper();
254 this.columnIndexMapper.addLocalHook('indexesSequenceChange', source => {
255 instance.runHooks('afterColumnSequenceChange', source);
256 });
257 this.rowIndexMapper.addLocalHook('indexesSequenceChange', source => {
258 instance.runHooks('afterRowSequenceChange', source);
259 });
260 dataSource = new _dataSource.default(instance);
261 if (!this.rootElement.id || this.rootElement.id.substring(0, 3) === 'ht_') {
262 this.rootElement.id = this.guid; // if root element does not have an id, assign a random id
263 }
264 const visualToRenderableCoords = coords => {
265 const {
266 row: visualRow,
267 col: visualColumn
268 } = coords;
269 return instance._createCellCoords(
270 // We just store indexes for rows and columns without headers.
271 visualRow >= 0 ? instance.rowIndexMapper.getRenderableFromVisualIndex(visualRow) : visualRow, visualColumn >= 0 ? instance.columnIndexMapper.getRenderableFromVisualIndex(visualColumn) : visualColumn);
272 };
273 const renderableToVisualCoords = coords => {
274 const {
275 row: renderableRow,
276 col: renderableColumn
277 } = coords;
278 return instance._createCellCoords(
279 // We just store indexes for rows and columns without headers.
280 renderableRow >= 0 ? instance.rowIndexMapper.getVisualFromRenderableIndex(renderableRow) : renderableRow, renderableColumn >= 0 ? instance.columnIndexMapper.getVisualFromRenderableIndex(renderableColumn) : renderableColumn // eslint-disable-line max-len
281 );
282 };
283 const findFirstNonHiddenRenderableRow = (visualRowFrom, visualRowTo) => {
284 const dir = visualRowTo > visualRowFrom ? 1 : -1;
285 const minIndex = Math.min(visualRowFrom, visualRowTo);
286 const maxIndex = Math.max(visualRowFrom, visualRowTo);
287 const rowIndex = instance.rowIndexMapper.getNearestNotHiddenIndex(visualRowFrom, dir);
288 if (rowIndex === null || dir === 1 && rowIndex > maxIndex || dir === -1 && rowIndex < minIndex) {
289 return null;
290 }
291 return rowIndex >= 0 ? instance.rowIndexMapper.getRenderableFromVisualIndex(rowIndex) : rowIndex;
292 };
293 const findFirstNonHiddenRenderableColumn = (visualColumnFrom, visualColumnTo) => {
294 const dir = visualColumnTo > visualColumnFrom ? 1 : -1;
295 const minIndex = Math.min(visualColumnFrom, visualColumnTo);
296 const maxIndex = Math.max(visualColumnFrom, visualColumnTo);
297 const columnIndex = instance.columnIndexMapper.getNearestNotHiddenIndex(visualColumnFrom, dir);
298 if (columnIndex === null || dir === 1 && columnIndex > maxIndex || dir === -1 && columnIndex < minIndex) {
299 return null;
300 }
301 return columnIndex >= 0 ? instance.columnIndexMapper.getRenderableFromVisualIndex(columnIndex) : columnIndex;
302 };
303 let selection = new _selection.Selection(tableMeta, {
304 rowIndexMapper: instance.rowIndexMapper,
305 columnIndexMapper: instance.columnIndexMapper,
306 countCols: () => instance.countCols(),
307 countRows: () => instance.countRows(),
308 propToCol: prop => datamap.propToCol(prop),
309 isEditorOpened: () => instance.getActiveEditor() ? instance.getActiveEditor().isOpened() : false,
310 countRenderableColumns: () => this.view.countRenderableColumns(),
311 countRenderableRows: () => this.view.countRenderableRows(),
312 countRowHeaders: () => this.countRowHeaders(),
313 countColHeaders: () => this.countColHeaders(),
314 countRenderableRowsInRange: function () {
315 return _this.view.countRenderableRowsInRange(...arguments);
316 },
317 countRenderableColumnsInRange: function () {
318 return _this.view.countRenderableColumnsInRange(...arguments);
319 },
320 getShortcutManager: () => instance.getShortcutManager(),
321 createCellCoords: (row, column) => instance._createCellCoords(row, column),
322 createCellRange: (highlight, from, to) => instance._createCellRange(highlight, from, to),
323 visualToRenderableCoords,
324 renderableToVisualCoords,
325 findFirstNonHiddenRenderableRow,
326 findFirstNonHiddenRenderableColumn,
327 isDisabledCellSelection: (visualRow, visualColumn) => {
328 if (visualRow < 0 || visualColumn < 0) {
329 return instance.getSettings().disableVisualSelection;
330 }
331 return instance.getCellMeta(visualRow, visualColumn).disableVisualSelection;
332 }
333 });
334 this.selection = selection;
335 const onIndexMapperCacheUpdate = _ref => {
336 let {
337 hiddenIndexesChanged
338 } = _ref;
339 if (hiddenIndexesChanged) {
340 this.selection.commit();
341 }
342 };
343 this.columnIndexMapper.addLocalHook('cacheUpdated', onIndexMapperCacheUpdate);
344 this.rowIndexMapper.addLocalHook('cacheUpdated', onIndexMapperCacheUpdate);
345 this.selection.addLocalHook('afterSetRangeEnd', (cellCoords, isLastSelectionLayer) => {
346 const preventScrolling = (0, _object.createObjectPropListener)(false);
347 const selectionRange = this.selection.getSelectedRange();
348 const {
349 from,
350 to
351 } = selectionRange.current();
352 const selectionLayerLevel = selectionRange.size() - 1;
353 this.runHooks('afterSelection', from.row, from.col, to.row, to.col, preventScrolling, selectionLayerLevel);
354 this.runHooks('afterSelectionByProp', from.row, instance.colToProp(from.col), to.row, instance.colToProp(to.col), preventScrolling, selectionLayerLevel);
355 if (isLastSelectionLayer && (!preventScrolling.isTouched() || preventScrolling.isTouched() && !preventScrolling.value)) {
356 viewportScroller.scrollTo(cellCoords);
357 }
358 const isSelectedByRowHeader = selection.isSelectedByRowHeader();
359 const isSelectedByColumnHeader = selection.isSelectedByColumnHeader();
360
361 // @TODO: These CSS classes are no longer needed anymore. They are used only as a indicator of the selected
362 // rows/columns in the MergedCells plugin (via border.js#L520 in the walkontable module). After fixing
363 // the Border class this should be removed.
364 if (isSelectedByRowHeader && isSelectedByColumnHeader) {
365 (0, _element.addClass)(this.rootElement, ['ht__selection--rows', 'ht__selection--columns']);
366 } else if (isSelectedByRowHeader) {
367 (0, _element.removeClass)(this.rootElement, 'ht__selection--columns');
368 (0, _element.addClass)(this.rootElement, 'ht__selection--rows');
369 } else if (isSelectedByColumnHeader) {
370 (0, _element.removeClass)(this.rootElement, 'ht__selection--rows');
371 (0, _element.addClass)(this.rootElement, 'ht__selection--columns');
372 } else {
373 (0, _element.removeClass)(this.rootElement, ['ht__selection--rows', 'ht__selection--columns']);
374 }
375 if (selection.getSelectionSource() !== 'shift') {
376 editorManager.closeEditor(null);
377 }
378 instance.view.render();
379 editorManager.prepareEditor();
380 });
381 this.selection.addLocalHook('beforeSetFocus', cellCoords => {
382 this.runHooks('beforeSelectionFocusSet', cellCoords.row, cellCoords.col);
383 });
384 this.selection.addLocalHook('afterSetFocus', cellCoords => {
385 const preventScrolling = (0, _object.createObjectPropListener)(false);
386 this.runHooks('afterSelectionFocusSet', cellCoords.row, cellCoords.col, preventScrolling);
387 if (!preventScrolling.isTouched() || preventScrolling.isTouched() && !preventScrolling.value) {
388 viewportScroller.scrollTo(cellCoords);
389 }
390 editorManager.closeEditor();
391 instance.view.render();
392 editorManager.prepareEditor();
393 });
394 this.selection.addLocalHook('afterSelectionFinished', cellRanges => {
395 const selectionLayerLevel = cellRanges.length - 1;
396 const {
397 from,
398 to
399 } = cellRanges[selectionLayerLevel];
400 this.runHooks('afterSelectionEnd', from.row, from.col, to.row, to.col, selectionLayerLevel);
401 this.runHooks('afterSelectionEndByProp', from.row, instance.colToProp(from.col), to.row, instance.colToProp(to.col), selectionLayerLevel);
402 });
403 this.selection.addLocalHook('afterIsMultipleSelection', isMultiple => {
404 const changedIsMultiple = this.runHooks('afterIsMultipleSelection', isMultiple.value);
405 if (isMultiple.value) {
406 isMultiple.value = changedIsMultiple;
407 }
408 });
409 this.selection.addLocalHook('afterDeselect', () => {
410 editorManager.closeEditor();
411 instance.view.render();
412 (0, _element.removeClass)(this.rootElement, ['ht__selection--rows', 'ht__selection--columns']);
413 this.runHooks('afterDeselect');
414 });
415 this.selection.addLocalHook('beforeHighlightSet', () => this.runHooks('beforeSelectionHighlightSet')).addLocalHook('beforeSetRangeStart', function () {
416 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
417 args[_key] = arguments[_key];
418 }
419 return _this.runHooks('beforeSetRangeStart', ...args);
420 }).addLocalHook('beforeSetRangeStartOnly', function () {
421 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
422 args[_key2] = arguments[_key2];
423 }
424 return _this.runHooks('beforeSetRangeStartOnly', ...args);
425 }).addLocalHook('beforeSetRangeEnd', function () {
426 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
427 args[_key3] = arguments[_key3];
428 }
429 return _this.runHooks('beforeSetRangeEnd', ...args);
430 }).addLocalHook('beforeSelectColumns', function () {
431 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
432 args[_key4] = arguments[_key4];
433 }
434 return _this.runHooks('beforeSelectColumns', ...args);
435 }).addLocalHook('afterSelectColumns', function () {
436 for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
437 args[_key5] = arguments[_key5];
438 }
439 return _this.runHooks('afterSelectColumns', ...args);
440 }).addLocalHook('beforeSelectRows', function () {
441 for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
442 args[_key6] = arguments[_key6];
443 }
444 return _this.runHooks('beforeSelectRows', ...args);
445 }).addLocalHook('afterSelectRows', function () {
446 for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
447 args[_key7] = arguments[_key7];
448 }
449 return _this.runHooks('afterSelectRows', ...args);
450 }).addLocalHook('beforeModifyTransformStart', function () {
451 for (var _len8 = arguments.length, args = new Array(_len8), _key8 = 0; _key8 < _len8; _key8++) {
452 args[_key8] = arguments[_key8];
453 }
454 return _this.runHooks('modifyTransformStart', ...args);
455 }).addLocalHook('afterModifyTransformStart', function () {
456 for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
457 args[_key9] = arguments[_key9];
458 }
459 return _this.runHooks('afterModifyTransformStart', ...args);
460 }).addLocalHook('beforeModifyTransformFocus', function () {
461 for (var _len10 = arguments.length, args = new Array(_len10), _key10 = 0; _key10 < _len10; _key10++) {
462 args[_key10] = arguments[_key10];
463 }
464 return _this.runHooks('modifyTransformFocus', ...args);
465 }).addLocalHook('afterModifyTransformFocus', function () {
466 for (var _len11 = arguments.length, args = new Array(_len11), _key11 = 0; _key11 < _len11; _key11++) {
467 args[_key11] = arguments[_key11];
468 }
469 return _this.runHooks('afterModifyTransformFocus', ...args);
470 }).addLocalHook('beforeModifyTransformEnd', function () {
471 for (var _len12 = arguments.length, args = new Array(_len12), _key12 = 0; _key12 < _len12; _key12++) {
472 args[_key12] = arguments[_key12];
473 }
474 return _this.runHooks('modifyTransformEnd', ...args);
475 }).addLocalHook('afterModifyTransformEnd', function () {
476 for (var _len13 = arguments.length, args = new Array(_len13), _key13 = 0; _key13 < _len13; _key13++) {
477 args[_key13] = arguments[_key13];
478 }
479 return _this.runHooks('afterModifyTransformEnd', ...args);
480 }).addLocalHook('beforeRowWrap', function () {
481 for (var _len14 = arguments.length, args = new Array(_len14), _key14 = 0; _key14 < _len14; _key14++) {
482 args[_key14] = arguments[_key14];
483 }
484 return _this.runHooks('beforeRowWrap', ...args);
485 }).addLocalHook('beforeColumnWrap', function () {
486 for (var _len15 = arguments.length, args = new Array(_len15), _key15 = 0; _key15 < _len15; _key15++) {
487 args[_key15] = arguments[_key15];
488 }
489 return _this.runHooks('beforeColumnWrap', ...args);
490 }).addLocalHook('insertRowRequire', totalRows => this.alter('insert_row_above', totalRows, 1, 'auto')).addLocalHook('insertColRequire', totalCols => this.alter('insert_col_start', totalCols, 1, 'auto'));
491 grid = {
492 /**
493 * Inserts or removes rows and columns.
494 *
495 * @private
496 * @param {string} action Possible values: "insert_row_above", "insert_row_below", "insert_col_start", "insert_col_end",
497 * "remove_row", "remove_col".
498 * @param {number|Array} index Row or column visual index which from the alter action will be triggered.
499 * Alter actions such as "remove_row" and "remove_col" support array indexes in the
500 * format `[[index, amount], [index, amount]...]` this can be used to remove
501 * non-consecutive columns or rows in one call.
502 * @param {number} [amount=1] Amount of rows or columns to remove.
503 * @param {string} [source] Optional. Source of hook runner.
504 * @param {boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
505 */
506 alter(action, index) {
507 var _index, _index2;
508 let amount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
509 let source = arguments.length > 3 ? arguments[3] : undefined;
510 let keepEmptyRows = arguments.length > 4 ? arguments[4] : undefined;
511 const normalizeIndexesGroup = indexes => {
512 if (indexes.length === 0) {
513 return [];
514 }
515 const sortedIndexes = [...indexes];
516
517 // Sort the indexes in ascending order.
518 sortedIndexes.sort((_ref2, _ref3) => {
519 let [indexA] = _ref2;
520 let [indexB] = _ref3;
521 if (indexA === indexB) {
522 return 0;
523 }
524 return indexA > indexB ? 1 : -1;
525 });
526
527 // Normalize the {index, amount} groups into bigger groups.
528 const normalizedIndexes = (0, _array.arrayReduce)(sortedIndexes, (acc, _ref4) => {
529 let [groupIndex, groupAmount] = _ref4;
530 const previousItem = acc[acc.length - 1];
531 const [prevIndex, prevAmount] = previousItem;
532 const prevLastIndex = prevIndex + prevAmount;
533 if (groupIndex <= prevLastIndex) {
534 const amountToAdd = Math.max(groupAmount - (prevLastIndex - groupIndex), 0);
535 previousItem[1] += amountToAdd;
536 } else {
537 acc.push([groupIndex, groupAmount]);
538 }
539 return acc;
540 }, [sortedIndexes[0]]);
541 return normalizedIndexes;
542 };
543
544 /* eslint-disable no-case-declarations */
545 switch (action) {
546 case 'insert_row_below':
547 case 'insert_row_above':
548 const numberOfSourceRows = instance.countSourceRows();
549 if (tableMeta.maxRows === numberOfSourceRows) {
550 return;
551 }
552
553 // `above` is the default behavior for creating new rows
554 const insertRowMode = action === 'insert_row_below' ? 'below' : 'above';
555
556 // Calling the `insert_row_above` action adds a new row at the beginning of the data set.
557 // eslint-disable-next-line no-param-reassign
558 index = (_index = index) !== null && _index !== void 0 ? _index : insertRowMode === 'below' ? numberOfSourceRows : 0;
559 const {
560 delta: rowDelta,
561 startPhysicalIndex: startRowPhysicalIndex
562 } = datamap.createRow(index, amount, {
563 source,
564 mode: insertRowMode
565 });
566 selection.shiftRows(instance.toVisualRow(startRowPhysicalIndex), rowDelta);
567 break;
568 case 'insert_col_start':
569 case 'insert_col_end':
570 // "start" is a default behavior for creating new columns
571 const insertColumnMode = action === 'insert_col_end' ? 'end' : 'start';
572
573 // Calling the `insert_col_start` action adds a new column to the left of the data set.
574 // eslint-disable-next-line no-param-reassign
575 index = (_index2 = index) !== null && _index2 !== void 0 ? _index2 : insertColumnMode === 'end' ? instance.countSourceCols() : 0;
576 const {
577 delta: colDelta,
578 startPhysicalIndex: startColumnPhysicalIndex
579 } = datamap.createCol(index, amount, {
580 source,
581 mode: insertColumnMode
582 });
583 if (colDelta) {
584 if (Array.isArray(tableMeta.colHeaders)) {
585 const spliceArray = [instance.toVisualColumn(startColumnPhysicalIndex), 0];
586 spliceArray.length += colDelta; // inserts empty (undefined) elements at the end of an array
587 Array.prototype.splice.apply(tableMeta.colHeaders, spliceArray); // inserts empty (undefined) elements into the colHeader array
588 }
589 selection.shiftColumns(instance.toVisualColumn(startColumnPhysicalIndex), colDelta);
590 }
591 break;
592 case 'remove_row':
593 const removeRow = indexes => {
594 let offset = 0;
595
596 // Normalize the {index, amount} groups into bigger groups.
597 (0, _array.arrayEach)(indexes, _ref5 => {
598 let [groupIndex, groupAmount] = _ref5;
599 const calcIndex = (0, _mixed.isEmpty)(groupIndex) ? instance.countRows() - 1 : Math.max(groupIndex - offset, 0);
600
601 // If the 'index' is an integer decrease it by 'offset' otherwise pass it through to make the value
602 // compatible with datamap.removeCol method.
603 if (Number.isInteger(groupIndex)) {
604 // eslint-disable-next-line no-param-reassign
605 groupIndex = Math.max(groupIndex - offset, 0);
606 }
607
608 // TODO: for datamap.removeRow index should be passed as it is (with undefined and null values). If not, the logic
609 // inside the datamap.removeRow breaks the removing functionality.
610 const wasRemoved = datamap.removeRow(groupIndex, groupAmount, source);
611 if (!wasRemoved) {
612 return;
613 }
614 if (selection.isSelected()) {
615 const {
616 row
617 } = instance.getSelectedRangeLast().highlight;
618 if (row >= groupIndex && row <= groupIndex + groupAmount - 1) {
619 editorManager.closeEditor(true);
620 }
621 }
622 const totalRows = instance.countRows();
623 if (totalRows === 0) {
624 selection.deselect();
625 } else if (source === 'ContextMenu.removeRow') {
626 selection.refresh();
627 } else {
628 selection.shiftRows(groupIndex, -groupAmount);
629 }
630 const fixedRowsTop = tableMeta.fixedRowsTop;
631 if (fixedRowsTop >= calcIndex + 1) {
632 tableMeta.fixedRowsTop -= Math.min(groupAmount, fixedRowsTop - calcIndex);
633 }
634 const fixedRowsBottom = tableMeta.fixedRowsBottom;
635 if (fixedRowsBottom && calcIndex >= totalRows - fixedRowsBottom) {
636 tableMeta.fixedRowsBottom -= Math.min(groupAmount, fixedRowsBottom);
637 }
638 offset += groupAmount;
639 });
640 };
641 if (Array.isArray(index)) {
642 removeRow(normalizeIndexesGroup(index));
643 } else {
644 removeRow([[index, amount]]);
645 }
646 break;
647 case 'remove_col':
648 const removeCol = indexes => {
649 let offset = 0;
650
651 // Normalize the {index, amount} groups into bigger groups.
652 (0, _array.arrayEach)(indexes, _ref6 => {
653 let [groupIndex, groupAmount] = _ref6;
654 const calcIndex = (0, _mixed.isEmpty)(groupIndex) ? instance.countCols() - 1 : Math.max(groupIndex - offset, 0);
655 let physicalColumnIndex = instance.toPhysicalColumn(calcIndex);
656
657 // If the 'index' is an integer decrease it by 'offset' otherwise pass it through to make the value
658 // compatible with datamap.removeCol method.
659 if (Number.isInteger(groupIndex)) {
660 // eslint-disable-next-line no-param-reassign
661 groupIndex = Math.max(groupIndex - offset, 0);
662 }
663
664 // TODO: for datamap.removeCol index should be passed as it is (with undefined and null values). If not, the logic
665 // inside the datamap.removeCol breaks the removing functionality.
666 const wasRemoved = datamap.removeCol(groupIndex, groupAmount, source);
667 if (!wasRemoved) {
668 return;
669 }
670 if (selection.isSelected()) {
671 const {
672 col
673 } = instance.getSelectedRangeLast().highlight;
674 if (col >= groupIndex && col <= groupIndex + groupAmount - 1) {
675 editorManager.closeEditor(true);
676 }
677 }
678 const totalColumns = instance.countCols();
679 if (totalColumns === 0) {
680 selection.deselect();
681 } else if (source === 'ContextMenu.removeColumn') {
682 selection.refresh();
683 } else {
684 selection.shiftColumns(groupIndex, -groupAmount);
685 }
686 const fixedColumnsStart = tableMeta.fixedColumnsStart;
687 if (fixedColumnsStart >= calcIndex + 1) {
688 tableMeta.fixedColumnsStart -= Math.min(groupAmount, fixedColumnsStart - calcIndex);
689 }
690 if (Array.isArray(tableMeta.colHeaders)) {
691 if (typeof physicalColumnIndex === 'undefined') {
692 physicalColumnIndex = -1;
693 }
694 tableMeta.colHeaders.splice(physicalColumnIndex, groupAmount);
695 }
696 offset += groupAmount;
697 });
698 };
699 if (Array.isArray(index)) {
700 removeCol(normalizeIndexesGroup(index));
701 } else {
702 removeCol([[index, amount]]);
703 }
704 break;
705 default:
706 throw new Error(`There is no such action "${action}"`);
707 }
708 instance.view.render();
709 if (!keepEmptyRows) {
710 grid.adjustRowsAndCols(); // makes sure that we did not add rows that will be removed in next refresh
711 }
712 },
713 /**
714 * Makes sure there are empty rows at the bottom of the table.
715 *
716 * @private
717 */
718 adjustRowsAndCols() {
719 const minRows = tableMeta.minRows;
720 const minSpareRows = tableMeta.minSpareRows;
721 const minCols = tableMeta.minCols;
722 const minSpareCols = tableMeta.minSpareCols;
723 if (minRows) {
724 // should I add empty rows to data source to meet minRows?
725 const nrOfRows = instance.countRows();
726 if (nrOfRows < minRows) {
727 // The synchronization with cell meta is not desired here. For `minRows` option,
728 // we don't want to touch/shift cell meta objects.
729 datamap.createRow(nrOfRows, minRows - nrOfRows, {
730 source: 'auto'
731 });
732 }
733 }
734 if (minSpareRows) {
735 const emptyRows = instance.countEmptyRows(true);
736
737 // should I add empty rows to meet minSpareRows?
738 if (emptyRows < minSpareRows) {
739 const emptyRowsMissing = minSpareRows - emptyRows;
740 const rowsToCreate = Math.min(emptyRowsMissing, tableMeta.maxRows - instance.countSourceRows());
741
742 // The synchronization with cell meta is not desired here. For `minSpareRows` option,
743 // we don't want to touch/shift cell meta objects.
744 datamap.createRow(instance.countRows(), rowsToCreate, {
745 source: 'auto'
746 });
747 }
748 }
749 {
750 let emptyCols;
751
752 // count currently empty cols
753 if (minCols || minSpareCols) {
754 emptyCols = instance.countEmptyCols(true);
755 }
756 let nrOfColumns = instance.countCols();
757
758 // should I add empty cols to meet minCols?
759 if (minCols && !tableMeta.columns && nrOfColumns < minCols) {
760 // The synchronization with cell meta is not desired here. For `minSpareRows` option,
761 // we don't want to touch/shift cell meta objects.
762 const colsToCreate = minCols - nrOfColumns;
763 emptyCols += colsToCreate;
764 datamap.createCol(nrOfColumns, colsToCreate, {
765 source: 'auto'
766 });
767 }
768 // should I add empty cols to meet minSpareCols?
769 if (minSpareCols && !tableMeta.columns && instance.dataType === 'array' && emptyCols < minSpareCols) {
770 nrOfColumns = instance.countCols();
771 const emptyColsMissing = minSpareCols - emptyCols;
772 const colsToCreate = Math.min(emptyColsMissing, tableMeta.maxCols - nrOfColumns);
773
774 // The synchronization with cell meta is not desired here. For `minSpareRows` option,
775 // we don't want to touch/shift cell meta objects.
776 datamap.createCol(nrOfColumns, colsToCreate, {
777 source: 'auto'
778 });
779 }
780 }
781 if (instance.view) {
782 instance.view.adjustElementsSize();
783 }
784 },
785 /**
786 * Populate the data from the provided 2d array from the given cell coordinates.
787 *
788 * @private
789 * @param {object} start Start selection position. Visual indexes.
790 * @param {Array} input 2d data array.
791 * @param {object} [end] End selection position (only for drag-down mode). Visual indexes.
792 * @param {string} [source="populateFromArray"] Source information string.
793 * @param {string} [method="overwrite"] Populate method. Possible options: `shift_down`, `shift_right`, `overwrite`.
794 * @returns {object|undefined} Ending td in pasted area (only if any cell was changed).
795 */
796 populateFromArray(start, input, end, source, method) {
797 let r;
798 let rlen;
799 let c;
800 let clen;
801 const setData = [];
802 const current = {};
803 const newDataByColumns = [];
804 const startRow = start.row;
805 const startColumn = start.col;
806 rlen = input.length;
807 if (rlen === 0) {
808 return false;
809 }
810 let columnsPopulationEnd = 0;
811 let rowsPopulationEnd = 0;
812 if ((0, _object.isObject)(end)) {
813 columnsPopulationEnd = end.col - startColumn + 1;
814 rowsPopulationEnd = end.row - startRow + 1;
815 }
816
817 // insert data with specified pasteMode method
818 switch (method) {
819 case 'shift_down':
820 // translate data from a list of rows to a list of columns
821 const populatedDataByColumns = (0, _array.pivot)(input);
822 const numberOfDataColumns = populatedDataByColumns.length;
823 // method's argument can extend the range of data population (data would be repeated)
824 const numberOfColumnsToPopulate = Math.max(numberOfDataColumns, columnsPopulationEnd);
825 const pushedDownDataByRows = instance.getData().slice(startRow);
826
827 // translate data from a list of rows to a list of columns
828 const pushedDownDataByColumns = (0, _array.pivot)(pushedDownDataByRows).slice(startColumn, startColumn + numberOfColumnsToPopulate);
829 for (c = 0; c < numberOfColumnsToPopulate; c += 1) {
830 if (c < numberOfDataColumns) {
831 for (r = 0, rlen = populatedDataByColumns[c].length; r < rowsPopulationEnd - rlen; r += 1) {
832 // repeating data for rows
833 populatedDataByColumns[c].push(populatedDataByColumns[c][r % rlen]);
834 }
835 if (c < pushedDownDataByColumns.length) {
836 newDataByColumns.push(populatedDataByColumns[c].concat(pushedDownDataByColumns[c]));
837 } else {
838 // if before data population, there was no data in the column
839 // we fill the required rows' newly-created cells with `null` values
840 newDataByColumns.push(populatedDataByColumns[c].concat(new Array(pushedDownDataByRows.length).fill(null)));
841 }
842 } else {
843 // Repeating data for columns.
844 newDataByColumns.push(populatedDataByColumns[c % numberOfDataColumns].concat(pushedDownDataByColumns[c]));
845 }
846 }
847 instance.populateFromArray(startRow, startColumn, (0, _array.pivot)(newDataByColumns));
848 break;
849 case 'shift_right':
850 const numberOfDataRows = input.length;
851 // method's argument can extend the range of data population (data would be repeated)
852 const numberOfRowsToPopulate = Math.max(numberOfDataRows, rowsPopulationEnd);
853 const pushedRightDataByRows = instance.getData().slice(startRow).map(rowData => rowData.slice(startColumn));
854 for (r = 0; r < numberOfRowsToPopulate; r += 1) {
855 if (r < numberOfDataRows) {
856 for (c = 0, clen = input[r].length; c < columnsPopulationEnd - clen; c += 1) {
857 // repeating data for rows
858 input[r].push(input[r][c % clen]);
859 }
860 if (r < pushedRightDataByRows.length) {
861 for (let i = 0; i < pushedRightDataByRows[r].length; i += 1) {
862 input[r].push(pushedRightDataByRows[r][i]);
863 }
864 } else {
865 // if before data population, there was no data in the row
866 // we fill the required columns' newly-created cells with `null` values
867 input[r].push(...new Array(pushedRightDataByRows[0].length).fill(null));
868 }
869 } else {
870 // Repeating data for columns.
871 input.push(input[r % rlen].slice(0, numberOfRowsToPopulate).concat(pushedRightDataByRows[r]));
872 }
873 }
874 instance.populateFromArray(startRow, startColumn, input);
875 break;
876 case 'overwrite':
877 default:
878 // overwrite and other not specified options
879 current.row = start.row;
880 current.col = start.col;
881 let skippedRow = 0;
882 let skippedColumn = 0;
883 let pushData = true;
884 let cellMeta;
885 const getInputValue = function getInputValue(row) {
886 let col = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
887 const rowValue = input[row % input.length];
888 if (col !== null) {
889 return rowValue[col % rowValue.length];
890 }
891 return rowValue;
892 };
893 const rowInputLength = input.length;
894 const rowSelectionLength = end ? end.row - start.row + 1 : 0;
895 if (end) {
896 rlen = rowSelectionLength;
897 } else {
898 rlen = Math.max(rowInputLength, rowSelectionLength);
899 }
900 for (r = 0; r < rlen; r++) {
901 if (end && current.row > end.row && rowSelectionLength > rowInputLength || !tableMeta.allowInsertRow && current.row > instance.countRows() - 1 || current.row >= tableMeta.maxRows) {
902 break;
903 }
904 const visualRow = r - skippedRow;
905 const colInputLength = getInputValue(visualRow).length;
906 const colSelectionLength = end ? end.col - start.col + 1 : 0;
907 if (end) {
908 clen = colSelectionLength;
909 } else {
910 clen = Math.max(colInputLength, colSelectionLength);
911 }
912 current.col = start.col;
913 cellMeta = instance.getCellMeta(current.row, current.col);
914 if ((source === 'CopyPaste.paste' || source === 'Autofill.fill') && cellMeta.skipRowOnPaste) {
915 skippedRow += 1;
916 current.row += 1;
917 rlen += 1;
918 /* eslint-disable no-continue */
919 continue;
920 }
921 skippedColumn = 0;
922 for (c = 0; c < clen; c++) {
923 if (end && current.col > end.col && colSelectionLength > colInputLength || !tableMeta.allowInsertColumn && current.col > instance.countCols() - 1 || current.col >= tableMeta.maxCols) {
924 break;
925 }
926 cellMeta = instance.getCellMeta(current.row, current.col);
927 if ((source === 'CopyPaste.paste' || source === 'Autofill.fill') && cellMeta.skipColumnOnPaste) {
928 skippedColumn += 1;
929 current.col += 1;
930 clen += 1;
931 continue;
932 }
933 if (cellMeta.readOnly && source !== 'UndoRedo.undo') {
934 current.col += 1;
935 /* eslint-disable no-continue */
936 continue;
937 }
938 const visualColumn = c - skippedColumn;
939 let value = getInputValue(visualRow, visualColumn);
940 let orgValue = instance.getDataAtCell(current.row, current.col);
941 if (value !== null && typeof value === 'object') {
942 // when 'value' is array and 'orgValue' is null, set 'orgValue' to
943 // an empty array so that the null value can be compared to 'value'
944 // as an empty value for the array context
945 if (Array.isArray(value) && orgValue === null) orgValue = [];
946 if (orgValue === null || typeof orgValue !== 'object') {
947 pushData = false;
948 } else {
949 const orgValueSchema = (0, _object.duckSchema)(Array.isArray(orgValue) ? orgValue : orgValue[0] || orgValue);
950 const valueSchema = (0, _object.duckSchema)(Array.isArray(value) ? value : value[0] || value);
951
952 // Allow overwriting values with the same object-based schema or any array-based schema.
953 if ((0, _object.isObjectEqual)(orgValueSchema, valueSchema) || Array.isArray(orgValueSchema) && Array.isArray(valueSchema)) {
954 value = (0, _object.deepClone)(value);
955 } else {
956 pushData = false;
957 }
958 }
959 } else if (orgValue !== null && typeof orgValue === 'object') {
960 pushData = false;
961 }
962 if (pushData) {
963 setData.push([current.row, current.col, value]);
964 }
965 pushData = true;
966 current.col += 1;
967 }
968 current.row += 1;
969 }
970 instance.setDataAtCell(setData, null, null, source || 'populateFromArray');
971 break;
972 }
973 }
974 };
975
976 /**
977 * Internal function to set `language` key of settings.
978 *
979 * @private
980 * @param {string} languageCode Language code for specific language i.e. 'en-US', 'pt-BR', 'de-DE'.
981 * @fires Hooks#afterLanguageChange
982 */
983 function setLanguage(languageCode) {
984 const normalizedLanguageCode = (0, _utils.normalizeLanguageCode)(languageCode);
985 if ((0, _registry5.hasLanguageDictionary)(normalizedLanguageCode)) {
986 instance.runHooks('beforeLanguageChange', normalizedLanguageCode);
987 globalMeta.language = normalizedLanguageCode;
988 instance.runHooks('afterLanguageChange', normalizedLanguageCode);
989 } else {
990 (0, _utils.warnUserAboutLanguageRegistration)(languageCode);
991 }
992 }
993
994 /**
995 * Internal function to set `className` or `tableClassName`, depending on the key from the settings object.
996 *
997 * @private
998 * @param {string} className `className` or `tableClassName` from the key in the settings object.
999 * @param {string|string[]} classSettings String or array of strings. Contains class name(s) from settings object.
1000 */
1001 function setClassName(className, classSettings) {
1002 const element = className === 'className' ? instance.rootElement : instance.table;
1003 if (firstRun) {
1004 (0, _element.addClass)(element, classSettings);
1005 } else {
1006 let globalMetaSettingsArray = [];
1007 let settingsArray = [];
1008 if (globalMeta[className]) {
1009 globalMetaSettingsArray = Array.isArray(globalMeta[className]) ? globalMeta[className] : (0, _array.stringToArray)(globalMeta[className]);
1010 }
1011 if (classSettings) {
1012 settingsArray = Array.isArray(classSettings) ? classSettings : (0, _array.stringToArray)(classSettings);
1013 }
1014 const classNameToRemove = (0, _array.getDifferenceOfArrays)(globalMetaSettingsArray, settingsArray);
1015 const classNameToAdd = (0, _array.getDifferenceOfArrays)(settingsArray, globalMetaSettingsArray);
1016 if (classNameToRemove.length) {
1017 (0, _element.removeClass)(element, classNameToRemove);
1018 }
1019 if (classNameToAdd.length) {
1020 (0, _element.addClass)(element, classNameToAdd);
1021 }
1022 }
1023 globalMeta[className] = classSettings;
1024 }
1025 this.init = function () {
1026 dataSource.setData(tableMeta.data);
1027 instance.runHooks('beforeInit');
1028 if ((0, _browser.isMobileBrowser)() || (0, _browser.isIpadOS)()) {
1029 (0, _element.addClass)(instance.rootElement, 'mobile');
1030 }
1031 this.updateSettings(tableMeta, true);
1032 this.view = new _tableView.default(this);
1033 editorManager = _editorManager.default.getInstance(instance, tableMeta, selection);
1034 viewportScroller = (0, _index3.createViewportScroller)(instance);
1035 focusManager = new _focusManager.FocusManager(instance);
1036 if ((0, _rootInstance.isRootInstance)(this)) {
1037 (0, _index3.installFocusCatcher)(instance);
1038 }
1039 instance.runHooks('init');
1040 this.forceFullRender = true; // used when data was changed
1041 this.view.render();
1042
1043 // Run the logic only if it's the table's initialization and the root element is not visible.
1044 if (!!firstRun && instance.rootElement.offsetParent === null) {
1045 (0, _element.observeVisibilityChangeOnce)(instance.rootElement, () => {
1046 // Update the spreader size cache before rendering.
1047 instance.view._wt.wtOverlays.updateLastSpreaderSize();
1048 instance.render();
1049 instance.view.adjustElementsSize();
1050 });
1051 }
1052 if (typeof firstRun === 'object') {
1053 instance.runHooks('afterChange', firstRun[0], firstRun[1]);
1054 firstRun = false;
1055 }
1056 instance.runHooks('afterInit');
1057 };
1058
1059 /**
1060 * @ignore
1061 * @returns {object}
1062 */
1063 function ValidatorsQueue() {
1064 // moved this one level up so it can be used in any function here. Probably this should be moved to a separate file
1065 let resolved = false;
1066 return {
1067 validatorsInQueue: 0,
1068 valid: true,
1069 addValidatorToQueue() {
1070 this.validatorsInQueue += 1;
1071 resolved = false;
1072 },
1073 removeValidatorFormQueue() {
1074 this.validatorsInQueue = this.validatorsInQueue - 1 < 0 ? 0 : this.validatorsInQueue - 1;
1075 this.checkIfQueueIsEmpty();
1076 },
1077 onQueueEmpty() {},
1078 checkIfQueueIsEmpty() {
1079 if (this.validatorsInQueue === 0 && resolved === false) {
1080 resolved = true;
1081 this.onQueueEmpty(this.valid);
1082 }
1083 }
1084 };
1085 }
1086
1087 /**
1088 * Get parsed number from numeric string.
1089 *
1090 * @private
1091 * @param {string} numericData Float (separated by a dot or a comma) or integer.
1092 * @returns {number} Number if we get data in parsable format, not changed value otherwise.
1093 */
1094 function getParsedNumber(numericData) {
1095 // Unifying "float like" string. Change from value with comma determiner to value with dot determiner,
1096 // for example from `450,65` to `450.65`.
1097 const unifiedNumericData = numericData.replace(',', '.');
1098 if (isNaN(parseFloat(unifiedNumericData)) === false) {
1099 return parseFloat(unifiedNumericData);
1100 }
1101 return numericData;
1102 }
1103
1104 /**
1105 * @ignore
1106 * @param {Array} changes The 2D array containing information about each of the edited cells.
1107 * @param {string} source The string that identifies source of validation.
1108 * @param {Function} callback The callback function fot async validation.
1109 */
1110 function validateChanges(changes, source, callback) {
1111 if (!changes.length) {
1112 callback();
1113 return;
1114 }
1115 const activeEditor = instance.getActiveEditor();
1116 const waitingForValidator = new ValidatorsQueue();
1117 let shouldBeCanceled = true;
1118 waitingForValidator.onQueueEmpty = () => {
1119 if (activeEditor && shouldBeCanceled) {
1120 activeEditor.cancelChanges();
1121 }
1122 callback(); // called when async validators are resolved and beforeChange was not async
1123 };
1124 for (let i = changes.length - 1; i >= 0; i--) {
1125 const [row, prop,, newValue] = changes[i];
1126 const visualCol = datamap.propToCol(prop);
1127 let cellProperties;
1128 if (Number.isInteger(visualCol)) {
1129 cellProperties = instance.getCellMeta(row, visualCol);
1130 } else {
1131 // If there's no requested visual column, we can use the table meta as the cell properties when retrieving
1132 // the cell validator.
1133 cellProperties = {
1134 ...Object.getPrototypeOf(tableMeta),
1135 ...tableMeta
1136 };
1137 }
1138 if (cellProperties.type === 'numeric' && typeof newValue === 'string' && (0, _number.isNumericLike)(newValue)) {
1139 changes[i][3] = getParsedNumber(newValue);
1140 }
1141
1142 /* eslint-disable no-loop-func */
1143 if (instance.getCellValidator(cellProperties)) {
1144 waitingForValidator.addValidatorToQueue();
1145 instance.validateCell(changes[i][3], cellProperties, function (index, cellPropertiesReference) {
1146 return function (result) {
1147 if (typeof result !== 'boolean') {
1148 throw new Error('Validation error: result is not boolean');
1149 }
1150 if (result === false && cellPropertiesReference.allowInvalid === false) {
1151 shouldBeCanceled = false;
1152 changes.splice(index, 1); // cancel the change
1153 cellPropertiesReference.valid = true; // we cancelled the change, so cell value is still valid
1154 }
1155 waitingForValidator.removeValidatorFormQueue();
1156 };
1157 }(i, cellProperties), source);
1158 }
1159 }
1160 waitingForValidator.checkIfQueueIsEmpty();
1161 }
1162
1163 /**
1164 * Internal function to apply changes. Called after validateChanges.
1165 *
1166 * @private
1167 * @param {Array} changes Array in form of [row, prop, oldValue, newValue].
1168 * @param {string} source String that identifies how this change will be described in changes array (useful in onChange callback).
1169 * @fires Hooks#beforeChangeRender
1170 * @fires Hooks#afterChange
1171 */
1172 function applyChanges(changes, source) {
1173 for (let i = changes.length - 1; i >= 0; i--) {
1174 let skipThisChange = false;
1175 if (changes[i] === null) {
1176 changes.splice(i, 1);
1177 /* eslint-disable no-continue */
1178 continue;
1179 }
1180 if ((changes[i][2] === null || changes[i][2] === undefined) && (changes[i][3] === null || changes[i][3] === undefined)) {
1181 /* eslint-disable no-continue */
1182 continue;
1183 }
1184 if (tableMeta.allowInsertRow) {
1185 while (changes[i][0] > instance.countRows() - 1) {
1186 const {
1187 delta: numberOfCreatedRows
1188 } = datamap.createRow(undefined, undefined, {
1189 source
1190 });
1191 if (numberOfCreatedRows === 0) {
1192 skipThisChange = true;
1193 break;
1194 }
1195 }
1196 }
1197 if (instance.dataType === 'array' && (!tableMeta.columns || tableMeta.columns.length === 0) && tableMeta.allowInsertColumn) {
1198 while (datamap.propToCol(changes[i][1]) > instance.countCols() - 1) {
1199 const {
1200 delta: numberOfCreatedColumns
1201 } = datamap.createCol(undefined, undefined, {
1202 source
1203 });
1204 if (numberOfCreatedColumns === 0) {
1205 skipThisChange = true;
1206 break;
1207 }
1208 }
1209 }
1210 if (skipThisChange) {
1211 /* eslint-disable no-continue */
1212 continue;
1213 }
1214 datamap.set(changes[i][0], changes[i][1], changes[i][3]);
1215 }
1216 const hasChanges = changes.length > 0;
1217 instance.forceFullRender = true; // used when data was changed or when all cells need to be re-rendered
1218
1219 if (hasChanges) {
1220 grid.adjustRowsAndCols();
1221 instance.runHooks('beforeChangeRender', changes, source);
1222 editorManager.closeEditor();
1223 instance.view.render();
1224 editorManager.prepareEditor();
1225 instance.view.adjustElementsSize();
1226 instance.runHooks('afterChange', changes, source || 'edit');
1227 const activeEditor = instance.getActiveEditor();
1228 if (activeEditor && (0, _mixed.isDefined)(activeEditor.refreshValue)) {
1229 activeEditor.refreshValue();
1230 }
1231 } else {
1232 instance.view.render();
1233 }
1234 }
1235
1236 /**
1237 * Creates and returns the CellCoords object.
1238 *
1239 * @private
1240 * @memberof Core#
1241 * @function _createCellCoords
1242 * @param {number} row The row index.
1243 * @param {number} column The column index.
1244 * @returns {CellCoords}
1245 */
1246 this._createCellCoords = function (row, column) {
1247 return instance.view._wt.createCellCoords(row, column);
1248 };
1249
1250 /**
1251 * Creates and returns the CellRange object.
1252 *
1253 * @private
1254 * @memberof Core#
1255 * @function _createCellRange
1256 * @param {CellCoords} highlight Defines the border around a cell where selection was started and to edit the cell
1257 * when you press Enter. The highlight cannot point to headers (negative values).
1258 * @param {CellCoords} from Initial coordinates.
1259 * @param {CellCoords} to Final coordinates.
1260 * @returns {CellRange}
1261 */
1262 this._createCellRange = function (highlight, from, to) {
1263 return instance.view._wt.createCellRange(highlight, from, to);
1264 };
1265
1266 /**
1267 * Validate a single cell.
1268 *
1269 * @memberof Core#
1270 * @function validateCell
1271 * @param {string|number} value The value to validate.
1272 * @param {object} cellProperties The cell meta which corresponds with the value.
1273 * @param {Function} callback The callback function.
1274 * @param {string} source The string that identifies source of the validation.
1275 */
1276 this.validateCell = function (value, cellProperties, callback, source) {
1277 let validator = instance.getCellValidator(cellProperties);
1278
1279 // the `canBeValidated = false` argument suggests, that the cell passes validation by default.
1280 /**
1281 * @private
1282 * @function done
1283 * @param {boolean} valid Indicates if the validation was successful.
1284 * @param {boolean} [canBeValidated=true] Flag which controls the validation process.
1285 */
1286 function done(valid) {
1287 let canBeValidated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1288 // Fixes GH#3903
1289 if (!canBeValidated || cellProperties.hidden === true) {
1290 callback(valid);
1291 return;
1292 }
1293 const col = cellProperties.visualCol;
1294 const row = cellProperties.visualRow;
1295 const td = instance.getCell(row, col, true);
1296 if (td && td.nodeName !== 'TH') {
1297 const renderableRow = instance.rowIndexMapper.getRenderableFromVisualIndex(row);
1298 const renderableColumn = instance.columnIndexMapper.getRenderableFromVisualIndex(col);
1299 instance.view._wt.getSetting('cellRenderer', renderableRow, renderableColumn, td);
1300 }
1301 callback(valid);
1302 }
1303 if ((0, _mixed.isRegExp)(validator)) {
1304 validator = function (expression) {
1305 return function (cellValue, validatorCallback) {
1306 validatorCallback(expression.test(cellValue));
1307 };
1308 }(validator);
1309 }
1310 if ((0, _function.isFunction)(validator)) {
1311 // eslint-disable-next-line no-param-reassign
1312 value = instance.runHooks('beforeValidate', value, cellProperties.visualRow, cellProperties.prop, source);
1313
1314 // To provide consistent behaviour, validation should be always asynchronous
1315 instance._registerImmediate(() => {
1316 validator.call(cellProperties, value, valid => {
1317 if (!instance) {
1318 return;
1319 }
1320 // eslint-disable-next-line no-param-reassign
1321 valid = instance.runHooks('afterValidate', valid, value, cellProperties.visualRow, cellProperties.prop, source);
1322 cellProperties.valid = valid;
1323 done(valid);
1324 instance.runHooks('postAfterValidate', valid, value, cellProperties.visualRow, cellProperties.prop, source);
1325 });
1326 });
1327 } else {
1328 // resolve callback even if validator function was not found
1329 instance._registerImmediate(() => {
1330 cellProperties.valid = true;
1331 done(cellProperties.valid, false);
1332 });
1333 }
1334 };
1335
1336 /**
1337 * @ignore
1338 * @param {number} row The visual row index.
1339 * @param {string|number} propOrCol The visual prop or column index.
1340 * @param {*} value The cell value.
1341 * @returns {Array}
1342 */
1343 function setDataInputToArray(row, propOrCol, value) {
1344 if (Array.isArray(row)) {
1345 // it's an array of changes
1346 return row;
1347 }
1348 return [[row, propOrCol, value]];
1349 }
1350
1351 /**
1352 * Process changes prepared for applying to the dataset (unifying list of changes, closing an editor - when needed,
1353 * calling a hook).
1354 *
1355 * @private
1356 * @param {Array} changes Array of changes in format `[[row, col, value],...]`.
1357 * @param {string} [source] String that identifies how this change will be described in the changes array (useful in afterChange or beforeChange callback). Set to 'edit' if left empty.
1358 * @returns {Array} List of changes finally applied to the dataset.
1359 */
1360 function processChanges(changes, source) {
1361 const activeEditor = instance.getActiveEditor();
1362 const beforeChangeResult = instance.runHooks('beforeChange', changes, source || 'edit');
1363 // The `beforeChange` hook could add a `null` for purpose of cancelling some dataset's change.
1364 const filteredChanges = changes.filter(change => change !== null);
1365 if (beforeChangeResult === false || filteredChanges.length === 0) {
1366 if (activeEditor) {
1367 activeEditor.cancelChanges();
1368 }
1369 return [];
1370 }
1371 return filteredChanges;
1372 }
1373
1374 /**
1375 * @description
1376 * Set new value to a cell. To change many cells at once (recommended way), pass an array of `changes` in format
1377 * `[[row, col, value],...]` as the first argument.
1378 *
1379 * @memberof Core#
1380 * @function setDataAtCell
1381 * @param {number|Array} row Visual row index or array of changes in format `[[row, col, value],...]`.
1382 * @param {number} [column] Visual column index.
1383 * @param {string} [value] New value.
1384 * @param {string} [source] String that identifies how this change will be described in the changes array (useful in afterChange or beforeChange callback). Set to 'edit' if left empty.
1385 */
1386 this.setDataAtCell = function (row, column, value, source) {
1387 const input = setDataInputToArray(row, column, value);
1388 const changes = [];
1389 let changeSource = source;
1390 let i;
1391 let ilen;
1392 let prop;
1393 for (i = 0, ilen = input.length; i < ilen; i++) {
1394 if (typeof input[i] !== 'object') {
1395 throw new Error('Method `setDataAtCell` accepts row number or changes array of arrays as its first parameter');
1396 }
1397 if (typeof input[i][1] !== 'number') {
1398 throw new Error('Method `setDataAtCell` accepts row and column number as its parameters. If you want to use object property name, use method `setDataAtRowProp`'); // eslint-disable-line max-len
1399 }
1400 if (input[i][1] >= this.countCols()) {
1401 prop = input[i][1];
1402 } else {
1403 prop = datamap.colToProp(input[i][1]);
1404 }
1405 changes.push([input[i][0], prop, dataSource.getAtCell(this.toPhysicalRow(input[i][0]), input[i][1]), input[i][2]]);
1406 }
1407 if (!changeSource && typeof row === 'object') {
1408 changeSource = column;
1409 }
1410 const processedChanges = processChanges(changes, source);
1411 instance.runHooks('afterSetDataAtCell', processedChanges, changeSource);
1412 validateChanges(processedChanges, changeSource, () => {
1413 applyChanges(processedChanges, changeSource);
1414 });
1415 };
1416
1417 /**
1418 * @description
1419 * Set new value to a cell. To change many cells at once (recommended way), pass an array of `changes` in format
1420 * `[[row, prop, value],...]` as the first argument.
1421 *
1422 * @memberof Core#
1423 * @function setDataAtRowProp
1424 * @param {number|Array} row Visual row index or array of changes in format `[[row, prop, value], ...]`.
1425 * @param {string} prop Property name or the source string (e.g. `'first.name'` or `'0'`).
1426 * @param {string} value Value to be set.
1427 * @param {string} [source] String that identifies how this change will be described in changes array (useful in onChange callback).
1428 */
1429 this.setDataAtRowProp = function (row, prop, value, source) {
1430 const input = setDataInputToArray(row, prop, value);
1431 const changes = [];
1432 let changeSource = source;
1433 let i;
1434 let ilen;
1435 for (i = 0, ilen = input.length; i < ilen; i++) {
1436 changes.push([input[i][0], input[i][1], dataSource.getAtCell(this.toPhysicalRow(input[i][0]), input[i][1]), input[i][2]]);
1437 }
1438
1439 // TODO: I don't think `prop` should be used as `changeSource` here, but removing it would be a breaking change.
1440 // We should remove it with the next major release.
1441 if (!changeSource && typeof row === 'object') {
1442 changeSource = prop;
1443 }
1444 const processedChanges = processChanges(changes, source);
1445 instance.runHooks('afterSetDataAtRowProp', processedChanges, changeSource);
1446 validateChanges(processedChanges, changeSource, () => {
1447 applyChanges(processedChanges, changeSource);
1448 });
1449 };
1450
1451 /**
1452 * Listen to the keyboard input on document body. This allows Handsontable to capture keyboard events and respond
1453 * in the right way.
1454 *
1455 * @memberof Core#
1456 * @function listen
1457 * @fires Hooks#afterListen
1458 */
1459 this.listen = function () {
1460 if (instance && !instance.isListening()) {
1461 foreignHotInstances.forEach(foreignHot => {
1462 if (instance !== foreignHot) {
1463 foreignHot.unlisten();
1464 }
1465 });
1466 activeGuid = instance.guid;
1467 instance.runHooks('afterListen');
1468 }
1469 };
1470
1471 /**
1472 * Stop listening to keyboard input on the document body. Calling this method makes the Handsontable inactive for
1473 * any keyboard events.
1474 *
1475 * @memberof Core#
1476 * @function unlisten
1477 */
1478 this.unlisten = function () {
1479 if (this.isListening()) {
1480 activeGuid = null;
1481 instance.runHooks('afterUnlisten');
1482 }
1483 };
1484
1485 /**
1486 * Returns `true` if the current Handsontable instance is listening to keyboard input on document body.
1487 *
1488 * @memberof Core#
1489 * @function isListening
1490 * @returns {boolean} `true` if the instance is listening, `false` otherwise.
1491 */
1492 this.isListening = function () {
1493 return activeGuid === instance.guid;
1494 };
1495
1496 /**
1497 * Destroys the current editor, render the table and prepares the editor of the newly selected cell.
1498 *
1499 * @memberof Core#
1500 * @function destroyEditor
1501 * @param {boolean} [revertOriginal=false] If `true`, the previous value will be restored. Otherwise, the edited value will be saved.
1502 * @param {boolean} [prepareEditorIfNeeded=true] If `true` the editor under the selected cell will be prepared to open.
1503 */
1504 this.destroyEditor = function () {
1505 let revertOriginal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1506 let prepareEditorIfNeeded = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1507 editorManager.closeEditor(revertOriginal);
1508 instance.view.render();
1509 if (prepareEditorIfNeeded && selection.isSelected()) {
1510 editorManager.prepareEditor();
1511 }
1512 };
1513
1514 /**
1515 * Populates cells at position with 2D input array (e.g. `[[1, 2], [3, 4]]`). Use `endRow`, `endCol` when you
1516 * want to cut input when a certain row is reached.
1517 *
1518 * The `populateFromArray()` method can't change [`readOnly`](@/api/options.md#readonly) cells.
1519 *
1520 * Optional `method` argument has the same effect as pasteMode option (see {@link Options#pasteMode}).
1521 *
1522 * @memberof Core#
1523 * @function populateFromArray
1524 * @param {number} row Start visual row index.
1525 * @param {number} column Start visual column index.
1526 * @param {Array} input 2d array.
1527 * @param {number} [endRow] End visual row index (use when you want to cut input when certain row is reached).
1528 * @param {number} [endCol] End visual column index (use when you want to cut input when certain column is reached).
1529 * @param {string} [source=populateFromArray] Used to identify this call in the resulting events (beforeChange, afterChange).
1530 * @param {string} [method=overwrite] Populate method, possible values: `'shift_down'`, `'shift_right'`, `'overwrite'`.
1531 * @returns {object|undefined} Ending td in pasted area (only if any cell was changed).
1532 */
1533 this.populateFromArray = function (row, column, input, endRow, endCol, source, method) {
1534 if (!(typeof input === 'object' && typeof input[0] === 'object')) {
1535 throw new Error('populateFromArray parameter `input` must be an array of arrays'); // API changed in 0.9-beta2, let's check if you use it correctly
1536 }
1537 const c = typeof endRow === 'number' ? instance._createCellCoords(endRow, endCol) : null;
1538 return grid.populateFromArray(instance._createCellCoords(row, column), input, c, source, method);
1539 };
1540
1541 /**
1542 * Adds/removes data from the column. This method works the same as Array.splice for arrays.
1543 *
1544 * @memberof Core#
1545 * @function spliceCol
1546 * @param {number} column Index of the column in which do you want to do splice.
1547 * @param {number} index Index at which to start changing the array. If negative, will begin that many elements from the end.
1548 * @param {number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed.
1549 * @param {...number} [elements] The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array.
1550 * @returns {Array} Returns removed portion of columns.
1551 */
1552 this.spliceCol = function (column, index, amount) {
1553 for (var _len16 = arguments.length, elements = new Array(_len16 > 3 ? _len16 - 3 : 0), _key16 = 3; _key16 < _len16; _key16++) {
1554 elements[_key16 - 3] = arguments[_key16];
1555 }
1556 return datamap.spliceCol(column, index, amount, ...elements);
1557 };
1558
1559 /**
1560 * Adds/removes data from the row. This method works the same as Array.splice for arrays.
1561 *
1562 * @memberof Core#
1563 * @function spliceRow
1564 * @param {number} row Index of column in which do you want to do splice.
1565 * @param {number} index Index at which to start changing the array. If negative, will begin that many elements from the end.
1566 * @param {number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed.
1567 * @param {...number} [elements] The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array.
1568 * @returns {Array} Returns removed portion of rows.
1569 */
1570 this.spliceRow = function (row, index, amount) {
1571 for (var _len17 = arguments.length, elements = new Array(_len17 > 3 ? _len17 - 3 : 0), _key17 = 3; _key17 < _len17; _key17++) {
1572 elements[_key17 - 3] = arguments[_key17];
1573 }
1574 return datamap.spliceRow(row, index, amount, ...elements);
1575 };
1576
1577 /**
1578 * Returns indexes of the currently selected cells as an array of arrays `[[startRow, startCol, endRow, endCol],...]`.
1579 *
1580 * Start row and start column are the coordinates of the active cell (where the selection was started).
1581 *
1582 * The version 0.36.0 adds a non-consecutive selection feature. Since this version, the method returns an array of arrays.
1583 * Additionally to collect the coordinates of the currently selected area (as it was previously done by the method)
1584 * you need to use `getSelectedLast` method.
1585 *
1586 * @memberof Core#
1587 * @function getSelected
1588 * @returns {Array[]|undefined} An array of arrays of the selection's coordinates.
1589 */
1590 this.getSelected = function () {
1591 // https://github.com/handsontable/handsontable/issues/44 //cjl
1592 if (selection.isSelected()) {
1593 return (0, _array.arrayMap)(selection.getSelectedRange(), _ref7 => {
1594 let {
1595 from,
1596 to
1597 } = _ref7;
1598 return [from.row, from.col, to.row, to.col];
1599 });
1600 }
1601 };
1602
1603 /**
1604 * Returns the last coordinates applied to the table as a an array `[startRow, startCol, endRow, endCol]`.
1605 *
1606 * @since 0.36.0
1607 * @memberof Core#
1608 * @function getSelectedLast
1609 * @returns {Array|undefined} An array of the selection's coordinates.
1610 */
1611 this.getSelectedLast = function () {
1612 const selected = this.getSelected();
1613 let result;
1614 if (selected && selected.length > 0) {
1615 result = selected[selected.length - 1];
1616 }
1617 return result;
1618 };
1619
1620 /**
1621 * Returns the current selection as an array of CellRange objects.
1622 *
1623 * The version 0.36.0 adds a non-consecutive selection feature. Since this version, the method returns an array of arrays.
1624 * Additionally to collect the coordinates of the currently selected area (as it was previously done by the method)
1625 * you need to use `getSelectedRangeLast` method.
1626 *
1627 * @memberof Core#
1628 * @function getSelectedRange
1629 * @returns {CellRange[]|undefined} Selected range object or undefined if there is no selection.
1630 */
1631 this.getSelectedRange = function () {
1632 // https://github.com/handsontable/handsontable/issues/44 //cjl
1633 if (selection.isSelected()) {
1634 return Array.from(selection.getSelectedRange());
1635 }
1636 };
1637
1638 /**
1639 * Returns the last coordinates applied to the table as a CellRange object.
1640 *
1641 * @memberof Core#
1642 * @function getSelectedRangeLast
1643 * @since 0.36.0
1644 * @returns {CellRange|undefined} Selected range object or undefined` if there is no selection.
1645 */
1646 this.getSelectedRangeLast = function () {
1647 const selectedRange = this.getSelectedRange();
1648 let result;
1649 if (selectedRange && selectedRange.length > 0) {
1650 result = selectedRange[selectedRange.length - 1];
1651 }
1652 return result;
1653 };
1654
1655 /**
1656 * Erases content from cells that have been selected in the table.
1657 *
1658 * @memberof Core#
1659 * @function emptySelectedCells
1660 * @param {string} [source] String that identifies how this change will be described in the changes array (useful in afterChange or beforeChange callback). Set to 'edit' if left empty.
1661 * @since 0.36.0
1662 */
1663 this.emptySelectedCells = function (source) {
1664 if (!selection.isSelected() || this.countRows() === 0 || this.countCols() === 0) {
1665 return;
1666 }
1667 const changes = [];
1668 (0, _array.arrayEach)(selection.getSelectedRange(), cellRange => {
1669 if (cellRange.isSingleHeader()) {
1670 return;
1671 }
1672 const topStart = cellRange.getTopStartCorner();
1673 const bottomEnd = cellRange.getBottomEndCorner();
1674 (0, _number.rangeEach)(topStart.row, bottomEnd.row, row => {
1675 (0, _number.rangeEach)(topStart.col, bottomEnd.col, column => {
1676 if (!this.getCellMeta(row, column).readOnly) {
1677 changes.push([row, column, null]);
1678 }
1679 });
1680 });
1681 });
1682 if (changes.length > 0) {
1683 this.setDataAtCell(changes, source);
1684 }
1685 };
1686
1687 /**
1688 * Checks if the table rendering process was suspended. See explanation in {@link Core#suspendRender}.
1689 *
1690 * @memberof Core#
1691 * @function isRenderSuspended
1692 * @since 8.3.0
1693 * @returns {boolean}
1694 */
1695 this.isRenderSuspended = function () {
1696 return this.renderSuspendedCounter > 0;
1697 };
1698
1699 /**
1700 * Suspends the rendering process. It's helpful to wrap the table render
1701 * cycles triggered by API calls or UI actions (or both) and call the "render"
1702 * once in the end. As a result, it improves the performance of wrapped operations.
1703 * When the table is in the suspend state, most operations will have no visual
1704 * effect until the rendering state is resumed. Resuming the state automatically
1705 * invokes the table rendering. To make sure that after executing all operations,
1706 * the table will be rendered, it's highly recommended to use the {@link Core#batchRender}
1707 * method or {@link Core#batch}, which additionally aggregates the logic execution
1708 * that happens behind the table.
1709 *
1710 * The method is intended to be used by advanced users. Suspending the rendering
1711 * process could cause visual glitches when wrongly implemented.
1712 *
1713 * Every [`suspendRender()`](@/api/core.md#suspendrender) call needs to correspond with one [`resumeRender()`](@/api/core.md#resumerender) call.
1714 * For example, if you call [`suspendRender()`](@/api/core.md#suspendrender) 5 times, you need to call [`resumeRender()`](@/api/core.md#resumerender) 5 times as well.
1715 *
1716 * @memberof Core#
1717 * @function suspendRender
1718 * @since 8.3.0
1719 * @example
1720 * ```js
1721 * hot.suspendRender();
1722 * hot.alter('insert_row_above', 5, 45);
1723 * hot.alter('insert_col_start', 10, 40);
1724 * hot.setDataAtCell(1, 1, 'John');
1725 * hot.setDataAtCell(2, 2, 'Mark');
1726 * hot.setDataAtCell(3, 3, 'Ann');
1727 * hot.setDataAtCell(4, 4, 'Sophia');
1728 * hot.setDataAtCell(5, 5, 'Mia');
1729 * hot.selectCell(0, 0);
1730 * hot.resumeRender(); // It re-renders the table internally
1731 * ```
1732 */
1733 this.suspendRender = function () {
1734 this.renderSuspendedCounter += 1;
1735 };
1736
1737 /**
1738 * Resumes the rendering process. In combination with the {@link Core#suspendRender}
1739 * method it allows aggregating the table render cycles triggered by API calls or UI
1740 * actions (or both) and calls the "render" once in the end. When the table is in
1741 * the suspend state, most operations will have no visual effect until the rendering
1742 * state is resumed. Resuming the state automatically invokes the table rendering.
1743 *
1744 * The method is intended to be used by advanced users. Suspending the rendering
1745 * process could cause visual glitches when wrongly implemented.
1746 *
1747 * Every [`suspendRender()`](@/api/core.md#suspendrender) call needs to correspond with one [`resumeRender()`](@/api/core.md#resumerender) call.
1748 * For example, if you call [`suspendRender()`](@/api/core.md#suspendrender) 5 times, you need to call [`resumeRender()`](@/api/core.md#resumerender) 5 times as well.
1749 *
1750 * @memberof Core#
1751 * @function resumeRender
1752 * @since 8.3.0
1753 * @example
1754 * ```js
1755 * hot.suspendRender();
1756 * hot.alter('insert_row_above', 5, 45);
1757 * hot.alter('insert_col_start', 10, 40);
1758 * hot.setDataAtCell(1, 1, 'John');
1759 * hot.setDataAtCell(2, 2, 'Mark');
1760 * hot.setDataAtCell(3, 3, 'Ann');
1761 * hot.setDataAtCell(4, 4, 'Sophia');
1762 * hot.setDataAtCell(5, 5, 'Mia');
1763 * hot.selectCell(0, 0);
1764 * hot.resumeRender(); // It re-renders the table internally
1765 * ```
1766 */
1767 this.resumeRender = function () {
1768 const nextValue = this.renderSuspendedCounter - 1;
1769 this.renderSuspendedCounter = Math.max(nextValue, 0);
1770 if (!this.isRenderSuspended() && nextValue === this.renderSuspendedCounter) {
1771 if (this.renderCall) {
1772 this.render();
1773 } else {
1774 instance.view.render();
1775 }
1776 }
1777 };
1778
1779 /**
1780 * Rerender the table. Calling this method starts the process of recalculating, redrawing and applying the changes
1781 * to the DOM. While rendering the table all cell renderers are recalled.
1782 *
1783 * Calling this method manually is not recommended. Handsontable tries to render itself by choosing the most
1784 * optimal moments in its lifecycle.
1785 *
1786 * @memberof Core#
1787 * @function render
1788 */
1789 this.render = function () {
1790 if (this.view) {
1791 this.renderCall = true;
1792 this.forceFullRender = true; // used when data was changed or when all cells need to be re-rendered
1793
1794 if (!this.isRenderSuspended()) {
1795 instance.view.render();
1796 }
1797 }
1798 };
1799
1800 /**
1801 * The method aggregates multi-line API calls into a callback and postpones the
1802 * table rendering process. After the execution of the operations, the table is
1803 * rendered once. As a result, it improves the performance of wrapped operations.
1804 * Without batching, a similar case could trigger multiple table render calls.
1805 *
1806 * @memberof Core#
1807 * @function batchRender
1808 * @param {Function} wrappedOperations Batched operations wrapped in a function.
1809 * @returns {*} Returns result from the wrappedOperations callback.
1810 * @since 8.3.0
1811 * @example
1812 * ```js
1813 * hot.batchRender(() => {
1814 * hot.alter('insert_row_above', 5, 45);
1815 * hot.alter('insert_col_start', 10, 40);
1816 * hot.setDataAtCell(1, 1, 'John');
1817 * hot.setDataAtCell(2, 2, 'Mark');
1818 * hot.setDataAtCell(3, 3, 'Ann');
1819 * hot.setDataAtCell(4, 4, 'Sophia');
1820 * hot.setDataAtCell(5, 5, 'Mia');
1821 * hot.selectCell(0, 0);
1822 * // The table will be rendered once after executing the callback
1823 * });
1824 * ```
1825 */
1826 this.batchRender = function (wrappedOperations) {
1827 this.suspendRender();
1828 const result = wrappedOperations();
1829 this.resumeRender();
1830 return result;
1831 };
1832
1833 /**
1834 * Checks if the table indexes recalculation process was suspended. See explanation
1835 * in {@link Core#suspendExecution}.
1836 *
1837 * @memberof Core#
1838 * @function isExecutionSuspended
1839 * @since 8.3.0
1840 * @returns {boolean}
1841 */
1842 this.isExecutionSuspended = function () {
1843 return this.executionSuspendedCounter > 0;
1844 };
1845
1846 /**
1847 * Suspends the execution process. It's helpful to wrap the table logic changes
1848 * such as index changes into one call after which the cache is updated. As a result,
1849 * it improves the performance of wrapped operations.
1850 *
1851 * The method is intended to be used by advanced users. Suspending the execution
1852 * process could cause visual glitches caused by not updated the internal table cache.
1853 *
1854 * @memberof Core#
1855 * @function suspendExecution
1856 * @since 8.3.0
1857 * @example
1858 * ```js
1859 * hot.suspendExecution();
1860 * const filters = hot.getPlugin('filters');
1861 *
1862 * filters.addCondition(2, 'contains', ['3']);
1863 * filters.filter();
1864 * hot.getPlugin('columnSorting').sort({ column: 1, sortOrder: 'desc' });
1865 * hot.resumeExecution(); // It updates the cache internally
1866 * ```
1867 */
1868 this.suspendExecution = function () {
1869 this.executionSuspendedCounter += 1;
1870 this.columnIndexMapper.suspendOperations();
1871 this.rowIndexMapper.suspendOperations();
1872 };
1873
1874 /**
1875 * Resumes the execution process. In combination with the {@link Core#suspendExecution}
1876 * method it allows aggregating the table logic changes after which the cache is
1877 * updated. Resuming the state automatically invokes the table cache updating process.
1878 *
1879 * The method is intended to be used by advanced users. Suspending the execution
1880 * process could cause visual glitches caused by not updated the internal table cache.
1881 *
1882 * @memberof Core#
1883 * @function resumeExecution
1884 * @param {boolean} [forceFlushChanges=false] If `true`, the table internal data cache
1885 * is recalculated after the execution of the batched operations. For nested
1886 * {@link Core#batchExecution} calls, it can be desire to recalculate the table
1887 * after each batch.
1888 * @since 8.3.0
1889 * @example
1890 * ```js
1891 * hot.suspendExecution();
1892 * const filters = hot.getPlugin('filters');
1893 *
1894 * filters.addCondition(2, 'contains', ['3']);
1895 * filters.filter();
1896 * hot.getPlugin('columnSorting').sort({ column: 1, sortOrder: 'desc' });
1897 * hot.resumeExecution(); // It updates the cache internally
1898 * ```
1899 */
1900 this.resumeExecution = function () {
1901 let forceFlushChanges = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1902 const nextValue = this.executionSuspendedCounter - 1;
1903 this.executionSuspendedCounter = Math.max(nextValue, 0);
1904 if (!this.isExecutionSuspended() && nextValue === this.executionSuspendedCounter || forceFlushChanges) {
1905 this.columnIndexMapper.resumeOperations();
1906 this.rowIndexMapper.resumeOperations();
1907 }
1908 };
1909
1910 /**
1911 * The method aggregates multi-line API calls into a callback and postpones the
1912 * table execution process. After the execution of the operations, the internal table
1913 * cache is recalculated once. As a result, it improves the performance of wrapped
1914 * operations. Without batching, a similar case could trigger multiple table cache rebuilds.
1915 *
1916 * @memberof Core#
1917 * @function batchExecution
1918 * @param {Function} wrappedOperations Batched operations wrapped in a function.
1919 * @param {boolean} [forceFlushChanges=false] If `true`, the table internal data cache
1920 * is recalculated after the execution of the batched operations. For nested calls,
1921 * it can be a desire to recalculate the table after each batch.
1922 * @returns {*} Returns result from the wrappedOperations callback.
1923 * @since 8.3.0
1924 * @example
1925 * ```js
1926 * hot.batchExecution(() => {
1927 * const filters = hot.getPlugin('filters');
1928 *
1929 * filters.addCondition(2, 'contains', ['3']);
1930 * filters.filter();
1931 * hot.getPlugin('columnSorting').sort({ column: 1, sortOrder: 'desc' });
1932 * // The table cache will be recalculated once after executing the callback
1933 * });
1934 * ```
1935 */
1936 this.batchExecution = function (wrappedOperations) {
1937 let forceFlushChanges = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1938 this.suspendExecution();
1939 const result = wrappedOperations();
1940 this.resumeExecution(forceFlushChanges);
1941 return result;
1942 };
1943
1944 /**
1945 * It batches the rendering process and index recalculations. The method aggregates
1946 * multi-line API calls into a callback and postpones the table rendering process
1947 * as well aggregates the table logic changes such as index changes into one call
1948 * after which the cache is updated. After the execution of the operations, the
1949 * table is rendered, and the cache is updated once. As a result, it improves the
1950 * performance of wrapped operations.
1951 *
1952 * @memberof Core#
1953 * @function batch
1954 * @param {Function} wrappedOperations Batched operations wrapped in a function.
1955 * @returns {*} Returns result from the wrappedOperations callback.
1956 * @since 8.3.0
1957 * @example
1958 * ```js
1959 * hot.batch(() => {
1960 * hot.alter('insert_row_above', 5, 45);
1961 * hot.alter('insert_col_start', 10, 40);
1962 * hot.setDataAtCell(1, 1, 'x');
1963 * hot.setDataAtCell(2, 2, 'c');
1964 * hot.setDataAtCell(3, 3, 'v');
1965 * hot.setDataAtCell(4, 4, 'b');
1966 * hot.setDataAtCell(5, 5, 'n');
1967 * hot.selectCell(0, 0);
1968 *
1969 * const filters = hot.getPlugin('filters');
1970 *
1971 * filters.addCondition(2, 'contains', ['3']);
1972 * filters.filter();
1973 * hot.getPlugin('columnSorting').sort({ column: 1, sortOrder: 'desc' });
1974 * // The table will be re-rendered and cache will be recalculated once after executing the callback
1975 * });
1976 * ```
1977 */
1978 this.batch = function (wrappedOperations) {
1979 this.suspendRender();
1980 this.suspendExecution();
1981 const result = wrappedOperations();
1982 this.resumeExecution();
1983 this.resumeRender();
1984 return result;
1985 };
1986
1987 /**
1988 * Updates dimensions of the table. The method compares previous dimensions with the current ones and updates accordingly.
1989 *
1990 * @memberof Core#
1991 * @function refreshDimensions
1992 * @fires Hooks#beforeRefreshDimensions
1993 * @fires Hooks#afterRefreshDimensions
1994 */
1995 this.refreshDimensions = function () {
1996 if (!instance.view) {
1997 return;
1998 }
1999 const {
2000 width: lastWidth,
2001 height: lastHeight
2002 } = instance.view.getLastSize();
2003 const {
2004 width,
2005 height
2006 } = instance.rootElement.getBoundingClientRect();
2007 const isSizeChanged = width !== lastWidth || height !== lastHeight;
2008 const isResizeBlocked = instance.runHooks('beforeRefreshDimensions', {
2009 width: lastWidth,
2010 height: lastHeight
2011 }, {
2012 width,
2013 height
2014 }, isSizeChanged) === false;
2015 if (isResizeBlocked) {
2016 return;
2017 }
2018 if (isSizeChanged || instance.view._wt.wtOverlays.scrollableElement === instance.rootWindow) {
2019 instance.view.setLastSize(width, height);
2020 instance.render();
2021 }
2022 instance.runHooks('afterRefreshDimensions', {
2023 width: lastWidth,
2024 height: lastHeight
2025 }, {
2026 width,
2027 height
2028 }, isSizeChanged);
2029 };
2030
2031 /**
2032 * The `updateData()` method replaces Handsontable's [`data`](@/api/options.md#data) with a new dataset.
2033 *
2034 * The `updateData()` method:
2035 * - Keeps cells' states (e.g. cells' [formatting](@/guides/cell-features/formatting-cells/formatting-cells.md) and cells' [`readOnly`](@/api/options.md#readonly) states)
2036 * - Keeps rows' states (e.g. row order)
2037 * - Keeps columns' states (e.g. column order)
2038 *
2039 * To replace Handsontable's [`data`](@/api/options.md#data) and reset states, use the [`loadData()`](#loaddata) method.
2040 *
2041 * Read more:
2042 * - [Binding to data](@/guides/getting-started/binding-to-data/binding-to-data.md)
2043 * - [Saving data](@/guides/getting-started/saving-data/saving-data.md)
2044 *
2045 * @memberof Core#
2046 * @function updateData
2047 * @since 11.1.0
2048 * @param {Array} data An [array of arrays](@/guides/getting-started/binding-to-data/binding-to-data.md#array-of-arrays), or an [array of objects](@/guides/getting-started/binding-to-data/binding-to-data.md#array-of-objects), that contains Handsontable's data
2049 * @param {string} [source] The source of the `updateData()` call
2050 * @fires Hooks#beforeUpdateData
2051 * @fires Hooks#afterUpdateData
2052 * @fires Hooks#afterChange
2053 */
2054 this.updateData = function (data, source) {
2055 (0, _dataMap.replaceData)(data, newDataMap => {
2056 datamap = newDataMap;
2057 }, newDataMap => {
2058 datamap = newDataMap;
2059 instance.columnIndexMapper.fitToLength(this.getInitialColumnCount());
2060 instance.rowIndexMapper.fitToLength(this.countSourceRows());
2061 grid.adjustRowsAndCols();
2062 selection.refresh();
2063 }, {
2064 hotInstance: instance,
2065 dataMap: datamap,
2066 dataSource,
2067 internalSource: 'updateData',
2068 source,
2069 metaManager,
2070 firstRun
2071 });
2072 };
2073
2074 /**
2075 * The `loadData()` method replaces Handsontable's [`data`](@/api/options.md#data) with a new dataset.
2076 *
2077 * Additionally, the `loadData()` method:
2078 * - Resets cells' states (e.g. cells' [formatting](@/guides/cell-features/formatting-cells/formatting-cells.md) and cells' [`readOnly`](@/api/options.md#readonly) states)
2079 * - Resets rows' states (e.g. row order)
2080 * - Resets columns' states (e.g. column order)
2081 *
2082 * To replace Handsontable's [`data`](@/api/options.md#data) without resetting states, use the [`updateData()`](#updatedata) method.
2083 *
2084 * Read more:
2085 * - [Binding to data](@/guides/getting-started/binding-to-data/binding-to-data.md)
2086 * - [Saving data](@/guides/getting-started/saving-data/saving-data.md)
2087 *
2088 * @memberof Core#
2089 * @function loadData
2090 * @param {Array} data An [array of arrays](@/guides/getting-started/binding-to-data/binding-to-data.md#array-of-arrays), or an [array of objects](@/guides/getting-started/binding-to-data/binding-to-data.md#array-of-objects), that contains Handsontable's data
2091 * @param {string} [source] The source of the `loadData()` call
2092 * @fires Hooks#beforeLoadData
2093 * @fires Hooks#afterLoadData
2094 * @fires Hooks#afterChange
2095 */
2096 this.loadData = function (data, source) {
2097 (0, _dataMap.replaceData)(data, newDataMap => {
2098 datamap = newDataMap;
2099 }, () => {
2100 metaManager.clearCellsCache();
2101 instance.initIndexMappers();
2102 grid.adjustRowsAndCols();
2103 selection.refresh();
2104 if (firstRun) {
2105 firstRun = [null, 'loadData'];
2106 }
2107 }, {
2108 hotInstance: instance,
2109 dataMap: datamap,
2110 dataSource,
2111 internalSource: 'loadData',
2112 source,
2113 metaManager,
2114 firstRun
2115 });
2116 };
2117
2118 /**
2119 * Gets the initial column count, calculated based on the `columns` setting.
2120 *
2121 * @private
2122 * @returns {number} The calculated number of columns.
2123 */
2124 this.getInitialColumnCount = function () {
2125 const columnsSettings = tableMeta.columns;
2126 let finalNrOfColumns = 0;
2127
2128 // We will check number of columns when the `columns` property was defined as an array. Columns option may
2129 // narrow down or expand displayed dataset in that case.
2130 if (Array.isArray(columnsSettings)) {
2131 finalNrOfColumns = columnsSettings.length;
2132 } else if ((0, _function.isFunction)(columnsSettings)) {
2133 if (instance.dataType === 'array') {
2134 const nrOfSourceColumns = this.countSourceCols();
2135 for (let columnIndex = 0; columnIndex < nrOfSourceColumns; columnIndex += 1) {
2136 if (columnsSettings(columnIndex)) {
2137 finalNrOfColumns += 1;
2138 }
2139 }
2140
2141 // Extended dataset by the `columns` property? Moved code right from the refactored `countCols` method.
2142 } else if (instance.dataType === 'object' || instance.dataType === 'function') {
2143 finalNrOfColumns = datamap.colToPropCache.length;
2144 }
2145
2146 // In some cases we need to check columns length from the schema, i.e. `data` may be empty.
2147 } else if ((0, _mixed.isDefined)(tableMeta.dataSchema)) {
2148 const schema = datamap.getSchema();
2149
2150 // Schema may be defined as an array of objects. Each object will define column.
2151 finalNrOfColumns = Array.isArray(schema) ? schema.length : (0, _object.deepObjectSize)(schema);
2152 } else {
2153 // We init index mappers by length of source data to provide indexes also for skipped indexes.
2154 finalNrOfColumns = this.countSourceCols();
2155 }
2156 return finalNrOfColumns;
2157 };
2158
2159 /**
2160 * Init index mapper which manage indexes assigned to the data.
2161 *
2162 * @private
2163 */
2164 this.initIndexMappers = function () {
2165 this.columnIndexMapper.initToLength(this.getInitialColumnCount());
2166 this.rowIndexMapper.initToLength(this.countSourceRows());
2167 };
2168
2169 /**
2170 * Returns the current data object (the same one that was passed by `data` configuration option or `loadData` method,
2171 * unless some modifications have been applied (i.e. Sequence of rows/columns was changed, some row/column was skipped).
2172 * If that's the case - use the {@link Core#getSourceData} method.).
2173 *
2174 * Optionally you can provide cell range by defining `row`, `column`, `row2`, `column2` to get only a fragment of table data.
2175 *
2176 * @memberof Core#
2177 * @function getData
2178 * @param {number} [row] From visual row index.
2179 * @param {number} [column] From visual column index.
2180 * @param {number} [row2] To visual row index.
2181 * @param {number} [column2] To visual column index.
2182 * @returns {Array[]} Array with the data.
2183 * @example
2184 * ```js
2185 * // Get all data (in order how it is rendered in the table).
2186 * hot.getData();
2187 * // Get data fragment (from top-left 0, 0 to bottom-right 3, 3).
2188 * hot.getData(3, 3);
2189 * // Get data fragment (from top-left 2, 1 to bottom-right 3, 3).
2190 * hot.getData(2, 1, 3, 3);
2191 * ```
2192 */
2193 this.getData = function (row, column, row2, column2) {
2194 if ((0, _mixed.isUndefined)(row)) {
2195 return datamap.getAll();
2196 }
2197 return datamap.getRange(instance._createCellCoords(row, column), instance._createCellCoords(row2, column2), datamap.DESTINATION_RENDERER);
2198 };
2199
2200 /**
2201 * Returns a string value of the selected range. Each column is separated by tab, each row is separated by a new
2202 * line character.
2203 *
2204 * @memberof Core#
2205 * @function getCopyableText
2206 * @param {number} startRow From visual row index.
2207 * @param {number} startCol From visual column index.
2208 * @param {number} endRow To visual row index.
2209 * @param {number} endCol To visual column index.
2210 * @returns {string}
2211 */
2212 this.getCopyableText = function (startRow, startCol, endRow, endCol) {
2213 return datamap.getCopyableText(instance._createCellCoords(startRow, startCol), instance._createCellCoords(endRow, endCol));
2214 };
2215
2216 /**
2217 * Returns the data's copyable value at specified `row` and `column` index.
2218 *
2219 * @memberof Core#
2220 * @function getCopyableData
2221 * @param {number} row Visual row index.
2222 * @param {number} column Visual column index.
2223 * @returns {string}
2224 */
2225 this.getCopyableData = function (row, column) {
2226 return datamap.getCopyable(row, datamap.colToProp(column));
2227 };
2228
2229 /**
2230 * Returns schema provided by constructor settings. If it doesn't exist then it returns the schema based on the data
2231 * structure in the first row.
2232 *
2233 * @memberof Core#
2234 * @function getSchema
2235 * @returns {object} Schema object.
2236 */
2237 this.getSchema = function () {
2238 return datamap.getSchema();
2239 };
2240
2241 /**
2242 * Use it if you need to change configuration after initialization. The `settings` argument is an object containing the changed
2243 * settings, declared the same way as in the initial settings object.
2244 *
2245 * __Note__, that although the `updateSettings` method doesn't overwrite the previously declared settings, it might reset
2246 * the settings made post-initialization. (for example - ignore changes made using the columnResize feature).
2247 *
2248 * Since 8.0.0 passing `columns` or `data` inside `settings` objects will result in resetting states corresponding to rows and columns
2249 * (for example, row/column sequence, column width, row height, frozen columns etc.).
2250 *
2251 * Since 12.0.0 passing `data` inside `settings` objects no longer results in resetting states corresponding to rows and columns
2252 * (for example, row/column sequence, column width, row height, frozen columns etc.).
2253 *
2254 * @memberof Core#
2255 * @function updateSettings
2256 * @param {object} settings A settings object (see {@link Options}). Only provide the settings that are changed, not the whole settings object that was used for initialization.
2257 * @param {boolean} [init=false] Internally used for in initialization mode.
2258 * @example
2259 * ```js
2260 * hot.updateSettings({
2261 * contextMenu: true,
2262 * colHeaders: true,
2263 * fixedRowsTop: 2
2264 * });
2265 * ```
2266 * @fires Hooks#afterCellMetaReset
2267 * @fires Hooks#afterUpdateSettings
2268 */
2269 this.updateSettings = function (settings) {
2270 let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2271 const dataUpdateFunction = (firstRun ? instance.loadData : instance.updateData).bind(this);
2272 let columnsAsFunc = false;
2273 let i;
2274 let j;
2275 if ((0, _mixed.isDefined)(settings.rows)) {
2276 throw new Error('The "rows" setting is no longer supported. Do you mean startRows, minRows or maxRows?');
2277 }
2278 if ((0, _mixed.isDefined)(settings.cols)) {
2279 throw new Error('The "cols" setting is no longer supported. Do you mean startCols, minCols or maxCols?');
2280 }
2281 if ((0, _mixed.isDefined)(settings.ganttChart)) {
2282 throw new Error('Since 8.0.0 the "ganttChart" setting is no longer supported.');
2283 }
2284
2285 // eslint-disable-next-line no-restricted-syntax
2286 for (i in settings) {
2287 if (i === 'data') {
2288 // Do nothing. loadData will be triggered later
2289 } else if (i === 'language') {
2290 setLanguage(settings.language);
2291 } else if (i === 'className') {
2292 setClassName('className', settings.className);
2293 } else if (i === 'tableClassName' && instance.table) {
2294 setClassName('tableClassName', settings.tableClassName);
2295 instance.view._wt.wtOverlays.syncOverlayTableClassNames();
2296 } else if (_pluginHooks.default.getSingleton().isRegistered(i) || _pluginHooks.default.getSingleton().isDeprecated(i)) {
2297 if ((0, _function.isFunction)(settings[i]) || Array.isArray(settings[i])) {
2298 settings[i].initialHook = true;
2299 instance.addHook(i, settings[i]);
2300 }
2301 } else if (!init && (0, _object.hasOwnProperty)(settings, i)) {
2302 // Update settings
2303 globalMeta[i] = settings[i];
2304 }
2305 }
2306
2307 // Load data or create data map
2308 if (settings.data === undefined && tableMeta.data === undefined) {
2309 dataUpdateFunction(null, 'updateSettings'); // data source created just now
2310 } else if (settings.data !== undefined) {
2311 dataUpdateFunction(settings.data, 'updateSettings'); // data source given as option
2312 } else if (settings.columns !== undefined) {
2313 datamap.createMap();
2314
2315 // The `column` property has changed - dataset may be expanded or narrowed down. The `loadData` do the same.
2316 instance.initIndexMappers();
2317 }
2318 const clen = instance.countCols();
2319 const columnSetting = tableMeta.columns;
2320
2321 // Init columns constructors configuration
2322 if (columnSetting && (0, _function.isFunction)(columnSetting)) {
2323 columnsAsFunc = true;
2324 }
2325
2326 // Clear cell meta cache
2327 if (settings.cell !== undefined || settings.cells !== undefined || settings.columns !== undefined) {
2328 metaManager.clearCache();
2329 }
2330 if (clen > 0) {
2331 for (i = 0, j = 0; i < clen; i++) {
2332 // Use settings provided by user
2333 if (columnSetting) {
2334 const column = columnsAsFunc ? columnSetting(i) : columnSetting[j];
2335 if (column) {
2336 metaManager.updateColumnMeta(j, column);
2337 }
2338 }
2339 j += 1;
2340 }
2341 }
2342 if ((0, _mixed.isDefined)(settings.cell)) {
2343 (0, _object.objectEach)(settings.cell, cell => {
2344 instance.setCellMetaObject(cell.row, cell.col, cell);
2345 });
2346 }
2347 instance.runHooks('afterCellMetaReset');
2348 let currentHeight = instance.rootElement.style.height;
2349 if (currentHeight !== '') {
2350 currentHeight = parseInt(instance.rootElement.style.height, 10);
2351 }
2352 let height = settings.height;
2353 if ((0, _function.isFunction)(height)) {
2354 height = height();
2355 }
2356 if (init) {
2357 const initialStyle = instance.rootElement.getAttribute('style');
2358 if (initialStyle) {
2359 instance.rootElement.setAttribute('data-initialstyle', instance.rootElement.getAttribute('style'));
2360 }
2361 }
2362 if (height === null) {
2363 const initialStyle = instance.rootElement.getAttribute('data-initialstyle');
2364 if (initialStyle && (initialStyle.indexOf('height') > -1 || initialStyle.indexOf('overflow') > -1)) {
2365 instance.rootElement.setAttribute('style', initialStyle);
2366 } else {
2367 instance.rootElement.style.height = '';
2368 instance.rootElement.style.overflow = '';
2369 }
2370 } else if (height !== undefined) {
2371 instance.rootElement.style.height = isNaN(height) ? `${height}` : `${height}px`;
2372 instance.rootElement.style.overflow = 'hidden';
2373 }
2374 if (typeof settings.width !== 'undefined') {
2375 let width = settings.width;
2376 if ((0, _function.isFunction)(width)) {
2377 width = width();
2378 }
2379 instance.rootElement.style.width = isNaN(width) ? `${width}` : `${width}px`;
2380 }
2381 if (!init) {
2382 if (instance.view) {
2383 instance.view._wt.wtViewport.resetHasOversizedColumnHeadersMarked();
2384 instance.view._wt.exportSettingsAsClassNames();
2385 }
2386 instance.runHooks('afterUpdateSettings', settings);
2387 }
2388 grid.adjustRowsAndCols();
2389 if (instance.view && !firstRun) {
2390 instance.forceFullRender = true; // used when data was changed
2391 instance.view.render();
2392 instance.view._wt.wtOverlays.adjustElementsSize();
2393 }
2394 if (!init && instance.view && (currentHeight === '' || height === '' || height === undefined) && currentHeight !== height) {
2395 instance.view._wt.wtOverlays.updateMainScrollableElements();
2396 }
2397 };
2398
2399 /**
2400 * Gets the value of the currently focused cell.
2401 *
2402 * For column headers and row headers, returns `null`.
2403 *
2404 * @memberof Core#
2405 * @function getValue
2406 * @returns {*} The value of the focused cell.
2407 */
2408 this.getValue = function () {
2409 const sel = instance.getSelectedLast();
2410 if (tableMeta.getValue) {
2411 if ((0, _function.isFunction)(tableMeta.getValue)) {
2412 return tableMeta.getValue.call(instance);
2413 } else if (sel) {
2414 return instance.getData()[sel[0][0]][tableMeta.getValue];
2415 }
2416 } else if (sel) {
2417 return instance.getDataAtCell(sel[0], sel[1]);
2418 }
2419 };
2420
2421 /**
2422 * Returns the object settings.
2423 *
2424 * @memberof Core#
2425 * @function getSettings
2426 * @returns {TableMeta} Object containing the current table settings.
2427 */
2428 this.getSettings = function () {
2429 return tableMeta;
2430 };
2431
2432 /**
2433 * Clears the data from the table (the table settings remain intact).
2434 *
2435 * @memberof Core#
2436 * @function clear
2437 */
2438 this.clear = function () {
2439 this.selectAll();
2440 this.emptySelectedCells();
2441 };
2442
2443 /**
2444 * The `alter()` method lets you alter the grid's structure
2445 * by adding or removing rows and columns at specified positions.
2446 *
2447 * ::: tip
2448 * If you use an array of objects in your [`data`](@/api/options.md#data), the column-related actions won't work.
2449 * :::
2450 *
2451 * ```js
2452 * // above row 10 (by visual index), insert 1 new row
2453 * hot.alter('insert_row_above', 10);
2454 * ```
2455 *
2456 * | Action | With `index` | Without `index` |
2457 * | -------------------- | ------------ | --------------- |
2458 * | `'insert_row_above'` | Inserts rows above the `index` row. | Inserts rows above the first row. |
2459 * | `'insert_row_below'` | Inserts rows below the `index` row. | Inserts rows below the last row. |
2460 * | `'remove_row'` | Removes rows, starting from the `index` row. | Removes rows, starting from the last row. |
2461 * | `'insert_col_start'` | Inserts columns before the `index` column. | Inserts columns before the first column. |
2462 * | `'insert_col_end'` | Inserts columns after the `index` column. | Inserts columns after the last column. |
2463 * | `'remove_col'` | Removes columns, starting from the `index` column. | Removes columns, starting from the last column. |
2464 *
2465 * Additional information about `'insert_col_start'` and `'insert_col_end'`:
2466 * - Their behavior depends on your [`layoutDirection`](@/api/options.md#layoutdirection).
2467 * - If the provided `index` is higher than the actual number of columns, Handsontable doesn't generate
2468 * the columns missing in between. Instead, the new columns are inserted next to the last column.
2469 *
2470 * @memberof Core#
2471 * @function alter
2472 * @param {string} action Available operations:
2473 * <ul>
2474 * <li> `'insert_row_above'` </li>
2475 * <li> `'insert_row_below'` </li>
2476 * <li> `'remove_row'` </li> </li>
2477 * <li> `'insert_col_start'` </li>
2478 * <li> `'insert_col_end'` </li>
2479 * <li> `'remove_col'` </li>
2480 * </ul>
2481 * @param {number|number[]} [index] A visual index of the row/column before or after which the new row/column will be
2482 * inserted or removed. Can also be an array of arrays, in format `[[index, amount],...]`.
2483 * @param {number} [amount] The amount of rows or columns to be inserted or removed (default: `1`).
2484 * @param {string} [source] Source indicator.
2485 * @param {boolean} [keepEmptyRows] If set to `true`: prevents removing empty rows.
2486 * @example
2487 * ```js
2488 * // above row 10 (by visual index), insert 1 new row
2489 * hot.alter('insert_row_above', 10);
2490 *
2491 * // below row 10 (by visual index), insert 3 new rows
2492 * hot.alter('insert_row_below', 10, 3);
2493 *
2494 * // in the LTR layout direction: to the left of column 10 (by visual index), insert 3 new columns
2495 * // in the RTL layout direction: to the right of column 10 (by visual index), insert 3 new columns
2496 * hot.alter('insert_col_start', 10, 3);
2497 *
2498 * // in the LTR layout direction: to the right of column 10 (by visual index), insert 1 new column
2499 * // in the RTL layout direction: to the left of column 10 (by visual index), insert 1 new column
2500 * hot.alter('insert_col_end', 10);
2501 *
2502 * // remove 2 rows, starting from row 10 (by visual index)
2503 * hot.alter('remove_row', 10, 2);
2504 *
2505 * // remove 3 rows, starting from row 1 (by visual index)
2506 * // remove 2 rows, starting from row 5 (by visual index)
2507 * hot.alter('remove_row', [[1, 3], [5, 2]]);
2508 * ```
2509 */
2510 this.alter = function (action, index, amount, source, keepEmptyRows) {
2511 grid.alter(action, index, amount, source, keepEmptyRows);
2512 };
2513
2514 /**
2515 * Returns a TD element for the given `row` and `column` arguments, if it is rendered on screen.
2516 * Returns `null` if the TD is not rendered on screen (probably because that part of the table is not visible).
2517 *
2518 * @memberof Core#
2519 * @function getCell
2520 * @param {number} row Visual row index.
2521 * @param {number} column Visual column index.
2522 * @param {boolean} [topmost=false] If set to `true`, it returns the TD element from the topmost overlay. For example,
2523 * if the wanted cell is in the range of fixed rows, it will return a TD element from the `top` overlay.
2524 * @returns {HTMLTableCellElement|null} The cell's TD element.
2525 */
2526 this.getCell = function (row, column) {
2527 let topmost = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
2528 let renderableColumnIndex = column; // Handling also column headers.
2529 let renderableRowIndex = row; // Handling also row headers.
2530
2531 if (column >= 0) {
2532 if (this.columnIndexMapper.isHidden(this.toPhysicalColumn(column))) {
2533 return null;
2534 }
2535 renderableColumnIndex = this.columnIndexMapper.getRenderableFromVisualIndex(column);
2536 }
2537 if (row >= 0) {
2538 if (this.rowIndexMapper.isHidden(this.toPhysicalRow(row))) {
2539 return null;
2540 }
2541 renderableRowIndex = this.rowIndexMapper.getRenderableFromVisualIndex(row);
2542 }
2543 if (renderableRowIndex === null || renderableColumnIndex === null || renderableRowIndex === undefined || renderableColumnIndex === undefined) {
2544 return null;
2545 }
2546 return instance.view.getCellAtCoords(instance._createCellCoords(renderableRowIndex, renderableColumnIndex), topmost);
2547 };
2548
2549 /**
2550 * Returns the coordinates of the cell, provided as a HTML table cell element.
2551 *
2552 * @memberof Core#
2553 * @function getCoords
2554 * @param {HTMLTableCellElement} element The HTML Element representing the cell.
2555 * @returns {CellCoords|null} Visual coordinates object.
2556 * @example
2557 * ```js
2558 * hot.getCoords(hot.getCell(1, 1));
2559 * // it returns CellCoords object instance with props row: 1 and col: 1.
2560 * ```
2561 */
2562 this.getCoords = function (element) {
2563 const renderableCoords = this.view._wt.wtTable.getCoords(element);
2564 if (renderableCoords === null) {
2565 return null;
2566 }
2567 const {
2568 row: renderableRow,
2569 col: renderableColumn
2570 } = renderableCoords;
2571 let visualRow = renderableRow;
2572 let visualColumn = renderableColumn;
2573 if (renderableRow >= 0) {
2574 visualRow = this.rowIndexMapper.getVisualFromRenderableIndex(renderableRow);
2575 }
2576 if (renderableColumn >= 0) {
2577 visualColumn = this.columnIndexMapper.getVisualFromRenderableIndex(renderableColumn);
2578 }
2579 return instance._createCellCoords(visualRow, visualColumn);
2580 };
2581
2582 /**
2583 * Returns the property name that corresponds with the given column index.
2584 * If the data source is an array of arrays, it returns the columns index.
2585 *
2586 * @memberof Core#
2587 * @function colToProp
2588 * @param {number} column Visual column index.
2589 * @returns {string|number} Column property or physical column index.
2590 */
2591 this.colToProp = function (column) {
2592 return datamap.colToProp(column);
2593 };
2594
2595 /**
2596 * Returns column index that corresponds with the given property.
2597 *
2598 * @memberof Core#
2599 * @function propToCol
2600 * @param {string|number} prop Property name or physical column index.
2601 * @returns {number} Visual column index.
2602 */
2603 this.propToCol = function (prop) {
2604 return datamap.propToCol(prop);
2605 };
2606
2607 /**
2608 * Translate physical row index into visual.
2609 *
2610 * This method is useful when you want to retrieve visual row index which can be reordered, moved or trimmed
2611 * based on a physical index.
2612 *
2613 * @memberof Core#
2614 * @function toVisualRow
2615 * @param {number} row Physical row index.
2616 * @returns {number} Returns visual row index.
2617 */
2618 this.toVisualRow = row => this.rowIndexMapper.getVisualFromPhysicalIndex(row);
2619
2620 /**
2621 * Translate physical column index into visual.
2622 *
2623 * This method is useful when you want to retrieve visual column index which can be reordered, moved or trimmed
2624 * based on a physical index.
2625 *
2626 * @memberof Core#
2627 * @function toVisualColumn
2628 * @param {number} column Physical column index.
2629 * @returns {number} Returns visual column index.
2630 */
2631 this.toVisualColumn = column => this.columnIndexMapper.getVisualFromPhysicalIndex(column);
2632
2633 /**
2634 * Translate visual row index into physical.
2635 *
2636 * This method is useful when you want to retrieve physical row index based on a visual index which can be
2637 * reordered, moved or trimmed.
2638 *
2639 * @memberof Core#
2640 * @function toPhysicalRow
2641 * @param {number} row Visual row index.
2642 * @returns {number} Returns physical row index.
2643 */
2644 this.toPhysicalRow = row => this.rowIndexMapper.getPhysicalFromVisualIndex(row);
2645
2646 /**
2647 * Translate visual column index into physical.
2648 *
2649 * This method is useful when you want to retrieve physical column index based on a visual index which can be
2650 * reordered, moved or trimmed.
2651 *
2652 * @memberof Core#
2653 * @function toPhysicalColumn
2654 * @param {number} column Visual column index.
2655 * @returns {number} Returns physical column index.
2656 */
2657 this.toPhysicalColumn = column => this.columnIndexMapper.getPhysicalFromVisualIndex(column);
2658
2659 /**
2660 * @description
2661 * Returns the cell value at `row`, `column`.
2662 *
2663 * __Note__: If data is reordered, sorted or trimmed, the currently visible order will be used.
2664 *
2665 * @memberof Core#
2666 * @function getDataAtCell
2667 * @param {number} row Visual row index.
2668 * @param {number} column Visual column index.
2669 * @returns {*} Data at cell.
2670 */
2671 this.getDataAtCell = function (row, column) {
2672 return datamap.get(row, datamap.colToProp(column));
2673 };
2674
2675 /**
2676 * Returns value at visual `row` and `prop` indexes.
2677 *
2678 * __Note__: If data is reordered, sorted or trimmed, the currently visible order will be used.
2679 *
2680 * @memberof Core#
2681 * @function getDataAtRowProp
2682 * @param {number} row Visual row index.
2683 * @param {string} prop Property name.
2684 * @returns {*} Cell value.
2685 */
2686 this.getDataAtRowProp = function (row, prop) {
2687 return datamap.get(row, prop);
2688 };
2689
2690 /**
2691 * @description
2692 * Returns array of column values from the data source.
2693 *
2694 * __Note__: If columns were reordered or sorted, the currently visible order will be used.
2695 *
2696 * @memberof Core#
2697 * @function getDataAtCol
2698 * @param {number} column Visual column index.
2699 * @returns {Array} Array of cell values.
2700 */
2701 this.getDataAtCol = function (column) {
2702 const columnData = [];
2703 const dataByRows = datamap.getRange(instance._createCellCoords(0, column), instance._createCellCoords(tableMeta.data.length - 1, column), datamap.DESTINATION_RENDERER);
2704 for (let i = 0; i < dataByRows.length; i += 1) {
2705 for (let j = 0; j < dataByRows[i].length; j += 1) {
2706 columnData.push(dataByRows[i][j]);
2707 }
2708 }
2709 return columnData;
2710 };
2711
2712 /**
2713 * Given the object property name (e.g. `'first.name'` or `'0'`), returns an array of column's values from the table data.
2714 * You can also provide a column index as the first argument.
2715 *
2716 * @memberof Core#
2717 * @function getDataAtProp
2718 * @param {string|number} prop Property name or physical column index.
2719 * @returns {Array} Array of cell values.
2720 */
2721 // TODO: Getting data from `datamap` should work on visual indexes.
2722 this.getDataAtProp = function (prop) {
2723 const columnData = [];
2724 const dataByRows = datamap.getRange(instance._createCellCoords(0, datamap.propToCol(prop)), instance._createCellCoords(tableMeta.data.length - 1, datamap.propToCol(prop)), datamap.DESTINATION_RENDERER);
2725 for (let i = 0; i < dataByRows.length; i += 1) {
2726 for (let j = 0; j < dataByRows[i].length; j += 1) {
2727 columnData.push(dataByRows[i][j]);
2728 }
2729 }
2730 return columnData;
2731 };
2732
2733 /**
2734 * Returns a clone of the source data object.
2735 * Optionally you can provide a cell range by using the `row`, `column`, `row2`, `column2` arguments, to get only a
2736 * fragment of the table data.
2737 *
2738 * __Note__: This method does not participate in data transformation. If the visual data of the table is reordered,
2739 * sorted or trimmed only physical indexes are correct.
2740 *
2741 * __Note__: This method may return incorrect values for cells that contain
2742 * [formulas](@/guides/formulas/formula-calculation/formula-calculation.md). This is because `getSourceData()`
2743 * operates on source data ([physical indexes](@/api/indexMapper.md)),
2744 * whereas formulas operate on visual data (visual indexes).
2745 *
2746 * @memberof Core#
2747 * @function getSourceData
2748 * @param {number} [row] From physical row index.
2749 * @param {number} [column] From physical column index (or visual index, if data type is an array of objects).
2750 * @param {number} [row2] To physical row index.
2751 * @param {number} [column2] To physical column index (or visual index, if data type is an array of objects).
2752 * @returns {Array[]|object[]} The table data.
2753 */
2754 this.getSourceData = function (row, column, row2, column2) {
2755 let data;
2756 if (row === undefined) {
2757 data = dataSource.getData();
2758 } else {
2759 data = dataSource.getByRange(instance._createCellCoords(row, column), instance._createCellCoords(row2, column2));
2760 }
2761 return data;
2762 };
2763
2764 /**
2765 * Returns the source data object as an arrays of arrays format even when source data was provided in another format.
2766 * Optionally you can provide a cell range by using the `row`, `column`, `row2`, `column2` arguments, to get only a
2767 * fragment of the table data.
2768 *
2769 * __Note__: This method does not participate in data transformation. If the visual data of the table is reordered,
2770 * sorted or trimmed only physical indexes are correct.
2771 *
2772 * @memberof Core#
2773 * @function getSourceDataArray
2774 * @param {number} [row] From physical row index.
2775 * @param {number} [column] From physical column index (or visual index, if data type is an array of objects).
2776 * @param {number} [row2] To physical row index.
2777 * @param {number} [column2] To physical column index (or visual index, if data type is an array of objects).
2778 * @returns {Array} An array of arrays.
2779 */
2780 this.getSourceDataArray = function (row, column, row2, column2) {
2781 let data;
2782 if (row === undefined) {
2783 data = dataSource.getData(true);
2784 } else {
2785 data = dataSource.getByRange(instance._createCellCoords(row, column), instance._createCellCoords(row2, column2), true);
2786 }
2787 return data;
2788 };
2789
2790 /**
2791 * Returns an array of column values from the data source.
2792 *
2793 * @memberof Core#
2794 * @function getSourceDataAtCol
2795 * @param {number} column Visual column index.
2796 * @returns {Array} Array of the column's cell values.
2797 */
2798 // TODO: Getting data from `sourceData` should work always on physical indexes.
2799 this.getSourceDataAtCol = function (column) {
2800 return dataSource.getAtColumn(column);
2801 };
2802
2803 /* eslint-disable jsdoc/require-param */
2804 /**
2805 * Set the provided value in the source data set at the provided coordinates.
2806 *
2807 * @memberof Core#
2808 * @function setSourceDataAtCell
2809 * @param {number|Array} row Physical row index or array of changes in format `[[row, prop, value], ...]`.
2810 * @param {number|string} column Physical column index / prop name.
2811 * @param {*} value The value to be set at the provided coordinates.
2812 * @param {string} [source] Source of the change as a string.
2813 */
2814 /* eslint-enable jsdoc/require-param */
2815 this.setSourceDataAtCell = function (row, column, value, source) {
2816 const input = setDataInputToArray(row, column, value);
2817 const isThereAnySetSourceListener = this.hasHook('afterSetSourceDataAtCell');
2818 const changesForHook = [];
2819 if (isThereAnySetSourceListener) {
2820 (0, _array.arrayEach)(input, _ref8 => {
2821 let [changeRow, changeProp, changeValue] = _ref8;
2822 changesForHook.push([changeRow, changeProp, dataSource.getAtCell(changeRow, changeProp),
2823 // The previous value.
2824 changeValue]);
2825 });
2826 }
2827 (0, _array.arrayEach)(input, _ref9 => {
2828 let [changeRow, changeProp, changeValue] = _ref9;
2829 dataSource.setAtCell(changeRow, changeProp, changeValue);
2830 });
2831 if (isThereAnySetSourceListener) {
2832 this.runHooks('afterSetSourceDataAtCell', changesForHook, source);
2833 }
2834 this.render();
2835 const activeEditor = instance.getActiveEditor();
2836 if (activeEditor && (0, _mixed.isDefined)(activeEditor.refreshValue)) {
2837 activeEditor.refreshValue();
2838 }
2839 };
2840
2841 /**
2842 * Returns a single row of the data (array or object, depending on what data format you use).
2843 *
2844 * __Note__: This method does not participate in data transformation. If the visual data of the table is reordered,
2845 * sorted or trimmed only physical indexes are correct.
2846 *
2847 * @memberof Core#
2848 * @function getSourceDataAtRow
2849 * @param {number} row Physical row index.
2850 * @returns {Array|object} Single row of data.
2851 */
2852 this.getSourceDataAtRow = function (row) {
2853 return dataSource.getAtRow(row);
2854 };
2855
2856 /**
2857 * Returns a single value from the data source.
2858 *
2859 * @memberof Core#
2860 * @function getSourceDataAtCell
2861 * @param {number} row Physical row index.
2862 * @param {number} column Visual column index.
2863 * @returns {*} Cell data.
2864 */
2865 // TODO: Getting data from `sourceData` should work always on physical indexes.
2866 this.getSourceDataAtCell = function (row, column) {
2867 return dataSource.getAtCell(row, column);
2868 };
2869
2870 /**
2871 * @description
2872 * Returns a single row of the data.
2873 *
2874 * __Note__: If rows were reordered, sorted or trimmed, the currently visible order will be used.
2875 *
2876 * @memberof Core#
2877 * @function getDataAtRow
2878 * @param {number} row Visual row index.
2879 * @returns {Array} Array of row's cell data.
2880 */
2881 this.getDataAtRow = function (row) {
2882 const data = datamap.getRange(instance._createCellCoords(row, 0), instance._createCellCoords(row, this.countCols() - 1), datamap.DESTINATION_RENDERER);
2883 return data[0] || [];
2884 };
2885
2886 /**
2887 * @description
2888 * Returns a data type defined in the Handsontable settings under the `type` key ({@link Options#type}).
2889 * If there are cells with different types in the selected range, it returns `'mixed'`.
2890 *
2891 * __Note__: If data is reordered, sorted or trimmed, the currently visible order will be used.
2892 *
2893 * @memberof Core#
2894 * @function getDataType
2895 * @param {number} rowFrom From visual row index.
2896 * @param {number} columnFrom From visual column index.
2897 * @param {number} rowTo To visual row index.
2898 * @param {number} columnTo To visual column index.
2899 * @returns {string} Cell type (e.q: `'mixed'`, `'text'`, `'numeric'`, `'autocomplete'`).
2900 */
2901 this.getDataType = function (rowFrom, columnFrom, rowTo, columnTo) {
2902 const coords = rowFrom === undefined ? [0, 0, this.countRows(), this.countCols()] : [rowFrom, columnFrom, rowTo, columnTo];
2903 const [rowStart, columnStart] = coords;
2904 let [,, rowEnd, columnEnd] = coords;
2905 let previousType = null;
2906 let currentType = null;
2907 if (rowEnd === undefined) {
2908 rowEnd = rowStart;
2909 }
2910 if (columnEnd === undefined) {
2911 columnEnd = columnStart;
2912 }
2913 let type = 'mixed';
2914 (0, _number.rangeEach)(Math.max(Math.min(rowStart, rowEnd), 0), Math.max(rowStart, rowEnd), row => {
2915 let isTypeEqual = true;
2916 (0, _number.rangeEach)(Math.max(Math.min(columnStart, columnEnd), 0), Math.max(columnStart, columnEnd), column => {
2917 const cellType = this.getCellMeta(row, column);
2918 currentType = cellType.type;
2919 if (previousType) {
2920 isTypeEqual = previousType === currentType;
2921 } else {
2922 previousType = currentType;
2923 }
2924 return isTypeEqual;
2925 });
2926 type = isTypeEqual ? currentType : 'mixed';
2927 return isTypeEqual;
2928 });
2929 return type;
2930 };
2931
2932 /**
2933 * Remove a property defined by the `key` argument from the cell meta object for the provided `row` and `column` coordinates.
2934 *
2935 * @memberof Core#
2936 * @function removeCellMeta
2937 * @param {number} row Visual row index.
2938 * @param {number} column Visual column index.
2939 * @param {string} key Property name.
2940 * @fires Hooks#beforeRemoveCellMeta
2941 * @fires Hooks#afterRemoveCellMeta
2942 */
2943 this.removeCellMeta = function (row, column, key) {
2944 const [physicalRow, physicalColumn] = [this.toPhysicalRow(row), this.toPhysicalColumn(column)];
2945 let cachedValue = metaManager.getCellMetaKeyValue(physicalRow, physicalColumn, key);
2946 const hookResult = instance.runHooks('beforeRemoveCellMeta', row, column, key, cachedValue);
2947 if (hookResult !== false) {
2948 metaManager.removeCellMeta(physicalRow, physicalColumn, key);
2949 instance.runHooks('afterRemoveCellMeta', row, column, key, cachedValue);
2950 }
2951 cachedValue = null;
2952 };
2953
2954 /**
2955 * Removes or adds one or more rows of the cell meta objects to the cell meta collections.
2956 *
2957 * @since 0.30.0
2958 * @memberof Core#
2959 * @function spliceCellsMeta
2960 * @param {number} visualIndex A visual index that specifies at what position to add/remove items.
2961 * @param {number} [deleteAmount=0] The number of items to be removed. If set to 0, no cell meta objects will be removed.
2962 * @param {...object} [cellMetaRows] The new cell meta row objects to be added to the cell meta collection.
2963 */
2964 this.spliceCellsMeta = function (visualIndex) {
2965 let deleteAmount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
2966 for (var _len18 = arguments.length, cellMetaRows = new Array(_len18 > 2 ? _len18 - 2 : 0), _key18 = 2; _key18 < _len18; _key18++) {
2967 cellMetaRows[_key18 - 2] = arguments[_key18];
2968 }
2969 if (cellMetaRows.length > 0 && !Array.isArray(cellMetaRows[0])) {
2970 throw new Error('The 3rd argument (cellMetaRows) has to be passed as an array of cell meta objects array.');
2971 }
2972 if (deleteAmount > 0) {
2973 metaManager.removeRow(this.toPhysicalRow(visualIndex), deleteAmount);
2974 }
2975 if (cellMetaRows.length > 0) {
2976 (0, _array.arrayEach)(cellMetaRows.reverse(), cellMetaRow => {
2977 metaManager.createRow(this.toPhysicalRow(visualIndex));
2978 (0, _array.arrayEach)(cellMetaRow, (cellMeta, columnIndex) => this.setCellMetaObject(visualIndex, columnIndex, cellMeta));
2979 });
2980 }
2981 instance.render();
2982 };
2983
2984 /**
2985 * Set cell meta data object defined by `prop` to the corresponding params `row` and `column`.
2986 *
2987 * @memberof Core#
2988 * @function setCellMetaObject
2989 * @param {number} row Visual row index.
2990 * @param {number} column Visual column index.
2991 * @param {object} prop Meta object.
2992 */
2993 this.setCellMetaObject = function (row, column, prop) {
2994 if (typeof prop === 'object') {
2995 (0, _object.objectEach)(prop, (value, key) => {
2996 this.setCellMeta(row, column, key, value);
2997 });
2998 }
2999 };
3000
3001 /**
3002 * Sets a property defined by the `key` property to the meta object of a cell corresponding to params `row` and `column`.
3003 *
3004 * @memberof Core#
3005 * @function setCellMeta
3006 * @param {number} row Visual row index.
3007 * @param {number} column Visual column index.
3008 * @param {string} key Property name.
3009 * @param {string} value Property value.
3010 * @fires Hooks#beforeSetCellMeta
3011 * @fires Hooks#afterSetCellMeta
3012 */
3013 this.setCellMeta = function (row, column, key, value) {
3014 const allowSetCellMeta = instance.runHooks('beforeSetCellMeta', row, column, key, value);
3015 if (allowSetCellMeta === false) {
3016 return;
3017 }
3018 let physicalRow = row;
3019 let physicalColumn = column;
3020 if (row < this.countRows()) {
3021 physicalRow = this.toPhysicalRow(row);
3022 }
3023 if (column < this.countCols()) {
3024 physicalColumn = this.toPhysicalColumn(column);
3025 }
3026 metaManager.setCellMeta(physicalRow, physicalColumn, key, value);
3027 instance.runHooks('afterSetCellMeta', row, column, key, value);
3028 };
3029
3030 /**
3031 * Get all the cells meta settings at least once generated in the table (in order of cell initialization).
3032 *
3033 * @memberof Core#
3034 * @function getCellsMeta
3035 * @returns {Array} Returns an array of ColumnSettings object instances.
3036 */
3037 this.getCellsMeta = function () {
3038 return metaManager.getCellsMeta();
3039 };
3040
3041 /**
3042 * Returns the cell properties object for the given `row` and `column` coordinates.
3043 *
3044 * @memberof Core#
3045 * @function getCellMeta
3046 * @param {number} row Visual row index.
3047 * @param {number} column Visual column index.
3048 * @returns {object} The cell properties object.
3049 * @fires Hooks#beforeGetCellMeta
3050 * @fires Hooks#afterGetCellMeta
3051 */
3052 this.getCellMeta = function (row, column) {
3053 let physicalRow = this.toPhysicalRow(row);
3054 let physicalColumn = this.toPhysicalColumn(column);
3055 if (physicalRow === null) {
3056 physicalRow = row;
3057 }
3058 if (physicalColumn === null) {
3059 physicalColumn = column;
3060 }
3061 return metaManager.getCellMeta(physicalRow, physicalColumn, {
3062 visualRow: row,
3063 visualColumn: column
3064 });
3065 };
3066
3067 /**
3068 * Returns the meta information for the provided column.
3069 *
3070 * @since 14.5.0
3071 * @memberof Core#
3072 * @function getColumnMeta
3073 * @param {number} column Visual column index.
3074 * @returns {object}
3075 */
3076 this.getColumnMeta = function (column) {
3077 return metaManager.getColumnMeta(this.toPhysicalColumn(column));
3078 };
3079
3080 /**
3081 * Returns an array of cell meta objects for specified physical row index.
3082 *
3083 * @memberof Core#
3084 * @function getCellMetaAtRow
3085 * @param {number} row Physical row index.
3086 * @returns {Array}
3087 */
3088 this.getCellMetaAtRow = function (row) {
3089 return metaManager.getCellsMetaAtRow(row);
3090 };
3091
3092 /**
3093 * Checks if your [data format](@/guides/getting-started/binding-to-data/binding-to-data.md#compatible-data-types)
3094 * and [configuration options](@/guides/getting-started/configuration-options/configuration-options.md)
3095 * allow for changing the number of columns.
3096 *
3097 * Returns `false` when your data is an array of objects,
3098 * or when you use the [`columns`](@/api/options.md#columns) option.
3099 * Otherwise, returns `true`.
3100 *
3101 * @memberof Core#
3102 * @function isColumnModificationAllowed
3103 * @returns {boolean}
3104 */
3105 this.isColumnModificationAllowed = function () {
3106 return !(instance.dataType === 'object' || tableMeta.columns);
3107 };
3108
3109 /**
3110 * Returns the cell renderer function by given `row` and `column` arguments.
3111 *
3112 * @memberof Core#
3113 * @function getCellRenderer
3114 * @param {number|object} rowOrMeta Visual row index or cell meta object (see {@link Core#getCellMeta}).
3115 * @param {number} column Visual column index.
3116 * @returns {Function} Returns the renderer function.
3117 * @example
3118 * ```js
3119 * // Get cell renderer using `row` and `column` coordinates.
3120 * hot.getCellRenderer(1, 1);
3121 * // Get cell renderer using cell meta object.
3122 * hot.getCellRenderer(hot.getCellMeta(1, 1));
3123 * ```
3124 */
3125 this.getCellRenderer = function (rowOrMeta, column) {
3126 const cellRenderer = typeof rowOrMeta === 'number' ? instance.getCellMeta(rowOrMeta, column).renderer : rowOrMeta.renderer;
3127 if (typeof cellRenderer === 'string') {
3128 return (0, _registry2.getRenderer)(cellRenderer);
3129 }
3130 return (0, _mixed.isUndefined)(cellRenderer) ? (0, _registry2.getRenderer)('text') : cellRenderer;
3131 };
3132
3133 /**
3134 * Returns the cell editor class by the provided `row` and `column` arguments.
3135 *
3136 * @memberof Core#
3137 * @function getCellEditor
3138 * @param {number} rowOrMeta Visual row index or cell meta object (see {@link Core#getCellMeta}).
3139 * @param {number} column Visual column index.
3140 * @returns {Function|boolean} Returns the editor class or `false` is cell editor is disabled.
3141 * @example
3142 * ```js
3143 * // Get cell editor class using `row` and `column` coordinates.
3144 * hot.getCellEditor(1, 1);
3145 * // Get cell editor class using cell meta object.
3146 * hot.getCellEditor(hot.getCellMeta(1, 1));
3147 * ```
3148 */
3149 this.getCellEditor = function (rowOrMeta, column) {
3150 const cellEditor = typeof rowOrMeta === 'number' ? instance.getCellMeta(rowOrMeta, column).editor : rowOrMeta.editor;
3151 if (typeof cellEditor === 'string') {
3152 return (0, _registry3.getEditor)(cellEditor);
3153 }
3154 return (0, _mixed.isUndefined)(cellEditor) ? (0, _registry3.getEditor)('text') : cellEditor;
3155 };
3156
3157 /**
3158 * Returns the cell validator by `row` and `column`.
3159 *
3160 * @memberof Core#
3161 * @function getCellValidator
3162 * @param {number|object} rowOrMeta Visual row index or cell meta object (see {@link Core#getCellMeta}).
3163 * @param {number} column Visual column index.
3164 * @returns {Function|RegExp|undefined} The validator function.
3165 * @example
3166 * ```js
3167 * // Get cell validator using `row` and `column` coordinates.
3168 * hot.getCellValidator(1, 1);
3169 * // Get cell validator using cell meta object.
3170 * hot.getCellValidator(hot.getCellMeta(1, 1));
3171 * ```
3172 */
3173 this.getCellValidator = function (rowOrMeta, column) {
3174 const cellValidator = typeof rowOrMeta === 'number' ? instance.getCellMeta(rowOrMeta, column).validator : rowOrMeta.validator;
3175 if (typeof cellValidator === 'string') {
3176 return (0, _registry4.getValidator)(cellValidator);
3177 }
3178 return cellValidator;
3179 };
3180
3181 /**
3182 * Validates every cell in the data set,
3183 * using a [validator function](@/guides/cell-functions/cell-validator/cell-validator.md) configured for each cell.
3184 *
3185 * Doesn't validate cells that are currently [trimmed](@/guides/rows/row-trimming/row-trimming.md),
3186 * [hidden](@/guides/rows/row-hiding/row-hiding.md), or [filtered](@/guides/columns/column-filter/column-filter.md),
3187 * as such cells are not included in the data set until you bring them back again.
3188 *
3189 * After the validation, the `callback` function is fired, with the `valid` argument set to:
3190 * - `true` for valid cells
3191 * - `false` for invalid cells
3192 *
3193 * @memberof Core#
3194 * @function validateCells
3195 * @param {Function} [callback] The callback function.
3196 * @example
3197 * ```js
3198 * hot.validateCells((valid) => {
3199 * if (valid) {
3200 * // ... code for validated cells
3201 * }
3202 * })
3203 * ```
3204 */
3205 this.validateCells = function (callback) {
3206 this._validateCells(callback);
3207 };
3208
3209 /**
3210 * Validates rows using their validator functions and calls callback when finished.
3211 *
3212 * If one of the cells is invalid, the callback will be fired with `'valid'` arguments as `false` - otherwise it
3213 * would equal `true`.
3214 *
3215 * @memberof Core#
3216 * @function validateRows
3217 * @param {Array} [rows] Array of validation target visual row indexes.
3218 * @param {Function} [callback] The callback function.
3219 * @example
3220 * ```js
3221 * hot.validateRows([3, 4, 5], (valid) => {
3222 * if (valid) {
3223 * // ... code for validated rows
3224 * }
3225 * })
3226 * ```
3227 */
3228 this.validateRows = function (rows, callback) {
3229 if (!Array.isArray(rows)) {
3230 throw new Error('validateRows parameter `rows` must be an array');
3231 }
3232 this._validateCells(callback, rows);
3233 };
3234
3235 /**
3236 * Validates columns using their validator functions and calls callback when finished.
3237 *
3238 * If one of the cells is invalid, the callback will be fired with `'valid'` arguments as `false` - otherwise it
3239 * would equal `true`.
3240 *
3241 * @memberof Core#
3242 * @function validateColumns
3243 * @param {Array} [columns] Array of validation target visual columns indexes.
3244 * @param {Function} [callback] The callback function.
3245 * @example
3246 * ```js
3247 * hot.validateColumns([3, 4, 5], (valid) => {
3248 * if (valid) {
3249 * // ... code for validated columns
3250 * }
3251 * })
3252 * ```
3253 */
3254 this.validateColumns = function (columns, callback) {
3255 if (!Array.isArray(columns)) {
3256 throw new Error('validateColumns parameter `columns` must be an array');
3257 }
3258 this._validateCells(callback, undefined, columns);
3259 };
3260
3261 /**
3262 * Validates all cells using their validator functions and calls callback when finished.
3263 *
3264 * If one of the cells is invalid, the callback will be fired with `'valid'` arguments as `false` - otherwise it would equal `true`.
3265 *
3266 * Private use intended.
3267 *
3268 * @private
3269 * @memberof Core#
3270 * @function _validateCells
3271 * @param {Function} [callback] The callback function.
3272 * @param {Array} [rows] An array of validation target visual row indexes.
3273 * @param {Array} [columns] An array of validation target visual column indexes.
3274 */
3275 this._validateCells = function (callback, rows, columns) {
3276 const waitingForValidator = new ValidatorsQueue();
3277 if (callback) {
3278 waitingForValidator.onQueueEmpty = callback;
3279 }
3280 let i = instance.countRows() - 1;
3281 while (i >= 0) {
3282 if (rows !== undefined && rows.indexOf(i) === -1) {
3283 i -= 1;
3284 continue;
3285 }
3286 let j = instance.countCols() - 1;
3287 while (j >= 0) {
3288 if (columns !== undefined && columns.indexOf(j) === -1) {
3289 j -= 1;
3290 continue;
3291 }
3292 waitingForValidator.addValidatorToQueue();
3293 instance.validateCell(instance.getDataAtCell(i, j), instance.getCellMeta(i, j), result => {
3294 if (typeof result !== 'boolean') {
3295 throw new Error('Validation error: result is not boolean');
3296 }
3297 if (result === false) {
3298 waitingForValidator.valid = false;
3299 }
3300 waitingForValidator.removeValidatorFormQueue();
3301 }, 'validateCells');
3302 j -= 1;
3303 }
3304 i -= 1;
3305 }
3306 waitingForValidator.checkIfQueueIsEmpty();
3307 };
3308
3309 /**
3310 * Returns an array of row headers' values (if they are enabled). If param `row` was given, it returns the header of the given row as a string.
3311 *
3312 * @memberof Core#
3313 * @function getRowHeader
3314 * @param {number} [row] Visual row index.
3315 * @fires Hooks#modifyRowHeader
3316 * @returns {Array|string|number} Array of header values / single header value.
3317 */
3318 this.getRowHeader = function (row) {
3319 let rowHeader = tableMeta.rowHeaders;
3320 let physicalRow = row;
3321 if (physicalRow !== undefined) {
3322 physicalRow = instance.runHooks('modifyRowHeader', physicalRow);
3323 }
3324 if (physicalRow === undefined) {
3325 rowHeader = [];
3326 (0, _number.rangeEach)(instance.countRows() - 1, i => {
3327 rowHeader.push(instance.getRowHeader(i));
3328 });
3329 } else if (Array.isArray(rowHeader) && rowHeader[physicalRow] !== undefined) {
3330 rowHeader = rowHeader[physicalRow];
3331 } else if ((0, _function.isFunction)(rowHeader)) {
3332 rowHeader = rowHeader(physicalRow);
3333 } else if (rowHeader && typeof rowHeader !== 'string' && typeof rowHeader !== 'number') {
3334 rowHeader = physicalRow + 1;
3335 }
3336 return rowHeader;
3337 };
3338
3339 /**
3340 * Returns information about if this table is configured to display row headers.
3341 *
3342 * @memberof Core#
3343 * @function hasRowHeaders
3344 * @returns {boolean} `true` if the instance has the row headers enabled, `false` otherwise.
3345 */
3346 this.hasRowHeaders = function () {
3347 return !!tableMeta.rowHeaders;
3348 };
3349
3350 /**
3351 * Returns information about if this table is configured to display column headers.
3352 *
3353 * @memberof Core#
3354 * @function hasColHeaders
3355 * @returns {boolean} `true` if the instance has the column headers enabled, `false` otherwise.
3356 */
3357 this.hasColHeaders = function () {
3358 if (tableMeta.colHeaders !== undefined && tableMeta.colHeaders !== null) {
3359 // Polymer has empty value = null
3360 return !!tableMeta.colHeaders;
3361 }
3362 for (let i = 0, ilen = instance.countCols(); i < ilen; i++) {
3363 if (instance.getColHeader(i)) {
3364 return true;
3365 }
3366 }
3367 return false;
3368 };
3369
3370 /**
3371 * Gets the values of column headers (if column headers are [enabled](@/api/options.md#colheaders)).
3372 *
3373 * To get an array with the values of all
3374 * [bottom-most](@/guides/cell-features/clipboard/clipboard.md#copy-with-headers) column headers,
3375 * call `getColHeader()` with no arguments.
3376 *
3377 * To get the value of the bottom-most header of a specific column, use the `column` parameter.
3378 *
3379 * To get the value of a [specific-level](@/guides/columns/column-groups/column-groups.md) header
3380 * of a specific column, use the `column` and `headerLevel` parameters.
3381 *
3382 * Read more:
3383 * - [Guides: Column groups](@/guides/columns/column-groups/column-groups.md)
3384 * - [Options: `colHeaders`](@/api/options.md#colheaders)
3385 * - [Guides: Copy with headers](@/guides/cell-features/clipboard/clipboard.md#copy-with-headers)
3386 *
3387 * ```js
3388 * // get the contents of all bottom-most column headers
3389 * hot.getColHeader();
3390 *
3391 * // get the contents of the bottom-most header of a specific column
3392 * hot.getColHeader(5);
3393 *
3394 * // get the contents of a specific column header at a specific level
3395 * hot.getColHeader(5, -2);
3396 * ```
3397 *
3398 * @memberof Core#
3399 * @function getColHeader
3400 * @param {number} [column] A visual column index.
3401 * @param {number} [headerLevel=-1] (Since 12.3.0) Header level index. Accepts positive (0 to n)
3402 * and negative (-1 to -n) values. For positive values, 0 points to the
3403 * topmost header. For negative values, -1 points to the bottom-most
3404 * header (the header closest to the cells).
3405 * @fires Hooks#modifyColHeader
3406 * @fires Hooks#modifyColumnHeaderValue
3407 * @returns {Array|string|number} Column header values.
3408 */
3409 this.getColHeader = function (column) {
3410 let headerLevel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
3411 const columnIndex = instance.runHooks('modifyColHeader', column);
3412 if (columnIndex === undefined) {
3413 const out = [];
3414 const ilen = instance.countCols();
3415 for (let i = 0; i < ilen; i++) {
3416 out.push(instance.getColHeader(i));
3417 }
3418 return out;
3419 }
3420 let result = tableMeta.colHeaders;
3421 const translateVisualIndexToColumns = function (visualColumnIndex) {
3422 const arr = [];
3423 const columnsLen = instance.countCols();
3424 let index = 0;
3425 for (; index < columnsLen; index++) {
3426 if ((0, _function.isFunction)(tableMeta.columns) && tableMeta.columns(index)) {
3427 arr.push(index);
3428 }
3429 }
3430 return arr[visualColumnIndex];
3431 };
3432 const physicalColumn = instance.toPhysicalColumn(columnIndex);
3433 const prop = translateVisualIndexToColumns(physicalColumn);
3434 if (tableMeta.colHeaders === false) {
3435 result = null;
3436 } else if (tableMeta.columns && (0, _function.isFunction)(tableMeta.columns) && tableMeta.columns(prop) && tableMeta.columns(prop).title) {
3437 result = tableMeta.columns(prop).title;
3438 } else if (tableMeta.columns && tableMeta.columns[physicalColumn] && tableMeta.columns[physicalColumn].title) {
3439 result = tableMeta.columns[physicalColumn].title;
3440 } else if (Array.isArray(tableMeta.colHeaders) && tableMeta.colHeaders[physicalColumn] !== undefined) {
3441 result = tableMeta.colHeaders[physicalColumn];
3442 } else if ((0, _function.isFunction)(tableMeta.colHeaders)) {
3443 result = tableMeta.colHeaders(physicalColumn);
3444 } else if (tableMeta.colHeaders && typeof tableMeta.colHeaders !== 'string' && typeof tableMeta.colHeaders !== 'number') {
3445 result = (0, _data.spreadsheetColumnLabel)(columnIndex); // see #1458
3446 }
3447 result = instance.runHooks('modifyColumnHeaderValue', result, column, headerLevel);
3448 return result;
3449 };
3450
3451 /**
3452 * Return column width from settings (no guessing). Private use intended.
3453 *
3454 * @private
3455 * @memberof Core#
3456 * @function _getColWidthFromSettings
3457 * @param {number} col Visual col index.
3458 * @returns {number}
3459 */
3460 this._getColWidthFromSettings = function (col) {
3461 let width;
3462
3463 // We currently don't support cell meta objects for headers (negative values)
3464 if (col >= 0) {
3465 const cellProperties = instance.getCellMeta(0, col);
3466 width = cellProperties.width;
3467 }
3468 if (width === undefined || width === tableMeta.width) {
3469 width = tableMeta.colWidths;
3470 }
3471 if (width !== undefined && width !== null) {
3472 switch (typeof width) {
3473 case 'object':
3474 // array
3475 width = width[col];
3476 break;
3477 case 'function':
3478 width = width(col);
3479 break;
3480 default:
3481 break;
3482 }
3483 if (typeof width === 'string') {
3484 width = parseInt(width, 10);
3485 }
3486 }
3487 return width;
3488 };
3489
3490 /**
3491 * Returns the width of the requested column.
3492 *
3493 * @memberof Core#
3494 * @function getColWidth
3495 * @param {number} column Visual column index.
3496 * @returns {number} Column width.
3497 * @fires Hooks#modifyColWidth
3498 */
3499 this.getColWidth = function (column) {
3500 let width = instance._getColWidthFromSettings(column);
3501 width = instance.runHooks('modifyColWidth', width, column);
3502 if (width === undefined) {
3503 width = _src.DEFAULT_COLUMN_WIDTH;
3504 }
3505 return width;
3506 };
3507
3508 /**
3509 * Return row height from settings (no guessing). Private use intended.
3510 *
3511 * @private
3512 * @memberof Core#
3513 * @function _getRowHeightFromSettings
3514 * @param {number} row Visual row index.
3515 * @returns {number}
3516 */
3517 this._getRowHeightFromSettings = function (row) {
3518 let height = tableMeta.rowHeights;
3519 if (height !== undefined && height !== null) {
3520 switch (typeof height) {
3521 case 'object':
3522 // array
3523 height = height[row];
3524 break;
3525 case 'function':
3526 height = height(row);
3527 break;
3528 default:
3529 break;
3530 }
3531 if (typeof height === 'string') {
3532 height = parseInt(height, 10);
3533 }
3534 }
3535 return height;
3536 };
3537
3538 /**
3539 * Returns a row's height, as recognized by Handsontable.
3540 *
3541 * Depending on your configuration, the method returns (in order of priority):
3542 * 1. The row height set by the [`ManualRowResize`](@/api/manualRowResize.md) plugin
3543 * (if the plugin is enabled).
3544 * 2. The row height set by the [`rowHeights`](@/api/options.md#rowheights) configuration option
3545 * (if the option is set).
3546 * 3. The row height as measured in the DOM by the [`AutoRowSize`](@/api/autoRowSize.md) plugin
3547 * (if the plugin is enabled).
3548 * 4. `undefined`, if neither [`ManualRowResize`](@/api/manualRowResize.md),
3549 * nor [`rowHeights`](@/api/options.md#rowheights),
3550 * nor [`AutoRowSize`](@/api/autoRowSize.md) is used.
3551 *
3552 * The height returned includes 1 px of the row's bottom border.
3553 *
3554 * Mind that this method is different from the
3555 * [`getRowHeight()`](@/api/autoRowSize.md#getrowheight) method
3556 * of the [`AutoRowSize`](@/api/autoRowSize.md) plugin.
3557 *
3558 * @memberof Core#
3559 * @function getRowHeight
3560 * @param {number} row A visual row index.
3561 * @returns {number|undefined} The height of the specified row, in pixels.
3562 * @fires Hooks#modifyRowHeight
3563 */
3564 this.getRowHeight = function (row) {
3565 let height = instance._getRowHeightFromSettings(row);
3566 height = instance.runHooks('modifyRowHeight', height, row);
3567 return height;
3568 };
3569
3570 /**
3571 * Returns the total number of rows in the data source.
3572 *
3573 * @memberof Core#
3574 * @function countSourceRows
3575 * @returns {number} Total number of rows.
3576 */
3577 this.countSourceRows = function () {
3578 return dataSource.countRows();
3579 };
3580
3581 /**
3582 * Returns the total number of columns in the data source.
3583 *
3584 * @memberof Core#
3585 * @function countSourceCols
3586 * @returns {number} Total number of columns.
3587 */
3588 this.countSourceCols = function () {
3589 return dataSource.countFirstRowKeys();
3590 };
3591
3592 /**
3593 * Returns the total number of visual rows in the table.
3594 *
3595 * @memberof Core#
3596 * @function countRows
3597 * @returns {number} Total number of rows.
3598 */
3599 this.countRows = function () {
3600 return datamap.getLength();
3601 };
3602
3603 /**
3604 * Returns the total number of visible columns in the table.
3605 *
3606 * @memberof Core#
3607 * @function countCols
3608 * @returns {number} Total number of columns.
3609 */
3610 this.countCols = function () {
3611 const maxCols = tableMeta.maxCols;
3612 const dataLen = this.columnIndexMapper.getNotTrimmedIndexesLength();
3613 return Math.min(maxCols, dataLen);
3614 };
3615
3616 /**
3617 * Returns the number of rendered rows including rows that are partially or fully rendered
3618 * outside the table viewport.
3619 *
3620 * @memberof Core#
3621 * @function countRenderedRows
3622 * @returns {number} Returns -1 if table is not visible.
3623 */
3624 this.countRenderedRows = function () {
3625 return instance.view._wt.drawn ? instance.view._wt.wtTable.getRenderedRowsCount() : -1;
3626 };
3627
3628 /**
3629 * Returns the number of rendered rows that are only visible in the table viewport.
3630 * The rows that are partially visible are not counted.
3631 *
3632 * @memberof Core#
3633 * @function countVisibleRows
3634 * @returns {number} Number of visible rows or -1.
3635 */
3636 this.countVisibleRows = function () {
3637 return instance.view._wt.drawn ? instance.view._wt.wtTable.getVisibleRowsCount() : -1;
3638 };
3639
3640 /**
3641 * Returns the number of rendered rows including columns that are partially or fully rendered
3642 * outside the table viewport.
3643 *
3644 * @memberof Core#
3645 * @function countRenderedCols
3646 * @returns {number} Returns -1 if table is not visible.
3647 */
3648 this.countRenderedCols = function () {
3649 return instance.view._wt.drawn ? instance.view._wt.wtTable.getRenderedColumnsCount() : -1;
3650 };
3651
3652 /**
3653 * Returns the number of rendered columns that are only visible in the table viewport.
3654 * The columns that are partially visible are not counted.
3655 *
3656 * @memberof Core#
3657 * @function countVisibleCols
3658 * @returns {number} Number of visible columns or -1.
3659 */
3660 this.countVisibleCols = function () {
3661 return instance.view._wt.drawn ? instance.view._wt.wtTable.getVisibleColumnsCount() : -1;
3662 };
3663
3664 /**
3665 * Returns the number of rendered row headers.
3666 *
3667 * @since 14.0.0
3668 * @memberof Core#
3669 * @function countRowHeaders
3670 * @returns {number} Number of row headers.
3671 */
3672 this.countRowHeaders = function () {
3673 return this.view.getRowHeadersCount();
3674 };
3675
3676 /**
3677 * Returns the number of rendered column headers.
3678 *
3679 * @since 14.0.0
3680 * @memberof Core#
3681 * @function countColHeaders
3682 * @returns {number} Number of column headers.
3683 */
3684 this.countColHeaders = function () {
3685 return this.view.getColumnHeadersCount();
3686 };
3687
3688 /**
3689 * Returns the number of empty rows. If the optional ending parameter is `true`, returns the
3690 * number of empty rows at the bottom of the table.
3691 *
3692 * @memberof Core#
3693 * @function countEmptyRows
3694 * @param {boolean} [ending=false] If `true`, will only count empty rows at the end of the data source.
3695 * @returns {number} Count empty rows.
3696 */
3697 this.countEmptyRows = function () {
3698 let ending = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
3699 let emptyRows = 0;
3700 (0, _number.rangeEachReverse)(instance.countRows() - 1, visualIndex => {
3701 if (instance.isEmptyRow(visualIndex)) {
3702 emptyRows += 1;
3703 } else if (ending === true) {
3704 return false;
3705 }
3706 });
3707 return emptyRows;
3708 };
3709
3710 /**
3711 * Returns the number of empty columns. If the optional ending parameter is `true`, returns the number of empty
3712 * columns at right hand edge of the table.
3713 *
3714 * @memberof Core#
3715 * @function countEmptyCols
3716 * @param {boolean} [ending=false] If `true`, will only count empty columns at the end of the data source row.
3717 * @returns {number} Count empty cols.
3718 */
3719 this.countEmptyCols = function () {
3720 let ending = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
3721 let emptyColumns = 0;
3722 (0, _number.rangeEachReverse)(instance.countCols() - 1, visualIndex => {
3723 if (instance.isEmptyCol(visualIndex)) {
3724 emptyColumns += 1;
3725 } else if (ending === true) {
3726 return false;
3727 }
3728 });
3729 return emptyColumns;
3730 };
3731
3732 /**
3733 * Check if all cells in the row declared by the `row` argument are empty.
3734 *
3735 * @memberof Core#
3736 * @function isEmptyRow
3737 * @param {number} row Visual row index.
3738 * @returns {boolean} `true` if the row at the given `row` is empty, `false` otherwise.
3739 */
3740 this.isEmptyRow = function (row) {
3741 return tableMeta.isEmptyRow.call(instance, row);
3742 };
3743
3744 /**
3745 * Check if all cells in the the column declared by the `column` argument are empty.
3746 *
3747 * @memberof Core#
3748 * @function isEmptyCol
3749 * @param {number} column Column index.
3750 * @returns {boolean} `true` if the column at the given `col` is empty, `false` otherwise.
3751 */
3752 this.isEmptyCol = function (column) {
3753 return tableMeta.isEmptyCol.call(instance, column);
3754 };
3755
3756 /**
3757 * Select a single cell, or a single range of adjacent cells.
3758 *
3759 * To select a cell, pass its visual row and column indexes, for example: `selectCell(2, 4)`.
3760 *
3761 * To select a range, pass the visual indexes of the first and last cell in the range, for example: `selectCell(2, 4, 3, 5)`.
3762 *
3763 * If your columns have properties, you can pass those properties' values instead of column indexes, for example: `selectCell(2, 'first_name')`.
3764 *
3765 * By default, `selectCell()` also:
3766 * - Scrolls the viewport to the newly-selected cells.
3767 * - Switches the keyboard focus to Handsontable (by calling Handsontable's [`listen()`](#listen) method).
3768 *
3769 * @example
3770 * ```js
3771 * // select a single cell
3772 * hot.selectCell(2, 4);
3773 *
3774 * // select a range of cells
3775 * hot.selectCell(2, 4, 3, 5);
3776 *
3777 * // select a single cell, using a column property
3778 * hot.selectCell(2, 'first_name');
3779 *
3780 * // select a range of cells, using column properties
3781 * hot.selectCell(2, 'first_name', 3, 'last_name');
3782 *
3783 * // select a range of cells, without scrolling to them
3784 * hot.selectCell(2, 4, 3, 5, false);
3785 *
3786 * // select a range of cells, without switching the keyboard focus to Handsontable
3787 * hot.selectCell(2, 4, 3, 5, null, false);
3788 * ```
3789 *
3790 * @memberof Core#
3791 * @function selectCell
3792 * @param {number} row A visual row index.
3793 * @param {number|string} column A visual column index (`number`), or a column property's value (`string`).
3794 * @param {number} [endRow] If selecting a range: the visual row index of the last cell in the range.
3795 * @param {number|string} [endColumn] If selecting a range: the visual column index (or a column property's value) of the last cell in the range.
3796 * @param {boolean} [scrollToCell=true] `true`: scroll the viewport to the newly-selected cells. `false`: keep the previous viewport.
3797 * @param {boolean} [changeListener=true] `true`: switch the keyboard focus to Handsontable. `false`: keep the previous keyboard focus.
3798 * @returns {boolean} `true`: the selection was successful, `false`: the selection failed.
3799 */
3800 this.selectCell = function (row, column, endRow, endColumn) {
3801 let scrollToCell = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
3802 let changeListener = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true;
3803 if ((0, _mixed.isUndefined)(row) || (0, _mixed.isUndefined)(column)) {
3804 return false;
3805 }
3806 return this.selectCells([[row, column, endRow, endColumn]], scrollToCell, changeListener);
3807 };
3808
3809 /**
3810 * Select multiple cells or ranges of cells, adjacent or non-adjacent.
3811 *
3812 * You can pass one of the below:
3813 * - An array of arrays (which matches the output of Handsontable's [`getSelected()`](#getselected) method).
3814 * - An array of [`CellRange`](@/api/cellRange.md) objects (which matches the output of Handsontable's [`getSelectedRange()`](#getselectedrange) method).
3815 *
3816 * To select multiple cells, pass the visual row and column indexes of each cell, for example: `hot.selectCells([[1, 1], [5, 5]])`.
3817 *
3818 * To select multiple ranges, pass the visual indexes of the first and last cell in each range, for example: `hot.selectCells([[1, 1, 2, 2], [6, 2, 0, 2]])`.
3819 *
3820 * If your columns have properties, you can pass those properties' values instead of column indexes, for example: `hot.selectCells([[1, 'first_name'], [5, 'last_name']])`.
3821 *
3822 * By default, `selectCell()` also:
3823 * - Scrolls the viewport to the newly-selected cells.
3824 * - Switches the keyboard focus to Handsontable (by calling Handsontable's [`listen()`](#listen) method).
3825 *
3826 * @example
3827 * ```js
3828 * // select non-adjacent cells
3829 * hot.selectCells([[1, 1], [5, 5], [10, 10]]);
3830 *
3831 * // select non-adjacent ranges of cells
3832 * hot.selectCells([[1, 1, 2, 2], [10, 10, 20, 20]]);
3833 *
3834 * // select cells and ranges of cells
3835 * hot.selectCells([[1, 1, 2, 2], [3, 3], [6, 2, 0, 2]]);
3836 *
3837 * // select cells, using column properties
3838 * hot.selectCells([[1, 'id', 2, 'first_name'], [3, 'full_name'], [6, 'last_name', 0, 'first_name']]);
3839 *
3840 * // select multiple ranges, using an array of `CellRange` objects
3841 * const selected = hot.getSelectedRange();
3842 *
3843 * selected[0].from.row = 0;
3844 * selected[0].from.col = 0;
3845 * selected[0].to.row = 5;
3846 * selected[0].to.col = 5;
3847 *
3848 * selected[1].from.row = 10;
3849 * selected[1].from.col = 10;
3850 * selected[1].to.row = 20;
3851 * selected[1].to.col = 20;
3852 *
3853 * hot.selectCells(selected);
3854 * ```
3855 *
3856 * @memberof Core#
3857 * @since 0.38.0
3858 * @function selectCells
3859 * @param {Array[]|CellRange[]} coords Visual coordinates,
3860 * passed either as an array of arrays (`[[rowStart, columnStart, rowEnd, columnEnd], ...]`)
3861 * or as an array of [`CellRange`](@/api/cellRange.md) objects.
3862 * @param {boolean} [scrollToCell=true] `true`: scroll the viewport to the newly-selected cells. `false`: keep the previous viewport.
3863 * @param {boolean} [changeListener=true] `true`: switch the keyboard focus to Handsontable. `false`: keep the previous keyboard focus.
3864 * @returns {boolean} `true`: the selection was successful, `false`: the selection failed.
3865 */
3866 this.selectCells = function () {
3867 let coords = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [[]];
3868 let scrollToCell = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
3869 let changeListener = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
3870 if (scrollToCell === false) {
3871 viewportScroller.suspend();
3872 }
3873 const wasSelected = selection.selectCells(coords);
3874 if (wasSelected && changeListener) {
3875 instance.listen();
3876 }
3877 viewportScroller.resume();
3878 return wasSelected;
3879 };
3880
3881 /**
3882 * Select column specified by `startColumn` visual index, column property or a range of columns finishing at `endColumn`.
3883 *
3884 * @example
3885 * ```js
3886 * // Select column using visual index.
3887 * hot.selectColumns(1);
3888 * // Select column using column property.
3889 * hot.selectColumns('id');
3890 * // Select range of columns using visual indexes.
3891 * hot.selectColumns(1, 4);
3892 * // Select range of columns using visual indexes and mark the first header as highlighted.
3893 * hot.selectColumns(1, 2, -1);
3894 * // Select range of columns using visual indexes and mark the second cell as highlighted.
3895 * hot.selectColumns(2, 1, 1);
3896 * // Select range of columns using visual indexes and move the focus position somewhere in the middle of the range.
3897 * hot.selectColumns(2, 5, { row: 2, col: 3 });
3898 * // Select range of columns using column properties.
3899 * hot.selectColumns('id', 'last_name');
3900 * ```
3901 *
3902 * @memberof Core#
3903 * @since 0.38.0
3904 * @function selectColumns
3905 * @param {number} startColumn The visual column index from which the selection starts.
3906 * @param {number} [endColumn=startColumn] The visual column index to which the selection finishes. If `endColumn`
3907 * is not defined the column defined by `startColumn` will be selected.
3908 * @param {number | { row: number, col: number } | CellCoords} [focusPosition=0] The argument allows changing the cell/header focus
3909 * position. The value can take visual row index from -N to N, where negative values point to the headers and positive
3910 * values point to the cell range. An object with `row` and `col` properties also can be passed to change the focus
3911 * position horizontally.
3912 * @returns {boolean} `true` if selection was successful, `false` otherwise.
3913 */
3914 this.selectColumns = function (startColumn) {
3915 let endColumn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startColumn;
3916 let focusPosition = arguments.length > 2 ? arguments[2] : undefined;
3917 return selection.selectColumns(startColumn, endColumn, focusPosition);
3918 };
3919
3920 /**
3921 * Select row specified by `startRow` visual index or a range of rows finishing at `endRow`.
3922 *
3923 * @example
3924 * ```js
3925 * // Select row using visual index.
3926 * hot.selectRows(1);
3927 * // select a range of rows, using visual indexes.
3928 * hot.selectRows(1, 4);
3929 * // select a range of rows, using visual indexes, and mark the header as highlighted.
3930 * hot.selectRows(1, 2, -1);
3931 * // Select range of rows using visual indexes and mark the second cell as highlighted.
3932 * hot.selectRows(2, 1, 1);
3933 * // Select range of rows using visual indexes and move the focus position somewhere in the middle of the range.
3934 * hot.selectRows(2, 5, { row: 2, col: 3 });
3935 * ```
3936 *
3937 * @memberof Core#
3938 * @since 0.38.0
3939 * @function selectRows
3940 * @param {number} startRow The visual row index from which the selection starts.
3941 * @param {number} [endRow=startRow] The visual row index to which the selection finishes. If `endRow`
3942 * is not defined the row defined by `startRow` will be selected.
3943 * @param {number | { row: number, col: number } | CellCoords} [focusPosition=0] The argument allows changing the cell/header focus
3944 * position. The value can take visual row index from -N to N, where negative values point to the headers and positive
3945 * values point to the cell range. An object with `row` and `col` properties also can be passed to change the focus
3946 * position vertically.
3947 * @returns {boolean} `true` if selection was successful, `false` otherwise.
3948 */
3949 this.selectRows = function (startRow) {
3950 let endRow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : startRow;
3951 let focusPosition = arguments.length > 2 ? arguments[2] : undefined;
3952 return selection.selectRows(startRow, endRow, focusPosition);
3953 };
3954
3955 /**
3956 * Deselects the current cell selection on the table.
3957 *
3958 * @memberof Core#
3959 * @function deselectCell
3960 */
3961 this.deselectCell = function () {
3962 selection.deselect();
3963 };
3964
3965 /**
3966 * Select all cells in the table excluding headers and corner elements.
3967 *
3968 * The previous selection is overwritten.
3969 *
3970 * ```js
3971 * // Select all cells in the table along with row headers, including all headers and the corner cell.
3972 * // Doesn't select column headers and corner elements.
3973 * hot.selectAll();
3974 *
3975 * // Select all cells in the table, including row headers but excluding the corner cell and column headers.
3976 * hot.selectAll(true, false);
3977 *
3978 * // Select all cells in the table, including all headers and the corner cell, but move the focus.
3979 * // highlight to position 2, 1
3980 * hot.selectAll(-2, -1, {
3981 * focusPosition: { row: 2, col: 1 }
3982 * });
3983 *
3984 * // Select all cells in the table, without headers and corner elements.
3985 * hot.selectAll(false);
3986 * ```
3987 *
3988 * @since 0.38.2
3989 * @memberof Core#
3990 * @function selectAll
3991 * @param {boolean} [includeRowHeaders=false] `true` If the selection should include the row headers,
3992 * `false` otherwise.
3993 * @param {boolean} [includeColumnHeaders=false] `true` If the selection should include the column
3994 * headers, `false` otherwise.
3995 *
3996 * @param {object} [options] Additional object with options. Since 14.0.0
3997 * @param {{row: number, col: number} | boolean} [options.focusPosition] The argument allows changing the cell/header
3998 * focus position. The value takes an object with a `row` and `col` properties from -N to N, where
3999 * negative values point to the headers and positive values point to the cell range. If `false`, the focus
4000 * position won't be changed. Example:
4001 * ```js
4002 * hot.selectAll(0, 0, {
4003 * focusPosition: { row: 0, col: 1 },
4004 * disableHeadersHighlight: true
4005 * })
4006 * ```
4007 *
4008 * @param {boolean} [options.disableHeadersHighlight] If `true`, disables highlighting the headers even when
4009 * the logical coordinates points on them.
4010 */
4011 this.selectAll = function () {
4012 let includeRowHeaders = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4013 let includeColumnHeaders = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : includeRowHeaders;
4014 let options = arguments.length > 2 ? arguments[2] : undefined;
4015 viewportScroller.skipNextScrollCycle();
4016 selection.selectAll(includeRowHeaders, includeColumnHeaders, options);
4017 };
4018 const getIndexToScroll = (indexMapper, visualIndex) => {
4019 // Looking for a visual index on the right and then (when not found) on the left.
4020 return indexMapper.getNearestNotHiddenIndex(visualIndex, 1, true);
4021 };
4022
4023 /**
4024 * Scroll viewport to coordinates specified by the `row` and/or `col` object properties.
4025 *
4026 * ```js
4027 * // scroll the viewport to the visual row index (leave the horizontal scroll untouched)
4028 * hot.scrollViewportTo({ row: 50 });
4029 *
4030 * // scroll the viewport to the passed coordinates so that the cell at 50, 50 will be snapped to
4031 * // the bottom-end table's edge.
4032 * hot.scrollViewportTo({
4033 * row: 50,
4034 * col: 50,
4035 * verticalSnap: 'bottom',
4036 * horizontalSnap: 'end',
4037 * });
4038 * ```
4039 *
4040 * @memberof Core#
4041 * @function scrollViewportTo
4042 * @param {object} options A dictionary containing the following parameters:
4043 * @param {number} [options.row] Specifies the number of visual rows along the Y axis to scroll the viewport.
4044 * @param {number} [options.col] Specifies the number of visual columns along the X axis to scroll the viewport.
4045 * @param {'top' | 'bottom'} [options.verticalSnap] Determines to which edge of the table the viewport will be scrolled based on the passed coordinates.
4046 * This option is a string which must take one of the following values:
4047 * - `top`: The viewport will be scrolled to a row in such a way that it will be positioned on the top of the viewport;
4048 * - `bottom`: The viewport will be scrolled to a row in such a way that it will be positioned on the bottom of the viewport;
4049 * - If the property is not defined the vertical auto-snapping is enabled. Depending on where the viewport is scrolled from, a row will
4050 * be positioned at the top or bottom of the viewport.
4051 * @param {'start' | 'end'} [options.horizontalSnap] Determines to which edge of the table the viewport will be scrolled based on the passed coordinates.
4052 * This option is a string which must take one of the following values:
4053 * - `start`: The viewport will be scrolled to a column in such a way that it will be positioned on the start (left edge or right, if the layout direction is set to `rtl`) of the viewport;
4054 * - `end`: The viewport will be scrolled to a column in such a way that it will be positioned on the end (right edge or left, if the layout direction is set to `rtl`) of the viewport;
4055 * - If the property is not defined the horizontal auto-snapping is enabled. Depending on where the viewport is scrolled from, a column will
4056 * be positioned at the start or end of the viewport.
4057 * @param {boolean} [options.considerHiddenIndexes=true] If `true`, we handle visual indexes, otherwise we handle only indexes which
4058 * may be rendered when they are in the viewport (we don't consider hidden indexes as they aren't rendered).
4059 * @returns {boolean} `true` if viewport was scrolled, `false` otherwise.
4060 */
4061 this.scrollViewportTo = function (options) {
4062 var _options;
4063 // Support for backward compatibility arguments: (row, col, snapToBottom, snapToRight, considerHiddenIndexes)
4064 if (typeof options === 'number') {
4065 var _arguments$;
4066 /* eslint-disable prefer-rest-params */
4067 options = {
4068 row: arguments[0],
4069 col: arguments[1],
4070 verticalSnap: arguments[2] ? 'bottom' : 'top',
4071 horizontalSnap: arguments[3] ? 'end' : 'start',
4072 considerHiddenIndexes: (_arguments$ = arguments[4]) !== null && _arguments$ !== void 0 ? _arguments$ : true
4073 };
4074 /* eslint-enable prefer-rest-params */
4075 }
4076 const {
4077 row,
4078 col,
4079 verticalSnap,
4080 horizontalSnap,
4081 considerHiddenIndexes
4082 } = (_options = options) !== null && _options !== void 0 ? _options : {};
4083 let snapToTop;
4084 let snapToBottom;
4085 let snapToInlineStart;
4086 let snapToInlineEnd;
4087 if (verticalSnap !== undefined) {
4088 snapToTop = verticalSnap === 'top';
4089 snapToBottom = !snapToTop;
4090 }
4091 if (horizontalSnap !== undefined) {
4092 snapToInlineStart = horizontalSnap === 'start';
4093 snapToInlineEnd = !snapToInlineStart;
4094 }
4095 let renderableRow = row;
4096 let renderableColumn = col;
4097 if (considerHiddenIndexes === undefined || considerHiddenIndexes) {
4098 const isValidRowGrid = Number.isInteger(row) && row >= 0;
4099 const isValidColumnGrid = Number.isInteger(col) && col >= 0;
4100 const visualRowToScroll = isValidRowGrid ? getIndexToScroll(this.rowIndexMapper, row) : undefined;
4101 const visualColumnToScroll = isValidColumnGrid ? getIndexToScroll(this.columnIndexMapper, col) : undefined;
4102 if (visualRowToScroll === null || visualColumnToScroll === null) {
4103 return false;
4104 }
4105 renderableRow = isValidRowGrid ? instance.rowIndexMapper.getRenderableFromVisualIndex(visualRowToScroll) : row;
4106 renderableColumn = isValidColumnGrid ? instance.columnIndexMapper.getRenderableFromVisualIndex(visualColumnToScroll) : col;
4107 }
4108 const isRowInteger = Number.isInteger(renderableRow);
4109 const isColumnInteger = Number.isInteger(renderableColumn);
4110 if (isRowInteger && renderableRow >= 0 && isColumnInteger && renderableColumn >= 0) {
4111 return instance.view.scrollViewport(instance._createCellCoords(renderableRow, renderableColumn), snapToTop, snapToInlineEnd, snapToBottom, snapToInlineStart);
4112 }
4113 if (isRowInteger && renderableRow >= 0 && (isColumnInteger && renderableColumn < 0 || !isColumnInteger)) {
4114 return instance.view.scrollViewportVertically(renderableRow, snapToTop, snapToBottom);
4115 }
4116 if (isColumnInteger && renderableColumn >= 0 && (isRowInteger && renderableRow < 0 || !isRowInteger)) {
4117 return instance.view.scrollViewportHorizontally(renderableColumn, snapToInlineEnd, snapToInlineStart);
4118 }
4119 return false;
4120 };
4121
4122 /**
4123 * Scrolls the viewport to coordinates specified by the currently focused cell.
4124 *
4125 * @since 14.0.0
4126 * @memberof Core#
4127 * @fires Hooks#afterScroll
4128 * @function scrollToFocusedCell
4129 * @param {Function} callback The callback function to call after the viewport is scrolled.
4130 */
4131 this.scrollToFocusedCell = function () {
4132 let callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : () => {};
4133 if (!this.selection.isSelected()) {
4134 return;
4135 }
4136 this.addHookOnce('afterScroll', callback);
4137 const {
4138 highlight
4139 } = this.getSelectedRangeLast();
4140 const isScrolled = this.scrollViewportTo(highlight.toObject());
4141 if (isScrolled) {
4142 this.view.render();
4143 } else {
4144 this.removeHook('afterScroll', callback);
4145 this._registerImmediate(() => callback());
4146 }
4147 };
4148
4149 /**
4150 * Removes the table from the DOM and destroys the instance of the Handsontable.
4151 *
4152 * @memberof Core#
4153 * @function destroy
4154 * @fires Hooks#afterDestroy
4155 */
4156 this.destroy = function () {
4157 instance._clearTimeouts();
4158 instance._clearImmediates();
4159 if (instance.view) {
4160 // in case HT is destroyed before initialization has finished
4161 instance.view.destroy();
4162 }
4163 if (dataSource) {
4164 dataSource.destroy();
4165 }
4166 dataSource = null;
4167 this.getShortcutManager().destroy();
4168 metaManager.clearCache();
4169 foreignHotInstances.delete(this.guid);
4170 if ((0, _rootInstance.isRootInstance)(instance)) {
4171 const licenseInfo = this.rootDocument.querySelector('.hot-display-license-info');
4172 if (licenseInfo) {
4173 licenseInfo.parentNode.removeChild(licenseInfo);
4174 }
4175 }
4176 (0, _element.empty)(instance.rootElement);
4177 eventManager.destroy();
4178 if (editorManager) {
4179 editorManager.destroy();
4180 }
4181
4182 // The plugin's `destroy` method is called as a consequence and it should handle
4183 // unregistration of plugin's maps. Some unregistered maps reset the cache.
4184 instance.batchExecution(() => {
4185 instance.rowIndexMapper.unregisterAll();
4186 instance.columnIndexMapper.unregisterAll();
4187 pluginsRegistry.getItems().forEach(_ref10 => {
4188 let [, plugin] = _ref10;
4189 plugin.destroy();
4190 });
4191 pluginsRegistry.clear();
4192 instance.runHooks('afterDestroy');
4193 }, true);
4194 _pluginHooks.default.getSingleton().destroy(instance);
4195 (0, _object.objectEach)(instance, (property, key, obj) => {
4196 // replace instance methods with post mortem
4197 if ((0, _function.isFunction)(property)) {
4198 obj[key] = postMortem(key);
4199 } else if (key !== 'guid') {
4200 // replace instance properties with null (restores memory)
4201 // it should not be necessary but this prevents a memory leak side effects that show itself in Jasmine tests
4202 obj[key] = null;
4203 }
4204 });
4205 instance.isDestroyed = true;
4206
4207 // replace private properties with null (restores memory)
4208 // it should not be necessary but this prevents a memory leak side effects that show itself in Jasmine tests
4209 if (datamap) {
4210 datamap.destroy();
4211 }
4212 datamap = null;
4213 grid = null;
4214 selection = null;
4215 editorManager = null;
4216 instance = null;
4217 };
4218
4219 /**
4220 * Replacement for all methods after the Handsontable was destroyed.
4221 *
4222 * @private
4223 * @param {string} method The method name.
4224 * @returns {Function}
4225 */
4226 function postMortem(method) {
4227 return () => {
4228 throw new Error(`The "${method}" method cannot be called because this Handsontable instance has been destroyed`);
4229 };
4230 }
4231
4232 /**
4233 * Returns the active editor class instance.
4234 *
4235 * @memberof Core#
4236 * @function getActiveEditor
4237 * @returns {BaseEditor} The active editor instance.
4238 */
4239 this.getActiveEditor = function () {
4240 return editorManager.getActiveEditor();
4241 };
4242
4243 /**
4244 * Returns the first rendered row in the DOM (usually, it is not visible in the table's viewport).
4245 *
4246 * @since 14.6.0
4247 * @memberof Core#
4248 * @function getFirstRenderedVisibleRow
4249 * @returns {number | null}
4250 */
4251 this.getFirstRenderedVisibleRow = function () {
4252 return instance.view.getFirstRenderedVisibleRow();
4253 };
4254
4255 /**
4256 * Returns the last rendered row in the DOM (usually, it is not visible in the table's viewport).
4257 *
4258 * @since 14.6.0
4259 * @memberof Core#
4260 * @function getLastRenderedVisibleRow
4261 * @returns {number | null}
4262 */
4263 this.getLastRenderedVisibleRow = function () {
4264 return instance.view.getLastRenderedVisibleRow();
4265 };
4266
4267 /**
4268 * Returns the first rendered column in the DOM (usually, it is not visible in the table's viewport).
4269 *
4270 * @since 14.6.0
4271 * @memberof Core#
4272 * @function getFirstRenderedVisibleColumn
4273 * @returns {number | null}
4274 */
4275 this.getFirstRenderedVisibleColumn = function () {
4276 return instance.view.getFirstRenderedVisibleColumn();
4277 };
4278
4279 /**
4280 * Returns the last rendered column in the DOM (usually, it is not visible in the table's viewport).
4281 *
4282 * @since 14.6.0
4283 * @memberof Core#
4284 * @function getLastRenderedVisibleColumn
4285 * @returns {number | null}
4286 */
4287 this.getLastRenderedVisibleColumn = function () {
4288 return instance.view.getLastRenderedVisibleColumn();
4289 };
4290
4291 /**
4292 * Returns the first fully visible row in the table viewport. When the table has overlays the method returns
4293 * the first row of the main table that is not overlapped by overlay.
4294 *
4295 * @since 14.6.0
4296 * @memberof Core#
4297 * @function getFirstFullyVisibleRow
4298 * @returns {number | null}
4299 */
4300 this.getFirstFullyVisibleRow = function () {
4301 return instance.view.getFirstFullyVisibleRow();
4302 };
4303
4304 /**
4305 * Returns the last fully visible row in the table viewport. When the table has overlays the method returns
4306 * the first row of the main table that is not overlapped by overlay.
4307 *
4308 * @since 14.6.0
4309 * @memberof Core#
4310 * @function getLastFullyVisibleRow
4311 * @returns {number | null}
4312 */
4313 this.getLastFullyVisibleRow = function () {
4314 return instance.view.getLastFullyVisibleRow();
4315 };
4316
4317 /**
4318 * Returns the first fully visible column in the table viewport. When the table has overlays the method returns
4319 * the first row of the main table that is not overlapped by overlay.
4320 *
4321 * @since 14.6.0
4322 * @memberof Core#
4323 * @function getFirstFullyVisibleColumn
4324 * @returns {number | null}
4325 */
4326 this.getFirstFullyVisibleColumn = function () {
4327 return instance.view.getFirstFullyVisibleColumn();
4328 };
4329
4330 /**
4331 * Returns the last fully visible column in the table viewport. When the table has overlays the method returns
4332 * the first row of the main table that is not overlapped by overlay.
4333 *
4334 * @since 14.6.0
4335 * @memberof Core#
4336 * @function getLastFullyVisibleColumn
4337 * @returns {number | null}
4338 */
4339 this.getLastFullyVisibleColumn = function () {
4340 return instance.view.getLastFullyVisibleColumn();
4341 };
4342
4343 /**
4344 * Returns the first partially visible row in the table viewport. When the table has overlays the method returns
4345 * the first row of the main table that is not overlapped by overlay.
4346 *
4347 * @since 14.6.0
4348 * @memberof Core#
4349 * @function getFirstPartiallyVisibleRow
4350 * @returns {number | null}
4351 */
4352 this.getFirstPartiallyVisibleRow = function () {
4353 return instance.view.getFirstPartiallyVisibleRow();
4354 };
4355
4356 /**
4357 * Returns the last partially visible row in the table viewport. When the table has overlays the method returns
4358 * the first row of the main table that is not overlapped by overlay.
4359 *
4360 * @since 14.6.0
4361 * @memberof Core#
4362 * @function getLastPartiallyVisibleRow
4363 * @returns {number | null}
4364 */
4365 this.getLastPartiallyVisibleRow = function () {
4366 return instance.view.getLastPartiallyVisibleRow();
4367 };
4368
4369 /**
4370 * Returns the first partially visible column in the table viewport. When the table has overlays the method returns
4371 * the first row of the main table that is not overlapped by overlay.
4372 *
4373 * @since 14.6.0
4374 * @memberof Core#
4375 * @function getFirstPartiallyVisibleColumn
4376 * @returns {number | null}
4377 */
4378 this.getFirstPartiallyVisibleColumn = function () {
4379 return instance.view.getFirstPartiallyVisibleColumn();
4380 };
4381
4382 /**
4383 * Returns the last partially visible column in the table viewport. When the table has overlays the method returns
4384 * the first row of the main table that is not overlapped by overlay.
4385 *
4386 * @since 14.6.0
4387 * @memberof Core#
4388 * @function getLastPartiallyVisibleColumn
4389 * @returns {number | null}
4390 */
4391 this.getLastPartiallyVisibleColumn = function () {
4392 return instance.view.getLastPartiallyVisibleColumn();
4393 };
4394
4395 /**
4396 * Returns plugin instance by provided its name.
4397 *
4398 * @memberof Core#
4399 * @function getPlugin
4400 * @param {string} pluginName The plugin name.
4401 * @returns {BasePlugin|undefined} The plugin instance or undefined if there is no plugin.
4402 */
4403 this.getPlugin = function (pluginName) {
4404 const unifiedPluginName = (0, _string.toUpperCaseFirst)(pluginName);
4405
4406 // Workaround for the UndoRedo plugin which, currently doesn't follow the plugin architecture.
4407 if (unifiedPluginName === 'UndoRedo') {
4408 return this.undoRedo;
4409 }
4410 return pluginsRegistry.getItem(unifiedPluginName);
4411 };
4412
4413 /**
4414 * Returns name of the passed plugin.
4415 *
4416 * @private
4417 * @memberof Core#
4418 * @param {BasePlugin} plugin The plugin instance.
4419 * @returns {string}
4420 */
4421 this.getPluginName = function (plugin) {
4422 // Workaround for the UndoRedo plugin which, currently doesn't follow the plugin architecture.
4423 if (plugin === this.undoRedo) {
4424 return this.undoRedo.constructor.PLUGIN_KEY;
4425 }
4426 return pluginsRegistry.getId(plugin);
4427 };
4428
4429 /**
4430 * Returns the Handsontable instance.
4431 *
4432 * @memberof Core#
4433 * @function getInstance
4434 * @returns {Handsontable} The Handsontable instance.
4435 */
4436 this.getInstance = function () {
4437 return instance;
4438 };
4439
4440 /**
4441 * Adds listener to the specified hook name (only for this Handsontable instance).
4442 *
4443 * @memberof Core#
4444 * @function addHook
4445 * @see Hooks#add
4446 * @param {string} key Hook name (see {@link Hooks}).
4447 * @param {Function|Array} callback Function or array of functions.
4448 * @param {number} [orderIndex] Order index of the callback.
4449 * If > 0, the callback will be added after the others, for example, with an index of 1, the callback will be added before the ones with an index of 2, 3, etc., but after the ones with an index of 0 and lower.
4450 * If < 0, the callback will be added before the others, for example, with an index of -1, the callback will be added after the ones with an index of -2, -3, etc., but before the ones with an index of 0 and higher.
4451 * If 0 or no order index is provided, the callback will be added between the "negative" and "positive" indexes.
4452 * @example
4453 * ```js
4454 * hot.addHook('beforeInit', myCallback);
4455 * ```
4456 */
4457 this.addHook = function (key, callback, orderIndex) {
4458 _pluginHooks.default.getSingleton().add(key, callback, instance, orderIndex);
4459 };
4460
4461 /**
4462 * Check if for a specified hook name there are added listeners (only for this Handsontable instance). All available
4463 * hooks you will find {@link Hooks}.
4464 *
4465 * @memberof Core#
4466 * @function hasHook
4467 * @see Hooks#has
4468 * @param {string} key Hook name.
4469 * @returns {boolean}
4470 *
4471 * @example
4472 * ```js
4473 * const hasBeforeInitListeners = hot.hasHook('beforeInit');
4474 * ```
4475 */
4476 this.hasHook = function (key) {
4477 return _pluginHooks.default.getSingleton().has(key, instance) || _pluginHooks.default.getSingleton().has(key);
4478 };
4479
4480 /**
4481 * Adds listener to specified hook name (only for this Handsontable instance). After the listener is triggered,
4482 * it will be automatically removed.
4483 *
4484 * @memberof Core#
4485 * @function addHookOnce
4486 * @see Hooks#once
4487 * @param {string} key Hook name (see {@link Hooks}).
4488 * @param {Function|Array} callback Function or array of functions.
4489 * @param {number} [orderIndex] Order index of the callback.
4490 * If > 0, the callback will be added after the others, for example, with an index of 1, the callback will be added before the ones with an index of 2, 3, etc., but after the ones with an index of 0 and lower.
4491 * If < 0, the callback will be added before the others, for example, with an index of -1, the callback will be added after the ones with an index of -2, -3, etc., but before the ones with an index of 0 and higher.
4492 * If 0 or no order index is provided, the callback will be added between the "negative" and "positive" indexes.
4493 * @example
4494 * ```js
4495 * hot.addHookOnce('beforeInit', myCallback);
4496 * ```
4497 */
4498 this.addHookOnce = function (key, callback, orderIndex) {
4499 _pluginHooks.default.getSingleton().once(key, callback, instance, orderIndex);
4500 };
4501
4502 /**
4503 * Removes the hook listener previously registered with {@link Core#addHook}.
4504 *
4505 * @memberof Core#
4506 * @function removeHook
4507 * @see Hooks#remove
4508 * @param {string} key Hook name.
4509 * @param {Function} callback Reference to the function which has been registered using {@link Core#addHook}.
4510 *
4511 * @example
4512 * ```js
4513 * hot.removeHook('beforeInit', myCallback);
4514 * ```
4515 */
4516 this.removeHook = function (key, callback) {
4517 _pluginHooks.default.getSingleton().remove(key, callback, instance);
4518 };
4519
4520 /**
4521 * Run the callbacks for the hook provided in the `key` argument using the parameters given in the other arguments.
4522 *
4523 * @memberof Core#
4524 * @function runHooks
4525 * @see Hooks#run
4526 * @param {string} key Hook name.
4527 * @param {*} [p1] Argument passed to the callback.
4528 * @param {*} [p2] Argument passed to the callback.
4529 * @param {*} [p3] Argument passed to the callback.
4530 * @param {*} [p4] Argument passed to the callback.
4531 * @param {*} [p5] Argument passed to the callback.
4532 * @param {*} [p6] Argument passed to the callback.
4533 * @returns {*}
4534 *
4535 * @example
4536 * ```js
4537 * // Run built-in hook
4538 * hot.runHooks('beforeInit');
4539 * // Run custom hook
4540 * hot.runHooks('customAction', 10, 'foo');
4541 * ```
4542 */
4543 this.runHooks = function (key, p1, p2, p3, p4, p5, p6) {
4544 return _pluginHooks.default.getSingleton().run(instance, key, p1, p2, p3, p4, p5, p6);
4545 };
4546
4547 /**
4548 * Get language phrase for specified dictionary key.
4549 *
4550 * @memberof Core#
4551 * @function getTranslatedPhrase
4552 * @since 0.35.0
4553 * @param {string} dictionaryKey Constant which is dictionary key.
4554 * @param {*} extraArguments Arguments which will be handled by formatters.
4555 * @returns {string}
4556 */
4557 this.getTranslatedPhrase = function (dictionaryKey, extraArguments) {
4558 return (0, _registry5.getTranslatedPhrase)(tableMeta.language, dictionaryKey, extraArguments);
4559 };
4560
4561 /**
4562 * Converts instance into outerHTML of HTMLTableElement.
4563 *
4564 * @memberof Core#
4565 * @function toHTML
4566 * @since 7.1.0
4567 * @returns {string}
4568 */
4569 this.toHTML = () => (0, _parseTable.instanceToHTML)(this);
4570
4571 /**
4572 * Converts instance into HTMLTableElement.
4573 *
4574 * @memberof Core#
4575 * @function toTableElement
4576 * @since 7.1.0
4577 * @returns {HTMLTableElement}
4578 */
4579 this.toTableElement = () => {
4580 const tempElement = this.rootDocument.createElement('div');
4581 tempElement.insertAdjacentHTML('afterbegin', (0, _parseTable.instanceToHTML)(this));
4582 return tempElement.firstElementChild;
4583 };
4584 this.timeouts = [];
4585
4586 /**
4587 * Sets timeout. Purpose of this method is to clear all known timeouts when `destroy` method is called.
4588 *
4589 * @param {number|Function} handle Handler returned from setTimeout or function to execute (it will be automatically wraped
4590 * by setTimeout function).
4591 * @param {number} [delay=0] If first argument is passed as a function this argument set delay of the execution of that function.
4592 * @private
4593 */
4594 this._registerTimeout = function (handle) {
4595 let delay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
4596 let handleFunc = handle;
4597 if (typeof handleFunc === 'function') {
4598 handleFunc = setTimeout(handleFunc, delay);
4599 }
4600 this.timeouts.push(handleFunc);
4601 };
4602
4603 /**
4604 * Clears all known timeouts.
4605 *
4606 * @private
4607 */
4608 this._clearTimeouts = function () {
4609 (0, _array.arrayEach)(this.timeouts, handler => {
4610 clearTimeout(handler);
4611 });
4612 };
4613 this.immediates = [];
4614
4615 /**
4616 * Execute function execution to the next event loop cycle. Purpose of this method is to clear all known timeouts when `destroy` method is called.
4617 *
4618 * @param {Function} callback Function to be delayed in execution.
4619 * @private
4620 */
4621 this._registerImmediate = function (callback) {
4622 this.immediates.push(setImmediate(callback));
4623 };
4624
4625 /**
4626 * Clears all known timeouts.
4627 *
4628 * @private
4629 */
4630 this._clearImmediates = function () {
4631 (0, _array.arrayEach)(this.immediates, handler => {
4632 clearImmediate(handler);
4633 });
4634 };
4635
4636 /**
4637 * Gets the instance of the EditorManager.
4638 *
4639 * @private
4640 * @returns {EditorManager}
4641 */
4642 this._getEditorManager = function () {
4643 return editorManager;
4644 };
4645
4646 /**
4647 * Check if currently it is RTL direction.
4648 *
4649 * @private
4650 * @memberof Core#
4651 * @function isRtl
4652 * @returns {boolean} True if RTL.
4653 */
4654 this.isRtl = function () {
4655 return instance.rootWindow.getComputedStyle(instance.rootElement).direction === 'rtl';
4656 };
4657
4658 /**
4659 * Check if currently it is LTR direction.
4660 *
4661 * @private
4662 * @memberof Core#
4663 * @function isLtr
4664 * @returns {boolean} True if LTR.
4665 */
4666 this.isLtr = function () {
4667 return !instance.isRtl();
4668 };
4669
4670 /**
4671 * Returns 1 for LTR; -1 for RTL. Useful for calculations.
4672 *
4673 * @private
4674 * @memberof Core#
4675 * @function getDirectionFactor
4676 * @returns {number} Returns 1 for LTR; -1 for RTL.
4677 */
4678 this.getDirectionFactor = function () {
4679 return instance.isLtr() ? 1 : -1;
4680 };
4681 const shortcutManager = (0, _shortcuts.createShortcutManager)({
4682 handleEvent() {
4683 return instance.isListening();
4684 },
4685 beforeKeyDown: event => {
4686 return this.runHooks('beforeKeyDown', event);
4687 },
4688 afterKeyDown: event => {
4689 if (this.isDestroyed) {
4690 // Handsontable could be destroyed after performing action (executing a callback).
4691 return;
4692 }
4693 instance.runHooks('afterDocumentKeyDown', event);
4694 },
4695 ownerWindow: this.rootWindow
4696 });
4697 this.addHook('beforeOnCellMouseDown', event => {
4698 // Releasing keys as some browser/system shortcuts break events sequence (thus the `keyup` event isn't triggered).
4699 if (event.ctrlKey === false && event.metaKey === false) {
4700 shortcutManager.releasePressedKeys();
4701 }
4702 });
4703
4704 /**
4705 * Returns instance of a manager responsible for handling shortcuts stored in some contexts. It run actions after
4706 * pressing key combination in active Handsontable instance.
4707 *
4708 * @memberof Core#
4709 * @since 12.0.0
4710 * @function getShortcutManager
4711 * @returns {ShortcutManager} Instance of {@link ShortcutManager}
4712 */
4713 this.getShortcutManager = function () {
4714 return shortcutManager;
4715 };
4716
4717 /**
4718 * Return the Focus Manager responsible for managing the browser's focus in the table.
4719 *
4720 * @memberof Core#
4721 * @since 14.0.0
4722 * @function getFocusManager
4723 * @returns {FocusManager}
4724 */
4725 this.getFocusManager = function () {
4726 return focusManager;
4727 };
4728 (0, _registry.getPluginsNames)().forEach(pluginName => {
4729 const PluginClass = (0, _registry.getPlugin)(pluginName);
4730 pluginsRegistry.addItem(pluginName, new PluginClass(this));
4731 });
4732 (0, _shortcutContexts.registerAllShortcutContexts)(instance);
4733 shortcutManager.setActiveContextName('grid');
4734 _pluginHooks.default.getSingleton().run(instance, 'construct');
4735}
\No newline at end of file