diff --git a/common/changes/@visactor/vtable/feat-fs-bs_2024-10-17-09-01.json b/common/changes/@visactor/vtable/feat-fs-bs_2024-10-17-09-01.json
new file mode 100644
index 000000000..30ab08dc0
--- /dev/null
+++ b/common/changes/@visactor/vtable/feat-fs-bs_2024-10-17-09-01.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@visactor/vtable",
+ "comment": "feat: add InvertHighlightPlugin ",
+ "type": "none"
+ }
+ ],
+ "packageName": "@visactor/vtable"
+}
\ No newline at end of file
diff --git a/common/changes/@visactor/vtable/feat-fs-bs_2024-10-17-09-02.json b/common/changes/@visactor/vtable/feat-fs-bs_2024-10-17-09-02.json
new file mode 100644
index 000000000..71ced78d6
--- /dev/null
+++ b/common/changes/@visactor/vtable/feat-fs-bs_2024-10-17-09-02.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@visactor/vtable",
+ "comment": "fix: add CarouselAnimationPlugin",
+ "type": "none"
+ }
+ ],
+ "packageName": "@visactor/vtable"
+}
\ No newline at end of file
diff --git a/packages/vtable/examples/interactive/highlight-color.ts b/packages/vtable/examples/interactive/highlight-color.ts
new file mode 100644
index 000000000..421cb1059
--- /dev/null
+++ b/packages/vtable/examples/interactive/highlight-color.ts
@@ -0,0 +1,82 @@
+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' + (i + 1) : 'front-end engineer' + (i + 1),
+ city: 'beijing',
+ image:
+ ''
+ }));
+};
+
+export function createTable() {
+ const records = generatePersons(20);
+ const columns: VTable.ColumnsDefine = [
+ {
+ 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,
+ rowSeriesNumber: {}
+ };
+ const tableInstance = new VTable.ListTable(option);
+ window.tableInstance = tableInstance;
+
+ bindDebugTool(tableInstance.scenegraph.stage, {
+ customGrapicKeys: ['col', 'row']
+ });
+
+ const highlightPlugin = new VTable.HeaderHighlightPlugin(tableInstance);
+ window.highlightPlugin = highlightPlugin;
+}
diff --git a/packages/vtable/examples/interactive/mask.ts b/packages/vtable/examples/interactive/mask.ts
new file mode 100644
index 000000000..49eba6c4c
--- /dev/null
+++ b/packages/vtable/examples/interactive/mask.ts
@@ -0,0 +1,132 @@
+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' + (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 option: VTable.ListTableConstructorOptions = {
+ container: document.getElementById(CONTAINER_ID),
+ records,
+ columns,
+ theme: VTable.themes.DARK,
+ // heightMode: 'adaptive',
+ select: {
+ disableSelect: true
+ }
+ };
+ const tableInstance = new VTable.ListTable(option);
+ window.tableInstance = tableInstance;
+
+ bindDebugTool(tableInstance.scenegraph.stage, {
+ customGrapicKeys: ['col', 'row']
+ });
+
+ const highlightPlugin = new VTable.InvertHighlightPlugin(tableInstance);
+ // highlightPlugin.setInvertHighlightRange({
+ // start: {
+ // col: 0,
+ // row: 6
+ // },
+ // end: {
+ // col: 6,
+ // row: 6
+ // }
+ // });
+
+ tableInstance.on('click_cell', event => {
+ const { col, row } = event;
+ if (tableInstance.isHeader(col, row)) {
+ highlightPlugin.setInvertHighlightRange(undefined);
+ } else {
+ highlightPlugin.setInvertHighlightRange({
+ start: {
+ col: 0,
+ row
+ },
+ end: {
+ col: tableInstance.colCount - 1,
+ row
+ }
+ });
+ }
+ });
+
+ const ca = new VTable.CarouselAnimationPlugin(tableInstance, {
+ rowCount: 2,
+ replaceScrollAction: true
+ });
+
+ // ca.play();
+
+ // setInterval(() => {
+ // row += 2;
+ // tableInstance.scrollToRow(row, { duration: 500 });
+ // }, 2000);
+}
diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts
index 53f95f780..bb1769086 100644
--- a/packages/vtable/examples/menu.ts
+++ b/packages/vtable/examples/menu.ts
@@ -641,6 +641,14 @@ export const menus = [
{
path: 'interactive',
name: 'pre-sort'
+ },
+ {
+ path: 'interactive',
+ name: 'mask'
+ },
+ {
+ path: 'interactive',
+ name: 'highlight-color'
}
]
},
diff --git a/packages/vtable/src/index.ts b/packages/vtable/src/index.ts
index 0ce1ec896..982870f04 100644
--- a/packages/vtable/src/index.ts
+++ b/packages/vtable/src/index.ts
@@ -129,3 +129,7 @@ export * from './scenegraph/group-creater/cell-type';
export { TABLE_EVENT_TYPE } from './core/TABLE_EVENT_TYPE';
export { PIVOT_CHART_EVENT_TYPE, PIVOT_TABLE_EVENT_TYPE } from './ts-types/pivot-table/PIVOT_TABLE_EVENT_TYPE';
+
+export * from './plugins/invert-highlight';
+export * from './plugins/carousel-animation';
+export * from './plugins/header-highlight';
diff --git a/packages/vtable/src/plugins/carousel-animation.ts b/packages/vtable/src/plugins/carousel-animation.ts
new file mode 100644
index 000000000..35fff969d
--- /dev/null
+++ b/packages/vtable/src/plugins/carousel-animation.ts
@@ -0,0 +1,147 @@
+import type { EasingType } from '@src/vrender';
+import type { BaseTableAPI } from '../ts-types/base-table';
+
+export interface ICarouselAnimationPluginOptions {
+ rowCount?: number;
+ colCount?: number;
+ animationDuration?: number;
+ animationDelay?: number;
+ animationEasing?: EasingType;
+ replaceScrollAction?: boolean;
+}
+
+export class CarouselAnimationPlugin {
+ table: BaseTableAPI;
+
+ rowCount: number;
+ colCount: number;
+ animationDuration: number;
+ animationDelay: number;
+ animationEasing: EasingType;
+ replaceScrollAction: boolean;
+
+ playing: boolean;
+ row: number;
+ col: number;
+ willUpdateRow: boolean = false;
+ willUpdateCol: boolean = false;
+ constructor(table: BaseTableAPI, options?: ICarouselAnimationPluginOptions) {
+ this.table = table;
+
+ 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.replaceScrollAction = options?.replaceScrollAction ?? false;
+
+ this.reset();
+ this.init();
+ }
+
+ init() {
+ if (this.replaceScrollAction) {
+ this.table.disableScroll();
+
+ this.table.scenegraph.stage.addEventListener('wheel', this.onScrollEnd.bind(this));
+ }
+ }
+
+ reset() {
+ this.playing = false;
+ this.row = this.table.frozenRowCount;
+ this.col = this.table.frozenColCount;
+ }
+
+ onScrollEnd(e: Event) {
+ if (this.rowCount) {
+ if ((e as any).deltaY > 0) {
+ this.row += this.rowCount;
+ this.row = Math.min(this.row, this.table.rowCount - this.table.frozenRowCount);
+ } else if ((e as any).deltaY < 0) {
+ this.row -= this.rowCount;
+ this.row = Math.max(this.row, this.table.frozenRowCount);
+ }
+ this.table.scrollToRow(this.row, { duration: this.animationDuration, easing: this.animationEasing });
+ } else if (this.colCount) {
+ if ((e as any).deltaX > 0) {
+ this.col += this.colCount;
+ this.col = Math.min(this.col, this.table.colCount - this.table.frozenColCount);
+ } else if ((e as any).deltaX < 0) {
+ this.col -= this.colCount;
+ this.col = Math.max(this.col, this.table.frozenColCount);
+ }
+ this.table.scrollToCol(this.col, { duration: this.animationDuration, easing: this.animationEasing });
+ }
+ }
+
+ play() {
+ 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) {
+ return;
+ }
+
+ let animation = true;
+ if (this.table.scenegraph.proxy.screenTopRow !== 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) {
+ return;
+ }
+
+ let animation = true;
+ if (this.table.scenegraph.proxy.screenLeftCol !== 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
+ );
+ }
+}
diff --git a/packages/vtable/src/plugins/custom-cell-style.ts b/packages/vtable/src/plugins/custom-cell-style.ts
index 210204dc9..5174295c8 100644
--- a/packages/vtable/src/plugins/custom-cell-style.ts
+++ b/packages/vtable/src/plugins/custom-cell-style.ts
@@ -34,20 +34,28 @@ export class CustomCellStylePlugin {
getCustomCellStyleId(col: number, row: number) {
let customStyleId;
- this.customCellStyleArrangement.forEach(style => {
- if (style.cellPosition.range) {
- if (
- style.cellPosition.range.start.col <= col &&
- style.cellPosition.range.end.col >= col &&
- style.cellPosition.range.start.row <= row &&
- style.cellPosition.range.end.row >= row
- ) {
- customStyleId = style.customStyleId;
- }
- } else if (style.cellPosition.col === col && style.cellPosition.row === row) {
- customStyleId = style.customStyleId;
+
+ const range = this.table.getCellRange(col, row);
+ for (let c = range.start.col; c <= range.end.col; c++) {
+ for (let r = range.start.row; r <= range.end.row; r++) {
+ // eslint-disable-next-line no-loop-func
+ this.customCellStyleArrangement.forEach(style => {
+ if (style.cellPosition.range) {
+ if (
+ style.cellPosition.range.start.col <= c &&
+ style.cellPosition.range.end.col >= c &&
+ style.cellPosition.range.start.row <= r &&
+ style.cellPosition.range.end.row >= r
+ ) {
+ customStyleId = style.customStyleId;
+ }
+ } else if (style.cellPosition.col === c && style.cellPosition.row === r) {
+ customStyleId = style.customStyleId;
+ }
+ });
}
- });
+ }
+
return customStyleId;
}
@@ -106,7 +114,11 @@ export class CustomCellStylePlugin {
return style.cellPosition.col === cellPos.col && style.cellPosition.row === cellPos.row;
});
- if (index === -1) {
+ if (index === -1 && !customStyleId) {
+ // do nothing
+ return;
+ } else if (index === -1 && customStyleId) {
+ // add new style
this.customCellStyleArrangement.push({
cellPosition: {
col: cellPos.col,
@@ -116,16 +128,27 @@ export class CustomCellStylePlugin {
customStyleId: customStyleId
});
} else if (this.customCellStyleArrangement[index].customStyleId === customStyleId) {
+ // same style
return;
- } else {
+ } else if (customStyleId) {
+ // update style
this.customCellStyleArrangement[index].customStyleId = customStyleId;
+ } else {
+ // delete useless style
+ this.customCellStyleArrangement.splice(index, 1);
}
// update cell group
if (cellPos.range) {
for (let col = cellPos.range.start.col; col <= cellPos.range.end.col; col++) {
for (let row = cellPos.range.start.row; row <= cellPos.range.end.row; row++) {
- this.table.scenegraph.updateCellContent(col, row);
+ const range = this.table.getCellRange(col, row);
+ for (let c = range.start.col; c <= range.end.col; c++) {
+ for (let r = range.start.row; r <= range.end.row; r++) {
+ this.table.scenegraph.updateCellContent(c, r);
+ }
+ }
+ // this.table.scenegraph.updateCellContent(col, row);
}
}
} else {
@@ -154,9 +177,9 @@ export function mergeStyle(cacheStyle: Style, customCellStyle: ColumnStyleOption
cacheStyle = cacheStyle.clone();
for (const key in customCellStyle) {
- const value = customCellStyle[key];
+ const value = (customCellStyle as any)[key];
if (value) {
- cacheStyle[`_${key}`] = value;
+ (cacheStyle as any)[`_${key}`] = value;
}
}
diff --git a/packages/vtable/src/plugins/header-highlight.ts b/packages/vtable/src/plugins/header-highlight.ts
new file mode 100644
index 000000000..371a7315f
--- /dev/null
+++ b/packages/vtable/src/plugins/header-highlight.ts
@@ -0,0 +1,158 @@
+import type { CellRange } from '../ts-types';
+import type { BaseTableAPI } from '../ts-types/base-table';
+
+export interface IHeaderHighlightPluginOptions {
+ rowHighlight?: boolean;
+ colHighlight?: boolean;
+ colHighlightBGColor?: string;
+ colHighlightColor?: string;
+ rowHighlightBGColor?: string;
+ rowHighlightColor?: string;
+}
+
+export class HeaderHighlightPlugin {
+ table: BaseTableAPI;
+ options: IHeaderHighlightPluginOptions;
+ colHeaderRange?: CellRange;
+ rowHeaderRange?: CellRange;
+ constructor(table: BaseTableAPI, options?: IHeaderHighlightPluginOptions) {
+ this.table = table;
+ this.options = options;
+
+ this.registerStyle();
+ this.bindEvent();
+ }
+
+ registerStyle() {
+ this.table.registerCustomCellStyle('col-highlight', {
+ bgColor: this.options?.colHighlightBGColor ?? '#82b2f5',
+ color: this.options?.colHighlightColor ?? '#FFF'
+ });
+
+ this.table.registerCustomCellStyle('row-highlight', {
+ bgColor: this.options?.rowHighlightBGColor ?? '#82b2f5',
+ color: this.options?.rowHighlightColor ?? '#FFF'
+ });
+ }
+
+ bindEvent() {
+ this.table.on('selected_cell', e => {
+ this.updateHighlight();
+ });
+
+ this.table.on('selected_clear', () => {
+ this.clearHighlight();
+ });
+ }
+
+ clearHighlight() {
+ this.colHeaderRange && this.table.arrangeCustomCellStyle({ range: this.colHeaderRange }, undefined);
+ this.rowHeaderRange && this.table.arrangeCustomCellStyle({ range: this.rowHeaderRange }, undefined);
+
+ // clear range
+ this.colHeaderRange = undefined;
+ this.rowHeaderRange = undefined;
+ }
+
+ updateHighlight() {
+ if (this.options?.colHighlight === false && this.options?.rowHighlight === false) {
+ return;
+ }
+ const selectRanges = this.table.getSelectedCellRanges();
+ if (selectRanges.length === 0) {
+ this.clearHighlight();
+ return;
+ }
+
+ const selectRange = selectRanges[0];
+ 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.options?.colHighlight !== false && !isSameRange(this.colHeaderRange, colHeaderRange)) {
+ this.colHeaderRange && this.table.arrangeCustomCellStyle({ range: this.colHeaderRange }, undefined);
+ colHeaderRange && this.table.arrangeCustomCellStyle({ range: colHeaderRange }, 'col-highlight');
+ this.colHeaderRange = colHeaderRange;
+ }
+
+ if (this.options?.rowHighlight !== false && !isSameRange(this.rowHeaderRange, rowHeaderRange)) {
+ this.rowHeaderRange && this.table.arrangeCustomCellStyle({ range: this.rowHeaderRange }, undefined);
+ rowHeaderRange && this.table.arrangeCustomCellStyle({ range: rowHeaderRange }, 'row-highlight');
+ this.rowHeaderRange = rowHeaderRange;
+ }
+ }
+}
+
+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/src/plugins/invert-highlight.ts b/packages/vtable/src/plugins/invert-highlight.ts
new file mode 100644
index 000000000..2dcabf4b2
--- /dev/null
+++ b/packages/vtable/src/plugins/invert-highlight.ts
@@ -0,0 +1,112 @@
+import type { Rect } from '@src/vrender';
+import { createRect } from '@src/vrender';
+import type { Group } from '../scenegraph/graphic/group';
+import { isSameRange } from '../tools/cell-range';
+import type { CellRange } from '../ts-types';
+import type { BaseTableAPI } from '../ts-types/base-table';
+import { cellInRange } from '../tools/helper';
+import { isValid } from '@visactor/vutils';
+
+export interface InvertHighlightPluginOptions {
+ fill?: string;
+ opacity?: number;
+}
+
+export class InvertHighlightPlugin {
+ table: BaseTableAPI;
+ range?: CellRange;
+ _fill: string;
+ _opacity: number;
+
+ constructor(table: BaseTableAPI, options?: InvertHighlightPluginOptions) {
+ this.table = table;
+
+ this._fill = options?.fill ?? '#000';
+ this._opacity = options?.opacity ?? 0.5;
+ }
+
+ setInvertHighlightRange(range?: CellRange) {
+ if (isSameRange(this.range, range)) {
+ return;
+ }
+
+ this.range = range;
+ 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?: CellRange) {
+ container.forEachChildrenSkipChild((column: Group) => {
+ if (column.role === 'column') {
+ column.forEachChildrenSkipChild((cell: Group) => {
+ if (cell.role !== 'cell') {
+ return;
+ }
+ cell.attachShadow(cell.shadowRoot);
+ const shadowGroup = cell.shadowRoot;
+ if (!range) {
+ // no highlight
+ shadowGroup.removeAllChild();
+ } else if (cellInRange(range, 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._fill,
+ opacity: this._opacity
+ });
+ shadowRect.name = 'shadow-rect';
+ shadowGroup.appendChild(shadowRect);
+ }
+ });
+ }
+ });
+ }
+}
+
+export function onBeforeAttributeUpdateForInvertHighlight(val: Record, attribute: any) {
+ // @ts-ignore
+ const graphic = this as any;
+ if (graphic.shadowRoot && graphic.shadowRoot.childrenCount && (isValid(val.width) || isValid(val.height))) {
+ const shadowRect = (graphic.shadowRoot as Group).findChildrenByName('shadow-rect')[0] as Rect;
+ if (shadowRect) {
+ shadowRect.setAttributes({
+ width: val.width ?? shadowRect.attribute.width,
+ height: val.height ?? shadowRect.attribute.height
+ });
+ }
+ }
+}
diff --git a/packages/vtable/src/scenegraph/graphic/group.ts b/packages/vtable/src/scenegraph/graphic/group.ts
index 6b75bb433..fd436b5e5 100644
--- a/packages/vtable/src/scenegraph/graphic/group.ts
+++ b/packages/vtable/src/scenegraph/graphic/group.ts
@@ -307,6 +307,10 @@ export class Group extends VRenderGroup {
// 更新bounds之后需要设置父节点,否则tag丢失
this.parent && this.parent.addChildUpdateBoundTag();
this.clearUpdateBoundTag();
+ if (this.shadowRoot) {
+ // this.shadowRoot.clearUpdateBoundTag();
+ this.shadowRoot.tryUpdateAABBBounds();
+ }
return this._AABBBounds;
} else if (
this.role === 'body' ||
diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts
index 36e20624a..d447d83e1 100644
--- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts
+++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts
@@ -1,4 +1,4 @@
-import type { Cursor, IGraphic, IThemeSpec, Group as VGroup } from '@src/vrender';
+import type { Cursor, IGraphic, IThemeSpec, Rect, Group as VGroup } from '@src/vrender';
import type { ProgressBarStyle } from '../../body-helper/style/ProgressBarStyle';
import { regUrl } from '../../tools/global';
import type {
@@ -33,9 +33,10 @@ import type { CreateCheckboxCellGroup } from './cell-type/checkbox-cell';
import { getHierarchyOffset } from '../utils/get-hierarchy-offset';
import { getQuadProps } from '../utils/padding';
import { updateCellContentHeight, updateCellContentWidth } from '../utils/text-icon-layout';
-import { isArray } from '@visactor/vutils';
+import { isArray, isValid } from '@visactor/vutils';
import { breakString } from '../utils/break-string';
import type { CreateRadioCellGroup } from './cell-type/radio-cell';
+import { onBeforeAttributeUpdateForInvertHighlight } from '../../plugins/invert-highlight';
export function createCell(
type: ColumnTypeOption,
@@ -386,6 +387,7 @@ export function createCell(
);
}
+ cellGroup.onBeforeAttributeUpdate = onBeforeAttributeUpdateForInvertHighlight as any;
return cellGroup;
}
@@ -902,7 +904,8 @@ export function dealWithMergeCellSize(
table.isAutoRowHeight(row),
padding,
textAlign,
- textBaseline
+ textBaseline,
+ table
// 'middle'
);
}
@@ -952,7 +955,8 @@ export function dealWithMergeCellSizeForShadow(
table.isAutoRowHeight(row),
padding,
textAlign,
- textBaseline
+ textBaseline,
+ table
// 'middle'
);
}
diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts
index 2924b51aa..c12b392c8 100644
--- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts
+++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts
@@ -57,7 +57,9 @@ export function computeRowsHeight(
const isDefaultHeaderHasAuto =
table.defaultHeaderRowHeight === 'auto' ||
(isArray(table.defaultHeaderRowHeight) && table.defaultHeaderRowHeight.some(item => item === 'auto'));
- const isAllRowsAuto = table.heightMode === 'autoHeight' || table.heightMode === 'adaptive';
+ const isAllRowsAuto =
+ table.heightMode === 'autoHeight' ||
+ (table.heightMode === 'adaptive' && table.options.autoHeightInAdaptiveMode !== false);
if (isAllRowsAuto || isDefaultHeaderHasAuto) {
rowStart = rowStart ?? 0;
@@ -91,7 +93,9 @@ export function computeRowsHeight(
const height = computeRowHeight(row, startCol, endCol, table);
newHeights[row] = Math.round(height);
//表头部分需要马上设置到缓存中 因为adaptive不会调整表头的高度 另外后面adaptive处理过程中有取值 table.getRowsHeight(0, table.columnHeaderLevelCount - 1);
- table._setRowHeight(row, height);
+ if (table.heightAdaptiveMode === 'only-body' || !update) {
+ table._setRowHeight(row, height);
+ }
}
}
@@ -293,7 +297,10 @@ export function computeRowsHeight(
if (update) {
for (let row = rowStart; row <= rowEnd; row++) {
const newRowHeight = newHeights[row] ?? table.getRowHeight(row);
- if (newRowHeight !== (oldRowHeights[row] ?? table.getRowHeight(row))) {
+ // if (newRowHeight !== (oldRowHeights[row] ?? table.getRowHeight(row))) {
+ // table._setRowHeight(row, newRowHeight);
+ // }
+ if (isValid(newRowHeight)) {
table._setRowHeight(row, newRowHeight);
}
}
diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts
index 3de0e22c3..148441516 100644
--- a/packages/vtable/src/scenegraph/layout/update-height.ts
+++ b/packages/vtable/src/scenegraph/layout/update-height.ts
@@ -361,7 +361,8 @@ function updateMergeCellContentHeight(
autoRowHeight,
getQuadProps(style.padding as number),
style.textAlign,
- style.textBaseline
+ style.textBaseline,
+ table
);
}
@@ -402,7 +403,8 @@ function updateMergeCellContentHeight(
autoRowHeight,
getQuadProps(style.padding as number),
style.textAlign,
- style.textBaseline
+ style.textBaseline,
+ table
);
}
}
diff --git a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts
index e89c3680b..652929b71 100644
--- a/packages/vtable/src/scenegraph/utils/text-icon-layout.ts
+++ b/packages/vtable/src/scenegraph/utils/text-icon-layout.ts
@@ -103,12 +103,19 @@ export function createCellContent(
wordBreak: 'break-word',
// widthLimit: autoColWidth ? -1 : colWidth - (padding[1] + padding[3]),
heightLimit:
- autoRowHeight && !table.options.customConfig?.multilinesForXTable
+ table.options.customConfig?.limitContentHeight === false
+ ? -1
+ : autoRowHeight && !table.options.customConfig?.multilinesForXTable
? -1
: cellHeight - Math.floor(padding[0] + padding[2]),
pickable: false,
dx: (textAlign === 'left' ? hierarchyOffset : 0) + _contentOffset,
- whiteSpace: text.length === 1 && !autoWrapText ? 'no-wrap' : 'normal'
+ whiteSpace:
+ table.options.customConfig?.limitContentHeight === false
+ ? 'normal'
+ : text.length === 1 && !autoWrapText
+ ? 'no-wrap'
+ : 'normal'
};
const wrapText = new Text(cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute);
wrapText.name = 'text';
@@ -241,14 +248,21 @@ export function createCellContent(
textBaseline: 'top',
// widthLimit: autoColWidth ? -1 : colWidth - (padding[1] + padding[3]),
heightLimit:
- autoRowHeight && !table.options.customConfig?.multilinesForXTable
+ table.options.customConfig?.limitContentHeight === false
+ ? -1
+ : autoRowHeight && !table.options.customConfig?.multilinesForXTable
? -1
: cellHeight - Math.floor(padding[0] + padding[2]),
pickable: false,
autoWrapText,
lineClamp,
wordBreak: 'break-word',
- whiteSpace: text.length === 1 && !autoWrapText ? 'no-wrap' : 'normal',
+ whiteSpace:
+ table.options.customConfig?.limitContentHeight === false
+ ? 'normal'
+ : text.length === 1 && !autoWrapText
+ ? 'no-wrap'
+ : 'normal',
dx: (textAlign === 'left' ? (!contentLeftIcons.length ? hierarchyOffset : 0) : 0) + _contentOffset
};
const wrapText = new Text(cellTheme.text ? (Object.assign({}, cellTheme.text, attribute) as any) : attribute);
@@ -702,7 +716,8 @@ export function updateCellContentHeight(
autoRowHeight: boolean,
padding: [number, number, number, number],
textAlign: CanvasTextAlign,
- textBaseline: CanvasTextBaseline
+ textBaseline: CanvasTextBaseline,
+ table: BaseTableAPI
) {
const newHeight = distHeight - Math.floor(padding[0] + padding[2]);
@@ -710,7 +725,7 @@ export function updateCellContentHeight(
if (textMark instanceof Text && !autoRowHeight) {
textMark.setAttributes({
- heightLimit: newHeight
+ heightLimit: table.options.customConfig?.limitContentHeight === false ? -1 : newHeight
} as any);
} else if (textMark instanceof RichText && !autoRowHeight) {
textMark.setAttributes({
diff --git a/packages/vtable/src/tools/cell-range.ts b/packages/vtable/src/tools/cell-range.ts
new file mode 100644
index 000000000..87e137b89
--- /dev/null
+++ b/packages/vtable/src/tools/cell-range.ts
@@ -0,0 +1,18 @@
+import type { CellRange } from '../ts-types';
+
+export function isSameRange(range1: CellRange | undefined | null, range2: CellRange | undefined | null) {
+ if (!range1 && !range2) {
+ return true;
+ }
+
+ if (!range1 || !range2) {
+ return false;
+ }
+
+ 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
+ );
+}
diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts
index 8fb7621c0..00a0e3480 100644
--- a/packages/vtable/src/ts-types/base-table.ts
+++ b/packages/vtable/src/ts-types/base-table.ts
@@ -101,7 +101,7 @@ import type { IEmptyTip } from './component/empty-tip';
import type { EmptyTip } from '../components/empty-tip/empty-tip';
import type { CustomCellStylePlugin } from '../plugins/custom-cell-style';
import type { EditManeger } from '../edit/edit-manager';
-import type { TableAnimationManager } from '../core/animation';
+import type { ITableAnimationOption, TableAnimationManager } from '../core/animation';
export interface IBaseTableProtected {
element: HTMLElement;
@@ -419,6 +419,8 @@ export interface BaseTableConstructorOptions {
/** adaptive 模式下高度的适应策略 **/
heightAdaptiveMode?: HeightAdaptiveModeDef;
+ autoHeightInAdaptiveMode?: boolean;
+
// /** 行高是否根据内容来计算 */
// autoRowHeight?: boolean;
/** 设备的像素比 不配的话默认获取window.devicePixelRatio */
@@ -504,6 +506,9 @@ export interface BaseTableConstructorOptions {
// 行列移动不更新表格
notUpdateInColumnRowMove?: boolean;
+
+ // 表格是否限制内容高度
+ limitContentHeight?: boolean;
}; // 部分特殊配置,兼容xTable等作用
animationAppear?: boolean | IAnimationAppear;
@@ -857,7 +862,9 @@ export interface BaseTableAPI {
* 滚动到具体某个单元格位置
* @param cellAddr 要滚动到的单元格位置
*/
- scrollToCell: (cellAddr: { col?: number; row?: number }) => void;
+ scrollToCell: (cellAddr: { col?: number; row?: number }, animationOption?: ITableAnimationOption | boolean) => void;
+ scrollToRow: (row: number, animationOption?: ITableAnimationOption | boolean) => void;
+ scrollToCol: (col: number, animationOption?: ITableAnimationOption | boolean) => void;
registerCustomCellStyle: (customStyleId: string, customStyle: ColumnStyleOption | undefined | null) => void;
arrangeCustomCellStyle: (cellPos: { col?: number; row?: number; range?: CellRange }, customStyleId: string) => void;
diff --git a/packages/vue-vtable/src/tables/base-table.vue b/packages/vue-vtable/src/tables/base-table.vue
index bc5631ce4..ef26edfc1 100644
--- a/packages/vue-vtable/src/tables/base-table.vue
+++ b/packages/vue-vtable/src/tables/base-table.vue
@@ -1,5 +1,6 @@
-
+