@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
1,359 lines (1,330 loc) • 46.4 kB
JavaScript
/*!
* 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