@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
699 lines (698 loc) • 27.2 kB
JavaScript
/*!
* Built by Revolist OU ❤️
*/
import { Host, h, } from "@stencil/core";
import ColumnService from "./column.service";
import { DATA_COL, DATA_ROW, ROW_FOCUSED_CLASS } from "../../utils/consts";
import { getSourceItem } from "../../store/index";
import RowRenderer, { PADDING_DEPTH } from "./row-renderer";
import GroupingRowRenderer from "../../plugins/groupingRow/grouping.row.renderer";
import { isGrouping } from "../../plugins/groupingRow/grouping.service";
import { RowHighlightPlugin } from "./row-highlight.plugin";
import { convertVNodeToHTML } from "../vnode/vnode.utils";
import { CellRenderer } from "./cell-renderer";
/**
* This component is responsible for rendering data
* Rows, columns, groups and cells
*/
export class RevogrData {
constructor() {
/**
* Rendered rows - virtual index vs vnode
*/
this.renderedRows = new Map();
this.readonly = undefined;
this.range = undefined;
this.rowClass = undefined;
this.additionalData = undefined;
this.rowSelectionStore = undefined;
this.viewportRow = undefined;
this.viewportCol = undefined;
this.dimensionRow = undefined;
this.colData = undefined;
this.dataStore = undefined;
this.type = undefined;
this.colType = undefined;
this.jobsBeforeRender = [];
this.providers = undefined;
}
/**
* Pointed cell update.
*/
async updateCell(e) {
var _a, _b, _c;
// Stencil tweak to update cell content
const cell = (_b = (_a = this.renderedRows.get(e.row)) === null || _a === void 0 ? void 0 : _a.$children$) === null || _b === void 0 ? void 0 : _b[e.col];
if ((_c = cell === null || cell === void 0 ? void 0 : cell.$attrs$) === null || _c === void 0 ? void 0 : _c.redraw) {
const children = await convertVNodeToHTML(this.element, cell.$attrs$.redraw);
cell.$elm$.innerHTML = children.html;
cell.$key$ = Math.random();
}
}
onDataStoreChange() {
this.onStoreChange();
}
onColDataChange() {
this.onStoreChange();
}
onStoreChange() {
var _a, _b;
(_a = this.columnService) === null || _a === void 0 ? void 0 : _a.destroy();
this.columnService = new ColumnService(this.dataStore, this.colData);
// make sure we have correct data, before render
this.providers = {
type: this.type,
colType: this.colType,
readonly: this.readonly,
data: this.dataStore,
columns: this.colData,
viewport: this.viewportCol,
dimension: this.dimensionRow,
selection: this.rowSelectionStore,
};
(_b = this.rangeUnsubscribe) === null || _b === void 0 ? void 0 : _b.call(this);
this.rangeUnsubscribe = this.rowSelectionStore.onChange('range', (e) => this.rowHighlightPlugin.selectionChange(e, this.renderedRows));
}
connectedCallback() {
this.rowHighlightPlugin = new RowHighlightPlugin();
this.onStoreChange();
}
disconnectedCallback() {
var _a, _b;
(_a = this.columnService) === null || _a === void 0 ? void 0 : _a.destroy();
(_b = this.rangeUnsubscribe) === null || _b === void 0 ? void 0 : _b.call(this);
}
async componentWillRender() {
this.beforeDataRender.emit({
rowType: this.type,
colType: this.colType,
});
return Promise.all(this.jobsBeforeRender.map(p => typeof p === 'function' ? p() : p));
}
componentDidRender() {
this.afterrender.emit({ type: this.type });
}
render() {
this.renderedRows = new Map();
const columnsData = this.columnService.columns;
if (!columnsData.length) {
return;
}
const rows = this.viewportRow.get('items');
if (!rows.length) {
return;
}
const cols = this.viewportCol.get('items');
if (!cols.length) {
return;
}
const rowsEls = [];
const depth = this.dataStore.get('groupingDepth');
const groupingCustomRenderer = this.dataStore.get('groupingCustomRenderer');
const groupDepth = this.columnService.hasGrouping ? depth : 0;
for (let rgRow of rows) {
const dataItem = getSourceItem(this.dataStore, rgRow.itemIndex);
// #region Grouping
if (isGrouping(dataItem)) {
const gmodel = Object.assign(Object.assign({}, rgRow), { index: rgRow.itemIndex, model: dataItem, groupingCustomRenderer,
// Only show expand button if grouping is enabled and not in row headers
hasExpand: this.columnService.hasGrouping && this.colType !== 'rowHeaders', columnItems: cols, providers: this.providers });
rowsEls.push(h(GroupingRowRenderer, Object.assign({}, gmodel)));
continue;
}
// #endregion
const cells = [];
// #region Cells
for (let rgCol of cols) {
const smodel = Object.assign(Object.assign({}, this.columnService.rowDataModel(rgRow.itemIndex, rgCol.itemIndex)), { providers: this.providers });
// call before cell render
const cellEvent = this.triggerBeforeCellRender(smodel, rgRow, rgCol);
// if event was prevented
if (cellEvent.defaultPrevented) {
continue;
}
const { detail: { column: columnProps, row: rowProps, model: schemaModel }, } = cellEvent;
const defaultProps = {
[DATA_COL]: columnProps.itemIndex,
[DATA_ROW]: rowProps.itemIndex,
style: {
width: `${columnProps.size}px`,
transform: `translateX(${columnProps.start}px)`,
height: rowProps.size ? `${rowProps.size}px` : undefined,
},
};
/**
* For grouping, can be removed in the future and replaced with event
*/
if (groupDepth && !columnProps.itemIndex && defaultProps.style) {
defaultProps.style.paddingLeft = `${PADDING_DEPTH * groupDepth}px`;
}
const props = this.columnService.mergeProperties(rowProps.itemIndex, columnProps.itemIndex, defaultProps, schemaModel);
// Never use webcomponent for cell render
// It's very slow because of webcomponent initialization takes time
const cellNode = h(CellRenderer, { renderProps: {
schemaModel,
additionalData: this.additionalData,
dragStartCell: this.dragStartCell,
}, cellProps: props });
cells.push(cellNode);
}
// #endregion
// #region Rows
let rowClass = this.rowClass
? this.columnService.getRowClass(rgRow.itemIndex, this.rowClass)
: '';
if (this.rowHighlightPlugin.isRowFocused(rgRow.itemIndex)) {
rowClass += ` ${ROW_FOCUSED_CLASS}`;
}
const row = (h(RowRenderer, { index: rgRow.itemIndex, rowClass: rowClass, size: rgRow.size, start: rgRow.start }, cells));
this.beforerowrender.emit({
node: row,
item: rgRow,
model: dataItem,
colType: this.columnService.type,
rowType: this.type,
});
rowsEls.push(row);
this.renderedRows.set(rgRow.itemIndex, row);
// #endregion
}
return (h(Host, null, h("slot", null), rowsEls));
}
triggerBeforeCellRender(model, row, column) {
const detail = {
column,
row,
model,
rowType: model.type,
colType: model.colType,
};
return this.beforeCellRender.emit(detail);
}
static get is() { return "revogr-data"; }
static get originalStyleUrls() {
return {
"$": ["revogr-data-style.scss"]
};
}
static get styleUrls() {
return {
"$": ["revogr-data-style.css"]
};
}
static get properties() {
return {
"readonly": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Readonly mode"
},
"getter": false,
"setter": false,
"attribute": "readonly",
"reflect": false
},
"range": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Range allowed"
},
"getter": false,
"setter": false,
"attribute": "range",
"reflect": false
},
"rowClass": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines property from which to read row class"
},
"getter": false,
"setter": false,
"attribute": "row-class",
"reflect": false
},
"additionalData": {
"type": "any",
"mutable": false,
"complexType": {
"original": "any",
"resolved": "any",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Additional data to pass to renderer\nUsed in plugins such as vue or react to pass root app entity to cells"
},
"getter": false,
"setter": false,
"attribute": "additional-data",
"reflect": false
},
"rowSelectionStore": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Observable<SelectionStoreState>",
"resolved": "ObservableMap<SelectionStoreState>",
"references": {
"Observable": {
"location": "import",
"path": "../../utils",
"id": "src/utils/index.ts::Observable"
},
"SelectionStoreState": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::SelectionStoreState"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Selection, range, focus for row selection"
},
"getter": false,
"setter": false
},
"viewportRow": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Observable<ViewportState>",
"resolved": "ObservableMap<ViewportState>",
"references": {
"Observable": {
"location": "import",
"path": "../../utils",
"id": "src/utils/index.ts::Observable"
},
"ViewportState": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::ViewportState"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Viewport Y"
},
"getter": false,
"setter": false
},
"viewportCol": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Observable<ViewportState>",
"resolved": "ObservableMap<ViewportState>",
"references": {
"Observable": {
"location": "import",
"path": "../../utils",
"id": "src/utils/index.ts::Observable"
},
"ViewportState": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::ViewportState"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Viewport X"
},
"getter": false,
"setter": false
},
"dimensionRow": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Observable<DimensionSettingsState>",
"resolved": "ObservableMap<DimensionSettingsState>",
"references": {
"Observable": {
"location": "import",
"path": "../../utils",
"id": "src/utils/index.ts::Observable"
},
"DimensionSettingsState": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DimensionSettingsState"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Dimension settings Y"
},
"getter": false,
"setter": false
},
"colData": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Observable<DSourceState<ColumnRegular, DimensionCols>>",
"resolved": "ObservableMap<DSourceState<ColumnRegular, DimensionCols>>",
"references": {
"Observable": {
"location": "import",
"path": "../../utils",
"id": "src/utils/index.ts::Observable"
},
"DSourceState": {
"location": "import",
"path": "@store",
"id": "src/store/index.ts::DSourceState"
},
"ColumnRegular": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::ColumnRegular"
},
"DimensionCols": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DimensionCols"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Column source"
},
"getter": false,
"setter": false
},
"dataStore": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Observable<DSourceState<DataType, DimensionRows>>",
"resolved": "ObservableMap<DSourceState<DataType, DimensionRows>>",
"references": {
"Observable": {
"location": "import",
"path": "../../utils",
"id": "src/utils/index.ts::Observable"
},
"DSourceState": {
"location": "import",
"path": "@store",
"id": "src/store/index.ts::DSourceState"
},
"DataType": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DataType"
},
"DimensionRows": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DimensionRows"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Data rows source"
},
"getter": false,
"setter": false
},
"type": {
"type": "string",
"mutable": false,
"complexType": {
"original": "DimensionRows",
"resolved": "\"rgRow\" | \"rowPinEnd\" | \"rowPinStart\"",
"references": {
"DimensionRows": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DimensionRows"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Row data type"
},
"getter": false,
"setter": false,
"attribute": "type",
"reflect": true
},
"colType": {
"type": "string",
"mutable": false,
"complexType": {
"original": "DimensionCols | 'rowHeaders'",
"resolved": "\"colPinEnd\" | \"colPinStart\" | \"rgCol\" | \"rowHeaders\"",
"references": {
"DimensionCols": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DimensionCols"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Column data type"
},
"getter": false,
"setter": false,
"attribute": "col-type",
"reflect": true
},
"jobsBeforeRender": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "(Promise<any> | (() => Promise<any>))[]",
"resolved": "(Promise<any> | (() => Promise<any>))[]",
"references": {
"Promise": {
"location": "global",
"id": "global::Promise"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Prevent rendering until job is done.\nCan be used for initial rendering performance improvement.\nWhen several plugins require initial rendering this will prevent double initial rendering."
},
"getter": false,
"setter": false,
"defaultValue": "[]"
}
};
}
static get states() {
return {
"providers": {}
};
}
static get events() {
return [{
"method": "beforerowrender",
"name": "beforerowrender",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Before each row render"
},
"complexType": {
"original": "BeforeRowRenderEvent",
"resolved": "BeforeRowRenderEvent<any>",
"references": {
"BeforeRowRenderEvent": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::BeforeRowRenderEvent"
}
}
}
}, {
"method": "afterrender",
"name": "afterrender",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "When data render finished for the designated type"
},
"complexType": {
"original": "{ type: DimensionRows }",
"resolved": "{ type: DimensionRows; }",
"references": {
"DimensionRows": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DimensionRows"
}
}
}
}, {
"method": "beforeCellRender",
"name": "beforecellrender",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Before each cell render function. Allows to override cell properties"
},
"complexType": {
"original": "BeforeCellRenderEvent<CellTemplateProp>",
"resolved": "BeforeCellRenderEvent<CellTemplateProp>",
"references": {
"BeforeCellRenderEvent": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::BeforeCellRenderEvent"
},
"CellTemplateProp": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::CellTemplateProp"
}
}
}
}, {
"method": "beforeDataRender",
"name": "beforedatarender",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Before data render"
},
"complexType": {
"original": "AllDimensionType",
"resolved": "AllDimensionType",
"references": {
"AllDimensionType": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::AllDimensionType"
}
}
}
}, {
"method": "dragStartCell",
"name": "dragstartcell",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Event emitted on cell drag start"
},
"complexType": {
"original": "DragStartEvent",
"resolved": "DragStartEvent",
"references": {
"DragStartEvent": {
"location": "import",
"path": "@type",
"id": "src/types/index.ts::DragStartEvent"
}
}
}
}];
}
static get methods() {
return {
"updateCell": {
"complexType": {
"signature": "(e: { row: number; col: number; }) => Promise<void>",
"parameters": [{
"name": "e",
"type": "{ row: number; col: number; }",
"docs": ""
}],
"references": {
"Promise": {
"location": "global",
"id": "global::Promise"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "Pointed cell update.",
"tags": []
}
}
};
}
static get elementRef() { return "element"; }
static get watchers() {
return [{
"propName": "dataStore",
"methodName": "onDataStoreChange"
}, {
"propName": "colData",
"methodName": "onColDataChange"
}];
}
}
//# sourceMappingURL=revogr-data.js.map