UNPKG

@revolist/revogrid

Version:

Virtual reactive data grid spreadsheet component - RevoGrid.

1,359 lines (1,330 loc) 46.4 kB
/*! * Built by Revolist OU ❤️ */ 'use strict'; const dimension_helpers = require('./dimension.helpers-29797371.js'); require('./index-8006d065.js'); /** * A specialized version of `_.reduce` for arrays without support for * iteratee shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} iteratee The function invoked per iteration. * @param {*} [accumulator] The initial value. * @param {boolean} [initAccum] Specify using the first element of `array` as * the initial value. * @returns {*} Returns the accumulated value. */ function arrayReduce(array, iteratee, accumulator, initAccum) { var index = -1, length = array == null ? 0 : array.length; if (initAccum && length) { accumulator = array[++index]; } while (++index < length) { accumulator = iteratee(accumulator, array[index], index, array); } return accumulator; } /** * Creates a base function for methods like `_.forIn` and `_.forOwn`. * * @private * @param {boolean} [fromRight] Specify iterating from right to left. * @returns {Function} Returns the new base function. */ function createBaseFor(fromRight) { return function(object, iteratee, keysFunc) { var index = -1, iterable = Object(object), props = keysFunc(object), length = props.length; while (length--) { var key = props[fromRight ? length : ++index]; if (iteratee(iterable[key], key, iterable) === false) { break; } } return object; }; } /** * The base implementation of `baseForOwn` which iterates over `object` * properties returned by `keysFunc` and invokes `iteratee` for each property. * Iteratee functions may exit iteration early by explicitly returning `false`. * * @private * @param {Object} object The object to iterate over. * @param {Function} iteratee The function invoked per iteration. * @param {Function} keysFunc The function to get the keys of `object`. * @returns {Object} Returns `object`. */ var baseFor = createBaseFor(); const baseFor$1 = baseFor; /** * The base implementation of `_.forOwn` without support for iteratee shorthands. * * @private * @param {Object} object The object to iterate over. * @param {Function} iteratee The function invoked per iteration. * @returns {Object} Returns `object`. */ function baseForOwn(object, iteratee) { return object && baseFor$1(object, iteratee, dimension_helpers.keys); } /** * Creates a `baseEach` or `baseEachRight` function. * * @private * @param {Function} eachFunc The function to iterate over a collection. * @param {boolean} [fromRight] Specify iterating from right to left. * @returns {Function} Returns the new base function. */ function createBaseEach(eachFunc, fromRight) { return function(collection, iteratee) { if (collection == null) { return collection; } if (!dimension_helpers.isArrayLike(collection)) { return eachFunc(collection, iteratee); } var length = collection.length, index = fromRight ? length : -1, iterable = Object(collection); while ((fromRight ? index-- : ++index < length)) { if (iteratee(iterable[index], index, iterable) === false) { break; } } return collection; }; } /** * The base implementation of `_.forEach` without support for iteratee shorthands. * * @private * @param {Array|Object} collection The collection to iterate over. * @param {Function} iteratee The function invoked per iteration. * @returns {Array|Object} Returns `collection`. */ var baseEach = createBaseEach(baseForOwn); const baseEach$1 = baseEach; /** * The base implementation of `_.reduce` and `_.reduceRight`, without support * for iteratee shorthands, which iterates over `collection` using `eachFunc`. * * @private * @param {Array|Object} collection The collection to iterate over. * @param {Function} iteratee The function invoked per iteration. * @param {*} accumulator The initial value. * @param {boolean} initAccum Specify using the first or last element of * `collection` as the initial value. * @param {Function} eachFunc The function to iterate over `collection`. * @returns {*} Returns the accumulated value. */ function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { eachFunc(collection, function(value, index, collection) { accumulator = initAccum ? (initAccum = false, value) : iteratee(accumulator, value, index, collection); }); return accumulator; } /** * Reduces `collection` to a value which is the accumulated result of running * each element in `collection` thru `iteratee`, where each successive * invocation is supplied the return value of the previous. If `accumulator` * is not given, the first element of `collection` is used as the initial * value. The iteratee is invoked with four arguments: * (accumulator, value, index|key, collection). * * Many lodash methods are guarded to work as iteratees for methods like * `_.reduce`, `_.reduceRight`, and `_.transform`. * * The guarded methods are: * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, * and `sortBy` * * @static * @memberOf _ * @since 0.1.0 * @category Collection * @param {Array|Object} collection The collection to iterate over. * @param {Function} [iteratee=_.identity] The function invoked per iteration. * @param {*} [accumulator] The initial value. * @returns {*} Returns the accumulated value. * @see _.reduceRight * @example * * _.reduce([1, 2], function(sum, n) { * return sum + n; * }, 0); * // => 3 * * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { * (result[value] || (result[value] = [])).push(key); * return result; * }, {}); * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) */ function reduce(collection, iteratee, accumulator) { var func = dimension_helpers.isArray(collection) ? arrayReduce : baseReduce, initAccum = arguments.length < 3; return func(collection, dimension_helpers.baseIteratee(iteratee), accumulator, initAccum, baseEach$1); } function getCellData(val) { if (typeof val === 'undefined' || val === null) { return ''; } return val; } function getCellRaw(model = {}, column) { if (!column) { return; } if (column.cellParser) { return column.cellParser(model, column); } return model[column.prop]; } function getCellDataParsed(model, column) { return getCellData(getCellRaw(model, column)); } /** * Get column type from column data */ function getColumnType(rgCol) { if (rgCol.pin) { return rgCol.pin; } return 'rgCol'; } function getColumnSizes(cols) { const res = {}; for (const [i, c] of cols.entries()) { if (c.size) { res[i] = c.size; } } return res; } /** * Check if column is grouping column */ function isColGrouping(colData) { return !!colData.children; } /** * This function is used to create a collection of columns. */ function getColumns(columns, level = 0, types) { const collection = { // columns as they are in stores per type columns: { rgCol: [], colPinStart: [], colPinEnd: [], }, // columns indexed by prop for quick access columnByProp: {}, // column grouping columnGrouping: { rgCol: [], colPinStart: [], colPinEnd: [], }, // max depth level for column grouping maxLevel: level, // sorting sort: {}, }; return reduce(columns, (res, colData) => { // Grouped column if (isColGrouping(colData)) { res = gatherGroup(res, colData, getColumns(colData.children, level + 1, types), level); return res; } // Column type const columnDefinitionFromType = colData.columnType && (types === null || types === void 0 ? void 0 : types[colData.columnType]); // Regular column const regularColumn = Object.assign(Object.assign({}, columnDefinitionFromType), colData); // Regular column, no Pin if (!regularColumn.pin) { res.columns.rgCol.push(regularColumn); // Pin } else { res.columns[regularColumn.pin].push(regularColumn); } if (regularColumn.order) { res.sort[regularColumn.prop] = regularColumn; } // it's possible that some columns have same prop, but better to avoid it if (!res.columnByProp[regularColumn.prop]) { res.columnByProp[regularColumn.prop] = []; } res.columnByProp[regularColumn.prop].push(regularColumn); // trigger setup hook if present regularColumn.beforeSetup && regularColumn.beforeSetup(regularColumn); return res; }, collection); } function gatherGroup(res, colData, collection, level = 0) { // group template const group = Object.assign(Object.assign({}, colData), { level, indexes: [] }); // check columns for update columnTypes.forEach(type => { const resultItem = res.columns[type]; const collectionItem = collection.columns[type]; // if column data if (dimension_helpers.isArray(resultItem) && dimension_helpers.isArray(collectionItem)) { // fill grouping const itemLength = collectionItem.length; if (itemLength) { const columnLength = resultItem.length; // fill columns resultItem.push(...collectionItem); // fill indexes per each viewport res.columnGrouping[type].push(Object.assign(Object.assign({}, group), { indexes: Array(itemLength).fill(columnLength).map((v, i) => v + i) })); } } }); // merge column groupings for (let k in collection.columnGrouping) { const key = k; const collectionItem = collection.columnGrouping[key]; res.columnGrouping[key].push(...collectionItem); } res.maxLevel = Math.max(res.maxLevel, collection.maxLevel); res.sort = Object.assign(Object.assign({}, res.sort), collection.sort); res.columnByProp = Object.assign(Object.assign({}, res.columnByProp), collection.columnByProp); return res; } function findColumn(columns, prop) { for (const c of columns) { if (isColGrouping(c)) { const found = findColumn(c.children, prop); if (found) { return found; } } else if (c.prop === prop) { return c; } } return undefined; } function getColumnByProp(columns, prop) { return findColumn(columns, prop); } /** * Selection store */ function defaultState() { return { range: null, tempRange: null, tempRangeType: null, focus: null, edit: null, lastCell: null, nextFocus: null, }; } class SelectionStore { constructor() { this.unsubscribe = []; this.store = dimension_helpers.createStore(defaultState()); this.store.on('set', (key, newVal) => { if (key === 'tempRange' && !newVal) { this.store.set('tempRangeType', null); } }); } onChange(propName, cb) { this.unsubscribe.push(this.store.onChange(propName, cb)); } clearFocus() { dimension_helpers.setStore(this.store, { focus: null, range: null, edit: null, tempRange: null }); } setFocus(focus, end) { if (!end) { dimension_helpers.setStore(this.store, { focus }); } else { dimension_helpers.setStore(this.store, { focus, range: getRange(focus, end), edit: null, tempRange: null, }); } } setNextFocus(focus) { dimension_helpers.setStore(this.store, { nextFocus: focus }); } setTempArea(range) { dimension_helpers.setStore(this.store, { tempRange: range === null || range === void 0 ? void 0 : range.area, tempRangeType: range === null || range === void 0 ? void 0 : range.type, edit: null }); } clearTemp() { dimension_helpers.setStore(this.store, { tempRange: null }); } /** Can be applied from selection change or from simple keyboard change clicks */ setRangeArea(range) { dimension_helpers.setStore(this.store, { range, edit: null, tempRange: null }); } setRange(start, end) { const range = getRange(start, end); this.setRangeArea(range); } setLastCell(lastCell) { dimension_helpers.setStore(this.store, { lastCell }); } setEdit(val) { const focus = this.store.get('focus'); if (focus && typeof val === 'string') { dimension_helpers.setStore(this.store, { edit: { x: focus.x, y: focus.y, val }, }); return; } dimension_helpers.setStore(this.store, { edit: null }); } dispose() { this.unsubscribe.forEach(f => f()); this.store.dispose(); } } const EMPTY_INDEX = -1; class SelectionStoreConnector { constructor() { // dirty flag required to cleanup whole store in case visibility of panels changed this.dirty = false; this.stores = {}; this.columnStores = {}; this.rowStores = {}; /** * Helpers for data conversion */ this.storesByType = {}; this.storesXToType = {}; this.storesYToType = {}; this.sections = []; } get focusedStore() { var _a; for (let y in this.stores) { for (let x in this.stores[y]) { const focused = (_a = this.stores[y][x]) === null || _a === void 0 ? void 0 : _a.store.get('focus'); if (focused) { return { entity: this.stores[y][x], cell: focused, position: { x: parseInt(x, 10), y: parseInt(y, 10), }, }; } } } return null; } get edit() { var _a; return (_a = this.focusedStore) === null || _a === void 0 ? void 0 : _a.entity.store.get('edit'); } get focused() { var _a; return (_a = this.focusedStore) === null || _a === void 0 ? void 0 : _a.entity.store.get('focus'); } get selectedRange() { var _a; return (_a = this.focusedStore) === null || _a === void 0 ? void 0 : _a.entity.store.get('range'); } registerSection(e) { if (!e) { this.sections.length = 0; // some elements removed, rebuild stores this.dirty = true; return; } if (this.sections.indexOf(e) === -1) { this.sections.push(e); } } // check if require to cleanup all stores beforeUpdate() { if (this.dirty) { for (let y in this.stores) { for (let x in this.stores[y]) { this.stores[y][x].dispose(); } } this.dirty = false; } } registerColumn(x, type) { // if hidden just create store but no operations needed if (isHiddenStore(x)) { return new SelectionStore(); } if (this.columnStores[x]) { return this.columnStores[x]; } this.columnStores[x] = new SelectionStore(); // build cross-linking type to position this.storesByType[type] = x; this.storesXToType[x] = type; return this.columnStores[x]; } registerRow(y, type) { // if hidden just create store if (isHiddenStore(y)) { return new SelectionStore(); } if (this.rowStores[y]) { return this.rowStores[y]; } this.rowStores[y] = new SelectionStore(); // build cross linking type to position this.storesByType[type] = y; this.storesYToType[y] = type; return this.rowStores[y]; } /** * Cross store proxy, based on multiple dimensions */ register({ x, y }) { var _a, _b; // if hidden just create store if (isHiddenStore(x) || isHiddenStore(y)) { return new SelectionStore(); } if (!this.stores[y]) { this.stores[y] = {}; } if (this.stores[y][x]) { // Store already registered. Do not register twice return this.stores[y][x]; } this.stores[y][x] = new SelectionStore(); // proxy update, column store trigger only range area (_a = this.stores[y][x]) === null || _a === void 0 ? void 0 : _a.onChange('range', c => { this.columnStores[x].setRangeArea(c); this.rowStores[y].setRangeArea(c); }); // clean up on remove (_b = this.stores[y][x]) === null || _b === void 0 ? void 0 : _b.store.on('dispose', () => this.destroy(x, y)); return this.stores[y][x]; } destroy(x, y) { var _a, _b; (_a = this.columnStores[x]) === null || _a === void 0 ? void 0 : _a.dispose(); (_b = this.rowStores[y]) === null || _b === void 0 ? void 0 : _b.dispose(); delete this.rowStores[y]; delete this.columnStores[x]; // clear x cross-link if (this.storesXToType[x]) { const type = this.storesXToType[x]; delete this.storesXToType[x]; delete this.storesByType[type]; } // clear y cross-link if (this.storesYToType[y]) { const type = this.storesYToType[y]; delete this.storesYToType[y]; delete this.storesByType[type]; } if (this.stores[y]) { delete this.stores[y][x]; } // clear empty rows if (!Object.keys(this.stores[y] || {}).length) { delete this.stores[y]; } } setEditByCell(storePos, editCell) { this.focusByCell(storePos, editCell, editCell); this.setEdit(''); } /** * Sets the next focus cell before the current one. * * @param focus - The cell to set as the next focus. */ beforeNextFocusCell(focus) { var _a; // If there is no focused store, return early. if (!this.focusedStore) { return; } // Get the next store based on the current focus and the last cell. const lastCell = this.focusedStore.entity.store.get('lastCell'); const next = lastCell && this.getNextStore(focus, this.focusedStore.position, lastCell); // Set the next focus cell in the store. (_a = next === null || next === void 0 ? void 0 : next.store) === null || _a === void 0 ? void 0 : _a.setNextFocus(Object.assign(Object.assign({}, focus), next.item)); } focusByCell(storePos, start, end) { const store = this.stores[storePos.y][storePos.x]; this.focus(store, { focus: start, end }); } focus(store, { focus, end }) { const currentStorePointer = this.getCurrentStorePointer(store); if (!currentStorePointer) { return null; } // check for the focus in nearby store/viewport const lastCell = store.store.get('lastCell'); const next = lastCell && this.getNextStore(focus, currentStorePointer, lastCell); // if next store present - update if (next === null || next === void 0 ? void 0 : next.store) { const item = Object.assign(Object.assign({}, focus), next.item); this.focus(next.store, { focus: item, end: item }); return null; } if (lastCell) { focus = cropCellToMax(focus, lastCell); end = cropCellToMax(end, lastCell); } store.setFocus(focus, end); return focus; } /** * Retrieves the current store pointer based on the active store. * Clears focus from all stores except the active one. */ getCurrentStorePointer(store) { let currentStorePointer; // Iterate through all stores for (let y in this.stores) { for (let x in this.stores[y]) { const s = this.stores[y][x]; // Clear focus from stores other than the active one if (s !== store) { s.clearFocus(); } else { // Update the current store pointer with the active store coordinates currentStorePointer = { x: parseInt(x, 10), y: parseInt(y, 10) }; } } } return currentStorePointer; } /** * Retrieves the next store based on the focus cell and current store pointer. * If the next store exists, returns an object with the next store and the item in the new store. * If the next store does not exist, returns null. */ getNextStore(focus, currentStorePointer, lastCell) { // item in new store const nextItem = nextCell(focus, lastCell); let nextStore; if (nextItem) { Object.entries(nextItem).forEach(([type, nextItemCoord]) => { let stores; switch (type) { case 'x': // Get the X stores for the current Y coordinate of the current store pointer stores = this.getXStores(currentStorePointer.y); break; case 'y': // Get the Y stores for the current X coordinate of the current store pointer stores = this.getYStores(currentStorePointer.x); break; } // Get the next store based on the item in the new store if (nextItemCoord >= 0) { nextStore = stores[++currentStorePointer[type]]; } else { nextStore = stores[--currentStorePointer[type]]; const nextLastCell = nextStore === null || nextStore === void 0 ? void 0 : nextStore.store.get('lastCell'); if (nextLastCell) { nextItem[type] = nextLastCell[type] + nextItemCoord; } } }); } return { store: nextStore, item: nextItem, }; } clearAll() { var _a; for (let y in this.stores) { for (let x in this.stores[y]) { (_a = this.stores[y][x]) === null || _a === void 0 ? void 0 : _a.clearFocus(); } } } setEdit(val) { if (!this.focusedStore) { return; } this.focusedStore.entity.setEdit(val); } /** * Select all cells across all stores */ selectAll() { for (let y in this.stores) { for (let x in this.stores[y]) { const store = this.stores[y][x]; if (!store) { continue; } const lastCell = store.store.get('lastCell'); if (lastCell) { store.setRange({ x: 0, y: 0 }, { x: lastCell.x - 1, y: lastCell.y - 1 }); } } } } getXStores(y) { return this.stores[y]; } getYStores(x) { const stores = {}; for (let i in this.stores) { stores[i] = this.stores[i][x]; } return stores; } } function isHiddenStore(pos) { return pos === EMPTY_INDEX; } function nextCell(cell, lastCell) { const nextItem = {}; let types = ['x', 'y']; // previous item check for (let t of types) { if (cell[t] < 0) { nextItem[t] = cell[t]; return nextItem; } } // next item check for (let t of types) { if (cell[t] >= lastCell[t]) { nextItem[t] = cell[t] - lastCell[t]; return nextItem; } } return null; } function cropCellToMax(cell, lastCell) { const croppedCell = Object.assign({}, cell); const cellCoordinates = ['x', 'y']; for (const coordinate of cellCoordinates) { if (cell[coordinate] < 0) { croppedCell[coordinate] = 0; } else if (cell[coordinate] >= lastCell[coordinate]) { croppedCell[coordinate] = lastCell[coordinate] - 1; } } return croppedCell; } function getRange(start, end) { return start && end ? { x: Math.min(start.x, end.x), y: Math.min(start.y, end.y), x1: Math.max(start.x, end.x), y1: Math.max(start.y, end.y), } : null; } function isRangeSingleCell(a) { return a.x === a.x1 && a.y === a.y1; } const rowTypes = ['rowPinStart', 'rgRow', 'rowPinEnd']; const columnTypes = [ 'colPinStart', 'rgCol', 'colPinEnd', ]; function isRowType(type) { return rowTypes.indexOf(type) > -1; } const GROUP_DEPTH = `${dimension_helpers.GRID_INTERNALS}-depth`; const PSEUDO_GROUP_ITEM = `${dimension_helpers.GRID_INTERNALS}-name`; const PSEUDO_GROUP_ITEM_ID = `${dimension_helpers.GRID_INTERNALS}-id`; const PSEUDO_GROUP_ITEM_VALUE = `${dimension_helpers.GRID_INTERNALS}-value`; const PSEUDO_GROUP_COLUMN = `${dimension_helpers.GRID_INTERNALS}-column`; const GROUP_EXPANDED = `${dimension_helpers.GRID_INTERNALS}-expanded`; const GROUP_COLUMN_PROP = `${dimension_helpers.GRID_INTERNALS}-prop`; const GROUP_ORIGINAL_INDEX = `${dimension_helpers.GRID_INTERNALS}-original-index`; const GROUP_EXPAND_BTN = `group-expand`; const GROUP_EXPAND_EVENT = `groupexpandclick`; const GROUPING_ROW_TYPE = 'rgRow'; function getGroupValueDefault(item, prop) { return item[prop] || null; } // get source based on proxy item collection to preserve rgRow order function getSource(source, items, withoutGrouping = false) { let index = 0; const result = { source: [], prevExpanded: {}, oldNewIndexes: {}, }; // order important here, expected parent is first, then others items.forEach(i => { const model = source[i]; if (!withoutGrouping) { result.source.push(model); return; } // grouping filter if (isGrouping(model)) { if (getExpanded(model)) { result.prevExpanded[model[PSEUDO_GROUP_ITEM_VALUE]] = true; } } else { result.source.push(model); result.oldNewIndexes[i] = index; index++; } }); return result; } function getExpanded(model = {}) { return model[GROUP_EXPANDED]; } function flattenGroupMaps({ groupedValues, parentIds, isExpanded, itemIndex, expandedAll, prevExpanded, columnProps, }) { const depth = parentIds.length; const sourceWithGroups = []; // collapse all groups in the beginning let trimmed = {}; // index mapping let oldNewIndexMap = {}; groupedValues.forEach((innerGroupedValues, groupId) => { const levelIds = [...parentIds, groupId]; const mergedIds = levelIds.join(','); const isGroupExpanded = isExpanded && (!!expandedAll || !!prevExpanded[mergedIds]); sourceWithGroups.push({ [PSEUDO_GROUP_ITEM]: groupId, [GROUP_DEPTH]: depth, [PSEUDO_GROUP_ITEM_ID]: JSON.stringify(levelIds), [PSEUDO_GROUP_ITEM_VALUE]: mergedIds, [GROUP_EXPANDED]: isGroupExpanded, [GROUP_COLUMN_PROP]: columnProps[depth], [columnProps[depth]]: groupId, }); itemIndex += 1; // If parent group is collapsed, mark all items as hidden if (!isExpanded && depth) { trimmed[itemIndex] = true; } if (Array.isArray(innerGroupedValues)) { // This branch handles leaf nodes (actual data items) innerGroupedValues.forEach(value => { itemIndex += 1; if (!isGroupExpanded) { trimmed[itemIndex] = true; // Mark items as hidden if group is collapsed } oldNewIndexMap[value[GROUP_ORIGINAL_INDEX]] = itemIndex; // Keep track of new positions }); sourceWithGroups.push(...innerGroupedValues); } else { // This branch handles nested groups (further subgroups) const children = flattenGroupMaps({ groupedValues: innerGroupedValues, parentIds: levelIds, isExpanded: isGroupExpanded, itemIndex, expandedAll, prevExpanded, columnProps, }); // Recursively process subgroups sourceWithGroups.push(...children.source); trimmed = Object.assign(Object.assign({}, children.trimmed), trimmed); oldNewIndexMap = Object.assign(Object.assign({}, children.oldNewIndexMap), oldNewIndexMap); itemIndex = children.itemIndex; } }); return { source: sourceWithGroups, oldNewIndexMap, trimmed, itemIndex, }; } /** * Gather data for grouping * @param array - flat data array * @param columnProps - ids of groups * @param expanded - potentially expanded items if present */ function gatherGrouping(array, columnProps, { prevExpanded = {}, expandedAll = false, getGroupValue = getGroupValueDefault, }) { const groupedItems = new Map(); array.forEach((item, originalIndex) => { const groupLevelValues = columnProps.map(groupId => getGroupValue(item, groupId)); const lastLevelValue = groupLevelValues.pop(); let currentGroupLevel = groupedItems; groupLevelValues.forEach(value => { if (!currentGroupLevel.has(value)) { currentGroupLevel.set(value, new Map()); } currentGroupLevel = currentGroupLevel.get(value); }); if (!currentGroupLevel.has(lastLevelValue)) { const groupItems = []; currentGroupLevel.set(lastLevelValue, groupItems); } const lastLevelItems = currentGroupLevel.get(lastLevelValue); lastLevelItems.push(Object.assign(Object.assign({}, item), { [GROUP_ORIGINAL_INDEX]: originalIndex })); }); const groupingDepth = columnProps.length; const { source: sourceWithGroups, trimmed, oldNewIndexMap } = flattenGroupMaps({ groupedValues: groupedItems, parentIds: [], isExpanded: true, itemIndex: -1, expandedAll, prevExpanded, columnProps }); return { sourceWithGroups, // updates source mirror depth: groupingDepth, // largest depth for grouping trimmed, // used for expand/collapse grouping values oldNewIndexMap, // used for mapping old values to new }; } function getGroupingName(rgRow) { return rgRow === null || rgRow === void 0 ? void 0 : rgRow[PSEUDO_GROUP_ITEM]; } function isGrouping(rgRow) { return typeof (rgRow === null || rgRow === void 0 ? void 0 : rgRow[PSEUDO_GROUP_ITEM]) !== 'undefined'; } function isGroupingColumn(column) { return typeof (column === null || column === void 0 ? void 0 : column[PSEUDO_GROUP_COLUMN]) !== 'undefined'; } function measureEqualDepth(groupA, groupB) { const ln = groupA.length; let i = 0; for (; i < ln; i++) { if (groupA[i] !== groupB[i]) { return i; } } return i; } function getParsedGroup(id) { const parseGroup = JSON.parse(id); // extra precaution and type safeguard if (!Array.isArray(parseGroup)) { return null; } return parseGroup; } // check if items is child of current clicked group function isSameGroup(currentGroup, currentModel, nextModel) { const nextGroup = getParsedGroup(nextModel[PSEUDO_GROUP_ITEM_ID]); if (!nextGroup) { return false; } const depth = measureEqualDepth(currentGroup, nextGroup); return currentModel[GROUP_DEPTH] < depth; } /** * The base implementation of `_.slice` without an iteratee call guard. * * @private * @param {Array} array The array to slice. * @param {number} [start=0] The start position. * @param {number} [end=array.length] The end position. * @returns {Array} Returns the slice of `array`. */ function baseSlice(array, start, end) { var index = -1, length = array.length; if (start < 0) { start = -start > length ? 0 : (length + start); } end = end > length ? length : end; if (end < 0) { end += length; } length = start > end ? 0 : ((end - start) >>> 0); start >>>= 0; var result = Array(length); while (++index < length) { result[index] = array[index + start]; } return result; } /** * Creates a slice of `array` from `start` up to, but not including, `end`. * * **Note:** This method is used instead of * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are * returned. * * @static * @memberOf _ * @since 3.0.0 * @category Array * @param {Array} array The array to slice. * @param {number} [start=0] The start position. * @param {number} [end=array.length] The end position. * @returns {Array} Returns the slice of `array`. */ function slice(array, start, end) { var length = array == null ? 0 : array.length; if (!length) { return []; } if (end && typeof end != 'number' && dimension_helpers.isIterateeCall(array, start, end)) { start = 0; end = length; } else { start = start == null ? 0 : dimension_helpers.toInteger(start); end = end === undefined ? length : dimension_helpers.toInteger(end); } return baseSlice(array, start, end); } function getCellEditor(column, editors = {}) { const editor = column === null || column === void 0 ? void 0 : column.editor; if (!editor) { return undefined; } // reference if (typeof editor === 'string') { return editors[editor]; } return editor; } class ColumnService { get columns() { return dimension_helpers.getVisibleSourceItem(this.source); } constructor(dataStore, source) { this.dataStore = dataStore; this.source = source; this.unsubscribe = []; this.hasGrouping = false; this.unsubscribe.push(source.onChange('source', s => this.checkGrouping(s))); this.checkGrouping(source.get('source')); this.type = source.get('type'); } checkGrouping(cols) { for (let rgCol of cols) { if (isGroupingColumn(rgCol)) { this.hasGrouping = true; return; } this.hasGrouping = false; } } isReadOnly(r, c) { var _a; const readOnly = (_a = this.columns[c]) === null || _a === void 0 ? void 0 : _a.readonly; if (typeof readOnly === 'function') { const data = this.rowDataModel(r, c); return readOnly(data); } return !!readOnly; } mergeProperties(r, c, defaultProps, schemaModel) { var _a, _b; const props = Object.assign({}, defaultProps); props.class = Object.assign(Object.assign({}, (typeof props.class === 'string' ? { [props.class]: true } : props.class)), { [dimension_helpers.CELL_CLASS]: true, [dimension_helpers.DISABLED_CLASS]: this.isReadOnly(r, c) }); const extra = (_b = (_a = schemaModel.column) === null || _a === void 0 ? void 0 : _a.cellProperties) === null || _b === void 0 ? void 0 : _b.call(_a, schemaModel); if (!extra) { return props; } return doPropMerge(props, extra); } getRowClass(r, prop) { const model = dimension_helpers.getSourceItem(this.dataStore, r) || {}; return model[prop] || ''; } getSaveData(rowIndex, colIndex, val) { const data = this.rowDataModel(rowIndex, colIndex); if (typeof val === 'undefined') { val = getCellData(data.value); } return Object.assign(Object.assign({}, data), { val }); } /** * Get cell data model for given rowIndex and colIndex * Used to pass data to editor/renderer */ rowDataModel(rowIndex, colIndex) { const column = this.columns[colIndex]; const prop = column === null || column === void 0 ? void 0 : column.prop; const model = dimension_helpers.getSourceItem(this.dataStore, rowIndex) || {}; const type = this.dataStore.get('type'); return { prop, model, data: this.dataStore.get('source'), column, rowIndex, colIndex, colType: this.type, type, value: getCellRaw(model, column), }; } getRangeData(d, columns) { var _a; const changed = {}; // get original length sizes const copyColLength = d.oldRange.x1 - d.oldRange.x + 1; const copyRowLength = d.oldRange.y1 - d.oldRange.y + 1; const mapping = {}; // rows for (let rowIndex = d.newRange.y, i = 0; rowIndex < d.newRange.y1 + 1; rowIndex++, i++) { // copy original data link const oldRowIndex = d.oldRange.y + (i % copyRowLength); const copyRow = dimension_helpers.getSourceItem(this.dataStore, oldRowIndex) || {}; // columns for (let colIndex = d.newRange.x, j = 0; colIndex < d.newRange.x1 + 1; colIndex++, j++) { // check if old range area if (rowIndex >= d.oldRange.y && rowIndex <= d.oldRange.y1 && colIndex >= d.oldRange.x && colIndex <= d.oldRange.x1) { continue; } // requested column beyond range if (!this.columns[colIndex]) { continue; } const prop = (_a = this.columns[colIndex]) === null || _a === void 0 ? void 0 : _a.prop; const copyColIndex = d.oldRange.x + (j % copyColLength); const copyColumnProp = columns[copyColIndex].prop; /** if can write */ if (!this.isReadOnly(rowIndex, colIndex)) { /** to show before save */ if (!changed[rowIndex]) { changed[rowIndex] = {}; } changed[rowIndex][prop] = copyRow[copyColumnProp]; /** Generate mapping object */ if (!mapping[rowIndex]) { mapping[rowIndex] = {}; } mapping[rowIndex][prop] = { colIndex: copyColIndex, colProp: copyColumnProp, rowIndex: oldRowIndex, }; } } } return { changed, mapping, }; } getTransformedDataToApply(start, data) { const changed = {}; const copyRowLength = data.length; const colLength = this.columns.length; const rowLength = this.dataStore.get('items').length; // rows let rowIndex = start.y; let maxCol = 0; for (let i = 0; rowIndex < rowLength && i < copyRowLength; rowIndex++, i++) { // copy original data link const copyRow = data[i % copyRowLength]; const copyColLength = (copyRow === null || copyRow === void 0 ? void 0 : copyRow.length) || 0; // columns let colIndex = start.x; for (let j = 0; colIndex < colLength && j < copyColLength; colIndex++, j++) { const p = this.columns[colIndex].prop; const currentCol = j % colLength; /** if can write */ if (!this.isReadOnly(rowIndex, colIndex)) { /** to show before save */ if (!changed[rowIndex]) { changed[rowIndex] = {}; } changed[rowIndex][p] = copyRow[currentCol]; } } maxCol = Math.max(maxCol, colIndex - 1); } const range = getRange(start, { y: rowIndex - 1, x: maxCol, }); return { changed, range, }; } getRangeStaticData(d, value) { const changed = {}; // rows for (let rowIndex = d.y, i = 0; rowIndex < d.y1 + 1; rowIndex++, i++) { // columns for (let colIndex = d.x, j = 0; colIndex < d.x1 + 1; colIndex++, j++) { // requested column beyond range if (!this.columns[colIndex]) { continue; } const p = this.columns[colIndex].prop; /** if can write */ if (!this.isReadOnly(rowIndex, colIndex)) { /** to show before save */ if (!changed[rowIndex]) { changed[rowIndex] = {}; } changed[rowIndex][p] = value; } } } return changed; } getRangeTransformedToProps(d, store) { var _a; const area = []; const type = this.dataStore.get('type'); // rows for (let rowIndex = d.y, i = 0; rowIndex < d.y1 + 1; rowIndex++, i++) { // columns for (let colIndex = d.x, j = 0; colIndex < d.x1 + 1; colIndex++, j++) { const prop = (_a = this.columns[colIndex]) === null || _a === void 0 ? void 0 : _a.prop; area.push({ prop, rowIndex, colIndex, model: dimension_helpers.getSourceItem(store, rowIndex), type, colType: this.type, }); } } return area; } copyRangeArray(range, store) { const cols = [...this.columns]; const props = slice(cols, range.x, range.x1 + 1).map(v => v.prop); const toCopy = []; const mapping = {}; // rows indexes for (let i = range.y; i <= range.y1; i++) { const rgRow = []; mapping[i] = {}; // columns indexes for (let prop of props) { const item = dimension_helpers.getSourceItem(store, i); // if no item - skip if (!item) { continue; } const val = item[prop]; rgRow.push(val); mapping[i][prop] = val; } toCopy.push(rgRow); } return { data: toCopy, mapping, }; } destroy() { this.unsubscribe.forEach(f => f()); } } /** * Checks if the given rowDrag is a service for dragging rows. */ function isRowDragService(rowDrag, model) { if (typeof rowDrag === 'function') { return rowDrag(model); } return !!rowDrag; } function mergeClasses(class1 = {}, class2 = {}) { if (typeof class1 === 'string') { class1 = { [class1]: true }; } if (typeof class2 === 'string') { class2 = { [class2]: true }; } return Object.assign(Object.assign({}, class1), class2); } function doPropMerge(existing, extra) { // if className is provided - remove it from props it messing with stencil if (extra.className) { extra.class = mergeClasses(extra.class, extra.className); delete extra.className; } let props = Object.assign(Object.assign({}, extra), existing); // extend existing props if (extra.class) { props.class = mergeClasses(props.class, extra.class); } if (extra.style) { props.style = Object.assign(Object.assign({}, extra.style), props.style); } return props; } exports.ColumnService = ColumnService; exports.EMPTY_INDEX = EMPTY_INDEX; exports.GROUPING_ROW_TYPE = GROUPING_ROW_TYPE; exports.GROUP_COLUMN_PROP = GROUP_COLUMN_PROP; exports.GROUP_DEPTH = GROUP_DEPTH; exports.GROUP_EXPANDED = GROUP_EXPANDED; exports.GROUP_EXPAND_BTN = GROUP_EXPAND_BTN; exports.GROUP_EXPAND_EVENT = GROUP_EXPAND_EVENT; exports.GROUP_ORIGINAL_INDEX = GROUP_ORIGINAL_INDEX; exports.PSEUDO_GROUP_COLUMN = PSEUDO_GROUP_COLUMN; exports.PSEUDO_GROUP_ITEM = PSEUDO_GROUP_ITEM; exports.PSEUDO_GROUP_ITEM_ID = PSEUDO_GROUP_ITEM_ID; exports.PSEUDO_GROUP_ITEM_VALUE = PSEUDO_GROUP_ITEM_VALUE; exports.SelectionStore = SelectionStore; exports.SelectionStoreConnector = SelectionStoreConnector; exports.baseEach = baseEach$1; exports.columnTypes = columnTypes; exports.cropCellToMax = cropCellToMax; exports.doPropMerge = doPropMerge; exports.gatherGroup = gatherGroup; exports.gatherGrouping = gatherGrouping; exports.getCellData = getCellData; exports.getCellDataParsed = getCellDataParsed; exports.getCellEditor = getCellEditor; exports.getCellRaw = getCellRaw; exports.getColumnByProp = getColumnByProp; exports.getColumnSizes = getColumnSizes; exports.getColumnType = getColumnType; exports.getColumns = getColumns; exports.getExpanded = getExpanded; exports.getGroupingName = getGroupingName; exports.getParsedGroup = getParsedGroup; exports.getRange = getRange; exports.getSource = getSource; exports.isColGrouping = isColGrouping; exports.isGrouping = isGrouping; exports.isGroupingColumn = isGroupingColumn; exports.isHiddenStore = isHiddenStore; exports.isRangeSingleCell = isRangeSingleCell; exports.isRowDragService = isRowDragService; exports.isRowType = isRowType; exports.isSameGroup = isSameGroup; exports.measureEqualDepth = measureEqualDepth; exports.nextCell = nextCell; exports.reduce = reduce; exports.rowTypes = rowTypes; //# sourceMappingURL=column.service-e6411bc8.js.map