| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import { CellRange } from './../3rdparty/walkontable/src';
- import { arrayEach, arrayReduce } from './../helpers/array';
- import { isUndefined } from './../helpers/mixed';
- export const SELECTION_TYPE_UNRECOGNIZED = 0;
- export const SELECTION_TYPE_EMPTY = 1;
- export const SELECTION_TYPE_ARRAY = 2;
- export const SELECTION_TYPE_OBJECT = 3;
- export const SELECTION_TYPES = [
- SELECTION_TYPE_OBJECT,
- SELECTION_TYPE_ARRAY,
- ];
- const ARRAY_TYPE_PATTERN = [['number'], ['number', 'string'], ['number', 'undefined'], ['number', 'string', 'undefined']];
- const rootCall = Symbol('root');
- const childCall = Symbol('child');
- /**
- * Detect selection schema structure.
- *
- * @param {*} selectionRanges The selected range or and array of selected ranges. This type of data is produced by
- * `hot.getSelected()`, `hot.getSelectedLast()`, `hot.getSelectedRange()`
- * and `hot.getSelectedRangeLast()` methods.
- * @returns {Number} Returns a number that specifies the type of detected selection schema. If selection schema type
- * is unrecognized than it returns `0`.
- */
- export function detectSelectionType(selectionRanges, _callSymbol = rootCall) {
- if (_callSymbol !== rootCall && _callSymbol !== childCall) {
- throw new Error('The second argument is used internally only and cannot be overwritten.');
- }
- const isArray = Array.isArray(selectionRanges);
- const isRootCall = _callSymbol === rootCall;
- let result = SELECTION_TYPE_UNRECOGNIZED;
- if (isArray) {
- const firstItem = selectionRanges[0];
- if (selectionRanges.length === 0) {
- result = SELECTION_TYPE_EMPTY;
- } else if (isRootCall && firstItem instanceof CellRange) {
- result = SELECTION_TYPE_OBJECT;
- } else if (isRootCall && Array.isArray(firstItem)) {
- result = detectSelectionType(firstItem, childCall);
- } else if (selectionRanges.length >= 2 && selectionRanges.length <= 4) {
- const isArrayType = !selectionRanges.some((value, index) => !ARRAY_TYPE_PATTERN[index].includes(typeof value));
- if (isArrayType) {
- result = SELECTION_TYPE_ARRAY;
- }
- }
- }
- return result;
- }
- /**
- * Factory function designed for normalization data schema from different data structures of the selection ranges.
- *
- * @param {String} type Selection type which will be processed.
- * @param {Object} [options]
- * @param {Boolean} [options.keepDirection=false] If `true`, the coordinates which contain the direction of the
- * selected cells won't be changed. Otherwise, the selection will be
- * normalized to values starting from top-left to bottom-right.
- * @param {Function} [options.propToCol] Pass the converting function (usually `datamap.propToCol`) if the column
- * defined as props should be normalized to the numeric values.
- * @returns {Number[]} Returns normalized data about selected range as an array (`[rowStart, columnStart, rowEnd, columnEnd]`).
- */
- export function normalizeSelectionFactory(type, { keepDirection = false, propToCol } = {}) {
- if (!SELECTION_TYPES.includes(type)) {
- throw new Error('Unsupported selection ranges schema type was provided.');
- }
- return function(selection) {
- const isObjectType = type === SELECTION_TYPE_OBJECT;
- let rowStart = isObjectType ? selection.from.row : selection[0];
- let columnStart = isObjectType ? selection.from.col : selection[1];
- let rowEnd = isObjectType ? selection.to.row : selection[2];
- let columnEnd = isObjectType ? selection.to.col : selection[3];
- if (typeof propToCol === 'function') {
- if (typeof columnStart === 'string') {
- columnStart = propToCol(columnStart);
- }
- if (typeof columnEnd === 'string') {
- columnEnd = propToCol(columnEnd);
- }
- }
- if (isUndefined(rowEnd)) {
- rowEnd = rowStart;
- }
- if (isUndefined(columnEnd)) {
- columnEnd = columnStart;
- }
- if (!keepDirection) {
- const origRowStart = rowStart;
- const origColumnStart = columnStart;
- const origRowEnd = rowEnd;
- const origColumnEnd = columnEnd;
- rowStart = Math.min(origRowStart, origRowEnd);
- columnStart = Math.min(origColumnStart, origColumnEnd);
- rowEnd = Math.max(origRowStart, origRowEnd);
- columnEnd = Math.max(origColumnStart, origColumnEnd);
- }
- return [rowStart, columnStart, rowEnd, columnEnd];
- };
- }
- /**
- * Function transform selection ranges (produced by `hot.getSelected()` and `hot.getSelectedRange()`) to normalized
- * data structure. It merges repeated ranges into consecutive coordinates. The returned structure
- * contains an array of arrays. The single item contains at index 0 visual column index from the selection was
- * started and at index 1 distance as a count of selected columns.
- *
- * @param {Array[]|CellRange[]} selectionRanges Selection ranges produced by Handsontable.
- * @return {Array[]} Returns an array of arrays with ranges defines in that schema:
- * `[[visualColumnStart, distance], [visualColumnStart, distance], ...]`.
- * The column distances are always created starting from the left (zero index) to the
- * right (the latest column index).
- */
- export function transformSelectionToColumnDistance(selectionRanges) {
- const selectionType = detectSelectionType(selectionRanges);
- if (selectionType === SELECTION_TYPE_UNRECOGNIZED || selectionType === SELECTION_TYPE_EMPTY) {
- return [];
- }
- const selectionSchemaNormalizer = normalizeSelectionFactory(selectionType);
- const unorderedIndexes = new Set();
- // Iterate through all ranges and collect all column indexes which are not saved yet.
- arrayEach(selectionRanges, (selection) => {
- const [, columnStart,, columnEnd] = selectionSchemaNormalizer(selection);
- const amount = columnEnd - columnStart + 1;
- arrayEach(Array.from(new Array(amount), (_, i) => columnStart + i), (index) => {
- if (!unorderedIndexes.has(index)) {
- unorderedIndexes.add(index);
- }
- });
- });
- // Sort indexes in ascending order to easily detecting non-consecutive columns.
- const orderedIndexes = Array.from(unorderedIndexes).sort((a, b) => a - b);
- const normalizedColumnRanges = arrayReduce(orderedIndexes, (acc, visualColumnIndex, index, array) => {
- if (index !== 0 && visualColumnIndex === array[index - 1] + 1) {
- acc[acc.length - 1][1] += 1;
- } else {
- acc.push([visualColumnIndex, 1]);
- }
- return acc;
- }, []);
- return normalizedColumnRanges;
- }
- /**
- * Function transform selection ranges (produced by `hot.getSelected()` and `hot.getSelectedRange()`) to normalized
- * data structure. It merges repeated ranges into consecutive coordinates. The returned structure
- * contains an array of arrays. The single item contains at index 0 visual column index from the selection was
- * started and at index 1 distance as a count of selected columns.
- *
- * @param {Array[]|CellRange[]} selectionRanges Selection ranges produced by Handsontable.
- * @return {Array[]} Returns an array of arrays with ranges defines in that schema:
- * `[[visualColumnStart, distance], [visualColumnStart, distance], ...]`.
- * The column distances are always created starting from the left (zero index) to the
- * right (the latest column index).
- */
- export function transformSelectionToRowDistance(selectionRanges) {
- const selectionType = detectSelectionType(selectionRanges);
- if (selectionType === SELECTION_TYPE_UNRECOGNIZED || selectionType === SELECTION_TYPE_EMPTY) {
- return [];
- }
- const selectionSchemaNormalizer = normalizeSelectionFactory(selectionType);
- const unorderedIndexes = new Set();
- // Iterate through all ranges and collect all column indexes which are not saved yet.
- arrayEach(selectionRanges, (selection) => {
- const [rowStart,, rowEnd] = selectionSchemaNormalizer(selection);
- const amount = rowEnd - rowStart + 1;
- arrayEach(Array.from(new Array(amount), (_, i) => rowStart + i), (index) => {
- if (!unorderedIndexes.has(index)) {
- unorderedIndexes.add(index);
- }
- });
- });
- // Sort indexes in ascending order to easily detecting non-consecutive columns.
- const orderedIndexes = Array.from(unorderedIndexes).sort((a, b) => a - b);
- const normalizedRowRanges = arrayReduce(orderedIndexes, (acc, rowIndex, index, array) => {
- if (index !== 0 && rowIndex === array[index - 1] + 1) {
- acc[acc.length - 1][1] += 1;
- } else {
- acc.push([rowIndex, 1]);
- }
- return acc;
- }, []);
- return normalizedRowRanges;
- }
- /**
- * Check if passed value can be treated as valid cell coordinate. The second argument is
- * used to check if the value doesn't exceed the defined max table rows/columns count.
- *
- * @param {*} coord
- * @param {Number} maxTableItemsCount The value that declares the maximum coordinate that is still validatable.
- * @return {Boolean}
- */
- export function isValidCoord(coord, maxTableItemsCount = Infinity) {
- return typeof coord === 'number' && coord >= 0 && coord < maxTableItemsCount;
- }
|