UNPKG

@revolist/revogrid

Version:

Virtual reactive data grid spreadsheet component - RevoGrid.

1,235 lines 150 kB
/*! * Built by Revolist OU ❤️ */ import { h, Host, } from "@stencil/core"; import ColumnDataProvider from "../../services/column.data.provider"; import { DataProvider } from "../../services/data.provider"; import { getVisibleSourceItem, rowTypes } from "../../store/index"; import DimensionProvider from "../../services/dimension.provider"; import ViewportProvider from "../../services/viewport.provider"; import ThemeService from "../../themeManager/theme.service"; import { timeout } from "../../utils"; import { AutoSizeColumnPlugin, } from "../../plugins/column.auto-size.plugin"; import { FilterPlugin, } from "../../plugins/filter/filter.plugin"; import { SortingPlugin } from "../../plugins/sorting/sorting.plugin"; import { ExportFilePlugin } from "../../plugins/export/export.plugin"; import { GroupingRowPlugin } from "../../plugins/groupingRow/grouping.row.plugin"; import ViewportService from "./viewport.service"; import { DATA_SLOT, HEADER_SLOT } from "./viewport.helpers"; import GridScrollingService from "./viewport.scrolling.service"; import { SelectionStoreConnector } from "../../services/selection.store.connector"; import OrderRenderer from "../order/order-renderer"; import { StretchColumn, isStretchPlugin, } from "../../plugins/column.stretch.plugin"; import { rowDefinitionByType, rowDefinitionRemoveByType } from "./grid.helpers"; import { ColumnMovePlugin } from "../../plugins/moveColumn/column.drag.plugin"; import { getPropertyFromEvent } from "../../utils/events"; import { isMobileDevice } from "../../utils/mobile"; import { getColumnByProp, getColumns } from "../../utils/column.utils"; import { WCAGPlugin } from "../../plugins/wcag"; import { PluginService } from "./plugin.service"; import { RTLPlugin } from "../../plugins/rtl/rtl.plugin"; /** * Revogrid - High-performance, customizable grid library for managing large datasets. * ### Events guide * * For a comprehensive events guide, check the [Events API Page](/guide/api/events). * All events propagate to the root level of the grid. [Dependency tree](#Dependencies). * * ### Type definitions * * Read [type definition file](https://github.com/revolist/revogrid/blob/master/src/interfaces.d.ts) for the full interface information. * * All complex property types such as `ColumnRegular`, `ColumnProp`, `ColumnDataSchemaModel` can be found there. * * ### HTMLRevoGridElement * * @slot data-{column-type}-{row-type}. @example data-rgCol-rgRow - main data slot. Applies extra elements in <revogr-data />. * @slot focus-{column-type}-{row-type}. @example focus-rgCol-rgRow - focus layer for main data. Applies extra elements in <revogr-focus />. * @slot viewport - Viewport slot. * @slot header - Header slot. * @slot footer - Footer slot. */ export class RevoGridComponent { constructor() { /** * Defines how many rows/columns should be rendered outside visible area. */ this.frameSize = 1; /** * Indicates default rgRow size. * By default 0, means theme package size will be applied * * Alternatively you can use `rowSize` to reset viewport */ this.rowSize = 0; /** Indicates default column size. */ this.colSize = 100; /** When true, user can range selection. */ this.range = false; /** When true, grid in read only mode. */ this.readonly = false; /** When true, columns are resizable. */ this.resize = false; /** When true cell focus appear. */ this.canFocus = true; /** When true enable clipboard. */ this.useClipboard = true; /** * Columns - defines an array of grid columns. * Can be column or grouped column. */ this.columns = []; /** * Source - defines main data source. * Can be an Object or 2 dimensional array([][]); * Keys/indexes referenced from columns Prop. */ this.source = []; /** Pinned top Source: {[T in ColumnProp]: any} - defines pinned top rows data source. */ this.pinnedTopSource = []; /** Pinned bottom Source: {[T in ColumnProp]: any} - defines pinned bottom rows data source. */ this.pinnedBottomSource = []; /** Custom row properies to be applied. See `RowDefinition` for more info. */ this.rowDefinitions = []; /** Custom editors register. */ this.editors = {}; /** * Apply changes in editor when closed except 'Escape' cases. * If custom editor in use method getValue required. * Check interfaces.d.ts `EditorBase` for more info. */ this.applyOnClose = false; /** * Custom grid plugins. Can be added or removed at runtime. * Every plugin should be inherited from BasePlugin class. * * For more details check [Plugin guide](https://rv-grid.com/guide/plugin/) */ this.plugins = []; /** * Column Types Format. * Every type represent multiple column properties. * Types will be merged but can be replaced with column properties. * Types were made as separate objects to be reusable per multiple columns. */ this.columnTypes = {}; /** Theme name. */ this.theme = 'default'; /** * Row class property mapping. * Map custom classes to rows from row object data. * Define this property in rgRow object and this will be mapped as rgRow class. */ this.rowClass = ''; /** * Autosize config. * Enables columns autoSize. * For more details check `autoSizeColumn` plugin. * By default disabled, hence operation is not performance efficient. * `true` to enable with default params (double header separator click for autosize). * Or define config. See `AutoSizeColumnConfig` for more details. */ this.autoSizeColumn = false; /** * Enables filter plugin. * Can be boolean. * Or can be filter collection See `FilterCollection` for more info. */ this.filter = false; /** * Enable column move plugin. */ this.canMoveColumns = false; /** * Trimmed rows. * Functionality which allows to hide rows from main data set. * `trimmedRows` are physical `rgRow` indexes to hide. */ this.trimmedRows = {}; /** * Enable export plugin. */ this.exporting = false; /** * Stretch strategy for columns by `StretchColumn` plugin. * For example if there are more space on the right last column size would be increased. */ this.stretch = false; /** * Additional data to be passed to plugins, renders or editors. * For example if you need to pass Vue component instance. */ this.additionalData = {}; /** * Disable lazy rendering mode for the `X axis`. * Use when not many columns present and you don't need rerenader cells during scroll. * Can be used for initial rendering performance improvement. */ this.disableVirtualX = false; /** * Disable lazy rendering mode for the `Y axis`. * Use when not many rows present and you don't need rerenader cells during scroll. * Can be used for initial rendering performance improvement. */ this.disableVirtualY = false; /** * Please only hide the attribution if you are subscribed to Pro version */ this.hideAttribution = false; /** * Prevent rendering until job is done. * Can be used for initial rendering performance improvement. * When several plugins require initial rendering this will prevent double initial rendering. */ this.jobsBeforeRender = []; /** * Register new virtual node inside of grid. * Used for additional items creation such as plugin elements. * Should be set before grid render inside of plugins. * Can return VNode result of h() function or a function that returns VNode. * Function can be used for performance improvement and additional renders. */ this.registerVNode = []; /** * Enable accessibility. If disabled, the grid will not be accessible. * @default true */ this.accessible = true; /** * Enable right-to-left (RTL) mode. When enabled, columns will be displayed from right to left. * @default false */ this.rtl = false; /** * Disable native drag&drop plugin. */ this.canDrag = true; this.extraElements = []; this.pluginService = new PluginService(); this.viewport = null; this.isInited = false; } // #endregion // #region Methods /** * Refreshes data viewport. * Can be specific part as rgRow or pinned rgRow or 'all' by default. */ async refresh(type = 'all') { if (!this.dataProvider) { throw new Error('Not connected'); } this.dataProvider.refresh(type); } /** * Refreshes data at specified cell. * Useful for performance optimization. * No viewport update will be triggered. * * @example * const grid = document.querySelector('revo-grid'); * grid.setDataAt({ row: 0, col: 0, val: 'test' }); // refresh */ async setDataAt({ row, col, colType = 'rgCol', rowType = 'rgRow', val, skipDataUpdate = false }) { var _a; if (this.dataProvider && this.columnProvider && !skipDataUpdate) { const columnProp = (_a = this.columnProvider.getColumn(col, colType)) === null || _a === void 0 ? void 0 : _a.prop; if (typeof columnProp !== 'undefined') { this.dataProvider.setCellData({ type: rowType, rowIndex: row, prop: columnProp, val, }, false); } } const dataElement = this.element.querySelector(`revogr-data[type="${rowType}"][col-type="${colType}"]`); return dataElement === null || dataElement === void 0 ? void 0 : dataElement.updateCell({ row, col, }); } /** * Scrolls viewport to specified row by index. */ async scrollToRow(coordinate = 0) { if (!this.dimensionProvider) { throw new Error('Not connected'); } const y = this.dimensionProvider.getViewPortPos({ coordinate, dimension: 'rgRow', }); await this.scrollToCoordinate({ y }); } /** * Scrolls viewport to specified column by index. */ async scrollToColumnIndex(coordinate = 0) { if (!this.dimensionProvider) { throw new Error('Not connected'); } const x = this.dimensionProvider.getViewPortPos({ coordinate, dimension: 'rgCol', }); await this.scrollToCoordinate({ x }); } /** * Scrolls viewport to specified column by prop */ async scrollToColumnProp(prop, dimension = 'rgCol') { if (!this.dimensionProvider || !this.columnProvider) { throw new Error('Not connected'); } const coordinate = this.columnProvider.getColumnIndexByProp(prop, dimension); if (coordinate < 0) { // already on the screen return; } const x = this.dimensionProvider.getViewPortPos({ coordinate, dimension, }); await this.scrollToCoordinate({ x }); } /** Update columns */ async updateColumns(cols) { var _a; (_a = this.columnProvider) === null || _a === void 0 ? void 0 : _a.updateColumns(cols); } /** Add trimmed by type */ async addTrimmed(trimmed, trimmedType = 'external', type = 'rgRow') { if (!this.dataProvider) { throw new Error('Not connected'); } const event = this.beforetrimmed.emit({ trimmed, trimmedType, type, }); if (event.defaultPrevented) { return event; } this.dataProvider.setTrimmed({ [trimmedType]: event.detail.trimmed }, type); this.aftertrimmed.emit(); return event; } /** Scrolls view port to coordinate */ async scrollToCoordinate(cell) { var _a; (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.scrollToCell(cell); } /** Open editor for cell. */ async setCellEdit(rgRow, prop, rowSource = 'rgRow') { var _a; const rgCol = getColumnByProp(this.columns, prop); if (!rgCol) { return; } await timeout(); const colGroup = rgCol.pin || 'rgCol'; if (!this.columnProvider) { throw new Error('Not connected'); } (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.setEdit(rgRow, this.columnProvider.getColumnIndexByProp(prop, colGroup), colGroup, rowSource); } /** Set focus range. */ async setCellsFocus(cellStart = { x: 0, y: 0 }, cellEnd = { x: 0, y: 0 }, colType = 'rgCol', rowType = 'rgRow') { var _a; (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.setFocus(colType, rowType, cellStart, cellEnd); } /** Get data from source */ async getSource(type = 'rgRow') { if (!this.dataProvider) { throw new Error('Not connected'); } return this.dataProvider.stores[type].store.get('source'); } /** * Get data from visible part of source * Trimmed/filtered rows will be excluded * @param type - type of source */ async getVisibleSource(type = 'rgRow') { if (!this.dataProvider) { throw new Error('Not connected'); } return getVisibleSourceItem(this.dataProvider.stores[type].store); } /** * Provides access to rows internal store observer * Can be used for plugin support * @param type - type of source */ async getSourceStore(type = 'rgRow') { if (!this.dataProvider) { throw new Error('Not connected'); } return this.dataProvider.stores[type].store; } /** * Provides access to column internal store observer * Can be used for plugin support * @param type - type of column */ async getColumnStore(type = 'rgCol') { if (!this.columnProvider) { throw new Error('Not connected'); } return this.columnProvider.stores[type].store; } /** * Update column sorting * @param column - column prop and cellCompare * @param order - order to apply * @param additive - if false will replace current order * * later passed to SortingPlugin */ async updateColumnSorting(column, order, additive) { this.sortingconfigchanged.emit({ columns: [{ prop: column.prop, order, cellCompare: column.cellCompare, }], additive, }); } /** * Clears column sorting */ async clearSorting() { this.sortingconfigchanged.emit({ columns: [], }); } /** * Receive all columns in data source */ async getColumns() { if (!this.columnProvider) { throw new Error('Not connected'); } return this.columnProvider.getColumns(); } /** * Clear current grid focus. Grid has no longer focus on it. */ async clearFocus() { var _a, _b; const focused = (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.getFocused(); const event = this.beforefocuslost.emit(focused); if (event.defaultPrevented) { return; } (_b = this.selectionStoreConnector) === null || _b === void 0 ? void 0 : _b.clearAll(); } /** * Get all active plugins instances */ async getPlugins() { return this.pluginService.get(); } /** * Get the currently focused cell. */ async getFocused() { var _a, _b; return (_b = (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.getFocused()) !== null && _b !== void 0 ? _b : null; } /** * Get size of content * Including all pinned data */ async getContentSize() { var _a; if (!this.dimensionProvider) { throw new Error('Not connected'); } return (_a = this.dimensionProvider) === null || _a === void 0 ? void 0 : _a.getFullSize(); } /** * Get the currently selected Range. */ async getSelectedRange() { var _a, _b; return (_b = (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.getSelectedRange()) !== null && _b !== void 0 ? _b : null; } /** * Refresh extra elements. Triggers re-rendering of extra elements and functions. * Part of extraElements and registerVNode methods. * Useful for plugins. */ async refreshExtraElements() { var _a; (_a = this.extraService) === null || _a === void 0 ? void 0 : _a.refresh(); } /** * Get all providers for grid * Useful for external grid integration */ async getProviders() { return this.getPluginData(); } mousedownHandle(event) { const screenX = getPropertyFromEvent(event, 'screenX'); const screenY = getPropertyFromEvent(event, 'screenY'); if (screenX === null || screenY === null) { return; } this.clickTrackForFocusClear = screenX + screenY; } /** * To keep your elements from losing focus use mouseup/touchend e.preventDefault(); */ async mouseupHandle(event) { var _a; const screenX = getPropertyFromEvent(event, 'screenX'); const screenY = getPropertyFromEvent(event, 'screenY'); if (screenX === null || screenY === null) { return; } if (event.defaultPrevented) { return; } const pos = screenX + screenY; // detect if mousemove then do nothing if (Math.abs(((_a = this.clickTrackForFocusClear) !== null && _a !== void 0 ? _a : 0) - pos) > 10) { return; } // Check if action finished inside the document // if event prevented, or it is current table don't clear focus const path = event.composedPath(); if (!path.includes(this.element) && !(this.element.shadowRoot && path.includes(this.element.shadowRoot))) { // Perform actions if the click is outside the component await this.clearFocus(); } } // #endregion // #region Listeners /** Drag events */ onRowDragStarted(e) { var _a; const dragStart = this.rowdragstart.emit(e.detail); if (dragStart.defaultPrevented) { e.preventDefault(); return; } (_a = this.orderService) === null || _a === void 0 ? void 0 : _a.start(this.element, Object.assign(Object.assign({}, e.detail), dragStart.detail)); } onRowDragEnd() { var _a; (_a = this.orderService) === null || _a === void 0 ? void 0 : _a.end(); } onRowOrderChange(e) { var _a; (_a = this.dataProvider) === null || _a === void 0 ? void 0 : _a.changeOrder(e.detail); } onRowDrag({ detail }) { var _a; (_a = this.orderService) === null || _a === void 0 ? void 0 : _a.move(detail); } onRowMouseMove(e) { var _a; (_a = this.orderService) === null || _a === void 0 ? void 0 : _a.moveTip(e.detail); } async onCellEdit(e) { var _a; const { defaultPrevented, detail } = this.beforeedit.emit(e.detail); await timeout(); // apply data if (!defaultPrevented) { (_a = this.dataProvider) === null || _a === void 0 ? void 0 : _a.setCellData(detail); // @feature: incrimental update for cells // this.dataProvider.setCellData(detail, false); // await this.setDataAt({ // row: detail.rowIndex, // col: detail.colIndex, // rowType: detail.type, // colType: detail.colType, // }); this.afteredit.emit(detail); } } onRangeEdit(e) { if (!this.dataProvider) { throw new Error('Not connected'); } const { defaultPrevented, detail } = this.beforerangeedit.emit(e.detail); if (defaultPrevented) { e.preventDefault(); return; } this.dataProvider.setRangeData(detail.data, detail.type); this.afteredit.emit(detail); } onRangeChanged(e) { const beforeange = this.beforerange.emit(e.detail); if (beforeange.defaultPrevented) { e.preventDefault(); } const beforeFill = this.beforeautofill.emit(beforeange.detail); if (beforeFill.defaultPrevented) { e.preventDefault(); } } onRowDropped(e) { // e.cancelBubble = true; const { defaultPrevented } = this.roworderchanged.emit(e.detail); if (defaultPrevented) { e.preventDefault(); } } onHeaderClick(e) { const { defaultPrevented } = this.headerclick.emit(Object.assign(Object.assign({}, e.detail.column), { originalEvent: e.detail.originalEvent })); if (defaultPrevented) { e.preventDefault(); } } onCellFocus(e) { const { defaultPrevented } = this.beforecellfocus.emit(e.detail); if (!this.canFocus || defaultPrevented) { e.preventDefault(); } } // #endregion // #region Watchers columnTypesChanged() { // Column format change will trigger column structure update this.columnChanged(this.columns); } columnChanged(newVal = [], _prevVal = undefined, __watchName = 'columns', init = false) { if (!this.dimensionProvider || !this.columnProvider) { return; } const columnGather = getColumns(newVal, 0, this.columnTypes); const beforeSetEvent = this.beforecolumnsset.emit(columnGather); if (beforeSetEvent.defaultPrevented) { return; } this.dimensionProvider.applyNewColumns(beforeSetEvent.detail.columns, this.disableVirtualX, init); const beforeApplyEvent = this.beforecolumnapplied.emit(columnGather); if (beforeApplyEvent.defaultPrevented) { return; } const columns = this.columnProvider.setColumns(beforeApplyEvent.detail); this.aftercolumnsset.emit({ columns, order: Object.entries(beforeApplyEvent.detail.sort).reduce((acc, [prop, column]) => { acc[prop] = column.order; return acc; }, {}), }); } disableVirtualXChanged(newVal = false, prevVal = false) { if (newVal === prevVal) { return; } this.columnChanged(this.columns); } rowSizeChanged(s) { if (!this.dimensionProvider) { return; } // clear existing data this.dimensionProvider.setSettings({ originItemSize: s }, 'rgRow'); this.rowDefChanged(this.rowDefinitions, this.rowDefinitions, 'rowSize', true); } themeChanged(t, _, __ = 'theme', init = false) { if (!this.dimensionProvider) { return; } this.themeService.register(t); this.dimensionProvider.setSettings({ originItemSize: this.themeService.rowSize }, 'rgRow'); this.dimensionProvider.setSettings({ originItemSize: this.colSize }, 'rgCol'); // if theme change we need to reapply row size and reset viewport if (!init) { // clear existing data this.dimensionProvider.setSettings({ originItemSize: this.themeService.rowSize }, 'rgRow'); this.rowDefChanged( // for cases when some custom size present and not this.rowDefinitions, this.rowDefinitions, 'theme', true); } this.afterthemechanged.emit(t); } dataSourceChanged(newVal = [], _, watchName) { if (!this.dataProvider) { return; } let type = 'rgRow'; switch (watchName) { case 'pinnedBottomSource': type = 'rowPinEnd'; break; case 'pinnedTopSource': type = 'rowPinStart'; break; case 'source': type = 'rgRow'; /** * Applied for source only for cross compatability between plugins */ const beforesourceset = this.beforesourceset.emit({ type, source: newVal, }); newVal = beforesourceset.detail.source; break; } const beforesourceset = this.beforeanysource.emit({ type, source: newVal, }); const newSource = [...beforesourceset.detail.source]; this.dataProvider.setData(newSource, type, this.disableVirtualY); /** * Applied for source only for cross compatability between plugins */ if (watchName === 'source') { this.aftersourceset.emit({ type, source: newVal, }); } this.afteranysource.emit({ type, source: newVal, }); } disableVirtualYChanged(newVal = false, prevVal = false) { if (newVal === prevVal) { return; } this.dataSourceChanged(this.source, this.source, 'source'); } rowDefChanged(after, before, _watchName, forceUpdate = true) { // in firefox it's triggered before init if (!this.dimensionProvider || !this.dataProvider) { return; } const { detail: { vals: newVal, oldVals: oldVal }, } = this.beforerowdefinition.emit({ vals: after, oldVals: before, }); // apply new values const newRows = rowDefinitionByType(newVal); // clear current defs if (oldVal) { const remove = rowDefinitionRemoveByType(oldVal); // clear all old data and drop sizes for (const t in remove) { if (remove.hasOwnProperty(t)) { const type = t; const store = this.dataProvider.stores[type]; const sourceLength = store.store.get('source').length; this.dimensionProvider.clearSize(type, sourceLength); } } } // set new sizes rowTypes.forEach((t) => { var _a; const newSizes = newRows[t]; // apply new sizes or force update if (newSizes || forceUpdate) { (_a = this.dimensionProvider) === null || _a === void 0 ? void 0 : _a.setCustomSizes(t, (newSizes === null || newSizes === void 0 ? void 0 : newSizes.sizes) || {}); } }); } trimmedRowsChanged(newVal = {}) { this.addTrimmed(newVal); } /** * Grouping */ groupingChanged(newVal = {}) { var _a; (_a = this.pluginService.getByClass(GroupingRowPlugin)) === null || _a === void 0 ? void 0 : _a.setGrouping(newVal || {}); } /** * Stretch Plugin Apply */ applyStretch(isStretch) { if (!this.dimensionProvider || !this.dataProvider || !this.columnProvider || !this.viewportProvider) { return; } if (isStretch === 'false') { isStretch = false; } const pluginData = this.getPluginData(); if (!pluginData) { return; } const stretch = this.pluginService.getByClass(StretchColumn); if ((typeof isStretch === 'boolean' && isStretch) || isStretch === 'true') { if (!stretch) { this.pluginService.add(new StretchColumn(this.element, pluginData)); } else if (isStretchPlugin(stretch)) { stretch.applyStretch(this.columnProvider.getRawColumns()); } } else if (stretch) { this.pluginService.remove(stretch); } } applyFilter(cfg) { this.filterconfigchanged.emit(cfg); } applySorting(cfg) { this.sortingconfigchanged.emit(cfg); } rowHeadersChange(rowHeaders) { this.rowheaderschanged.emit(rowHeaders); } /** * Register external VNodes */ registerOutsideVNodes(elements = []) { this.extraElements = elements; } additionalDataChanged(data) { this.additionaldatachanged.emit(data); } /** * Watch for RTL changes and reapply column ordering */ rtlChanged() { // The RTL plugin will handle the transformation automatically // Just trigger a column refresh to ensure the plugin processes the change this.columnChanged(this.columns); } /** * User can add plugins via plugins property */ pluginsChanged(plugins = [], prevPlugins) { this.pluginService.addUserPluginsAndCreate(this.element, plugins, prevPlugins, this.getPluginData()); } // #endregion // #region Plugins setPlugins() { // remove old plugins if any this.removePlugins(); // pass data provider to plugins const pluginData = this.getPluginData(); if (!pluginData) { return; } // register system plugins this.setCorePlugins(pluginData); // register user plugins this.pluginsChanged(this.plugins); } setCorePlugins(pluginData) { if (this.accessible) { this.pluginService.add(new WCAGPlugin(this.element, pluginData)); } // register RTL plugin this.pluginService.add(new RTLPlugin(this.element, pluginData)); // register auto size plugin if (this.autoSizeColumn) { this.pluginService.add(new AutoSizeColumnPlugin(this.element, pluginData, typeof this.autoSizeColumn === 'object' ? this.autoSizeColumn : undefined)); } // register filter plugin if (this.filter) { this.pluginService.add(new FilterPlugin(this.element, pluginData, typeof this.filter === 'object' ? this.filter : undefined)); } // register export plugin if (this.exporting) { this.pluginService.add(new ExportFilePlugin(this.element, pluginData)); } // register sorting plugin this.pluginService.add(new SortingPlugin(this.element, pluginData)); // register grouping plugin this.pluginService.add(new GroupingRowPlugin(this.element, pluginData)); if (this.canMoveColumns) { this.pluginService.add(new ColumnMovePlugin(this.element, pluginData)); } } getPluginData() { if (!this.dimensionProvider || !this.dataProvider || !this.columnProvider || !this.viewportProvider || !this.selectionStoreConnector) { return; } // pass data provider to plugins const pluginData = { data: this.dataProvider, column: this.columnProvider, dimension: this.dimensionProvider, viewport: this.viewportProvider, selection: this.selectionStoreConnector, plugins: this.pluginService, }; return pluginData; } removePlugins() { this.pluginService.destroy(); } // #endregion // if reconnect to dom we need to set up plugins connectedCallback() { if (this.isInited) { this.setPlugins(); } this.created.emit(); } /** * Called once just after the component is first connected to the DOM. * Since this method is only called once, it's a good place to load data asynchronously and to setup the state * without triggering extra re-renders. * A promise can be returned, that can be used to wait for the first render(). */ componentWillLoad() { var _a; // #region Setup Providers this.viewportProvider = new ViewportProvider(); this.themeService = new ThemeService({ rowSize: this.rowSize, }); this.dimensionProvider = new DimensionProvider(this.viewportProvider, { realSizeChanged: (k) => this.contentsizechanged.emit(k), }); this.columnProvider = new ColumnDataProvider(); this.selectionStoreConnector = new SelectionStoreConnector(); this.dataProvider = new DataProvider(this.dimensionProvider); // #endregion this.registerOutsideVNodes(this.registerVNode); // init plugins this.setPlugins(); // set data this.applyStretch(this.stretch); this.themeChanged(this.theme, undefined, undefined, true); this.columnChanged(this.columns, undefined, undefined, true); this.dataSourceChanged(this.source, undefined, 'source'); this.dataSourceChanged(this.pinnedTopSource, undefined, 'pinnedTopSource'); this.dataSourceChanged(this.pinnedBottomSource, undefined, 'pinnedBottomSource'); if (Object.keys((_a = this.trimmedRows) !== null && _a !== void 0 ? _a : {}).length > 0) { this.trimmedRowsChanged(this.trimmedRows); } this.rowDefChanged(this.rowDefinitions); // init grouping if (this.grouping && Object.keys(this.grouping).length > 0) { this.groupingChanged(this.grouping); } // init scrolling service this.scrollingService = new GridScrollingService((e) => { var _a; (_a = this.dimensionProvider) === null || _a === void 0 ? void 0 : _a.setViewPortCoordinate({ coordinate: e.coordinate, type: e.dimension, }); this.viewportscroll.emit(e); }); this.aftergridinit.emit(); // set inited flag for connectedCallback this.isInited = true; } componentWillRender() { const event = this.beforegridrender.emit(); if (event.defaultPrevented) { return false; } return Promise.all(this.jobsBeforeRender); } componentDidRender() { this.aftergridrender.emit(); } render() { if (!this.dimensionProvider || !this.dataProvider || !this.columnProvider || !this.viewportProvider || !this.selectionStoreConnector) { return; } const contentHeight = this.dimensionProvider.stores['rgRow'].store.get('realSize'); // init viewport service helpers this.viewport = new ViewportService({ columnProvider: this.columnProvider, dataProvider: this.dataProvider, dimensionProvider: this.dimensionProvider, viewportProvider: this.viewportProvider, scrollingService: this.scrollingService, orderService: this.orderService, selectionStoreConnector: this.selectionStoreConnector, disableVirtualX: this.disableVirtualX, disableVirtualY: this.disableVirtualY, resize: c => this.aftercolumnresize.emit(c), }, contentHeight); // #region ViewportSections /** * The code renders a viewport divided into sections. * It starts by rendering the pinned start, data, and pinned end sections. * Within each section, it renders columns along with their headers, pinned top, center data, and pinned bottom. * The code iterates over the columns and their data to generate the view port's HTML structure. */ const viewportSections = []; // Row headers setting if (this.rowHeaders && this.viewport.columns.length) { const anyView = this.viewport.columns[0]; viewportSections.push(h("revogr-row-headers", { additionalData: this.additionalData, height: contentHeight, rowClass: this.rowClass, resize: this.resize, dataPorts: anyView.dataPorts, headerProp: anyView.headerProp, jobsBeforeRender: this.jobsBeforeRender, rowHeaderColumn: typeof this.rowHeaders === 'object' ? this.rowHeaders : undefined, onScrollview: ({ detail: e }) => this.scrollingService.proxyScroll(e, 'headerRow'), onRef: ({ detail: e }) => this.scrollingService.registerElement(e, 'headerRow') })); } // Viewport section render const isMobile = isMobileDevice(); const viewPortHtml = []; // Render viewports column(horizontal sections) for (let view of this.viewport.columns) { const headerProperties = Object.assign(Object.assign({}, view.headerProp), { type: view.type, additionalData: this.additionalData, viewportCol: view.viewportCol, selectionStore: view.columnSelectionStore, canResize: this.resize, readonly: this.readonly, columnFilter: !!this.filter }); // Column headers const dataViews = [ h("revogr-header", Object.assign({}, headerProperties, { slot: HEADER_SLOT })), ]; // Render viewport data (vertical sections) view.dataPorts.forEach(data => { const key = `${data.type}_${view.type}`; const dataView = (h("revogr-overlay-selection", Object.assign({}, data, { canDrag: this.canDrag && data.canDrag, isMobileDevice: isMobile, onSelectall: () => { var _a; return (_a = this.selectionStoreConnector) === null || _a === void 0 ? void 0 : _a.selectAll(); }, editors: this.editors, readonly: this.readonly, range: this.range, useClipboard: this.useClipboard, applyChangesOnClose: this.applyOnClose, additionalData: this.additionalData, slot: data.slot, onBeforenextvpfocus: (e) => { var _a; return (_a = this.selectionStoreConnector) === null || _a === void 0 ? void 0 : _a.beforeNextFocusCell(e.detail); }, onCanceledit: () => { var _a; return (_a = this.selectionStoreConnector) === null || _a === void 0 ? void 0 : _a.setEdit(false); }, onSetedit: ({ detail }) => { var _a; const event = this.beforeeditstart.emit(detail); if (!event.defaultPrevented) { (_a = this.selectionStoreConnector) === null || _a === void 0 ? void 0 : _a.setEdit(detail.val); } } }), h("revogr-data", Object.assign({}, data, { colType: view.type, key: key, readonly: this.readonly, range: this.range, rowClass: this.rowClass, rowSelectionStore: data.rowSelectionStore, additionalData: this.additionalData, jobsBeforeRender: this.jobsBeforeRender, slot: DATA_SLOT }), h("slot", { name: `data-${view.type}-${data.type}` })), h("revogr-temp-range", { selectionStore: data.selectionStore, dimensionRow: data.dimensionRow, dimensionCol: data.dimensionCol }), h("revogr-focus", { colData: data.colData, dataStore: data.dataStore, focusTemplate: this.focusTemplate, rowType: data.type, colType: view.type, selectionStore: data.selectionStore, dimensionRow: data.dimensionRow, dimensionCol: data.dimensionCol }, h("slot", { name: `focus-${view.type}-${data.type}` })))); dataViews.push(dataView); }); // Add viewport scroll in the end viewPortHtml.push(h("revogr-viewport-scroll", Object.assign({}, view.prop, { ref: el => this.scrollingService.registerElement(el, `${view.prop.key}`), onScrollviewport: e => this.scrollingService.proxyScroll(e.detail, `${view.prop.key}`), onScrollviewportsilent: e => this.scrollingService.scrollSilentService(e.detail, `${view.prop.key}`) }), dataViews)); } viewportSections.push(viewPortHtml); // #endregion const typeRow = 'rgRow'; const typeCol = 'rgCol'; const viewports = this.viewportProvider.stores; const dimensions = this.dimensionProvider.stores; const verticalScroll = (h("revogr-scroll-virtual", { class: "vertical", dimension: typeRow, clientSize: viewports[typeRow].store.get('clientSize'), virtualSize: viewports[typeRow].store.get('virtualSize'), realSize: dimensions[typeRow].store.get('realSize'), ref: el => this.scrollingService.registerElement(el, 'rowScroll'), onScrollvirtual: e => this.scrollingService.proxyScroll(e.detail) })); const horizontalScroll = (h("revogr-scroll-virtual", { class: "horizontal", dimension: typeCol, clientSize: viewports[typeCol].store.get('clientSize'), virtualSize: viewports[typeCol].store.get('virtualSize'), realSize: dimensions[typeCol].store.get('realSize'), ref: el => this.scrollingService.registerElement(el, 'colScroll'), onScrollvirtual: e => this.scrollingService.proxyScroll(e.detail) })); return (h(Host, { dir: this.rtl ? 'rtl' : 'ltr' }, this.hideAttribution ? null : (h("revogr-attribution", { class: "attribution" })), h("slot", { name: "header" }), h("div", { class: "main-viewport", onClick: (e) => { var _a; if (e.currentTarget === e.target) { (_a = this.viewport) === null || _a === void 0 ? void 0 : _a.clearEdit(); } } }, h("div", { class: "viewports" }, h("slot", { name: "viewport" }), viewportSections, verticalScroll, h(OrderRenderer, { ref: e => (this.orderService = e) }))), horizontalScroll, h("revogr-extra", { ref: el => (this.extraService = el), nodes: this.extraElements }), h("slot", { name: "footer" }))); } disconnectedCallback() { // Remove all plugins, to avoid memory leaks // and unexpected behaviour when the component is removed this.removePlugins(); } static get is() { return "revo-grid"; } static get originalStyleUrls() { return { "$": ["revo-grid-style.scss"] }; } static get styleUrls() { return { "$": ["revo-grid-style.css"] }; } static get properties() { return { "rowHeaders": { "type": "boolean", "attribute": "row-headers", "mutable": false, "complexType": { "original": "RowHeaders | boolean", "resolved": "RowHeaders | boolean", "references": { "RowHeaders": { "location": "import", "path": "@type", "id": "src/types/index.ts::RowHeaders" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Excel like functionality.\nShow row numbers.\nAlso can be used for custom row header render if object provided." }, "getter": false, "setter": false, "reflect": false }, "frameSize": { "type": "number", "attribute": "frame-size", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Defines how many rows/columns should be rendered outside visible area." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "1" }, "rowSize": { "type": "number", "attribute": "row-size", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Indicates default rgRow size.\nBy default 0, means theme package size will be applied\n\nAlternatively you can use `rowSize` to reset viewport" }, "getter": false, "setter": false, "reflect": false, "defaultValue": "0" }, "colSize": { "type": "number", "attribute": "col-size", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Indicates default column size." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "100" }, "range": { "type": "boolean", "attribute": "range", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, user can range selection." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "readonly": { "type": "boolean", "attribute": "readonly", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, grid in read only mode." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "resize": { "type": "boolean", "attribute": "resize", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, columns are resizable." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "canFocus": { "type": "boolean", "attribute": "can-focus", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true cell focus appear." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "true" }, "useClipboard": { "type": "boolean", "attribute": "use-clipboard", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true enable clipboard." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "true" }, "columns": { "type": "unknown", "attribute": "columns", "mutable": false, "complexType": { "original": "(ColumnRegular | ColumnGrouping)[]", "resolved": "(ColumnRegular | ColumnGrouping<any>)[]", "references": { "ColumnRegular": { "location": "import", "path": "@type", "id": "src/types/index.ts::ColumnRegular" }, "ColumnGrouping": { "location": "import", "path": "@type", "id": "src/types/index.ts::ColumnGrouping" } } }, "required