@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
1,235 lines • 150 kB
JavaScript
/*!
* 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