UNPKG

@revolist/revogrid

Version:

Virtual reactive data grid spreadsheet component - RevoGrid.

274 lines (273 loc) 9.9 kB
/*! * Built by Revolist OU ❤️ */ import { cropCellToMax, nextCell, SelectionStore } from "../store/index"; export class SelectionStoreConnector { constructor() { this.stores = {}; this.columnStores = {}; this.rowStores = {}; /** * Helpers for data conversion */ this.storesByType = {}; this.storesXToType = {}; this.storesYToType = {}; } 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'); } registerColumn(x, type) { 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 (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 }) { if (!this.stores[y]) { this.stores[y] = {}; } let store = this.stores[y][x]; if (store) { // Store already registered. Do not register twice return store; } this.stores[y][x] = store = new SelectionStore(); // proxy update, column store trigger only range area store.onChange('range', c => { this.columnStores[x].setRangeArea(c); this.rowStores[y].setRangeArea(c); }); // clean up on remove store.store.on('dispose', () => this.destroy(x, y)); return store; } 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; } } }); } // if last cell is empty store is empty, no next store const lastCellNext = nextStore === null || nextStore === void 0 ? void 0 : nextStore.store.get('lastCell'); if (!(lastCellNext === null || lastCellNext === void 0 ? void 0 : lastCellNext.x) || !(lastCellNext === null || lastCellNext === void 0 ? void 0 : lastCellNext.y)) { nextStore = undefined; } 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; } }