@visactor/vue-vtable
Version:
The vue version of VTable
1,180 lines (1,166 loc) • 59 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@visactor/vtable'), require('vue'), require('@visactor/vutils'), require('@visactor/vtable/es/vrender')) :
typeof define === 'function' && define.amd ? define(['exports', '@visactor/vtable', 'vue', '@visactor/vutils', '@visactor/vtable/es/vrender'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VueVTable = {}, global.VTable, global.Vue, global.VUtils, global.VTable.vrender));
})(this, (function (exports, VTable, vue, vutils, vrender) { 'use strict';
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var VTable__namespace = /*#__PURE__*/_interopNamespaceDefault(VTable);
function toCamelCase(str) {
return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
}
function convertPropsToCamelCase(props) {
const newProps = {};
for (const key in props) {
if (props.hasOwnProperty(key)) {
const camelCaseKey = toCamelCase(key);
newProps[camelCaseKey] = props[key];
}
}
return newProps;
}
function flattenVNodes(vnodes) {
return vnodes.flatMap(vnode => (Array.isArray(vnode.children) ? flattenVNodes(vnode.children) : vnode));
}
function isEventProp(key, props) {
return key.startsWith('on') && vutils.isFunction(props[key]);
}
function createCustomLayout(children, isHeader, args) {
const componentMap = {
Group: VTable__namespace.CustomLayout.Group,
Image: VTable__namespace.CustomLayout.Image,
Text: VTable__namespace.CustomLayout.Text,
Tag: VTable__namespace.CustomLayout.Tag,
Radio: VTable__namespace.CustomLayout.Radio,
CheckBox: VTable__namespace.CustomLayout.CheckBox
};
function createComponent(child) {
if (!child) {
return null;
}
const { type, children: childChildren } = child;
const props = convertPropsToCamelCase(child.props);
const componentName = type?.symbol || type?.name;
const ComponentClass = componentMap[componentName];
if (!ComponentClass) {
return null;
}
const component = new ComponentClass({ ...props });
bindComponentEvents(component, props);
const subChildren = resolveChildren(childChildren);
if (vutils.isObject(props?.vue)) {
const { element } = props.vue;
let targetVNode = element ?? subChildren.find(node => node?.type !== Symbol.for('v-cmt'));
if (vue.isVNode(targetVNode)) {
targetVNode = !targetVNode.key
? vue.cloneVNode(targetVNode, { key: `row_${args.row}_col_${args.col}` })
: targetVNode;
}
else {
targetVNode = null;
}
Object.assign(child.props.vue, {
element: targetVNode,
container: isHeader ? args?.table?.headerDomContainer : args?.table?.bodyDomContainer
});
return component;
}
subChildren.forEach((subChild) => {
const subComponent = createComponent(subChild);
if (subComponent) {
component.add(subComponent);
}
else if (subChild.type === Symbol.for('v-fgt')) {
subChild.children.forEach((nestedChild) => {
const nestedComponent = createComponent(nestedChild);
if (nestedComponent) {
component.add(nestedComponent);
}
});
}
});
return component;
}
function resolveChildren(childChildren) {
return childChildren?.default?.() || childChildren || [];
}
function bindComponentEvents(component, props) {
Object.keys(props).forEach(key => {
if (isEventProp(key, props)) {
let eventName;
if (key.startsWith('on')) {
eventName = key.slice(2).toLowerCase();
}
else {
eventName = toCamelCase(key.slice(2)).toLowerCase();
}
component.addEventListener(eventName, props[key]);
}
});
}
return { rootComponent: createComponent(children) };
}
function createCustomLayoutHandler(children, isHeader) {
return (args) => {
const { table, row, col, rect } = args;
const record = table.getCellOriginRecord(col, row);
const { height, width } = rect ?? table.getCellRect(col, row);
const customLayoutKey = isHeader ? 'headerCustomLayout' : 'customLayout';
if (!children[customLayoutKey]) {
return null;
}
const rootContainer = children[customLayoutKey]({ table, row, col, rect, record, height, width })[0];
const { rootComponent } = createCustomLayout(rootContainer, isHeader, args);
return {
rootContainer: rootComponent,
renderDefault: false
};
};
}
class DynamicRenderEditor {
wrapContainer;
tableContainer;
currentValue;
nodeMap;
currentContext;
constructor(currentContext) {
this.currentContext = currentContext;
this.tableContainer = null;
this.currentValue = null;
this.wrapContainer = null;
this.nodeMap = new Map();
}
registerNode(tableId, key, getNode) {
if (!vutils.isValid(tableId) || !vutils.isValid(key) || typeof getNode !== 'function') {
return;
}
if (!this.nodeMap.has(tableId)) {
this.nodeMap.set(tableId, new Map());
}
this.nodeMap.get(tableId).set(key, getNode);
}
getNode(tableId, key) {
return this.nodeMap.get(tableId)?.get(key);
}
removeNode(tableId) {
this.nodeMap.delete(tableId);
}
release(tableId) {
if (!vutils.isValid(tableId)) {
this.nodeMap.clear();
}
else {
this.removeNode(tableId);
}
}
async onStart(editorContext) {
const { value } = editorContext;
this.setValue(value);
if (!(await this.createElement(editorContext))) {
return;
}
}
async createElement(editorContext) {
const { row, col, value, table, container, referencePosition } = editorContext;
if (!container) {
return false;
}
const define = table.getBodyColumnDefine(col, row);
const { editConfig } = define || {};
const { id } = table;
const key = this.getColumnKeyField(define);
if (!vutils.isValid(key) || !vutils.isValid(id)) {
return false;
}
if (typeof editConfig?.editBefore === 'function') {
const v = await editConfig.editBefore(editorContext);
if (!v) {
table.showTooltip(col, row, {
content: editConfig.disablePrompt || 'This field is not allowed to be edited',
referencePosition: { rect: referencePosition?.rect, placement: VTable.TYPES.Placement.top },
style: {
bgColor: 'black',
color: 'white',
arrowMark: true
},
disappearDelay: 1000
});
return false;
}
}
const record = table?.getCellOriginRecord(col, row);
const vnode = this.getNode(id, key)?.({
row,
col,
value,
record,
table,
onChange: (value) => this.setValue(value)
})?.find((node) => node?.type !== Symbol.for('v-cmt'));
if (!vnode || !vue.isVNode(vnode)) {
return false;
}
this.checkToPassAppContext(vnode, table);
const wrapContainer = document.createElement('div');
wrapContainer.style.position = 'absolute';
wrapContainer.style.width = '100%';
wrapContainer.style.boxSizing = 'border-box';
const { bgColor } = table.getCellStyle(col, row) || {};
wrapContainer.style.backgroundColor = bgColor || '#FFFFFF';
this.wrapContainer = wrapContainer;
this.tableContainer = container;
this.tableContainer.appendChild(wrapContainer);
vue.render(vnode, wrapContainer);
if (referencePosition?.rect) {
this.adjustPosition(referencePosition.rect);
}
return true;
}
checkToPassAppContext(vnode, table) {
try {
const userAppContext = table.options?.customConfig?.getVueUserAppContext?.() ?? this.currentContext;
if (!!userAppContext?.components && !!userAppContext?.directives) {
vnode.appContext = userAppContext;
}
}
catch (error) { }
}
getColumnKeyField(column) {
const { field, key } = column || {};
return vutils.isValid(key) ? key : field;
}
getValue() {
return this.currentValue;
}
setValue(value) {
this.currentValue = value;
}
adjustPosition(rect) {
if (this.wrapContainer) {
this.wrapContainer.style.top = `${rect.top}px`;
this.wrapContainer.style.left = `${rect.left}px`;
this.wrapContainer.style.width = `${rect.width}px`;
this.wrapContainer.style.height = `${rect.height}px`;
}
}
async validateValue(value, oldValue, editCell, table) {
const { col, row } = editCell || {};
if (!vutils.isValid(col) || !vutils.isValid(row)) {
return true;
}
const define = table.getBodyColumnDefine(col, row);
const { editConfig } = define || {};
if (typeof editConfig?.validateValue === 'function') {
const validate = await editConfig.validateValue({ col, row, value, oldValue, table });
if (validate === false) {
const rect = table.getVisibleCellRangeRelativeRect({ col, row });
table.showTooltip(col, row, {
content: editConfig.invalidPrompt || 'invalid',
referencePosition: { rect, placement: VTable.TYPES.Placement.top },
style: {
bgColor: 'red',
color: 'white',
arrowMark: true
},
disappearDelay: 1000
});
return false;
}
return validate;
}
return true;
}
onEnd() {
if (this.wrapContainer && this.tableContainer) {
vue.render(null, this.wrapContainer);
this.tableContainer.removeChild(this.wrapContainer);
}
this.wrapContainer = null;
this.tableContainer = null;
}
isEditorElement(target) {
return this.wrapContainer?.contains(target) || this.isClickEditorElement(target);
}
isClickEditorElement(target) {
while (target) {
if (target.classList && target.classList.contains('table-editor-element')) {
return true;
}
target = target.parentNode;
}
return false;
}
}
const DYNAMIC_RENDER_EDITOR = 'dynamic-render-editor';
function checkRenderEditor(column, getEditCustomNode) {
const { editor } = column || {};
const key = getRenderEditorColumnKeyField(column);
if (!vutils.isValid(key) || editor !== DYNAMIC_RENDER_EDITOR) {
return false;
}
if (typeof getEditCustomNode === 'function') {
column.getEditCustomNode = getEditCustomNode;
return true;
}
return typeof column.getEditCustomNode === 'function';
}
function getRenderEditorColumnKeyField(column) {
const { field, key } = column || {};
return vutils.isValid(key) ? key : field;
}
function getRenderEditor(create, currentContext) {
let renderEditor = VTable.register.editor(DYNAMIC_RENDER_EDITOR);
if (!renderEditor && !!create) {
renderEditor = new DynamicRenderEditor(currentContext);
VTable.register.editor(DYNAMIC_RENDER_EDITOR, renderEditor);
}
return renderEditor;
}
function extractPivotSlotOptions(vnodes) {
const options = {
columns: [],
columnHeaderTitle: [],
rows: [],
rowHeaderTitle: [],
indicators: [],
corner: {},
tooltip: {},
menu: {}
};
const typeMapping = {
PivotColumnDimension: 'columns',
PivotColumnHeaderTitle: 'columnHeaderTitle',
PivotRowDimension: 'rows',
PivotRowHeaderTitle: 'rowHeaderTitle',
PivotCorner: 'corner',
PivotIndicator: 'indicators',
Tooltip: 'tooltip',
Menu: 'menu'
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (Array.isArray(options[optionKey])) {
if (vnode.props.hasOwnProperty('objectHandler')) {
options[optionKey].push(vnode.props.objectHandler);
}
else {
options[optionKey].push(vnode.props);
}
}
else {
options[optionKey] = vnode.props;
}
}
});
return options;
}
function extractListSlotOptions(vnodes) {
const options = {
columns: [],
tooltip: {},
menu: {}
};
const typeMapping = {
ListColumn: 'columns',
Tooltip: 'tooltip',
Menu: 'menu'
};
vnodes.forEach(vnode => {
vnode.props = convertPropsToCamelCase(vnode.props);
const typeName = vnode.type?.symbol || vnode.type?.name;
const optionKey = typeMapping[typeName];
if (optionKey) {
if (optionKey === 'columns' && vnode.children) {
if (vnode.children.customLayout) {
vnode.props.customLayout = createCustomLayoutHandler(vnode.children);
}
if (vnode.children.headerCustomLayout) {
vnode.props.headerCustomLayout = createCustomLayoutHandler(vnode.children, true);
}
checkRenderEditor(vnode.props, vnode.children.edit);
}
if (Array.isArray(options[optionKey])) {
options[optionKey].push(vnode.props);
}
else {
options[optionKey] = vnode.props;
}
}
});
return options;
}
function mergeSlotOptions(propsOptions, slotOptions) {
return {
...propsOptions,
columns: slotOptions.columns && slotOptions.columns.length ? slotOptions.columns : propsOptions.columns,
columnHeaderTitle: slotOptions.columnHeaderTitle && slotOptions.columnHeaderTitle.length
? slotOptions.columnHeaderTitle
: propsOptions.columnHeaderTitle,
rows: slotOptions.rows && slotOptions.rows.length ? slotOptions.rows : propsOptions.rows,
rowHeaderTitle: slotOptions.rowHeaderTitle && slotOptions.rowHeaderTitle.length
? slotOptions.rowHeaderTitle
: propsOptions.rowHeaderTitle,
indicators: slotOptions.indicators && slotOptions.indicators.length ? slotOptions.indicators : propsOptions.indicators,
corner: Object.keys(propsOptions.corner || {}).length ? propsOptions.corner : slotOptions.corner,
tooltip: Object.keys(slotOptions.tooltip || {}).length ? slotOptions.tooltip : propsOptions.tooltip,
menu: Object.keys(slotOptions.menu || {}).length ? slotOptions.menu : propsOptions.menu
};
}
const EVENT_TYPE = {
...VTable.ListTable.EVENT_TYPE,
...VTable.PivotTable.EVENT_TYPE,
...VTable.PivotChart.EVENT_TYPE
};
const TABLE_EVENTS = {
onClickCell: EVENT_TYPE.CLICK_CELL,
onDblClickCell: EVENT_TYPE.DBLCLICK_CELL,
onMouseDownCell: EVENT_TYPE.MOUSEDOWN_CELL,
onMouseUpCell: EVENT_TYPE.MOUSEUP_CELL,
onSelectedCell: EVENT_TYPE.SELECTED_CELL,
onKeyDown: EVENT_TYPE.KEYDOWN,
onMouseEnterTable: EVENT_TYPE.MOUSEENTER_TABLE,
onMouseLeaveTable: EVENT_TYPE.MOUSELEAVE_TABLE,
onMouseDownTable: EVENT_TYPE.MOUSEDOWN_TABLE,
onMouseMoveCell: EVENT_TYPE.MOUSEMOVE_CELL,
onMouseEnterCell: EVENT_TYPE.MOUSEENTER_CELL,
onMouseLeaveCell: EVENT_TYPE.MOUSELEAVE_CELL,
onContextMenuCell: EVENT_TYPE.CONTEXTMENU_CELL,
onResizeColumn: EVENT_TYPE.RESIZE_COLUMN,
onResizeColumnEnd: EVENT_TYPE.RESIZE_COLUMN_END,
onChangeHeaderPosition: EVENT_TYPE.CHANGE_HEADER_POSITION,
onChangeHeaderPositionStart: EVENT_TYPE.CHANGE_HEADER_POSITION_START,
onChangeHeaderPositionFail: EVENT_TYPE.CHANGE_HEADER_POSITION_FAIL,
onSortClick: EVENT_TYPE.SORT_CLICK,
onFreezeClick: EVENT_TYPE.FREEZE_CLICK,
onScroll: EVENT_TYPE.SCROLL,
onDropdownMenuClick: EVENT_TYPE.DROPDOWN_MENU_CLICK,
onMouseOverChartSymbol: EVENT_TYPE.MOUSEOVER_CHART_SYMBOL,
onDragSelectEnd: EVENT_TYPE.DRAG_SELECT_END,
onDropdownIconClick: EVENT_TYPE.DROPDOWN_ICON_CLICK,
onDropdownMenuClear: EVENT_TYPE.DROPDOWN_MENU_CLEAR,
onTreeHierarchyStateChange: EVENT_TYPE.TREE_HIERARCHY_STATE_CHANGE,
onShowMenu: EVENT_TYPE.SHOW_MENU,
onHideMenu: EVENT_TYPE.HIDE_MENU,
onIconClick: EVENT_TYPE.ICON_CLICK,
onLegendItemClick: EVENT_TYPE.LEGEND_ITEM_CLICK,
onLegendItemHover: EVENT_TYPE.LEGEND_ITEM_HOVER,
onLegendItemUnHover: EVENT_TYPE.LEGEND_ITEM_UNHOVER,
onLegendChange: EVENT_TYPE.LEGEND_CHANGE,
onMouseEnterAxis: EVENT_TYPE.MOUSEENTER_AXIS,
onMouseLeaveAxis: EVENT_TYPE.MOUSELEAVE_AXIS,
onCheckboxStateChange: EVENT_TYPE.CHECKBOX_STATE_CHANGE,
onRadioStateChange: EVENT_TYPE.RADIO_STATE_CHANGE,
onAfterRender: EVENT_TYPE.AFTER_RENDER,
onInitialized: EVENT_TYPE.INITIALIZED,
onPivotSortClick: EVENT_TYPE.PIVOT_SORT_CLICK,
onDrillMenuClick: EVENT_TYPE.DRILLMENU_CLICK,
onVChartEventType: EVENT_TYPE.VCHART_EVENT_TYPE,
onChangeCellValue: EVENT_TYPE.CHANGE_CELL_VALUE,
onMousedownFillHandle: EVENT_TYPE.MOUSEDOWN_FILL_HANDLE,
onDragFillHandleEnd: EVENT_TYPE.DRAG_FILL_HANDLE_END,
onDblclickFillHandle: EVENT_TYPE.DBLCLICK_FILL_HANDLE,
onScrollVerticalEnd: EVENT_TYPE.SCROLL_VERTICAL_END,
onScrollHorizontalEnd: EVENT_TYPE.SCROLL_HORIZONTAL_END,
onChangCellValue: EVENT_TYPE.CHANGE_CELL_VALUE,
onEmptyTipClick: EVENT_TYPE.EMPTY_TIP_CLICK,
onEmptyTipDblClick: EVENT_TYPE.EMPTY_TIP_DBLCLICK,
onButtonClick: EVENT_TYPE.BUTTON_CLICK,
onBeforeCacheChartImage: EVENT_TYPE.BEFORE_CACHE_CHART_IMAGE,
onPastedData: EVENT_TYPE.PASTED_DATA,
onSelectedClear: EVENT_TYPE.SELECTED_CLEAR
};
const TABLE_EVENTS_KEYS = Object.keys(TABLE_EVENTS);
function useEditorRender(props, tableRef) {
const instance = vue.getCurrentInstance();
const validColumns = vue.computed(() => {
const columns = props.options?.columns;
if (!vutils.isArray(columns)) {
return [];
}
return columns.filter(col => !!vutils.isObject(col) && !!checkRenderEditor(col));
});
vue.watchEffect(() => {
resolveRenderEditor();
});
vue.onBeforeUnmount(() => {
releaseRenderEditor();
});
function resolveRenderEditor() {
const id = getTableId();
if (!vutils.isValid(id)) {
return;
}
let renderEditor = getRenderEditor();
if (renderEditor) {
renderEditor.removeNode(id);
}
else if (validColumns.value.length > 0) {
renderEditor = getRenderEditor(true, instance?.appContext);
}
validColumns.value.forEach(column => {
const { getEditCustomNode } = column;
const key = getRenderEditorColumnKeyField(column);
renderEditor.registerNode(id, key, getEditCustomNode);
delete column.editCustomNode;
});
}
function releaseRenderEditor() {
const id = getTableId();
if (!vutils.isValid(id)) {
return;
}
const renderEditor = getRenderEditor();
renderEditor?.release(id);
}
function getTableId() {
return tableRef.value?.id;
}
}
class VTableVueAttributePlugin extends vrender.HtmlAttributePlugin {
name = 'VTableVueAttributePlugin';
renderQueue = new Set();
isRendering = false;
MAX_CACHE_COUNT = 100;
accessQueue = [];
VIEWPORT_BUFFER = 100;
BUFFER_ZONE = 500;
styleUpdateQueue = new Map();
styleUpdateRequested = false;
eventHandlers = new WeakMap();
currentContext;
constructor(currentContext) {
super();
this.currentContext = currentContext;
}
renderGraphicHTML(graphic) {
if (!this.checkNeedRender(graphic)) {
return;
}
this.renderQueue.add(graphic);
this.scheduleRender();
}
scheduleRender() {
if (this.isRendering) {
return;
}
this.isRendering = true;
requestAnimationFrame(() => {
this.renderQueue.forEach(graphic => {
try {
this.doRenderGraphic(graphic);
}
catch (error) {
const { id } = this.getGraphicOptions(graphic) || {};
this.removeElement(id, true);
}
});
this.renderQueue.clear();
this.isRendering = false;
});
}
doRenderGraphic(graphic) {
const { id, options } = this.getGraphicOptions(graphic);
if (!id) {
return;
}
const stage = graphic.stage;
const { element, container: expectedContainer } = options;
const actualContainer = expectedContainer ? checkFrozenContainer(graphic) : expectedContainer;
let targetMap = this.htmlMap?.[id];
if (targetMap && actualContainer && actualContainer !== targetMap.container) {
this.removeElement(id);
targetMap = null;
}
this.checkToPassAppContext(element, graphic);
if (!targetMap || !this.checkDom(targetMap.wrapContainer)) {
this.checkAndClearCache(graphic);
const { wrapContainer, nativeContainer, reuse } = this.getWrapContainer(stage, actualContainer, { id, options });
if (wrapContainer) {
const dataRenderId = `${this.renderId}`;
wrapContainer.id = id;
wrapContainer.setAttribute('data-vue-renderId', dataRenderId);
wrapContainer.style.display = 'none';
if (!reuse) {
vue.render(element, wrapContainer);
}
targetMap = {
wrapContainer,
nativeContainer,
container: actualContainer,
renderId: this.renderId,
graphic,
isInViewport: true,
lastPosition: null,
lastStyle: {}
};
this.htmlMap[id] = targetMap;
}
}
if (targetMap) {
targetMap.renderId = this.renderId;
targetMap.lastAccessed = Date.now();
this.updateAccessQueue(id);
this.updateStyleOfWrapContainer(graphic, stage, targetMap.wrapContainer, targetMap.nativeContainer);
}
}
getGraphicOptions(graphic) {
const { vue } = graphic?.attribute || {};
if (!vue) {
return null;
}
const id = vutils.isNil(vue.id) ? graphic.id ?? graphic._uid : vue.id;
return { id: `vue_${id}`, options: vue };
}
checkToPassAppContext(vnode, graphic) {
try {
const customConfig = this.getCustomConfig(graphic);
const userAppContext = customConfig?.getVueUserAppContext?.() ?? this.currentContext;
if (!!userAppContext?.components && !!userAppContext?.directives) {
vnode.appContext = userAppContext;
}
}
catch (error) { }
}
getCustomConfig(graphic) {
const target = getTargetGroup(graphic);
return target?.stage?.table?.options?.customConfig;
}
checkNeedRender(graphic) {
const { id, options } = this.getGraphicOptions(graphic) || {};
if (!id) {
return false;
}
const stage = graphic.stage;
if (!stage) {
return false;
}
const { element } = options;
if (!element) {
return false;
}
const isInViewport = this.checkInViewport(graphic);
return isInViewport;
}
checkInViewport(graphic) {
return this.checkInViewportByZone(graphic, this.VIEWPORT_BUFFER);
}
checkInBuffer(graphic) {
return this.checkInViewportByZone(graphic, this.BUFFER_ZONE);
}
checkInViewportByZone(graphic, buffer = 0) {
const { stage, globalAABBBounds: cBounds } = graphic;
if (!stage) {
return false;
}
const { AABBBounds: vBounds } = stage;
const eBounds = {
x1: vBounds.x1 - buffer,
x2: vBounds.x2 + buffer,
y1: vBounds.y1 - buffer,
y2: vBounds.y2 + buffer
};
const isIntersecting = cBounds.x1 < eBounds.x2 && cBounds.x2 > eBounds.x1 && cBounds.y1 < eBounds.y2 && cBounds.y2 > eBounds.y1;
return isIntersecting;
}
updateAccessQueue(id) {
const index = this.accessQueue.indexOf(id);
if (index > -1) {
this.accessQueue.splice(index, 1);
}
this.accessQueue.unshift(id);
}
checkAndClearCache(graphic) {
const { viewportNodes, bufferNodes, cacheNodes } = this.classifyNodes();
const total = viewportNodes.length + bufferNodes.length + cacheNodes.length;
const customConfig = this.getCustomConfig(graphic);
const maxTotal = customConfig?.maxDomCacheCount ?? this.MAX_CACHE_COUNT;
if (total <= maxTotal) {
return;
}
const exceedingCount = total - maxTotal;
let toRemove = cacheNodes.slice(0, exceedingCount);
if (toRemove.length < exceedingCount) {
const bufferCandidates = bufferNodes
.sort((a, b) => this.htmlMap[a].lastAccessed - this.htmlMap[b].lastAccessed)
.slice(0, exceedingCount - toRemove.length);
toRemove = toRemove.concat(bufferCandidates);
}
toRemove.forEach(id => this.removeElement(id, true));
}
classifyNodes() {
const viewportNodes = [];
const bufferNodes = [];
const cacheNodes = [];
Object.keys(this.htmlMap).forEach(id => {
const node = this.htmlMap[id];
if (node.isInViewport) {
viewportNodes.push(id);
}
else if (this.checkInBuffer(node.graphic)) {
bufferNodes.push(id);
}
else {
cacheNodes.push(id);
}
});
return {
viewportNodes,
bufferNodes,
cacheNodes
};
}
checkDom(dom) {
if (!dom) {
return false;
}
return document.contains(dom);
}
removeAllDom(g) {
if (this.htmlMap) {
Object.keys(this.htmlMap).forEach(key => {
this.removeElement(key, true);
});
this.htmlMap = null;
}
}
removeElement(id, clear) {
const record = this.htmlMap?.[id];
if (!record) {
return;
}
const { wrapContainer } = record;
if (!wrapContainer) {
return;
}
if (!clear) {
wrapContainer.remove();
record.isInViewport = false;
const index = this.accessQueue.indexOf(id);
if (index > -1) {
this.accessQueue.splice(index, 1);
}
}
else {
vue.render(null, wrapContainer);
this.checkDom(wrapContainer) && super.removeElement(id);
delete this.htmlMap[id];
}
this.removeWrapContainerEventListener(wrapContainer);
}
getWrapContainer(stage, userContainer, domParams) {
let nativeContainer;
if (userContainer) {
nativeContainer =
typeof userContainer === 'string' ? vrender.application.global.getElementById(userContainer) : userContainer;
}
else {
nativeContainer = stage.window.getContainer();
}
const { id } = domParams || {};
const record = this.htmlMap?.[id];
if (record && !record.isInViewport) {
const { wrapContainer } = record;
if (!this.checkDom(wrapContainer)) {
nativeContainer.appendChild(wrapContainer);
}
return {
reuse: true,
wrapContainer,
nativeContainer
};
}
return {
wrapContainer: vrender.application.global.createDom({ tagName: 'div', parent: nativeContainer }),
nativeContainer
};
}
updateStyleOfWrapContainer(graphic, stage, wrapContainer, nativeContainer) {
const { attribute, type } = graphic;
const { vue: options, width, height, visible, display, ...rest } = attribute || {};
const { x: left, y: top } = this.calculatePosition(graphic, options.anchorType);
const { left: offsetX, top: offsetTop } = this.calculateOffset(stage, nativeContainer, left, top);
const { id } = this.getGraphicOptions(graphic) || {};
const record = id ? this.htmlMap[id] : null;
if (!record) {
return;
}
const positionChanged = !record.lastPosition || record.lastPosition.x !== offsetX || record.lastPosition.y !== offsetTop;
if (!positionChanged) {
return;
}
const { pointerEvents } = options;
const calculateStyle = this.parseDefaultStyleFromGraphic(graphic);
const style = this.convertCellStyle(graphic);
Object.assign(calculateStyle, {
width: `${width}px`,
height: `${height}px`,
overflow: 'hidden',
...(style || {}),
...(rest || {}),
transform: `translate(${offsetX}px, ${offsetTop}px)`,
boxSizing: 'border-box',
display: visible !== false ? display || 'block' : 'none',
pointerEvents: pointerEvents === true ? 'all' : pointerEvents || 'none',
position: 'absolute'
});
if (calculateStyle.pointerEvents !== 'none') {
this.checkToAddEventListener(wrapContainer);
}
if (type === 'text' && options.anchorType === 'position') {
Object.assign(calculateStyle, this.getTransformOfText(graphic));
}
this.applyUserStyles(options, calculateStyle, { offsetX, offsetTop, graphic, wrapContainer });
const styleChanged = !vutils.isEqual(record.lastStyle, calculateStyle);
if (styleChanged) {
this.styleUpdateQueue.set(wrapContainer.id, calculateStyle);
this.requestStyleUpdate();
record.lastStyle = calculateStyle;
}
}
checkToAddEventListener(wrapContainer) {
if (!this.eventHandlers.has(wrapContainer)) {
const handler = (e) => {
e.preventDefault();
this.onWheel(e);
};
wrapContainer.addEventListener('wheel', handler, { passive: false });
this.eventHandlers.set(wrapContainer, handler);
}
}
requestStyleUpdate() {
if (!this.styleUpdateRequested) {
this.styleUpdateRequested = true;
requestAnimationFrame(() => {
this.styleUpdateQueue.forEach((changes, id) => {
const container = this.htmlMap?.[id]?.wrapContainer;
if (container) {
Object.assign(container.style, changes);
}
});
this.styleUpdateQueue.clear();
this.styleUpdateRequested = false;
});
}
}
convertCellStyle(graphic) {
const { col, row, stage } = getTargetGroup(graphic);
const style = stage?.table?.getCellStyle(col, row);
if (!vutils.isObject(style)) {
return;
}
const { lineHeight, padding, ...rest } = style;
return {
...rest,
padding: vutils.isArray(padding) ? padding.map(value => `${value}px`).join(' ') : padding
};
}
calculatePosition(graphic, anchorType) {
const bounds = graphic.globalAABBBounds;
if (anchorType === 'position' || bounds.empty()) {
const matrix = graphic.globalTransMatrix;
return { x: matrix.e, y: matrix.f };
}
return vutils.calculateAnchorOfBounds(bounds, anchorType || 'top-left');
}
calculateOffset(stage, nativeContainer, x, y) {
const containerTL = vrender.application.global.getElementTopLeft(nativeContainer, false);
const windowTL = stage.window.getTopLeft(false);
return {
left: x + windowTL.left - containerTL.left,
top: y + windowTL.top - containerTL.top
};
}
applyUserStyles(options, baseStyle, context) {
if (vutils.isFunction(options.style)) {
const userStyle = options.style({
top: context.offsetTop,
left: context.offsetX,
width: context.graphic.globalAABBBounds.width(),
height: context.graphic.globalAABBBounds.height()
}, context.graphic, context.wrapContainer);
Object.assign(baseStyle, userStyle);
}
else if (vutils.isObject(options.style)) {
Object.assign(baseStyle, options.style);
}
else if (vutils.isString(options.style)) {
Object.assign(baseStyle, vutils.styleStringToObject(options.style));
}
}
}
function checkFrozenContainer(graphic) {
const { col, row, stage } = getTargetGroup(graphic);
let container = graphic.attribute.vue?.container;
const { table } = stage;
if (container === table.bodyDomContainer) {
if (col < table.frozenColCount && row >= table.rowCount - table.bottomFrozenRowCount) {
container = table.bottomFrozenBodyDomContainer;
}
else if (col >= table.colCount - table.rightFrozenColCount &&
row >= table.rowCount - table.bottomFrozenRowCount) {
container = table.rightFrozenBottomDomContainer;
}
else if (row >= table.rowCount - table.bottomFrozenRowCount) {
container = table.bottomFrozenBodyDomContainer;
}
else if (col < table.frozenColCount) {
container = table.frozenBodyDomContainer;
}
else if (col >= table.colCount - table.rightFrozenColCount) {
container = table.rightFrozenBodyDomContainer;
}
}
else if (container === table.headerDomContainer) {
if (col < table.frozenColCount) {
container = table.frozenHeaderDomContainer;
}
else if (col >= table.colCount - table.rightFrozenColCount) {
container = table.rightFrozenHeaderDomContainer;
}
}
return container;
}
function getTargetGroup(target) {
while (target?.parent) {
if (target.name === 'custom-container') {
return target;
}
target = target.parent;
}
return { col: -1, row: -1, stage: null };
}
function useCellRender(props, tableRef) {
const instance = vue.getCurrentInstance();
const createReactContainer = props?.options?.customConfig?.createReactContainer;
vue.watchEffect(() => {
if (!createReactContainer) {
return;
}
const pluginService = tableRef.value?.scenegraph?.stage?.pluginService;
if (!pluginService) {
return;
}
const exist = pluginService.findPluginsByName('VTableVueAttributePlugin');
if (vutils.isArray(exist) && !!exist.length) {
return;
}
const plugin = new VTableVueAttributePlugin(instance?.appContext);
pluginService.register(plugin);
});
}
var _sfc_main$3 = vue.defineComponent({
__name: 'base-table',
props: {
type: { type: String, required: false },
options: { type: null, required: false },
records: { type: Array, required: false },
width: { type: [Number, String], required: false, default: '100%' },
height: { type: [Number, String], required: false, default: '100%' },
onReady: { type: Function, required: false },
onError: { type: Function, required: false },
keepColumnWidthChange: { type: Boolean, required: false },
onClickCell: { type: null, required: false },
onDblClickCell: { type: null, required: false },
onMouseDownCell: { type: null, required: false },
onMouseUpCell: { type: null, required: false },
onSelectedCell: { type: null, required: false },
onKeyDown: { type: null, required: false },
onMouseEnterTable: { type: null, required: false },
onMouseLeaveTable: { type: null, required: false },
onMouseDownTable: { type: null, required: false },
onMouseMoveCell: { type: null, required: false },
onMouseEnterCell: { type: null, required: false },
onMouseLeaveCell: { type: null, required: false },
onContextMenuCell: { type: null, required: false },
onResizeColumn: { type: null, required: false },
onResizeColumnEnd: { type: null, required: false },
onChangeHeaderPosition: { type: null, required: false },
onChangeHeaderPositionStart: { type: null, required: false },
onChangeHeaderPositionFail: { type: null, required: false },
onSortClick: { type: null, required: false },
onFreezeClick: { type: null, required: false },
onScroll: { type: null, required: false },
onDropdownMenuClick: { type: null, required: false },
onMouseOverChartSymbol: { type: null, required: false },
onDragSelectEnd: { type: null, required: false },
onDropdownIconClick: { type: null, required: false },
onDropdownMenuClear: { type: null, required: false },
onTreeHierarchyStateChange: { type: null, required: false },
onShowMenu: { type: null, required: false },
onHideMenu: { type: null, required: false },
onIconClick: { type: null, required: false },
onLegendItemClick: { type: null, required: false },
onLegendItemHover: { type: null, required: false },
onLegendItemUnHover: { type: null, required: false },
onLegendChange: { type: null, required: false },
onMouseEnterAxis: { type: null, required: false },
onMouseLeaveAxis: { type: null, required: false },
onCheckboxStateChange: { type: null, required: false },
onRadioStateChange: { type: null, required: false },
onAfterRender: { type: null, required: false },
onInitialized: { type: null, required: false },
onPivotSortClick: { type: null, required: false },
onDrillMenuClick: { type: null, required: false },
onVChartEventType: { type: null, required: false },
onChangeCellValue: { type: null, required: false },
onMousedownFillHandle: { type: null, required: false },
onDragFillHandleEnd: { type: null, required: false },
onDblclickFillHandle: { type: null, required: false },
onScrollVerticalEnd: { type: null, required: false },
onScrollHorizontalEnd: { type: null, required: false },
onChangCellValue: { type: null, required: false },
onEmptyTipClick: { type: null, required: false },
onEmptyTipDblClick: { type: null, required: false },
onButtonClick: { type: null, required: false },
onBeforeCacheChartImage: { type: null, required: false },
onPastedData: { type: null, required: false }
},
emits: TABLE_EVENTS_KEYS,
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const vTableContainer = vue.ref(null);
const vTableInstance = vue.shallowRef(null);
const columnWidths = vue.ref(new Map());
const pivotColumnWidths = vue.ref([]);
const pivotHeaderColumnWidths = vue.ref([]);
useEditorRender(props, vTableInstance);
useCellRender(props, vTableInstance);
__expose({ vTableInstance });
const containerWidth = vue.computed(() => (typeof props.width === 'number' ? `${props.width}px` : props.width));
const containerHeight = vue.computed(() => (typeof props.height === 'number' ? `${props.height}px` : props.height));
const emit = __emit;
const bindEvents = (instance) => {
TABLE_EVENTS_KEYS.forEach(eventKey => {
const vueEventHandler = (event) => {
emit(eventKey, event);
};
instance.on(TABLE_EVENTS[eventKey], vueEventHandler);
});
};
const createTableInstance = (Type, options) => {
const vtable = new Type(vTableContainer.value, options);
vTableInstance.value = vtable;
columnWidths.value.clear();
pivotColumnWidths.value = [];
pivotHeaderColumnWidths.value = [];
vtable.on('resize_column_end', (args) => {
if (!props.keepColumnWidthChange) {
return;
}
const { col, colWidths } = args;
const width = colWidths[col];
if (vtable.isPivotTable()) {
const path = vtable.getCellHeaderPaths(col, vtable.columnHeaderLevelCount);
let dimensions = null;
if (path.cellLocation === 'rowHeader') {
dimensions = path.rowHeaderPaths;
}
else {
dimensions = path.colHeaderPaths;
}
let found = false;
for (let i = 0; i < pivotColumnWidths.value.length; i++) {
const item = pivotColumnWidths.value[i];
if (JSON.stringify(item.dimensions) === JSON.stringify(dimensions)) {
item.width = width;
found = true;
}
}
if (!found) {
pivotColumnWidths.value.push({ dimensions, width });
}
}
else {
const define = vtable.getBodyColumnDefine(col, 0);
if (define?.key) {
columnWidths.value.set(define.key, width);
}
}
});
};
const createVTable = () => {
if (!vTableContainer.value) {
return;
}
if (vTableInstance.value) {
vTableInstance.value.release();
}
const getRecords = () => {
return props.records !== undefined && props.records !== null && props.records.length > 0
? props.records
: props.options.records;
};
try {
switch (props.type) {
case 'list':
createTableInstance(VTable.ListTable, {
...props.options,
records: getRecords()
});
break;
case 'pivot':
createTableInstance(VTable.PivotTable, {
...props.options,
records: getRecords()
});
break;
case 'chart':
createTableInstance(VTable.PivotChart, {
...props.options,
records: getRecords()
});
break;
}
bindEvents(vTableInstance.value);
props.onReady?.(vTableInstance.value, true);
}
catch (err) {
props.onError?.(err);
}
};
const updateVTable = (newOptions) => {
if (!vTableInstance.value) {
return;
}
try {
if (props.keepColumnWidthChange) {
const columnWidthConfig = updateWidthCache(columnWidths.value, pivotColumnWidths.value, vTableInstance.value);
newOptions = {
...newOptions,
columnWidthConfig: columnWidthConfig,
columnWidthConfigForRowHeader: columnWidthConfig
};
}
switch (props.type) {
case 'list':
if (vTableInstance.value instanceof VTable.ListTable) {
vTableInstance.value.updateOption(newOptions);
}
break;
case 'pivot':
if (vTableInstance.value instanceof VTable.PivotTable) {
vTableInstance.value.updateOption(newOptions);
}
break;
case 'chart':
if (vTableInstance.value instanceof VTable.PivotChart) {
vTableInstance.value.updateOption(newOptions);
}
break;