diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index b331198a8..1e735ceda 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -181,9 +181,9 @@ importers: '@types/react-dom': ^18.0.0 '@visactor/vchart': 1.9.0 '@visactor/vdataset': ~0.17.1 - '@visactor/vrender-components': 0.17.19-alpha.1 - '@visactor/vrender-core': 0.17.19-alpha.1 - '@visactor/vrender-kits': 0.17.19-alpha.1 + '@visactor/vrender-components': 0.17.20-alpha.3 + '@visactor/vrender-core': 0.17.20-alpha.3 + '@visactor/vrender-kits': 0.17.20-alpha.3 '@visactor/vscale': ~0.17.1 '@visactor/vtable-editors': workspace:* '@visactor/vutils': ~0.17.1 @@ -226,9 +226,9 @@ importers: vite-plugin-markdown: ^2.1.0 dependencies: '@visactor/vdataset': 0.17.4 - '@visactor/vrender-components': 0.17.19-alpha.1 - '@visactor/vrender-core': 0.17.19-alpha.1 - '@visactor/vrender-kits': 0.17.19-alpha.1 + '@visactor/vrender-components': 0.17.20-alpha.3 + '@visactor/vrender-core': 0.17.20-alpha.3 + '@visactor/vrender-kits': 0.17.20-alpha.3 '@visactor/vscale': 0.17.4 '@visactor/vtable-editors': link:../vtable-editors '@visactor/vutils': 0.17.4 @@ -3468,11 +3468,11 @@ packages: '@visactor/vscale': 0.17.4 '@visactor/vutils': 0.17.4 - /@visactor/vrender-components/0.17.19-alpha.1: - resolution: {integrity: sha512-86oJ8Jg4nKLHsAwNzAmk/54SdC6gx2d5Y7Cn0x6Q7hpsjb3eWrcjS8g8rHPp8HOn3Bl/mphGK5DHrpk3ZSjA0Q==} + /@visactor/vrender-components/0.17.20-alpha.3: + resolution: {integrity: sha512-85aqV302wGvwSaACuMAcv2lmqUQi0f91zMliXifuK5f2+3LOnEonLpbT5OhTxge8MLXsBlYEPnNW9BFhLmT+fQ==} dependencies: - '@visactor/vrender-core': 0.17.19-alpha.1 - '@visactor/vrender-kits': 0.17.19-alpha.1 + '@visactor/vrender-core': 0.17.20-alpha.3 + '@visactor/vrender-kits': 0.17.20-alpha.3 '@visactor/vscale': 0.17.4 '@visactor/vutils': 0.17.4 dev: false @@ -3490,8 +3490,8 @@ packages: '@visactor/vutils': 0.17.4 color-convert: 2.0.1 - /@visactor/vrender-core/0.17.19-alpha.1: - resolution: {integrity: sha512-9io+5xdvxWF4i9sr1P4W9i2PjaAqfTCnMUKC1rDl3CLzreqVCl9NA0UKqItv4OFhTCmcH7zwJzgUSjamJvbJPQ==} + /@visactor/vrender-core/0.17.20-alpha.3: + resolution: {integrity: sha512-9LodZqDMSEfTHfqpaBEUSiy+PtkT6yKfGM/kxGoqzV3XDul3M2UoskUqbkw9rzSKUr8Y9XVuC/CXG1VBCMWUcA==} dependencies: '@visactor/vutils': 0.17.4 color-convert: 2.0.1 @@ -3514,11 +3514,11 @@ packages: '@visactor/vutils': 0.17.4 roughjs: 4.5.2 - /@visactor/vrender-kits/0.17.19-alpha.1: - resolution: {integrity: sha512-O0lrvTIBZgPULCEtWqBxXZ47cQqjLtYs4MhqoFiLjrQr9VTBxomOyGAZDq08YLAfNFgQFRjiaWBeTi3JIyfxfQ==} + /@visactor/vrender-kits/0.17.20-alpha.3: + resolution: {integrity: sha512-II9fXUtMZADuk5ZH1CNzLIY8F37oosGWTFDEWogWzmVY6SAUgqavsdjJaPD3lgy3ecyepiCICHIbX+0VY4ZIrg==} dependencies: '@resvg/resvg-js': 2.4.1 - '@visactor/vrender-core': 0.17.19-alpha.1 + '@visactor/vrender-core': 0.17.20-alpha.3 '@visactor/vutils': 0.17.4 roughjs: 4.5.2 dev: false diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 102065e98..4783e57e3 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.18.4","mainProject":"@visactor/vtable","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"0.19.0","mainProject":"@visactor/vtable","nextBump":"minor"}] diff --git a/docs/assets/demo/en/edit/custom-editor.md b/docs/assets/demo/en/edit/custom-editor.md index 2daf60e5d..f780b8eec 100644 --- a/docs/assets/demo/en/edit/custom-editor.md +++ b/docs/assets/demo/en/edit/custom-editor.md @@ -59,9 +59,10 @@ Promise.all([loadCSS(cssUrl), loadJS(jsUrl)]) this.editorConfig = editorConfig; } - beginEditing(container, referencePosition, value) { + onStart({ container, referencePosition, value, endEdit }) { const that = this; this.container = container; + this.successCallback = endEdit; const input = document.createElement('input'); input.setAttribute('type', 'text'); @@ -112,21 +113,17 @@ Promise.all([loadCSS(cssUrl), loadJS(jsUrl)]) return this.element.value; } - exit() { + onEnd() { this.picker.destroy(); this.container.removeChild(this.element); } - targetIsOnEditor(target) { + isEditorElement(target) { if (target === this.element || this.picker.el.contains(target)) { return true; } return false; } - - bindSuccessCallback(successCallback) { - this.successCallback = successCallback; - } } const custom_date_editor = new DateEditor(); VTable.register.editor('custom-date', custom_date_editor); @@ -308,5 +305,5 @@ Promise.all([loadCSS(cssUrl), loadJS(jsUrl)]) }) .catch((error) => { // 处理加载错误 -}); +}); ``` diff --git a/docs/assets/demo/zh/edit/custom-editor.md b/docs/assets/demo/zh/edit/custom-editor.md index d77dbfcf8..ddec07583 100644 --- a/docs/assets/demo/zh/edit/custom-editor.md +++ b/docs/assets/demo/zh/edit/custom-editor.md @@ -23,7 +23,7 @@ let tableInstance; // 使用时需要引入插件包@visactor/vtable-editors // import * as VTable_editors from '@visactor/vtable-editors'; // 正常使用方式 const input_editor = new VTable.editors.InputEditor(); -// 官网编辑器中将 VTable.editors重命名成了VTable_editors +// 官网编辑器中将 VTable.editors重命名成了VTable_editors const input_editor = new VTable_editors.InputEditor(); VTable.register.editor('input-editor', input_editor); const timestamp = new Date().getTime(); @@ -59,9 +59,10 @@ Promise.all([loadCSS(cssUrl), loadJS(jsUrl)]) this.editorConfig = editorConfig; } - beginEditing(container, referencePosition, value) { + onStart({ container, referencePosition, value, endEdit }) { const that = this; this.container = container; + this.successCallback = endEdit; const input = document.createElement('input'); input.setAttribute('type', 'text'); @@ -112,21 +113,17 @@ Promise.all([loadCSS(cssUrl), loadJS(jsUrl)]) return this.element.value; } - exit() { + onEnd() { this.picker.destroy(); this.container.removeChild(this.element); } - targetIsOnEditor(target) { + isEditorElement(target) { if (target === this.element || this.picker.el.contains(target)) { return true; } return false; } - - bindSuccessCallback(successCallback) { - this.successCallback = successCallback; - } } const custom_date_editor = new DateEditor(); VTable.register.editor('custom-date', custom_date_editor); @@ -311,5 +308,5 @@ Promise.all([loadCSS(cssUrl), loadJS(jsUrl)]) }) .catch((error) => { // 处理加载错误 -}); +}); ``` diff --git a/docs/assets/faq/en/20-How to use VTable in Vue.md b/docs/assets/faq/en/20-How to use VTable in Vue.md index fbb8e0845..e22fa1a96 100644 --- a/docs/assets/faq/en/20-How to use VTable in Vue.md +++ b/docs/assets/faq/en/20-How to use VTable in Vue.md @@ -8,7 +8,7 @@ VTable does not encapsulate the Vue component, so how do you VTable in Vue? In Vue 3.x, using VTable -Composition API, you can refer to[ the online demo ](https://codesandbox.io/p/sandbox/mystifying-hamilton-3wl76r?file=%2Fsrc%2Fcomponents%2FPivotChart.vue%3A9339%2C1)for details. +Composition API, you can refer to[ the online demo ](https://codesandbox.io/p/devbox/magical-nash-t6t33f)for details. ## Code Example diff --git a/docs/assets/faq/zh/20-How to use VTable in Vue.md b/docs/assets/faq/zh/20-How to use VTable in Vue.md index b0b274411..4695a7e62 100644 --- a/docs/assets/faq/zh/20-How to use VTable in Vue.md +++ b/docs/assets/faq/zh/20-How to use VTable in Vue.md @@ -8,7 +8,7 @@ VTable没有封装Vue组件,那么如何在 Vue 中VTable呢? 在 Vue 3.x 中使用 Vtable -组合式 API,具体可以[参考在线 demo](https://codesandbox.io/p/sandbox/mystifying-hamilton-3wl76r?file=%2Fsrc%2Fcomponents%2FPivotChart.vue%3A9339%2C1) +组合式 API,具体可以[参考在线 demo](https://codesandbox.io/p/devbox/magical-nash-t6t33f) 不同的表格,封装方式都是类似的 diff --git a/docs/assets/guide/en/Developer_Ecology/react.md b/docs/assets/guide/en/Developer_Ecology/react.md index c5375a68a..a1e08adb0 100644 --- a/docs/assets/guide/en/Developer_Ecology/react.md +++ b/docs/assets/guide/en/Developer_Ecology/react.md @@ -143,7 +143,7 @@ The props attributes accepted by PivotTable&PivotChart are the same as options. - PivotColumnHeaderTitle: column header title configuration, consistent with the definition of columnHeaderTitle in option [api](../../option/PivotTable#rowHeaderTitle) - PivotRowHeaderTitle: row header title configuration, consistent with the definition of rowHeaderTitle in option [api](../../option/PivotTable#columnHeaderTitle) - PivotCorner: Corner configuration, consistent with the definition of corner in option [api](../../option/PivotTable#corner) - + ```jsx return ( ; - onDblClickCell?: EventCallback; - onMouseDownCell?: EventCallback; - onMouseUpCell?: EventCallback; - onSelectedCell?: EventCallback; - onKeyDown?: EventCallback; - onMouseEnterTable?: EventCallback; - onMouseLeaveTable?: EventCallback; - onMouseMoveCell?: EventCallback; - onMouseEnterCell?: EventCallback; - onMouseLeaveCell?: EventCallback; - onContextMenuCell?: EventCallback; - onResizeColumn?: EventCallback; - onResizeColumnEnd?: EventCallback; - onChangeHeaderPosition?: EventCallback; - onSortClick?: EventCallback; - onFreezeClick?: EventCallback; - onScroll?: EventCallback; - onDropdownMenuClick?: EventCallback; - onMouseOverChartSymbol?: EventCallback; - onDragSelectEnd?: EventCallback; - - onDropdownIconClick?: EventCallback; - onDropdownMenuClear?: EventCallback; - - onTreeHierarchyStateChange?: EventCallback; - - onShowMenu?: EventCallback; - onHideMenu?: EventCallback; - - onIconClick?: EventCallback; - - onLegendItemClick?: EventCallback; - onLegendItemHover?: EventCallback; - onLegendItemUnHover?: EventCallback; - onLegendChange?: EventCallback; - - onMouseEnterAxis?: EventCallback; - onMouseLeaveAxis?: EventCallback; - - onCheckboxStateChange?: EventCallback; - onAfterRender?: EventCallback; - onInitialized?: EventCallback; + onClickCell?: EventCallback; + onDblClickCell?: EventCallback; + onMouseDownCell?: EventCallback; + onMouseUpCell?: EventCallback; + onSelectedCell?: EventCallback; + onKeyDown?: EventCallback; + onMouseEnterTable?: EventCallback; + onMouseLeaveTable?: EventCallback; + onMouseMoveCell?: EventCallback; + onMouseEnterCell?: EventCallback; + onMouseLeaveCell?: EventCallback; + onContextMenuCell?: EventCallback; + onResizeColumn?: EventCallback; + onResizeColumnEnd?: EventCallback; + onChangeHeaderPosition?: EventCallback; + onSortClick?: EventCallback; + onFreezeClick?: EventCallback; + onScroll?: EventCallback; + onDropdownMenuClick?: EventCallback; + onMouseOverChartSymbol?: EventCallback; + onDragSelectEnd?: EventCallback; + + onDropdownIconClick?: EventCallback; + onDropdownMenuClear?: EventCallback; + + onTreeHierarchyStateChange?: EventCallback; + + onShowMenu?: EventCallback; + onHideMenu?: EventCallback; + + onIconClick?: EventCallback; + + onLegendItemClick?: EventCallback; + onLegendItemHover?: EventCallback; + onLegendItemUnHover?: EventCallback; + onLegendChange?: EventCallback; + + onMouseEnterAxis?: EventCallback; + onMouseLeaveAxis?: EventCallback; + + onCheckboxStateChange?: EventCallback; + onAfterRender?: EventCallback; + onInitialized?: EventCallback; // pivot table only - onPivotSortClick?: EventCallback; - onDrillMenuClick?: EventCallback; + onPivotSortClick?: EventCallback; + onDrillMenuClick?: EventCallback; // pivot chart only - onVChartEventType?: EventCallback; + onVChartEventType?: EventCallback; } ``` diff --git a/docs/assets/guide/en/Developer_Ecology/vue.md b/docs/assets/guide/en/Developer_Ecology/vue.md index a2eba0c42..ddb2ee243 100644 --- a/docs/assets/guide/en/Developer_Ecology/vue.md +++ b/docs/assets/guide/en/Developer_Ecology/vue.md @@ -2,7 +2,7 @@ In Vue 3.x, using VTable -Composition API, you can refer to[ the online demo ](https://codesandbox.io/p/devbox/vchart-demo-vue-73h8wl)for details. +Composition API, you can refer to[ the online demo ](https://codesandbox.io/p/devbox/magical-nash-t6t33f)for details. ## Code Example @@ -136,4 +136,14 @@ onMounted(() => { }); +``` + +## Notice + +If you need to use ref to save table instances, please use `shallowRef`. Using `ref` will affect internal attribute updates and cause rendering exceptions. + + +``` +const tableInstance = shallowRef(); +tableInstance.value = new ListTable(listTableRef.value, option); ``` \ No newline at end of file diff --git a/docs/assets/guide/en/edit/edit_cell.md b/docs/assets/guide/en/edit/edit_cell.md index 2d04e6a29..b5a9cf951 100644 --- a/docs/assets/guide/en/edit/edit_cell.md +++ b/docs/assets/guide/en/edit/edit_cell.md @@ -67,7 +67,7 @@ If the several editors provided by the VTable-ediotrs library cannot meet your n You can use the following flow chart to understand the relationship between the editor and VTable: -![image](https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/editCellProcess.png) +![image](https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/editCellProcess1.png) The following is sample code for a custom editor: @@ -157,31 +157,51 @@ VTable.register.editor('custom-date', custom_date_editor); ``` In the above example, we created a custom editor named `DateEditor` and implemented the methods required by the `IEditor` interface. Then, we register the custom editor into the VTable through the `VTable.register.editor` method for use in the table. -`IEditor` interface's definition(github:https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts): -``` -export interface IEditor { - /** 编辑器类型 */ - editorType?: string; - /** 编辑配置 */ - editorConfig: any; - /* 编辑器挂载的容器 由vtable传入 */ +`IEditor` [definition](https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts): +```ts +export interface IEditor { + /** Called when cell enters edit mode. */ + onStart?: (context: EditContext) => void; + /** called when cell exits edit mode. */ + onEnd?: () => void; + /** + * Called when user click somewhere while editor is in edit mode. + * + * If returns falsy, VTable will exit edit mode. + * + * If returns truthy or not defined, nothing will happen. + * Which means, in this scenario, you need to call `endEdit` manually + * to end edit mode. + */ + isEditorElement?: (target: HTMLElement) => boolean; + /** + * Called when editor mode is exited by any means. + * Expected to return the current value of the cell. + */ + getValue: () => V; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface EditContext { + /** Container element of the VTable instance. */ container: HTMLElement; - /** 编辑完成后调用。注意如果是(enter键,鼠标点击其他位置)这类编辑完成已有VTable实现,编辑器内部有完成按钮等类似的完成操作需要调用这个方法 */ - successCallback?: Function; - /** 获取编辑器当前值 */ - getValue: () => string | number | null; - /** 编辑器进入编辑状态 */ - beginEditing: ( - container: HTMLElement, - referencePosition: { rect: RectProps; placement?: Placement }, - value?: string - ) => void; - /** 编辑器退出编辑状态 */ - exit: () => void; - /** 判断鼠标点击的target是否属于编辑器内部元素 */ - targetIsOnEditor: (target: HTMLElement) => boolean; - /** 由VTable调用来传入编辑成功的回调 请将callback赋值到successCallback */ - bindSuccessCallback?: (callback: Function) => void; + /** Position info of the cell that is being edited. */ + referencePosition: ReferencePosition; + /** Cell value before editing. */ + value: V; + /** + * Callback function that can be used to end edit mode. + * + * In most cases you don't need to call this function, + * since Enter key click is handled by VTable automatically, + * and mouse click can be handled by `isEditorElement`. + * + * However, if your editor has its own complete button, + * or you have external elements like Tooltip, + * you may want to use this callback to help you + * end edit mode. + */ + endEdit: () => void; } ``` @@ -207,17 +227,20 @@ tableInstance.records; ## 7. Edit trigger timing Editing trigger timing support: double-click a cell to enter editing, click a cell to enter editing, and call the API to manually start editing. -``` +```ts +interface ListTableConstructorOptions { /** Editing trigger timing Double-click event Click event API manually starts editing. The default is double-click 'doubleclick' */ editCellTrigger?: 'doubleclick' | 'click' | 'api'; + // ... +} ``` ## 8. Related APIs -``` +```ts +interface ListTableAPI { /** Set the value of the cell. Note that it corresponds to the original value of the source data, and the vtable instance records will be modified accordingly */ changeCellValue: (col: number, row: number, value: string | number | null) => void; - /** * Batch update data of multiple cells * @param col The starting column number of pasted data @@ -225,19 +248,18 @@ Editing trigger timing support: double-click a cell to enter editing, click a ce * @param values Data array of multiple cells */ changeCellValues(startCol: number, startRow: number, values: string[][]) - /** Get the editor of cell configuration */ getEditor: (col: number, row: number) => IEditor; - /** Enable cell editing */ startEditCell: (col?: number, row?: number) => void; - /** End editing */ completeEditCell: () => void; + // ... +} ``` ## Header Editing The basic table supports editing the display title in the header. You can enable this by configuring `headerEditor` globally or within a column. The usage is the same as `editor`. -Through the above steps, you can create a table with editing functions, select the appropriate editor type according to business needs, customize the editor, listen to editing events, and obtain edited data. In this way, users can easily edit the data in the table, and you can process the edited data accordingly. \ No newline at end of file +Through the above steps, you can create a table with editing functions, select the appropriate editor type according to business needs, customize the editor, listen to editing events, and obtain edited data. In this way, users can easily edit the data in the table, and you can process the edited data accordingly. diff --git a/docs/assets/guide/en/interaction/drag_header.md b/docs/assets/guide/en/interaction/drag_header.md index 0c8905131..bbf169605 100644 --- a/docs/assets/guide/en/interaction/drag_header.md +++ b/docs/assets/guide/en/interaction/drag_header.md @@ -60,4 +60,18 @@ Sometimes, we don't want certain columns to participate in drag-and-drop transpo As with the above code, the "Sales" column cannot be dragged and transposed. +## Restrict dragging of frozen columns +Drag and drop the table header to move the position. Select different effects according to the business scenario for the rules of the frozen part. For example, you can prohibit dragging of frozen columns, or adjust the number of frozen columns. + +Constraints can be made through the following configuration (only valid for ListTable): +``` +Drag the table header to move the position. Rules for frozen parts. The default is fixedFrozenCount. +frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount'; +``` +The different rules are described below: + +- "disabled" (disables adjusting the position of frozen columns): The headers of other columns are not allowed to be moved into the frozen column, nor are the frozen columns allowed to be moved out. The frozen column remains unchanged. +- "adjustFrozenCount" (adjust the number of frozen columns based on the interaction results): allows the headers of other columns to move into the frozen column, and the frozen column to move out, and adjusts the number of frozen columns based on the dragging action. When the headers of other columns are dragged into the frozen column position, the number of frozen columns increases; when the headers of other columns are dragged out of the frozen column position, the number of frozen columns decreases. +- "fixedFrozenCount" (can adjust frozen columns and keep the number of frozen columns unchanged): Allows you to freely drag the headers of other columns into or out of the frozen column position while keeping the number of frozen columns unchanged. + So far, we have introduced the drag-and-drop header transposition function of VTable, including the activation of the drag-and-drop header transposition function, the style configuration of the drag-and-drop header transposition mark line, and whether a certain column can be dragged. By mastering these functions, you can more easily perform data analytics and processing in VTable. diff --git a/docs/assets/guide/zh/Developer_Ecology/react.md b/docs/assets/guide/zh/Developer_Ecology/react.md index 9907858ff..18343bf79 100644 --- a/docs/assets/guide/zh/Developer_Ecology/react.md +++ b/docs/assets/guide/zh/Developer_Ecology/react.md @@ -143,7 +143,7 @@ PivotTable&PivotChart接受的props属性与option一致,子组件如下: - PivotColumnHeaderTitle: 列表头标题配置,同option中的columnHeaderTitle的定义一致 [api](../../option/PivotTable#rowHeaderTitle) - PivotRowHeaderTitle: 行头标题配置,同option中的rowHeaderTitle的定义一致 [api](../../option/PivotTable#columnHeaderTitle) - PivotCorner: 角头配置,同option中的corner的定义一致 [api](../../option/PivotTable#corner) - + ```jsx return ( ; - onDblClickCell?: EventCallback; - onMouseDownCell?: EventCallback; - onMouseUpCell?: EventCallback; - onSelectedCell?: EventCallback; - onKeyDown?: EventCallback; - onMouseEnterTable?: EventCallback; - onMouseLeaveTable?: EventCallback; - onMouseMoveCell?: EventCallback; - onMouseEnterCell?: EventCallback; - onMouseLeaveCell?: EventCallback; - onContextMenuCell?: EventCallback; - onResizeColumn?: EventCallback; - onResizeColumnEnd?: EventCallback; - onChangeHeaderPosition?: EventCallback; - onSortClick?: EventCallback; - onFreezeClick?: EventCallback; - onScroll?: EventCallback; - onDropdownMenuClick?: EventCallback; - onMouseOverChartSymbol?: EventCallback; - onDragSelectEnd?: EventCallback; - - onDropdownIconClick?: EventCallback; - onDropdownMenuClear?: EventCallback; - - onTreeHierarchyStateChange?: EventCallback; - - onShowMenu?: EventCallback; - onHideMenu?: EventCallback; - - onIconClick?: EventCallback; - - onLegendItemClick?: EventCallback; - onLegendItemHover?: EventCallback; - onLegendItemUnHover?: EventCallback; - onLegendChange?: EventCallback; - - onMouseEnterAxis?: EventCallback; - onMouseLeaveAxis?: EventCallback; - - onCheckboxStateChange?: EventCallback; - onAfterRender?: EventCallback; - onInitialized?: EventCallback; + onClickCell?: EventCallback; + onDblClickCell?: EventCallback; + onMouseDownCell?: EventCallback; + onMouseUpCell?: EventCallback; + onSelectedCell?: EventCallback; + onKeyDown?: EventCallback; + onMouseEnterTable?: EventCallback; + onMouseLeaveTable?: EventCallback; + onMouseMoveCell?: EventCallback; + onMouseEnterCell?: EventCallback; + onMouseLeaveCell?: EventCallback; + onContextMenuCell?: EventCallback; + onResizeColumn?: EventCallback; + onResizeColumnEnd?: EventCallback; + onChangeHeaderPosition?: EventCallback; + onSortClick?: EventCallback; + onFreezeClick?: EventCallback; + onScroll?: EventCallback; + onDropdownMenuClick?: EventCallback; + onMouseOverChartSymbol?: EventCallback; + onDragSelectEnd?: EventCallback; + + onDropdownIconClick?: EventCallback; + onDropdownMenuClear?: EventCallback; + + onTreeHierarchyStateChange?: EventCallback; + + onShowMenu?: EventCallback; + onHideMenu?: EventCallback; + + onIconClick?: EventCallback; + + onLegendItemClick?: EventCallback; + onLegendItemHover?: EventCallback; + onLegendItemUnHover?: EventCallback; + onLegendChange?: EventCallback; + + onMouseEnterAxis?: EventCallback; + onMouseLeaveAxis?: EventCallback; + + onCheckboxStateChange?: EventCallback; + onAfterRender?: EventCallback; + onInitialized?: EventCallback; // pivot table only - onPivotSortClick?: EventCallback; - onDrillMenuClick?: EventCallback; + onPivotSortClick?: EventCallback; + onDrillMenuClick?: EventCallback; // pivot chart only - onVChartEventType?: EventCallback; + onVChartEventType?: EventCallback; } ``` diff --git a/docs/assets/guide/zh/Developer_Ecology/vue.md b/docs/assets/guide/zh/Developer_Ecology/vue.md index 72607f95c..957ff6bb6 100644 --- a/docs/assets/guide/zh/Developer_Ecology/vue.md +++ b/docs/assets/guide/zh/Developer_Ecology/vue.md @@ -2,7 +2,7 @@ 在 Vue 3.x 中使用 Vtable -组合式 API,具体可以[参考在线 demo](https://codesandbox.io/p/devbox/vchart-demo-vue-73h8wl) +组合式 API,具体可以[参考在线 demo](https://codesandbox.io/p/devbox/magical-nash-t6t33f) ## 代码示例 @@ -136,3 +136,13 @@ onMounted(() => { ``` + +## 注意 + +如果需要使用ref保存table实例,请使用`shallowRef`,使用`ref`会影响内部属性更新,导致渲染异常。 + + +``` +const tableInstance = shallowRef(); +tableInstance.value = new ListTable(listTableRef.value, option); +``` \ No newline at end of file diff --git a/docs/assets/guide/zh/edit/edit_cell.md b/docs/assets/guide/zh/edit/edit_cell.md index 4126814c9..89db3eff2 100644 --- a/docs/assets/guide/zh/edit/edit_cell.md +++ b/docs/assets/guide/zh/edit/edit_cell.md @@ -65,19 +65,22 @@ columns: [ editor配置可以在columns中,也可以在全局options中定义,同时可以支持自定义函数写法: -``` -editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); +```ts +interface ColumnDefine { + // ... + editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); +} ``` ## 4. 自定义实现一个编辑器: 如果VTable-ediotrs库提供的几种编辑器无法满足你的需求,你可以自定义实现一个编辑器。为此,你需要创建一个类,实现编辑器接口(`IEditor`)的要求,并提供必要的方法和逻辑。 可以结合下面这个流程图来理解编辑器和VTable之间的关系: -![image](https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/editCellProcess.png) +![image](https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/editCellProcess1.png) 以下是一个自定义编辑器的示例代码: -```javascript +```ts class DateEditor implements IEditor { editorConfig: any; element: HTMLInputElement; @@ -87,9 +90,10 @@ class DateEditor implements IEditor { constructor(editorConfig: any) { this.editorConfig = editorConfig; } - beginEditing(container: HTMLElement, referencePosition: { rect: RectProps; placement?: Placement }, value?: string) { + onStart({ container, value, referencePosition, endEdit }: EditContext) { const that = this; this.container = container; + this.successCallback = endEdit // const cellValue = luxon.DateTime.fromFormat(value, 'yyyy年MM月dd日').toFormat('yyyy-MM-dd'); const input = document.createElement('input'); @@ -143,19 +147,16 @@ class DateEditor implements IEditor { getValue() { return this.element.value; } - exit() { + onEnd() { this.picker.destroy(); this.container.removeChild(this.element); } - targetIsOnEditor(target: HTMLElement) { + isEditorElement(target: HTMLElement) { if (target === this.element || this.picker.el.contains(target)) { return true; } return false; } - bindSuccessCallback(successCallback: Function) { - this.successCallback = successCallback; - } } const custom_date_editor = new DateEditor({}); VTable.register.editor('custom-date', custom_date_editor); @@ -164,31 +165,42 @@ VTable.register.editor('custom-date', custom_date_editor); 在上面的示例中,我们创建了一个名为`DateEditor`的自定义编辑器,并实现了`IEditor`接口所要求的方法。然后,我们通过`VTable.register.editor`方法将自定义编辑器注册到VTable中,以便在表格中使用。 -`IEditor`接口具体定义的源码(github:https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts): -``` -export interface IEditor { - /** 编辑器类型 */ - editorType?: string; - /** 编辑配置 */ - editorConfig: any; - /* 编辑器挂载的容器 由vtable传入 */ +`IEditor` 接口[定义](https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts): +```ts +export interface IEditor { + /** * 单元格进入编辑状态时调用 */ + onStart?: (context: EditContext) => void; + /** * 单元格退出编辑状态时调用 */ + onEnd?: () => void; + /** + * 如果提供了此函数,VTable 将会在用户点击其他地方时调用此函数。 + * 如果此函数返回了一个假值,VTable 将会调用 `onEnd` 并退出编辑状态。 + */ + isEditorElement?: (target: HTMLElement) => boolean; + /** * 获取编辑器当前值。将在 `onEnd` 调用后调用。 */ + getValue: () => V; + // ... +} + +export interface EditContext { + /** VTable 实例所处的容器元素 */ container: HTMLElement; - /** 编辑完成后调用。注意如果是(enter键,鼠标点击其他位置)这类编辑完成已有VTable实现,编辑器内部有完成按钮等类似的完成操作需要调用这个方法 */ - successCallback?: Function; - /** 获取编辑器当前值 */ - getValue: () => string | number | null; - /** 编辑器进入编辑状态 */ - beginEditing: ( - container: HTMLElement, - referencePosition: { rect: RectProps; placement?: Placement }, - value?: string - ) => void; - /** 编辑器退出编辑状态 */ - exit: () => void; - /** 判断鼠标点击的target是否属于编辑器内部元素 */ - targetIsOnEditor: (target: HTMLElement) => boolean; - /** 由VTable调用来传入编辑成功的回调 请将callback赋值到successCallback */ - bindSuccessCallback?: (callback: Function) => void; + /** 正在编辑的单元格位置信息 */ + referencePosition: ReferencePosition; + /** 正在进入编辑状态的单元格当前值 */ + value: V; + /** + * 用于结束编辑状态的回调。 + * + * 大多数情况下你不需要使用此回调,因为 VTable 已经自带了 Enter 键按下 + * 来结束编辑状态的行为;而鼠标点击其他位置来结束编辑状态的行为你也 + * 可以通过 `isEditorElement` 函数来获得。 + * + * 然而,如果你有特殊的需求,比如你想在编辑器内部提供一个“完成”按钮, + * 或者你有像 Tooltip 这样无法获取到的外部元素, + * 这时你可以保存这个回调并在你需要的时候来手动结束编辑状态。 + */ + endEdit: () => void; } ``` @@ -214,36 +226,38 @@ tableInstance.records; ## 7. 编辑触发时机 编辑触发时机支持:双击单元格进入编辑,单击单元格进入编辑,调用api手动开启编辑. -``` +```ts +interface ListTableConstructorOptions { /** 编辑触发时机 双击事件 单击事件 api手动开启编辑。默认为双击'doubleclick' */ editCellTrigger?: 'doubleclick' | 'click' | 'api'; + // ... +} ``` ## 8. 相关api -``` +```ts +interface ListTableAPI { /** 设置单元格的value值,注意对应的是源数据的原始值,vtable实例records会做对应修改 */ changeCellValue: (col: number, row: number, value: string | number | null) => void; - /** * 批量更新多个单元格的数据 * @param col 粘贴数据的起始列号 * @param row 粘贴数据的起始行号 * @param values 多个单元格的数据数组 */ - changeCellValues(startCol: number, startRow: number, values: string[][]) - + changeCellValues(startCol: number, startRow: number, values: string[][]) /** 获取单元格配置的编辑器 */ getEditor: (col: number, row: number) => IEditor; - /** 开启单元格编辑 */ startEditCell: (col?: number, row?: number) => void; - /** 结束编辑 */ completeEditCell: () => void; + // ... +} ``` ## 表头编辑 基本表格可支持编辑表头显示标题title,在全局或者在column中配置`headerEditor`来开启,具体用法同`editor`。 -通过以上步骤,你可以创建一个具有编辑功能的表格,并根据业务需求选择合适的编辑器类型、自定义编辑器、监听编辑事件以及获取编辑后的数据。这样,用户就可以方便地编辑表格中的数据,并且你可以对编辑后的数据进行相应的处理。 \ No newline at end of file +通过以上步骤,你可以创建一个具有编辑功能的表格,并根据业务需求选择合适的编辑器类型、自定义编辑器、监听编辑事件以及获取编辑后的数据。这样,用户就可以方便地编辑表格中的数据,并且你可以对编辑后的数据进行相应的处理。 diff --git a/docs/assets/guide/zh/interaction/drag_header.md b/docs/assets/guide/zh/interaction/drag_header.md index f624c61a9..cd39e2eb6 100644 --- a/docs/assets/guide/zh/interaction/drag_header.md +++ b/docs/assets/guide/zh/interaction/drag_header.md @@ -60,4 +60,18 @@ const table = new VTable.ListTable({ 如上述代码,“销量”列不可拖拽换位。 +## 限制冻结列的拖拽 +拖拽表头移动位置 针对冻结部分的规则根据业务场景选择不同的效果,如可以禁止拖拽冻结列,或者调整冻结列的数量。 + +可以通过下面的配置进行约束(仅针对ListTable生效): +``` +拖拽表头移动位置 针对冻结部分的规则 默认为fixedFrozenCount +frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount'; +``` +不同规则描述如下: + +- "disabled"(禁止调整冻结列位置):不允许其他列的表头移入冻结列,也不允许冻结列移出,冻结列保持不变。 +- "adjustFrozenCount"(根据交互结果调整冻结数量):允许其他列的表头移入冻结列,及冻结列移出,并根据拖拽的动作调整冻结列的数量。当其他列的表头被拖拽进入冻结列位置时,冻结列数量增加;当其他列的表头被拖拽移出冻结列位置时,冻结列数量减少。 +- "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 + 至此,我们已经介绍了 VTable 的拖拽表头换位功能,括拖拽表头换位功能的开启、拖拽表头换位标记线的样式配置以及某一列是否可以拖拽。通过掌握这些功能,您可以更便捷地在 VTable 中进行数据分析与处理。 diff --git a/docs/assets/option/en/column/base-column-type.md b/docs/assets/option/en/column/base-column-type.md index 8b48d959a..ceeacb5e1 100644 --- a/docs/assets/option/en/column/base-column-type.md +++ b/docs/assets/option/en/column/base-column-type.md @@ -207,7 +207,7 @@ Configure the column cell editor ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts . +Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . ${prefix} headerEditor (string|Object|Function) @@ -218,4 +218,4 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ``` ${prefix} columns (Array) -Configure arrays with upper columns, nesting structures to describe column grouping relationships. \ No newline at end of file +Configure arrays with upper columns, nesting structures to describe column grouping relationships. diff --git a/docs/assets/option/en/indicator/base-indicator-type.md b/docs/assets/option/en/indicator/base-indicator-type.md index 8fe6ed226..f098c3ddc 100644 --- a/docs/assets/option/en/indicator/base-indicator-type.md +++ b/docs/assets/option/en/indicator/base-indicator-type.md @@ -160,4 +160,4 @@ Configure the indicator cell editor ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts . \ No newline at end of file +Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . diff --git a/docs/assets/option/en/table/listTable.md b/docs/assets/option/en/table/listTable.md index 81bd053ec..95b09fb82 100644 --- a/docs/assets/option/en/table/listTable.md +++ b/docs/assets/option/en/table/listTable.md @@ -76,7 +76,7 @@ Global configuration cell editor ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts . +Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . ${prefix} headerEditor (string|Object|Function) @@ -97,4 +97,12 @@ When displayed as a tree structure, the indentation value of each layer of conte ## hierarchyExpandLevel(number) -When displayed as a tree structure, the number of levels is expanded by default. The default value is 1, which only displays the root node. If configured to `Infinity`, all nodes will be expanded. \ No newline at end of file +When displayed as a tree structure, the number of levels is expanded by default. The default value is 1, which only displays the root node. If configured to `Infinity`, all nodes will be expanded. + +## frozenColDragHeaderMode(string) = 'fixedFrozenCount' + +Drag the table header to move the position. Rules for frozen parts. The default is fixedFrozenCount. + +- "disabled" (disables adjusting the position of frozen columns): The headers of other columns are not allowed to be moved into the frozen column, nor are the frozen columns allowed to be moved out. The frozen column remains unchanged. +- "adjustFrozenCount" (adjust the number of frozen columns based on the interaction results): allows the headers of other columns to move into the frozen column, and the frozen column to move out, and adjusts the number of frozen columns based on the dragging action. When the headers of other columns are dragged into the frozen column position, the number of frozen columns increases; when the headers of other columns are dragged out of the frozen column position, the number of frozen columns decreases. +- "fixedFrozenCount" (can adjust frozen columns and keep the number of frozen columns unchanged): Allows you to freely drag the headers of other columns into or out of the frozen column position while keeping the number of frozen columns unchanged. diff --git a/docs/assets/option/en/table/pivotTable.md b/docs/assets/option/en/table/pivotTable.md index 22e43eed4..2bf13d14e 100644 --- a/docs/assets/option/en/table/pivotTable.md +++ b/docs/assets/option/en/table/pivotTable.md @@ -339,10 +339,10 @@ Global configuration cell editor ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts . +Among them, IEditor is the editor interface defined in @visactor/vtable-editors. For details, please refer to the source code: https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts . {{ use: common-option-secondary( prefix = '#', tableType = 'listTable' ) }} -``` \ No newline at end of file +``` diff --git a/docs/assets/option/zh/column/base-column-type.md b/docs/assets/option/zh/column/base-column-type.md index c3e9e5f91..5aaeac17d 100644 --- a/docs/assets/option/zh/column/base-column-type.md +++ b/docs/assets/option/zh/column/base-column-type.md @@ -217,7 +217,7 @@ ${prefix} editor (string|Object|Function) ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts。 +其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 ${prefix} headerEditor (string|Object|Function) @@ -227,4 +227,4 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ``` ${prefix} columns (Array) -同上层的列配置数组,嵌套结构来描述列分组关系。 \ No newline at end of file +同上层的列配置数组,嵌套结构来描述列分组关系。 diff --git a/docs/assets/option/zh/indicator/base-indicator-type.md b/docs/assets/option/zh/indicator/base-indicator-type.md index 9896cd763..0756b2746 100644 --- a/docs/assets/option/zh/indicator/base-indicator-type.md +++ b/docs/assets/option/zh/indicator/base-indicator-type.md @@ -160,4 +160,4 @@ ${prefix} editor (string|Object|Function) ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts。 \ No newline at end of file +其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 diff --git a/docs/assets/option/zh/table/listTable.md b/docs/assets/option/zh/table/listTable.md index 5d7f95f17..7e8b7b643 100644 --- a/docs/assets/option/zh/table/listTable.md +++ b/docs/assets/option/zh/table/listTable.md @@ -75,7 +75,7 @@ SortState { ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts。 +其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 ${prefix} headerEditor (string|Object|Function) @@ -96,3 +96,12 @@ headerEditor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI } ## hierarchyExpandLevel(number) 展示为树形结构时,默认展开层数。默认为1只显示根节点,配置为`Infinity`则全部展开。 + + +## frozenColDragHeaderMode(string) = 'fixedFrozenCount' + +拖拽表头移动位置 针对冻结部分的规则 默认为fixedFrozenCount + +- "disabled"(禁止调整冻结列位置):不允许其他列的表头移入冻结列,也不允许冻结列移出,冻结列保持不变。 +- "adjustFrozenCount"(根据交互结果调整冻结数量):允许其他列的表头移入冻结列,及冻结列移出,并根据拖拽的动作调整冻结列的数量。当其他列的表头被拖拽进入冻结列位置时,冻结列数量增加;当其他列的表头被拖拽移出冻结列位置时,冻结列数量减少。 +- "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 \ No newline at end of file diff --git a/docs/assets/option/zh/table/pivotTable.md b/docs/assets/option/zh/table/pivotTable.md index dc47befe2..22e7323c6 100644 --- a/docs/assets/option/zh/table/pivotTable.md +++ b/docs/assets/option/zh/table/pivotTable.md @@ -343,7 +343,7 @@ export interface IIndicatorHeaderNode { ``` editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); ``` -其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/feat/editCell/packages/vtable-editors/src/types.ts。 +其中IEditor是@visactor/vtable-editors中定义的编辑器接口,具体可以参看源码:https://github.com/VisActor/VTable/blob/main/packages/vtable-editors/src/types.ts。 {{ use: common-option-secondary( prefix = '#', diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index b2c27ad93..34a42b702 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "0.18.4", + "version": "0.19.0", "description": "The react version of VTable", "keywords": [ "react", diff --git a/packages/react-vtable/src/eventsUtils.ts b/packages/react-vtable/src/eventsUtils.ts index 85ab9629d..487eaaad4 100644 --- a/packages/react-vtable/src/eventsUtils.ts +++ b/packages/react-vtable/src/eventsUtils.ts @@ -1,6 +1,6 @@ import { ListTable, PivotTable, PivotChart } from '@visactor/vtable'; import type { IVTable } from './tables/base-table'; -import type { TableEventHandlersEventArgumentMap } from '@visactor/vtable/src/ts-types'; +import type { TYPES } from '@visactor/vtable'; export type EventCallback = (params: Params) => void; @@ -11,56 +11,56 @@ const EVENT_TYPE = { }; export interface EventsProps { - onClickCell?: EventCallback; - onDblClickCell?: EventCallback; - onMouseDownCell?: EventCallback; - onMouseUpCell?: EventCallback; - onSelectedCell?: EventCallback; - onKeyDown?: EventCallback; - onMouseEnterTable?: EventCallback; - onMouseLeaveTable?: EventCallback; - onMouseMoveCell?: EventCallback; - onMouseEnterCell?: EventCallback; - onMouseLeaveCell?: EventCallback; - onContextMenuCell?: EventCallback; - onResizeColumn?: EventCallback; - onResizeColumnEnd?: EventCallback; - onChangeHeaderPosition?: EventCallback; - onSortClick?: EventCallback; - onFreezeClick?: EventCallback; - onScroll?: EventCallback; - onDropdownMenuClick?: EventCallback; - onMouseOverChartSymbol?: EventCallback; - onDragSelectEnd?: EventCallback; - - onDropdownIconClick?: EventCallback; - onDropdownMenuClear?: EventCallback; - - onTreeHierarchyStateChange?: EventCallback; - - onShowMenu?: EventCallback; - onHideMenu?: EventCallback; - - onIconClick?: EventCallback; - - onLegendItemClick?: EventCallback; - onLegendItemHover?: EventCallback; - onLegendItemUnHover?: EventCallback; - onLegendChange?: EventCallback; - - onMouseEnterAxis?: EventCallback; - onMouseLeaveAxis?: EventCallback; - - onCheckboxStateChange?: EventCallback; - onAfterRender?: EventCallback; - onInitialized?: EventCallback; + onClickCell?: EventCallback; + onDblClickCell?: EventCallback; + onMouseDownCell?: EventCallback; + onMouseUpCell?: EventCallback; + onSelectedCell?: EventCallback; + onKeyDown?: EventCallback; + onMouseEnterTable?: EventCallback; + onMouseLeaveTable?: EventCallback; + onMouseMoveCell?: EventCallback; + onMouseEnterCell?: EventCallback; + onMouseLeaveCell?: EventCallback; + onContextMenuCell?: EventCallback; + onResizeColumn?: EventCallback; + onResizeColumnEnd?: EventCallback; + onChangeHeaderPosition?: EventCallback; + onSortClick?: EventCallback; + onFreezeClick?: EventCallback; + onScroll?: EventCallback; + onDropdownMenuClick?: EventCallback; + onMouseOverChartSymbol?: EventCallback; + onDragSelectEnd?: EventCallback; + + onDropdownIconClick?: EventCallback; + onDropdownMenuClear?: EventCallback; + + onTreeHierarchyStateChange?: EventCallback; + + onShowMenu?: EventCallback; + onHideMenu?: EventCallback; + + onIconClick?: EventCallback; + + onLegendItemClick?: EventCallback; + onLegendItemHover?: EventCallback; + onLegendItemUnHover?: EventCallback; + onLegendChange?: EventCallback; + + onMouseEnterAxis?: EventCallback; + onMouseLeaveAxis?: EventCallback; + + onCheckboxStateChange?: EventCallback; + onAfterRender?: EventCallback; + onInitialized?: EventCallback; // pivot table only - onPivotSortClick?: EventCallback; - onDrillMenuClick?: EventCallback; + onPivotSortClick?: EventCallback; + onDrillMenuClick?: EventCallback; // pivot chart only - onVChartEventType?: EventCallback; + onVChartEventType?: EventCallback; } export const TABLE_EVENTS = { @@ -157,7 +157,7 @@ export const bindEventsToTable = ( if (newEventProps) { Object.keys(newEventProps).forEach(eventKey => { if (!prevEventProps || !prevEventProps[eventKey] || prevEventProps[eventKey] !== newEventProps[eventKey]) { - table.on(supportedEvents[eventKey] as keyof TableEventHandlersEventArgumentMap, newEventProps[eventKey]); + table.on(supportedEvents[eventKey] as keyof TYPES.TableEventHandlersEventArgumentMap, newEventProps[eventKey]); } }); } diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index 9438ed8c2..ed277b994 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "0.18.4", + "version": "0.19.0", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-editors/src/date-input-editor.ts b/packages/vtable-editors/src/date-input-editor.ts index 28ec4616a..b16ff997b 100644 --- a/packages/vtable-editors/src/date-input-editor.ts +++ b/packages/vtable-editors/src/date-input-editor.ts @@ -7,8 +7,6 @@ export interface DateInputEditorConfig { export class DateInputEditor extends InputEditor implements IEditor { editorType: string = 'DateInput'; - declare element: HTMLInputElement; - successCallback: Function; constructor(editorConfig?: DateInputEditorConfig) { super(editorConfig); this.editorConfig = editorConfig; @@ -30,7 +28,4 @@ export class DateInputEditor extends InputEditor implements IEditor { // this.successCallback(); // }; } - bindSuccessCallback(success: Function) { - this.successCallback = success; - } } diff --git a/packages/vtable-editors/src/input-editor.ts b/packages/vtable-editors/src/input-editor.ts index 4b83598d6..0a161f924 100644 --- a/packages/vtable-editors/src/input-editor.ts +++ b/packages/vtable-editors/src/input-editor.ts @@ -1,4 +1,5 @@ -import type { IEditor, Placement, RectProps } from './types'; +import type { EditContext, IEditor, Placement, RectProps } from './types'; + export interface InputEditorConfig { max?: number; min?: number; @@ -8,10 +9,13 @@ export class InputEditor implements IEditor { editorType: string = 'Input'; editorConfig: InputEditorConfig; container: HTMLElement; - declare element: HTMLInputElement; + successCallback?: () => void; + element?: HTMLInputElement; + constructor(editorConfig?: InputEditorConfig) { this.editorConfig = editorConfig; } + createElement() { const input = document.createElement('input'); input.setAttribute('type', 'text'); @@ -23,15 +27,19 @@ export class InputEditor implements IEditor { this.container.appendChild(input); } + setValue(value: string) { this.element.value = typeof value !== 'undefined' ? value : ''; } + getValue() { return this.element.value; } - beginEditing(container: HTMLElement, referencePosition: { rect: RectProps; placement?: Placement }, value?: string) { + + onStart({ value, referencePosition, container, endEdit }: EditContext) { console.log('input', 'beginEditing---- '); this.container = container; + this.successCallback = endEdit; this.createElement(); if (value) { @@ -43,24 +51,24 @@ export class InputEditor implements IEditor { this.element.focus(); // do nothing } + adjustPosition(rect: RectProps) { this.element.style.top = rect.top + 'px'; this.element.style.left = rect.left + 'px'; this.element.style.width = rect.width + 'px'; this.element.style.height = rect.height + 'px'; } + endEditing() { // do nothing } - exit() { + onEnd() { // do nothing this.container.removeChild(this.element); } - targetIsOnEditor(target: HTMLElement) { - if (target === this.element) { - return true; - } - return false; + + isEditorElement(target: HTMLElement) { + return target === this.element; } } diff --git a/packages/vtable-editors/src/list-editor.ts b/packages/vtable-editors/src/list-editor.ts index 5f6180bf6..d0ec1d7c3 100644 --- a/packages/vtable-editors/src/list-editor.ts +++ b/packages/vtable-editors/src/list-editor.ts @@ -1,15 +1,15 @@ -import type { IEditor, Placement, RectProps } from './types'; +import type { EditContext, IEditor, Placement, RectProps } from './types'; export interface ListEditorConfig { values: string[]; } export class ListEditor implements IEditor { editorType: string = 'Input'; - input: HTMLInputElement; - editorConfig: ListEditorConfig; - container: HTMLElement; - element: HTMLSelectElement; - successCallback: Function; + input?: HTMLInputElement; + editorConfig?: ListEditorConfig; + container?: HTMLElement; + element?: HTMLSelectElement; + successCallback?: () => void; constructor(editorConfig: ListEditorConfig) { console.log('listEditor constructor'); @@ -54,8 +54,9 @@ export class ListEditor implements IEditor { return this.element.value; } - beginEditing(container: HTMLElement, referencePosition: { rect: RectProps; placement?: Placement }, value?: string) { + onStart({ container, value, referencePosition, endEdit }: EditContext) { this.container = container; + this.successCallback = endEdit; this.createElement(value); @@ -79,18 +80,11 @@ export class ListEditor implements IEditor { // do nothing } - exit() { + onEnd() { this.container.removeChild(this.element); } - targetIsOnEditor(target: HTMLElement) { - if (target === this.element) { - return true; - } - return false; - } - - bindSuccessCallback(success: Function) { - this.successCallback = success; + isEditorElement(target: HTMLElement) { + return target === this.element; } } diff --git a/packages/vtable-editors/src/types.ts b/packages/vtable-editors/src/types.ts index 865a4f7c4..399f3984b 100644 --- a/packages/vtable-editors/src/types.ts +++ b/packages/vtable-editors/src/types.ts @@ -1,36 +1,96 @@ -export interface IEditor { - /** 编辑器类型 */ - editorType?: string; - /** 编辑配置 */ - editorConfig: any; - /* 编辑器挂载的容器 由vtable传入 */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface IEditor { + /** + * Called when cell enters edit mode. + * + * Warning will be thrown if you don't provide this function + * after removal of `beginEditing`. + */ + onStart?: (context: EditContext) => void; + /** + * called when cell exits edit mode. + * + * Warning will be thrown if you don't provide this function + * after removal of `exit`. + */ + onEnd?: () => void; + /** + * Called when user click somewhere while editor is in edit mode. + * + * If returns falsy, VTable will exit edit mode. + * + * If returns truthy or not defined, nothing will happen. + * Which means, in this scenario, you need to call `endEdit` manually + * to end edit mode. + */ + isEditorElement?: (target: HTMLElement) => boolean; + /** + * Called when editor mode is exited by any means. + * Expected to return the current value of the cell. + */ + getValue: () => V; + /** + * Called when cell enter edit mode. + * @deprecated use `onStart` instead. + */ + beginEditing?: (container: HTMLElement, referencePosition: ReferencePosition, value: V) => void; + /** + * @see onEnd + * @deprecated use `onEnd` instead. + */ + exit?: () => void; + /** + * @see isEditorElement + * @deprecated use `isEditorElement` instead. + */ + targetIsOnEditor?: (target: HTMLElement) => boolean; + /** + * Called when cell enters edit mode with a callback function + * that can be used to end edit mode. + * @see EditContext#endEdit + * @deprecated callback is provided as `endEdit` in `EditContext`, use `onStart` instead. + */ + bindSuccessCallback?: (callback: () => void) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface EditContext { + /** Container element of the VTable instance. */ container: HTMLElement; - /** 编辑完成后调用。注意如果是(enter键,鼠标点击其他位置)这类编辑完成已有VTable实现,编辑器内部有完成按钮等类似的完成操作需要调用这个方法 */ - successCallback?: Function; - /** 获取编辑器当前值 */ - getValue: () => string | number | null; - /** 编辑器进入编辑状态 */ - beginEditing: ( - container: HTMLElement, - referencePosition: { rect: RectProps; placement?: Placement }, - value?: string - ) => void; - /** 编辑器退出编辑状态 */ - exit: () => void; - /** 判断鼠标点击的target是否属于编辑器内部元素 */ - targetIsOnEditor: (target: HTMLElement) => boolean; - /** 由VTable调用来传入编辑成功的回调 请将callback赋值到successCallback */ - bindSuccessCallback?: (callback: Function) => void; + /** Position info of the cell that is being edited. */ + referencePosition: ReferencePosition; + /** Cell value before editing. */ + value: V; + /** + * Callback function that can be used to end edit mode. + * + * In most cases you don't need to call this function, + * since Enter key click is handled by VTable automatically, + * and mouse click can be handled by `isEditorElement`. + * + * However, if your editor has its own complete button, + * or you have external elements like Tooltip, + * you may want to use this callback to help you + * end edit mode. + */ + endEdit: () => void; } + export interface RectProps { left: number; top: number; width: number; height: number; } + export enum Placement { top = 'top', bottom = 'bottom', left = 'left', right = 'right' } + +export interface ReferencePosition { + rect: RectProps; + placement?: Placement; +} diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index d7b58f41d..a341d3b01 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "0.18.4", + "version": "0.19.0", "description": "The export util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index 173852352..692045a9d 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,42 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "0.19.0", + "tag": "@visactor/vtable_v0.19.0", + "date": "Fri, 02 Feb 2024 04:13:16 GMT", + "comments": { + "none": [ + { + "comment": "fix: select region saved problem #1018\n\n" + }, + { + "comment": "fix: when call updateColumns and discount col occor error #1015\n\n" + }, + { + "comment": "fix: rightFrozenColCount drag header move more time the column width is error #1019\n\n" + }, + { + "comment": "fix: empty string compute row height error #1031\n\n" + }, + { + "comment": "feat: support get sorted columns #986\n\n" + }, + { + "comment": "feat: add option frozenColDragHeaderMode\n\n" + }, + { + "comment": "refactor: when drag header move to frozen region then markLine show positon\n\n" + }, + { + "comment": "refactor: optimize updateRow api performance & resize bottom frozen row not right\n\n" + }, + { + "comment": "fix: fix merge image cell update problem" + } + ] + } + }, { "version": "0.18.4", "tag": "@visactor/vtable_v0.18.4", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index 6053b777e..0d18c4186 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,37 @@ # Change Log - @visactor/vtable -This log was last generated on Fri, 26 Jan 2024 09:15:07 GMT and should not be manually modified. +This log was last generated on Fri, 02 Feb 2024 04:13:16 GMT and should not be manually modified. + +## 0.19.0 +Fri, 02 Feb 2024 04:13:16 GMT + +### Updates + +- fix: select region saved problem #1018 + + +- fix: when call updateColumns and discount col occor error #1015 + + +- fix: rightFrozenColCount drag header move more time the column width is error #1019 + + +- fix: empty string compute row height error #1031 + + +- feat: support get sorted columns #986 + + +- feat: add option frozenColDragHeaderMode + + +- refactor: when drag header move to frozen region then markLine show positon + + +- refactor: optimize updateRow api performance & resize bottom frozen row not right + + +- fix: fix merge image cell update problem ## 0.18.4 Fri, 26 Jan 2024 09:15:07 GMT diff --git a/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts b/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts new file mode 100644 index 000000000..05a0db9f8 --- /dev/null +++ b/packages/vtable/__tests__/columns/listTable-dragHeader.test.ts @@ -0,0 +1,126 @@ +// @ts-nocheck +// 有问题可对照demo unitTestListTable +import { ListTable } from '../../src/ListTable'; +import { createDiv } from '../dom'; +global.__VERSION__ = 'none'; +const generatePersons = count => { + return Array.from(new Array(count)).map((_, i) => ({ + id: i + 1, + email1: `${i + 1}@xxx.com`, + name: `小明${i + 1}`, + lastName: '王', + date1: '2022年9月1日', + tel: '000-0000-0000', + sex: i % 2 === 0 ? 'boy' : 'girl', + work: i % 2 === 0 ? 'back-end engineer' : 'front-end engineer', + city: 'beijing' + })); +}; +describe('listTable-cellType-function init test', () => { + const containerDom: HTMLElement = createDiv(); + containerDom.style.position = 'relative'; + containerDom.style.width = '1000px'; + containerDom.style.height = '800px'; + const records = generatePersons(10); + const columns = [ + { + field: 'id', + title: 'ID', + sort: true, + width: 'auto' + }, + { + field: 'email1', + title: 'email', + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name' + }, + { + field: 'name', + title: 'Last Name' + } + ] + }, + { + field: 'date1', + title: 'birthday' + // width: 200 + }, + { + field: 'sex', + title: 'sex' + }, + { + field: 'tel', + title: 'telephone' + }, + { + field: 'work', + title: 'job' + }, + { + field: 'city', + title: 'city' + } + ]; + const option = { + records, + columns, + dragHeaderMode: 'all', + autoWrapText: true + }; + const listTable = new ListTable(containerDom, option); + test('listTable dragHeader interaction', () => { + listTable.selectCell(4, 1); + listTable.stateManager.startMoveCol(4, 1, 342, 60); + listTable.stateManager.updateMoveCol(1, 1, 100, 60); + listTable.stateManager.endMoveCol(); + expect(listTable.columns).toEqual([ + { field: 'id', sort: true, title: 'ID', width: 'auto' }, + { field: 'date1', title: 'birthday' }, + { field: 'email1', sort: true, title: 'email' }, + { + columns: [ + { field: 'name', title: 'First Name' }, + { field: 'name', title: 'Last Name' } + ], + title: 'full name' + }, + { field: 'sex', title: 'sex' }, + { field: 'tel', title: 'telephone' }, + { field: 'work', title: 'job' }, + { field: 'city', title: 'city' } + ]); + }); + test('listTable dragHeader interaction', () => { + option.transpose = true; + listTable.updateOption(option); + listTable.selectCell(1, 4); + listTable.stateManager.startMoveCol(1, 4, 120, 60); + listTable.stateManager.updateMoveCol(1, 1, 120, 177); + listTable.stateManager.endMoveCol(); + expect(listTable.columns).toEqual([ + { field: 'id', sort: true, title: 'ID', width: 'auto' }, + { field: 'date1', title: 'birthday' }, + { field: 'email1', sort: true, title: 'email' }, + { + columns: [ + { field: 'name', title: 'First Name' }, + { field: 'name', title: 'Last Name' } + ], + title: 'full name' + }, + { field: 'sex', title: 'sex' }, + { field: 'tel', title: 'telephone' }, + { field: 'work', title: 'job' }, + { field: 'city', title: 'city' } + ]); + }); + listTable.release(); +}); diff --git a/packages/vtable/examples/cell-move/column.ts b/packages/vtable/examples/cell-move/column-move.ts similarity index 100% rename from packages/vtable/examples/cell-move/column.ts rename to packages/vtable/examples/cell-move/column-move.ts diff --git a/packages/vtable/examples/cell-move/pivot.ts b/packages/vtable/examples/cell-move/pivot-move.ts similarity index 99% rename from packages/vtable/examples/cell-move/pivot.ts rename to packages/vtable/examples/cell-move/pivot-move.ts index d05afd55e..4f385bb3a 100644 --- a/packages/vtable/examples/cell-move/pivot.ts +++ b/packages/vtable/examples/cell-move/pivot-move.ts @@ -8,6 +8,11 @@ export function createTable() { enableDataAnalysis: false, allowRangePaste: true, columnTree: [ + { + dimensionKey: '地区', + //title: '地区', + value: '东北22' + }, { dimensionKey: '地区', //title: '地区', @@ -452,6 +457,8 @@ export function createTable() { }; option.container = document.getElementById(CONTAINER_ID); const instance = new PivotTable(option); + // 只为了方便控制太调试用,不要拷贝 + window.tableInstance = instance; instance.on('copy_data', e => { console.log('copy_data', e); }); @@ -459,7 +466,4 @@ export function createTable() { VTable.bindDebugTool(instance.scenegraph.stage as any, { customGrapicKeys: ['role', '_updateTag'] }); - - // 只为了方便控制太调试用,不要拷贝 - window.tableInstance = instance; } diff --git a/packages/vtable/examples/cell-move/row.ts b/packages/vtable/examples/cell-move/row-move.ts similarity index 100% rename from packages/vtable/examples/cell-move/row.ts rename to packages/vtable/examples/cell-move/row-move.ts diff --git a/packages/vtable/examples/editor/custom-date-editor.ts b/packages/vtable/examples/editor/custom-date-editor.ts index c5064a226..58f98e062 100644 --- a/packages/vtable/examples/editor/custom-date-editor.ts +++ b/packages/vtable/examples/editor/custom-date-editor.ts @@ -1,5 +1,5 @@ import * as VTable from '../../src'; -import type { IEditor, RectProps, Placement } from '@visactor/vtable-editors'; +import type { IEditor, RectProps, Placement, EditContext } from '@visactor/vtable-editors'; import { DateInputEditor, InputEditor, ListEditor } from '@visactor/vtable-editors'; import * as luxon from 'luxon'; import * as Pikaday from 'pikaday'; @@ -21,9 +21,10 @@ class DateEditor implements IEditor { constructor(editorConfig: any) { this.editorConfig = editorConfig; } - beginEditing(container: HTMLElement, referencePosition: { rect: RectProps; placement?: Placement }, value?: string) { + onStart({ container, value, referencePosition, endEdit }: EditContext) { const that = this; this.container = container; + this.successCallback = endEdit; // const cellValue = luxon.DateTime.fromFormat(value, 'yyyy年MM月dd日').toFormat('yyyy-MM-dd'); const input = document.createElement('input'); @@ -79,19 +80,16 @@ class DateEditor implements IEditor { // const cellValue = luxon.DateTime.fromFormat(this.element.value, 'yyyy-MM-dd').toFormat('yyyy年MM月dd日'); return this.element.value; } - exit() { + onEnd() { this.picker.destroy(); this.container.removeChild(this.element); } - targetIsOnEditor(target: HTMLElement) { + isEditorElement(target: HTMLElement) { if (target === this.element || this.picker.el.contains(target)) { return true; } return false; } - bindSuccessCallback(successCallback: Function) { - this.successCallback = successCallback; - } } const custom_date_editor = new DateEditor({}); VTable.register.editor('custom-date', custom_date_editor); diff --git a/packages/vtable/examples/list/list-100w.ts b/packages/vtable/examples/list/list-100w.ts index ed850d3f1..7102a0dfb 100644 --- a/packages/vtable/examples/list/list-100w.ts +++ b/packages/vtable/examples/list/list-100w.ts @@ -180,4 +180,5 @@ export function createTable() { console.log('initialized'); }); window.tableInstance = tableInstance; + bindDebugTool(tableInstance.scenegraph.stage, { customGrapicKeys: ['col', 'row'] }); } diff --git a/packages/vtable/examples/list/list.ts b/packages/vtable/examples/list/list.ts index 65b063a78..4af73c6e9 100644 --- a/packages/vtable/examples/list/list.ts +++ b/packages/vtable/examples/list/list.ts @@ -16,7 +16,7 @@ const generatePersons = count => { }; export function createTable() { - const records = generatePersons(1000000); + const records = generatePersons(10); const columns: VTable.ColumnsDefine = [ { field: '', @@ -29,8 +29,8 @@ export function createTable() { { field: 'id', title: 'ID', - width: '1%', - minWidth: 200, + width: 'auto', + minWidth: 50, sort: true }, { @@ -149,156 +149,6 @@ export function createTable() { title: 'job', width: 200 }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, - { - field: 'city', - title: 'city', - width: 150 - }, - { - field: 'date1', - title: 'birthday', - width: 200 - }, - { - field: 'sex', - title: 'sex', - width: 100 - }, - { - field: 'tel', - title: 'telephone', - width: 150 - }, - { - field: 'work', - title: 'job', - width: 200 - }, { field: 'city', title: 'city', @@ -317,7 +167,8 @@ export function createTable() { rightFrozenColCount: 2, overscrollBehavior: 'none', autoWrapText: true, - heightMode: 'autoHeight', + heightMode: 'adaptive', + widthMode: 'adaptive', dragHeaderMode: 'all', keyboardOptions: { pasteValueToCell: true diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index f3a635512..331115cbd 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -546,15 +546,15 @@ export const menus = [ children: [ { path: 'cell-move', - name: 'column' + name: 'column-move' }, { path: 'cell-move', - name: 'row' + name: 'row-move' }, { path: 'cell-move', - name: 'pivot' + name: 'pivot-move' } ] }, diff --git a/packages/vtable/package.json b/packages/vtable/package.json index e5cbb945e..3174fbe4b 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "0.18.4", + "version": "0.19.0", "description": "canvas table width high performance", "keywords": [ "grid", @@ -50,9 +50,9 @@ }, "dependencies": { "@visactor/vtable-editors": "workspace:*", - "@visactor/vrender-core": "0.17.19-alpha.1", - "@visactor/vrender-kits": "0.17.19-alpha.1", - "@visactor/vrender-components": "0.17.19-alpha.1", + "@visactor/vrender-core": "0.17.20-alpha.3", + "@visactor/vrender-kits": "0.17.20-alpha.3", + "@visactor/vrender-components": "0.17.20-alpha.3", "@visactor/vutils-extension": "~1.8.5", "@visactor/vutils": "~0.17.1", "@visactor/vscale": "~0.17.1", diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 4891f77de..f6a1d0267 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -58,6 +58,7 @@ export class ListTable extends BaseTable implements ListTableAPI { super(container as HTMLElement, options); const internalProps = this.internalProps; + internalProps.frozenColDragHeaderMode = options.frozenColDragHeaderMode; //分页配置 this.pagination = options.pagination; internalProps.sortState = options.sortState; @@ -148,7 +149,8 @@ export class ListTable extends BaseTable implements ListTableAPI { this.eventManager.updateEventBinder(); } get columns(): ColumnsDefine { - return this.internalProps.columns; + // return this.internalProps.columns; + return this.internalProps.layoutMap.columnTree.getCopiedTree(); //调整顺序后的columns } /** *@deprecated 请使用columns @@ -315,6 +317,7 @@ export class ListTable extends BaseTable implements ListTableAPI { updateOption(options: ListTableConstructorOptions, accelerateFirstScreen = false) { const internalProps = this.internalProps; super.updateOption(options); + internalProps.frozenColDragHeaderMode = options.frozenColDragHeaderMode; //分页配置 this.pagination = options.pagination; //更新protectedSpace diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 5b850af56..79bca6277 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -28,7 +28,7 @@ import type { BaseTableAPI, PivotTableProtected } from './ts-types/base-table'; import { Title } from './components/title/title'; import { cloneDeep } from '@visactor/vutils'; import { Env } from './tools/env'; -import type { LayouTreeNode } from './layout/pivot-layout-helper'; +import type { LayouTreeNode } from './layout/layout-helper'; import { TABLE_EVENT_TYPE } from './core/TABLE_EVENT_TYPE'; import { EditManeger } from './edit/edit-manager'; import * as editors from './edit/editors'; diff --git a/packages/vtable/src/components/axis/axis.ts b/packages/vtable/src/components/axis/axis.ts index 216a68b91..ecd0d54b9 100644 --- a/packages/vtable/src/components/axis/axis.ts +++ b/packages/vtable/src/components/axis/axis.ts @@ -217,7 +217,7 @@ export class CartesianAxis { // visible: this.option.grid.visible // }, title: { - text: this.option.title.text, + text: this.option.title.text as string, maxWidth: this._getTitleLimit(isX) }, items: this.getLabelItems(axisLength), diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 29d219cff..0130723c5 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -91,6 +91,7 @@ import { Title } from '../components/title/title'; import type { Chart } from '../scenegraph/graphic/chart'; import { setBatchRenderChartCount } from '../scenegraph/graphic/contributions/chart-render-helper'; import { isLeftOrRightAxis, isTopOrBottomAxis } from '../layout/chart-helper/get-axis-config'; +import { NumberRangeMap } from '../layout/row-height-map'; const { toBoxArray } = utilStyle; const { isTouchEvent } = event; const rangeReg = /^\$(\d+)\$(\d+)$/; @@ -153,6 +154,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { container: HTMLElement; isReleased: boolean = false; _chartEventMap: Record = {}; + constructor(container: HTMLElement, options: BaseTableConstructorOptions = {}) { super(); if (!container && options.mode !== 'node') { @@ -265,7 +267,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { internalProps.renderChartAsync = renderChartAsync; setBatchRenderChartCount(renderChartAsyncBatchCount); internalProps.overscrollBehavior = overscrollBehavior ?? 'auto'; - internalProps._rowHeightsMap = new NumberMap(); + internalProps._rowHeightsMap = new NumberRangeMap(this); internalProps._rowRangeHeightsMap = new Map(); internalProps._colRangeWidthsMap = new Map(); internalProps._widthResizedColMap = new Set(); @@ -364,7 +366,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.bodyStyleCache = new Map(); this.bodyBottomStyleCache = new Map(); - internalProps.stick = { changedCells: [] }; + internalProps.stick = { changedCells: new Map() }; internalProps.customMergeCell = options.customMergeCell; } @@ -621,13 +623,13 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { /** * Get the columns width. */ - get rowHeightsMap(): NumberMap { + get rowHeightsMap(): NumberRangeMap { return this.internalProps._rowHeightsMap; } /** * Set the columns width. */ - set rowHeightsMap(rowHeightsMap: NumberMap) { + set rowHeightsMap(rowHeightsMap: NumberRangeMap) { this.internalProps._rowHeightsMap = rowHeightsMap; } /** @@ -838,7 +840,8 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @returns */ getColsWidth(startCol: number, endCol: number): number { - endCol = Math.min(endCol, this.colCount - 1); // endCol最大为this.colCount - 1,超过会导致width计算为NaN + startCol = Math.max(startCol, 0); + endCol = Math.min(endCol, (this.colCount ?? Infinity) - 1); // endCol最大为this.colCount - 1,超过会导致width计算为NaN //通过缓存获取指定范围列宽 const cachedColWidth = this._colRangeWidthsMap.get(`$${startCol}$${endCol}`); if (cachedColWidth !== null && cachedColWidth !== undefined) { @@ -909,7 +912,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { // : this.defaultHeaderRowHeight // : this.internalProps.defaultRowHeight) // ); - if (this.rowHeightsMap.get(row)) { + if (isValid(this.rowHeightsMap.get(row))) { return this.rowHeightsMap.get(row); } const defaultHeight = this.getDefaultRowHeight(row); @@ -940,6 +943,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @returns */ _setRowHeight(row: number, height: number, clearCache?: boolean): void { + // this.rowHeightsMap.put(row, Math.round(height)); this.rowHeightsMap.put(row, Math.round(height)); // 清楚影响缓存 if (clearCache) { @@ -953,30 +957,32 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @returns */ getRowsHeight(startRow: number, endRow: number): number { + startRow = Math.max(startRow, 0); + endRow = Math.min(endRow, (this.rowCount ?? Infinity) - 1); //通过缓存获取指定范围行高 - const cachedRowHeight = this._rowRangeHeightsMap.get(`$${startRow}$${endRow}`); - if (cachedRowHeight !== null && cachedRowHeight !== undefined) { - return cachedRowHeight; - } - //特殊处理 先尝试获取startRow->endRow-1的行高 - const cachedLowerRowHeight = this._rowRangeHeightsMap.get(`$${startRow}$${endRow - 1}`); - if (cachedLowerRowHeight !== null && cachedLowerRowHeight !== undefined) { - const height = Math.round( - cachedLowerRowHeight + - (this.rowHeightsMap.get(endRow) ?? - (this.isColumnHeader(0, endRow) || this.isCornerHeader(0, endRow) - ? Array.isArray(this.defaultHeaderRowHeight) && isNumber(this.defaultHeaderRowHeight[endRow]) - ? (this.defaultHeaderRowHeight[endRow] as number) - : isNumber(this.defaultHeaderRowHeight) - ? (this.defaultHeaderRowHeight as number) - : this.internalProps.defaultRowHeight - : this.internalProps.defaultRowHeight)) - ); - if (startRow >= 0 && endRow >= 0) { - this._rowRangeHeightsMap.set(`$${startRow}$${endRow}`, Math.round(height)); - } - return height; - } + // const cachedRowHeight = this._rowRangeHeightsMap.get(`$${startRow}$${endRow}`); + // if (cachedRowHeight !== null && cachedRowHeight !== undefined) { + // return cachedRowHeight; + // } + // //特殊处理 先尝试获取startRow->endRow-1的行高 + // const cachedLowerRowHeight = this._rowRangeHeightsMap.get(`$${startRow}$${endRow - 1}`); + // if (cachedLowerRowHeight !== null && cachedLowerRowHeight !== undefined) { + // const height = Math.round( + // cachedLowerRowHeight + + // (this.rowHeightsMap.get(endRow) ?? + // (this.isColumnHeader(0, endRow) || this.isCornerHeader(0, endRow) + // ? Array.isArray(this.defaultHeaderRowHeight) && isNumber(this.defaultHeaderRowHeight[endRow]) + // ? (this.defaultHeaderRowHeight[endRow] as number) + // : isNumber(this.defaultHeaderRowHeight) + // ? (this.defaultHeaderRowHeight as number) + // : this.internalProps.defaultRowHeight + // : this.internalProps.defaultRowHeight)) + // ); + // if (startRow >= 0 && endRow >= 0) { + // this._rowRangeHeightsMap.set(`$${startRow}$${endRow}`, Math.round(height)); + // } + // return height; + // } let h = 0; // for (let i = startRow; i <= endRow; i++) { @@ -1004,13 +1010,14 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { // part in body h += this.defaultRowHeight * (endRow - Math.max(this.columnHeaderLevelCount, startRow) + 1); } else { - for (let i = startRow; i <= endRow; i++) { - h += this.getRowHeight(i); - } - } - if (startRow >= 0 && endRow >= 0 && h > 0) { - this._rowRangeHeightsMap.set(`$${startRow}$${endRow}`, Math.round(h)); + // for (let i = startRow; i <= endRow; i++) { + // h += this.getRowHeight(i); + // } + h = this.rowHeightsMap.getSumInRange(startRow, endRow); } + // if (startRow >= 0 && endRow >= 0 && h > 0) { + // this._rowRangeHeightsMap.set(`$${startRow}$${endRow}`, Math.round(h)); + // } // } return Math.round(h); } @@ -1151,21 +1158,22 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { * @param row */ _clearRowRangeHeightsMap(row?: number): void { - if (typeof row !== 'number') { - this._rowRangeHeightsMap.clear(); - } else { - const keys = this._rowRangeHeightsMap.keys(); - for (const key of keys) { - const reg = rangeReg.exec(key); - if (reg) { - const start = Number(reg[1]); - const end = Number(reg[2]); - if (row >= start && row <= end) { - this._rowRangeHeightsMap.delete(key); - } - } - } - } + this.rowHeightsMap.clearRange(); + // if (typeof row !== 'number') { + // this._rowRangeHeightsMap.clear(); + // } else { + // const keys = this._rowRangeHeightsMap.keys(); + // for (const key of keys) { + // const reg = rangeReg.exec(key); + // if (reg) { + // const start = Number(reg[1]); + // const end = Number(reg[2]); + // if (row >= start && row <= end) { + // this._rowRangeHeightsMap.delete(key); + // } + // } + // } + // } } /** * 获取某一列内容的宽度 不关乎该列列宽值有多少 @@ -1895,6 +1903,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { // disableRowHeaderColumnResize, columnResizeMode, dragHeaderMode, + // scrollBar, showFrozenIcon, allowFrozenColCount, @@ -1972,7 +1981,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { setBatchRenderChartCount(renderChartAsyncBatchCount); internalProps.overscrollBehavior = overscrollBehavior ?? 'auto'; internalProps.cellTextOverflows = {}; - internalProps._rowHeightsMap = new NumberMap(); + internalProps._rowHeightsMap = new NumberRangeMap(this); internalProps._rowRangeHeightsMap = new Map(); internalProps._colRangeWidthsMap = new Map(); @@ -1982,6 +1991,8 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.colContentWidthsMap = new NumberMap(); this.colWidthsLimit = {}; + internalProps.stick.changedCells.clear(); + internalProps.theme = themes.of(options.theme ?? themes.DEFAULT); this.scenegraph.updateStageBackground(); // this._updateSize(); @@ -3041,6 +3052,8 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.headerStyleCache.clear(); this.bodyStyleCache.clear(); this.bodyBottomStyleCache.clear(); + + // this._newRowHeightsMap.clear(); } /** * 清除行高度缓存对象 @@ -3101,6 +3114,12 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { */ _canDragHeaderPosition(col: number, row: number): boolean { if (this.isHeader(col, row) && this.stateManager.isSelected(col, row)) { + if ( + this.internalProps.frozenColDragHeaderMode === 'disabled' && + (this.isFrozenColumn(col) || this.isRightFrozenColumn(col)) + ) { + return false; + } const selectRange = this.stateManager.select.ranges[0]; //判断是否整行或者整列选中 if (this.isColumnHeader(col, row)) { diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index a8d070688..c66e5ef5c 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -8,10 +8,12 @@ export class EditManeger { table: BaseTableAPI; editingEditor: IEditor; editCell: { col: number; row: number }; + constructor(table: BaseTableAPI) { this.table = table; this.bindEvent(); } + bindEvent() { const handler = this.table.internalProps.handler; this.table.on(TABLE_EVENT_TYPE.DBLCLICK_CELL, e => { @@ -48,6 +50,7 @@ export class EditManeger { this.completeEdit(); }); } + startEditCell(col: number, row: number) { //透视表的表头不允许编辑 if (this.table.isPivotTable() && this.table.isHeader(col, row)) { @@ -68,34 +71,66 @@ export class EditManeger { return; } } - editor.bindSuccessCallback?.(() => { - this.completeEdit(); - }); this.editingEditor = editor; this.editCell = { col, row }; const dataValue = this.table.getCellOriginValue(col, row); const rect = this.table.getCellRangeRelativeRect(this.table.getCellRange(col, row)); - editor.beginEditing( - this.table.getElement(), - { rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height } }, - dataValue - ); + const referencePosition = { rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height } }; + + editor.beginEditing && console.warn('VTable Warn: `beginEditing` is deprecated, please use `onStart` instead.'); + editor.beginEditing?.(this.table.getElement(), referencePosition, dataValue); + + if (editor.bindSuccessCallback) { + console.warn('VTable Warn: `bindSuccessCallback` is deprecated, please use `onStart` instead.'); + } + editor.bindSuccessCallback?.(() => { + this.completeEdit(); + }); + editor.onStart?.({ + value: dataValue, + endEdit: () => { + this.completeEdit(); + }, + referencePosition, + container: this.table.getElement() + }); } } + /** 如果是事件触发调用该接口 请传入原始事件对象 将判断事件对象是否在编辑器本身上面 来处理是否结束编辑 */ - completeEdit(e?: any) { - const target = e?.target; - if (this.editingEditor && (!target || (target && !this.editingEditor.targetIsOnEditor(target as HTMLElement)))) { - const changedValue = this.editingEditor.getValue(); - (this.table as ListTableAPI).changeCellValue(this.editCell.col, this.editCell.row, changedValue); - this.editingEditor.exit(); - this.editingEditor = null; + completeEdit(e?: Event) { + if (!this.editingEditor) { + return; + } + + const target = e?.target as HTMLElement | undefined; + + if (this.editingEditor.targetIsOnEditor) { + console.warn('VTable Warn: `targetIsOnEditor` is deprecated, please use `isEditorElement` instead.'); + if (target && this.editingEditor.targetIsOnEditor(target)) { + return; + } + } else if (!this.editingEditor.isEditorElement || (target && this.editingEditor.isEditorElement?.(target))) { + return; } + + if (!this.editingEditor.getValue) { + console.warn('VTable Warn: `getValue` is not provided, did you forget to implement it?'); + } + const changedValue = this.editingEditor.getValue?.(); + (this.table as ListTableAPI).changeCellValue(this.editCell.col, this.editCell.row, changedValue); + + this.editingEditor.exit && console.warn('VTable Warn: `exit` is deprecated, please use `onEnd` instead.'); + this.editingEditor.exit?.(); + this.editingEditor.onEnd?.(); + this.editingEditor = null; } cancelEdit() { if (this.editingEditor) { - this.editingEditor.exit(); + // TODO: 添加开发时弃用警告 + this.editingEditor.exit?.(); + this.editingEditor.onEnd?.(); this.editingEditor = null; } } diff --git a/packages/vtable/src/event/event.ts b/packages/vtable/src/event/event.ts index d7ed34299..a708da566 100644 --- a/packages/vtable/src/event/event.ts +++ b/packages/vtable/src/event/event.ts @@ -23,6 +23,7 @@ import { bindAxisHoverEvent } from './pivot-chart/axis-hover'; import type { PivotTable } from '../PivotTable'; import { Env } from '../tools/env'; import type { ListTable } from '../ListTable'; +import { isValid } from '@visactor/vutils'; export class EventManager { table: BaseTableAPI; @@ -323,12 +324,14 @@ export class EventManager { dealColumnMover(eventArgsSet: SceneEvent) { const { eventArgs } = eventArgsSet; - this.table.stateManager.updateMoveCol( - eventArgs.col, - eventArgs.row, - eventArgsSet.abstractPos.x, - eventArgsSet.abstractPos.y - ); + if (isValid(eventArgs.col) && isValid(eventArgs.row)) { + this.table.stateManager.updateMoveCol( + eventArgs.col, + eventArgs.row, + eventArgsSet.abstractPos.x, + eventArgsSet.abstractPos.y + ); + } } startColumnResize(eventArgsSet: SceneEvent) { diff --git a/packages/vtable/src/event/listener/table-group.ts b/packages/vtable/src/event/listener/table-group.ts index c6945d174..1936680b3 100644 --- a/packages/vtable/src/event/listener/table-group.ts +++ b/packages/vtable/src/event/listener/table-group.ts @@ -83,16 +83,15 @@ export function bindTableGroupListener(eventManager: EventManager) { if (stateManager.interactionState === InteractionState.scrolling) { return; } - if ( - stateManager.interactionState === InteractionState.grabing && - Math.abs(lastX - e.x) + Math.abs(lastY - e.y) >= 1 - ) { - if (stateManager.isResizeCol()) { - /* do nothing */ - } else if (stateManager.isMoveCol()) { - eventManager.dealColumnMover(eventArgsSet); - } else { - eventManager.dealTableSelect(eventArgsSet, true); + if (stateManager.interactionState === InteractionState.grabing) { + if (Math.abs(lastX - e.x) + Math.abs(lastY - e.y) >= 1) { + if (stateManager.isResizeCol()) { + /* do nothing */ + } else if (stateManager.isMoveCol()) { + eventManager.dealColumnMover(eventArgsSet); + } else { + eventManager.dealTableSelect(eventArgsSet, true); + } } return; } diff --git a/packages/vtable/src/icons.ts b/packages/vtable/src/icons.ts index c82a6bcae..4c35247c5 100644 --- a/packages/vtable/src/icons.ts +++ b/packages/vtable/src/icons.ts @@ -228,6 +228,16 @@ const builtins = { cursor: 'pointer' }; }, + get loading_pic(): SvgIcon { + return { + type: 'svg', + svg: '', + width: 400, + height: 300, + name: 'loading_pic', + positionType: IconPosition.left + }; + }, //展开状态的按钮 向下三角 get expand(): SvgIcon { return { diff --git a/packages/vtable/src/layout/pivot-layout-helper.ts b/packages/vtable/src/layout/layout-helper.ts similarity index 85% rename from packages/vtable/src/layout/pivot-layout-helper.ts rename to packages/vtable/src/layout/layout-helper.ts index 040860533..c4cb04c5a 100644 --- a/packages/vtable/src/layout/pivot-layout-helper.ts +++ b/packages/vtable/src/layout/layout-helper.ts @@ -1,4 +1,4 @@ -import { isValid } from '@visactor/vutils'; +import { cloneDeep, isValid } from '@visactor/vutils'; import { NumberMap } from '../tools/NumberMap'; import { IndicatorDimensionKeyPlaceholder } from '../tools/global'; import type { Either } from '../tools/helper'; @@ -19,13 +19,14 @@ import type { ILinkDimension } from '../ts-types/pivot-table/dimension/link-dime import type { IImageDimension } from '../ts-types/pivot-table/dimension/image-dimension'; // import { sharedVar } from './pivot-header-layout'; -interface IPivotLayoutBaseHeadNode { +interface ITreeLayoutBaseHeadNode { id: number; // dimensionKey: string; // // title: string; // indicatorKey?: string; value: string; - children: IPivotLayoutHeadNode[] | undefined; + children: ITreeLayoutHeadNode[] | undefined; + columns?: any; //兼容ListTable情况 simple-header-layout中增加了columnTree level: number; /** 节点跨占层数 如汇总节点跨几层维度 */ levelSpan: number; @@ -40,13 +41,13 @@ interface IPivotLayoutBaseHeadNode { headerIcon?: (string | ColumnIconOption)[] | ((args: CellInfo) => (string | ColumnIconOption)[]); } -interface IPivotLayoutDimensionHeadNode extends IPivotLayoutBaseHeadNode { +interface ITreeLayoutDimensionHeadNode extends ITreeLayoutBaseHeadNode { dimensionKey: string; } -interface IPivotLayoutIndicatorHeadNode extends IPivotLayoutBaseHeadNode { +interface ITreeLayoutIndicatorHeadNode extends ITreeLayoutBaseHeadNode { indicatorKey: string; } -export type IPivotLayoutHeadNode = Either; +export type ITreeLayoutHeadNode = Either; export class DimensionTree { sharedVar: { seqId: number }; // 每一个值对应的序号 结果缓存 @@ -57,7 +58,7 @@ export class DimensionTree { sizeIncludeParent = false; rowExpandLevel: number; hierarchyType: 'grid' | 'tree'; - tree: IPivotLayoutHeadNode = { + tree: ITreeLayoutHeadNode = { id: 0, dimensionKey: '', // title: '', @@ -82,7 +83,7 @@ export class DimensionTree { cache: Map = new Map(); constructor( - tree: IPivotLayoutHeadNode[], + tree: ITreeLayoutHeadNode[], sharedVar: { seqId: number }, hierarchyType: 'grid' | 'tree' = 'grid', rowExpandLevel: number = undefined @@ -94,20 +95,20 @@ export class DimensionTree { this.reset(tree); } - reset(tree: IPivotLayoutHeadNode[], updateTreeNode = false) { + reset(tree: ITreeLayoutHeadNode[], updateTreeNode = false) { // 清空缓存的计算 // this.cache = {}; // this.dimensions = dimensions; this.cache.clear(); this.dimensionKeys = new NumberMap(); - this.tree.children = tree as IPivotLayoutHeadNode[]; + this.tree.children = tree as ITreeLayoutHeadNode[]; // const re = { totalLevel: 0 }; // if (updateTreeNode) this.updateTreeNode(this.tree, 0, re, this.tree); // else this.setTreeNode(this.tree, 0, this.tree); this.totalLevel = this.dimensionKeys.count(); } - setTreeNode(node: IPivotLayoutHeadNode, startIndex: number, parent: IPivotLayoutHeadNode): number { + setTreeNode(node: ITreeLayoutHeadNode, startIndex: number, parent: ITreeLayoutHeadNode): number { node.startIndex = startIndex; node.startInTotal = (parent.startInTotal ?? 0) + node.startIndex; // if (node.dimensionKey) { @@ -123,10 +124,11 @@ export class DimensionTree { } } let size = node.dimensionKey ? (this.sizeIncludeParent ? 1 : 0) : 0; + const children = node.children || node.columns; //平铺展示 分析所有层级 if (this.hierarchyType === 'grid') { - if (node.children?.length >= 1) { - node.children.forEach(n => { + if (children?.length >= 1) { + children.forEach((n: any) => { n.level = (node.level ?? 0) + 1; size += this.setTreeNode(n, size, node); }); @@ -134,29 +136,29 @@ export class DimensionTree { size = 1; // re.totalLevel = Math.max(re.totalLevel, (node.level ?? -1) + 1); } - } else if (node.hierarchyState === HierarchyState.expand && node.children?.length >= 1) { + } else if (node.hierarchyState === HierarchyState.expand && children?.length >= 1) { //树形展示 有子节点 且下一层需要展开 - node.children.forEach(n => { + children.forEach((n: any) => { n.level = (node.level ?? 0) + 1; size += this.setTreeNode(n, size, node); }); - } else if (node.hierarchyState === HierarchyState.collapse && node.children?.length >= 1) { + } else if (node.hierarchyState === HierarchyState.collapse && children?.length >= 1) { //树形展示 有子节点 且下一层不需要展开 - node.children.forEach(n => { + children.forEach((n: any) => { n.level = (node.level ?? 0) + 1; this.setTreeNode(n, size, node); }); - } else if (!node.hierarchyState && node.level + 1 < this.rowExpandLevel && node.children?.length >= 1) { + } else if (!node.hierarchyState && node.level + 1 < this.rowExpandLevel && children?.length >= 1) { //树形展示 有子节点 且下一层需要展开 node.hierarchyState = HierarchyState.expand; - node.children.forEach(n => { + children.forEach((n: any) => { n.level = (node.level ?? 0) + 1; size += this.setTreeNode(n, size, node); }); - } else if (node.children?.length >= 1) { + } else if (children?.length >= 1) { //树形展示 有子节点 且下一层不需要展开 node.hierarchyState = HierarchyState.collapse; - node.children.forEach(n => { + children.forEach((n: any) => { n.level = (node.level ?? 0) + 1; this.setTreeNode(n, size, node); }); @@ -171,14 +173,14 @@ export class DimensionTree { // startInTotal = parent.startIndex + prevStartIndex return size; } - getTreePath(index: number, maxDeep = 30): Array { + getTreePath(index: number, maxDeep = 30): Array { const path: any[] = []; this.searchPath(index, this.tree, path, maxDeep); path.shift(); return path; } - getTreePathByCellIds(ids: LayoutObjectId[]): Array { + getTreePathByCellIds(ids: LayoutObjectId[]): Array { const path: any[] = []; let nodes = this.tree.children; for (let i = 0; i < ids.length; i++) { @@ -194,12 +196,12 @@ export class DimensionTree { // path.shift(); return path; } - findNodeById(nodes: IPivotLayoutHeadNode[], id: LayoutObjectId) { + findNodeById(nodes: ITreeLayoutHeadNode[], id: LayoutObjectId) { return nodes.find(node => { return node.id === id; }); } - searchPath(index: number, node: IPivotLayoutHeadNode, path: Array, maxDeep: number) { + searchPath(index: number, node: ITreeLayoutHeadNode, path: Array, maxDeep: number) { if (!node) { return; } @@ -267,7 +269,7 @@ export class DimensionTree { */ movePosition(level: number, sourceIndex: number, targetIndex: number) { // let sourceNode: IPivotLayoutHeadNode; - let parNode: IPivotLayoutHeadNode; + let parNode: ITreeLayoutHeadNode; let sourceSubIndex: number; let targetSubIndex: number; /** @@ -277,7 +279,7 @@ export class DimensionTree { * @param subIndex * @returns */ - const findTargetNode = (node: IPivotLayoutHeadNode, subIndex: number) => { + const findTargetNode = (node: ITreeLayoutHeadNode, subIndex: number) => { if (sourceSubIndex !== undefined && targetSubIndex !== undefined) { return; } @@ -292,17 +294,15 @@ export class DimensionTree { targetSubIndex = subIndex; } } - - if (node.children && node.level < level) { + const children = node.children || node.columns; + if (children && node.level < level) { parNode = node; - for (let i = 0; i < node.children.length; i++) { + for (let i = 0; i < children.length; i++) { if ( - (sourceIndex >= node.children[i].startInTotal && - sourceIndex <= node.children[i].startInTotal + node.children[i].size) || - (targetIndex >= node.children[i].startInTotal && - targetIndex <= node.children[i].startInTotal + node.children[i].size) + (sourceIndex >= children[i].startInTotal && sourceIndex <= children[i].startInTotal + children[i].size) || + (targetIndex >= children[i].startInTotal && targetIndex <= children[i].startInTotal + children[i].size) ) { - findTargetNode(node.children[i], i); + findTargetNode(children[i], i); } } } @@ -310,9 +310,16 @@ export class DimensionTree { findTargetNode(this.tree, 0); //对parNode子节点位置进行移位【根据sourceSubIndex和targetSubIndex】 - const sourceColumns = parNode.children.splice(sourceSubIndex, 1); + const children = parNode.children || parNode.columns; + const sourceColumns = children.splice(sourceSubIndex, 1); sourceColumns.unshift(targetSubIndex as any, 0 as any); - Array.prototype.splice.apply(parNode.children, sourceColumns); + Array.prototype.splice.apply(children, sourceColumns); + } + /** 获取纯净树结构 没有level size index这些属性 */ + getCopiedTree() { + const children = cloneDeep(this.tree.children); + clearNode(children); + return children; } } @@ -325,8 +332,8 @@ export type LayouTreeNode = { children?: LayouTreeNode[]; }; -export function generateLayoutTree(tree: LayouTreeNode[], children: IPivotLayoutHeadNode[]) { - children?.forEach((node: IPivotLayoutHeadNode) => { +export function generateLayoutTree(tree: LayouTreeNode[], children: ITreeLayoutHeadNode[]) { + children?.forEach((node: ITreeLayoutHeadNode) => { const diemnsonNode: { dimensionKey?: string; indicatorKey?: string; @@ -352,7 +359,7 @@ export function generateLayoutTree(tree: LayouTreeNode[], children: IPivotLayout //#region 为方法getLayoutRowTreeCount提的工具方法 export function countLayoutTree(children: { children?: any }[], countParentNode: boolean) { let count = 0; - children?.forEach((node: IPivotLayoutHeadNode) => { + children?.forEach((node: ITreeLayoutHeadNode) => { if (countParentNode) { count++; } else { @@ -369,7 +376,7 @@ export function countLayoutTree(children: { children?: any }[], countParentNode: //#endregion export function dealHeader( - hd: IPivotLayoutHeadNode, + hd: ITreeLayoutHeadNode, _headerCellIds: number[][], results: HeaderData[], roots: number[], @@ -482,9 +489,9 @@ export function dealHeader( } } - if ((hd as IPivotLayoutHeadNode).children?.length >= 1) { + if ((hd as ITreeLayoutHeadNode).children?.length >= 1) { layoutMap - ._addHeaders(_headerCellIds, row + ((hd as any).levelSpan ?? 1), (hd as IPivotLayoutHeadNode).children ?? [], [ + ._addHeaders(_headerCellIds, row + ((hd as any).levelSpan ?? 1), (hd as ITreeLayoutHeadNode).children ?? [], [ ...roots, ...Array((hd as any).levelSpan ?? 1).fill(id) ]) @@ -505,7 +512,7 @@ export function dealHeader( } export function dealHeaderForTreeMode( - hd: IPivotLayoutHeadNode, + hd: ITreeLayoutHeadNode, _headerCellIds: number[][], results: HeaderData[], roots: number[], @@ -581,14 +588,14 @@ export function dealHeaderForTreeMode( for (let r = row - 1; r >= 0; r--) { _headerCellIds[r][layoutMap.colIndex] = roots[r]; } - if (hd.hierarchyState === HierarchyState.expand && (hd as IPivotLayoutHeadNode).children?.length >= 1) { + if (hd.hierarchyState === HierarchyState.expand && (hd as ITreeLayoutHeadNode).children?.length >= 1) { //row传值 colIndex++和_addHeaders有区别 show && layoutMap.colIndex++; layoutMap ._addHeadersForTreeMode( _headerCellIds, row, - (hd as IPivotLayoutHeadNode).children ?? [], + (hd as ITreeLayoutHeadNode).children ?? [], [...roots, id], totalLevel, show && hd.hierarchyState === HierarchyState.expand, //当前节点show 且当前节点状态为展开 则传给子节点为show:true @@ -603,3 +610,19 @@ export function dealHeaderForTreeMode( } } } + +function clearNode(children: any) { + for (let i = 0; i < children.length; i++) { + const node = children[i]; + delete node.level; + delete node.startIndex; + delete node.id; + delete node.levelSpan; + delete node.size; + delete node.startInTotal; + const childrenNew = node.children || node.columns; + if (childrenNew) { + clearNode(childrenNew); + } + } +} diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index ac5d1d387..ff18c7814 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -45,14 +45,8 @@ import { isCartesianChart, isHasCartesianChartInline } from './chart-helper/get-chart-spec'; -import type { LayouTreeNode, IPivotLayoutHeadNode } from './pivot-layout-helper'; -import { - DimensionTree, - countLayoutTree, - dealHeader, - dealHeaderForTreeMode, - generateLayoutTree -} from './pivot-layout-helper'; +import type { LayouTreeNode, ITreeLayoutHeadNode } from './layout-helper'; +import { DimensionTree, countLayoutTree, dealHeader, dealHeaderForTreeMode, generateLayoutTree } from './layout-helper'; import type { Dataset } from '../dataset/dataset'; import { cloneDeep, isArray, isValid } from '@visactor/vutils'; import type { TextStyle } from '../body-helper/style'; @@ -217,9 +211,9 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { this.indicatorKeys.push(indicator.indicatorKey); } }); - this.columnDimensionTree = new DimensionTree((this.columnTree as IPivotLayoutHeadNode[]) ?? [], this.sharedVar); + this.columnDimensionTree = new DimensionTree((this.columnTree as ITreeLayoutHeadNode[]) ?? [], this.sharedVar); this.rowDimensionTree = new DimensionTree( - (this.rowTree as IPivotLayoutHeadNode[]) ?? [], + (this.rowTree as ITreeLayoutHeadNode[]) ?? [], this.sharedVar, this.rowHierarchyType, this.rowHierarchyType === 'tree' ? this.rowExpandLevel : undefined @@ -471,7 +465,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { this.setColumnWidths(); } - _addHeaders(_headerCellIds: number[][], row: number, header: IPivotLayoutHeadNode[], roots: number[]): HeaderData[] { + _addHeaders(_headerCellIds: number[][], row: number, header: ITreeLayoutHeadNode[], roots: number[]): HeaderData[] { const _this = this; function _newRow(row: number): number[] { const newRow: number[] = (_headerCellIds[row] = []); @@ -498,7 +492,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { _addHeadersForTreeMode( _headerCellIds: number[][], row: number, - header: IPivotLayoutHeadNode[], + header: ITreeLayoutHeadNode[], roots: number[], totalLevel: number, show: boolean, @@ -1329,6 +1323,44 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { if (this.isRightFrozenColumn(col, row) || this.isBottomFrozenRow(col, row)) { return result; } + + if (this._table.isPivotChart()) { + if (this.isLeftBottomCorner(col, row)) { + return { + start: { + col: 0, + row: this.rowCount - this.bottomFrozenRowCount + }, + end: { + col: this.frozenColCount - 1, + row: this.rowCount - 1 + } + }; + } else if (this.isRightTopCorner(col, row)) { + return { + start: { + col: this.colCount - this.rightFrozenColCount, + row: 0 + }, + end: { + col: this.colCount - 1, + row: this.frozenRowCount - 1 + } + }; + } else if (this.isRightBottomCorner(col, row)) { + return { + start: { + col: this.colCount - this.rightFrozenColCount, + row: this.rowCount - this.bottomFrozenRowCount + }, + end: { + col: this.colCount - 1, + row: this.rowCount - 1 + } + }; + } + } + // if (this._cellRangeMap.has(`$${col}$${row}`)) { // return this._cellRangeMap.get(`$${col}$${row}`); // } @@ -1459,8 +1491,8 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { // console.log(`${col}-${row}`); const recordCol = this.getBodyIndexByCol(col); const recordRow = this.getBodyIndexByRow(row) + this.currentPageStartIndex; - let colPath: IPivotLayoutHeadNode[] = []; - let rowPath: IPivotLayoutHeadNode[] = []; + let colPath: ITreeLayoutHeadNode[] = []; + let rowPath: ITreeLayoutHeadNode[] = []; if (row >= 0 && recordCol >= 0) { colPath = this.columnDimensionTree.getTreePath( recordCol, @@ -1808,7 +1840,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { let row = 0; if (rowDimension) { row = this.columnHeaderLevelCount; - const { startInTotal, level } = rowDimension as IPivotLayoutHeadNode; + const { startInTotal, level } = rowDimension as ITreeLayoutHeadNode; row += startInTotal; if (this.rowHierarchyType === 'grid') { col = this.rowHeaderTitle ? level + 1 : level; @@ -1818,7 +1850,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { return { col, row }; } else if (colDimension) { col = this.rowHeaderLevelCount; - const { startInTotal, level } = colDimension as IPivotLayoutHeadNode; + const { startInTotal, level } = colDimension as ITreeLayoutHeadNode; col += startInTotal; row = this.columnHeaderTitle ? level + 1 : level; return { col, row }; @@ -2013,7 +2045,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { // 对维度树结构调整节点位置 this.columnDimensionTree.movePosition( - source.row, + this.getCellHeaderPathsWidthTreeNode(source.col, source.row).colHeaderPaths.length - 1, sourceCellRange.start.col - this.rowHeaderLevelCount, targetIndex - this.rowHeaderLevelCount ); @@ -2247,7 +2279,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { // 通过dimension获取col和row if (rowDimensionFinded || forceBody) { row = this.columnHeaderLevelCount; - const { startInTotal, level } = (rowDimensionFinded as IPivotLayoutHeadNode) ?? defaultDimension; + const { startInTotal, level } = (rowDimensionFinded as ITreeLayoutHeadNode) ?? defaultDimension; row += startInTotal ?? 0; if (this.rowHierarchyType === 'grid') { defaultCol = this.rowHeaderTitle ? level + 1 : level; @@ -2257,7 +2289,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } if (colDimensionFinded || forceBody) { col = this.rowHeaderLevelCount; - const { startInTotal, level } = (colDimensionFinded as IPivotLayoutHeadNode) ?? defaultDimension; + const { startInTotal, level } = (colDimensionFinded as ITreeLayoutHeadNode) ?? defaultDimension; col += startInTotal ?? 0; defaultRow = this.columnHeaderTitle ? level + 1 : level; } diff --git a/packages/vtable/src/layout/row-height-map.ts b/packages/vtable/src/layout/row-height-map.ts new file mode 100644 index 000000000..35fd4e599 --- /dev/null +++ b/packages/vtable/src/layout/row-height-map.ts @@ -0,0 +1,264 @@ +import { isValid } from '@visactor/vutils'; +import type { BaseTableAPI } from '../ts-types/base-table'; + +export class NumberRangeMap { + data: Map; + cumulativeSum: Map; + difference: Map; + totalSum: number; + table: BaseTableAPI; + isUpdate = false; + private _keys: number[] = []; + private _sorted = false; + + constructor(table: BaseTableAPI) { + this.data = new Map(); + this._keys.length = 0; + this.cumulativeSum = new Map(); + this.difference = new Map(); + this.totalSum = 0; + this.table = table; + } + + clear() { + this.data.clear(); + this.cumulativeSum.clear(); + this.difference.clear(); + this.totalSum = 0; + } + + clearRange() { + this.cumulativeSum.clear(); + this.difference.clear(); + } + + add(position: number, value: number) { + const defaultValue = this.table.getRowHeight(position); + if (!this.data.has(position)) { + this._keys.push(position); + this._sorted = false; + } + this.data.set(position, value); + this.totalSum += value; + // this.updateCumulativeSum(position, value); + this.updateDifference(position, value - defaultValue); + } + + remove(position: number) { + if (this.data.has(position)) { + const value = this.data.get(position); + this.data.delete(position); + const index = this._keys.indexOf(position); + if (index !== -1) { + this._keys.splice(index, 1); // 使用 splice() 方法删除指定索引位置的元素 + } + this.totalSum -= value; + const defaultValue = this.table.getRowHeight(position); + // this.updateCumulativeSum(position, -value); + this.updateDifference(position, defaultValue - value); + } + } + + put(position: number, newValue: number) { + if (this.data.has(position)) { + const oldValue = this.data.get(position); + this.data.set(position, newValue); + const difference = newValue - oldValue; + this.totalSum += difference; + // this.updateCumulativeSum(position, difference); + this.updateDifference(position, difference); + } else { + this.add(position, newValue); + } + } + + get(position: number) { + return this.data.get(position); + } + + has(position: number) { + return this.data.has(position); + } + + private _sort() { + const { _keys: keys } = this; + if (!this._sorted) { + keys.sort((a, b) => { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }); + this._sorted = true; + } + } + + updateDifference(position: number, difference: number) { + const oldDifference = this.difference.get(position) ?? 0; + this.difference.set(position, oldDifference + difference); + this.update(); + } + + getSumInRange(start: number, end: number) { + return this.calculatePrefixSum(end) - this.calculatePrefixSum(start - 1); + } + + updateCumulativeSum(position: number, difference: number) { + // 更新累加和 + for (const [pos, sum] of this.cumulativeSum) { + if (pos >= position) { + this.cumulativeSum.set(pos, sum + difference); + } + } + } + + calculatePrefixSum(position: number) { + if (position < 0) { + return 0; + } + if (this.cumulativeSum.has(position)) { + let cache = this.cumulativeSum.get(position); + for (const [pos, difference] of this.difference) { + if (pos <= position) { + cache += difference; + } + } + return cache; + } + + this.dealDiffenence(); + return this.getCumulativeSum(position); + } + + getCumulativeSum(position: number) { + let sum = 0; + for (let i = position; i >= 0; i--) { + if (this.cumulativeSum.has(i)) { + sum += this.cumulativeSum.get(i); + break; + } else { + sum += this.data.get(i) ?? this.table.getRowHeight(i); + } + } + this.cumulativeSum.set(position, sum); + return sum; + } + + update() { + if (this.isUpdate) { + return; + } + this.isUpdate = true; + setTimeout(() => { + this.dealDiffenence(); + this.isUpdate = false; + }, 0); + } + + dealDiffenence() { + for (const [sumPos, sum] of this.cumulativeSum) { + for (const [difPos, difference] of this.difference) { + if (sumPos >= difPos) { + this.cumulativeSum.set(sumPos, sum + difference); + } + } + } + + this.difference.clear(); + } + + // add and reorder + insert(position: number, value?: number) { + const lastIndex = this.getLastIndex() + 1; + this.adjustOrder(position, position + 1, lastIndex - position); + if (isValid(value)) { + this.put(position, value); + } + } + + getLastIndex() { + this._sort(); + return this._keys[this._keys.length - 1]; + } + + delLast() { + const lastIndex = this.getLastIndex(); + this.remove(lastIndex); + } + + // del and reorder + delete(position: number) { + if (!this.has(position)) { + return; + } + const lastIndex = this.getLastIndex(); + + this.adjustOrder(position + 1, position, lastIndex - position); + this.delLast(); + } + + /** + * 将sourceIndex位置开始 往后moveCount个值 调整到targetIndex位置处 + * @param sourceIndex + * @param targetIndex + * @param moveCount + */ + adjustOrder(sourceIndex: number, targetIndex: number, moveCount: number) { + this.clearRange(); + this._sort(); + const { _keys: keys } = this; + + if (sourceIndex > targetIndex) { + const sourceVals = []; + for (let i = indexFirst(keys, sourceIndex + moveCount - 1); i >= 0; i--) { + const key = keys[i]; + if (key >= sourceIndex) { + sourceVals.push(this.get(key)); + } else if (targetIndex <= key && key < sourceIndex) { + this.put(key + moveCount, this.get(key)); + } else if (key < targetIndex) { + break; + } + } + for (let i = 0; i < moveCount; i++) { + this.put(targetIndex + i, sourceVals[moveCount - 1 - i]); + } + } + const { length } = keys; + if (sourceIndex < targetIndex) { + const sourceVals = []; + for (let i = indexFirst(keys, sourceIndex); i < length; i++) { + const key = keys[i]; + if (key >= sourceIndex && key < sourceIndex + moveCount) { + sourceVals.push(this.get(key)); + } else if (sourceIndex + moveCount <= key && key <= targetIndex) { + this.put(key - moveCount, this.get(key)); + } else if (key > targetIndex) { + break; + } + } + for (let i = 0; i < moveCount; i++) { + this.put(targetIndex + i, sourceVals[i]); + } + } + } +} + +function indexFirst(arr: number[], elm: number): number { + let low = 0; + let high = arr.length - 1; + while (low <= high) { + const i = Math.floor((low + high) / 2); + if (arr[i] === elm) { + return i; + } else if (arr[i] > elm) { + high = i - 1; + } else { + low = i + 1; + } + } + return high < 0 ? 0 : high; +} diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index b9336bf0d..ffecfe47c 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -12,6 +12,7 @@ import type { WidthData } from '../ts-types/list-table/layout-map/api'; import { checkHasChart, getChartDataId } from './chart-helper/get-chart-spec'; +import { DimensionTree } from './layout-helper'; // import { EmptyDataCache } from './utils'; // let seqId = 0; @@ -22,6 +23,8 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { // private _headerObjectFieldKey: { [key in string]: HeaderData }; private _headerCellIds: number[][]; private _columns: ColumnData[]; + /** 后期加的 对应pivot-header-layout 中的columnDimensionTree 为了排序后获取到排序后的columns */ + columnTree: DimensionTree; readonly bodyRowSpanCount: number = 1; //透视表中树形结构使用 这里为了table逻辑不报错 // rowHierarchyIndent?: number = 0; @@ -40,6 +43,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { this._columns = []; this._headerCellIds = []; this.hierarchyIndent = hierarchyIndent ?? 20; + this.columnTree = new DimensionTree(columns as any, { seqId: 0 }); //seqId这里没有利用上 所有顺便传了0 this._headerObjects = this._addHeaders(0, columns, []); this._headerObjectMap = this._headerObjects.reduce((o, e) => { o[e.id as number] = e; @@ -181,7 +185,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { } } else { row = col; - if (this.frozenRowCount > 0 && row >= this.rowCount - this.bottomFrozenRowCount) { + if (this.bottomFrozenRowCount > 0 && row >= this.rowCount - this.bottomFrozenRowCount) { return true; } } @@ -775,6 +779,12 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { if (source.col < 0 || source.row < 0 || target.col < 0 || target.row < 0) { return false; } + if (this._table.internalProps.frozenColDragHeaderMode === 'disabled') { + if (this._table.isFrozenColumn(target.col) || this._table.isRightFrozenColumn(target.col)) { + return false; + } + } + // 获取操作单元格的range范围 const sourceCellRange = this.getCellRange(source.col, source.row); // 获取source和target对应sourceCellRange.start.row的headerId @@ -829,6 +839,9 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { sourceColumns.unshift(targetIndex as any, 0 as any); Array.prototype.splice.apply(this._columns, sourceColumns); + // 对表头columnTree调整节点位置 + this.columnTree.movePosition(sourceCellRange.start.row, sourceCellRange.start.col, targetIndex); + this._cellRangeMap = new Map(); return { sourceIndex: sourceCellRange.start.col, @@ -865,6 +878,9 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { sourceColumns.unshift(targetIndex as any, 0 as any); Array.prototype.splice.apply(this._columns, sourceColumns); + // 对表头columnTree调整节点位置 + this.columnTree.movePosition(sourceCellRange.start.col, sourceCellRange.start.row, targetIndex); + this._cellRangeMap = new Map(); return { sourceIndex: sourceCellRange.start.row, diff --git a/packages/vtable/src/scenegraph/component/cell-mover.ts b/packages/vtable/src/scenegraph/component/cell-mover.ts index 53d9a1b2d..a24e443e4 100644 --- a/packages/vtable/src/scenegraph/component/cell-mover.ts +++ b/packages/vtable/src/scenegraph/component/cell-mover.ts @@ -141,12 +141,12 @@ export class CellMover { update(backX: number | undefined, lineX: number | undefined, backY: number | undefined, lineY: number | undefined) { if (typeof backX === 'number' && typeof lineX === 'number') { - this.columnMoverLabel.setAttribute('x', lineX - this.table.stateManager.scroll.horizontalBarPos); - this.columnMoverLine.setAttribute('x', lineX - this.table.stateManager.scroll.horizontalBarPos); + this.columnMoverLabel.setAttribute('x', lineX); + this.columnMoverLine.setAttribute('x', lineX); this.columnMoverBack.setAttribute('x', backX); } else if (typeof backY === 'number' && typeof lineY === 'number') { - this.columnMoverLabel.setAttribute('y', lineY - this.table.stateManager.scroll.verticalBarPos); - this.columnMoverLine.setAttribute('y', lineY - this.table.stateManager.scroll.verticalBarPos); + this.columnMoverLabel.setAttribute('y', lineY); + this.columnMoverLine.setAttribute('y', lineY); this.columnMoverBack.setAttribute('y', backY); } } diff --git a/packages/vtable/src/scenegraph/graphic/contributions/draw-interceptor.ts b/packages/vtable/src/scenegraph/graphic/contributions/draw-interceptor.ts new file mode 100644 index 000000000..bc7a3ea8f --- /dev/null +++ b/packages/vtable/src/scenegraph/graphic/contributions/draw-interceptor.ts @@ -0,0 +1,143 @@ +import type { + IDrawItemInterceptorContribution, + IGraphic, + IRenderService, + IDrawContext, + IDrawContribution, + IGraphicRenderDrawParams, + IImage +} from '@src/vrender'; +import { injectable, createImage } from '@src/vrender'; +import * as icons from '../../../icons'; +import { calcKeepAspectRatioSize } from '../../utils/keep-aspect-ratio'; +let loadingImage: IImage; + +@injectable() +export class VTableDrawItemInterceptorContribution implements IDrawItemInterceptorContribution { + order: number = 1; + interceptors: IDrawItemInterceptorContribution[]; + constructor() { + this.interceptors = [new ImageDrawItemInterceptorContribution()]; + } + afterDrawItem( + graphic: IGraphic, + renderService: IRenderService, + drawContext: IDrawContext, + drawContribution: IDrawContribution, + params?: IGraphicRenderDrawParams + ): boolean { + for (let i = 0; i < this.interceptors.length; i++) { + if ( + this.interceptors[i].afterDrawItem && + this.interceptors[i].afterDrawItem(graphic, renderService, drawContext, drawContribution, params) + ) { + return true; + } + } + return false; + } + + beforeDrawItem( + graphic: IGraphic, + renderService: IRenderService, + drawContext: IDrawContext, + drawContribution: IDrawContribution, + params?: IGraphicRenderDrawParams + ): boolean { + // 【性能方案】判定写在外层,减少遍历判断耗时,10000条数据减少1ms + if ( + (!graphic.in3dMode || drawContext.in3dInterceptor) && + !graphic.shadowRoot && + !(graphic.baseGraphic || graphic.attribute.globalZIndex || graphic.interactiveGraphic) + ) { + return false; + } + + for (let i = 0; i < this.interceptors.length; i++) { + if ( + this.interceptors[i].beforeDrawItem && + this.interceptors[i].beforeDrawItem(graphic, renderService, drawContext, drawContribution, params) + ) { + return true; + } + } + return false; + } +} + +export class ImageDrawItemInterceptorContribution implements IDrawItemInterceptorContribution { + order: number = 1; + + afterDrawItem( + graphic: IGraphic, + renderService: IRenderService, + drawContext: IDrawContext, + drawContribution: IDrawContribution, + params?: IGraphicRenderDrawParams + ): boolean { + if (graphic.type === 'image') { + this.drawItem(graphic, renderService, drawContext, drawContribution, params); + } + return false; + } + + protected drawItem( + graphic: IImage, + renderService: IRenderService, + drawContext: IDrawContext, + drawContribution: IDrawContribution, + params?: IGraphicRenderDrawParams + ): boolean { + const { image: url } = graphic.attribute; + if (!url || !graphic.resources) { + return false; + } + const res = graphic.resources.get(url); + if (res.state !== 'loading') { + return false; + } + + if (!loadingImage) { + const regedIcons = icons.get(); + const svg = (regedIcons.loading_pic as any).svg; + const width = (regedIcons.loading_pic as any).width; + const height = (regedIcons.loading_pic as any).height; + loadingImage = createImage({ + width, + height, + image: svg + }); + } + const { image: loadingUrl } = loadingImage.attribute; + if (!url || !loadingImage.resources) { + return false; + } + const loadingRes = loadingImage.resources.get(loadingUrl); + if (loadingRes.state !== 'success') { + return false; + } + + const { context } = drawContext; + context.highPerformanceSave(); + // 直接transform + graphic.parent && context.setTransformFromMatrix(graphic.parent.globalTransMatrix, true); + graphic.glyphHost && + graphic.glyphHost.parent && + context.setTransformFromMatrix(graphic.glyphHost.parent.globalTransMatrix, true); + + const b = graphic.AABBBounds; + + const { width, height } = calcKeepAspectRatioSize( + loadingRes.data.width, + loadingRes.data.height, + b.width(), + b.height() + ); + + context.drawImage(loadingRes.data, b.x1 + (b.width() - width) / 2, b.y1 + (b.height() - height) / 2, width, height); + + context.highPerformanceRestore(); + + return true; + } +} diff --git a/packages/vtable/src/scenegraph/graphic/contributions/index.ts b/packages/vtable/src/scenegraph/graphic/contributions/index.ts index d74d060ec..65648d490 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/index.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/index.ts @@ -5,7 +5,8 @@ import { RectRenderContribution, SplitRectBeforeRenderContribution, SplitRectAfterRenderContribution, - ContainerModule + ContainerModule, + DrawItemInterceptor } from '@src/vrender'; import { ChartRender, DefaultCanvasChartRender } from './chart-render'; import { AfterImageRenderContribution, BeforeImageRenderContribution } from './image-contribution-render'; @@ -25,6 +26,7 @@ import { ClipBodyGroupBeforeRenderContribution, ClipBodyGroupAfterRenderContribution } from './group-contribution-render'; +import { VTableDrawItemInterceptorContribution } from './draw-interceptor'; export default new ContainerModule((bind, unbind, isBound, rebind) => { // rect 渲染器注入contributions @@ -77,4 +79,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(GroupRenderContribution).toService(ClipBodyGroupBeforeRenderContribution); bind(ClipBodyGroupAfterRenderContribution).toSelf().inSingletonScope(); bind(GroupRenderContribution).toService(ClipBodyGroupAfterRenderContribution); + + // interceptor + bind(VTableDrawItemInterceptorContribution).toSelf().inSingletonScope(); + bind(DrawItemInterceptor).toService(VTableDrawItemInterceptorContribution); }); diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts index d86997d87..2effcf8ae 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts @@ -12,8 +12,6 @@ import { getProp, getFunctionalProp } from '../../utils/get-prop'; import { isValid } from '@visactor/vutils'; import { getQuadProps } from '../../utils/padding'; -const regedIcons = icons.get(); - export function createImageCellGroup( columnGroup: Group, xOrigin: number, @@ -83,64 +81,48 @@ export function createImageCellGroup( image.name = 'image'; image.keepAspectRatio = keepAspectRatio; if (keepAspectRatio || imageAutoSizing) { - image.successCallback = () => { - const originImage = image.resources.get(image.attribute.image).data; - - if (imageAutoSizing) { - _adjustWidthHeight( - col, - row, - (originImage as HTMLImageElement).width, - (originImage as HTMLImageElement).height, - table.scenegraph, - padding - ); - } - - if (keepAspectRatio) { - const { width: cellWidth, height: cellHeight, isMerge } = getCellRange(cellGroup, table); - - const { width: imageWidth, height: imageHeight } = calcKeepAspectRatioSize( - originImage.width, - originImage.height, - // cellGroup.attribute.width - padding[1] - padding[3], - // cellGroup.attribute.height - padding[0] - padding[2] - cellWidth - padding[1] - padding[3], - cellHeight - padding[0] - padding[2] - ); - - // const left = 0; - // const top = 0; - const pos = calcStartPosition( - 0, - 0, - // cellGroup.attribute.width, - // cellGroup.attribute.height, - cellWidth, - cellHeight, - imageWidth, - imageHeight, + if ( + image.resources && + image.resources.has(image.attribute.image) && + image.resources.get(image.attribute.image).state === 'success' + ) { + updateAutoSizingAndKeepAspectRatio( + imageAutoSizing, + keepAspectRatio, + padding, + textAlign, + textBaseline, + image, + cellGroup, + table + ); + } else { + image.successCallback = () => { + updateAutoSizingAndKeepAspectRatio( + imageAutoSizing, + keepAspectRatio, + padding, textAlign, textBaseline, - padding + image, + cellGroup, + table ); - - image.setAttributes({ - x: pos.x, - y: pos.y, - width: imageWidth, - height: imageHeight, - dx: isMerge ? -table.getColsWidth(cellGroup.mergeStartCol, col - 1) : 0, - dy: isMerge ? -table.getRowsHeight(cellGroup.mergeStartRow, row - 1) : 0 - }); - } - - table.scenegraph.updateNextFrame(); - }; + table.scenegraph.updateNextFrame(); + }; + } } else { - image.successCallback = () => { + if ( + image.resources && + image.resources.has(image.attribute.image) && + image.resources.get(image.attribute.image).state === 'success' + ) { updateImageCellContentWhileResize(cellGroup, col, row, table); - }; + } else { + image.successCallback = () => { + updateImageCellContentWhileResize(cellGroup, col, row, table); + }; + } } (image as any).failCallback = () => { const regedIcons = icons.get(); @@ -320,3 +302,97 @@ function getCellRange(cellGroup: Group, table: BaseTableAPI) { isMerge: false }; } + +function updateImageDxDy(startCol, endCol, startRow, endRow, table) { + for (let col = startCol; col <= endCol; col++) { + for (let row = startRow; row <= endRow; row++) { + const cellGroup = table.scenegraph.getCell(col, row); + if (cellGroup) { + const image = cellGroup.getChildByName('image'); + if (image) { + image.setAttributes({ + dx: -table.getColsWidth(cellGroup.mergeStartCol, col - 1), + dy: -table.getRowsHeight(cellGroup.mergeStartRow, row - 1) + }); + } + } + } + } +} + +function updateAutoSizingAndKeepAspectRatio( + imageAutoSizing: boolean, + keepAspectRatio: boolean, + padding: [number, number, number, number], + textAlign: CanvasTextAlign, + textBaseline: CanvasTextBaseline, + image: Image, + cellGroup: Group, + table: BaseTableAPI +) { + const originImage = image.resources.get(image.attribute.image).data; + const { col, row } = cellGroup; + + if (imageAutoSizing && !isDamagePic(image)) { + _adjustWidthHeight( + col, + row, + (originImage as HTMLImageElement).width, + (originImage as HTMLImageElement).height, + table.scenegraph, + padding + ); + } + if (keepAspectRatio || isDamagePic(image)) { + const { width: cellWidth, height: cellHeight, isMerge } = getCellRange(cellGroup, table); + + const { width: imageWidth, height: imageHeight } = calcKeepAspectRatioSize( + originImage.width, + originImage.height, + // cellGroup.attribute.width - padding[1] - padding[3], + // cellGroup.attribute.height - padding[0] - padding[2] + cellWidth - padding[1] - padding[3], + cellHeight - padding[0] - padding[2] + ); + + // const left = 0; + // const top = 0; + const pos = calcStartPosition( + 0, + 0, + // cellGroup.attribute.width, + // cellGroup.attribute.height, + cellWidth, + cellHeight, + imageWidth, + imageHeight, + textAlign, + textBaseline, + padding + ); + + image.setAttributes({ + x: pos.x, + y: pos.y, + width: imageWidth, + height: imageHeight + // dx: isMerge ? -table.getColsWidth(cellGroup.mergeStartCol, col - 1) : 0, + // dy: isMerge ? -table.getRowsHeight(cellGroup.mergeStartRow, row - 1) : 0 + }); + + if (isMerge) { + updateImageDxDy( + cellGroup.mergeStartCol, + cellGroup.mergeEndCol, + cellGroup.mergeStartRow, + cellGroup.mergeEndRow, + table + ); + } + } +} + +function isDamagePic(image: IImage) { + const regedIcons = icons.get(); + return image.attribute.image === (regedIcons.damage_pic as any).svg; +} diff --git a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts index d63a62d12..d9c3694f0 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/proxy.ts @@ -509,7 +509,8 @@ export class SceneProxy { this.rowUpdatePos, // rowStart distRow, // rowEnd this.table, - this.rowUpdateDirection + this.rowUpdateDirection, + true ); // row header group updateAutoRow( @@ -518,7 +519,8 @@ export class SceneProxy { this.rowUpdatePos, // rowStart distRow, // rowEnd this.table, - this.rowUpdateDirection + this.rowUpdateDirection, + true ); // right frozen group updateAutoRow( @@ -527,7 +529,8 @@ export class SceneProxy { this.rowUpdatePos, // rowStart distRow, // rowEnd this.table, - this.rowUpdateDirection + this.rowUpdateDirection, + true ); } diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts index ab2413a39..f0f15a779 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-x.ts @@ -92,7 +92,11 @@ async function moveColumn( proxy.table.scenegraph.proxy.deltaX += deltaX; proxy.currentCol = direction === 'left' ? proxy.currentCol + count : proxy.currentCol - count; - proxy.totalCol = direction === 'left' ? proxy.totalCol + count : proxy.totalCol - count; + proxy.totalCol = Math.max( + 0, + Math.min(proxy.table.colCount - 1, direction === 'left' ? proxy.totalCol + count : proxy.totalCol - count) + ); + proxy.referenceCol = proxy.colStart + Math.floor((proxy.colEnd - proxy.colStart) / 2); proxy.colUpdatePos = distStartCol; proxy.colUpdateDirection = direction; @@ -147,7 +151,10 @@ async function moveColumn( } proxy.currentCol = direction === 'left' ? proxy.currentCol + count : proxy.currentCol - count; - proxy.totalCol = direction === 'left' ? proxy.totalCol + count : proxy.totalCol - count; + proxy.totalCol = Math.max( + 0, + Math.min(proxy.table.colCount - 1, direction === 'left' ? proxy.totalCol + count : proxy.totalCol - count) + ); proxy.referenceCol = proxy.colStart + Math.floor((proxy.colEnd - proxy.colStart) / 2); proxy.colUpdatePos = proxy.colStart; proxy.colUpdateDirection = distEndCol > proxy.bodyRightCol - (proxy.colEnd - proxy.colStart + 1) ? 'right' : 'left'; diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts index ffdb342ad..96c30f447 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/dynamic-set-y.ts @@ -101,7 +101,8 @@ async function moveCell( syncTopRow, // rowStart syncBottomRow, // rowEnd proxy.table, - distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up' // 跳转到底部时,从下向上对齐 + distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up', // 跳转到底部时,从下向上对齐 + true ); // row header group @@ -111,7 +112,8 @@ async function moveCell( syncTopRow, // rowStart syncBottomRow, // rowEnd proxy.table, - distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up' // 跳转到底部时,从下向上对齐 + distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up', // 跳转到底部时,从下向上对齐 + true ); // right frozen group @@ -121,7 +123,8 @@ async function moveCell( syncTopRow, // rowStart syncBottomRow, // rowEnd proxy.table, - distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up' // 跳转到底部时,从下向上对齐 + distEndRow > proxy.bodyBottomRow - (proxy.rowEnd - proxy.rowStart + 1) ? 'down' : 'up', // 跳转到底部时,从下向上对齐 + true ); const cellGroup = proxy.table.scenegraph.highPerformanceGetCell(proxy.colStart, screenTopRow, true); diff --git a/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts b/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts index 21fa63c3c..ddf94fe8d 100644 --- a/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts +++ b/packages/vtable/src/scenegraph/group-creater/progress/update-position/update-auto-row.ts @@ -7,7 +7,8 @@ export function updateAutoRow( rowStart: number, rowEnd: number, table: BaseTableAPI, - direction: 'up' | 'down' = 'up' + direction: 'up' | 'down' = 'up', + part?: boolean ) { // 更新y位置 if (direction === 'up') { @@ -21,6 +22,12 @@ export function updateAutoRow( if (cellGroup._prev) { // y = ((cellGroup._prev as Group)?.attribute.y ?? 0) + ((cellGroup._prev as Group)?.attribute.height ?? 0); y = (cellGroup._prev as Group)?.attribute.y + table.getRowHeight((cellGroup._prev as Group).row); + } else if (part) { + const baseCellGroup = table.scenegraph.highPerformanceGetCell(col, rowEnd + 1, true); + y = baseCellGroup.attribute.y; + for (let r = rowStart; r <= rowEnd; r++) { + y -= table.getRowHeight(r); + } } else { // 估计位置 y = table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row - 1); @@ -39,6 +46,12 @@ export function updateAutoRow( if (cellGroup._next) { // y = ((cellGroup._next as Group)?.attribute.y ?? 0) - (cellGroup.attribute.height ?? 0); y = (cellGroup._next as Group)?.attribute.y - table.getRowHeight(cellGroup.row); + } else if (part) { + const baseCellGroup = table.scenegraph.highPerformanceGetCell(col, rowStart - 1, true); + y = baseCellGroup.attribute.y; + for (let r = rowStart; r <= rowEnd; r++) { + y += table.getRowHeight(r); + } } else { // 估计位置 y = table.getRowsHeight(table.columnHeaderLevelCount, cellGroup.row - 1); diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts index 9473e3523..ebd041049 100644 --- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts +++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts @@ -79,11 +79,9 @@ export function computeRowsHeight( } if (isAllRowsAuto || table.getDefaultRowHeight(row) === 'auto') { const height = computeRowHeight(row, startCol, endCol, table); - if (update) { - newHeights[row] = Math.round(height); - } else { - table._setRowHeight(row, height); - } + newHeights[row] = Math.round(height); + //表头部分需要马上设置到缓存中 因为adaptive不会调整表头的高度 另外后面adaptive处理过程中有取值 table.getRowsHeight(0, table.columnHeaderLevelCount - 1); + table._setRowHeight(row, height); } } @@ -168,7 +166,8 @@ export function computeRowsHeight( } } } else { - table.rowHeightsMap.clear(); + // table.rowHeightsMap.clear(); + table.clearRowHeightCache(); for (let row = 0; row < table.rowCount; row++) { newHeights[row] = table.getRowHeight(row); } @@ -271,16 +270,33 @@ export function computeRowsHeight( for (let row = 0; row < table.rowCount; row++) { const newRowHeight = newHeights[row] ?? table.getRowHeight(row); if (newRowHeight !== oldRowHeights[row]) { - // update the row height in scenegraph table._setRowHeight(row, newRowHeight); - // table.scenegraph.updateRowHeight(row, newRowHeight - oldRowHeights[row], true); } } - for (let row = 0; row < table.rowCount; row++) { + + if ( + table.heightMode === 'adaptive' || + (table.autoFillHeight && table.getAllRowsHeight() <= table.tableNoFrameHeight) + ) { + for (let row = 0; row <= table.columnHeaderLevelCount - 1; row++) { + const newRowHeight = table.getRowHeight(row); + if (newRowHeight !== oldRowHeights[row]) { + // update the row height in scenegraph + table.scenegraph.updateRowHeight(row, newRowHeight - oldRowHeights[row], true); + } + } + for (let row = table.rowCount - table.bottomFrozenRowCount; row <= table.rowCount - 1; row++) { + const newRowHeight = table.getRowHeight(row); + if (newRowHeight !== oldRowHeights[row]) { + // update the row height in scenegraph + table.scenegraph.updateRowHeight(row, newRowHeight - oldRowHeights[row], true); + } + } + } + for (let row = table.scenegraph.proxy.rowStart; row <= table.scenegraph.proxy.rowEnd; row++) { const newRowHeight = table.getRowHeight(row); if (newRowHeight !== oldRowHeights[row]) { // update the row height in scenegraph - // table._setRowHeight(row, newRowHeight); table.scenegraph.updateRowHeight(row, newRowHeight - oldRowHeights[row], true); } } @@ -695,7 +711,7 @@ function computeTextHeight(col: number, row: number, cellType: ColumnTypeOption, wordBreak: 'break-word', whiteSpace: lines.length === 1 && !autoWrapText ? 'no-wrap' : 'normal' }); - maxHeight = utilTextMark.AABBBounds.height(); + maxHeight = utilTextMark.AABBBounds.height() || (typeof lineHeight === 'number' ? lineHeight : fontSize); } else { // autoWrapText = false maxHeight = lines.length * lineHeight; diff --git a/packages/vtable/src/scenegraph/layout/move-cell.ts b/packages/vtable/src/scenegraph/layout/move-cell.ts index e758e0874..29e22ab0f 100644 --- a/packages/vtable/src/scenegraph/layout/move-cell.ts +++ b/packages/vtable/src/scenegraph/layout/move-cell.ts @@ -81,6 +81,8 @@ export function moveHeaderPosition( const columnHeaderGroup = table.scenegraph.getColGroup(col, true); const columnGroup = table.scenegraph.getColGroup(col); const columnBottomGroup = table.scenegraph.getColGroupInBottom(col); + const columnLeftBottomGroup = table.scenegraph.getColGroupInLeftBottomCorner(col); + const columnRightBottomGroup = table.scenegraph.getColGroupInRightBottomCorner(col); if (columnHeaderGroup) { columnHeaderGroup.setAttribute('width', columnWidth); columnHeaderGroup.forEachChildren((child: Group) => { @@ -99,6 +101,18 @@ export function moveHeaderPosition( child.setAttribute('width', columnWidth); }); } + if (columnRightBottomGroup) { + columnRightBottomGroup.setAttribute('width', columnWidth); + columnRightBottomGroup.forEachChildren((child: Group) => { + child.setAttribute('width', columnWidth); + }); + } + if (columnLeftBottomGroup) { + columnLeftBottomGroup.setAttribute('width', columnWidth); + columnLeftBottomGroup.forEachChildren((child: Group) => { + child.setAttribute('width', columnWidth); + }); + } } // 更新容器尺寸 diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts index 40458263c..338cd5ea2 100644 --- a/packages/vtable/src/scenegraph/layout/update-height.ts +++ b/packages/vtable/src/scenegraph/layout/update-height.ts @@ -49,10 +49,13 @@ export function updateRowHeight(scene: Scenegraph, row: number, detaY: number, s rowStart = row + 1; rowEnd = scene.table.columnHeaderLevelCount - 1; + } else if (row >= scene.table.rowCount - scene.table.bottomFrozenRowCount) { + rowStart = row + 1; + rowEnd = scene.table.rowCount - 1; } else { rowStart = row + 1; // rowEnd = scene.table.rowCount - 1; - rowEnd = scene.bodyRowEnd; //- scene.table.bottomFrozenRowCount; + rowEnd = Math.min(scene.proxy.rowEnd, scene.table.rowCount - scene.table.bottomFrozenRowCount - 1); //- scene.table.bottomFrozenRowCount; } // 更新以下行位置 diff --git a/packages/vtable/src/scenegraph/layout/update-row.ts b/packages/vtable/src/scenegraph/layout/update-row.ts index 09f7df91d..4dc2250e8 100644 --- a/packages/vtable/src/scenegraph/layout/update-row.ts +++ b/packages/vtable/src/scenegraph/layout/update-row.ts @@ -5,6 +5,7 @@ import { Group } from '../graphic/group'; import { updateCell } from '../group-creater/cell-helper'; import type { Scenegraph } from '../scenegraph'; import { getCellMergeInfo } from '../utils/get-cell-merge'; +import { deduplication } from '../../tools/util'; /** * add and remove rows in scenegraph @@ -28,7 +29,7 @@ export function updateRow( const rowHeightsMap = table.rowHeightsMap; removeRows.forEach(row => { - rowHeightsMap.delAndReorder(row); + rowHeightsMap.delete(row); }); if (removeRows.length) { @@ -46,7 +47,7 @@ export function updateRow( addRows.forEach(row => { const needUpdateAfter = addRow(row, scene); updateAfter = updateAfter ?? needUpdateAfter; - rowHeightsMap.addAndReorder(row); + rowHeightsMap.insert(row); }); // reset attribute y and row number in CellGroup @@ -201,18 +202,6 @@ function addRow(row: number, scene: Scenegraph) { // scene.proxy.rowEnd++; // scene.proxy.currentRow++; } - -// array deduplication -function deduplication(array: number[]) { - const result = []; - for (let i = 0; i < array.length; i++) { - if (result.indexOf(array[i]) === -1) { - result.push(array[i]); - } - } - return result; -} - function resetRowNumber(scene: Scenegraph) { scene.bodyGroup.forEachChildren((colGroup: Group) => { let rowIndex = scene.bodyRowStart; diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts index bbd7a96ca..e589c20c6 100644 --- a/packages/vtable/src/scenegraph/scenegraph.ts +++ b/packages/vtable/src/scenegraph/scenegraph.ts @@ -34,7 +34,7 @@ import { moveSelectingRangeComponentsToSelectedRangeComponents } from './select/ import { deleteAllSelectBorder, deleteLastSelectedRangeComponents } from './select/delete-select-border'; import { updateRow } from './layout/update-row'; import { handleTextStick } from './stick-text'; -import { computeRowsHeight } from './layout/compute-row-height'; +import { computeRowHeight, computeRowsHeight } from './layout/compute-row-height'; import { emptyGroup } from './utils/empty-group'; import { dealBottomFrozen, dealFrozen, dealRightFrozen, resetFrozen } from './layout/frozen'; import { updateChartSize, updateChartState } from './refresh-node/update-chart'; @@ -60,6 +60,7 @@ import { import { Env } from '../tools/env'; import { createCornerCell } from './style/corner-cell'; import { updateCol } from './layout/update-col'; +import { deduplication } from '../tools/util'; // import { contextModule } from './context/module'; registerForVrender(); @@ -941,11 +942,9 @@ export class Scenegraph { } updateRowHeight(row: number, detaY: number, skipTableHeightMap?: boolean) { - if (row >= this.proxy.rowStart && row <= this.proxy.rowEnd) { - detaY = Math.round(detaY); - updateRowHeight(this, row, detaY, skipTableHeightMap); - this.updateContainerHeight(row, detaY); - } + detaY = Math.round(detaY); + updateRowHeight(this, row, detaY, skipTableHeightMap); + this.updateContainerHeight(row, detaY); } updateRowsHeight(rows: number[], detaYs: number[], skipTableHeightMap?: boolean) { for (let i = 0; i < rows.length; i++) { @@ -1026,7 +1025,12 @@ export class Scenegraph { if (oldHeight === height) { return; } - this.updateRowHeight(row, height - oldHeight); + if ( + (row >= this.proxy.rowStart && row <= this.proxy.rowEnd) || + (row >= this.table.rowCount - this.table.bottomFrozenRowCount && row <= this.table.rowCount - 1) + ) { + this.updateRowHeight(row, height - oldHeight); + } this.table._clearRowRangeHeightsMap(row); } @@ -1407,7 +1411,7 @@ export class Scenegraph { // this.rightFrozenGroup.setDeltaWidth(colHeaderX - this.table.getRightFrozenColsWidth()); this.rowHeaderGroup.setDeltaWidth(rowHeaderX - this.rowHeaderGroup.attribute.width); this.bottomFrozenGroup.setDeltaWidth(colHeaderX - this.bottomFrozenGroup.attribute.width); - this.rightFrozenGroup.setDeltaWidth(rightX - this.table.getRightFrozenColsWidth()); + this.rightFrozenGroup.setDeltaWidth(rightX - this.rightFrozenGroup.attribute.width); this.rightTopCornerGroup.setDeltaWidth(rightX - this.rightTopCornerGroup.attribute.width); this.rightBottomCornerGroup.setDeltaWidth(rightX - this.rightBottomCornerGroup.attribute.width); this.bodyGroup.setDeltaWidth(bodyX - this.bodyGroup.attribute.width); @@ -1652,14 +1656,42 @@ export class Scenegraph { } updateRow(removeCells: CellAddress[], addCells: CellAddress[], updateCells: CellAddress[] = []) { + const addRows = deduplication(addCells.map(cell => cell.row)).sort((a, b) => a - b); + const updateRows = deduplication(updateCells.map(cell => cell.row)).sort((a, b) => a - b); + //这个值是后续为了autoFillHeight判断逻辑中用到的 判断是否更新前是未填满的情况 + const isNotFillHeight = + this.table.getAllRowsHeight() - + [...addRows, ...updateRows].reduce((tolHeight, rowNumber) => { + return tolHeight + this.table.getRowHeight(rowNumber); + }, 0) <= + this.table.tableNoFrameHeight; + // add or move rows updateRow(removeCells, addCells, updateCells, this.table); // update column width and row height this.recalculateColWidths(); - this.recalculateRowHeights(); + // this.recalculateRowHeights(); + if ( + this.table.heightMode === 'adaptive' || + (this.table.autoFillHeight && (this.table.getAllRowsHeight() <= this.table.tableNoFrameHeight || isNotFillHeight)) + ) { + this.table.scenegraph.recalculateRowHeights(); + } else if (this.table.heightMode === 'autoHeight') { + for (let i = 0; i < updateRows.length; i++) { + const row = updateRows[i]; + const oldHeight = this.table.getRowHeight(row); + const newHeight = computeRowHeight(row, 0, this.table.colCount - 1, this.table); + if ( + (row >= this.proxy.rowStart && row <= this.proxy.rowEnd) || + (row >= this.table.rowCount - this.table.bottomFrozenRowCount && row <= this.table.rowCount - 1) + ) { + this.table.scenegraph.updateRowHeight(row, newHeight - oldHeight); + } + } + } // check frozen status this.table.stateManager.checkFrozen(); diff --git a/packages/vtable/src/scenegraph/stick-text/index.ts b/packages/vtable/src/scenegraph/stick-text/index.ts index 781c93e48..a000caf5b 100644 --- a/packages/vtable/src/scenegraph/stick-text/index.ts +++ b/packages/vtable/src/scenegraph/stick-text/index.ts @@ -7,7 +7,7 @@ import { isNumber } from '@visactor/vutils'; export function handleTextStick(table: BaseTableAPI) { // reset - const { changedCells } = table.internalProps.stick; + const { changedCells } = table.internalProps.stick; // changedCells only cache one time changedCells.forEach((cellPos: StickCell) => { const cellGroup = table.scenegraph.getCell(cellPos.col, cellPos.row); cellGroup.forEachChildren((child: IGraphic) => { @@ -17,7 +17,7 @@ export function handleTextStick(table: BaseTableAPI) { }); }); }); - changedCells.length = 0; + changedCells.clear(); const { scrollTop, scrollLeft, frozenRowCount, frozenColCount } = table; const frozenRowsHeight = table.getFrozenRowsHeight(); @@ -130,7 +130,7 @@ function adjustCellContentVerticalLayout( cellGroup: Group, minTop: number, maxTop: number, - changedCells: StickCell[], + changedCells: Map, table: BaseTableAPI ) { if ( @@ -150,7 +150,7 @@ function adjustCellContentVerticalLayout( } } -function dealVertical(cellGroup: Group, minTop: number, maxTop: number, changedCells: StickCell[]) { +function dealVertical(cellGroup: Group, minTop: number, maxTop: number, changedCells: Map) { // get text element const graphic = (cellGroup.getChildByName('text', true) as Text) || (cellGroup.getChildByName('image', true) as Image); @@ -171,28 +171,35 @@ function dealVertical(cellGroup: Group, minTop: number, maxTop: number, changedC graphic.AABBBounds.width(); const textTop = graphic.globalAABBBounds.y1; const textBottom = graphic.globalAABBBounds.y2; + // const textCellTop = graphic.AABBBounds.y1; + // const textCellBottom = graphic.AABBBounds.y2; + // if (textCellTop < cellGroup.attribute.height || textCellBottom < 0) { + // return; + // } if (textTop < minTop) { const deltaHeight = textTop - minTop; // text is out of view, move all elements down - changedCells.push({ - col: cellGroup.col, - row: cellGroup.row, - dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, - dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 - }); + !changedCells.has(`${cellGroup.col}-${cellGroup.row}`) && + changedCells.set(`${cellGroup.col}-${cellGroup.row}`, { + col: cellGroup.col, + row: cellGroup.row, + dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, + dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 + }); cellGroup.forEachChildren((child: IGraphic) => { child.setAttribute('dy', (child.attribute.dy ?? 0) - deltaHeight + 2); // 2 is the buffer }); } else if (textBottom > maxTop) { const deltaHeight = textBottom - maxTop; // text is out of view, move all elements down - changedCells.push({ - col: cellGroup.col, - row: cellGroup.row, - dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, - dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 - }); + !changedCells.has(`${cellGroup.col}-${cellGroup.row}`) && + changedCells.set(`${cellGroup.col}-${cellGroup.row}`, { + col: cellGroup.col, + row: cellGroup.row, + dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, + dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 + }); cellGroup.forEachChildren((child: IGraphic) => { child.setAttribute('dy', (child.attribute.dy ?? 0) - deltaHeight); // 2 is the buffer }); @@ -208,7 +215,7 @@ function adjustCellContentHorizontalLayout( cellGroup: Group, minLeft: number, maxLeft: number, - changedCells: StickCell[], + changedCells: Map, table: BaseTableAPI ) { if ( @@ -228,7 +235,7 @@ function adjustCellContentHorizontalLayout( } } -function dealHorizontal(cellGroup: Group, minLeft: number, maxLeft: number, changedCells: StickCell[]) { +function dealHorizontal(cellGroup: Group, minLeft: number, maxLeft: number, changedCells: Map) { // get text element const text = cellGroup.getChildByName('text', true) as Text; if (!text) { @@ -240,24 +247,26 @@ function dealHorizontal(cellGroup: Group, minLeft: number, maxLeft: number, chan if (textLeft < minLeft) { const deltaWidth = textLeft - minLeft; // text is out of view, move all elements right - changedCells.push({ - col: cellGroup.col, - row: cellGroup.row, - dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, - dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 - }); + !changedCells.has(`${cellGroup.col}-${cellGroup.row}`) && + changedCells.set(`${cellGroup.col}-${cellGroup.row}`, { + col: cellGroup.col, + row: cellGroup.row, + dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, + dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 + }); cellGroup.forEachChildren((child: IGraphic) => { child.setAttribute('dx', (child.attribute.dx ?? 0) - deltaWidth + 2); // 2 is the buffer }); } else if (textRight > maxLeft) { const deltaWidth = textRight - maxLeft; // text is out of view, move all elements down - changedCells.push({ - col: cellGroup.col, - row: cellGroup.row, - dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, - dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 - }); + !changedCells.has(`${cellGroup.col}-${cellGroup.row}`) && + changedCells.set(`${cellGroup.col}-${cellGroup.row}`, { + col: cellGroup.col, + row: cellGroup.row, + dx: (cellGroup.firstChild as IGraphic)?.attribute.dx ?? 0, + dy: (cellGroup.firstChild as IGraphic)?.attribute.dy ?? 0 + }); cellGroup.forEachChildren((child: IGraphic) => { child.setAttribute('dx', (child.attribute.dx ?? 0) - deltaWidth); // 2 is the buffer }); diff --git a/packages/vtable/src/scenegraph/utils/keep-aspect-ratio.ts b/packages/vtable/src/scenegraph/utils/keep-aspect-ratio.ts index 76ce96a7f..0430e40ba 100644 --- a/packages/vtable/src/scenegraph/utils/keep-aspect-ratio.ts +++ b/packages/vtable/src/scenegraph/utils/keep-aspect-ratio.ts @@ -1,22 +1,52 @@ export function calcKeepAspectRatioSize( - width: number, - height: number, - maxWidth: number, - maxHeight: number + width: number, // image width + height: number, // image height + maxWidth: number, // cell width + maxHeight: number // cell height ): { width: number; height: number; } { - let newWidth = width; - let newHeight = height; - if (newWidth > maxWidth) { - newWidth = maxWidth; - newHeight = (newWidth * height) / width; - } - if (newHeight > maxHeight) { - newHeight = maxHeight; - newWidth = (newHeight * width) / height; + // let newWidth = width; + // let newHeight = height; + // if (newWidth > maxWidth) { + // newWidth = maxWidth; + // newHeight = (newWidth * height) / width; + // } + // if (newHeight > maxHeight) { + // newHeight = maxHeight; + // newWidth = (newHeight * width) / height; + // } + // return { + // width: newWidth, + // height: newHeight + // }; + + const rectWidth = width; + const rectHeight = height; + const containerWidth = maxWidth; + const containerHeight = maxHeight; + const containerRatio = containerWidth / containerHeight; + const rectRatio = rectWidth / rectHeight; + let newWidth; + let newHeight; + let offsetX; + let offsetY; + + if (rectRatio > containerRatio) { + // 矩形的宽高比较大,以容器的宽度为基准进行缩放 + newWidth = containerWidth; + newHeight = newWidth / rectRatio; + offsetX = 0; + offsetY = (containerHeight - newHeight) / 2; + } else { + // 矩形的高宽比较大,以容器的高度为基准进行缩放 + newHeight = containerHeight; + newWidth = newHeight * rectRatio; + offsetY = 0; + offsetX = (containerWidth - newWidth) / 2; } + return { width: newWidth, height: newHeight diff --git a/packages/vtable/src/state/cell-move/index.ts b/packages/vtable/src/state/cell-move/index.ts index b3c89b608..4daaf8f69 100644 --- a/packages/vtable/src/state/cell-move/index.ts +++ b/packages/vtable/src/state/cell-move/index.ts @@ -1,5 +1,6 @@ import type { ListTable } from '../../ListTable'; import { getCellMergeInfo } from '../../scenegraph/utils/get-cell-merge'; +import type { CellRange } from '../../ts-types'; import type { BaseTableAPI } from '../../ts-types/base-table'; import type { StateManager } from '../state'; import { adjustMoveHeaderTarget } from './adjust-header'; @@ -57,19 +58,39 @@ export function updateMoveCol(col: number, row: number, x: number, y: number, st let backX; let lineY; let backY; - const cellLocation = state.table.getCellLocation(col, row); + const cellLocation = state.table.getCellLocation(state.columnMove.colSource, state.columnMove.rowSource); if (cellLocation === 'columnHeader') { - (backX = state.columnMove.x), - (lineX = + backX = state.columnMove.x; + if (state.table.isFrozenColumn(col)) { + lineX = state.columnMove.colTarget >= state.columnMove.colSource ? state.table.getColsWidth(0, state.columnMove.colTarget) - : state.table.getColsWidth(0, state.columnMove.colTarget - 1)); + : state.table.getColsWidth(0, state.columnMove.colTarget - 1); + } else if (state.table.isRightFrozenColumn(col)) { + lineX = state.table.tableNoFrameWidth - state.table.getColsWidth(targetCell.col + 1, state.table.colCount - 1); + } else { + lineX = + (state.columnMove.colTarget >= state.columnMove.colSource + ? state.table.getColsWidth(0, state.columnMove.colTarget) + : state.table.getColsWidth(0, state.columnMove.colTarget - 1)) - + state.table.stateManager.scroll.horizontalBarPos; + } } else if (cellLocation === 'rowHeader') { - (backY = state.columnMove.y), - (lineY = + backY = state.columnMove.y; + if (state.table.isFrozenRow(row)) { + lineY = state.columnMove.rowTarget >= state.columnMove.rowSource ? state.table.getRowsHeight(0, state.columnMove.rowTarget) - : state.table.getRowsHeight(0, state.columnMove.rowTarget - 1)); + : state.table.getRowsHeight(0, state.columnMove.rowTarget - 1); + } else if (state.table.isBottomFrozenRow(row)) { + lineY = state.table.tableNoFrameHeight - state.table.getRowsHeight(targetCell.row + 1, state.table.rowCount - 1); + } else { + lineY = + (state.columnMove.rowTarget >= state.columnMove.rowSource + ? state.table.getRowsHeight(0, state.columnMove.rowTarget) + : state.table.getRowsHeight(0, state.columnMove.rowTarget - 1)) - + state.table.stateManager.scroll.verticalBarPos; + } } state.table.scenegraph.component.updateMoveCol(backX, lineX, backY, lineY); @@ -116,6 +137,35 @@ export function endMoveCol(state: StateManager) { sourceMergeInfo, targetMergeInfo ); + //调整冻结列数量 + if (state.table.internalProps.frozenColDragHeaderMode === 'adjustFrozenCount' && state.table.isListTable()) { + if ( + state.table.isFrozenColumn(state.columnMove.colTarget) && + !state.table.isFrozenColumn(state.columnMove.colSource) + ) { + state.table.frozenColCount += + (sourceMergeInfo as CellRange).end.col - (sourceMergeInfo as CellRange).start.col + 1; + } else if ( + state.table.isFrozenColumn(state.columnMove.colSource) && + !state.table.isFrozenColumn(state.columnMove.colTarget) + ) { + state.table.frozenColCount -= + (sourceMergeInfo as CellRange).end.col - (sourceMergeInfo as CellRange).start.col + 1; + } + if ( + state.table.isRightFrozenColumn(state.columnMove.colTarget) && + !state.table.isRightFrozenColumn(state.columnMove.colSource) + ) { + state.table.rightFrozenColCount += + (sourceMergeInfo as CellRange).end.col - (sourceMergeInfo as CellRange).start.col + 1; + } else if ( + state.table.isRightFrozenColumn(state.columnMove.colSource) && + !state.table.isRightFrozenColumn(state.columnMove.colTarget) + ) { + state.table.rightFrozenColCount -= + (sourceMergeInfo as CellRange).end.col - (sourceMergeInfo as CellRange).start.col + 1; + } + } } state.updateCursor(); diff --git a/packages/vtable/src/state/select/update-position.ts b/packages/vtable/src/state/select/update-position.ts index 05fd34936..aa6006157 100644 --- a/packages/vtable/src/state/select/update-position.ts +++ b/packages/vtable/src/state/select/update-position.ts @@ -136,7 +136,10 @@ export function updateSelectPosition( }); } else if (col >= 0 && row >= 0) { const cellRange = table.getCellRange(col, row); - state.select.ranges.push(cellRange); + state.select.ranges.push({ + start: { col: cellRange.start.col, row: cellRange.start.row }, + end: { col: cellRange.end.col, row: cellRange.end.row } + }); } cellPos.col = col; cellPos.row = row; diff --git a/packages/vtable/src/tools/util.ts b/packages/vtable/src/tools/util.ts index 8d6eebb95..c16df72bb 100644 --- a/packages/vtable/src/tools/util.ts +++ b/packages/vtable/src/tools/util.ts @@ -435,3 +435,13 @@ export function isAllDigits(str: string) { const pattern = /^-?\d+(\.\d+)?$/; return pattern.test(str); } +// array deduplication +export function deduplication(array: number[]) { + const result = []; + for (let i = 0; i < array.length; i++) { + if (result.indexOf(array[i]) === -1) { + result.push(array[i]); + } + } + return result; +} diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index e8afc5e23..e38f83cf9 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -67,6 +67,7 @@ import type { Title } from '../components/title/title'; import type { ITitle } from './component/title'; import type { DiscreteTableLegend } from '../components/legend/discrete-legend/discrete-legend'; import type { ContinueTableLegend } from '../components/legend/continue-legend/continue-legend'; +import type { NumberRangeMap } from '../layout/row-height-map'; export interface IBaseTableProtected { element: HTMLElement; @@ -98,10 +99,15 @@ export interface IBaseTableProtected { columnResizeType?: 'column' | 'indicator' | 'all' | 'indicatorGroup'; /** 控制拖拽表头移动位置顺序开关 */ dragHeaderMode?: 'all' | 'none' | 'column' | 'row'; - + /** 拖拽表头移动位置 针对冻结部分的规则 + * "disabled"(禁止调整冻结列位置):不允许其他列的表头移入冻结列,也不允许冻结列移出,冻结列保持不变。 + * "adjustFrozenCount"(根据交互结果调整冻结数量):允许其他列的表头移入冻结列,及冻结列移出,并根据拖拽的动作调整冻结列的数量。当其他列的表头被拖拽进入冻结列位置时,冻结列数量增加;当其他列的表头被拖拽移出冻结列位置时,冻结列数量减少。 + * "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 + */ + frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount'; cachedRecordsRowHeightMap: NumberMap; //存储每一条记录对应行的行高,只有当设置为自动换行随内容撑开才会起作用 // headerRowHeightsMap: NumberMap; //目前是用来存储了表头各行的高度,从headerRowHeight计算而来,headerRowHeight可以设置为数组的形式 - _rowHeightsMap: NumberMap; //存储数据条目每行高度 + _rowHeightsMap: NumberRangeMap; //存储数据条目每行高度 _colWidthsMap: NumberMap; //存储各列的宽度 _colContentWidthsMap: NumberMap; //存储各列的内容宽度 _colWidthsLimit: { @@ -199,7 +205,7 @@ export interface IBaseTableProtected { // // 开启图表异步渲染 每批次渐进渲染图表个数 // renderChartAsyncBatchCount?: number; - stick: { changedCells: StickCell[] }; + stick: { changedCells: Map }; customMergeCell?: CustomMergeCell; /** @@ -252,6 +258,7 @@ export interface BaseTableConstructorOptions { columnResizeMode?: 'all' | 'none' | 'header' | 'body'; /** 控制拖拽表头移动位置顺序开关 */ dragHeaderMode?: 'all' | 'none' | 'column' | 'row'; + /** * 是否显示固定列图钉 基本表格生效 */ @@ -442,7 +449,8 @@ export interface BaseTableAPI { isReleased: boolean; - rowHeightsMap: NumberMap; + // rowHeightsMap: NumberMap; + rowHeightsMap: NumberRangeMap; colWidthsMap: NumberMap; on: ( diff --git a/packages/vtable/src/ts-types/component/axis.ts b/packages/vtable/src/ts-types/component/axis.ts index 350ac5acb..c9c87e6a3 100644 --- a/packages/vtable/src/ts-types/component/axis.ts +++ b/packages/vtable/src/ts-types/component/axis.ts @@ -1,21 +1,21 @@ -import type { ICartesianAxisSpec } from '@visactor/vchart'; +// import type { ICartesianAxisSpec } from '@visactor/vchart'; -export type ICellAxisOption = Omit & - ( - | { - type: 'band'; - domain: (number | string)[]; - __vtableChartTheme?: any; - } - | { - type: 'linear' | 'time'; - range: { - min: number; - max: number; - }; - __ticksForVTable?: number[]; - __vtableChartTheme?: any; - } - ); - -export type ITableAxisOption = ICartesianAxisSpec; +// export type ICellAxisOption = Omit & +// ( +// | { +// type: 'band'; +// domain: (number | string)[]; +// __vtableChartTheme?: any; +// } +// | { +// type: 'linear' | 'time'; +// range: { +// min: number; +// max: number; +// }; +// __ticksForVTable?: number[]; +// __vtableChartTheme?: any; +// } +// ); +export type ICellAxisOption = any; +export type ITableAxisOption = ICellAxisOption; diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 270995db3..6c2aa5a80 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -170,6 +170,12 @@ export interface ListTableConstructorOptions extends BaseTableConstructorOptions editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); /** 编辑触发时机 双击事件 单击事件 api手动开启编辑。默认为双击'doubleclick' */ editCellTrigger?: 'doubleclick' | 'click' | 'api'; + /** 拖拽表头移动位置 针对冻结部分的规则 默认为fixedFrozenCount + * "disabled"(禁止调整冻结列位置):不允许其他列的表头移入冻结列,也不允许冻结列移出,冻结列保持不变。 + * "adjustFrozenCount"(根据交互结果调整冻结数量):允许其他列的表头移入冻结列,及冻结列移出,并根据拖拽的动作调整冻结列的数量。当其他列的表头被拖拽进入冻结列位置时,冻结列数量增加;当其他列的表头被拖拽移出冻结列位置时,冻结列数量减少。 + * "fixedFrozenCount"(可调整冻结列,并维持冻结数量不变):允许自由拖拽其他列的表头移入或移出冻结列位置,同时保持冻结列的数量不变。 + */ + frozenColDragHeaderMode?: 'disabled' | 'adjustFrozenCount' | 'fixedFrozenCount'; } export interface ListTableAPI extends BaseTableAPI {