diff --git a/common/changes/@visactor/vtable/fix-fix-switch-direction_2025-04-02-09-23.json b/common/changes/@visactor/vtable/chore-release-minor_2025-04-17-07-31.json similarity index 57% rename from common/changes/@visactor/vtable/fix-fix-switch-direction_2025-04-02-09-23.json rename to common/changes/@visactor/vtable/chore-release-minor_2025-04-17-07-31.json index 629f20f3ce..425c4841c6 100644 --- a/common/changes/@visactor/vtable/fix-fix-switch-direction_2025-04-02-09-23.json +++ b/common/changes/@visactor/vtable/chore-release-minor_2025-04-17-07-31.json @@ -2,8 +2,8 @@ "changes": [ { "packageName": "@visactor/vtable", - "comment": "fix: fix switch default direction #3667", - "type": "minor" + "comment": "chore: release 1.18.0", + "type": "none" } ], "packageName": "@visactor/vtable" diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 7f3d48cab2..f92b8ea905 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -1 +1 @@ -[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"1.17.6","mainProject":"@visactor/vtable","nextBump":"patch"}] +[{"definitionName":"lockStepVersion","policyName":"vtableMain","version":"1.18.0","mainProject":"@visactor/vtable","nextBump":"minor"}] diff --git a/docs/assets/changelog/en/release.md b/docs/assets/changelog/en/release.md index 71d91e1a83..ce12ec6dfc 100644 --- a/docs/assets/changelog/en/release.md +++ b/docs/assets/changelog/en/release.md @@ -1,23 +1,51 @@ +# v1.18.0 + v1.17.7 same content + +2025-04-17 + +**💥 Breaking change** + +- **@visactor/vtable**: fix switch default direction [#3667](https://github.com/VisActor/VTable/issues/3667) +- **@visactor/vtable-editors**: fix input editor default style + +**🆕 New feature** + +- **@visactor/vtable**: add onBeforeCacheChartImage event +- **@visactor/vtable**: support customConfig disableBuildInChartActive +- **@visactor/vtable**: add dynamicUpdateSelectionSize config in theme.selectionStyle + +**🐛 Bug fix** + +- **@visactor/vtable**: fix table size in getCellsRect() [#3681](https://github.com/VisActor/VTable/issues/3681) +- **@visactor/vtable**: correct column index calculation when rowSeriesNumber is configured +- **@visactor/vtable**: fix image flash problem [#3588](https://github.com/VisActor/VTable/issues/3588) +- **@visactor/vtable**: fix row/column update problem in text-stick [#3744](https://github.com/VisActor/VTable/issues/3744) +- **@visactor/vtable**: fix switch default direction [#3667](https://github.com/VisActor/VTable/issues/3667) + + + +[more detail about v1.17.7](https://github.com/VisActor/VTable/releases/tag/v1.17.7) + # v1.17.6 2025-04-10 **🆕 New feature** - -- **@visactor/vtable**: listTable added tiggerEvent parameter to changeCellValue -- **@visactor/vtable**: list table header support hierarchy + +- **@visactor/vtable**: listTable added tiggerEvent parameter to changeCellValue +- **@visactor/vtable**: list table header support hierarchy **🐛 Bug fix** - -- **@visactor/vtable**: when move tree node position code occor error [#3645](https://github.com/VisActor/VTable/issues/3645) [#3706](https://github.com/VisActor/VTable/issues/3706) -- **@visactor/vtable**: frame border set array render bottom line position error [#3684](https://github.com/VisActor/VTable/issues/3684) -- **@visactor/vtable**: mobile touch event resize column width [#3693](https://github.com/VisActor/VTable/issues/3693) -- **@visactor/vtable**: when set frozen disableDragSelect not work [#3702](https://github.com/VisActor/VTable/issues/3702) -- **@visactor/vtable**: fix flex layout update in react-custom-layout component [#3696](https://github.com/VisActor/VTable/issues/3696) -- **@visactor/vtable**: updateTaskRecord api [#3639](https://github.com/VisActor/VTable/issues/3639) -- **@visactor/vtable**: repeat call computeColsWidth adaptive mode result error - + +- **@visactor/vtable**: when move tree node position code occor error [#3645](https://github.com/VisActor/VTable/issues/3645) [#3706](https://github.com/VisActor/VTable/issues/3706) +- **@visactor/vtable**: frame border set array render bottom line position error [#3684](https://github.com/VisActor/VTable/issues/3684) +- **@visactor/vtable**: mobile touch event resize column width [#3693](https://github.com/VisActor/VTable/issues/3693) +- **@visactor/vtable**: when set frozen disableDragSelect not work [#3702](https://github.com/VisActor/VTable/issues/3702) +- **@visactor/vtable**: fix flex layout update in react-custom-layout component [#3696](https://github.com/VisActor/VTable/issues/3696) +- **@visactor/vtable**: updateTaskRecord api [#3639](https://github.com/VisActor/VTable/issues/3639) +- **@visactor/vtable**: repeat call computeColsWidth adaptive mode result error + [more detail about v1.17.6](https://github.com/VisActor/VTable/releases/tag/v1.17.6) @@ -28,23 +56,23 @@ **🆕 New feature** - -- **@visactor/vtable**: cell support marked function [#3583](https://github.com/VisActor/VTable/issues/3583) -- **@visactor/vtable**: refactor pivotTable corner with no columns or rows case [#3653](https://github.com/VisActor/VTable/issues/3653) + +- **@visactor/vtable**: cell support marked function [#3583](https://github.com/VisActor/VTable/issues/3583) +- **@visactor/vtable**: refactor pivotTable corner with no columns or rows case [#3653](https://github.com/VisActor/VTable/issues/3653) **🐛 Bug fix** - -- **@visactor/vtable**: gantt scale set quarter parser problem [#3612](https://github.com/VisActor/VTable/issues/3612) -- **@visactor/vtable**: gantt overscrollBehavior none work [#3638](https://github.com/VisActor/VTable/issues/3638) -- **@visactor/vtable**: gantt chart updateRecords error when table is tree mode [#3639](https://github.com/VisActor/VTable/issues/3639) -- **@visactor/vtable**: rowHeight error when set adaptive heightMode [#3640](https://github.com/VisActor/VTable/issues/3640) -- **@visactor/vtable**: when set renderChartAsync setRecords api render error [#3661](https://github.com/VisActor/VTable/issues/3661) -- **@visactor/vtable**: fix merge cell checkbox state update [#3668](https://github.com/VisActor/VTable/issues/3668) + +- **@visactor/vtable**: gantt scale set quarter parser problem [#3612](https://github.com/VisActor/VTable/issues/3612) +- **@visactor/vtable**: gantt overscrollBehavior none work [#3638](https://github.com/VisActor/VTable/issues/3638) +- **@visactor/vtable**: gantt chart updateRecords error when table is tree mode [#3639](https://github.com/VisActor/VTable/issues/3639) +- **@visactor/vtable**: rowHeight error when set adaptive heightMode [#3640](https://github.com/VisActor/VTable/issues/3640) +- **@visactor/vtable**: when set renderChartAsync setRecords api render error [#3661](https://github.com/VisActor/VTable/issues/3661) +- **@visactor/vtable**: fix merge cell checkbox state update [#3668](https://github.com/VisActor/VTable/issues/3668) **🔨 Refactor** - -- **@visactor/vtable**: fillHandle function [#3582](https://github.com/VisActor/VTable/issues/3582) - + +- **@visactor/vtable**: fillHandle function [#3582](https://github.com/VisActor/VTable/issues/3582) + [more detail about v1.17.5](https://github.com/VisActor/VTable/releases/tag/v1.17.5) @@ -55,15 +83,15 @@ **🆕 New feature** - -- **@visactor/vtable**: add barMarkInBar style config in progressbar [#3616](https://github.com/VisActor/VTable/issues/3616) + +- **@visactor/vtable**: add barMarkInBar style config in progressbar [#3616](https://github.com/VisActor/VTable/issues/3616) **🐛 Bug fix** - -- **@visactor/vtable**: fix button style problem [#3614](https://github.com/VisActor/VTable/issues/3614) -- **@visactor/vtable**: fix checkbox state order update [#3606](https://github.com/VisActor/VTable/issues/3606) -- **@visactor/vtable**: add isCustom tag for merge cell range [#3504](https://github.com/VisActor/VTable/issues/3504) -- **@visactor/vtable**: fix tree checkbox state update problem + +- **@visactor/vtable**: fix button style problem [#3614](https://github.com/VisActor/VTable/issues/3614) +- **@visactor/vtable**: fix checkbox state order update [#3606](https://github.com/VisActor/VTable/issues/3606) +- **@visactor/vtable**: add isCustom tag for merge cell range [#3504](https://github.com/VisActor/VTable/issues/3504) +- **@visactor/vtable**: fix tree checkbox state update problem - **@visactor/vtable**: disable group title editor [more detail about v1.17.4](https://github.com/VisActor/VTable/releases/tag/v1.17.4) @@ -74,21 +102,21 @@ **🆕 New feature** - -- **@visactor/vtable**: rowSeriesNumber support cell type radio [#3558](https://github.com/VisActor/VTable/issues/3558) -- **@visactor/vtable**: add custom reactAttributePlugin in react-vtable -- **@visactor/vtable**: add maintainedColumnCount config + +- **@visactor/vtable**: rowSeriesNumber support cell type radio [#3558](https://github.com/VisActor/VTable/issues/3558) +- **@visactor/vtable**: add custom reactAttributePlugin in react-vtable +- **@visactor/vtable**: add maintainedColumnCount config **🐛 Bug fix** - -- **@visactor/vtable**: selection mergeCell extend range [#3529](https://github.com/VisActor/VTable/issues/3529) -- **@visactor/vtable**: set cellInnerBorder false frame border render error [#3574](https://github.com/VisActor/VTable/issues/3574) -- **@visactor/vtable**: fix cell border in cell with corner-radius -- **@visactor/vtable**: fix axis label autosize computation -- **@visactor/vtable**: fix small window size frozen column count -- **@visactor/vtable**: columnWidthConfig match dimension error -- **@visactor/vtable**: fix react component update [#3474](https://github.com/VisActor/VTable/issues/3474) -- **@visactor/vtable**: fix right button select problem + +- **@visactor/vtable**: selection mergeCell extend range [#3529](https://github.com/VisActor/VTable/issues/3529) +- **@visactor/vtable**: set cellInnerBorder false frame border render error [#3574](https://github.com/VisActor/VTable/issues/3574) +- **@visactor/vtable**: fix cell border in cell with corner-radius +- **@visactor/vtable**: fix axis label autosize computation +- **@visactor/vtable**: fix small window size frozen column count +- **@visactor/vtable**: columnWidthConfig match dimension error +- **@visactor/vtable**: fix react component update [#3474](https://github.com/VisActor/VTable/issues/3474) +- **@visactor/vtable**: fix right button select problem - **@visactor/vtable**: fix row update range [#3468](https://github.com/VisActor/VTable/issues/3468) [more detail about v1.17.3](https://github.com/VisActor/VTable/releases/tag/v1.17.3) diff --git a/docs/assets/changelog/zh/release.md b/docs/assets/changelog/zh/release.md index 0b5da6c5eb..002700d3fb 100644 --- a/docs/assets/changelog/zh/release.md +++ b/docs/assets/changelog/zh/release.md @@ -1,3 +1,30 @@ +# v1.18.0 + v1.17.7 版本相同内容 + +2025-04-17 + +**💥 Breaking change** + +- **@visactor/vtable**: 修复 switch 默认方向问题 [#3667](https://github.com/VisActor/VTable/issues/3667) +- **@visactor/vtable-editors**: 修改了input编辑器中的默认样式 + +**🆕 新增功能** +- **@visactor/vtable-plugins**: 新增 行列新增 行列序号 excel键盘对齐等插件 +- **@visactor/vtable**: 新增 onBeforeCacheChartImage 事件 +- **@visactor/vtable**: 支持 customConfig disableBuildInChartActive 配置 +- **@visactor/vtable**: 在 theme.selectionStyle 中新增 dynamicUpdateSelectionSize 配置 + +**🐛 功能修复** + +- **@visactor/vtable**: 修复 getCellsRect() 中表格大小问题 [#3681](https://github.com/VisActor/VTable/issues/3681) +- **@visactor/vtable**: 修复配置 rowSeriesNumber 时列索引计算错误问题 +- **@visactor/vtable**: 修复图片闪烁问题 [#3588](https://github.com/VisActor/VTable/issues/3588) +- **@visactor/vtable**: 修复 text-stick 中行/列更新问题 [#3744](https://github.com/VisActor/VTable/issues/3744) + + + +[更多详情请查看 v1.17.7](https://github.com/VisActor/VTable/releases/tag/v1.17.7) + # v1.17.6 2025-04-10 diff --git a/docs/assets/demo/en/animation/carousel-animation.md b/docs/assets/demo/en/animation/carousel-animation.md index 97cf86297b..06c9bbecb6 100644 --- a/docs/assets/demo/en/animation/carousel-animation.md +++ b/docs/assets/demo/en/animation/carousel-animation.md @@ -1,26 +1,25 @@ --- category: examples group: Animation -title: carousel animation +title: Carousel Animation cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/carousel-animation.gif link: animation/carousel_animation --- # Carousel Animation -Carousel animation in VTable +Table carousel animation display -## Key configuration +## Key Configuration -- `CarouselAnimationPlugin` carousel animation plugin - - `rowCount` scroll row count in a carousel animation - - `colCount` scroll column count in a carousel animation - - `animationDuration` The duration of a single carousel animation, in milliseconds - - `animationDelay` The delay of a single carousel animation, in milliseconds - - `animationEasing` The easing function of a single carousel animation - - `replaceScrollAction` Whether to replace the scroll action, if true, the scroll action will be replaced by the carousel animation +- `TableCarouselAnimationPlugin` Carousel animation plugin + - `rowCount` Number of rows scrolled in one animation + - `colCount` Number of columns scrolled in one animation + - `animationDuration` Duration of a single scroll animation + - `animationDelay` Time interval between animations + - `animationEasing` Animation easing function -## Code demonstration +## Code demo ```javascript livedemo template=vtable // use this for project @@ -31,6 +30,10 @@ let tableInstance; fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_data100.json') .then(res => res.json()) .then(data => { + + const animationPlugin = new VTablePlugins.TableCarouselAnimationPlugin( { + rowCount: 2, + }); const columns = [ { field: 'Category', @@ -92,16 +95,12 @@ fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American const option = { records: data.slice(0, 20), columns, - widthMode: 'standard' + widthMode: 'standard', + plugins: [animationPlugin] }; tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window['tableInstance'] = tableInstance; - const ca = new VTablePlugins.CarouselAnimationPlugin(tableInstance, { - rowCount: 2, - replaceScrollAction: true - }); - - ca.play(); + }); ``` diff --git a/docs/assets/demo/en/interaction/head-highlight.md b/docs/assets/demo/en/interaction/head-highlight.md index 627d2a8328..22a5a3bbee 100644 --- a/docs/assets/demo/en/interaction/head-highlight.md +++ b/docs/assets/demo/en/interaction/head-highlight.md @@ -12,7 +12,7 @@ Highlight the header when selecting the cell. ## Key Configurations -- `HeaderHighlightPlugin` highlight plugin +- `HighlightHeaderWhenSelectCellPlugin` highlight plugin - `columnHighlight` whether highlight the column - `rowHighlight` whether highlight the row - `colHighlightBGColor` the background color of the column highlight @@ -90,14 +90,15 @@ const columns = [ width: 100 } ]; - +const highlightPlugin = new VTablePlugins.HighlightHeaderWhenSelectCellPlugin(); const option = { records, columns, - rowSeriesNumber: {} + rowSeriesNumber: {}, + plugins: [highlightPlugin] }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window['tableInstance'] = tableInstance; -const highlightPlugin = new VTablePlugins.HeaderHighlightPlugin(tableInstance, {}); + ``` diff --git a/docs/assets/demo/en/interaction/invert-highlight.md b/docs/assets/demo/en/interaction/invert-highlight.md index b1c1d10ea5..f7ab935d30 100644 --- a/docs/assets/demo/en/interaction/invert-highlight.md +++ b/docs/assets/demo/en/interaction/invert-highlight.md @@ -12,7 +12,7 @@ Show the highlight effect when set highlight range. ## Key Configurations -- `InvertHighlightPlugin` invert highlight plugin +- `FocusHighlightPlugin` invert highlight plugin - `fill` invert highlight background color - `opacity` invert highlight opacity - `setInvertHighlightRange` set highlight range @@ -87,25 +87,16 @@ const columns = [ width: 100 } ]; - +const highlightPlugin = new VTablePlugins.FocusHighlightPlugin(); const option = { records, columns, - theme: VTable.themes.DARK + theme: VTable.themes.DARK, + plugins: [highlightPlugin] }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window['tableInstance'] = tableInstance; -const highlightPlugin = new VTablePlugins.InvertHighlightPlugin(tableInstance, {}); -highlightPlugin.setInvertHighlightRange({ - start: { - col: 0, - row: 6 - }, - end: { - col: 5, - row: 6 - } -}); + ``` diff --git a/docs/assets/demo/en/plugin/excel-online-editing.md b/docs/assets/demo/en/plugin/excel-online-editing.md new file mode 100644 index 0000000000..7ea44127c7 --- /dev/null +++ b/docs/assets/demo/en/plugin/excel-online-editing.md @@ -0,0 +1,114 @@ +--- +category: examples +group: plugin +title: Excel-like Online Editing +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/excel-online-editing.gif +link: plugin/usage +option: plugins +--- + +# Excel-like Online Editing + +Based on VTable's plugin mechanism, this example implements Excel-like online editing functionality. + +In this example, we use the following plugins in combination: +- `AddRowColumnPlugin`: Add rows and columns +- `ColumnSeriesPlugin`: Column series plugin +- `RowSeriesPlugin`: Row series plugin +- `HighlightHeaderWhenSelectCellPlugin`: Highlight selected cells +- `ExcelEditCellKeyboardPlugin`: Excel-style cell editing keyboard plugin + +It's important to note that there are dependencies between plugins. For example, in the `AddRowColumnPlugin`, we call the `columnSeries.resetColumnCount` method to reset the column count, ensuring that after adding columns, the count remains consistent with the column count in the `ColumnSeriesPlugin`. + +## Key Configuration + +- `editCellTrigger`: Cell editing trigger method +- `editor`: Editor type +- `plugins`: Plugin list + + +## Code Demo + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +// 注册编辑器 +const input_editor = new VTable_editors.InputEditor(); +VTable.register.editor('input', input_editor); + +// 注册插件 + const addRowColumn = new VTablePlugins.AddRowColumnPlugin({ + addColumnCallback: col => { + // 新增列时,重置列数 + columnSeries.resetColumnCount(columnSeries.pluginOptions.columnCount + 1); + // 将table实例中的数据源records每一个数组中新增一个空字符串,对应新增的列 + const newRecords = tableInstance.records.map(record => { + if (Array.isArray(record)) { + record.splice(col - 1, 0, ''); + } + return record; + }); + tableInstance.setRecords(newRecords); + }, + addRowCallback: row => { + // 新增行时,填充空行数据 + tableInstance.addRecord([], row - tableInstance.columnHeaderLevelCount); + } + }); + + const columnSeries = new VTablePlugins.ColumnSeriesPlugin({ + columnCount: 26 + }); + const rowSeries = new VTablePlugins.RowSeriesPlugin({ + rowCount: 100, + //records数据以外 填充空行数据 + fillRowRecord: (index) => { + return []; + }, + rowSeriesNumber: { + width: 'auto' + } + }); + const highlightPlugin = new VTablePlugins.HighlightHeaderWhenSelectCellPlugin({ + colHighlight: true, + rowHighlight: true + }); + const excelEditCellKeyboardPlugin = new VTablePlugins.ExcelEditCellKeyboardPlugin(); + const option = { + // 二维数组的数据 和excel的行列一致 + records: [ + ['姓名', '年龄', '地址'], + ['张三', 18, '北京'], + ['李四', 20, '上海'], + ['王五', 22, '广州'], + ['赵六', 24, '深圳'], + ['孙七', 26, '成都'] + ], + + padding: 30, + editor: 'input', + editCellTrigger: ['api', 'keydown', 'doubleclick'],// 编辑单元格触发方式 + select: { + cornerHeaderSelectMode: 'body', + headerSelectMode: 'body' + }, + theme: VTable.themes.DEFAULT.extends({ + defaultStyle: { + textAlign: 'left', + padding: [2, 6, 2, 6] + }, + headerStyle: { + textAlign: 'center' + } + }), + defaultRowHeight: 30, + plugins: [addRowColumn, columnSeries, rowSeries, highlightPlugin, excelEditCellKeyboardPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; + +``` \ No newline at end of file diff --git a/docs/assets/demo/menu.json b/docs/assets/demo/menu.json index 465824021a..f1944b1de4 100644 --- a/docs/assets/demo/menu.json +++ b/docs/assets/demo/menu.json @@ -1482,6 +1482,22 @@ } ] }, + { + "path": "plugin", + "title": { + "zh": "插件库", + "en": "plugin library" + }, + "children": [ + { + "path": "excel-online-editing", + "title": { + "zh": "类Excel在线编辑", + "en": "Excel online editing" + } + } + ] + }, { "path": "export", "title": { diff --git a/docs/assets/demo/zh/animation/carousel-animation.md b/docs/assets/demo/zh/animation/carousel-animation.md index 5df873ba27..5d10703650 100644 --- a/docs/assets/demo/zh/animation/carousel-animation.md +++ b/docs/assets/demo/zh/animation/carousel-animation.md @@ -12,13 +12,12 @@ link: animation/carousel_animation ## 关键配置 -- `CarouselAnimationPlugin` 轮播动画插件 +- `TableCarouselAnimationPlugin` 轮播动画插件 - `rowCount` 一次动画滚动的行数 - `colCount` 一次动画滚动的列数 - `animationDuration` 一次滚动动画的时间 - `animationDelay` 动画间隔时间 - `animationEasing` 动画缓动函数 - - `replaceScrollAction` 是否替换滚动行为,如果为 true ,每次滚动操作会移动对于的行数/列数 ## 代码演示 @@ -31,6 +30,10 @@ let tableInstance; fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_data100.json') .then(res => res.json()) .then(data => { + + const animationPlugin = new VTablePlugins.TableCarouselAnimationPlugin( { + rowCount: 2, + }); const columns = [ { field: 'Category', @@ -92,16 +95,12 @@ fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American const option = { records: data.slice(0, 20), columns, - widthMode: 'standard' + widthMode: 'standard', + plugins: [animationPlugin] }; tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window['tableInstance'] = tableInstance; - const ca = new VTablePlugins.CarouselAnimationPlugin(tableInstance, { - rowCount: 2, - replaceScrollAction: true - }); - - ca.play(); + }); ``` diff --git a/docs/assets/demo/zh/interaction/head-highlight.md b/docs/assets/demo/zh/interaction/head-highlight.md index 347b00b9e7..58afa26726 100644 --- a/docs/assets/demo/zh/interaction/head-highlight.md +++ b/docs/assets/demo/zh/interaction/head-highlight.md @@ -12,7 +12,7 @@ link: plugin/header-highlight ## 关键配置 -- `HeaderHighlightPlugin` 高亮表头插件 +- `HighlightHeaderWhenSelectCellPlugin` 高亮表头插件 - `columnHighlight` 是否高亮列头 - `rowHighlight` 是否高亮行头 - `colHighlightBGColor` 列头高亮背景色 @@ -90,14 +90,13 @@ const columns = [ width: 100 } ]; - +const highlightPlugin = new VTablePlugins.HighlightHeaderWhenSelectCellPlugin(); const option = { records, columns, - rowSeriesNumber: {} + rowSeriesNumber: {}, + plugins: [highlightPlugin] }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window['tableInstance'] = tableInstance; - -const highlightPlugin = new VTablePlugins.HeaderHighlightPlugin(tableInstance, {}); ``` diff --git a/docs/assets/demo/zh/interaction/invert-highlight.md b/docs/assets/demo/zh/interaction/invert-highlight.md index 3b1dae99a7..1720566ed8 100644 --- a/docs/assets/demo/zh/interaction/invert-highlight.md +++ b/docs/assets/demo/zh/interaction/invert-highlight.md @@ -12,7 +12,7 @@ link: plugin/invert-highlight ## 关键配置 -- `InvertHighlightPlugin` 反选高亮插件 +- `FocusHighlightPlugin` 反选高亮插件 - `fill` 反选高亮背景色 - `opacity` 反选高亮透明度 - `setInvertHighlightRange` 设置反选高亮范围 @@ -87,25 +87,16 @@ const columns = [ width: 100 } ]; - +const highlightPlugin = new VTablePlugins.FocusHighlightPlugin(); const option = { records, columns, - theme: VTable.themes.DARK + theme: VTable.themes.DARK, + plugins: [highlightPlugin] }; const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID), option); window['tableInstance'] = tableInstance; -const highlightPlugin = new VTablePlugins.InvertHighlightPlugin(tableInstance, {}); -highlightPlugin.setInvertHighlightRange({ - start: { - col: 0, - row: 6 - }, - end: { - col: 5, - row: 6 - } -}); + ``` diff --git a/docs/assets/demo/zh/plugin/excel-online-editing.md b/docs/assets/demo/zh/plugin/excel-online-editing.md new file mode 100644 index 0000000000..38e6a27c97 --- /dev/null +++ b/docs/assets/demo/zh/plugin/excel-online-editing.md @@ -0,0 +1,114 @@ +--- +category: examples +group: plugin +title: 类Excel在线编辑 +cover: https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/preview/excel-online-editing.gif +link: plugin/usage +option: plugins +--- + +# 类Excel在线编辑 + +基于VTable的插件机制,实现类Excel在线编辑功能。 + +在本示例中我们组合运用了以下插件: +- `AddRowColumnPlugin`:添加行和列; +- `ColumnSeriesPlugin`:列系列插件; +- `RowSeriesPlugin`:行系列插件; +- `HighlightHeaderWhenSelectCellPlugin`:高亮选中单元格; +- `ExcelEditCellKeyboardPlugin`:Excel编辑单元格键盘插件; + +需要注意的是插件之间存在一定的依赖关系,如在新增行列插件`AddRowColumnPlugin`中,我们调用了`columnSeries.resetColumnCount`方法,重置列数,以确保新增列后,列数与列系列插件`ColumnSeriesPlugin`中的列数一致。 + +## 关键配置 + +- `editCellTrigger`: 编辑单元格触发方式 +- `editor`: 编辑器类型 +- `plugins`: 插件列表 + + +## 代码演示 + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +// 注册编辑器 +const input_editor = new VTable_editors.InputEditor(); +VTable.register.editor('input', input_editor); + +// 注册插件 + const addRowColumn = new VTablePlugins.AddRowColumnPlugin({ + addColumnCallback: col => { + // 新增列时,重置列数 + columnSeries.resetColumnCount(columnSeries.pluginOptions.columnCount + 1); + // 将table实例中的数据源records每一个数组中新增一个空字符串,对应新增的列 + const newRecords = tableInstance.records.map(record => { + if (Array.isArray(record)) { + record.splice(col - 1, 0, ''); + } + return record; + }); + tableInstance.setRecords(newRecords); + }, + addRowCallback: row => { + // 新增行时,填充空行数据 + tableInstance.addRecord([], row - tableInstance.columnHeaderLevelCount); + } + }); + + const columnSeries = new VTablePlugins.ColumnSeriesPlugin({ + columnCount: 26 + }); + const rowSeries = new VTablePlugins.RowSeriesPlugin({ + rowCount: 100, + //records数据以外 填充空行数据 + fillRowRecord: (index) => { + return []; + }, + rowSeriesNumber: { + width: 'auto' + } + }); + const highlightPlugin = new VTablePlugins.HighlightHeaderWhenSelectCellPlugin({ + colHighlight: true, + rowHighlight: true + }); + const excelEditCellKeyboardPlugin = new VTablePlugins.ExcelEditCellKeyboardPlugin(); + const option = { + // 二维数组的数据 和excel的行列一致 + records: [ + ['姓名', '年龄', '地址'], + ['张三', 18, '北京'], + ['李四', 20, '上海'], + ['王五', 22, '广州'], + ['赵六', 24, '深圳'], + ['孙七', 26, '成都'] + ], + + padding: 30, + editor: 'input', + editCellTrigger: ['api', 'keydown', 'doubleclick'],// 编辑单元格触发方式 + select: { + cornerHeaderSelectMode: 'body', + headerSelectMode: 'body' + }, + theme: VTable.themes.DEFAULT.extends({ + defaultStyle: { + textAlign: 'left', + padding: [2, 6, 2, 6] + }, + headerStyle: { + textAlign: 'center' + } + }), + defaultRowHeight: 30, + plugins: [addRowColumn, columnSeries, rowSeries, highlightPlugin, excelEditCellKeyboardPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; + +``` \ No newline at end of file diff --git a/docs/assets/guide/en/animation/carousel_animation.md b/docs/assets/guide/en/animation/carousel_animation.md index 796799d660..be3a225d3b 100644 --- a/docs/assets/guide/en/animation/carousel_animation.md +++ b/docs/assets/guide/en/animation/carousel_animation.md @@ -1,51 +1,2 @@ # Carousel Animation - -VTable provides carousel animation plugin, which can implement the carousel scrolling animation effect of the table. - -
- -
- -## Carousel Animation Configuration - -- `CarouselAnimationPlugin` carousel animation plugin, can configure the following parameters: - - `rowCount` scroll row count in a carousel animation - - `colCount` scroll column count in a carousel animation - - `animationDuration` The duration of a single carousel animation, in milliseconds - - `animationDelay` The delay of a single carousel animation, in milliseconds - - `animationEasing` The easing function of a single carousel animation - - `replaceScrollAction` Whether to replace the scroll action, if true, the scroll action will be replaced by the carousel animation - -```js -const carouselAnimationPlugin = new CarouselAnimationPlugin(tableInstance, { - rowCount: 2, - replaceScrollAction: true -}); - -carouselAnimationPlugin.play(); -``` - -For specific usage, please refer to [demo](../../demo/animation/carousel-animation) - -## Carousel Animation API - -### play - -Start carousel animation. -``` -carouselAnimationPlugin.play() -``` - -### pause - -Pause carousel animation. -``` -carouselAnimationPlugin.pause() -``` - -### reset - -Reset carousel animation. -``` -carouselAnimationPlugin.reset() -``` +Please jump to the link: [Table Carousel Animation](../plugin/table-carousel-animation) \ No newline at end of file diff --git a/docs/assets/guide/en/plugin/contribute.md b/docs/assets/guide/en/plugin/contribute.md new file mode 100644 index 0000000000..6b8ee79ab5 --- /dev/null +++ b/docs/assets/guide/en/plugin/contribute.md @@ -0,0 +1,96 @@ +# Contributing Plugins + +When businesses use VTable, they may need customized functionality, which can be implemented through plugins. Extracting common functionality into plugins avoids reinventing the wheel and makes it easier for other businesses to use these features. + +Sharing plugins can improve development efficiency and reduce maintenance costs! We encourage everyone to actively contribute plugins and help improve the VTable ecosystem! + +## Guidelines for Contributing Plugins + +1. Plugins must follow VTable's plugin specifications. +2. Plugins must include detailed documentation, including parameter descriptions, usage examples, etc. + +### Plugin Specifications +#### Interface Specifications + +Plugins need to implement the `VTable.plugins.IVTablePlugin` interface. + +```ts +// Plugin unified interface +export interface IVTablePlugin { + // Plugin unique identifier + id: string; + // Plugin runtime trigger + runTime: TableEvents[keyof TableEvents] | TableEvents[keyof TableEvents][]; + // Initialization method, called after VTable instance creation and before first render + run: (...args: any[]) => void; + // Update method, called when table data or configuration updates + update?: (table: BaseTableAPI, options?: any) => void; + // Destruction method, called before VTable instance is destroyed + release?: (table: BaseTableAPI) => void; +} +``` + +The `runTime` parameter specifies when the plugin will run, configuring it with event types from `TableEvents`. + +#### Component Lifecycle Process: + +
+ +
+ +Attached Mermaid sequence diagram code (for future updates, you can modify this code and update the image above): +```mermaid +sequenceDiagram + participant User + participant DOM + participant ListTable + participant EventManager + participant PluginManager + participant Plugin as IVTablePlugin + participant RenderManager + + %% Initialization + ListTable->>PluginManager: register plugins + PluginManager->>Plugin: store plugin instances + + %% User interaction flow + User->>DOM: interact (click, scroll, etc.) + DOM->>EventManager: dispatch browser event + EventManager->>PluginManager: notify(eventType, args) + PluginManager->>PluginManager: filter plugins by runTime + + loop For each matching plugin + PluginManager->>Plugin: run(eventArgs, runTime, tableAPI) + Plugin->>ListTable: read/modify table state + Plugin->>Plugin: process event logic + end + + Plugin->>RenderManager: requestRender() + RenderManager->>ListTable: render updates + ListTable->>DOM: update display + + %% Release + ListTable->>PluginManager: release + PluginManager->>Plugin: release() +``` + +From the above diagram, you can understand the runtime timing of plugins: +- The key role of `runTime` in plugins is to specify which VTable events they depend on. +- In the plugin's `run` method, you can access the table instance, configuration, and data; you should also handle the plugin's specific business logic in the `run` method. +- Remember to release resources in the plugin's `release` method to avoid memory leaks. + +### Plugin Documentation + +Plugins need to provide detailed documentation, including parameter descriptions, usage examples, etc. + +Documentation generally should include the following: +- Plugin name +- Plugin description +- Plugin parameter descriptions +- Plugin usage examples +- Plugin notes and considerations +- Plugin source code link + +Documentation should be placed in the `docs/assets/plugins` directory, with the filename `plugin-name.md`. + + diff --git a/docs/assets/guide/en/plugin/excel-keyboard-alignment.md b/docs/assets/guide/en/plugin/excel-keyboard-alignment.md new file mode 100644 index 0000000000..c69608537f --- /dev/null +++ b/docs/assets/guide/en/plugin/excel-keyboard-alignment.md @@ -0,0 +1,135 @@ +# Excel Edit Cell Keyboard Behavior Alignment Plugin + +`ExcelEditCellKeyboardPlugin` is a VTable extension component that aligns keyboard behavior in cell editing with Excel functionality. + +## Plugin Capabilities +Regarding keyboard response settings, VTable has the following two configuration entry points: + +| keyboard | Response | +| :--------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| enter | If in edit state, confirms editing completion;
If keyboardOptions.moveFocusCellOnEnter is true, pressing enter switches the selected cell to the cell below.
If keyboardOptions.editCellOnEnter is true, pressing enter will enter edit mode when a cell is selected. | +| tab | Requires keyboardOptions.moveFocusCellOnTab to be enabled.
Pressing tab switches the selected cell, if currently editing a cell, the next cell will also be in edit mode. | +| left | Direction key, switches the selected cell.
If keyboardOptions.moveEditCellOnArrowKeys is enabled, you can also switch the editing cell in edit mode | +| right | Same as above | +| top | Same as above | +| bottom | Same as above | +| ctrl+c | The keybinding is not exact, this copy matches the browser's shortcut.
Copies selected cell content, requires keyboardOptions.copySelected to be enabled | +| ctrl+v | The keybinding is not exact, paste shortcut matches the browser's shortcut.
Pastes content to cells, requires keyboardOptions.pasteValueToCell to be enabled, paste only works on cells configured with editor | +| ctrl+a | Select all, requires keyboardOptions.selectAllOnCtrlA to be enabled | +| shift | Hold shift and left mouse button to select cells in a continuous area | +| ctrl | Hold ctrl and left mouse button to select multiple areas | +| any key | Can listen to tableInstance.on('keydown',(args)=>{ }) | + +These settings can satisfy most editing table keyboard response requirements, but compared to Excel, VTable's keyboard behavior still has some differences, such as: + +- In VTable, when editing a cell, pressing arrow keys doesn't switch to the next cell, but moves the cursor within the editing cell. +- In VTable, when editing a cell, pressing enter doesn't confirm editing completion, but switches to the next cell. +- In VTable, when editing a cell, pressing tab doesn't switch to the next cell, but moves the cursor within the editing cell. +- In VTable, when editing a cell, pressing shift and left mouse button doesn't select cells in a continuous area. +- In VTable, when editing a cell, holding ctrl and left mouse button doesn't select multiple areas. + + +Combined with VTable's existing capabilities that partially meet the requirements, we developed the `ExcelEditCellKeyboardPlugin` plugin to align cell editing keyboard behavior with Excel functionality. + +## Plugin Usage Example + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + const excelEditCellKeyboardPlugin = new VTablePlugins.ExcelEditCellKeyboardPlugin(); + + const option = { + records: generatePersons(20), + columns:[ + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + editor: new VTable_editors.InputEditor(), + editCellTrigger: ['keydown'], + plugins: [excelEditCellKeyboardPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; + + +``` + +## Future Plugin Improvements + +Other keyboard behaviors that differ from Excel, such as: + +- Support for configuration option to respond to the delete key +- Support for configuration option to respond to ctrl+c and ctrl+v +- Support for configuration option to respond to shift and left mouse button +- Support for configuration option to respond to ctrl and left mouse button + +Whether each response behavior needs to be explicitly configured by users, such as providing configuration options: +```ts +const excelEditCellKeyboardPlugin = new ExcelEditCellKeyboardPlugin({ + enableDeleteKey: false +}); +const excelEditCellKeyboardPlugin = new ExcelEditCellKeyboardPlugin(excelEditCellKeyboardPlugin); + +``` + +We welcome your contributions to write more plugins! Let's build the VTable ecosystem together! \ No newline at end of file diff --git a/docs/assets/guide/en/plugin/focus-highlight.md b/docs/assets/guide/en/plugin/focus-highlight.md new file mode 100644 index 0000000000..d97780014a --- /dev/null +++ b/docs/assets/guide/en/plugin/focus-highlight.md @@ -0,0 +1,118 @@ +# Focus Highlight Plugin + +VTable provides a focus highlight plugin that supports highlighting specified areas. + +
+ +
+ +## Focus Highlight Plugin Configuration Options + +- `FocusHighlightPlugin` Focus Highlight Plugin, can be configured with the following parameters: + - `fill` Focus highlight background color + - `opacity` Focus highlight opacity + - `highlightRange` Initial focus highlight range + +``` +export interface FocusHighlightPluginOptions { + fill?: string; + opacity?: number; + highlightRange?: CellRange; +} +``` + +## Usage Example: + + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const highlightPlugin = new VTablePlugins.FocusHighlightPlugin({ + fill: '#000', + opacity: 0.5, + highlightRange: { + start: { + col: 4, + row: 4 + }, + end: { + col: 4, + row: 4 + } + } + }); + const option = { + records: generatePersons(20), + columns:[ + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + theme: VTable.themes.DARK, + plugins: [highlightPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; +``` + +For specific usage, refer to the [demo](../../demo/interaction/head-highlight) diff --git a/docs/assets/guide/en/plugin/header-highlight.md b/docs/assets/guide/en/plugin/header-highlight.md index 4e73715f38..0b3b1e4863 100644 --- a/docs/assets/guide/en/plugin/header-highlight.md +++ b/docs/assets/guide/en/plugin/header-highlight.md @@ -1,23 +1,106 @@ -# Header Highlight Plugin +# Highlight Header When Cell Selected Plugin -VTable provides Header Highlight plugin, which can highlight the corresponding header (row header and column header) after selecting a cell. +VTable provides a plugin to highlight the corresponding headers when a cell is selected, supporting highlighting of row and column headers.
-## Header Highlight Plugin Configuration +## Header Highlight Plugin Configuration Options -- `HeaderHighlightPlugin` Header Highlight, can configure the following parameters: - - `columnHighlight` whether highlight the column - - `rowHighlight` whether highlight the row - - `colHighlightBGColor` the background color of the column highlight - - `rowHighlightBGColor` the background color of the row highlight - - `colHighlightColor` the color of the column highlight - - `rowHighlightColor` the color of the row highlight +- `HighlightHeaderWhenSelectCellPlugin` Header highlight when cell selected plugin, can be configured with the following parameters: + - `columnHighlight` Whether to highlight column headers + - `rowHighlight` Whether to highlight row headers + - `colHighlightBGColor` Column header highlight background color + - `rowHighlightBGColor` Row header highlight background color + - `colHighlightColor` Column header highlight font color + - `rowHighlightColor` Row header highlight font color + +Plugin parameter types: +``` +interface IHighlightHeaderWhenSelectCellPluginOptions { + rowHighlight?: boolean; + colHighlight?: boolean; + colHighlightBGColor?: string; + colHighlightColor?: string; + rowHighlightBGColor?: string; + rowHighlightColor?: string; +} +``` + +## Usage Example: + + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const highlightPlugin = new VTablePlugins.HighlightHeaderWhenSelectCellPlugin({ + colHighlight: true, + rowHighlight: true + }); + const option = { + records: generatePersons(20), + rowSeriesNumber: {}, + columns:[ + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + }, + + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], -```js -const highlightPlugin = new HeaderHighlightPlugin(tableInstance, {}); + plugins: [highlightPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; ``` -For specific usage, please refer to [demo](../../demo/interaction/head-highlight) +For specific usage, refer to the [demo](../../demo/interaction/head-highlight) diff --git a/docs/assets/guide/en/plugin/invert-highlight.md b/docs/assets/guide/en/plugin/invert-highlight.md deleted file mode 100644 index 6b26c31a06..0000000000 --- a/docs/assets/guide/en/plugin/invert-highlight.md +++ /dev/null @@ -1,50 +0,0 @@ -# Invert Highlight Plugin - -VTable provides Invert Highlight plugin, which can highlight the specified area after deselection. - -
- -
- -## Invert Highlight Plugin Configuration - -- `InvertHighlightPlugin` Invert Highlight Plugin, can configure the following parameters: - - `fill` invert highlight background color - - `opacity` invert highlight opacity -- `setInvertHighlightRange` set highlight range - -```js -const highlightPlugin = new InvertHighlightPlugin(tableInstance, {}); - -highlightPlugin.setInvertHighlightRange({ - start: { - col: 0, - row: 6 - }, - end: { - col: 5, - row: 6 - } -}); -``` - -For specific usage, please refer to [demo](../../demo/interaction/head-highlight) - -## Invert Highlight Plugin API - -### setInvertHighlightRange - -Set highlight range. - -```ts -setInvertHighlightRange(range: { - start: { - col: number; - row: number; - }; - end: { - col: number; - row: number; - }; -}): void; -``` \ No newline at end of file diff --git a/docs/assets/guide/en/plugin/row-column-add.md b/docs/assets/guide/en/plugin/row-column-add.md new file mode 100644 index 0000000000..e61aac7129 --- /dev/null +++ b/docs/assets/guide/en/plugin/row-column-add.md @@ -0,0 +1,139 @@ +# Row and Column Addition Plugin + +## Introduction + +AddRowColumnPlugin is a plugin designed to extend VTable with dynamic row and column addition capabilities. + +This plugin monitors the `vTable` instance's `MOUSEENTER_CELL`, `MOUSELEAVE_CELL`, and `MOUSELEAVE_TABLE` events! + +When the mouse hovers over a table cell, dots and plus signs for adding rows and columns will be displayed; when the mouse leaves the table cell, these indicators will be hidden. + +## Plugin Configuration + +Configuration options for the row and column addition plugin: + +```ts +export interface AddRowColumnOptions { + /** + * Whether to enable column addition + */ + addColumnEnable?: boolean; + /** + * Whether to enable row addition + */ + addRowEnable?: boolean; + /** + * Callback function for adding a column + */ + addColumnCallback?: (col: number) => void; + /** + * Callback function for adding a row + */ + addRowCallback?: (row: number) => void; +} +``` + +## Plugin Example +Initialize the plugin object and add it to the vTable configuration's plugins. +``` +const addRowColumn = new AddRowColumnPlugin(); +const option = { + records, + columns, + padding: 30, + plugins: [addRowColumn] +}; +``` +To control the content of newly added row data and update data and column information after adding columns, you can use the configuration options provided by the plugin. When initializing the plugin object, provide hook functions for adding rows and columns, and set the values for new rows or column information in these functions. +```ts + const addRowColumn = new AddRowColumnPlugin({ + addColumnCallback: col => { + columns.splice(addColIndex, 0, { + field: ``, + title: `New Column ${col}`, + width: 100 + }); + this.table.updateColumns(columns); + const newRecords = tableInstance.records.map(record => { + if (Array.isArray(record)) { + record.splice(col - 1, 0, ''); + } + return record; + }); + tableInstance.setRecords(newRecords); + }, + addRowCallback: row => { + tableInstance.addRecord([], row - tableInstance.columnHeaderLevelCount); + } + }); +``` + +Runnable example: + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + const addRowColumn = new VTablePlugins.AddRowColumnPlugin(); +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const option = { + records: generatePersons(20), + rowSeriesNumber: {}, + columns:[ + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + }, + + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + + plugins: [addRowColumn] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; +``` diff --git a/docs/assets/guide/en/plugin/row-column-series.md b/docs/assets/guide/en/plugin/row-column-series.md new file mode 100644 index 0000000000..e9aec194c2 --- /dev/null +++ b/docs/assets/guide/en/plugin/row-column-series.md @@ -0,0 +1,140 @@ +# Row and Column Series Plugins + +## Introduction + +ColumnSeriesPlugin and RowSeriesPlugin are VTable extension components that automatically generate row and column numbers. + +
+ +
+ +## Plugin Parameters + +```typescript +export interface ColumnSeriesOptions { + columnCount: number; + generateColumnTitle?: (index: number) => string; // Custom column title generation function + generateColumnField?: (index: number) => string;// Custom column field name generation function + /** + * Whether to automatically extend columns + * @default true + */ + autoExtendColumn?: boolean; +} + +export interface RowSeriesOptions { + rowCount: number; + fillRowRecord?: (index: number) => any; // Custom data generation function for filling empty rows + rowSeriesNumber?: VTable.TYPES.IRowSeriesNumber; + /** + * Whether to automatically extend rows + * @default true + */ + autoExtendRow?: boolean; +} +``` + +In the column series plugin configuration, in addition to setting the number of columns to generate, you can also configure the field names and titles needed for VTable column configuration. + +In the row series plugin configuration, `rowCount` can be set to configure the number of rows to generate, `rowSeriesNumber` can be used to configure row numbering, which takes precedence over the options.rowSeriesNumber in the VTable instance initialization. `fillRowRecord` can be configured to generate row data; if not configured, it defaults to returning an empty object `{}`. + +## Basic Usage + +```typescript +import * as VTable from '@visactor/vtable'; +import { ColumnSeriesPlugin } from '@visactor/vtable-plugins'; + +// Create a ColumnSeries plugin instance +const columnSeries = new ColumnSeriesPlugin({ + columnCount: 100 // Set the number of columns +}); + +// Create a RowSeries plugin instance +const rowSeries = new RowSeriesPlugin({ + rowCount: 100 // Set the number of rows +}); + +// Use the plugins in VTable options +const option: VTable.ListTableConstructorOptions = { + container: document.getElementById('container'), + records: data, + plugins: [columnSeries, rowSeries], + // Other VTable configurations... +}; + +// Create the table instance +const tableInstance = new VTable.ListTable(option); +``` + +## Advanced Usage + +### Custom Column Titles and Field Names + +```typescript +const columnSeries = new ColumnSeriesPlugin({ + columnCount: 100, + // Custom column title generation + columnTitleGenerator: (index) => `Custom Title ${index}`, + // Custom field name generation + columnFieldGenerator: (index) => `field_${index}` +}); +``` + +### Custom Row Data + +```typescript +const rowSeries = new RowSeriesPlugin({ + rowCount: 100, + // Custom row data generation returning an empty array + fillRowRecord: (index) => ([]) +}); + +// or +const rowSeries = new RowSeriesPlugin({ + rowCount: 100, + // Custom row data generation + fillRowRecord: (index) => (['Name', 'Age', 'Address']) +}); +``` + +## Specific Example + +```javascript livedemo template=vtable + +let tableInstance; +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +// 创建 ColumnSeries 插件实例 +const columnSeries = new VTablePlugins.ColumnSeriesPlugin({ + columnCount: 100, + // 自定义列标题生成 + // generateColumnTitle(index) + // { + // return `自定义标题 ${index}` + // }, + // 自定义字段名生成 + generateColumnField: (index) => `field_${index}` +}); + +// 创建 RowSeries 插件实例 +const rowSeries = new VTablePlugins.RowSeriesPlugin({ + rowCount: 100, + // 自定义行数据生成 + fillRowRecord: (index) => ([]) +}); + +// 在 VTable 选项中使用插件 +const option = { + records: [], + plugins: [columnSeries, rowSeries], + // 其他 VTable 配置... +}; + +// 创建表格实例 +tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); +window.tableInstance = tableInstance; +``` diff --git a/docs/assets/guide/en/plugin/table-carousel-animation.md b/docs/assets/guide/en/plugin/table-carousel-animation.md new file mode 100644 index 0000000000..692a09ff65 --- /dev/null +++ b/docs/assets/guide/en/plugin/table-carousel-animation.md @@ -0,0 +1,132 @@ +# Table Carousel Animation Plugin + +VTable provides a table carousel animation plugin that supports row or column carousel animations for tables. + +Effect shown below: +
+ +
+ +## Usage Example + +```ts +const tableCarouselAnimationPlugin = new TableCarouselAnimationPlugin({ + rowCount: 10, + colCount: 10, + animationDuration: 1000, + animationDelay: 0, + animationEasing: 'linear', + autoPlay: true, + autoPlayDelay: 1000, +}); + +const option: VTable.ListTableConstructorOptions = { + records, + columns, + plugins: [tableCarouselAnimationPlugin], +}; + +``` +If you don't want the table to start playing immediately after initialization, you can set `autoPlay` to `false` and then call the `play` method manually to start playing. + +```ts +tableCarouselAnimationPlugin.play(); +``` + + + +## Plugin Parameter Description + +The plugin provides customization options, with the following parameters: + +| Parameter | Type | Description | +| --- | --- | --- | +| rowCount | number | Number of rows to scroll per animation | +| colCount | number | Number of columns to scroll per animation | +| animationDuration | number | Animation duration | +| animationDelay | number | Animation delay | +| animationEasing | string | Animation easing function | +| autoPlay | boolean | Whether to auto-play | +| autoPlayDelay | number | Auto-play delay | +| customDistRowFunction | (row: number, table: BaseTableAPI) => { distRow: number; animation?: boolean } | Custom animation distance function for rows | +| customDistColFunction | (col: number, table: BaseTableAPI) => { distCol: number; animation?: boolean } | Custom animation distance function for columns | + +## Running Example + + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const animationPlugin = new VTablePlugins.TableCarouselAnimationPlugin({ + rowCount: 2, + // colCount: 2, + autoPlay: true, + autoPlayDelay: 1000 + }); + const option = { + records: generatePersons(30), + rowSeriesNumber: { + title: 'No.' + }, + columns:[ + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + }, + + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + + plugins: [animationPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; +``` + diff --git a/docs/assets/guide/en/plugin/usage.md b/docs/assets/guide/en/plugin/usage.md new file mode 100644 index 0000000000..178583d244 --- /dev/null +++ b/docs/assets/guide/en/plugin/usage.md @@ -0,0 +1,52 @@ +# Plugin Usage + +Get the plugin package + +```bash +npm install @visactor/vtable-plugins +``` +Import plugins + +```ts +import { TableCarouselAnimationPlugin } from '@visactor/vtable-plugins'; +``` + +Use the plugin + +```ts +const tableCarouselAnimationPlugin = new TableCarouselAnimationPlugin({ + ... +}); +``` + +Add the plugin to the plugin list + +```ts +const option: VTable.ListTableConstructorOptions = { + ... + plugins: [tableCarouselAnimationPlugin] +}; +``` + +Combining multiple plugins + +```ts +const option: VTable.ListTableConstructorOptions = { + ... + plugins: [tableCarouselAnimationPlugin, ...] +}; +``` + +The order of plugin usage generally has no special requirements. Please carefully read the documentation for each plugin to understand its execution timing, and if necessary, refer to the plugin's source code. + +If you encounter issues with plugin usage, please provide feedback promptly. + +## Plugin List +| Plugin Name | Plugin Description | Applicable Object | +| --- | --- | --- | +| `AddRowColumnPlugin` | Add rows and columns | `ListTable` | +| `ColumnSeriesPlugin` | Column series plugin, can specify the number of columns in the table and define the function to generate the column serial number | `ListTable` | +| `RowSeriesPlugin` | Row series plugin, can specify the number of rows in the table and define the function to generate the data corresponding to the empty number | `ListTable` | +| `HighlightHeaderWhenSelectCellPlugin` | Highlight the selected cell | `ListTable`,`PivotTable` | +| `ExcelEditCellKeyboardPlugin` | Excel edit cell keyboard plugin | `ListTable`,`PivotTable` | +| `TableCarouselAnimationPlugin` | Table carousel animation plugin | `ListTable`,`PivotTable` | \ No newline at end of file diff --git a/docs/assets/guide/menu.json b/docs/assets/guide/menu.json index 0577c37e5d..9d69adf87c 100644 --- a/docs/assets/guide/menu.json +++ b/docs/assets/guide/menu.json @@ -740,10 +740,31 @@ }, "children": [ { - "path": "invert-highlight", + "path": "usage", "title": { - "zh": "反选高亮", - "en": "Invert Highlight" + "zh": "插件使用", + "en": "plugin usage" + } + }, + { + "path": "contribute", + "title": { + "zh": "插件贡献", + "en": "plugin contribute" + } + }, + { + "path": "row-column-series", + "title": { + "zh": "行列序号", + "en": "Row Column Series" + } + }, + { + "path": "focus-highlight", + "title": { + "zh": "聚焦高亮", + "en": "Focus Highlight" } }, { @@ -752,6 +773,27 @@ "zh": "表头高亮", "en": "Header Highlight" } + }, + { + "path": "excel-keyboard-alignment", + "title": { + "zh": "excel键盘交互对齐", + "en": "excel keyboard interaction alignment" + } + }, + { + "path": "row-column-add", + "title": { + "zh": "行列新增插件", + "en": "add row column plugin" + } + }, + { + "path": "table-carousel-animation", + "title": { + "zh": "表格轮播动画", + "en": "table carousel animation" + } } ] }, diff --git a/docs/assets/guide/zh/animation/carousel_animation.md b/docs/assets/guide/zh/animation/carousel_animation.md index 4d3aa22fbf..3a9b2db7d6 100644 --- a/docs/assets/guide/zh/animation/carousel_animation.md +++ b/docs/assets/guide/zh/animation/carousel_animation.md @@ -1,51 +1,2 @@ # 表格轮播动画 - -VTable 提供轮播动画插件,可以实现表格的轮播滚动动画效果。 - -
- -
- -## 轮播动画配置项 - -- `CarouselAnimationPlugin` 轮播动画插件,可以配置以下参数: - - `rowCount` 一次动画滚动的行数 - - `colCount` 一次动画滚动的列数 - - `animationDuration` 一次滚动动画的时间 - - `animationDelay` 动画间隔时间 - - `animationEasing` 动画缓动函数 - - `replaceScrollAction` 是否替换滚动行为,如果为 true ,每次滚动操作会移动对于的行数/列数 - -```js -const carouselAnimationPlugin = new CarouselAnimationPlugin(tableInstance, { - rowCount: 2, - replaceScrollAction: true -}); - -carouselAnimationPlugin.play(); -``` - -具体使用参考[demo](../../demo/animation/carousel-animation) - -## 轮播动画API - -### play - -开始轮播动画。 -``` -carouselAnimationPlugin.play() -``` - -### pause - -暂停轮播动画。 -``` -carouselAnimationPlugin.pause() -``` - -### reset - -重置轮播动画。 -``` -carouselAnimationPlugin.reset() -``` +请跳转到链接:[表格轮播动画](../plugin/table-carousel-animation) \ No newline at end of file diff --git a/docs/assets/guide/zh/plugin/contribute.md b/docs/assets/guide/zh/plugin/contribute.md new file mode 100644 index 0000000000..8ff72ff740 --- /dev/null +++ b/docs/assets/guide/zh/plugin/contribute.md @@ -0,0 +1,96 @@ +# 贡献插件 + +每个业务使用 VTable 时,可能需要一些定制化的功能,此时可以通过插件来实现。把通用功能抽离成插件,可以避免重复造轮子,同时也可以方便其他业务使用。 + +共享插件,可以提高开发效率,减少维护成本!期望大家积极贡献插件,共同完善 VTable 生态! + +## 贡献插件注意事项 + +1. 插件需要遵循 VTable 的插件规范。 +2. 插件需要有详细的文档说明,包括插件的参数说明、使用示例等。 + +### 插件规范 +#### 接口规范 + +插件需要实现 `VTable.plugins.IVTablePlugin` 接口。 + +```ts +// 插件统一接口 +export interface IVTablePlugin { + // 插件唯一标识 + id: string; + // 插件运行时机 + runTime: TableEvents[keyof TableEvents] | TableEvents[keyof TableEvents][]; + // 初始化方法,在VTable实例创建后、首次渲染前调用 + run: (...args: any[]) => void; + // 更新方法,当表格数据或配置更新时调用 + update?: (table: BaseTableAPI, options?: any) => void; + // 销毁方法,在VTable实例销毁前调用 + release?: (table: BaseTableAPI) => void; +} +``` + +其中`runTime`指定了插件的运行时机,配置是`TableEvents`中的事件类型。 + +#### 组件的生命周期过程: + +
+ +
+ +附Mermaid 序列图代码(后续如果有变动可以根据如下代码做调整更新,并更新上面的图片): +```mermaid +sequenceDiagram + participant User + participant DOM + participant ListTable + participant EventManager + participant PluginManager + participant Plugin as IVTablePlugin + participant RenderManager + + %% Initialization + ListTable->>PluginManager: register plugins + PluginManager->>Plugin: store plugin instances + + %% User interaction flow + User->>DOM: interact (click, scroll, etc.) + DOM->>EventManager: dispatch browser event + EventManager->>PluginManager: notify(eventType, args) + PluginManager->>PluginManager: filter plugins by runTime + + loop For each matching plugin + PluginManager->>Plugin: run(eventArgs, runTime, tableAPI) + Plugin->>ListTable: read/modify table state + Plugin->>Plugin: process event logic + end + + Plugin->>RenderManager: requestRender() + RenderManager->>ListTable: render updates + ListTable->>DOM: update display + + %% Release + ListTable->>PluginManager: release + PluginManager->>Plugin: release() +``` + +通过上图可以了解到插件的运行时机: +- `runTime`在插件中的关键作用是指定了依赖VTable的哪些事件。 +- 在插件的`run`方法中,可以获取到表格的实例,以及表格的配置和数据;同时需要在`run`方法中,处理插件具体的业务逻辑。 +- 请记得在插件的`release`方法中,释放插件占用的资源,避免内存泄漏。 + +### 插件文档说明 + +插件需要提供详细的文档说明,包括插件的参数说明、使用示例等。 + +文档一般需要包含以下内容: +- 插件的名称 +- 插件的描述 +- 插件的参数说明 +- 插件的使用示例 +- 插件的注意事项 +- 插件的源码地址 + +文档需要放在 `docs/assets/plugins` 目录下,文件名称为 `plugin-name.md`。 + + diff --git a/docs/assets/guide/zh/plugin/excel-keyboard-alignment.md b/docs/assets/guide/zh/plugin/excel-keyboard-alignment.md new file mode 100644 index 0000000000..352f7677aa --- /dev/null +++ b/docs/assets/guide/zh/plugin/excel-keyboard-alignment.md @@ -0,0 +1,135 @@ +# 编辑单元格键盘行为对齐Excel插件使用说明 + +`ExcelEditCellKeyboardPlugin`是 VTable 的扩展组件,能够实现编辑单元格键盘行为对齐Excel的功能。 + +## 插件实现能力说明 +关于键盘响应方便VTable原有的能力有以下两个配置入口, + +| keyboard | 响应 | +| :--------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| enter | 如果在编辑状态,则确认编辑完成;
如果 keyboardOptions.moveFocusCellOnEnter 为 true 时,按下 enter 会切换选中单元格到下面的单元格。
如果 keyboardOptions.editCellOnEnter 为 true 时,如果当前选中了某个可编辑的单元格,按 enter 进入编辑状态。 | +| tab | 需开启 keyboardOptions.moveFocusCellOnTab。
按 tab 切换选中单元格,如果当前是在编辑单元格 则移动到下一个单元格也是编辑状态。 | +| left | 方向键,切换选中单元格。
如果开启 keyboardOptions.moveEditCellOnArrowKeys,那么在编辑状态中也可以切换编辑单元格 | +| right | 同上 | +| top | 同上 | +| bottom | 同上 | +| ctrl+c | 键位不准,这个 copy 是和浏览器的快捷键一致的。
复制选中单元格内容,需要开启 keyboardOptions.copySelected | +| ctrl+v | 键位不准,粘贴快捷键和浏览器的快捷键一致的。
粘贴内容到单元格,需要开启 keyboardOptions.pasteValueToCell,粘贴生效仅针对配置了编辑 editor 的单元格 | +| ctrl+a | 全选,需要开启 keyboardOptions.selectAllOnCtrlA | +| shift | 按住 shift 和鼠标左键,连续区域选中单元格 | +| ctrl | 按住 ctrl 和鼠标左键,选中多个区域 | +| 任何一个键 | 可以监听 tableInstance.on('keydown',(args)=>{ }) | + +能满足大部分的编辑表格的键盘响应要求,但相比Excel来说,VTable的键盘行为还是有一些差异的,比如: + +- 在VTable中,编辑单元格时,按下方向键不会切换到下一个单元格,而是会切换到编辑单元格的光标。 +- 在VTable中,编辑单元格时,按下 enter 不会确认编辑完成,而是会切换到下一个单元格。 +- 在VTable中,编辑单元格时,按下 tab 不会切换到下一个单元格,而是会切换到编辑单元格的光标。 +- 在VTable中,编辑单元格时,按下 shift 和鼠标左键,不会连续区域选中单元格。 +- 在VTable中,编辑单元格时,按住 ctrl 和鼠标左键,不会选中多个区域。 + + +结合VTable已有的部分符合需求的能力,我们开发了`ExcelEditCellKeyboardPlugin`插件,能够实现编辑单元格键盘行为对齐Excel的功能。 + +## 插件使用示例 + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + const excelEditCellKeyboardPlugin = new VTablePlugins.ExcelEditCellKeyboardPlugin(); + + const option = { + records: generatePersons(20), + columns:[ + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + editor: new VTable_editors.InputEditor(), + editCellTrigger: ['keydown'], + plugins: [excelEditCellKeyboardPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; + + +``` + +## 插件后续完善 + +其他和Excel不一致的键盘行为,如: + +- 支持配置项,是否响应删除键 +- 支持配置项,是否响应 ctrl+c 和 ctrl+v +- 支持配置项,是否响应 shift 和鼠标左键 +- 支持配置项,是否响应 ctrl 和鼠标左键 + +各个响应的行为是否需要使用者明确进行配置,如提供配置项: +```ts +const excelEditCellKeyboardPlugin = new ExcelEditCellKeyboardPlugin({ + enableDeleteKey: false +}); +const excelEditCellKeyboardPlugin = new ExcelEditCellKeyboardPlugin(excelEditCellKeyboardPlugin); + +``` + +欢迎大家贡献自己的力量,一起来写更多插件!一起建设VTable的生态! \ No newline at end of file diff --git a/docs/assets/guide/zh/plugin/focus-highlight.md b/docs/assets/guide/zh/plugin/focus-highlight.md new file mode 100644 index 0000000000..f67a743076 --- /dev/null +++ b/docs/assets/guide/zh/plugin/focus-highlight.md @@ -0,0 +1,118 @@ +# 聚焦高亮插件 + +VTable 提供聚焦高亮插件,支持聚焦高亮指定区域。 + +
+ +
+ +## 聚焦高亮插件配置项 + +- `FocusHighlightPlugin` 聚焦高亮插件,可以配置以下参数: + - `fill` 聚焦高亮背景色 + - `opacity` 反选高亮透明度 + - `highlightRange` 初始化聚焦高亮范围 + +``` +export interface FocusHighlightPluginOptions { + fill?: string; + opacity?: number; + highlightRange?: CellRange; +} +``` + +## 使用示例: + + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const highlightPlugin = new VTablePlugins.FocusHighlightPlugin({ + fill: '#000', + opacity: 0.5, + highlightRange: { + start: { + col: 4, + row: 4 + }, + end: { + col: 4, + row: 4 + } + } + }); + const option = { + records: generatePersons(20), + columns:[ + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + theme: VTable.themes.DARK, + plugins: [highlightPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; +``` + +具体使用参考[demo](../../demo/interaction/head-highlight) diff --git a/docs/assets/guide/zh/plugin/header-highlight.md b/docs/assets/guide/zh/plugin/header-highlight.md index a017810452..113dafff89 100644 --- a/docs/assets/guide/zh/plugin/header-highlight.md +++ b/docs/assets/guide/zh/plugin/header-highlight.md @@ -1,6 +1,6 @@ -# 表头高亮插件 +# 选中单元格对应表头高亮插件 -VTable 提供表头高亮插件,支持选中单元格后,高亮对应的表头(行头和列头)。 +VTable 提供选中单元格对应表头高亮插件,支持选中单元格后,高亮对应的表头(行头和列头)。
@@ -8,16 +8,99 @@ VTable 提供表头高亮插件,支持选中单元格后,高亮对应的表 ## 表头高亮插件配置项 -- `HeaderHighlightPlugin` 表头高亮插件,可以配置以下参数: +- `HighlightHeaderWhenSelectCellPlugin` 选中单元格对应表头高亮插件,可以配置以下参数: - `columnHighlight` 是否高亮列头 - `rowHighlight` 是否高亮行头 - `colHighlightBGColor` 列头高亮背景色 - `rowHighlightBGColor` 行头高亮背景色 - `colHighlightColor` 列头高亮字体色 - `rowHighlightColor` 行头高亮字体色 + +插件参数类型: +``` +interface IHighlightHeaderWhenSelectCellPluginOptions { + rowHighlight?: boolean; + colHighlight?: boolean; + colHighlightBGColor?: string; + colHighlightColor?: string; + rowHighlightBGColor?: string; + rowHighlightColor?: string; +} +``` + +## 使用示例: + + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const highlightPlugin = new VTablePlugins.HighlightHeaderWhenSelectCellPlugin({ + colHighlight: true, + rowHighlight: true + }); + const option = { + records: generatePersons(20), + rowSeriesNumber: {}, + columns:[ + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + }, + + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], -```js -const highlightPlugin = new HeaderHighlightPlugin(tableInstance, {}); + plugins: [highlightPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; ``` 具体使用参考[demo](../../demo/interaction/head-highlight) diff --git a/docs/assets/guide/zh/plugin/invert-highlight.md b/docs/assets/guide/zh/plugin/invert-highlight.md deleted file mode 100644 index 1be1fee5b7..0000000000 --- a/docs/assets/guide/zh/plugin/invert-highlight.md +++ /dev/null @@ -1,49 +0,0 @@ -# 反选高亮插件 - -VTable 提供反选高亮插件,支持反选高亮指定区域。 - -
- -
- -## 反选高亮插件配置项 - -- `InvertHighlightPlugin` 反选高亮插件,可以配置以下参数: - - `fill` 反选高亮背景色 - - `opacity` 反选高亮透明度 - -```js -const highlightPlugin = new InvertHighlightPlugin(tableInstance, {}); - -highlightPlugin.setInvertHighlightRange({ - start: { - col: 0, - row: 6 - }, - end: { - col: 5, - row: 6 - } -}); -``` - -具体使用参考[demo](../../demo/interaction/head-highlight) - -## 反选高亮插件 API - -### setInvertHighlightRange - -设置反选高亮范围。 - -```ts -setInvertHighlightRange(range: { - start: { - col: number; - row: number; - }; - end: { - col: number; - row: number; - }; -}): void; -``` \ No newline at end of file diff --git a/docs/assets/guide/zh/plugin/row-column-add.md b/docs/assets/guide/zh/plugin/row-column-add.md new file mode 100644 index 0000000000..a14b333c0e --- /dev/null +++ b/docs/assets/guide/zh/plugin/row-column-add.md @@ -0,0 +1,139 @@ +# 行列新增插件 + +## 功能介绍 + +AddRowColumnPlugin 是为了扩展VTable支持动态新增行列而写的插件。 + +该插件监听了`vTable`实例的 `MOUSEENTER_CELL`, `MOUSELEAVE_CELL`, `MOUSELEAVE_TABLE`事件! + +当鼠标hover到table的cell时,会显示添加行和列的dot和加号;当鼠标离开table的cell时,会隐藏添加行和列的dot和加号。 + +## 插件配置 + +添加行和列的插件的配置选项: + +```ts +export interface AddRowColumnOptions { + /** + * 是否启用添加列 + */ + addColumnEnable?: boolean; + /** + * 是否启用添加行 + */ + addRowEnable?: boolean; + /** + * 添加列的回调函数 + */ + addColumnCallback?: (col: number) => void; + /** + * 添加行的回调函数 + */ + addRowCallback?: (row: number) => void; +} +``` + +## 插件示例 +初始化插件对象,添加到vTable配置的plugins中。 +``` +const addRowColumn = new AddRowColumnPlugin(); +const option = { + records, + columns, + padding: 30, + plugins: [addRowColumn] +}; +``` +为了能控制新增行数据的内容,以及控制新增列后数据的更新以及columns的信息,可以使用插件提供的配置项来优化使用,在初始化插件对象时提供新增行列的勾子函数,在函数中赋值新增的行数据或者列信息。 +```ts + const addRowColumn = new AddRowColumnPlugin({ + addColumnCallback: col => { + columns.splice(addColIndex, 0, { + field: ``, + title: `New Column ${col}`, + width: 100 + }); + this.table.updateColumns(columns); + const newRecords = tableInstance.records.map(record => { + if (Array.isArray(record)) { + record.splice(col - 1, 0, ''); + } + return record; + }); + tableInstance.setRecords(newRecords); + }, + addRowCallback: row => { + tableInstance.addRecord([], row - tableInstance.columnHeaderLevelCount); + } + }); +``` + +可运行示例: + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + const addRowColumn = new VTablePlugins.AddRowColumnPlugin(); +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const option = { + records: generatePersons(20), + rowSeriesNumber: {}, + columns:[ + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + }, + + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + + plugins: [addRowColumn] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; +``` diff --git a/docs/assets/guide/zh/plugin/row-column-series.md b/docs/assets/guide/zh/plugin/row-column-series.md new file mode 100644 index 0000000000..3b3bb9e42e --- /dev/null +++ b/docs/assets/guide/zh/plugin/row-column-series.md @@ -0,0 +1,140 @@ +# 行列序号插件 + +## 功能介绍 + +ColumnSeriesPlugin 和 RowSeriesPlugin 这两个插件 是 VTable 的扩展组件,能够自动生成行列序号。 + +
+ +
+ +## 插件参数 + +```typescript +export interface ColumnSeriesOptions { + columnCount: number; + generateColumnTitle?: (index: number) => string; // 自定义列标题生成函数 + generateColumnField?: (index: number) => string;// 自定义列字段名生成函数 + /** + * 是否自动扩展列 + * @default true + */ + autoExtendColumn?: boolean; +} + +export interface RowSeriesOptions { + rowCount: number; + fillRowRecord?: (index: number) => any; // 填充空行的 自定义生成数据函数 + rowSeriesNumber?: VTable.TYPES.IRowSeriesNumber; + /** + * 是否自动扩展行 + * @default true + */ + autoExtendRow?: boolean; +} +``` + +列序号插件的配置中除了可以配置生成列的数量,以及对应到VTable中columns的配置所需要的字段名field和标题title。 + +行序号插件的配置中`rowCount`可以配置生成行的数量,`rowSeriesNumber`可以配置行序号的配置,这里配置后比new VTable实例中options.rowSeriesNumber优先级更高。`fillRowRecord`可以配置生成行的数据,如果不配置的话 默认是返回个空对象`{}`。 + +## 基本用法 + +```typescript +import * as VTable from '@visactor/vtable'; +import { ColumnSeriesPlugin } from '@visactor/vtable-plugins'; + +// 创建 ColumnSeries 插件实例 +const columnSeries = new ColumnSeriesPlugin({ + columnCount: 100 // 设置列数量 +}); + +// 创建 RowSeries 插件实例 +const rowSeries = new RowSeriesPlugin({ + rowCount: 100 // 设置行数量 +}); + +// 在 VTable 选项中使用插件 +const option: VTable.ListTableConstructorOptions = { + container: document.getElementById('container'), + records: data, + plugins: [columnSeries, rowSeries], + // 其他 VTable 配置... +}; + +// 创建表格实例 +const tableInstance = new VTable.ListTable(option); +``` + +## 高级用法 + +### 自定义列标题和字段名 + +```typescript +const columnSeries = new ColumnSeriesPlugin({ + columnCount: 100, + // 自定义列标题生成 + columnTitleGenerator: (index) => `自定义标题 ${index}`, + // 自定义字段名生成 + columnFieldGenerator: (index) => `field_${index}` +}); +``` + +### 自定义行数据 + +```typescript +const rowSeries = new RowSeriesPlugin({ + rowCount: 100, + // 自定义行数据生成 返回一个空数组 + fillRowRecord: (index) => ([]) +}); + +// or +const rowSeries = new RowSeriesPlugin({ + rowCount: 100, + // 自定义行数据生成 + fillRowRecord: (index) => (['姓名', '年龄', '地址']) +}); +``` + +## 具体示例 + +```javascript livedemo template=vtable + +let tableInstance; +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +// 创建 ColumnSeries 插件实例 +const columnSeries = new VTablePlugins.ColumnSeriesPlugin({ + columnCount: 100, + // 自定义列标题生成 + // generateColumnTitle(index) + // { + // return `自定义标题 ${index}` + // }, + // 自定义字段名生成 + generateColumnField: (index) => `field_${index}` +}); + +// 创建 RowSeries 插件实例 +const rowSeries = new VTablePlugins.RowSeriesPlugin({ + rowCount: 100, + // 自定义行数据生成 + fillRowRecord: (index) => ([]) +}); + +// 在 VTable 选项中使用插件 +const option = { + records: [], + plugins: [columnSeries, rowSeries], + // 其他 VTable 配置... +}; + +// 创建表格实例 +tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); +window.tableInstance = tableInstance; +``` diff --git a/docs/assets/guide/zh/plugin/table-carousel-animation.md b/docs/assets/guide/zh/plugin/table-carousel-animation.md new file mode 100644 index 0000000000..a2aacb6b25 --- /dev/null +++ b/docs/assets/guide/zh/plugin/table-carousel-animation.md @@ -0,0 +1,132 @@ +# 表格轮播动画插件 + +VTable 提供表格轮播动画插件,支持表格的行或列的轮播动画。 + +效果如下: +
+ +
+ +## 使用示例 + +```ts +const tableCarouselAnimationPlugin = new TableCarouselAnimationPlugin({ + rowCount: 10, + colCount: 10, + animationDuration: 1000, + animationDelay: 0, + animationEasing: 'linear', + autoPlay: true, + autoPlayDelay: 1000, +}); + +const option: VTable.ListTableConstructorOptions = { + records, + columns, + plugins: [tableCarouselAnimationPlugin], +}; + +``` +如果并不期望表格初始化后马上进行播放的话,可以配置`autoPlay`为`false`,然后手动调用`play`方法进行播放。 + +```ts +tableCarouselAnimationPlugin.play(); +``` + + + +## 插件参数说明 + +插件提供个性化配置,可以配置以下参数: + +| 参数 | 类型 | 说明 | +| --- | --- | --- | +| rowCount | number | 每次动画滚动行数 | +| colCount | number | 每次动画滚动列数 | +| animationDuration | number | 动画时长 | +| animationDelay | number | 动画延迟 | +| animationEasing | string | 动画缓动函数 | +| autoPlay | boolean | 是否自动播放 | +| autoPlayDelay | number | 自动播放延迟 | +| customDistRowFunction | (row: number, table: BaseTableAPI) => { distRow: number; animation?: boolean } | 自定义动画距离 | +| customDistColFunction | (col: number, table: BaseTableAPI) => { distCol: number; animation?: boolean } | 自定义动画距离 | + +## 运行示例 + + +```javascript livedemo template=vtable +// import * as VTable from '@visactor/vtable'; +// 使用时需要引入插件包@visactor/vtable-plugins +// import * as VTablePlugins from '@visactor/vtable-plugins'; +// 正常使用方式 const columnSeries = new VTable.plugins.ColumnSeriesPlugin({}); +// 官网编辑器中将 VTable.plugins重命名成了VTablePlugins + +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + + const animationPlugin = new VTablePlugins.TableCarouselAnimationPlugin({ + rowCount: 2, + // colCount: 2, + autoPlay: true, + autoPlayDelay: 1000 + }); + const option = { + records: generatePersons(30), + rowSeriesNumber: { + title: 'No.' + }, + columns:[ + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + }, + + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ], + + plugins: [animationPlugin] + }; + const tableInstance = new VTable.ListTable( document.getElementById(CONTAINER_ID),option); + window.tableInstance = tableInstance; +``` + diff --git a/docs/assets/guide/zh/plugin/usage.md b/docs/assets/guide/zh/plugin/usage.md new file mode 100644 index 0000000000..43234c0e78 --- /dev/null +++ b/docs/assets/guide/zh/plugin/usage.md @@ -0,0 +1,53 @@ +# 插件使用 + +获取插件包 + +```bash +npm install @visactor/vtable-plugins +``` +引入插件 + +```ts +import { TableCarouselAnimationPlugin } from '@visactor/vtable-plugins'; +``` + +使用插件 + +```ts +const tableCarouselAnimationPlugin = new TableCarouselAnimationPlugin({ + ... +}); +``` + +在插件列表中添加插件 + +```ts +const option: VTable.ListTableConstructorOptions = { + ... + plugins: [tableCarouselAnimationPlugin] +}; +``` + +插件组合使用 + +```ts +const option: VTable.ListTableConstructorOptions = { + ... + plugins: [tableCarouselAnimationPlugin, ...] +}; +``` + +插件使用顺序一般情况没有特殊要求,请详细阅读每个插件的文档,了解插件的执行时机,如果必要可阅读插件的源码。 + +如果你发现使用插件上存在问题,请及时反馈。 + + +## 插件列表 +| 插件名称 | 插件描述 |适用对象| +| --- | --- | --- | +| `AddRowColumnPlugin` | 添加行和列 | `ListTable` | +| `ColumnSeriesPlugin` | 列序号插件,可以指定表格列数,并定义生成列序号的函数 | `ListTable` | +| `RowSeriesPlugin` | 行序号插件,可以指定表格行数,并定义生成空号对应数据的函数 | `ListTable` | +| `HighlightHeaderWhenSelectCellPlugin` | 高亮选中单元格 | `ListTable`,`PivotTable` | +| `ExcelEditCellKeyboardPlugin` | Excel编辑单元格键盘插件 | `ListTable`,`PivotTable` | +| `TableCarouselAnimationPlugin` | 表格轮播动画插件 | `ListTable`,`PivotTable` | \ No newline at end of file diff --git a/docs/assets/option/en/common/menu-list-item.md b/docs/assets/option/en/common/menu-list-item.md index 658be74fdb..b2f71027f5 100644 --- a/docs/assets/option/en/common/menu-list-item.md +++ b/docs/assets/option/en/common/menu-list-item.md @@ -13,5 +13,6 @@ type MenuListItem = selectedIcon?: Icon; stateIcon?: Icon; children?: MenuListItem[]; + disabled?: boolean; }; -``` \ No newline at end of file +``` diff --git a/docs/assets/option/en/common/option-secondary.md b/docs/assets/option/en/common/option-secondary.md index 0b91964b75..a507b7aa96 100644 --- a/docs/assets/option/en/common/option-secondary.md +++ b/docs/assets/option/en/common/option-secondary.md @@ -505,6 +505,15 @@ Custom cell style assignment - Cell range: `{ range: { start: { row: number, column: number }, end: { row: number, column: number} } }` - customStyleId: Custom style id, the same as the id defined when registering the custom style + +#${prefix} rowSeriesNumber(IRowSeriesNumber) + +set row serial number. +{{ use: row-series-number( + prefix = '###', +) }} + + #${prefix} editor (string|Object|Function) Global configuration cell editor @@ -534,12 +543,13 @@ The trigger timing for entering the editing state. editCellTrigger?:'doubleclick' | 'click' | 'api' | 'keydown' | ('doubleclick' | 'click' | 'api' | 'keydown')[]; ``` -#${prefix} rowSeriesNumber(IRowSeriesNumber) +#${prefix} plugins(IVTablePlugin[]) -set row serial number. -{{ use: row-series-number( - prefix = '###', -) }} +Configure plugins. For details, please refer to the tutorial [click here](../guide/plugin/usage) + +``` +plugins?: IVTablePlugin[]; +``` #${prefix} enableLineBreak(boolean) = false diff --git a/docs/assets/option/zh/common/menu-list-item.md b/docs/assets/option/zh/common/menu-list-item.md index b90a421215..9d5066ce9e 100644 --- a/docs/assets/option/zh/common/menu-list-item.md +++ b/docs/assets/option/zh/common/menu-list-item.md @@ -1,6 +1,6 @@ {{ target: common-menu-list-item }} -MenuListItem定义如下: +MenuListItem 定义如下: ``` type MenuListItem = @@ -13,5 +13,6 @@ type MenuListItem = selectedIcon?: Icon; stateIcon?: Icon; children?: MenuListItem[]; + disabled?: boolean; }; ``` diff --git a/docs/assets/option/zh/common/option-secondary.md b/docs/assets/option/zh/common/option-secondary.md index 9290e53fb5..4d027d2697 100644 --- a/docs/assets/option/zh/common/option-secondary.md +++ b/docs/assets/option/zh/common/option-secondary.md @@ -539,6 +539,14 @@ editCellTrigger?: 'doubleclick' | 'click' | 'api' | 'keydown' | ('doubleclick' | ``` +#${prefix} plugins(IVTablePlugin[]) + +配置插件。具体教程[点击这里](../guide/plugin/usage) + +``` +plugins?: IVTablePlugin[]; +``` + #${prefix} enableLineBreak(boolean) = false 是否开启换行符解析,开启后,单元格内容中包含换行符时,会自动解析换行。 diff --git a/packages/openinula-vtable/package.json b/packages/openinula-vtable/package.json index c63269c6a7..7ac2e0858c 100644 --- a/packages/openinula-vtable/package.json +++ b/packages/openinula-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/openinula-vtable", - "version": "1.17.6", + "version": "1.18.0", "description": "The openinula version of VTable", "keywords": [ "openinula", diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index 1a3b7899db..eff744bdf5 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "1.17.6", + "version": "1.18.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 6d49a1424c..75e3c5a58d 100644 --- a/packages/react-vtable/src/eventsUtils.ts +++ b/packages/react-vtable/src/eventsUtils.ts @@ -86,6 +86,7 @@ export interface EventsProps { onEmptyTipClick?: EventCallback; onEmptyTipDblClick?: EventCallback; onButtonClick?: EventCallback; + onBeforeCacheChartImage?: EventCallback; } export const TABLE_EVENTS = { @@ -160,7 +161,8 @@ export const TABLE_EVENTS = { onChangCellValue: EVENT_TYPE.CHANGE_CELL_VALUE, onEmptyTipClick: EVENT_TYPE.EMPTY_TIP_CLICK, onEmptyTipDblClick: EVENT_TYPE.EMPTY_TIP_DBLCLICK, - onButtonClick: EVENT_TYPE.BUTTON_CLICK + onButtonClick: EVENT_TYPE.BUTTON_CLICK, + onBeforeCacheChartImage: EVENT_TYPE.BEFORE_CACHE_CHART_IMAGE }; export const TABLE_EVENTS_KEYS = Object.keys(TABLE_EVENTS); diff --git a/packages/vtable-calendar/package.json b/packages/vtable-calendar/package.json index 2beae330c5..b0a9e4b3ac 100644 --- a/packages/vtable-calendar/package.json +++ b/packages/vtable-calendar/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-calendar", - "version": "1.17.6", + "version": "1.18.0", "description": "The calendar component of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index 6d098ddc61..431030b0ae 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "1.17.6", + "version": "1.18.0", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-editors/src/input-editor.ts b/packages/vtable-editors/src/input-editor.ts index 0cd54890ac..22ee60e469 100644 --- a/packages/vtable-editors/src/input-editor.ts +++ b/packages/vtable-editors/src/input-editor.ts @@ -29,6 +29,19 @@ export class InputEditor implements IEditor { input.style.width = '100%'; input.style.boxSizing = 'border-box'; input.style.backgroundColor = '#FFFFFF'; + input.style.borderRadius = '0px'; + input.style.border = '2px solid #d9d9d9'; + // #region 为了保证input在focus时,没有圆角 + input.addEventListener('focus', () => { + input.style.borderColor = '#4A90E2'; + input.style.outline = 'none'; + }); + + input.addEventListener('blur', () => { + input.style.borderColor = '#d9d9d9'; + // input.style.boxShadow = 'none'; + }); + // #endregion this.element = input; this.container.appendChild(input); @@ -72,10 +85,17 @@ export class InputEditor implements IEditor { } 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'; + //使border均分input位置rect的上下左右 + const borderWidth = 2; + const top = rect.top - borderWidth / 2; + const left = rect.left - borderWidth / 2; + const width = rect.width + borderWidth; + const height = rect.height + borderWidth; + + this.element.style.top = top + 'px'; + this.element.style.left = left + 'px'; + this.element.style.width = width + 'px'; + this.element.style.height = height + 'px'; } endEditing() { diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index e74f5de67f..83be2ea256 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "1.17.6", + "version": "1.18.0", "description": "The export util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable-export/src/excel/index.ts b/packages/vtable-export/src/excel/index.ts index 8b6d556d7e..7bde71bda9 100644 --- a/packages/vtable-export/src/excel/index.ts +++ b/packages/vtable-export/src/excel/index.ts @@ -34,14 +34,18 @@ export type ExportVTableToExcelOptions = { }; function requestIdleCallbackPromise(options?: IdleRequestOptions) { - return new Promise((resolve) => { - requestIdleCallback((deadline) => { + return new Promise(resolve => { + requestIdleCallback(deadline => { resolve(deadline); }, options); }); } -export async function exportVTableToExcel(tableInstance: IVTable, options?: ExportVTableToExcelOptions, optimization = false) { +export async function exportVTableToExcel( + tableInstance: IVTable, + options?: ExportVTableToExcelOptions, + optimization = false +) { const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('sheet1'); worksheet.properties.defaultRowHeight = 40; @@ -59,7 +63,7 @@ export async function exportVTableToExcel(tableInstance: IVTable, options?: Expo function processSlice(deadline?: IdleDeadline) { return new Promise(async resolve => { - while (currentRow <= maxRow && (!optimization || (deadline?.timeRemaining() > 0))) { + while (currentRow <= maxRow && (!optimization || deadline?.timeRemaining() > 0)) { const endRow = Math.min(currentRow + SLICE_SIZE - 1, maxRow); for (let col = minCol; col <= maxCol; col++) { const colWidth = tableInstance.getColWidth(col); @@ -94,7 +98,7 @@ export async function exportVTableToExcel(tableInstance: IVTable, options?: Expo } else { let nextDeadline: IdleDeadline | undefined; if (optimization) { - nextDeadline = await requestIdleCallbackPromise() + nextDeadline = await requestIdleCallbackPromise(); } await processSlice(nextDeadline); resolve(); @@ -105,7 +109,7 @@ export async function exportVTableToExcel(tableInstance: IVTable, options?: Expo await new Promise(async resolve => { let deadline: IdleDeadline | undefined; if (optimization) { - deadline = await requestIdleCallbackPromise() + deadline = await requestIdleCallbackPromise(); } await processSlice(deadline); resolve(); diff --git a/packages/vtable-gantt/package.json b/packages/vtable-gantt/package.json index 6f90da0999..efee7f01ce 100644 --- a/packages/vtable-gantt/package.json +++ b/packages/vtable-gantt/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-gantt", - "version": "1.17.6", + "version": "1.18.0", "description": "canvas table width high performance", "keywords": [ "vtable-gantt", diff --git a/packages/vtable-plugins/demo/add-row-column/add-row-column.ts b/packages/vtable-plugins/demo/add-row-column/add-row-column.ts new file mode 100644 index 0000000000..f63e3a869e --- /dev/null +++ b/packages/vtable-plugins/demo/add-row-column/add-row-column.ts @@ -0,0 +1,89 @@ +import * as VTable from '@visactor/vtable'; +import { bindDebugTool } from '@visactor/vtable/es/scenegraph/debug-tool'; +import { AddRowColumnPlugin } from '../../src'; +const CONTAINER_ID = 'vTable'; +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + +export function createTable() { + const records = generatePersons(20); + const columns: VTable.ColumnsDefine = [ + { + field: 'image', + title: '行号', + width: 80, + cellType: 'image', + keepAspectRatio: true + }, + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ]; + const addRowColumn = new AddRowColumnPlugin(); + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + columns, + padding: 30, + plugins: [addRowColumn] + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); +} diff --git a/packages/vtable-plugins/demo/carousel-animation/carousel-animation.ts b/packages/vtable-plugins/demo/carousel-animation/(deprecated)carousel-animation.ts similarity index 91% rename from packages/vtable-plugins/demo/carousel-animation/carousel-animation.ts rename to packages/vtable-plugins/demo/carousel-animation/(deprecated)carousel-animation.ts index c9108ce291..3a01bd3fd0 100644 --- a/packages/vtable-plugins/demo/carousel-animation/carousel-animation.ts +++ b/packages/vtable-plugins/demo/carousel-animation/(deprecated)carousel-animation.ts @@ -19,7 +19,7 @@ const generatePersons = count => { }; export function createTable() { - const records = generatePersons(20); + const records = generatePersons(30); const columns: VTable.ColumnsDefine = [ { field: 'image', @@ -76,7 +76,11 @@ export function createTable() { container: document.getElementById(CONTAINER_ID), records, columns, - theme: VTable.themes.DARK, + theme: VTable.themes.DARK.extends({ + scrollStyle: { + visible: 'none' + } + }), // heightMode: 'adaptive', select: { disableSelect: true @@ -84,14 +88,10 @@ export function createTable() { }; const tableInstance = new VTable.ListTable(option); window.tableInstance = tableInstance; - - bindDebugTool(tableInstance.scenegraph.stage, { - customGrapicKeys: ['col', 'row'] - }); - + tableInstance.disableScroll(); const ca = new CarouselAnimationPlugin(tableInstance, { rowCount: 2, - replaceScrollAction: true + replaceScrollAction: false }); ca.play(); diff --git a/packages/vtable-plugins/demo/column-series/column-series.ts b/packages/vtable-plugins/demo/column-series/column-series.ts new file mode 100644 index 0000000000..df518c6ec3 --- /dev/null +++ b/packages/vtable-plugins/demo/column-series/column-series.ts @@ -0,0 +1,51 @@ +import * as VTable from '@visactor/vtable'; +import { bindDebugTool } from '@visactor/vtable/es/scenegraph/debug-tool'; +import { ColumnSeriesPlugin, RowSeriesPlugin } from '../../src'; +const CONTAINER_ID = 'vTable'; +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + +export function createTable() { + const records = generatePersons(20); + + const columnSeries = new ColumnSeriesPlugin({ + columnCount: 100 + }); + const rowSeries = new RowSeriesPlugin({ + rowCount: 100 + // fillRowRecord: index => { + // const record = generatePersons(1)[0]; + // record.id = index; + // return record; + // } + }); + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + plugins: [columnSeries, rowSeries] + // theme: { + // selectionStyle: { + // cellBorderLineWidth: 4 + // } + // } + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); +} diff --git a/packages/vtable-plugins/demo/combine-plugins/combine-plugins.ts b/packages/vtable-plugins/demo/combine-plugins/combine-plugins.ts new file mode 100644 index 0000000000..e04b475bda --- /dev/null +++ b/packages/vtable-plugins/demo/combine-plugins/combine-plugins.ts @@ -0,0 +1,90 @@ +import * as VTable from '@visactor/vtable'; +import { bindDebugTool } from '@visactor/vtable/es/scenegraph/debug-tool'; +import { + AddRowColumnPlugin, + ColumnSeriesPlugin, + ExcelEditCellKeyboardPlugin, + HighlightHeaderWhenSelectCellPlugin, + RowSeriesPlugin +} from '../../src'; +import { InputEditor } from '@visactor/vtable-editors'; +import { table } from 'console'; +const CONTAINER_ID = 'vTable'; +const input_editor = new InputEditor({}); +VTable.register.editor('input', input_editor); + +export function createTable() { + const addRowColumn = new AddRowColumnPlugin({ + addColumnCallback: col => { + columnSeries.resetColumnCount(columnSeries.pluginOptions.columnCount + 1); + const newRecords = tableInstance.records.map(record => { + if (Array.isArray(record)) { + record.splice(col - 1, 0, ''); + } + return record; + }); + tableInstance.setRecords(newRecords); + }, + addRowCallback: row => { + tableInstance.addRecord([], row - tableInstance.columnHeaderLevelCount); + } + }); + + const columnSeries = new ColumnSeriesPlugin({ + columnCount: 3 + }); + const rowSeries = new RowSeriesPlugin({ + rowCount: 6, + fillRowRecord: (index: number) => { + return []; + }, + rowSeriesNumber: { + width: 'auto' + } + }); + const highlightPlugin = new HighlightHeaderWhenSelectCellPlugin({ + colHighlight: true, + rowHighlight: true + }); + const excelEditCellKeyboardPlugin = new ExcelEditCellKeyboardPlugin(); + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records: [ + ['姓名', '年龄', '地址'], + ['张三', 18, '北京'], + ['李四', 20, '上海'], + ['王五', 22, '广州'], + ['赵六', 24, '深圳'], + ['孙七', 26, '成都'] + ], + + padding: 30, + editor: 'input', + editCellTrigger: ['api', 'keydown', 'doubleclick'], + select: { + cornerHeaderSelectMode: 'body', + headerSelectMode: 'body' + }, + theme: VTable.themes.DEFAULT.extends({ + defaultStyle: { + textAlign: 'left', + padding: [2, 6, 2, 6] + }, + headerStyle: { + textAlign: 'center' + } + }), + keyboardOptions: { + moveFocusCellOnEnter: true + // editCellOnEnter: false + }, + defaultRowHeight: 30, + plugins: [addRowColumn, columnSeries, rowSeries, highlightPlugin, excelEditCellKeyboardPlugin] + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); +} diff --git a/packages/vtable-plugins/demo/focus-highlight/focus-highlight.ts b/packages/vtable-plugins/demo/focus-highlight/focus-highlight.ts new file mode 100644 index 0000000000..9188f66c0d --- /dev/null +++ b/packages/vtable-plugins/demo/focus-highlight/focus-highlight.ts @@ -0,0 +1,107 @@ +import * as VTable from '@visactor/vtable'; +import { bindDebugTool } from '@visactor/vtable/es/scenegraph/debug-tool'; +import { FocusHighlightPlugin } from '../../src'; +const CONTAINER_ID = 'vTable'; +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + +export function createTable() { + const highlightPlugin = new FocusHighlightPlugin({ + highlightRange: { + start: { + col: 0, + row: 5 + }, + end: { + col: 6, + row: 6 + } + } + }); + const records = generatePersons(20); + const columns: VTable.ColumnsDefine = [ + { + field: 'image', + title: '行号', + width: 80, + cellType: 'image', + keepAspectRatio: true + }, + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + columns, + theme: VTable.themes.DARK, + select: { + headerSelectMode: 'cell' + }, + // heightMode: 'adaptive', + // select: { + // disableSelect: true + // }, + plugins: [highlightPlugin] + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); +} diff --git a/packages/vtable-plugins/demo/header-highlight/header-highlight.ts b/packages/vtable-plugins/demo/header-highlight/(deprecated)header-highlight.ts similarity index 100% rename from packages/vtable-plugins/demo/header-highlight/header-highlight.ts rename to packages/vtable-plugins/demo/header-highlight/(deprecated)header-highlight.ts diff --git a/packages/vtable-plugins/demo/highlight-header/highlight-header.ts b/packages/vtable-plugins/demo/highlight-header/highlight-header.ts new file mode 100644 index 0000000000..50a3fb5f71 --- /dev/null +++ b/packages/vtable-plugins/demo/highlight-header/highlight-header.ts @@ -0,0 +1,88 @@ +import * as VTable from '@visactor/vtable'; +import { bindDebugTool } from '@visactor/vtable/es/scenegraph/debug-tool'; +import * as VTable_editors from '@visactor/vtable-editors'; + +import { HighlightHeaderWhenSelectCellPlugin } from '../../src'; +const CONTAINER_ID = 'vTable'; +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + +export function createTable() { + const input_editor = new VTable_editors.InputEditor(); + VTable.register.editor('input-editor', input_editor); + + const records = generatePersons(20); + const columns: VTable.ColumnsDefine = [ + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true, + headerEditor: 'input-editor', + editor: 'input-editor' + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ]; + + const highlightPlugin = new HighlightHeaderWhenSelectCellPlugin({ + colHighlight: true, + rowHighlight: true + }); + const option: VTable.ListTableConstructorOptions = { + records, + columns, + rowSeriesNumber: {}, + select: { + outsideClickDeselect: true, + headerSelectMode: 'body' + }, + autoWrapText: true, + editor: 'input-editor', + menu: { + contextMenuItems: ['copy', 'paste', 'delete', '...'] + }, + plugins: [highlightPlugin] + }; + const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID)!, option); + window.tableInstance = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); + + // tableInstance.scenegraph.temporarilyUpdateSelectRectStyle({stroke: false}) +} diff --git a/packages/vtable-plugins/demo/invert-highlight/invert-highlight.ts b/packages/vtable-plugins/demo/invert-highlight/(deprecated)invert-highlight.ts similarity index 100% rename from packages/vtable-plugins/demo/invert-highlight/invert-highlight.ts rename to packages/vtable-plugins/demo/invert-highlight/(deprecated)invert-highlight.ts diff --git a/packages/vtable-plugins/demo/menu.ts b/packages/vtable-plugins/demo/menu.ts index 269806252f..d6b5778cf2 100644 --- a/packages/vtable-plugins/demo/menu.ts +++ b/packages/vtable-plugins/demo/menu.ts @@ -1,28 +1,46 @@ export const menus = [ { - menu: 'carousel-animation', - children: [ - { - path: 'carousel-animation', - name: 'carousel-animation' - } - ] + path: 'carousel-animation', + name: '(deprecated)carousel-animation' }, { - menu: 'header-highlight', - children: [ - { - path: 'header-highlight', - name: 'header-highlight' - } - ] + path: 'header-highlight', + name: '(deprecated)header-highlight' + }, + { + path: 'invert-highlight', + name: '(deprecated)invert-highlight' + }, + { + path: 'focus-highlight', + name: 'focus-highlight' + }, + { + path: 'highlight-header', + name: 'highlight-header' + }, + { + path: 'table-carousel-animation', + name: 'table-carousel-animation' + }, + { + path: 'add-row-column', + name: 'add-row-column' + }, + { + path: 'column-series', + name: 'column-series' + }, + { + path: 'combine-plugins', + name: 'combine-plugins' }, { - menu: 'invert-highlight', + menu: 'pivot-plugin', children: [ { - path: 'invert-highlight', - name: 'invert-highlight' + path: 'pivot', + name: 'pivot-plugin' } ] } diff --git a/packages/vtable-plugins/demo/pivot/pivot-plugin.ts b/packages/vtable-plugins/demo/pivot/pivot-plugin.ts new file mode 100644 index 0000000000..5d78acba80 --- /dev/null +++ b/packages/vtable-plugins/demo/pivot/pivot-plugin.ts @@ -0,0 +1,319 @@ +import * as VTable from '@visactor/vtable'; +import { + ExcelEditCellKeyboardPlugin, + HighlightHeaderWhenSelectCellPlugin, + TableCarouselAnimationPlugin +} from '../../src'; +import { InputEditor } from '@visactor/vtable-editors'; +const PivotTable = VTable.PivotTable; +const CONTAINER_ID = 'vTable'; +const input_editor = new InputEditor({}); +VTable.register.editor('input', input_editor); +export function createTable() { + const tableCarouselAnimationPlugin = new TableCarouselAnimationPlugin({ + rowCount: 2, + // colCount: 2, + autoPlay: true, + autoPlayDelay: 1000 + }); + const option: VTable.PivotTableConstructorOptions = { + rows: ['province', 'city'], + columns: ['category', 'sub_category'], + indicators: ['sales', 'number'], + plugins: [tableCarouselAnimationPlugin], + editor: 'input', + editCellTrigger: ['api', 'keydown', 'doubleclick'], + indicatorTitle: '指标名称', + indicatorsAsCol: false, + dataConfig: { + totals: { + row: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['province'], + grandTotalLabel: '行总计', + subTotalLabel: '小计' + }, + column: { + showGrandTotals: true, + showSubTotals: true, + subTotalsDimensions: ['category'], + grandTotalLabel: '列总计', + subTotalLabel: '小计' + } + } + }, + columnResizeMode: 'header', + corner: { titleOnDimension: 'row' }, + theme: VTable.themes.DEFAULT.extends({ + columnResize: { + visibleOnHover: true + } + }), + records: [ + { + sales: 891, + number: 7789, + province: '浙江省', + city: '杭州市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 792, + number: 2367, + province: '浙江省', + city: '绍兴市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 893, + number: 3877, + province: '浙江省', + city: '宁波市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 1094, + number: 4342, + province: '浙江省', + city: '舟山市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 1295, + number: 5343, + province: '浙江省', + city: '杭州市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 496, + number: 632, + province: '浙江省', + city: '绍兴市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 1097, + number: 7234, + province: '浙江省', + city: '宁波市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 998, + number: 834, + province: '浙江省', + city: '舟山市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 766, + number: 945, + province: '浙江省', + city: '杭州市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 990, + number: 1304, + province: '浙江省', + city: '绍兴市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 891, + number: 1145, + province: '浙江省', + city: '宁波市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 792, + number: 1432, + province: '浙江省', + city: '舟山市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 745, + number: 1343, + province: '浙江省', + city: '杭州市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 843, + number: 1354, + province: '浙江省', + city: '绍兴市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 895, + number: 1523, + province: '浙江省', + city: '宁波市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 965, + number: 1634, + province: '浙江省', + city: '舟山市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 776, + number: 1723, + province: '四川省', + city: '成都市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 634, + number: 1822, + province: '四川省', + city: '绵阳市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 909, + number: 1943, + province: '四川省', + city: '南充市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 399, + number: 2330, + province: '四川省', + city: '乐山市', + category: '家具', + sub_category: '桌子' + }, + { + sales: 700, + number: 2451, + province: '四川省', + city: '成都市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 689, + number: 2244, + province: '四川省', + city: '绵阳市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 500, + number: 2333, + province: '四川省', + city: '南充市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 800, + number: 2445, + province: '四川省', + city: '乐山市', + category: '家具', + sub_category: '沙发' + }, + { + sales: 1044, + number: 2335, + province: '四川省', + city: '成都市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 689, + number: 245, + province: '四川省', + city: '绵阳市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 794, + number: 2457, + province: '四川省', + city: '南充市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 566, + number: 2458, + province: '四川省', + city: '乐山市', + category: '办公用品', + sub_category: '笔' + }, + { + sales: 865, + number: 4004, + province: '四川省', + city: '成都市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 999, + number: 3077, + province: '四川省', + city: '绵阳市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 999, + number: 3551, + province: '四川省', + city: '南充市', + category: '办公用品', + sub_category: '纸张' + }, + { + sales: 999, + number: 352, + province: '四川省', + city: '乐山市', + category: '办公用品', + sub_category: '纸张' + } + ], + widthMode: 'autoWidth' // 宽度模式:standard 标准模式; adaptive 自动填满容器 + }; + + const instance = new PivotTable(document.getElementById(CONTAINER_ID)!, option); + window.tableInstance = instance; + + // 只为了方便控制太调试用,不要拷贝 + window.tableInstance = instance; +} diff --git a/packages/vtable-plugins/demo/table-carousel-animation/table-carousel-animation.ts b/packages/vtable-plugins/demo/table-carousel-animation/table-carousel-animation.ts new file mode 100644 index 0000000000..e5c78fcae1 --- /dev/null +++ b/packages/vtable-plugins/demo/table-carousel-animation/table-carousel-animation.ts @@ -0,0 +1,98 @@ +import * as VTable from '@visactor/vtable'; +import { bindDebugTool } from '@visactor/vtable/es/scenegraph/debug-tool'; +import { InvertHighlightPlugin, TableCarouselAnimationPlugin } from '../../src'; +const CONTAINER_ID = 'vTable'; +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' + (i + 1) : 'front-end engineer' + (i + 1), + city: 'beijing', + image: + '' + })); +}; + +export function createTable() { + const records = generatePersons(30); + const columns: VTable.ColumnsDefine = [ + { + field: 'image', + title: '行号', + width: 80, + cellType: 'image', + keepAspectRatio: true + }, + { + field: 'id', + title: 'ID', + width: 'auto', + minWidth: 50, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true, + style: { + underline: true, + underlineDash: [2, 0], + underlineOffset: 3 + } + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + { + field: 'date1', + title: 'birthday', + width: 200 + }, + { + field: 'sex', + title: 'sex', + width: 100 + } + ]; + const tca = new TableCarouselAnimationPlugin({ + rowCount: 2, + // colCount: 2, + autoPlay: true, + autoPlayDelay: 1000 + }); + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + columns, + theme: VTable.themes.DARK, + // heightMode: 'adaptive', + select: { + disableSelect: true + }, + plugins: [tca] + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + + bindDebugTool(tableInstance.scenegraph.stage, { + customGrapicKeys: ['col', 'row'] + }); +} diff --git a/packages/vtable-plugins/package.json b/packages/vtable-plugins/package.json index 59a5dc1959..2751355e80 100644 --- a/packages/vtable-plugins/package.json +++ b/packages/vtable-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-plugins", - "version": "1.17.6", + "version": "1.18.0", "description": "The search util of VTable", "author": { "name": "VisActor", @@ -23,7 +23,7 @@ } }, "scripts": { - "start": "vite ./demo", + "demo": "vite ./demo", "build": "bundle --clean" }, "unpkg": "latest", diff --git a/packages/vtable-plugins/src/add-row-column.ts b/packages/vtable-plugins/src/add-row-column.ts new file mode 100644 index 0000000000..5fc230e59b --- /dev/null +++ b/packages/vtable-plugins/src/add-row-column.ts @@ -0,0 +1,459 @@ +import * as VTable from '@visactor/vtable'; +/** + * 添加行和列的插件的配置选项 + */ +export interface AddRowColumnOptions { + /** + * 是否启用添加列 + */ + addColumnEnable?: boolean; + /** + * 是否启用添加行 + */ + addRowEnable?: boolean; + /** + * 添加列的回调函数 + */ + addColumnCallback?: (col: number) => void; + /** + * 添加行的回调函数 + */ + addRowCallback?: (row: number) => void; +} +/** + * 添加行和列的插件 + * 该插件监听了table的MOUSEENTER_CELL,MOUSELEAVE_CELL,MOUSELEAVE_TABLE事件 + * 当鼠标hover到table的cell时,会显示添加行和列的dot和加号 + * 当鼠标离开table的cell时,会隐藏添加行和列的dot和加号 + */ +export class AddRowColumnPlugin implements VTable.plugins.IVTablePlugin { + id = 'add-row-column'; + runTime = [ + VTable.TABLE_EVENT_TYPE.MOUSEENTER_CELL, + VTable.TABLE_EVENT_TYPE.MOUSELEAVE_CELL, + VTable.TABLE_EVENT_TYPE.MOUSELEAVE_TABLE + ]; + pluginOptions: AddRowColumnOptions; + table: VTable.ListTable; + hoverCell: VTable.TYPES.CellAddressWithBound; + hideAllTimeoutId_addColumn: NodeJS.Timeout; + hideAllTimeoutId_addRow: NodeJS.Timeout; + leftDotForAddColumn: HTMLElement; + rightDotForAddColumn: HTMLElement; + addIconForAddColumn: HTMLElement; + addLineForAddColumn: HTMLElement; + topDotForAddRow: HTMLElement; + bottomDotForAddRow: HTMLElement; + addIconForAddRow: HTMLElement; + addLineForAddRow: HTMLElement; + + constructor( + pluginOptions: AddRowColumnOptions = { + addColumnEnable: true, + addRowEnable: true + } + ) { + this.pluginOptions = pluginOptions; + this.pluginOptions.addColumnEnable = this.pluginOptions.addColumnEnable ?? true; + this.pluginOptions.addRowEnable = this.pluginOptions.addRowEnable ?? true; + if (this.pluginOptions.addColumnEnable) { + this.initAddColumnDomElement(); + this.bindEventForAddColumn(); + } + if (this.pluginOptions.addRowEnable) { + this.initAddRowDomElement(); + this.bindEventForAddRow(); + } + } + run(...args: any[]) { + const eventArgs = args[0]; + const runTime = args[1]; + const table: VTable.BaseTableAPI = args[2]; + this.table = table as VTable.ListTable; + if (runTime === VTable.TABLE_EVENT_TYPE.MOUSEENTER_CELL) { + clearTimeout(this.hideAllTimeoutId_addColumn); + clearTimeout(this.hideAllTimeoutId_addRow); + const canvasBounds = table.canvas.getBoundingClientRect(); + const cell = table.getCellAtRelativePosition( + eventArgs.event.clientX - canvasBounds.left, + eventArgs.event.clientY - canvasBounds.top + ); + this.hoverCell = cell; + const cellRect = table.getCellRelativeRect(cell.col, cell.row); + if (this.pluginOptions.addColumnEnable) { + const isRowSerierNumberCol = table.isSeriesNumber(cell.col, 0); + this.showDotForAddColumn( + canvasBounds.top - 6, + cellRect.left + canvasBounds.left, + cellRect.right + canvasBounds.left, + !isRowSerierNumberCol + ); + } + if (this.pluginOptions.addRowEnable) { + const isHeader = table.isHeader(cell.col, cell.row); + this.showDotForAddRow( + cellRect.top + canvasBounds.top, + canvasBounds.left - 6, + cellRect.bottom + canvasBounds.top, + !isHeader, + !isHeader + ); + } + } else if (runTime === VTable.TABLE_EVENT_TYPE.MOUSELEAVE_CELL) { + } else if (runTime === VTable.TABLE_EVENT_TYPE.MOUSELEAVE_TABLE) { + if (this.pluginOptions.addColumnEnable) { + this.delayHideAllForAddColumn(); + } + if (this.pluginOptions.addRowEnable) { + this.delayHideAllForAddRow(); + } + } + } + // #region 添加列 + initAddColumnDomElement() { + //创建一个div 作为hoverCell的顶部左侧的圆点 + this.leftDotForAddColumn = document.createElement('div'); + this.leftDotForAddColumn.style.width = '6px'; + this.leftDotForAddColumn.style.height = '6px'; + this.leftDotForAddColumn.style.backgroundColor = '#4A90E2'; // 蓝色 + this.leftDotForAddColumn.style.position = 'absolute'; + this.leftDotForAddColumn.style.cursor = 'pointer'; + this.leftDotForAddColumn.style.zIndex = '1000'; + this.leftDotForAddColumn.style.borderRadius = '50%'; + this.leftDotForAddColumn.style.border = '1px solid white'; + this.leftDotForAddColumn.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)'; + document.body.appendChild(this.leftDotForAddColumn); + + //创建一个div 作为hoverCell的顶部右侧的圆点 + this.rightDotForAddColumn = document.createElement('div'); + this.rightDotForAddColumn.style.width = '6px'; + this.rightDotForAddColumn.style.height = '6px'; + this.rightDotForAddColumn.style.backgroundColor = '#4A90E2'; // 蓝色 + this.rightDotForAddColumn.style.position = 'absolute'; + this.rightDotForAddColumn.style.cursor = 'pointer'; + this.rightDotForAddColumn.style.zIndex = '1000'; + this.rightDotForAddColumn.style.borderRadius = '50%'; + this.rightDotForAddColumn.style.border = '1px solid white'; + this.rightDotForAddColumn.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)'; + document.body.appendChild(this.rightDotForAddColumn); + + //创建+加号 当鼠标hover到圆点上时,显示+加号 + this.addIconForAddColumn = document.createElement('div'); + this.addIconForAddColumn.style.width = '18px'; + this.addIconForAddColumn.style.height = '18px'; + this.addIconForAddColumn.style.backgroundColor = '#4A90E2'; // 蓝色 + this.addIconForAddColumn.style.position = 'absolute'; + this.addIconForAddColumn.style.zIndex = '1001'; + this.addIconForAddColumn.style.display = 'none'; + this.addIconForAddColumn.style.borderRadius = '50%'; + this.addIconForAddColumn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; + this.addIconForAddColumn.style.display = 'flex'; + this.addIconForAddColumn.style.justifyContent = 'center'; + this.addIconForAddColumn.style.alignItems = 'center'; + this.addIconForAddColumn.style.border = '1px solid white'; + document.body.appendChild(this.addIconForAddColumn); + + //addIcon中添加一个+号 + const addIconText = document.createElement('div'); + addIconText.textContent = '+'; + addIconText.style.color = 'white'; + addIconText.style.fontSize = '18px'; + addIconText.style.fontWeight = 'bold'; + addIconText.style.lineHeight = '15px'; + addIconText.style.userSelect = 'none'; + addIconText.style.cursor = 'pointer'; + addIconText.style.verticalAlign = 'top'; + addIconText.style.textAlign = 'center'; + + this.addIconForAddColumn.appendChild(addIconText); + + // 创建加号下面列间隔线 + this.addLineForAddColumn = document.createElement('div'); + this.addLineForAddColumn.style.width = '2px'; + this.addLineForAddColumn.style.height = '10px'; + this.addLineForAddColumn.style.backgroundColor = '#4A90E2'; // 蓝色 + this.addLineForAddColumn.style.position = 'absolute'; + this.addLineForAddColumn.style.zIndex = '1001'; + this.addLineForAddColumn.style.display = 'none'; + document.body.appendChild(this.addLineForAddColumn); + } + bindEventForAddColumn() { + this.leftDotForAddColumn.addEventListener('mouseenter', () => { + clearTimeout(this.hideAllTimeoutId_addColumn); + this.addIconForAddColumn.style.display = 'block'; + const dotWidth = this.leftDotForAddColumn.offsetWidth; + const dotHeight = this.leftDotForAddColumn.offsetHeight; + this.showAddIconForAddColumn( + this.leftDotForAddColumn.offsetLeft + dotWidth / 2, + this.leftDotForAddColumn.offsetTop + dotHeight / 2, + true + ); + this.showSplitLineForAddColumn( + this.leftDotForAddColumn.offsetLeft + dotWidth / 2 - 1, + this.leftDotForAddColumn.offsetTop + dotHeight / 2 + 2, + this.table.getDrawRange().height + ); + }); + + this.rightDotForAddColumn.addEventListener('mouseenter', () => { + clearTimeout(this.hideAllTimeoutId_addColumn); + this.addIconForAddColumn.style.display = 'block'; + const dotWidth = this.rightDotForAddColumn.offsetWidth; + const dotHeight = this.rightDotForAddColumn.offsetHeight; + this.showAddIconForAddColumn( + this.rightDotForAddColumn.offsetLeft + dotWidth / 2, + this.rightDotForAddColumn.offsetTop + dotHeight / 2, + false + ); + this.showSplitLineForAddColumn( + this.rightDotForAddColumn.offsetLeft + dotWidth / 2 - 1, + this.rightDotForAddColumn.offsetTop + dotHeight / 2 + 2, + this.table.getDrawRange().height + ); + }); + + // this.addIconForAddColumn.addEventListener('mouseenter', () => { + // clearTimeout(this.hideAllTimeoutId_addColumn); + // }); + this.addIconForAddColumn.addEventListener('mouseleave', () => { + this.addIconForAddColumn.style.display = 'none'; + this.addLineForAddColumn.style.display = 'none'; + this.delayHideAllForAddColumn(); + }); + + this.addIconForAddColumn.addEventListener('click', (e: MouseEvent) => { + const isLeft = this.addIconForAddColumn.dataset.addIconType === 'left'; + const columns = this.table.options.columns; + const col = this.hoverCell.col; + const addColIndex = isLeft ? col : col + 1; + if (this.pluginOptions.addColumnCallback) { + this.pluginOptions.addColumnCallback(addColIndex); + } else { + columns.splice(addColIndex, 0, { + field: ``, + title: `New Column ${col}`, + width: 100 + }); + this.table.updateColumns(columns); + } + this.delayHideAllForAddColumn(0); + }); + } + showDotForAddColumn( + top: number, + left: number, + right: number, + isShowLeft: boolean = true, + isShowRight: boolean = true + ) { + // 动态获取元素尺寸 + const dotWidth = this.leftDotForAddColumn.offsetWidth; + const dotHeight = this.leftDotForAddColumn.offsetHeight; + this.leftDotForAddColumn.style.left = `${left - dotWidth / 2}px`; + this.leftDotForAddColumn.style.top = `${top - dotHeight / 2}px`; + this.rightDotForAddColumn.style.left = `${right - dotWidth / 2}px`; + this.rightDotForAddColumn.style.top = `${top - dotHeight / 2}px`; + this.leftDotForAddColumn.style.display = isShowLeft ? 'block' : 'none'; + this.rightDotForAddColumn.style.display = isShowRight ? 'block' : 'none'; + } + showAddIconForAddColumn(left: number, top: number, isLeft: boolean) { + // 动态获取元素尺寸 + const iconWidth = this.addIconForAddColumn.offsetWidth; + const iconHeight = this.addIconForAddColumn.offsetHeight; + const dotHeight = this.leftDotForAddColumn.offsetHeight; + this.addIconForAddColumn.style.left = `${left - iconWidth / 2}px`; + this.addIconForAddColumn.style.top = `${top - iconHeight / 2 - dotHeight / 2}px`; + if (isLeft) { + this.addIconForAddColumn.dataset.addIconType = 'left'; + } else { + this.addIconForAddColumn.dataset.addIconType = 'right'; + } + } + showSplitLineForAddColumn(left: number, top: number, height: number) { + this.addLineForAddColumn.style.left = `${left}px`; + this.addLineForAddColumn.style.top = `${top}px`; + this.addLineForAddColumn.style.height = `${height}px`; + this.addLineForAddColumn.style.display = 'block'; + } + delayHideAllForAddColumn(delay: number = 1000) { + this.hideAllTimeoutId_addColumn = setTimeout(() => { + this.addIconForAddColumn.style.display = 'none'; + this.addLineForAddColumn.style.display = 'none'; + this.leftDotForAddColumn.style.display = 'none'; + this.rightDotForAddColumn.style.display = 'none'; + }, delay); + } + // #endregion + + // #region 添加行 + initAddRowDomElement() { + //创建一个div 作为hoverCell的顶部左侧的圆点 + this.topDotForAddRow = document.createElement('div'); + this.topDotForAddRow.style.width = '6px'; + this.topDotForAddRow.style.height = '6px'; + this.topDotForAddRow.style.backgroundColor = '#4A90E2'; // 蓝色 + this.topDotForAddRow.style.position = 'absolute'; + this.topDotForAddRow.style.cursor = 'pointer'; + this.topDotForAddRow.style.zIndex = '1000'; + this.topDotForAddRow.style.borderRadius = '50%'; + this.topDotForAddRow.style.border = '1px solid white'; + this.topDotForAddRow.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)'; + document.body.appendChild(this.topDotForAddRow); + + //创建一个div 作为hoverCell的底部右侧的圆点 + this.bottomDotForAddRow = document.createElement('div'); + this.bottomDotForAddRow.style.width = '6px'; + this.bottomDotForAddRow.style.height = '6px'; + this.bottomDotForAddRow.style.backgroundColor = '#4A90E2'; // 蓝色 + this.bottomDotForAddRow.style.position = 'absolute'; + this.bottomDotForAddRow.style.cursor = 'pointer'; + this.bottomDotForAddRow.style.zIndex = '1000'; + this.bottomDotForAddRow.style.borderRadius = '50%'; + this.bottomDotForAddRow.style.border = '1px solid white'; + this.bottomDotForAddRow.style.boxShadow = '0 1px 3px rgba(0,0,0,0.2)'; + document.body.appendChild(this.bottomDotForAddRow); + + //创建+加号 当鼠标hover到圆点上时,显示+加号 + this.addIconForAddRow = document.createElement('div'); + this.addIconForAddRow.style.width = '18px'; + this.addIconForAddRow.style.height = '18px'; + this.addIconForAddRow.style.backgroundColor = '#4A90E2'; // 蓝色 + this.addIconForAddRow.style.position = 'absolute'; + this.addIconForAddRow.style.zIndex = '1001'; + this.addIconForAddRow.style.display = 'none'; + this.addIconForAddRow.style.borderRadius = '50%'; + this.addIconForAddRow.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; + this.addIconForAddRow.style.display = 'flex'; + this.addIconForAddRow.style.justifyContent = 'center'; + this.addIconForAddRow.style.alignItems = 'center'; + this.addIconForAddRow.style.border = '1px solid white'; + document.body.appendChild(this.addIconForAddRow); + + //addIcon中添加一个+号 + const addIconText = document.createElement('div'); + addIconText.textContent = '+'; + addIconText.style.color = 'white'; + addIconText.style.fontSize = '18px'; + addIconText.style.fontWeight = 'bold'; + addIconText.style.lineHeight = '15px'; + addIconText.style.userSelect = 'none'; + addIconText.style.cursor = 'pointer'; + addIconText.style.verticalAlign = 'top'; + addIconText.style.textAlign = 'center'; + + this.addIconForAddRow.appendChild(addIconText); + + // 创建加号下面行间隔线 + this.addLineForAddRow = document.createElement('div'); + this.addLineForAddRow.style.width = '10px'; + this.addLineForAddRow.style.height = '2px'; + this.addLineForAddRow.style.backgroundColor = '#4A90E2'; // 蓝色 + this.addLineForAddRow.style.position = 'absolute'; + this.addLineForAddRow.style.zIndex = '1001'; + this.addLineForAddRow.style.display = 'none'; + document.body.appendChild(this.addLineForAddRow); + } + bindEventForAddRow() { + this.topDotForAddRow.addEventListener('mouseenter', () => { + clearTimeout(this.hideAllTimeoutId_addRow); + this.addIconForAddRow.style.display = 'block'; + const dotWidth = this.topDotForAddRow.offsetWidth; + const dotHeight = this.topDotForAddRow.offsetHeight; + this.showAddIconForAddRow( + this.topDotForAddRow.offsetLeft + dotWidth / 2, + this.topDotForAddRow.offsetTop + dotHeight / 2, + true + ); + this.showSplitLineForAddRow( + this.topDotForAddRow.offsetLeft + dotWidth + 2, + this.topDotForAddRow.offsetTop + dotHeight / 2 - 1, + this.table.getDrawRange().width + ); + }); + + this.bottomDotForAddRow.addEventListener('mouseenter', () => { + clearTimeout(this.hideAllTimeoutId_addRow); + this.addIconForAddRow.style.display = 'block'; + const dotWidth = this.bottomDotForAddRow.offsetWidth; + const dotHeight = this.bottomDotForAddRow.offsetHeight; + this.showAddIconForAddRow( + this.bottomDotForAddRow.offsetLeft + dotWidth / 2, + this.bottomDotForAddRow.offsetTop + dotHeight / 2, + false + ); + this.showSplitLineForAddRow( + this.bottomDotForAddRow.offsetLeft + dotWidth + 2, + this.bottomDotForAddRow.offsetTop + dotHeight / 2 - 1, + this.table.getDrawRange().height + ); + }); + + this.addIconForAddRow.addEventListener('mouseleave', () => { + this.addIconForAddRow.style.display = 'none'; + this.addLineForAddRow.style.display = 'none'; + this.delayHideAllForAddRow(); + }); + + this.addIconForAddRow.addEventListener('click', (e: MouseEvent) => { + const isTop = this.addIconForAddRow.dataset.addIconType === 'top'; + const row = this.hoverCell.row; + const addRowIndex = isTop ? row : row + 1; + if (this.pluginOptions.addRowCallback) { + this.pluginOptions.addRowCallback(addRowIndex); + } else { + const recordIndex = this.table.getRecordIndexByCell(0, addRowIndex); + this.table.addRecord({}, recordIndex); + } + this.delayHideAllForAddRow(0); + }); + } + showDotForAddRow(top: number, left: number, bottom: number, isShowTop: boolean = true, isShowBottom: boolean = true) { + // 动态获取元素尺寸 + const dotWidth = this.topDotForAddRow.offsetWidth; + const dotHeight = this.topDotForAddRow.offsetHeight; + this.topDotForAddRow.style.left = `${left - dotWidth / 2}px`; + this.topDotForAddRow.style.top = `${top - dotHeight / 2}px`; + this.bottomDotForAddRow.style.left = `${left - dotWidth / 2}px`; + this.bottomDotForAddRow.style.top = `${bottom - dotHeight / 2}px`; + this.topDotForAddRow.style.display = isShowTop ? 'block' : 'none'; + this.bottomDotForAddRow.style.display = isShowBottom ? 'block' : 'none'; + } + showAddIconForAddRow(left: number, top: number, isTop: boolean) { + // 动态获取元素尺寸 + const iconWidth = this.addIconForAddRow.offsetWidth; + const iconHeight = this.addIconForAddRow.offsetHeight; + const dotWidth = this.topDotForAddRow.offsetWidth; + this.addIconForAddRow.style.left = `${left - iconWidth / 2 - dotWidth / 2}px`; + this.addIconForAddRow.style.top = `${top - iconHeight / 2}px`; + if (isTop) { + this.addIconForAddRow.dataset.addIconType = 'top'; + } else { + this.addIconForAddRow.dataset.addIconType = 'bottom'; + } + } + showSplitLineForAddRow(left: number, top: number, width: number) { + this.addLineForAddRow.style.left = `${left}px`; + this.addLineForAddRow.style.top = `${top}px`; + this.addLineForAddRow.style.width = `${width}px`; + this.addLineForAddRow.style.display = 'block'; + } + delayHideAllForAddRow(delay: number = 1000) { + this.hideAllTimeoutId_addRow = setTimeout(() => { + this.addIconForAddRow.style.display = 'none'; + this.addLineForAddRow.style.display = 'none'; + this.topDotForAddRow.style.display = 'none'; + this.bottomDotForAddRow.style.display = 'none'; + }, delay); + } + // #endregion + release() { + this.leftDotForAddColumn.remove(); + this.rightDotForAddColumn.remove(); + this.addIconForAddColumn.remove(); + this.addLineForAddColumn.remove(); + this.topDotForAddRow.remove(); + this.bottomDotForAddRow.remove(); + this.addIconForAddRow.remove(); + this.addLineForAddRow.remove(); + } +} diff --git a/packages/vtable-plugins/src/column-series.ts b/packages/vtable-plugins/src/column-series.ts new file mode 100644 index 0000000000..5e30bf65b4 --- /dev/null +++ b/packages/vtable-plugins/src/column-series.ts @@ -0,0 +1,101 @@ +import * as VTable from '@visactor/vtable'; +/** + * 添加行和列的插件的配置选项 + */ +export interface ColumnSeriesOptions { + columnCount: number; + generateColumnTitle?: (index: number) => string; + generateColumnField?: (index: number) => string; + /** + * 是否自动扩展列 + * @default true + */ + autoExtendColumn?: boolean; +} +/** + * 生成列序号标题的插件 + */ +export class ColumnSeriesPlugin implements VTable.plugins.IVTablePlugin { + id = 'column-series'; + runTime = [VTable.TABLE_EVENT_TYPE.BEFORE_INIT, VTable.TABLE_EVENT_TYPE.BEFORE_KEYDOWN]; + pluginOptions: ColumnSeriesOptions; + table: VTable.ListTable; + columns: { field?: string; title: string }[] = []; + constructor(pluginOptions: ColumnSeriesOptions) { + this.pluginOptions = Object.assign({ columnCount: 100, autoExtendColumn: true }, pluginOptions); + } + run(...args: any[]) { + if (args[1] === VTable.TABLE_EVENT_TYPE.BEFORE_INIT) { + const eventArgs = args[0]; + const table: VTable.BaseTableAPI = args[2]; + this.table = table as VTable.ListTable; + const options = eventArgs.options; + //根据pluginOptions的columnCount组织columns,column的title生成规则和excel一致,如A~Z,AA~AZ,AB~AZ,AA~ZZ,AAA~ZZZ + this.columns = this.generateColumns(this.pluginOptions.columnCount); + options.columns = this.columns; + } else if (args[1] === VTable.TABLE_EVENT_TYPE.BEFORE_KEYDOWN) { + const eventArgs = args[0]; + const e = eventArgs.event; + if (e.key === 'ArrowRight') { + if ( + this.pluginOptions.autoExtendColumn && + this.table.stateManager.select.cellPos.col === this.table.colCount - 1 + ) { + this.table.addColumn(this.generateColumn(this.table.colCount - 1) as VTable.ColumnDefine); + } + } + } + } + /** + * 生成列字段和标题 + * 规则和excel一致,如A~Z,AA~AZ,AB~AZ,AA~ZZ,AAA~ZZZ + * @param columnCount 列数 + * @returns 列字段和标题的数组 + */ + generateColumns(columnCount: number): { field?: string; title: string }[] { + const columnFields = []; + for (let i = 0; i < columnCount; i++) { + columnFields.push(this.generateColumn(i)); + } + return columnFields; + } + generateColumn(index: number): { field?: string; title: string } { + const column = { + // field: this.pluginOptions.generateColumnField + // ? this.pluginOptions.generateColumnField(i) + // : this.generateColumnField(i), + title: this.pluginOptions.generateColumnTitle + ? this.pluginOptions.generateColumnTitle(index) + : this.generateColumnField(index) + }; + return column; + } + /** + * 生成excel的列标题,规则和excel一致,如A~Z,AA~AZ,AB~AZ,AA~ZZ,AAA~ZZZ + * @param index 从0开始 + * @returns + */ + generateColumnField(index: number): string { + // 处理0-25的情况(A-Z) + if (index < 26) { + return String.fromCharCode(65 + index); + } + + const title = []; + index++; // 调整索引,使得第一个26变成AA + + while (index > 0) { + index--; // 每次循环前减1,以正确处理进位 + title.unshift(String.fromCharCode(65 + (index % 26))); + index = Math.floor(index / 26); + } + + return title.join(''); + } + + resetColumnCount(columnCount: number) { + this.pluginOptions.columnCount = columnCount; + this.columns = this.generateColumns(columnCount); + this.table.updateColumns(this.columns as VTable.ColumnsDefine); + } +} diff --git a/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts b/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts new file mode 100644 index 0000000000..c835ce9c77 --- /dev/null +++ b/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts @@ -0,0 +1,121 @@ +import type { CellRange } from '@visactor/vtable/es/ts-types'; +import type { BaseTableAPI } from '@visactor/vtable/es/ts-types/base-table'; +import * as VTable from '@visactor/vtable'; +import type { TableEvents } from '@visactor/vtable/src/core/TABLE_EVENT_TYPE'; +import type { EventArg } from './types'; +//备用 插件配置项 目前感觉都走默认逻辑就行 +export type IExcelEditCellKeyboardPluginOptions = { + // 是否响应删除 + // enableDeleteKey?: boolean; +}; + +export class ExcelEditCellKeyboardPlugin implements VTable.plugins.IVTablePlugin { + id = 'excel-edit-cell-keyboard'; + runTime = [VTable.TABLE_EVENT_TYPE.INITIALIZED]; + table: VTable.ListTable; + pluginOptions: IExcelEditCellKeyboardPluginOptions; + constructor(pluginOptions?: IExcelEditCellKeyboardPluginOptions) { + this.pluginOptions = pluginOptions; + + this.bindEvent(); + } + run(...args: [EventArg, TableEvents[keyof TableEvents] | TableEvents[keyof TableEvents][], VTable.BaseTableAPI]) { + const table: VTable.BaseTableAPI = args[2]; + this.table = table as VTable.ListTable; + } + + bindEvent() { + //监听document全局的keydown事件 捕获阶段监听 可以及时阻止事件传播和默认行为 + document.addEventListener('keydown', this.handleKeyDown.bind(this), true); + // this.table.on('selected_cell', () => { + // this.updateHighlight(); + // }); + + // this.table.on('selected_clear', () => { + // this.clearHighlight(); + // }); + + // this.table.on('mousemove_table', () => { + // if (this.table.stateManager.select.selecting) { + // this.updateHighlight(); + // } + // }); + } + handleKeyDown(event: KeyboardEvent) { + if (this.table.editorManager) { + //判断是键盘触发编辑单元格的情况下,那么在编辑状态中切换方向需要选中下一个继续编辑 + if (this.table.editorManager.beginTriggerEditCellMode === 'keydown') { + if (this.table.editorManager.editingEditor && this.isExcelShortcutKey(event)) { + const { col, row } = this.table.editorManager.editCell; + this.table.editorManager.completeEdit(); + this.table.getElement().focus(); + if (!event.shiftKey && !event.ctrlKey && !event.metaKey) { + //有这些配合键,则不进行选中下一个单元格的行为 执行vtable内部逻辑 + if (event.key === 'Enter') { + this.table.selectCell(col, row + 1); + } else if (event.key === 'Tab') { + this.table.selectCell(col + 1, row); + } else if (event.key === 'ArrowLeft') { + this.table.selectCell(col - 1, row); + } else if (event.key === 'ArrowRight') { + this.table.selectCell(col + 1, row); + } else if (event.key === 'ArrowDown') { + this.table.selectCell(col, row + 1); + } else if (event.key === 'ArrowUp') { + this.table.selectCell(col, row - 1); + } + // 阻止事件传播和默认行为 + event.stopPropagation(); + event.preventDefault(); + } + } + } else { + const { col, row } = this.table.stateManager.select.cellPos; + if (this.table.editorManager.editingEditor && (event.key === 'Enter' || event.key === 'Tab')) { + this.table.editorManager.completeEdit(); + this.table.getElement().focus(); + if (event.key === 'Enter') { + this.table.selectCell(col, row + 1); + } else if (event.key === 'Tab') { + this.table.selectCell(col + 1, row); + } + // 阻止事件传播和默认行为 + event.stopPropagation(); + event.preventDefault(); + } else if (event.key === 'Delete') { + //响应删除键,删除 + const selectCells = this.table.getSelectedCellInfos(); + if (selectCells?.length > 0) { + // 如果选中的是范围,则删除范围内的所有单元格 + deleteSelectRange(selectCells, this.table); + } + // 阻止事件传播和默认行为 + event.stopPropagation(); + event.preventDefault(); + } + } + } + } + // 判断event的keyCode是否是excel的快捷键 + isExcelShortcutKey(event: KeyboardEvent) { + return ( + event.key === 'Enter' || + event.key === 'Tab' || + event.key === 'ArrowLeft' || + event.key === 'ArrowRight' || + event.key === 'ArrowDown' || + event.key === 'ArrowUp' + ); + } + release() { + document.removeEventListener('keydown', this.handleKeyDown, true); + } +} +//将选中单元格的值设置为空 +function deleteSelectRange(selectCells: VTable.TYPES.CellInfo[][], tableInstance: VTable.ListTable) { + for (let i = 0; i < selectCells.length; i++) { + for (let j = 0; j < selectCells[i].length; j++) { + tableInstance.changeCellValue(selectCells[i][j].col, selectCells[i][j].row, ''); + } + } +} diff --git a/packages/vtable-plugins/src/focus-highlight.ts b/packages/vtable-plugins/src/focus-highlight.ts new file mode 100644 index 0000000000..98e3b956a3 --- /dev/null +++ b/packages/vtable-plugins/src/focus-highlight.ts @@ -0,0 +1,155 @@ +import type { INode } from '@visactor/vtable/es/vrender'; +import { createRect } from '@visactor/vtable/es/vrender'; +import type { Group } from '@visactor/vtable/es/scenegraph/graphic/group'; +import { isSameRange } from '@visactor/vtable/es/tools/cell-range'; +import type { CellAddress, CellRange } from '@visactor/vtable/es/ts-types'; +import type { BaseTableAPI } from '@visactor/vtable/es/ts-types/base-table'; +import { cellInRange } from '@visactor/vtable/es/tools/helper'; +import { TABLE_EVENT_TYPE } from '@visactor/vtable'; +import type * as VTable from '@visactor/vtable'; +export interface FocusHighlightPluginOptions { + fill?: string; + opacity?: number; + highlightRange?: CellAddress | CellRange; //初始化聚焦高亮范围 +} + +export class FocusHighlightPlugin implements VTable.plugins.IVTablePlugin { + id = 'focus-highlight'; + name = 'Focus Highlight'; + runTime = [TABLE_EVENT_TYPE.INITIALIZED, TABLE_EVENT_TYPE.SELECTED_CELL, TABLE_EVENT_TYPE.SELECTED_CLEAR]; + table: BaseTableAPI; + range?: CellRange; + pluginOptions: FocusHighlightPluginOptions; + + constructor( + options: FocusHighlightPluginOptions = { + fill: '#000', + opacity: 0.5, + highlightRange: undefined + } + ) { + this.pluginOptions = Object.assign( + { + fill: '#000', + opacity: 0.5 + }, + options + ); + } + run(...args: any[]) { + if (!this.table) { + this.table = args[2] as BaseTableAPI; + } + if (args[1] === TABLE_EVENT_TYPE.INITIALIZED) { + this.pluginOptions.highlightRange && this.setFocusHighlightRange(this.pluginOptions.highlightRange); + } else if (args[1] === TABLE_EVENT_TYPE.SELECTED_CELL) { + const posCell = this.table.stateManager.select.cellPos; + if (this.table.isHeader(posCell.col, posCell.row)) { + this.setFocusHighlightRange(undefined); + } else { + const ranges = this.table.stateManager.select.ranges; + const min_col = 0; + const max_col = this.table.colCount - 1; + const min_row = Math.min(ranges[0].start.row, ranges[0].end.row); + const max_row = Math.max(ranges[0].start.row, ranges[0].end.row); + this.setFocusHighlightRange({ + start: { col: min_col, row: min_row }, + end: { col: max_col, row: max_row } + }); + } + } else if (args[1] === TABLE_EVENT_TYPE.SELECTED_CLEAR) { + this.setFocusHighlightRange(undefined); + } + } + + setFocusHighlightRange(range?: CellAddress | CellRange) { + let cellRange: CellRange; + if (range && 'start' in range && 'end' in range) { + cellRange = range as CellRange; + } else if (range) { + cellRange = { + start: range as CellAddress, + end: range as CellAddress + }; + } + if (isSameRange(this.range, cellRange)) { + return; + } + + this.range = cellRange; + if (!range) { + // reset highlight + this.deleteAllCellGroupShadow(); + } else { + // update highlight + this.updateCellGroupShadow(); + } + + this.table.scenegraph.updateNextFrame(); + } + + deleteAllCellGroupShadow() { + if (!this.table.isPivotTable()) { + this.updateCellGroupShadowInContainer(this.table.scenegraph.rowHeaderGroup); + this.updateCellGroupShadowInContainer(this.table.scenegraph.leftBottomCornerGroup); + } + this.updateCellGroupShadowInContainer(this.table.scenegraph.bodyGroup); + this.updateCellGroupShadowInContainer(this.table.scenegraph.rightFrozenGroup); + this.updateCellGroupShadowInContainer(this.table.scenegraph.bottomFrozenGroup); + this.updateCellGroupShadowInContainer(this.table.scenegraph.rightBottomCornerGroup); + } + + updateCellGroupShadow() { + if (!this.table.isPivotTable()) { + this.updateCellGroupShadowInContainer(this.table.scenegraph.rowHeaderGroup, this.range); + this.updateCellGroupShadowInContainer(this.table.scenegraph.leftBottomCornerGroup, this.range); + } + this.updateCellGroupShadowInContainer(this.table.scenegraph.bodyGroup, this.range); + this.updateCellGroupShadowInContainer(this.table.scenegraph.rightFrozenGroup, this.range); + this.updateCellGroupShadowInContainer(this.table.scenegraph.bottomFrozenGroup), this.range; + this.updateCellGroupShadowInContainer(this.table.scenegraph.rightBottomCornerGroup, this.range); + } + updateCellGroupShadowInContainer(container: Group, range?: CellAddress | CellRange) { + let cellRange: CellRange; + if (range && 'start' in range && 'end' in range) { + cellRange = range; + } else if (range) { + cellRange = { + start: range as CellAddress, + end: range as CellAddress + }; + } + container.forEachChildrenSkipChild((item: INode) => { + const column = item as unknown as Group; + if (column.role === 'column') { + column.forEachChildrenSkipChild((item: INode) => { + const cell = item as unknown as Group; + if (cell.role !== 'cell') { + return; + } + cell.attachShadow(cell.shadowRoot); + const shadowGroup = cell.shadowRoot; + if (!cellRange) { + // no highlight + shadowGroup.removeAllChild(); + } else if (cellInRange(cellRange, cell.col, cell.row)) { + // inside highlight + shadowGroup.removeAllChild(); + } else if (!shadowGroup.firstChild) { + // outside highlight + const shadowRect = createRect({ + x: 0, + y: 0, + width: cell.attribute.width, + height: cell.attribute.height, + fill: this.pluginOptions.fill, + opacity: this.pluginOptions.opacity + }); + shadowRect.name = 'shadow-rect'; + shadowGroup.appendChild(shadowRect); + } + }); + } + }); + } +} diff --git a/packages/vtable-plugins/src/header-highlight.ts b/packages/vtable-plugins/src/header-highlight.ts index 3d2c4fec9a..6b83c67778 100644 --- a/packages/vtable-plugins/src/header-highlight.ts +++ b/packages/vtable-plugins/src/header-highlight.ts @@ -10,6 +10,9 @@ export interface IHeaderHighlightPluginOptions { rowHighlightColor?: string; } +/** + * @deprecated 请使用 HighlightHeaderPlugin 插件 + */ export class HeaderHighlightPlugin { table: BaseTableAPI; options: IHeaderHighlightPluginOptions; diff --git a/packages/vtable-plugins/src/highlight-header-when-select-cell.ts b/packages/vtable-plugins/src/highlight-header-when-select-cell.ts new file mode 100644 index 0000000000..992c2171e5 --- /dev/null +++ b/packages/vtable-plugins/src/highlight-header-when-select-cell.ts @@ -0,0 +1,184 @@ +import type { CellRange } from '@visactor/vtable/es/ts-types'; +import { TABLE_EVENT_TYPE } from '@visactor/vtable'; +import type { BaseTableAPI, plugins } from '@visactor/vtable'; +interface IHighlightHeaderWhenSelectCellPluginOptions { + rowHighlight?: boolean; + colHighlight?: boolean; + colHighlightBGColor?: string; + colHighlightColor?: string; + rowHighlightBGColor?: string; + rowHighlightColor?: string; +} + +export class HighlightHeaderWhenSelectCellPlugin implements plugins.IVTablePlugin { + id = 'highlight-header-when-select-cell'; + runTime = [ + TABLE_EVENT_TYPE.INITIALIZED, + TABLE_EVENT_TYPE.SELECTED_CLEAR, + TABLE_EVENT_TYPE.SELECTED_CELL, + TABLE_EVENT_TYPE.MOUSEMOVE_TABLE + ]; + table: BaseTableAPI; + pluginOptions: IHighlightHeaderWhenSelectCellPluginOptions; + colHeaderRanges: CellRange[] = []; + rowHeaderRanges: CellRange[] = []; + constructor(pluginOptions: IHighlightHeaderWhenSelectCellPluginOptions) { + this.pluginOptions = pluginOptions; + } + run(...args: any[]) { + // const eventArgs = args[0]; + const runTime = args[1]; + const table: BaseTableAPI = args[2]; + this.table = table; + if (runTime === TABLE_EVENT_TYPE.SELECTED_CLEAR) { + this.clearHighlight(); + } else if (runTime === TABLE_EVENT_TYPE.SELECTED_CELL) { + this.updateHighlight(); + } else if (runTime === TABLE_EVENT_TYPE.MOUSEMOVE_TABLE) { + this.updateHighlight(); + } else if (runTime === TABLE_EVENT_TYPE.INITIALIZED) { + this.registerStyle(); + } + } + + registerStyle() { + this.table.registerCustomCellStyle('col-highlight', { + bgColor: this.pluginOptions?.colHighlightBGColor ?? '#82b2f5', + color: this.pluginOptions?.colHighlightColor ?? '#FFF' + }); + + this.table.registerCustomCellStyle('row-highlight', { + bgColor: this.pluginOptions?.rowHighlightBGColor ?? '#82b2f5', + color: this.pluginOptions?.rowHighlightColor ?? 'yellow' + }); + } + + clearHighlight() { + if (this.colHeaderRanges) { + this.colHeaderRanges.forEach(range => { + this.table.arrangeCustomCellStyle({ range }, undefined); + }); + } + if (this.rowHeaderRanges) { + this.rowHeaderRanges.forEach(range => { + this.table.arrangeCustomCellStyle({ range }, undefined); + }); + } + // clear range + this.colHeaderRanges = []; + this.rowHeaderRanges = []; + } + + updateHighlight() { + if (this.pluginOptions?.colHighlight === false && this.pluginOptions?.rowHighlight === false) { + return; + } + const selectRanges = this.table.getSelectedCellRanges(); + if (selectRanges.length < 2) { + this.clearHighlight(); + // return; + } + for (let i = 0; i < selectRanges.length; i++) { + const selectRange = selectRanges[i]; + const rowSelectRange = [selectRange.start.row, selectRange.end.row]; + rowSelectRange.sort((a, b) => a - b); // sort + const colSelectRange = [selectRange.start.col, selectRange.end.col]; + colSelectRange.sort((a, b) => a - b); // sort + + let colHeaderRange: CellRange; + let rowHeaderRange: CellRange; + if (this.table.isPivotTable()) { + colHeaderRange = { + start: { + col: colSelectRange[0], + row: 0 + }, + end: { + col: colSelectRange[1], + row: this.table.columnHeaderLevelCount - 1 + } + }; + rowHeaderRange = { + start: { + col: 0, + row: rowSelectRange[0] + }, + end: { + col: this.table.rowHeaderLevelCount - 1, + row: rowSelectRange[1] + } + }; + } else if (this.table.internalProps.transpose) { + rowHeaderRange = { + start: { + col: 0, + row: rowSelectRange[0] + }, + end: { + col: this.table.rowHeaderLevelCount - 1, + row: rowSelectRange[1] + } + }; + } else { + colHeaderRange = { + start: { + col: colSelectRange[0], + row: 0 + }, + end: { + col: colSelectRange[1], + row: this.table.columnHeaderLevelCount - 1 + } + }; + if (this.table.internalProps.rowSeriesNumber) { + rowHeaderRange = { + start: { + col: 0, + row: rowSelectRange[0] + }, + end: { + col: 0, + row: rowSelectRange[1] + } + }; + } + } + + if ( + this.pluginOptions?.colHighlight !== false && + !this.colHeaderRanges.find(range => isSameRange(range, colHeaderRange)) + ) { + // this.colHeaderRanges && this.table.arrangeCustomCellStyle({ range: this.colHeaderRanges }, undefined); + colHeaderRange && this.table.arrangeCustomCellStyle({ range: colHeaderRange }, 'col-highlight'); + this.colHeaderRanges.push(colHeaderRange); + } + + if ( + this.pluginOptions?.rowHighlight !== false && + !this.rowHeaderRanges.find(range => isSameRange(range, rowHeaderRange)) + ) { + // this.rowHeaderRanges && this.table.arrangeCustomCellStyle({ range: this.rowHeaderRanges }, undefined); + rowHeaderRange && this.table.arrangeCustomCellStyle({ range: rowHeaderRange }, 'row-highlight'); + this.rowHeaderRanges.push(rowHeaderRange); + } + } + } + release() { + this.rowHeaderRanges = []; + this.colHeaderRanges = []; + } +} + +function isSameRange(a: CellRange | undefined, b: CellRange | undefined) { + if (a === undefined && b === undefined) { + return true; + } + + if (a === undefined || b === undefined) { + return false; + } + + return ( + a.start.col === b.start.col && a.start.row === b.start.row && a.end.col === b.end.col && a.end.row === b.end.row + ); +} diff --git a/packages/vtable-plugins/src/index.ts b/packages/vtable-plugins/src/index.ts index dfb38a632a..90b20be375 100644 --- a/packages/vtable-plugins/src/index.ts +++ b/packages/vtable-plugins/src/index.ts @@ -1,3 +1,11 @@ export * from './carousel-animation'; export * from './invert-highlight'; export * from './header-highlight'; +export * from './add-row-column'; +export * from './column-series'; +export * from './row-series'; +export * from './highlight-header-when-select-cell'; +export * from './excel-edit-cell-keyboard'; +export * from './types'; +export * from './focus-highlight'; +export * from './table-carousel-animation'; diff --git a/packages/vtable-plugins/src/invert-highlight.ts b/packages/vtable-plugins/src/invert-highlight.ts index c5f5fd13b5..6047c7e0a3 100644 --- a/packages/vtable-plugins/src/invert-highlight.ts +++ b/packages/vtable-plugins/src/invert-highlight.ts @@ -11,6 +11,9 @@ export interface InvertHighlightPluginOptions { opacity?: number; } +/** + * @deprecated 请使用 FocusHighlightPlugin 插件 + */ export class InvertHighlightPlugin { table: BaseTableAPI; range?: CellRange; diff --git a/packages/vtable-plugins/src/row-series.ts b/packages/vtable-plugins/src/row-series.ts new file mode 100644 index 0000000000..6e730577aa --- /dev/null +++ b/packages/vtable-plugins/src/row-series.ts @@ -0,0 +1,68 @@ +import { TABLE_EVENT_TYPE } from '@visactor/vtable'; +import type { TYPES, BaseTableAPI, ListTable, ListTableConstructorOptions, plugins } from '@visactor/vtable'; +/** + * 添加行和列的插件的配置选项 + */ +export interface RowSeriesOptions { + rowCount: number; + fillRowRecord?: (index: number) => any; + rowSeriesNumber?: TYPES.IRowSeriesNumber; + /** + * 是否自动扩展行 + * @default true + */ + autoExtendRow?: boolean; +} +/** + * 生成行序号标题的插件 + */ +export class RowSeriesPlugin implements plugins.IVTablePlugin { + id = 'row-series'; + runTime = [TABLE_EVENT_TYPE.BEFORE_INIT, TABLE_EVENT_TYPE.BEFORE_KEYDOWN]; + pluginOptions: RowSeriesOptions; + table: ListTable; + + constructor(pluginOptions: RowSeriesOptions) { + this.pluginOptions = Object.assign({ rowCount: 100, autoExtendRow: true }, pluginOptions); + } + run(...args: any[]) { + if (args[1] === TABLE_EVENT_TYPE.BEFORE_INIT) { + const eventArgs = args[0]; + const table: BaseTableAPI = args[2]; + this.table = table as ListTable; + const options: ListTableConstructorOptions = eventArgs.options; + const records = options.records ?? []; + //用空数据将records填充到pluginOptions.rowCount + for (let i = records.length; i < this.pluginOptions.rowCount; i++) { + records.push(this.pluginOptions.fillRowRecord ? this.pluginOptions.fillRowRecord(i) : {}); + } + options.records = records; + if (this.pluginOptions.rowSeriesNumber) { + options.rowSeriesNumber = this.pluginOptions.rowSeriesNumber; + if (!this.pluginOptions.rowSeriesNumber.width) { + options.rowSeriesNumber.width = 'auto'; + } + } else if (!options.rowSeriesNumber) { + options.rowSeriesNumber = { + width: 'auto' + }; + } + } else if (args[1] === TABLE_EVENT_TYPE.BEFORE_KEYDOWN) { + const eventArgs = args[0]; + + const e = eventArgs.event; + if (e.key === 'ArrowDown') { + if ( + this.pluginOptions.autoExtendRow && + this.table.stateManager.select.cellPos.row === this.table.rowCount - 1 + ) { + (this.table as ListTable).addRecord( + this.pluginOptions.fillRowRecord + ? this.pluginOptions.fillRowRecord(this.table.rowCount - this.table.columnHeaderLevelCount) + : {} + ); + } + } + } + } +} diff --git a/packages/vtable-plugins/src/table-carousel-animation.ts b/packages/vtable-plugins/src/table-carousel-animation.ts new file mode 100644 index 0000000000..8777b5c73c --- /dev/null +++ b/packages/vtable-plugins/src/table-carousel-animation.ts @@ -0,0 +1,162 @@ +import type { EasingType } from '@visactor/vtable/es/vrender'; +import type { BaseTableAPI } from '@visactor/vtable/es/ts-types/base-table'; +import { TABLE_EVENT_TYPE } from '@visactor/vtable'; +import type * as VTable from '@visactor/vtable'; +function isInteger(value: number) { + return Math.floor(value) === value; +} + +export interface ITableCarouselAnimationPluginOptions { + rowCount?: number; + colCount?: number; + animationDuration?: number; + animationDelay?: number; + animationEasing?: EasingType; + autoPlay?: boolean; + autoPlayDelay?: number; + + customDistRowFunction?: (row: number, table: BaseTableAPI) => { distRow: number; animation?: boolean } | undefined; + customDistColFunction?: (col: number, table: BaseTableAPI) => { distCol: number; animation?: boolean } | undefined; +} + +export class TableCarouselAnimationPlugin implements VTable.plugins.IVTablePlugin { + id = 'table-carousel-animation'; + runTime = [TABLE_EVENT_TYPE.INITIALIZED]; + table: BaseTableAPI; + + rowCount: number; + colCount: number; + animationDuration: number; + animationDelay: number; + animationEasing: EasingType; + + playing: boolean; + row: number; + col: number; + willUpdateRow: boolean = false; + willUpdateCol: boolean = false; + autoPlay: boolean; + autoPlayDelay: number; + + customDistRowFunction?: (row: number, table: BaseTableAPI) => { distRow: number; animation?: boolean } | undefined; + customDistColFunction?: (col: number, table: BaseTableAPI) => { distCol: number; animation?: boolean } | undefined; + constructor(options: ITableCarouselAnimationPluginOptions = {}) { + this.rowCount = options?.rowCount ?? undefined; + this.colCount = options?.colCount ?? undefined; + this.animationDuration = options?.animationDuration ?? 500; + this.animationDelay = options?.animationDelay ?? 1000; + this.animationEasing = options?.animationEasing ?? 'linear'; + this.autoPlay = options?.autoPlay ?? false; + this.autoPlayDelay = options?.autoPlayDelay ?? 0; + this.customDistColFunction = options.customDistColFunction; + this.customDistRowFunction = options.customDistRowFunction; + } + run(...args: any[]) { + if (!this.table) { + this.table = args[2] as BaseTableAPI; + } + this.reset(); + + if (this.autoPlay) { + setTimeout(() => { + this.play(); + }, this.autoPlayDelay); + } + } + + reset() { + this.playing = false; + this.row = this.table.frozenRowCount; + this.col = this.table.frozenColCount; + } + + play() { + if (!this.table) { + throw new Error('table is not initialized'); + } + this.playing = true; + + if (this.rowCount && !this.willUpdateRow) { + this.updateRow(); + } else if (this.colCount && !this.willUpdateCol) { + this.updateCol(); + } + } + + pause() { + this.playing = false; + } + + updateRow() { + if (!this.playing || this.table.isReleased) { + return; + } + + let animation = true; + const customRow = this.customDistRowFunction && this.customDistRowFunction(this.row, this.table); + if (customRow) { + this.row = customRow.distRow; + animation = customRow.animation ?? true; + } else if (isInteger(this.row) && this.table.scenegraph.proxy.screenTopRow !== this.row) { + this.row = this.table.frozenRowCount; + animation = false; + } else if (!isInteger(this.row) && this.table.scenegraph.proxy.screenTopRow !== Math.floor(this.row)) { + this.row = this.table.frozenRowCount; + animation = false; + } else { + this.row += this.rowCount; + } + this.table.scrollToRow( + this.row, + animation ? { duration: this.animationDuration, easing: this.animationEasing } : undefined + ); + this.willUpdateRow = true; + setTimeout( + () => { + this.willUpdateRow = false; + this.updateRow(); + }, + // animation ? this.animationDuration + this.animationDelay : 0 + this.animationDuration + this.animationDelay + ); + } + + updateCol() { + if (!this.playing || this.table.isReleased) { + return; + } + + let animation = true; + const customCol = this.customDistColFunction && this.customDistColFunction(this.col, this.table); + if (customCol) { + this.col = customCol.distCol; + animation = customCol.animation ?? true; + } else if (isInteger(this.col) && this.table.scenegraph.proxy.screenLeftCol !== this.col) { + this.col = this.table.frozenColCount; + animation = false; + } else if (!isInteger(this.col) && this.table.scenegraph.proxy.screenLeftCol !== Math.floor(this.col)) { + this.col = this.table.frozenColCount; + animation = false; + } else { + this.col += this.colCount; + } + + this.table.scrollToCol( + this.col, + animation ? { duration: this.animationDuration, easing: this.animationEasing } : undefined + ); + + this.willUpdateCol = true; + setTimeout( + () => { + this.willUpdateCol = false; + this.updateCol(); + }, + // animation ? this.animationDuration + this.animationDelay : 0 + this.animationDuration + this.animationDelay + ); + } + release() { + // do nothing + } +} diff --git a/packages/vtable-plugins/src/types.ts b/packages/vtable-plugins/src/types.ts new file mode 100644 index 0000000000..3aaa64e1d8 --- /dev/null +++ b/packages/vtable-plugins/src/types.ts @@ -0,0 +1,4 @@ +// 定义一个接口,要求必须包含 event 属性 +export type EventArg = { + event: MouseEvent | KeyboardEvent; +}; diff --git a/packages/vtable-search/package.json b/packages/vtable-search/package.json index 46c96076a0..442db1412e 100644 --- a/packages/vtable-search/package.json +++ b/packages/vtable-search/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-search", - "version": "1.17.6", + "version": "1.18.0", "description": "The search util of VTable", "author": { "name": "VisActor", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index b3c55464ef..753232d089 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,53 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "1.18.0", + "tag": "@visactor/vtable_v1.18.0", + "date": "Thu, 17 Apr 2025 07:40:27 GMT", + "comments": { + "none": [ + { + "comment": "chore: release 1.18.0" + } + ] + } + }, + { + "version": "1.17.7", + "tag": "@visactor/vtable_v1.17.7", + "date": "Thu, 17 Apr 2025 06:30:44 GMT", + "comments": { + "none": [ + { + "comment": "feat: add onBeforeCacheChartImage event\n\n" + }, + { + "comment": "feat: support customConfig disableBuildInChartActive\n\n" + }, + { + "comment": "fix: fix table size in getCellsRect() #3681" + }, + { + "comment": "fix: correct column index calculation when rowSeriesNumber is configured\n\n" + }, + { + "comment": "fix: fix image flash problem #3588" + }, + { + "comment": "feat: add dynamicUpdateSelectionSize config in theme.selectionStyle" + }, + { + "comment": "fix: fix row/column update problem in text-stick #3744" + } + ], + "minor": [ + { + "comment": "fix: fix switch default direction #3667" + } + ] + } + }, { "version": "1.17.6", "tag": "@visactor/vtable_v1.17.6", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index ad902f033d..2669e11f84 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,36 @@ # Change Log - @visactor/vtable -This log was last generated on Thu, 10 Apr 2025 09:18:51 GMT and should not be manually modified. +This log was last generated on Thu, 17 Apr 2025 07:40:27 GMT and should not be manually modified. + +## 1.18.0 +Thu, 17 Apr 2025 07:40:27 GMT + +### Updates + +- chore: release 1.18.0 + +## 1.17.7 +Thu, 17 Apr 2025 06:30:44 GMT + +### Minor changes + +- fix: fix switch default direction #3667 + +### Updates + +- feat: add onBeforeCacheChartImage event + + +- feat: support customConfig disableBuildInChartActive + + +- fix: fix table size in getCellsRect() #3681 +- fix: correct column index calculation when rowSeriesNumber is configured + + +- fix: fix image flash problem #3588 +- feat: add dynamicUpdateSelectionSize config in theme.selectionStyle +- fix: fix row/column update problem in text-stick #3744 ## 1.17.6 Thu, 10 Apr 2025 09:18:51 GMT diff --git a/packages/vtable/examples/list/list-contextMenu-disabledMenu.ts b/packages/vtable/examples/list/list-contextMenu-disabledMenu.ts new file mode 100644 index 0000000000..c1c12a964c --- /dev/null +++ b/packages/vtable/examples/list/list-contextMenu-disabledMenu.ts @@ -0,0 +1,191 @@ +import * as VTable from '../../src'; +import { bindDebugTool } from '../../src/scenegraph/debug-tool'; +const CONTAINER_ID = 'vTable'; +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' + })); +}; + +export function createTable() { + const records = generatePersons(20); + const columns: VTable.ColumnsDefine = [ + { + field: 'id', + title: 'ID', + width: '1%', + minWidth: 200, + sort: true + }, + { + field: 'email1', + title: 'email', + width: 200, + sort: true + }, + { + title: 'full name', + columns: [ + { + field: 'name', + title: 'First Name', + width: 200 + }, + { + field: 'name', + title: 'Last Name', + width: 200 + } + ] + }, + + { + field: 'tel', + title: 'telephone', + width: 150 + }, + { + field: 'work', + title: 'job', + width: 200 + }, + { + field: 'city', + title: 'city', + width: 150 + } + ]; + const option: VTable.ListTableConstructorOptions = { + container: document.getElementById(CONTAINER_ID), + records, + columns, + tooltip: { + isShowOverflowTextTooltip: true + }, + pagination: { + perPageCount: 10, + currentPage: 0 + }, + + rowSeriesNumber: { + title: '行号', + dragOrder: true, + headerStyle: { + bgColor: '#EEF1F5', + borderColor: '#e1e4e8' + }, + style: { + borderColor: '#e1e4e8' + } + }, + menu: { + // contextMenuItems: ['向下插入数据', '向下插入空行', '修改掉整行值', '修改值', '删除该行'], + contextMenuItems: [ + { + text: '向下插入数据', + menuKey: 'insertData' + }, + { + text: '向下插入空行', + menuKey: 'insertRow' + }, + { + text: '修改掉整行值', + menuKey: 'modifyRow', + disabled: true + }, + { + text: '修改值', + menuKey: 'modifyCell' + }, + { + text: '删除该行', + menuKey: 'deleteRow', + children: [ + { + text: '删除1行', + menuKey: 'deleteRow1' + }, + { + text: '删除2行', + menuKey: 'deleteRow2', + disabled: true + } + ] + } + ], + dropDownMenuHighlight: [ + { + menuKey: 'se3' + } + ], + defaultHeaderMenuItems: [ + { + text: '刷新', + menuKey: 'refresh' + }, + { + text: '关闭', + menuKey: 'close', + disabled: true, + children: [ + { + text: '二级菜单1', + menuKey: 'se1', + disabled: true, + children: [ + { + text: '三级菜单1', + menuKey: 'se1-1' + }, + { + text: '三级菜单2', + menuKey: 'se1-2' + } + ] + }, + { + text: '二级菜单2', + menuKey: 'se2' + }, + { + text: '二级菜单3', + menuKey: 'se3' + } + ] + }, + { + text: '删除', + menuKey: 'delete' + } + ] + } + + // bottomFrozenRowCount: 1 + // autoWrapText: true, + // heightMode: 'autoHeight', + // widthMode: 'adaptive' + }; + const tableInstance = new VTable.ListTable(option); + window.tableInstance = tableInstance; + + // bindDebugTool(tableInstance.scenegraph.stage, { customGrapicKeys: ['col', 'row'] }); + // tableInstance.on('sort_click', args => { + // tableInstance.updateSortState( + // { + // field: args.field, + // order: Date.now() % 3 === 0 ? 'desc' : Date.now() % 3 === 1 ? 'asc' : 'normal' + // }, + // false + // ); + // return false; //return false代表不执行内部排序逻辑 + // }); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 88c4060648..43acadaad6 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -107,6 +107,11 @@ export const menus = [ path: 'list', name: 'list-100w' }, + { + path: 'list', + name: 'list-contextMenu-disabledMenu' + }, + { path: 'list', name: 'list-rowSeriesNumber' diff --git a/packages/vtable/examples/pivot-chart/pivotChart.ts b/packages/vtable/examples/pivot-chart/pivotChart.ts index f3090bf0c3..b7edbcda75 100644 --- a/packages/vtable/examples/pivot-chart/pivotChart.ts +++ b/packages/vtable/examples/pivot-chart/pivotChart.ts @@ -9422,10 +9422,10 @@ export function createTable() { cellBgColor: '' } }, - selectionStyle: { - cellBgColor: '', - cellBorderColor: '' - }, + // selectionStyle: { + // cellBgColor: '', + // cellBorderColor: '' + // }, frameStyle: { borderLineWidth: 0 }, @@ -9477,6 +9477,9 @@ export function createTable() { }; const tableInstance = new VTable.PivotChart(option); + tableInstance.on('before_cache_chart_image', args => { + console.log('before_cache_chart_image', args); + }); tableInstance.onVChartEvent('click', args => { console.log('onVChartEvent click', args); }); diff --git a/packages/vtable/examples/pivot/pivot-grid-tree.ts b/packages/vtable/examples/pivot/pivot-grid-tree.ts index 673a2899d3..1eb179cdfa 100644 --- a/packages/vtable/examples/pivot/pivot-grid-tree.ts +++ b/packages/vtable/examples/pivot/pivot-grid-tree.ts @@ -260,7 +260,24 @@ export function createTable() { title: '类别', headerFormat(value) { return `${value}`; - } + }, + + // corner菜单 + cornerDropDownMenu: [ + { + menuKey: '升序排序C', + text: '升序排序C', + disabled: true + }, + { + menuKey: '降序排序I', + text: '降序排序I' + }, + { + menuKey: '冻结列I', + text: '冻结列I' + } + ] // width: 200 } // { diff --git a/packages/vtable/package.json b/packages/vtable/package.json index bf25048a35..f1664253d8 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "1.17.6", + "version": "1.18.0", "description": "canvas table width high performance", "keywords": [ "grid", @@ -129,4 +129,4 @@ "url": "https://github.com/VisActor/VTable.git", "directory": "packages/vtable" } -} \ No newline at end of file +} diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index 67bc135294..10535e0417 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -104,18 +104,8 @@ export class ListTable extends BaseTable implements ListTableAPI { constructor(options: ListTableConstructorOptions); constructor(container: HTMLElement, options: ListTableConstructorOptions); constructor(container?: HTMLElement | ListTableConstructorOptions, options?: ListTableConstructorOptions) { - if (Env.mode === 'node') { - options = container as ListTableConstructorOptions; - container = null; - } else if (!(container instanceof HTMLElement)) { - options = container as ListTableConstructorOptions; - if ((container as ListTableConstructorOptions).container) { - container = (container as ListTableConstructorOptions).container; - } else { - container = null; - } - } super(container as HTMLElement, options); + options = this.options; const internalProps = this.internalProps; internalProps.frozenColDragHeaderMode = options.dragOrder?.frozenColDragHeaderMode ?? options.frozenColDragHeaderMode; @@ -249,6 +239,15 @@ export class ListTable extends BaseTable implements ListTableAPI { this.renderAsync(); this.eventManager.updateEventBinder(); } + /** + * 添加列 TODO: 需要优化 这个方法目前直接调用了updateColumns 可以避免调用 做优化性能 + * @param column + */ + addColumn(column: ColumnDefine) { + const columns = this.options.columns; + columns.push(column); + this.updateColumns(columns); + } get columns(): ColumnsDefine { // return this.internalProps.columns; return this.internalProps.layoutMap.columnTree.getCopiedTree(); //调整顺序后的columns diff --git a/packages/vtable/src/PivotChart.ts b/packages/vtable/src/PivotChart.ts index 24ca9d3c1a..fb85397e90 100644 --- a/packages/vtable/src/PivotChart.ts +++ b/packages/vtable/src/PivotChart.ts @@ -107,18 +107,8 @@ export class PivotChart extends BaseTable implements PivotChartAPI { constructor(options: PivotChartConstructorOptions); constructor(container: HTMLElement, options: PivotChartConstructorOptions); constructor(container?: HTMLElement | PivotChartConstructorOptions, options?: PivotChartConstructorOptions) { - if (Env.mode === 'node') { - options = container as PivotChartConstructorOptions; - container = null; - } else if (!(container instanceof HTMLElement)) { - options = container as PivotChartConstructorOptions; - if ((container as PivotChartConstructorOptions).container) { - container = (container as PivotChartConstructorOptions).container; - } else { - container = null; - } - } super(container as HTMLElement, options); + options = this.options; if ((options as any).layout) { //TODO hack处理之前的demo都是定义到layout上的 所以这里直接并到options中 Object.assign(options, (options as any).layout); diff --git a/packages/vtable/src/PivotTable.ts b/packages/vtable/src/PivotTable.ts index 970ad2f0d3..82fa46e15b 100644 --- a/packages/vtable/src/PivotTable.ts +++ b/packages/vtable/src/PivotTable.ts @@ -70,18 +70,8 @@ export class PivotTable extends BaseTable implements PivotTableAPI { constructor(options: PivotTableConstructorOptions); constructor(container: HTMLElement, options: PivotTableConstructorOptions); constructor(container?: HTMLElement | PivotTableConstructorOptions, options?: PivotTableConstructorOptions) { - if (Env.mode === 'node') { - options = container as PivotTableConstructorOptions; - container = null; - } else if (!(container instanceof HTMLElement)) { - options = container as PivotTableConstructorOptions; - if ((container as PivotTableConstructorOptions).container) { - container = (container as PivotTableConstructorOptions).container; - } else { - container = null; - } - } super(container as HTMLElement, options); + options = this.options; if (options) { if (!options.rowHierarchyType) { options.rowHierarchyType = 'grid'; diff --git a/packages/vtable/src/body-helper/style.ts b/packages/vtable/src/body-helper/style.ts index b82463ec65..e4011b331d 100644 --- a/packages/vtable/src/body-helper/style.ts +++ b/packages/vtable/src/body-helper/style.ts @@ -13,7 +13,7 @@ import { ImageStyle } from './style/ImageStyle'; import { TextStyle } from './style/MultilineTextStyle'; import { NumberStyle } from './style/NumberStyle'; import { Style } from './style/Style'; -import type { TableTheme } from '../themes/theme'; +import type { TableTheme } from '../themes/theme-define'; import { CheckboxStyle } from './style/CheckboxStyle'; import { RadioStyle } from './style/RadioStyle'; import { SwitchStyle } from './style/SwitchStyle'; diff --git a/packages/vtable/src/chartModule.ts b/packages/vtable/src/chartModule.ts index 06f9d4aee2..a44069e3a6 100644 --- a/packages/vtable/src/chartModule.ts +++ b/packages/vtable/src/chartModule.ts @@ -1,7 +1,7 @@ /* eslint-disable sort-imports */ import { extend } from './tools/helper'; -import { chartTypes as plugins } from './plugins/chartModules'; +export const chartTypes: { [key: string]: any } = {}; const builtin = {}; export function get(): { [key: string]: any } { - return extend(builtin, plugins); + return extend(builtin, chartTypes); } diff --git a/packages/vtable/src/components/axis/axis.ts b/packages/vtable/src/components/axis/axis.ts index 026a3862fa..2bd745a455 100644 --- a/packages/vtable/src/components/axis/axis.ts +++ b/packages/vtable/src/components/axis/axis.ts @@ -13,7 +13,7 @@ import type { IBaseScale } from '@visactor/vscale'; import { ticks } from '@src/vrender'; import { LinearAxisScale } from './linear-scale'; import { doOverlap } from './label-overlap'; -import type { TableTheme } from '../../themes/theme'; +import type { TableTheme } from '../../themes/theme-define'; const DEFAULT_BAND_INNER_PADDING = 0.1; const DEFAULT_BAND_OUTER_PADDING = 0.3; diff --git a/packages/vtable/src/components/menu/dom/logic/MenuElement.ts b/packages/vtable/src/components/menu/dom/logic/MenuElement.ts index 511c5605ce..c3a7618898 100644 --- a/packages/vtable/src/components/menu/dom/logic/MenuElement.ts +++ b/packages/vtable/src/components/menu/dom/logic/MenuElement.ts @@ -30,6 +30,7 @@ const TITLE_CLASSNAME = `${CLASSNAME}__title`; const ARROW_CLASSNAME = `${CLASSNAME}__arrow`; const NOEVENT_CLASSNAME = `${CLASSNAME}__no-event`; const ITEMTEXT_CLASSNAME = `${CLASSNAME}__item-text`; +const ITEM_DISABLED_CLASSNAME = `${CLASSNAME}__item-disabled`; function createMenuDomElement(): HTMLElement { const rootElement = createElement('div', [CLASSNAME, HIDDEN_CLASSNAME]); @@ -118,6 +119,11 @@ export class MenuElement { this._rootElement?.addEventListener('touchend', e => { e.stopPropagation(); e.preventDefault(); + + // disabled菜单项,禁用菜单点击 + if ((e.target as HTMLElement).classList.contains(ITEM_DISABLED_CLASSNAME)) { + return; + } if (this._rootElement.classList.contains(HIDDEN_CLASSNAME)) { return; } @@ -155,6 +161,11 @@ export class MenuElement { this._rootElement?.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); + + // disabled菜单项,禁用菜单点击 + if ((e.target as HTMLElement).classList.contains(ITEM_DISABLED_CLASSNAME)) { + return; + } if (this._rootElement.classList.contains(HIDDEN_CLASSNAME)) { return; } @@ -283,6 +294,11 @@ export class MenuElement { this._secondElement?.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); + + // disabled菜单项,禁用菜单点击 + if ((e.target as HTMLElement).classList.contains(ITEM_DISABLED_CLASSNAME)) { + return; + } if (this._secondElement.classList.contains(HIDDEN_CLASSNAME)) { return; } @@ -608,7 +624,7 @@ export class MenuElement { y: secondTop, width: secondWidth, height: secondHeight - } = rootElement.getBoundingClientRect(); + } = secondElement.getBoundingClientRect(); if ( x > secondLeft - 5 && x < secondLeft + secondWidth + 5 && @@ -628,6 +644,11 @@ function createItem(info: MenuListItem, isHighlight: boolean): HTMLDivElement { isHighlight ? SELECT_CLASSNAME : NORAML_CLASSNAME ]) as HTMLDivElement; + // 添加disabled样式 + if (typeof info === 'object' && info.disabled) { + itemContainer.classList.add(ITEM_DISABLED_CLASSNAME); + } + if (typeof info === 'string') { const item = createElement('span', [CONTENT_CLASSNAME, NOEVENT_CLASSNAME, ITEMTEXT_CLASSNAME]); item.innerHTML = info; diff --git a/packages/vtable/src/components/menu/dom/logic/MenuElementStyle.ts b/packages/vtable/src/components/menu/dom/logic/MenuElementStyle.ts index dfecb6fa1a..bd86ec0031 100644 --- a/packages/vtable/src/components/menu/dom/logic/MenuElementStyle.ts +++ b/packages/vtable/src/components/menu/dom/logic/MenuElementStyle.ts @@ -75,9 +75,18 @@ export function importStyle() { } .vtable__menu-element--select { color: #2E68CF; -}.vtable__menu-element--normal { +} +.vtable__menu-element--normal { color: rgba(20, 20, 20, 0.9);; } +.vtable__menu-element__item-disabled { + color: rgba(0, 0, 0, 0.25); + cursor: not-allowed; + background-color: #fff; +} +.vtable__menu-element__item-disabled:hover { + background-color: #fff; +} .vtable__menu-element__split { height: 0px; border: 1px solid rgb(209, 213, 218); diff --git a/packages/vtable/src/core/BaseTable.ts b/packages/vtable/src/core/BaseTable.ts index 35ed9a2ad5..cde6f93a35 100644 --- a/packages/vtable/src/core/BaseTable.ts +++ b/packages/vtable/src/core/BaseTable.ts @@ -62,7 +62,7 @@ import { EventHandler } from '../event/EventHandler'; import { EventTarget } from '../event/EventTarget'; import { NumberMap } from '../tools/NumberMap'; import { Rect } from '../tools/Rect'; -import type { TableTheme } from '../themes/theme'; +import type { TableTheme } from '../themes/theme-define'; import { throttle2 } from '../tools/util'; import themes from '../themes'; import { Env } from '../tools/env'; @@ -96,7 +96,6 @@ import type { SeriesNumberColumnData } from '../ts-types/list-table/layout-map/api'; import type { TooltipOptions } from '../ts-types/tooltip'; -import { IconCache } from '../plugins/icons'; import { _applyColWidthLimits, _getScrollableVisibleRect, @@ -158,6 +157,8 @@ import type { CustomCellStylePlugin, ICustomCellStylePlugin } from '../plugins/c import { isCellDisableSelect } from '../state/select/is-cell-select-highlight'; import { getCustomMergeCellFunc } from './utils/get-custom-merge-cell-func'; import { vglobal } from '@src/vrender'; +import { PluginManager } from '../plugins/plugin-manager'; +import type { IVTablePlugin } from '../plugins/interface'; const { toBoxArray } = utilStyle; const { isTouchEvent } = event; @@ -233,12 +234,28 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { reactCustomLayout?: ReactCustomLayout; _hasAutoImageColumn?: boolean; + pluginManager: PluginManager; constructor(container: HTMLElement, options: BaseTableConstructorOptions = {}) { super(); + + if (Env.mode === 'node') { + options = container as BaseTableConstructorOptions; + container = null; + } else if (!(container instanceof HTMLElement)) { + options = container as BaseTableConstructorOptions; + if ((container as BaseTableConstructorOptions).container) { + container = (container as BaseTableConstructorOptions).container; + } else { + container = null; + } + } if (!container && options.mode !== 'node' && !options.canvas) { throw new Error("vtable's container is undefined"); } + this.pluginManager = new PluginManager(this, options); + this.fireListeners(TABLE_EVENT_TYPE.BEFORE_INIT, { options, container }); + container = options.container && options.container instanceof HTMLElement ? options.container : container; // for image anonymous if (options.customConfig?.imageAnonymous === false) { vglobal.isImageAnonymous = false; @@ -1167,6 +1184,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { height - ((lineWidths[0] ?? 0) + (shadowWidths[0] ?? 0)) - ((lineWidths[2] ?? 0) + (shadowWidths[2] ?? 0)); } } + + this._clearColRangeWidthsMap(); + this._clearRowRangeHeightsMap(); } updateViewBox(newViewBox: IBoundsLike) { @@ -1866,6 +1886,9 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { let absoluteLeft = this.getColsWidth(0, startCol - 1) || 0; // startCol为0时,absoluteLeft计算为Nan let width = this.getColsWidth(startCol, endCol); const scrollLeft = this.scrollLeft; + + const tableWidth = Math.min(this.tableNoFrameWidth, this.getAllColsWidth()); + const tableHeight = Math.min(this.tableNoFrameHeight, this.getAllRowsHeight()); if (this.isLeftFrozenColumn(startCol) && this.isRightFrozenColumn(endCol)) { width = this.tableNoFrameWidth - (this.getColsWidth(startCol + 1, this.colCount - 1) ?? 0) - absoluteLeft; // width = @@ -1875,10 +1898,10 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } else if (this.isLeftFrozenColumn(startCol) && !this.isLeftFrozenColumn(endCol)) { width = Math.max(width - scrollLeft, this.getColsWidth(startCol, this.frozenColCount - 1)); } else if (!this.isRightFrozenColumn(startCol) && this.isRightFrozenColumn(endCol)) { - absoluteLeft = Math.min(absoluteLeft - scrollLeft, this.tableNoFrameWidth - this.getRightFrozenColsWidth()); - width = this.tableNoFrameWidth - (this.getColsWidth(startCol + 1, this.colCount - 1) ?? 0) - absoluteLeft; + absoluteLeft = Math.min(absoluteLeft - scrollLeft, tableWidth - this.getRightFrozenColsWidth()); + width = tableWidth - (this.getColsWidth(startCol + 1, this.colCount - 1) ?? 0) - absoluteLeft; } else if (this.isRightFrozenColumn(startCol)) { - absoluteLeft = this.tableNoFrameWidth - (this.getColsWidth(startCol, this.colCount - 1) ?? 0); + absoluteLeft = tableWidth - (this.getColsWidth(startCol, this.colCount - 1) ?? 0); } else { // 范围全部在整体一块区域 如都在右侧冻结区域 都可以走这块逻辑 // do nothing @@ -1896,10 +1919,10 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } else if (this.isTopFrozenRow(startRow) && !this.isTopFrozenRow(endRow)) { height = Math.max(height - scrollTop, this.getRowsHeight(startRow, this.frozenRowCount - 1)); } else if (!this.isBottomFrozenRow(startRow) && this.isBottomFrozenRow(endRow)) { - absoluteTop = Math.min(absoluteTop - scrollTop, this.tableNoFrameHeight - this.getBottomFrozenRowsHeight()); - height = this.tableNoFrameHeight - (this.getRowsHeight(startRow + 1, this.rowCount - 1) ?? 0) - absoluteTop; + absoluteTop = Math.min(absoluteTop - scrollTop, tableHeight - this.getBottomFrozenRowsHeight()); + height = tableHeight - (this.getRowsHeight(startRow + 1, this.rowCount - 1) ?? 0) - absoluteTop; } else if (this.isBottomFrozenRow(startRow)) { - absoluteTop = this.tableNoFrameHeight - (this.getRowsHeight(startRow, this.rowCount - 1) ?? 0); + absoluteTop = tableHeight - (this.getRowsHeight(startRow, this.rowCount - 1) ?? 0); } else { // 范围全部在整体一块区域 如都在右侧冻结区域 都可以走这块逻辑 // do nothing @@ -2286,7 +2309,6 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { } internalProps.tooltipHandler?.release?.(); internalProps.menuHandler?.release?.(); - IconCache.clearAll(); super.release?.(); internalProps.handler?.release?.(); @@ -2320,6 +2342,7 @@ export abstract class BaseTable extends EventTarget implements BaseTableAPI { this.internalProps = null; this.reactCustomLayout?.clearCache(); + this.pluginManager.release(); clearChartRenderQueue(); } diff --git a/packages/vtable/src/core/TABLE_EVENT_TYPE.ts b/packages/vtable/src/core/TABLE_EVENT_TYPE.ts index 66a2929d1d..a32b012048 100644 --- a/packages/vtable/src/core/TABLE_EVENT_TYPE.ts +++ b/packages/vtable/src/core/TABLE_EVENT_TYPE.ts @@ -25,8 +25,11 @@ export interface TableEvents { * 单元格选中状态改变事件 */ SELECTED_CLEAR: 'selected_clear'; + + /** 键盘按下事件 内部逻辑处理前 */ + BEFORE_KEYDOWN: 'before_keydown'; /** - * 键盘按下事件 + * 键盘按下事件 触发时机是在内部处理keydown逻辑之后 */ KEYDOWN: 'keydown'; /** @@ -158,12 +161,17 @@ export interface TableEvents { RADIO_STATE_CHANGE: 'radio_state_change'; SWITCH_STATE_CHANGE: 'switch_state_change'; //#region lifecircle + /** 表格实例初始化前触发 */ + BEFORE_INIT: 'before_init'; + /** 设置表格大小前触发 */ + BEFORE_SET_SIZE: 'before_set_size'; /** 每次渲染完成触发 */ AFTER_RENDER: 'after_render'; /** 表格实例初始化完成 */ INITIALIZED: 'initialized'; //#endregion + /** 编辑单元格 */ CHANGE_CELL_VALUE: 'change_cell_value'; /** @@ -192,6 +200,10 @@ export interface TableEvents { * 按钮点击事件 */ BUTTON_CLICK: 'button_click'; + /** + * 缓存图表事件 + */ + BEFORE_CACHE_CHART_IMAGE: 'before_cache_chart_image'; } /** * Table event types @@ -203,6 +215,7 @@ export const TABLE_EVENT_TYPE: TableEvents = { MOUSEUP_CELL: 'mouseup_cell', SELECTED_CELL: 'selected_cell', SELECTED_CLEAR: 'selected_clear', + BEFORE_KEYDOWN: 'before_keydown', KEYDOWN: 'keydown', MOUSEENTER_TABLE: 'mouseenter_table', MOUSELEAVE_TABLE: 'mouseleave_table', @@ -255,6 +268,8 @@ export const TABLE_EVENT_TYPE: TableEvents = { CHECKBOX_STATE_CHANGE: 'checkbox_state_change', RADIO_STATE_CHANGE: 'radio_state_change', SWITCH_STATE_CHANGE: 'switch_state_change', + BEFORE_SET_SIZE: 'before_set_size', + BEFORE_INIT: 'before_init', AFTER_RENDER: 'after_render', INITIALIZED: 'initialized', CHANGE_CELL_VALUE: 'change_cell_value', @@ -265,5 +280,6 @@ export const TABLE_EVENT_TYPE: TableEvents = { EMPTY_TIP_CLICK: 'empty_tip_click', EMPTY_TIP_DBLCLICK: 'empty_tip_dblclick', - BUTTON_CLICK: 'button_click' + BUTTON_CLICK: 'button_click', + BEFORE_CACHE_CHART_IMAGE: 'before_cache_chart_image' } as TableEvents; diff --git a/packages/vtable/src/core/tableHelper.ts b/packages/vtable/src/core/tableHelper.ts index 1a41bccd9c..c8695a2ef1 100644 --- a/packages/vtable/src/core/tableHelper.ts +++ b/packages/vtable/src/core/tableHelper.ts @@ -368,25 +368,43 @@ export function getCellCornerRadius(col: number, row: number, table: BaseTableAP const tableCornerRadius = table.theme.frameStyle.cornerRadius; if (table.theme.cellInnerBorder) { if (Array.isArray(tableCornerRadius)) { + const radius = [0, 0, 0, 0]; if (col === 0 && row === 0) { - return [tableCornerRadius[0], 0, 0, 0]; - } else if (col === table.colCount - 1 && row === 0) { - return [0, tableCornerRadius[1], 0, 0]; - } else if (col === 0 && row === table.rowCount - 1) { - return [0, 0, 0, tableCornerRadius[3]]; - } else if (col === table.colCount - 1 && row === table.rowCount - 1) { - return [0, 0, tableCornerRadius[2], 0]; + // return [tableCornerRadius[0], 0, 0, 0]; + radius[0] = tableCornerRadius[0]; } + if (col === table.colCount - 1 && row === 0) { + // return [0, tableCornerRadius[1], 0, 0]; + radius[1] = tableCornerRadius[1]; + } + if (col === 0 && row === table.rowCount - 1) { + // return [0, 0, 0, tableCornerRadius[3]]; + radius[3] = tableCornerRadius[3]; + } + if (col === table.colCount - 1 && row === table.rowCount - 1) { + // return [0, 0, tableCornerRadius[2], 0]; + radius[2] = tableCornerRadius[2]; + } + return radius; } else if (tableCornerRadius) { + const radius = [0, 0, 0, 0]; if (col === 0 && row === 0) { - return [tableCornerRadius, 0, 0, 0]; - } else if (col === table.colCount - 1 && row === 0) { - return [0, tableCornerRadius, 0, 0]; - } else if (col === 0 && row === table.rowCount - 1) { - return [0, 0, 0, tableCornerRadius]; - } else if (col === table.colCount - 1 && row === table.rowCount - 1) { - return [0, 0, tableCornerRadius, 0]; + // return [tableCornerRadius, 0, 0, 0]; + radius[0] = tableCornerRadius; + } + if (col === table.colCount - 1 && row === 0) { + // return [0, tableCornerRadius, 0, 0]; + radius[1] = tableCornerRadius; + } + if (col === 0 && row === table.rowCount - 1) { + // return [0, 0, 0, tableCornerRadius]; + radius[3] = tableCornerRadius; + } + if (col === table.colCount - 1 && row === table.rowCount - 1) { + // return [0, 0, tableCornerRadius, 0]; + radius[2] = tableCornerRadius; } + return radius; } } return 0; diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index c181c763fe..9c6e8dbc60 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -95,6 +95,11 @@ export function getField( return record.then((r: any) => getField(r, field, col, row, table, promiseCallBack)); } const fieldGet: any = isFieldAssessor(field) ? field.get : field; + // 如果fieldGet为undefined或'' 并且record是数组 则取值逻辑按照colIndex取数组值 返回record[col - table.leftRowSeriesNumberCount] + if ((fieldGet === undefined || fieldGet === '') && Array.isArray(record)) { + const colIndex = col - table.leftRowSeriesNumberCount; + return record[colIndex]; + } if (isObject(record) && fieldGet in (record as any)) { const fieldResult = (record as any)[fieldGet]; @@ -757,6 +762,10 @@ export class DataSource extends EventTarget implements DataSourceAPI { const dataIndex = this.getIndexKey(index); this.cacheBeforeChangedRecord(dataIndex, table); + // 如果field为undefined或'' 按照colIndex取数组值 + if (field === undefined || field === '') { + field = col - table.leftRowSeriesNumberCount; + } if (typeof field === 'string' || typeof field === 'number') { const beforeChangedValue = this.beforeChangedRecordsMap.get(dataIndex.toString())?.[field as any]; // this.getOriginalField(index, field, col, row, table); const record = this.getOriginalRecord(dataIndex); @@ -767,7 +776,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { if (isPromise(record)) { record .then(record => { - record[field] = formatValue; + record[field as string | number] = formatValue; }) .catch((err: Error) => { console.error('VTable Error:', err); diff --git a/packages/vtable/src/edit/edit-manager.ts b/packages/vtable/src/edit/edit-manager.ts index 931287127f..ef4ebe7ebe 100644 --- a/packages/vtable/src/edit/edit-manager.ts +++ b/packages/vtable/src/edit/edit-manager.ts @@ -13,7 +13,7 @@ export class EditManager { isValidatingValue: boolean = false; editCell: { col: number; row: number }; listenersId: number[] = []; - + beginTriggerEditCellMode: 'doubleclick' | 'click' | 'keydown'; constructor(table: BaseTableAPI) { this.table = table; this.bindEvent(); @@ -43,12 +43,14 @@ export class EditManager { // 如果是双击自动列宽 则编辑不开启 return; } + this.beginTriggerEditCellMode = 'doubleclick'; this.startEditCell(col, row); }); const clickEventId = table.on(TABLE_EVENT_TYPE.CLICK_CELL, e => { const { editCellTrigger = 'doubleclick' } = table.options; if (editCellTrigger === 'click' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('click'))) { + this.beginTriggerEditCellMode = 'click'; const { col, row } = e; this.startEditCell(col, row); } @@ -110,9 +112,13 @@ export class EditManager { // adjust last col&row, same as packages/vtable/src/scenegraph/graphic/contributions/group-contribution-render.ts getCellSizeForDraw if (col === this.table.colCount - 1) { referencePosition.rect.width = rect.width - 1; + } else { + referencePosition.rect.width = rect.width + 1; // 这里的1应该根据单元格的borderWidth来定; } if (row === this.table.rowCount - 1) { referencePosition.rect.height = rect.height - 1; + } else { + referencePosition.rect.height = rect.height + 1; // 这里的1应该根据单元格的borderWidth来定; } editor.beginEditing && console.warn('VTable Warn: `beginEditing` is deprecated, please use `onStart` instead.'); @@ -216,6 +222,7 @@ export class EditManager { this.editingEditor.onEnd?.(); this.editingEditor = null; this.isValidatingValue = false; + this.beginTriggerEditCellMode = null; } cancelEdit() { diff --git a/packages/vtable/src/event/listener/container-dom.ts b/packages/vtable/src/event/listener/container-dom.ts index a026bed238..4a5fbe571b 100644 --- a/packages/vtable/src/event/listener/container-dom.ts +++ b/packages/vtable/src/event/listener/container-dom.ts @@ -28,6 +28,14 @@ export function bindContainerDomListener(eventManager: EventManager) { // 监听键盘事件 handler.on(table.getElement(), 'keydown', (e: KeyboardEvent) => { + // 键盘按下事件 内部逻辑处理前 + const beforeKeydownEvent: KeydownEvent = { + keyCode: e.keyCode ?? e.which, + code: e.code, + event: e + }; + table.fireListeners(TABLE_EVENT_TYPE.BEFORE_KEYDOWN, beforeKeydownEvent); + // 键盘按下事件 内部逻辑处理 if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { if (table.keyboardOptions?.selectAllOnCtrlA) { // 处理全选 @@ -204,7 +212,7 @@ export function bindContainerDomListener(eventManager: EventManager) { } } } - } else if (!(e.ctrlKey || e.metaKey || e.shiftKey)) { + } else if (!(e.ctrlKey || e.metaKey)) { const editCellTrigger = (table.options as ListTableConstructorOptions).editCellTrigger; if ( (editCellTrigger === 'keydown' || (Array.isArray(editCellTrigger) && editCellTrigger.includes('keydown'))) && @@ -212,6 +220,7 @@ export function bindContainerDomListener(eventManager: EventManager) { ) { const allowedKeys = /^[a-zA-Z0-9+\-*\/%=.,\s]$/; // 允许的键值正则表达式 if (e.key.match(allowedKeys)) { + table.editorManager.beginTriggerEditCellMode = 'keydown'; table.editorManager.startEditCell(stateManager.select.cellPos.col, stateManager.select.cellPos.row, ''); } } diff --git a/packages/vtable/src/header-helper/style.ts b/packages/vtable/src/header-helper/style.ts index 89828219e2..53b791ea50 100644 --- a/packages/vtable/src/header-helper/style.ts +++ b/packages/vtable/src/header-helper/style.ts @@ -3,7 +3,7 @@ import type { FullExtendStyle, HeaderStyleOption, StylePropertyFunctionArg } fro import { TextHeaderStyle } from './style/MultilineTextHeaderStyle'; // import { SortHeaderStyle } from "./style/SortHeaderStyle"; import { Style } from './style/Style'; -import type { TableTheme } from '../themes/theme'; +import type { TableTheme } from '../themes/theme-define'; import { CheckboxStyle } from './style/CheckboxStyle'; export { Style, TextHeaderStyle }; diff --git a/packages/vtable/src/icons.ts b/packages/vtable/src/icons.ts index 0837289ee2..a022513c85 100644 --- a/packages/vtable/src/icons.ts +++ b/packages/vtable/src/icons.ts @@ -4,7 +4,7 @@ import type { ColumnIconOption, ImageIcon, ITableThemeDefine, SvgIcon } from './ts-types'; import { IconPosition, IconFuncTypeEnum } from './ts-types'; import { extend } from './tools/helper'; -import { icons as plugins } from './plugins/icons'; +import { icons as plugins } from './icons'; import { DrillDown, DrillUp } from './tools/global'; let sort_color: string; let sort_color_opacity: string; @@ -395,7 +395,7 @@ const builtins = { }; } }; - +export const icons: { [key: string]: ColumnIconOption } = {}; export function get(): { [key: string]: ColumnIconOption } { return extend(builtins, plugins); } diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts index 9fc8b7745c..0ecd316bcc 100644 --- a/packages/vtable/src/index.ts +++ b/packages/vtable/src/index.ts @@ -9,6 +9,7 @@ import * as icons from './icons'; import * as register from './register'; import * as themes from './themes'; import * as DataStatistics from './dataset/DataStatistics'; +import * as plugins from './plugins'; import type { ColumnDefine, ColumnsDefine, @@ -42,6 +43,7 @@ import * as CustomLayout from './render/layout'; import { updateCell } from './scenegraph/group-creater/cell-helper'; import { renderChart } from './scenegraph/graphic/contributions/chart-render-helper'; import { restoreMeasureText, setCustomAlphabetCharSet } from './scenegraph/utils/text-measure'; +import type { BaseTableAPI } from './ts-types/base-table'; // import { container, loadCanvasPicker } from '@src/vrender'; // loadCanvasPicker(container); @@ -68,6 +70,7 @@ export { core, ListTable, ListTableSimple, + BaseTableAPI, ListTableConstructorOptions, PivotTable, PivotTableSimple, @@ -106,8 +109,8 @@ export { renderChart, graphicUtil, setCustomAlphabetCharSet, - restoreMeasureText - + restoreMeasureText, + plugins // VRender // should use import {xxx} from '@visactor/vtable/es/vrender' }; diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts index a4a5439f10..ebe3a2c6f0 100644 --- a/packages/vtable/src/layout/pivot-header-layout.ts +++ b/packages/vtable/src/layout/pivot-header-layout.ts @@ -2939,8 +2939,8 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI { } { // 判断从source地址是否可以移动到target地址 if ( - (this._table.options.dragOrder?.validateDragOrderOnEnd(source, target) || - !this._table.options.dragOrder?.validateDragOrderOnEnd) && + (!this._table.options.dragOrder?.validateDragOrderOnEnd || + this._table.options.dragOrder?.validateDragOrderOnEnd(source, target)) && this.canMoveHeaderPosition(source, target) && !this.isCellRangeEqual(source.col, source.row, target.col, target.row) ) { diff --git a/packages/vtable/src/layout/simple-header-layout.ts b/packages/vtable/src/layout/simple-header-layout.ts index 40c529e298..4ee01e26b6 100644 --- a/packages/vtable/src/layout/simple-header-layout.ts +++ b/packages/vtable/src/layout/simple-header-layout.ts @@ -1203,8 +1203,8 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { } { // 判断从source地址是否可以移动到target地址 if ( - (this._table.options.dragOrder?.validateDragOrderOnEnd(source, target) || - !this._table.options.dragOrder?.validateDragOrderOnEnd) && + (!this._table.options.dragOrder?.validateDragOrderOnEnd || + this._table.options.dragOrder?.validateDragOrderOnEnd(source, target)) && this.canMoveHeaderPosition(source, target) ) { let sourceCellRange = this.getCellRange(source.col, source.row); @@ -1462,7 +1462,7 @@ export class SimpleHeaderLayoutMap implements LayoutMapAPI { let col; const result = this.columnObjects?.find((columnData: ColumnData, index) => { if (columnData.define?.key === key) { - col = index; + col = index + this.leftRowSeriesNumberColumnCount; return true; } return false; diff --git a/packages/vtable/src/plugins/chartModules.ts b/packages/vtable/src/plugins/chartModules.ts deleted file mode 100644 index 9d89bf5763..0000000000 --- a/packages/vtable/src/plugins/chartModules.ts +++ /dev/null @@ -1 +0,0 @@ -export const chartTypes: { [key: string]: any } = {}; diff --git a/packages/vtable/src/plugins/icons.ts b/packages/vtable/src/plugins/icons.ts deleted file mode 100644 index c4c0391cac..0000000000 --- a/packages/vtable/src/plugins/icons.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { ColumnIconOption } from '../ts-types'; - -export const icons: { [key: string]: ColumnIconOption } = {}; - -export class IconCache { - private static cache: Map = new Map(); - - static setIcon(key: string, icon: ColumnIconOption) { - this.cache.set(key, icon); - } - - static getIcon(key: string): ColumnIconOption | null { - if (this.cache.has(key)) { - return this.cache.get(key) as ColumnIconOption; - } - return null; - } - - static hasIcon(key: string): boolean { - return this.cache.has(key); - } - - static clear(key: string): boolean { - return this.cache.delete(key); - } - - static clearAll() { - this.cache = new Map(); - } -} diff --git a/packages/vtable/src/plugins/index.ts b/packages/vtable/src/plugins/index.ts new file mode 100644 index 0000000000..dc5c41f6f0 --- /dev/null +++ b/packages/vtable/src/plugins/index.ts @@ -0,0 +1,3 @@ +export type { IVTablePlugin } from './interface'; +export { PluginManager } from './plugin-manager'; +export { CustomCellStylePlugin } from './custom-cell-style'; diff --git a/packages/vtable/src/plugins/interface.ts b/packages/vtable/src/plugins/interface.ts new file mode 100644 index 0000000000..094baf2760 --- /dev/null +++ b/packages/vtable/src/plugins/interface.ts @@ -0,0 +1,23 @@ +import type { TableEvents } from '../core/TABLE_EVENT_TYPE'; +import type { BaseTableAPI } from '../ts-types/base-table'; + +// 插件生命周期接口 +export interface IVTablePlugin { + // 插件唯一标识 + id: string; + // // 插件优先级,数字越小优先级越高 TODO:目前还没起作用,后续是否有安排插件优先级的需求 + // priority?: number; + + // // 插件类型,用于区分不同功能的插件 + // type: 'layout' | 'interaction' | 'style' | 'animation'; + // 插件运行时机 + runTime: TableEvents[keyof TableEvents][]; + // // 插件依赖 + // dependencies?: string[]; + // 初始化方法,在VTable实例创建后、首次渲染前调用 + run: (...args: any[]) => void; + // 更新方法,当表格数据或配置更新时调用 + update?: (table: BaseTableAPI, options?: any) => void; + // 销毁方法,在VTable实例销毁前调用 + release?: (table: BaseTableAPI) => void; +} diff --git a/packages/vtable/src/plugins/plugin-manager.ts b/packages/vtable/src/plugins/plugin-manager.ts new file mode 100644 index 0000000000..074075d696 --- /dev/null +++ b/packages/vtable/src/plugins/plugin-manager.ts @@ -0,0 +1,55 @@ +import type { TableEvents } from '../core/TABLE_EVENT_TYPE'; +import type { BaseTableAPI, BaseTableConstructorOptions } from '../ts-types/base-table'; +import type { IVTablePlugin } from './interface'; + +export class PluginManager { + private plugins: Map = new Map(); + private table: BaseTableAPI; + + constructor(table: BaseTableAPI, options: BaseTableConstructorOptions) { + this.table = table; + options.plugins?.map(plugin => { + this.register(plugin); + }); + this.initPlugins(table); + } + + // 注册插件 + register(plugin: IVTablePlugin): void { + this.plugins.set(plugin.id, plugin); + } + + // 注册多个插件 + registerAll(plugins: IVTablePlugin[]): void { + plugins.forEach(plugin => this.register(plugin)); + } + + // 获取插件 + getPlugin(id: string): IVTablePlugin | undefined { + return this.plugins.get(id); + } + + initPlugins(table: BaseTableAPI) { + this.plugins.forEach(plugin => { + plugin.runTime?.forEach(runTime => { + table.on(runTime, (...args) => { + plugin.run?.(...args, runTime, table); + }); + }); + }); + } + + // 更新所有插件 + updatePlugins(): void { + this.plugins.forEach(plugin => { + if (plugin.update) { + plugin.update(this.table); + } + }); + } + release() { + this.plugins.forEach(plugin => { + plugin.release?.(this.table); + }); + } +} diff --git a/packages/vtable/src/register.ts b/packages/vtable/src/register.ts index 2fd8e14182..dcfe211036 100644 --- a/packages/vtable/src/register.ts +++ b/packages/vtable/src/register.ts @@ -1,6 +1,6 @@ -import { icons as iconPlugins } from './plugins/icons'; -import { themes as themePlugins } from './plugins/themes'; -import { chartTypes as chartTypePlugins } from './plugins/chartModules'; +import { icons as iconPlugins } from './icons'; +import { themes as themePlugins } from './themes/themes'; +import { chartTypes as chartTypePlugins } from './chartModule'; import type { ColumnIconOption, ITableThemeDefine } from './ts-types'; import type { IEditor } from '@visactor/vtable-editors'; import { editors } from './edit/editors'; diff --git a/packages/vtable/src/scenegraph/graphic/chart.ts b/packages/vtable/src/scenegraph/graphic/chart.ts index 499b595305..57f80f653d 100644 --- a/packages/vtable/src/scenegraph/graphic/chart.ts +++ b/packages/vtable/src/scenegraph/graphic/chart.ts @@ -1,5 +1,5 @@ -import type { GraphicType, IGroupGraphicAttribute, Stage } from '@src/vrender'; -import { genNumberType, Group } from '@src/vrender'; +import type { GraphicType, IGroupGraphicAttribute, Stage, Group } from '@src/vrender'; +import { genNumberType, Rect } from '@src/vrender'; import { Bounds, merge } from '@visactor/vutils'; import type { BaseTableAPI } from '../../ts-types/base-table'; import type { PivotChart } from '../../PivotChart'; @@ -29,7 +29,7 @@ interface IChartGraphicAttribute extends IGroupGraphicAttribute { export const CHART_NUMBER_TYPE = genNumberType(); -export class Chart extends Group { +export class Chart extends Rect { type: GraphicType = 'chart' as any; declare attribute: IChartGraphicAttribute; chartInstance: any; diff --git a/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts b/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts index 44fae47464..4831633f7b 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/chart-render-helper.ts @@ -158,6 +158,7 @@ export function renderChart(chart: Chart) { } } + table.fireListeners('before_cache_chart_image', { chartInstance }); const sg = chartInstance.getStage(); cacheStageCanvas(sg, chart); // chart.cacheCanvas = sg.toCanvas(); diff --git a/packages/vtable/src/scenegraph/graphic/contributions/index.ts b/packages/vtable/src/scenegraph/graphic/contributions/index.ts index ea284520e4..19926b756a 100644 --- a/packages/vtable/src/scenegraph/graphic/contributions/index.ts +++ b/packages/vtable/src/scenegraph/graphic/contributions/index.ts @@ -7,7 +7,8 @@ import { SplitRectAfterRenderContribution, ContainerModule, DrawItemInterceptor, - TextRenderContribution + TextRenderContribution, + CanvasPickerContribution } from '@src/vrender'; import { ChartRender, DefaultCanvasChartRender } from './chart-render'; import { @@ -34,6 +35,8 @@ import { } from './group-contribution-render'; import { VTableDrawItemInterceptorContribution } from './draw-interceptor'; import { SuffixTextBeforeRenderContribution } from './text-contribution-render'; +import { VChartPicker } from './vchart-graphic-picker'; +// import { VChartPickServiceInterceptorContribution } from './picker-interceptor'; export default new ContainerModule((bind, unbind, isBound, rebind) => { // rect 渲染器注入contributions @@ -54,6 +57,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(DefaultCanvasChartRender).toSelf().inSingletonScope(); bind(ChartRender).to(DefaultCanvasChartRender); bind(GraphicRender).to(DefaultCanvasChartRender); + // chart picker 注入 + bind(VChartPicker).toSelf().inSingletonScope(); + bind(CanvasPickerContribution).toService(VChartPicker); + // bind(VChartPickServiceInterceptorContribution).toSelf().inSingletonScope(); + // bind(PickServiceInterceptor).toService(VChartPickServiceInterceptorContribution); // image 渲染器注入contributions bind(BeforeImageRenderContribution).toSelf().inSingletonScope(); diff --git a/packages/vtable/src/scenegraph/graphic/contributions/picker-interceptor.ts b/packages/vtable/src/scenegraph/graphic/contributions/picker-interceptor.ts new file mode 100644 index 0000000000..81ed9c4d25 --- /dev/null +++ b/packages/vtable/src/scenegraph/graphic/contributions/picker-interceptor.ts @@ -0,0 +1,52 @@ +import type { IPickerService, IPickParams, PickResult } from '@src/vrender'; +import { injectable } from '@src/vrender'; +import type { IMatrix, IPointLike } from '@visactor/vutils'; + +@injectable() +export class VChartPickServiceInterceptorContribution { + order: number = 1; + afterPickItem( + result: PickResult, + pickerService: IPickerService, + point: IPointLike, + pickParams: IPickParams, + params?: { + parentMatrix: IMatrix; + } + ): null | PickResult { + // 点击到图表的空白区域了,那么就判断该位置是否有其他图元,如果有,那就返回false,否则还是认为选中了图表 + if ( + result.graphic === null && + result.group && + (result.group as any).stage && + (result.group as any).stage.id === 'vstory' + ) { + // console.log('aaaa', result); + const stage = (result.group as any).stage; + const charts = stage.getElementsByType('chart'); + const nextPoint = { x: point.x, y: point.y }; + if (params && params.parentMatrix) { + params.parentMatrix.transformPoint(point, nextPoint); + } + + for (let i = charts.length - 1; i >= 0; i--) { + const chart = charts[i]; + const pointInChart = { x: nextPoint.x, y: nextPoint.y }; + chart.globalTransMatrix.transformPoint(pointInChart, pointInChart); + if (!chart.activeChartInstance) { + continue; + } + const viewBox = chart.activeChartInstance.getStage().viewBox; + // console.log(chart); + if (viewBox.contains(pointInChart.x, pointInChart.y)) { + result.graphic = chart; + result.group = null; + // result.group = + return result; + } + } + } + + return result; + } +} diff --git a/packages/vtable/src/scenegraph/graphic/contributions/vchart-graphic-picker.ts b/packages/vtable/src/scenegraph/graphic/contributions/vchart-graphic-picker.ts new file mode 100644 index 0000000000..56d3f5471e --- /dev/null +++ b/packages/vtable/src/scenegraph/graphic/contributions/vchart-graphic-picker.ts @@ -0,0 +1,17 @@ +import { injectable } from '@src/vrender'; +import type { IGraphicPicker, IPickParams } from '@src/vrender'; +import type { Chart as VChartGraphic } from '../chart'; +import { CHART_NUMBER_TYPE } from '../chart'; + +@injectable() +export class VChartPicker implements IGraphicPicker { + type = 'chart'; + numberType: number = CHART_NUMBER_TYPE; + + contains(chart: any, point: any, params?: IPickParams): boolean | any { + if (!chart.AABBBounds.containsPoint(point)) { + return false; + } + return true; + } +} 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 6cb59600e3..70356a63fd 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 @@ -170,7 +170,9 @@ export function createImageCellGroup( image.resources.has(image.attribute.image) && image.resources.get(image.attribute.image).state === 'success' ) { + image.setAttribute('opacity', 0); // hack for image update setTimeout(() => { + image.setAttribute('opacity', 1); // hack for image update; 如果图片资源存在,会先安照单元格大小渲染一次,再异步调整大小,这样可能出现图片闪烁的问题,这里使用opacity来规避 updateAutoSizingAndKeepAspectRatio( imageAutoSizing, keepAspectRatio, @@ -181,6 +183,7 @@ export function createImageCellGroup( cellGroup, table ); + table.scenegraph.updateNextFrame(); }, 0); } else { image.successCallback = () => { diff --git a/packages/vtable/src/scenegraph/layout/update-col.ts b/packages/vtable/src/scenegraph/layout/update-col.ts index f02f6527d0..c90130b368 100644 --- a/packages/vtable/src/scenegraph/layout/update-col.ts +++ b/packages/vtable/src/scenegraph/layout/update-col.ts @@ -6,6 +6,7 @@ import { updateCell } from '../group-creater/cell-helper'; import type { Scenegraph } from '../scenegraph'; import { getCellMergeInfo } from '../utils/get-cell-merge'; import type { IGroup } from '@src/vrender'; +import { checkHaveTextStick, resetTextStick } from '../stick-text'; /** * add and remove rows in scenegraph @@ -16,6 +17,10 @@ export function updateCol( updateCells: CellAddress[], table: BaseTableAPI ) { + if (checkHaveTextStick(table)) { + resetTextStick(table); // reset text stick + } + const scene = table.scenegraph; // deduplication const removeCols = deduplication(removeCells.map(cell => cell.col)).sort((a, b) => b - a); diff --git a/packages/vtable/src/scenegraph/layout/update-row.ts b/packages/vtable/src/scenegraph/layout/update-row.ts index f18bd87dc0..8905123e16 100644 --- a/packages/vtable/src/scenegraph/layout/update-row.ts +++ b/packages/vtable/src/scenegraph/layout/update-row.ts @@ -6,6 +6,7 @@ import { updateCell } from '../group-creater/cell-helper'; import type { Scenegraph } from '../scenegraph'; import { getCellMergeInfo } from '../utils/get-cell-merge'; import { deduplication } from '../../tools/util'; +import { checkHaveTextStick, resetTextStick } from '../stick-text'; /** * add and remove rows in scenegraph @@ -17,6 +18,10 @@ export function updateRow( table: BaseTableAPI, skipUpdateProxy?: boolean ) { + if (checkHaveTextStick(table)) { + resetTextStick(table); // reset text stick + } + const scene = table.scenegraph; // deduplication const removeRows = deduplication(removeCells.map(cell => cell.row)).sort((a, b) => b - a); diff --git a/packages/vtable/src/scenegraph/select/update-select-border.ts b/packages/vtable/src/scenegraph/select/update-select-border.ts index 10e6e96e57..e7ac811b4c 100644 --- a/packages/vtable/src/scenegraph/select/update-select-border.ts +++ b/packages/vtable/src/scenegraph/select/update-select-border.ts @@ -187,11 +187,12 @@ function updateComponent( } } + const { dynamicUpdateSelectionSize } = table.theme.selectionStyle; if ( - (isNearRowHeader && selectComp.rect.attribute.stroke[3]) || - (isNearRightRowHeader && selectComp.rect.attribute.stroke[1]) || - (isNearColHeader && selectComp.rect.attribute.stroke[0]) || - (isNearBottomColHeader && selectComp.rect.attribute.stroke[2]) + (isNearRowHeader && (selectComp.rect.attribute.stroke[3] || dynamicUpdateSelectionSize)) || + (isNearRightRowHeader && (selectComp.rect.attribute.stroke[1] || dynamicUpdateSelectionSize)) || + (isNearColHeader && (selectComp.rect.attribute.stroke[0] || dynamicUpdateSelectionSize)) || + (isNearBottomColHeader && (selectComp.rect.attribute.stroke[2] || dynamicUpdateSelectionSize)) ) { if (isNearRowHeader && selectComp.rect.attribute.stroke[3]) { scene.tableGroup.insertAfter( diff --git a/packages/vtable/src/scenegraph/stick-text/index.ts b/packages/vtable/src/scenegraph/stick-text/index.ts index 0fa2090840..3c09560f7a 100644 --- a/packages/vtable/src/scenegraph/stick-text/index.ts +++ b/packages/vtable/src/scenegraph/stick-text/index.ts @@ -6,7 +6,7 @@ import type { ITextStyleOption, StickCell } from '../../ts-types'; import { isNumber, min } from '@visactor/vutils'; import { getCellMergeRange } from '../../tools/merge-range'; -export function handleTextStick(table: BaseTableAPI) { +export function resetTextStick(table: BaseTableAPI) { // reset const { changedCells } = table.internalProps.stick; // changedCells only cache one time changedCells.forEach((cellPos: StickCell) => { @@ -19,6 +19,11 @@ export function handleTextStick(table: BaseTableAPI) { }); }); changedCells.clear(); + return changedCells; +} + +export function handleTextStick(table: BaseTableAPI) { + const changedCells = resetTextStick(table); const { scrollTop, scrollLeft, frozenRowCount, frozenColCount } = table; const frozenRowsHeight = table.getFrozenRowsHeight(); diff --git a/packages/vtable/src/state/hover/update-position.ts b/packages/vtable/src/state/hover/update-position.ts index c199b58ac1..843f8875f6 100644 --- a/packages/vtable/src/state/hover/update-position.ts +++ b/packages/vtable/src/state/hover/update-position.ts @@ -43,9 +43,12 @@ export function updateHoverPosition(state: StateManager, col: number, row: numbe if (prevHoverCellCol === col && prevHoverCellRow === row) { return; } - // 将hover单元格的图表实例激活 并将上一个失去焦点 - scenegraph.deactivateChart(prevHoverCellCol, prevHoverCellRow); - scenegraph.activateChart(col, row); + + if (!state.table.options.customConfig?.disableBuildInChartActive) { + // 将hover单元格的图表实例激活 并将上一个失去焦点 + scenegraph.deactivateChart(prevHoverCellCol, prevHoverCellRow); + scenegraph.activateChart(col, row); + } let updateScenegraph = false; const { diff --git a/packages/vtable/src/themes.ts b/packages/vtable/src/themes.ts index 0b71d9b9ce..b9dc51c180 100644 --- a/packages/vtable/src/themes.ts +++ b/packages/vtable/src/themes.ts @@ -5,8 +5,8 @@ import brightTheme from './themes/BRIGHT'; import arcoTheme from './themes/ARCO'; import defaultTheme from './themes/DEFAULT'; import materialDesignTheme from './themes/SIMPLIFY'; -import { themes as plugins } from './plugins/themes'; -import { TableTheme } from './themes/theme'; +import { themes as plugins } from './themes/themes'; +import { TableTheme } from './themes/theme-define'; import type { ITableThemeDefine } from './ts-types'; export const DARK = new TableTheme(darkTheme, darkTheme); export const BRIGHT = new TableTheme(brightTheme, brightTheme); diff --git a/packages/vtable/src/themes/theme.ts b/packages/vtable/src/themes/theme-define.ts similarity index 99% rename from packages/vtable/src/themes/theme.ts rename to packages/vtable/src/themes/theme-define.ts index 5f5d42f438..94004a960a 100644 --- a/packages/vtable/src/themes/theme.ts +++ b/packages/vtable/src/themes/theme-define.ts @@ -719,6 +719,9 @@ export class TableTheme implements ITableThemeDefine { }, get selectionFillMode(): 'overlay' | 'replace' { return selectionStyle?.selectionFillMode ?? 'overlay'; + }, + get dynamicUpdateSelectionSize(): boolean { + return selectionStyle?.dynamicUpdateSelectionSize ?? false; } }; } diff --git a/packages/vtable/src/plugins/themes.ts b/packages/vtable/src/themes/themes.ts similarity index 50% rename from packages/vtable/src/plugins/themes.ts rename to packages/vtable/src/themes/themes.ts index 5d0a211894..99c1f88a3f 100644 --- a/packages/vtable/src/plugins/themes.ts +++ b/packages/vtable/src/themes/themes.ts @@ -1,3 +1,3 @@ -import type { TableTheme } from '../themes/theme'; +import type { TableTheme } from '../themes/theme-define'; export const themes: { [key: string]: TableTheme } = {}; diff --git a/packages/vtable/src/tools/cell-range.ts b/packages/vtable/src/tools/cell-range.ts index 87e137b898..010b32b351 100644 --- a/packages/vtable/src/tools/cell-range.ts +++ b/packages/vtable/src/tools/cell-range.ts @@ -10,9 +10,9 @@ export function isSameRange(range1: CellRange | undefined | null, range2: CellRa } return ( - range1.start.col === range2.start.col && - range1.start.row === range2.start.row && - range1.end.col === range2.end.col && - range1.end.row === range2.end.row + range1.start?.col === range2.start?.col && + range1.start?.row === range2.start?.row && + range1.end?.col === range2.end?.col && + range1.end?.row === range2.end?.row ); } diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 99349eae5b..5802c9e5ef 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -27,7 +27,7 @@ import type { SeriesNumberColumnData } from './list-table/layout-map/api'; export type { HeaderData } from './list-table/layout-map/api'; -import type { TableTheme } from '../themes/theme'; +import type { TableTheme } from '../themes/theme-define'; import type { ICustomRender } from './customElement'; import type { LayoutObjectId } from './table-engine'; import type { Rect } from '../tools/Rect'; @@ -104,6 +104,7 @@ import type { EmptyTip } from '../components/empty-tip/empty-tip'; import type { EditManager } from '../edit/edit-manager'; import type { TableAnimationManager } from '../core/animation'; import type { CustomCellStylePlugin } from '../plugins/custom-cell-style'; +import type { IVTablePlugin } from '../plugins/interface'; export interface IBaseTableProtected { element: HTMLElement; @@ -569,6 +570,9 @@ export interface BaseTableConstructorOptions { // 开启透视结构缓存 enablePivotPathCache?: boolean; + + // 是否禁用内置图表激活 + disableBuildInChartActive?: boolean; }; // 部分特殊配置,兼容xTable等作用 animationAppear?: boolean | IAnimationAppear; @@ -601,6 +605,8 @@ export interface BaseTableConstructorOptions { /** 拖拽移动位置结束时进行验证 */ validateDragOrderOnEnd?: (source: CellAddress, target: CellAddress) => boolean; }; + /** 插件配置 */ + plugins?: IVTablePlugin[]; } export interface BaseTableAPI { id: string; diff --git a/packages/vtable/src/ts-types/events.ts b/packages/vtable/src/ts-types/events.ts index acab71eb7f..82eac21d5f 100644 --- a/packages/vtable/src/ts-types/events.ts +++ b/packages/vtable/src/ts-types/events.ts @@ -1,10 +1,19 @@ -import type { CellAddress, CellRange, CellLocation, FieldDef, CellAddressWithBound } from './table-engine'; +import type { + CellAddress, + CellRange, + CellLocation, + FieldDef, + CellAddressWithBound, + ListTableConstructorOptions, + PivotTableConstructorOptions +} from './table-engine'; import type { DropDownMenuEventArgs, MenuListItem, PivotInfo } from './menu'; import type { IDimensionInfo, MergeCellInfo, RectProps, SortOrder } from './common'; import type { IconFuncTypeEnum, CellInfo, HierarchyState } from '.'; import type { Icon } from '../scenegraph/graphic/icon'; import type { FederatedPointerEvent, IEventTarget } from '@src/vrender'; +import type { BaseTableConstructorOptions } from './base-table'; export type KeyboardEventListener = (e: KeyboardEvent) => void; export type TableEventListener = ( @@ -74,6 +83,7 @@ export interface TableEventHandlersEventArgumentMap { mousedown_cell: MousePointerCellEvent; mouseup_cell: MousePointerCellEvent; contextmenu_cell: MousePointerMultiCellEvent; + before_keydown: KeydownEvent; keydown: KeydownEvent; scroll: { event: WheelEvent; @@ -226,6 +236,8 @@ export interface TableEventHandlersEventArgumentMap { checkbox_state_change: MousePointerCellEvent & { checked: boolean }; radio_state_change: MousePointerCellEvent & { radioIndexInCell: number | undefined }; switch_state_change: MousePointerCellEvent & { checked: boolean }; + before_init: { options: BaseTableConstructorOptions; container: HTMLElement | null }; + before_set_size: { width: number; height: number }; after_render: null; initialized: null; @@ -249,6 +261,7 @@ export interface TableEventHandlersEventArgumentMap { row: number; event: Event; }; + before_cache_chart_image: { chartInstance: any }; } export interface DrillMenuEventInfo { dimensionKey: string | number; @@ -276,6 +289,7 @@ export interface TableEventHandlersReturnMap { mousedown_cell: boolean; mouseup_cell: void; contextmenu_cell: void; + before_keydown: void; keydown: void; scroll: void; can_scroll: void | boolean; @@ -325,6 +339,8 @@ export interface TableEventHandlersReturnMap { checkbox_state_change: void; radio_state_change: void; switch_state_change: void; + before_init: void; + before_set_size: void; after_render: void; initialized: void; @@ -341,4 +357,5 @@ export interface TableEventHandlersReturnMap { empty_tip_dblclick: void; button_click: void; + before_cache_chart_image: void; } diff --git a/packages/vtable/src/ts-types/index.ts b/packages/vtable/src/ts-types/index.ts index a9d2de4f59..633f6f0d5e 100644 --- a/packages/vtable/src/ts-types/index.ts +++ b/packages/vtable/src/ts-types/index.ts @@ -15,3 +15,4 @@ export * from './pivot-table'; export * from './component'; export * from './animation'; export * from './dataset'; +export * from './base-table'; diff --git a/packages/vtable/src/ts-types/menu.ts b/packages/vtable/src/ts-types/menu.ts index d5b8b84aea..a19dcfc0ac 100644 --- a/packages/vtable/src/ts-types/menu.ts +++ b/packages/vtable/src/ts-types/menu.ts @@ -30,6 +30,7 @@ export type MenuListItem = selectedIcon?: Icon; stateIcon?: Icon; children?: MenuListItem[]; + disabled?: boolean; // 禁用菜单项 }; export type PivotInfo = { diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index ce2a7fdddd..8dd34c19cb 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -33,7 +33,7 @@ import type { EditManager } from '../edit/edit-manager'; import type { ICustomRender } from './customElement'; import type { ICustomLayout } from './customLayout'; import type { ColorPropertyDefine, StylePropertyFunctionArg } from './style-define'; -import type { TableTheme } from '../themes/theme'; +import type { TableTheme } from '../themes/theme-define'; export interface CellAddress { col: number; diff --git a/packages/vtable/src/ts-types/theme.ts b/packages/vtable/src/ts-types/theme.ts index 87a0924630..9c9f22fc57 100644 --- a/packages/vtable/src/ts-types/theme.ts +++ b/packages/vtable/src/ts-types/theme.ts @@ -154,6 +154,7 @@ export interface ITableThemeDefine { inlineRowBgColor?: string; //交互所在整行的背景颜色 inlineColumnBgColor?: string; //交互所在整列的背景颜色 selectionFillMode?: 'overlay' | 'replace'; //选择框填充模式,overlay表示选择框背景色覆盖在表格上(需要配饰透明度),replace表示背景色替换原有单元格的背景色 + dynamicUpdateSelectionSize?: boolean; // 选择框大小随滚动动态变化,用于冻结并且背景透明的场景,默认false,开启后性能会有一定影响 }; // style for axis diff --git a/packages/vue-vtable/package.json b/packages/vue-vtable/package.json index 17bb6f86b0..c2158d07bc 100644 --- a/packages/vue-vtable/package.json +++ b/packages/vue-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vue-vtable", - "version": "1.17.6", + "version": "1.18.0", "description": "The vue version of VTable", "keywords": [ "vue",