]*>([\s\S]*?)<\/tr>/gi; // for webpack3
+ // const cellRegex = /| ]*>(.*?)<\/td>/gs; // 匹配 | 标签及其内容
+ const cellRegex = /]*>([\s\S]*?)<\/t[dh]>/gi; // for webpack3
const table = this.table;
- const rows = pastedData.split('\n'); // 将数据拆分为行
- const values: (string | number)[][] = [];
+ const pasteRange = pasteContext.pasteRange;
+ const { col, row, maxCol, maxRow } = pasteRange;
+ let pasteValuesColCount = 0;
+ let pasteValuesRowCount = 0;
+ let values: (string | number)[][] = [];
- rows.forEach(function (rowCells: any) {
- const cells = rowCells.split('\t'); // 将行数据拆分为单元格
- const rowValues: (string | number)[] = [];
- values.push(rowValues);
- cells.forEach(function (cell: string, cellIndex: number) {
- // 去掉单元格数据末尾的 '\r'
- if (cellIndex === cells.length - 1) {
- cell = cell.trim();
- }
- rowValues.push(cell);
+ if (!pastedData || !/标签中的内容
+ const cellMatches: RegExpMatchArray[] = Array.from(rowContent.matchAll(cellRegex)); // 获取| / | 标签及其内容
+ if (!cellMatches.length) {
+ continue;
+ }
+ const rowValues = cellMatches.map(cellMatch => {
+ return this.decodeHTMLCellValue(cellMatch[1]);
+ // .trim(); // 去除首尾空格
});
- });
+ values.push(rowValues);
+ pasteValuesColCount = Math.max(pasteValuesColCount, rowValues?.length ?? 0);
+ }
+
+ pasteValuesRowCount = values.length ?? 0;
+ if (!pasteValuesRowCount || !pasteValuesColCount) {
+ return this.getEmptyPasteResult();
+ }
+ values = this.handlePasteValues(
+ values,
+ pasteValuesRowCount,
+ pasteValuesColCount,
+ maxRow - row + 1,
+ maxCol - col + 1
+ );
let processedValues;
// 检查是否支持公式处理(针对vtable-sheet)
- if (table.options.keyboardOptions?.processFormulaBeforePaste && this.copySourceRange) {
- // 利用复制时记录的源位置,对粘贴的数据进行公式处理
+ if (table.options.keyboardOptions?.processFormulaBeforePaste && pasteContext.copySourceRange) {
+ // 使用复制时记录的源位置(而不是当前的选中位置)
processedValues = table.options.keyboardOptions.processFormulaBeforePaste(
values,
- this.copySourceRange.startCol,
- this.copySourceRange.startRow,
+ pasteContext.copySourceRange.startCol,
+ pasteContext.copySourceRange.startRow,
col,
row
);
}
- // 保持与 navigator.clipboard.read 中的操作一致
- const changedCellResults = await (table as ListTableAPI).changeCellValues(
- col,
- row,
- processedValues ? processedValues : values,
- true
- );
- if (table.hasListeners(TABLE_EVENT_TYPE.PASTED_DATA)) {
- table.fireListeners(TABLE_EVENT_TYPE.PASTED_DATA, {
- col,
- row,
- pasteData: processedValues ? processedValues : values,
- changedCellResults
- });
+ const valuesToPaste = processedValues ? processedValues : values;
+ const targetCells = this.getPasteTargetCells(pasteRange, valuesToPaste);
+ if (!this.isPasteOperationCurrent(pasteContext.pasteOperationId)) {
+ return this.getEmptyPasteResult();
}
- }
- // 清空选中区域的内容
- private clearCutArea(table: ListTableAPI): void {
- try {
- const ranges = this.cutRanges;
- if (!ranges || ranges.length === 0) {
- return;
+ if (pasteContext.cutState) {
+ const validatedCellResults = await this.validatePasteTargetCells(pasteRange, valuesToPaste);
+ if (!this.isPasteOperationCurrent(pasteContext.pasteOperationId)) {
+ return this.getEmptyPasteResult();
+ }
+ if (!this.isFullPasteAllowed(valuesToPaste, validatedCellResults)) {
+ return this.getEmptyPasteResult(validatedCellResults);
}
-
- table.changeCellValuesByRanges(ranges, '');
- } catch (error) {
- console.error('清空单元格内容失败', error);
}
- }
-
- // 检查剪贴板内容是否被其他应用更改
- private async checkClipboardChanged(): Promise {
- // 如果不支持读取剪贴板,则无法检测变化
- if (!navigator.clipboard || !navigator.clipboard.readText) {
- return false;
+ const rawChangedCellResults = await (table as ListTableAPI).changeCellValues(
+ col,
+ row,
+ valuesToPaste,
+ !pasteContext.cutState,
+ true,
+ undefined,
+ () => !this.isPasteOperationCurrent(pasteContext.pasteOperationId)
+ );
+ if (!this.isPasteOperationCurrent(pasteContext.pasteOperationId)) {
+ return this.getEmptyPasteResult();
}
-
- try {
- const currentContent = await navigator.clipboard.readText();
- // console.log('当前剪贴板内容:', currentContent);
- // console.log('上次保存的剪贴板内容:', this.lastClipboardContent);
-
- // 比较当前剪贴板内容与剪切时保存的内容
- return currentContent !== this.lastClipboardContent;
- } catch (err) {
- console.warn('检查剪贴板状态失败:', err);
- // 出错时假设剪贴板未变化
- return false;
+ const changedCellResults = this.normalizeChangedCellResults(rawChangedCellResults, pasteRange, valuesToPaste);
+ const changedCells = this.getChangedCells(targetCells, changedCellResults);
+ const migratedSourceCells = this.getMigratedSourceCells(pasteContext.cutState, changedCellResults);
+ if (!changedCells.length) {
+ return this.getEmptyPasteResult(changedCellResults);
}
+ this.firePastedDataEvent(col, row, valuesToPaste, changedCellResults);
+ return { pasted: true, changedCellResults, changedCells, migratedSourceCells };
}
- // 保存剪贴板内容
- private saveClipboardContent(): void {
- // 尝试获取剪贴板内容
- if (navigator.clipboard && navigator.clipboard.readText) {
- // 延迟一点以确保剪贴板内容已更新
- setTimeout(() => {
- navigator.clipboard
- .readText()
- .then(text => {
- this.lastClipboardContent = text;
- console.log('已保存剪贴板状态');
- })
- .catch(err => {
- console.warn('无法读取剪贴板内容:', err);
- });
- }, 50);
- }
- }
- private pasteHtmlToTable(item: ClipboardItem) {
- // const regex = /]*>(.*?)<\/tr>/gs; // 匹配 标签及其内容
- const regex = / ]*>([\s\S]*?)<\/tr>/g; // for webpack3
- // const cellRegex = /| ]*>(.*?)<\/td>/gs; // 匹配 | 标签及其内容
- const cellRegex = / | ]*>([\s\S]*?)<\/td>/g; // for webpack3
- const table = this.table;
- const ranges = table.stateManager.select.ranges;
- const selectRangeLength = ranges.length;
- const col = Math.min(ranges[selectRangeLength - 1].start.col, ranges[selectRangeLength - 1].end.col);
- const row = Math.min(ranges[selectRangeLength - 1].start.row, ranges[selectRangeLength - 1].end.row);
- const maxCol = Math.max(ranges[selectRangeLength - 1].start.col, ranges[selectRangeLength - 1].end.col);
- const maxRow = Math.max(ranges[selectRangeLength - 1].start.row, ranges[selectRangeLength - 1].end.row);
- let pasteValuesColCount = 0;
- let pasteValuesRowCount = 0;
- let values: (string | number)[][] = [];
- item.getType('text/html').then((blob: any) => {
- blob.text().then(async (pastedData: any) => {
- // 解析html数据
- if (pastedData && /(标签中的内容
- const cellMatches: RegExpMatchArray[] = Array.from(rowContent.matchAll(cellRegex)); // 获取标签中的内容
- const rowValues = cellMatches.map(cellMatch => {
- return (
- cellMatch[1]
- .replace(/(<(?!br)([^>]+)>)/gi, '') // 除了 标签以外的所有 HTML 标签都替换为空字符串
- .replace(/ [\r\n]?/gim, '\n') // 将字符串中的 标签以及其后可能存在的空白字符和斜杠都替换为换行符 \n
- // .replace(/ /g, '\n') // 替换 标签为换行符
- // .replace(/<(?:.|\n)*?>/gm, '') // 去除HTML标签
- //将字符串中的 HTML 实体字符转换为原始的字符
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/ /gi, '\t')
- .replace(/ /g, ' ')
- );
- // .trim(); // 去除首尾空格
- });
- values.push(rowValues);
- pasteValuesColCount = Math.max(pasteValuesColCount, rowValues?.length ?? 0);
- }
- pasteValuesRowCount = values.length ?? 0;
- values = this.handlePasteValues(
- values,
- pasteValuesRowCount,
- pasteValuesColCount,
- maxRow - row + 1,
- maxCol - col + 1
- );
- let processedValues;
- // 检查是否支持公式处理(针对vtable-sheet)
- if (table.options.keyboardOptions?.processFormulaBeforePaste && this.copySourceRange) {
- // 使用复制时记录的源位置(而不是当前的选中位置)
- processedValues = table.options.keyboardOptions.processFormulaBeforePaste(
- values,
- this.copySourceRange.startCol,
- this.copySourceRange.startRow,
- col,
- row
- );
- }
-
- const changedCellResults = await (table as ListTableAPI).changeCellValues(
- col,
- row,
- processedValues ? processedValues : values,
- true
- );
- if (table.hasListeners(TABLE_EVENT_TYPE.PASTED_DATA)) {
- table.fireListeners(TABLE_EVENT_TYPE.PASTED_DATA, {
- col,
- row,
- pasteData: processedValues ? processedValues : values,
- changedCellResults
- });
- }
- } else {
- navigator.clipboard.read().then(clipboardItems => {
- for (const item of clipboardItems) {
- if (item.types.includes('text/plain')) {
- item.getType('text/plain').then((blob: Blob) => {
- blob.text().then(data => this._pasteValue(data));
- });
- }
- }
- });
- }
- });
- });
- }
-
- private async _pasteValue(pastedData: string) {
+ // 处理粘贴的文本数据
+ private async processPastedText(pastedData: string, pasteContext: PasteContext): Promise {
const table = this.table;
- const ranges = table.stateManager.select.ranges;
- const selectRangeLength = ranges.length;
- const col = Math.min(ranges[selectRangeLength - 1].start.col, ranges[selectRangeLength - 1].end.col);
- const row = Math.min(ranges[selectRangeLength - 1].start.row, ranges[selectRangeLength - 1].end.row);
- const maxCol = Math.max(ranges[selectRangeLength - 1].start.col, ranges[selectRangeLength - 1].end.col);
- const maxRow = Math.max(ranges[selectRangeLength - 1].start.row, ranges[selectRangeLength - 1].end.row);
- let pasteValuesColCount = 0;
- let pasteValuesRowCount = 0;
- let values: (string | number)[][] = [];
- const rows = pastedData.split('\n'); // 将数据拆分为行
- rows.forEach(function (rowCells: any, rowIndex: number) {
- const cells = rowCells.split('\t'); // 将行数据拆分为单元格
- const rowValues: (string | number)[] = [];
- values.push(rowValues);
- cells.forEach(function (cell: string, cellIndex: number) {
- // 去掉单元格数据末尾的 '\r'
- if (cellIndex === cells.length - 1) {
- cell = cell.trim();
- }
- rowValues.push(cell);
- });
- pasteValuesColCount = Math.max(pasteValuesColCount, rowValues?.length ?? 0);
- });
- pasteValuesRowCount = values.length ?? 0;
+ const pasteRange = pasteContext.pasteRange;
+ const { col, row, maxCol, maxRow } = pasteRange;
+ let values = this.parsePlainTextPastedData(pastedData);
+ const pasteValuesRowCount = values.length;
+ const pasteValuesColCount = Math.max(...values.map(rowValues => rowValues.length));
+ if (!pasteValuesRowCount || !pasteValuesColCount) {
+ return this.getEmptyPasteResult();
+ }
values = this.handlePasteValues(
values,
pasteValuesRowCount,
@@ -1305,71 +1877,347 @@ export class EventManager {
);
let processedValues;
// 检查是否支持公式处理(针对vtable-sheet)
- if (table.options.keyboardOptions?.processFormulaBeforePaste && this.copySourceRange) {
+ if (table.options.keyboardOptions?.processFormulaBeforePaste && pasteContext.copySourceRange) {
// 利用复制时记录的源位置,对粘贴的数据进行公式处理
processedValues = table.options.keyboardOptions.processFormulaBeforePaste(
values,
- this.copySourceRange.startCol,
- this.copySourceRange.startRow,
+ pasteContext.copySourceRange.startCol,
+ pasteContext.copySourceRange.startRow,
col,
row
);
}
+
+ const valuesToPaste = processedValues ? processedValues : values;
+ const targetCells = this.getPasteTargetCells(pasteRange, valuesToPaste);
// 保持与 navigator.clipboard.read 中的操作一致
- const changedCellResults = await (table as ListTableAPI).changeCellValues(
+ if (!this.isPasteOperationCurrent(pasteContext.pasteOperationId)) {
+ return this.getEmptyPasteResult();
+ }
+ if (pasteContext.cutState) {
+ const validatedCellResults = await this.validatePasteTargetCells(pasteRange, valuesToPaste);
+ if (!this.isPasteOperationCurrent(pasteContext.pasteOperationId)) {
+ return this.getEmptyPasteResult();
+ }
+ if (!this.isFullPasteAllowed(valuesToPaste, validatedCellResults)) {
+ return this.getEmptyPasteResult(validatedCellResults);
+ }
+ }
+ const rawChangedCellResults = await (table as ListTableAPI).changeCellValues(
col,
row,
- processedValues ? processedValues : values,
- true
+ valuesToPaste,
+ !pasteContext.cutState,
+ true,
+ undefined,
+ () => !this.isPasteOperationCurrent(pasteContext.pasteOperationId)
);
- if (table.hasListeners(TABLE_EVENT_TYPE.PASTED_DATA)) {
+ if (!this.isPasteOperationCurrent(pasteContext.pasteOperationId)) {
+ return this.getEmptyPasteResult();
+ }
+ const changedCellResults = this.normalizeChangedCellResults(rawChangedCellResults, pasteRange, valuesToPaste);
+ const changedCells = this.getChangedCells(targetCells, changedCellResults);
+ const migratedSourceCells = this.getMigratedSourceCells(pasteContext.cutState, changedCellResults);
+ if (!changedCells.length) {
+ return this.getEmptyPasteResult(changedCellResults);
+ }
+ this.firePastedDataEvent(col, row, valuesToPaste, changedCellResults);
+ return { pasted: true, changedCellResults, changedCells, migratedSourceCells };
+ }
+
+ private firePastedDataEvent(
+ col: number,
+ row: number,
+ pasteData: (string | number)[][],
+ changedCellResults: any
+ ): void {
+ const table = this.table;
+ if (!table.hasListeners(TABLE_EVENT_TYPE.PASTED_DATA)) {
+ return;
+ }
+ try {
table.fireListeners(TABLE_EVENT_TYPE.PASTED_DATA, {
col,
row,
- pasteData: processedValues ? processedValues : values,
+ pasteData,
changedCellResults
});
+ } catch (error) {
+ console.warn('PASTED_DATA listener failed:', error);
}
}
- private async pasteTextToTable(item: ClipboardItem) {
- const table = this.table;
- // 如果只有 'text/plain'
- const ranges = table.stateManager.select.ranges;
- const selectRangeLength = ranges.length;
- const col = Math.min(ranges[selectRangeLength - 1].start.col, ranges[selectRangeLength - 1].end.col);
- const row = Math.min(ranges[selectRangeLength - 1].start.row, ranges[selectRangeLength - 1].end.row);
- const maxCol = Math.max(ranges[selectRangeLength - 1].start.col, ranges[selectRangeLength - 1].end.col);
- const maxRow = Math.max(ranges[selectRangeLength - 1].start.row, ranges[selectRangeLength - 1].end.row);
+ private getEmptyPasteResult(changedCellResults: boolean[][] = []): PasteWriteResult {
+ return {
+ pasted: false,
+ changedCellResults,
+ changedCells: [],
+ migratedSourceCells: []
+ };
+ }
- try {
- const blob = await item.getType('text/plain');
- const pastedData = await blob.text();
- const values = this.parsePastedData(pastedData);
+ private getPasteTargetCells(pasteRange: PasteRange, values: (string | number)[][]): SourceCell[][] {
+ const targetCells: SourceCell[][] = [];
+ for (let rowIndex = 0; rowIndex < values.length; rowIndex++) {
+ targetCells[rowIndex] = [];
+ for (let colIndex = 0; colIndex < values[rowIndex].length; colIndex++) {
+ targetCells[rowIndex][colIndex] = this.getCellIdentity(pasteRange.col + colIndex, pasteRange.row + rowIndex);
+ }
+ }
+ return targetCells;
+ }
- const pasteValuesRowCount = values.length;
- const pasteValuesColCount = Math.max(...values.map(row => row.length), 0);
+ private getChangedCells(targetCells: SourceCell[][], changedCellResults: boolean[][]): SourceCell[] {
+ const changedCells: SourceCell[] = [];
+ for (let rowIndex = 0; rowIndex < changedCellResults.length; rowIndex++) {
+ for (let colIndex = 0; colIndex < (changedCellResults[rowIndex]?.length ?? 0); colIndex++) {
+ if (changedCellResults[rowIndex][colIndex] && targetCells[rowIndex]?.[colIndex]) {
+ changedCells.push(targetCells[rowIndex][colIndex]);
+ }
+ }
+ }
+ return changedCells;
+ }
- const processedValues = this.handlePasteValues(
- values,
- pasteValuesRowCount,
- pasteValuesColCount,
- maxRow - row + 1,
- maxCol - col + 1
- );
+ private normalizeChangedCellResults(
+ changedCellResults: boolean[][] | void,
+ pasteRange: PasteRange,
+ values: (string | number)[][]
+ ): boolean[][] {
+ if (Array.isArray(changedCellResults)) {
+ return changedCellResults;
+ }
+ const table = this.table as any;
+ return values.map((rowValues, rowIndex) =>
+ rowValues.map((_value, colIndex) => {
+ const targetCol = pasteRange.col + colIndex;
+ const targetRow = pasteRange.row + rowIndex;
+ return (
+ targetCol < table.colCount &&
+ targetRow < table.rowCount &&
+ (!table.isHasEditorDefine || table.isHasEditorDefine(targetCol, targetRow))
+ );
+ })
+ );
+ }
- const changedCellResults = await (table as ListTableAPI).changeCellValues(col, row, processedValues, true);
+ private async validatePasteTargetCells(pasteRange: PasteRange, values: (string | number)[][]): Promise {
+ const table = this.table as ListTableAPI;
+ const changedCellResults: boolean[][] = [];
+ for (let rowIndex = 0; rowIndex < values.length; rowIndex++) {
+ changedCellResults[rowIndex] = [];
+ for (let colIndex = 0; colIndex < values[rowIndex].length; colIndex++) {
+ const targetCol = pasteRange.col + colIndex;
+ const targetRow = pasteRange.row + rowIndex;
+ const tableAny = table as any;
+ let canChange =
+ targetCol < tableAny.colCount &&
+ targetRow < tableAny.rowCount &&
+ (!tableAny.isHasEditorDefine || tableAny.isHasEditorDefine(targetCol, targetRow));
+ if (canChange) {
+ const editor = tableAny.getEditor?.(targetCol, targetRow);
+ const oldValue = table.getCellOriginValue?.(targetCol, targetRow);
+ const validateResult =
+ editor?.validateValue?.(values[rowIndex][colIndex], oldValue, { col: targetCol, row: targetRow }, table) ??
+ true;
+ const resolvedValidateResult =
+ validateResult && typeof (validateResult as Promise).then === 'function'
+ ? await validateResult
+ : validateResult;
+ canChange =
+ resolvedValidateResult === true ||
+ resolvedValidateResult === 'validate-exit' ||
+ resolvedValidateResult === 'validate-not-exit';
+ }
+ changedCellResults[rowIndex][colIndex] = canChange;
+ }
+ }
+ return changedCellResults;
+ }
- if (table.hasListeners(TABLE_EVENT_TYPE.PASTED_DATA)) {
- table.fireListeners(TABLE_EVENT_TYPE.PASTED_DATA, {
- col,
- row,
- pasteData: processedValues,
- changedCellResults
+ private isFullPasteAllowed(values: (string | number)[][], changedCellResults: boolean[][]): boolean {
+ return values.every((rowValues, rowIndex) =>
+ rowValues.every((_value, colIndex) => changedCellResults[rowIndex]?.[colIndex] === true)
+ );
+ }
+
+ private getMigratedSourceCells(cutState: CutState | null | undefined, changedCellResults: boolean[][]): SourceCell[] {
+ if (!cutState?.sourceCells.length || !cutState.sourceColCount || !cutState.sourceRowCount) {
+ return [];
+ }
+ const migratedSourceCells: SourceCell[] = [];
+ const migratedKeys = new Set();
+ if (!cutState.isSingleRange) {
+ for (let rowIndex = 0; rowIndex < changedCellResults.length; rowIndex++) {
+ for (let colIndex = 0; colIndex < (changedCellResults[rowIndex]?.length ?? 0); colIndex++) {
+ if (!changedCellResults[rowIndex][colIndex]) {
+ continue;
+ }
+ const sourceRow = rowIndex % cutState.sourceRowCount;
+ const sourceCol = colIndex % cutState.sourceColCount;
+ const sourceCell = cutState.sourceCellMatrix[sourceRow]?.[sourceCol];
+ if (!sourceCell) {
+ continue;
+ }
+ const sourceKey = this.getCellKey(sourceCell);
+ if (!migratedKeys.has(sourceKey)) {
+ migratedKeys.add(sourceKey);
+ migratedSourceCells.push(sourceCell);
+ }
+ }
+ }
+ return migratedSourceCells.length === cutState.sourceCells.length ? migratedSourceCells : [];
+ }
+ for (let rowIndex = 0; rowIndex < changedCellResults.length; rowIndex++) {
+ for (let colIndex = 0; colIndex < (changedCellResults[rowIndex]?.length ?? 0); colIndex++) {
+ if (!changedCellResults[rowIndex][colIndex]) {
+ continue;
+ }
+ const sourceRow = rowIndex % cutState.sourceRowCount;
+ const sourceCol = colIndex % cutState.sourceColCount;
+ const sourceIndex = sourceRow * cutState.sourceColCount + sourceCol;
+ const sourceCell = cutState.sourceCells[sourceIndex];
+ if (!sourceCell) {
+ continue;
+ }
+ const sourceKey = this.getCellKey(sourceCell);
+ if (migratedKeys.has(sourceKey)) {
+ continue;
+ }
+ migratedKeys.add(sourceKey);
+ migratedSourceCells.push(sourceCell);
+ }
+ }
+ return migratedSourceCells;
+ }
+
+ private getCellIdentity(col: number, row: number): SourceCell {
+ const table = this.table as ListTableAPI;
+ const recordShowIndex = table.getRecordShowIndexByCell?.(col, row);
+ const recordIndex = recordShowIndex >= 0 ? (table as any).dataSource?.getIndexKey?.(recordShowIndex) : undefined;
+ const field = table.internalProps?.layoutMap?.getBody?.(col, row)?.field;
+ return { col, row, recordIndex, field };
+ }
+
+ // 清空选中区域的内容
+ private async clearCutArea(migratedSourceCells: SourceCell[], changedTargetCells: SourceCell[]): Promise {
+ try {
+ const table = this.table as ListTableAPI;
+ const changedTargetKeys = new Set();
+ changedTargetCells.forEach(cell => {
+ changedTargetKeys.add(this.getCellKey(cell));
+ changedTargetKeys.add(this.getCoordKey(cell));
+ });
+ const clearedKeys = new Set();
+ const sourceChanges: { recordIndex: number | number[]; field: any; value: string }[] = [];
+ const coordChanges: { col: number; row: number }[] = [];
+ for (let i = 0; i < migratedSourceCells.length; i++) {
+ const sourceCell = migratedSourceCells[i];
+ const sourceKey = this.getCellKey(sourceCell);
+ if (
+ clearedKeys.has(sourceKey) ||
+ changedTargetKeys.has(sourceKey) ||
+ changedTargetKeys.has(this.getCoordKey(sourceCell))
+ ) {
+ continue;
+ }
+ const currentAddress = this.getCurrentCellAddress(sourceCell);
+ if (!currentAddress) {
+ continue;
+ }
+ if (!this.isSourceCellUnchanged(sourceCell, currentAddress)) {
+ continue;
+ }
+ if (
+ sourceCell.recordIndex !== undefined &&
+ sourceCell.field !== undefined &&
+ (table as any).changeCellValuesByRecords
+ ) {
+ sourceChanges.push({ recordIndex: sourceCell.recordIndex, field: sourceCell.field, value: '' });
+ } else {
+ coordChanges.push(currentAddress);
+ }
+ clearedKeys.add(sourceKey);
+ }
+ if (sourceChanges.length) {
+ (table as any).changeCellValuesByRecords(sourceChanges, {
+ triggerEvent: true,
+ autoRefresh: false
});
}
+ for (let i = 0; i < coordChanges.length; i++) {
+ await table.changeCellValues(coordChanges[i].col, coordChanges[i].row, [['']], false);
+ }
+ if (sourceChanges.length) {
+ (table as any).refreshAfterSourceChange?.();
+ }
+ return true;
} catch (error) {
- // 静默处理粘贴错误,保持原有行为
- console.warn('Paste operation failed:', error);
+ console.error('清空单元格内容失败', error);
+ return false;
+ }
+ }
+
+ private isSourceCellUnchanged(sourceCell: SourceCell, currentAddress: { col: number; row: number }): boolean {
+ const table = this.table as ListTableAPI;
+ return Object.is(table.getCellOriginValue?.(currentAddress.col, currentAddress.row), sourceCell.value);
+ }
+
+ private getCurrentCellAddress(sourceCell: SourceCell): { col: number; row: number } | null {
+ const table = this.table as ListTableAPI;
+ if (sourceCell.recordIndex !== undefined && sourceCell.field !== undefined && table.getCellAddrByFieldRecord) {
+ const address = table.getCellAddrByFieldRecord(sourceCell.field, sourceCell.recordIndex);
+ if (address && address.col >= 0 && address.row >= 0) {
+ return address;
+ }
+ return null;
+ }
+ return { col: sourceCell.col, row: sourceCell.row };
+ }
+
+ private getCellKey(cell: SourceCell): string {
+ if (cell.recordIndex !== undefined && cell.field !== undefined) {
+ return `record:${this.getStableKeyPart(cell.recordIndex)}:${this.getStableKeyPart(cell.field)}`;
+ }
+ return this.getCoordKey(cell);
+ }
+
+ private getCoordKey(cell: SourceCell): string {
+ return `coord:${cell.col}:${cell.row}`;
+ }
+
+ private getStableKeyPart(value: any): string {
+ if (value === null) {
+ return 'null';
+ }
+ const valueType = typeof value;
+ if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
+ return `${valueType}:${JSON.stringify(value)}`;
+ }
+ if (Array.isArray(value)) {
+ return `array:[${value.map(item => this.getStableKeyPart(item)).join(',')}]`;
+ }
+ if (valueType === 'object' || valueType === 'function') {
+ let id = this.fieldKeyIds.get(value);
+ if (id === undefined) {
+ id = ++this.fieldKeyId;
+ this.fieldKeyIds.set(value, id);
+ }
+ return `${valueType}:#${id}`;
+ }
+ return `${valueType}:${String(value)}`;
+ }
+
+ private resetCutState(): void {
+ this.cutWaitPaste = false;
+ this.activeCutState = null;
+ this.cutCellRange = null;
+ this.cutRanges = null;
+ if (this.table.keyboardOptions?.showCopyCellBorder) {
+ clearActiveCellRangeState(this.table);
+ }
+ if (this.clipboardCheckTimer) {
+ clearTimeout(this.clipboardCheckTimer);
+ this.clipboardCheckTimer = null;
}
}
@@ -1391,6 +2239,14 @@ export class EventManager {
return values;
}
+ private parsePlainTextPastedData(pastedData: string): (string | number)[][] {
+ const rows = pastedData.replace(/\r\n|\r/g, '\n').split('\n');
+ if (rows.length > 1 && rows[rows.length - 1] === '') {
+ rows.pop();
+ }
+ return rows.map(rowCells => rowCells.split('\t'));
+ }
+
private processCellValue(cell: string): string | number {
if (cell.includes('\n')) {
cell = cell
diff --git a/packages/vtable/src/event/media-click.ts b/packages/vtable/src/event/media-click.ts
index 2634896777..25819d5cad 100644
--- a/packages/vtable/src/event/media-click.ts
+++ b/packages/vtable/src/event/media-click.ts
@@ -339,6 +339,9 @@ export function bindMediaClick(table: BaseTableAPI): void {
if (clickToPreview === false) {
return;
}
+ if (!cellValue) {
+ return;
+ }
const previewManager = getMediaPreviewManager(table);
// 开启蒙版
@@ -390,6 +393,9 @@ export function bindMediaClick(table: BaseTableAPI): void {
if (clickToPreview === false) {
return;
}
+ if (!cellValue) {
+ return;
+ }
const previewManager = getMediaPreviewManager(table);
// 开启蒙版
@@ -431,6 +437,9 @@ export function bindMediaClick(table: BaseTableAPI): void {
if (clickToPreview === false) {
return;
}
+ if (!cellValue) {
+ return;
+ }
const previewManager = getMediaPreviewManager(table);
// 开启蒙版
diff --git a/packages/vtable/src/event/util.ts b/packages/vtable/src/event/util.ts
index 0469ac0a13..fcc388a116 100644
--- a/packages/vtable/src/event/util.ts
+++ b/packages/vtable/src/event/util.ts
@@ -134,7 +134,7 @@ export function setDataToHTML(data: string) {
cells.forEach(function (cell: string, cellIndex: number) {
// 单元格数据处理
const parsedCellData = !cell
- ? ' '
+ ? ''
: cell
.toString()
.replace(/&/g, '&') // replace & with & to prevent XSS attacks
diff --git a/packages/vtable/src/layout/pivot-header-layout.ts b/packages/vtable/src/layout/pivot-header-layout.ts
index c05a9dacb6..4220b87f9b 100644
--- a/packages/vtable/src/layout/pivot-header-layout.ts
+++ b/packages/vtable/src/layout/pivot-header-layout.ts
@@ -2348,6 +2348,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
dimensionKey?: string;
indicatorKey?: string;
value?: string;
+ dataValue?: string;
virtual?: boolean;
role?: CellPivotRole;
} = {};
@@ -2359,6 +2360,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
(colHeader.indicatorKey
? this.getIndicatorInfoByIndicatorKey(colHeader.indicatorKey)?.title ?? ''
: colHeader.value);
+ colHeaderPath.dataValue = colHeader.dataValue;
colHeaderPath.virtual = colHeader.virtual;
colHeaderPath.role = colHeader.role;
headerPaths.colHeaderPaths!.push(colHeaderPath);
@@ -2370,6 +2372,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
dimensionKey?: string;
indicatorKey?: string;
value?: string;
+ dataValue?: string;
virtual?: boolean;
role?: CellPivotRole;
} = {};
@@ -2381,6 +2384,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
(rowHeader.indicatorKey
? this.getIndicatorInfoByIndicatorKey(rowHeader.indicatorKey)?.title ?? ''
: rowHeader.value);
+ rowHeaderPath.dataValue = rowHeader.dataValue;
rowHeaderPath.virtual = rowHeader.virtual;
rowHeaderPath.role = rowHeader.role;
headerPaths.rowHeaderPaths!.push(rowHeaderPath);
@@ -3419,10 +3423,14 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
const isMatch =
(!isValid(currentPath.indicatorKey) &&
dimension.dimensionKey === currentPath.dimensionKey &&
- dimension.value === currentPath.value) ||
+ (isValid(currentPath.dataValue)
+ ? ((dimension as any).dataValue ?? dimension.value) === currentPath.dataValue
+ : dimension.value === currentPath.value)) ||
(isValid(currentPath.indicatorKey) &&
dimension.indicatorKey === currentPath.indicatorKey &&
- ((isValid(dimension.value) && isValid(currentPath.value) && currentPath.value === dimension.value) ||
+ ((isValid(currentPath.dataValue) &&
+ ((dimension as any).dataValue ?? dimension.value) === currentPath.dataValue) ||
+ (isValid(dimension.value) && isValid(currentPath.value) && currentPath.value === dimension.value) ||
!isValid(dimension.value) ||
!isValid(currentPath.value)));
@@ -3621,7 +3629,9 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
.filter((hd: HeaderData) => {
return (
(hd?.field === rowDimension.dimensionKey || hd?.field === rowDimension.indicatorKey) &&
- hd?.title === rowDimension.value
+ (isValid(rowDimension.dataValue)
+ ? ((hd as any).dataValue ?? (hd as any).define?.dataValue ?? hd.title) === rowDimension.dataValue
+ : hd?.title === rowDimension.value)
);
})
.map((hd: HeaderData) => {
@@ -3634,17 +3644,24 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
// 从上述过程中找到的pathCellIds中找到正确匹配完整路径rowHeaderPaths的一个 然后计算row行号
const findedCellIdPath = findedCellIdPaths.find(pathIds => {
const fullCellIds = this.findFullCellIds(pathIds);
+ const matchHeaderPath = (curHd: HeaderData, rowDimensionPath: IDimensionInfo) =>
+ rowDimensionPath.dimensionKey === curHd.field &&
+ (isValid(rowDimensionPath.dataValue)
+ ? ((curHd as any).dataValue ?? (curHd as any).define?.dataValue ?? curHd.title) ===
+ rowDimensionPath.dataValue
+ : rowDimensionPath.value === curHd.title);
return (
fullCellIds.length === rowHeaderPaths.length &&
- fullCellIds.every(id => {
- const curHd = this._headerObjectMap[id];
- return rowHeaderPaths.find(rowDimensionPath => {
- return rowDimensionPath.dimensionKey === curHd.field && rowDimensionPath.value === curHd.title;
- });
- })
+ (rowHeaderPaths.some(rowDimensionPath => isValid(rowDimensionPath.dataValue))
+ ? fullCellIds.every((id, index) => matchHeaderPath(this._headerObjectMap[id], rowHeaderPaths[index]))
+ : fullCellIds.every(id =>
+ rowHeaderPaths.some(rowDimensionPath => matchHeaderPath(this._headerObjectMap[id], rowDimensionPath))
+ ))
);
});
- row = this._rowHeaderCellIds.indexOf(findedCellIdPath) + this.columnHeaderLevelCount;
+ if (findedCellIdPath) {
+ row = this._rowHeaderCellIds.indexOf(findedCellIdPath) + this.columnHeaderLevelCount;
+ }
} else {
rowDimensionFinded = this.matchDimensionPath(rowHeaderPaths, this.rowTree, needLowestLevel_rowPaths, true) as
| ITreeLayoutHeadNode
@@ -3659,7 +3676,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
}
}
// 通过dimension获取col和row
- if (rowDimensionFinded || forceBody) {
+ if (rowDimensionFinded || (forceBody && !isValid(row))) {
row = this.columnHeaderLevelCount;
const { startInTotal, afterSpanLevel } = (rowDimensionFinded as ITreeLayoutHeadNode) ?? defaultDimension;
row += startInTotal ?? 0;
@@ -4254,7 +4271,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
this.dataset.collectedValues[key]?.[
path
.map(pathObj => {
- return pathObj.value;
+ return pathObj.dataValue ?? pathObj.value;
})
.join(this.dataset.stringJoinChar)
];
@@ -4287,7 +4304,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
this.dataset.collectedValues[key]?.[
path
.map(pathObj => {
- return pathObj.value;
+ return pathObj.dataValue ?? pathObj.value;
})
.join(this.dataset.stringJoinChar)
];
@@ -4358,7 +4375,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
if (path.colHeaderPaths.length) {
path.colHeaderPaths.forEach(path => {
if (path.dimensionKey) {
- colKey.push(path.value);
+ colKey.push(path.dataValue ?? path.value);
}
});
}
@@ -4374,7 +4391,7 @@ export class PivotHeaderLayoutMap implements LayoutMapAPI {
if (path.rowHeaderPaths.length) {
path.rowHeaderPaths.forEach(path => {
if (path.dimensionKey) {
- rowKey.push(path.value);
+ rowKey.push(path.dataValue ?? path.value);
}
});
}
diff --git a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts
index bc7d557538..bdc4cf3e64 100644
--- a/packages/vtable/src/scenegraph/group-creater/cell-helper.ts
+++ b/packages/vtable/src/scenegraph/group-creater/cell-helper.ts
@@ -43,6 +43,11 @@ import { onBeforeAttributeUpdateForInvertHighlight } from '../../plugins/invert-
import { getCellBorderStrokeWidth } from '../utils/cell-border-stroke-width';
import type { CreateSwitchCellGroup } from './cell-type/switch-cell';
import type { CreateButtonCellGroup } from './cell-type/button-cell';
+import {
+ createCornerCustomMergeContainer,
+ isCornerCustomMergeRange,
+ shouldRenderCornerCustomMergeContent
+} from '../utils/corner-custom-merge';
const PROMISE_CELL_UPDATE_TOKEN_KEY = '__vtable_promise_cell_update_token__';
@@ -590,9 +595,23 @@ function _generateCustomElementsGroup(
) {
let customElementsGroup;
let renderDefault = true;
+ const shouldRenderContent = shouldRenderCornerCustomMergeContent(col, row, range, table);
+ if (!shouldRenderContent) {
+ return {
+ customElementsGroup,
+ renderDefault: false
+ };
+ }
+
if (customResult) {
// custom merge custom render
- customElementsGroup = customResult.elementsGroup;
+ customElementsGroup = createCornerCustomMergeContainer(
+ customResult.elementsGroup,
+ cellWidth,
+ cellHeight,
+ range,
+ table
+ );
renderDefault = customResult.renderDefault;
} else if (range?.isCustom && !table.isCornerHeader(col, row)) {
// 判断不是角头单元格,来兼容corner中设置的customLayout
@@ -610,11 +629,13 @@ function _generateCustomElementsGroup(
customLayout = define?.customLayout;
}
if (customLayout || customRender) {
+ const customCol = range && isCornerCustomMergeRange(range, table) ? range.start.col : col;
+ const customRow = range && isCornerCustomMergeRange(range, table) ? range.start.row : row;
const customResult = dealWithCustom(
customLayout,
customRender,
- col,
- row,
+ customCol,
+ customRow,
cellWidth,
cellHeight,
false,
@@ -623,7 +644,13 @@ function _generateCustomElementsGroup(
range,
table
);
- customElementsGroup = customResult.elementsGroup;
+ customElementsGroup = createCornerCustomMergeContainer(
+ customResult.elementsGroup,
+ cellWidth,
+ cellHeight,
+ range,
+ table
+ );
renderDefault = customResult.renderDefault;
}
}
@@ -675,7 +702,7 @@ export function updateCell(
// cellTheme.group.cornerRadius = getCellCornerRadius(col, row, table);
// }
- if (customLayout || customRender) {
+ if ((customLayout || customRender) && shouldRenderCornerCustomMergeContent(col, row, customMergeRange, table)) {
customResult = dealWithCustom(
customLayout,
customRender,
@@ -1364,6 +1391,24 @@ export function getCustomCellMergeCustom(col: number, row: number, cellGroup: Gr
} = customMerge;
if (customMergeLayout || customMergeRender) {
+ const rangeHeight = table.getRowHeight(row);
+ const rangeWidth = table.getColWidth(col);
+ const contentWidth = table.getColsWidth(customMergeRange.start.col, customMergeRange.end.col);
+ const contentHeight = table.getRowsHeight(customMergeRange.start.row, customMergeRange.end.row);
+
+ cellGroup.contentWidth = contentWidth;
+ cellGroup.contentHeight = contentHeight;
+
+ if (!shouldRenderCornerCustomMergeContent(col, row, customMergeRange, table)) {
+ const customContainer = cellGroup.getChildByName(CUSTOM_CONTAINER_NAME);
+ if (customContainer) {
+ cellGroup.removeChild(customContainer);
+ }
+ cellGroup.setAttribute('clip', true);
+ resizeCellGroup(cellGroup, rangeWidth, rangeHeight, customMergeRange, table);
+ return customMergeRange;
+ }
+
const customResult = dealWithCustom(
customMergeLayout,
customMergeRender,
@@ -1378,7 +1423,13 @@ export function getCustomCellMergeCustom(col: number, row: number, cellGroup: Gr
table
);
- const customElementsGroup = customResult.elementsGroup;
+ const customElementsGroup = createCornerCustomMergeContainer(
+ customResult.elementsGroup,
+ contentWidth,
+ contentHeight,
+ customMergeRange,
+ table
+ );
if (cellGroup.childrenCount > 0 && customElementsGroup) {
cellGroup.insertBefore(customElementsGroup, cellGroup.firstChild);
@@ -1386,15 +1437,10 @@ export function getCustomCellMergeCustom(col: number, row: number, cellGroup: Gr
cellGroup.appendChild(customElementsGroup);
}
- const rangeHeight = table.getRowHeight(row);
- const rangeWidth = table.getColWidth(col);
-
- const { width: contentWidth } = cellGroup.attribute;
- const { height: contentHeight } = cellGroup.attribute;
- cellGroup.contentWidth = contentWidth;
- cellGroup.contentHeight = contentHeight;
-
resizeCellGroup(cellGroup, rangeWidth, rangeHeight, customMergeRange, table);
+ if (isCornerCustomMergeRange(customMergeRange, table)) {
+ cellGroup.setAttribute('clip', false);
+ }
return customMergeRange;
}
diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts
index 8ce3149af2..36ed060fc2 100644
--- a/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts
+++ b/packages/vtable/src/scenegraph/group-creater/cell-type/text-cell.ts
@@ -9,6 +9,7 @@ import type { BaseTableAPI } from '../../../ts-types/base-table';
import type { CellRange } from '../../../ts-types';
import { getCellBorderStrokeWidth } from '../../utils/cell-border-stroke-width';
import { createMark } from '../../graphic/mark';
+import { isCornerCustomMergeRange } from '../../utils/corner-custom-merge';
/**
* @description: 创建单元格场景节点
@@ -42,12 +43,15 @@ export function createCellGroup(
textAlign: CanvasTextAlign,
textBaseline: CanvasTextBaseline,
mayHaveIcon: boolean,
- customElementsGroup: VGroup,
+ customElementsGroup: VGroup | undefined,
renderDefault: boolean,
cellTheme: IThemeSpec,
range: CellRange | undefined,
isAsync: boolean
): Group {
+ const isCornerCustomMergeContentCell =
+ isCornerCustomMergeRange(range, table) && col === range.end.col && row === range.end.row;
+ // The carrier cell must allow the clipped merge container to span internal cell bounds.
const headerStyle = table._getCellStyle(col, row); // to be fixed
const functionalPadding = getFunctionalProp('padding', headerStyle, col, row, table);
if (isValid(functionalPadding)) {
@@ -84,7 +88,7 @@ export function createCellGroup(
cursor: (cellTheme?.group as any)?.cursor ?? undefined,
lineDash: cellTheme?.group?.lineDash ?? undefined,
lineCap: 'butt',
- clip: true,
+ clip: !isCornerCustomMergeContentCell,
cornerRadius: cellTheme.group.cornerRadius
} as any);
}
@@ -106,7 +110,7 @@ export function createCellGroup(
lineCap: 'butt',
- clip: true,
+ clip: !isCornerCustomMergeContentCell,
cornerRadius: cellTheme.group.cornerRadius
} as any);
@@ -118,6 +122,12 @@ export function createCellGroup(
if (customElementsGroup) {
cellGroup.appendChild(customElementsGroup);
}
+ if (isCornerCustomMergeContentCell) {
+ cellGroup.setAttributes({
+ width: Math.max(cellGroup.attribute.width, customElementsGroup?.attribute.width ?? 0),
+ height: Math.max(cellGroup.attribute.height, customElementsGroup?.attribute.height ?? 0)
+ });
+ }
if (renderDefault) {
const textStr: string = value;
let icons;
diff --git a/packages/vtable/src/scenegraph/group-creater/progress/create-group-for-first-screen.ts b/packages/vtable/src/scenegraph/group-creater/progress/create-group-for-first-screen.ts
index 01b31ddeb1..4e16f01836 100644
--- a/packages/vtable/src/scenegraph/group-creater/progress/create-group-for-first-screen.ts
+++ b/packages/vtable/src/scenegraph/group-creater/progress/create-group-for-first-screen.ts
@@ -14,7 +14,9 @@ function fillVisibleBodyRows(proxy: SceneProxy, distRow: number): number {
while (targetRow < bodyBottomRow && table.getRowsHeight(table.frozenRowCount, targetRow) < visibleBodyHeight) {
const nextRow = targetRow + 1;
- computeRowsHeight(table, nextRow, nextRow, false);
+ if (!table.internalProps._heightResizedRowMap.has(nextRow)) {
+ computeRowsHeight(table, nextRow, nextRow, false);
+ }
targetRow = nextRow;
}
@@ -75,25 +77,38 @@ export function createGroupForFirstScreen(
distRow = Math.min(proxy.firstScreenRowLimit - 1, table.rowCount - 1);
}
let bodyDistRow = Math.min(proxy.bodyBottomRow, distRow - table.bottomFrozenRowCount);
- if (table.internalProps._widthResizedColMap.size === 0) {
- // compute colums width in first screen
- computeColsWidth(table, 0, distColForCompute ?? distCol);
+ const colEndForCompute = distColForCompute ?? distCol;
+ let hasUnresizedColumns = false;
+ for (let col = 0; col <= colEndForCompute; col++) {
+ if (!table.internalProps._widthResizedColMap.has(col)) {
+ hasUnresizedColumns = true;
+ break;
+ }
+ }
+ if (hasUnresizedColumns) {
+ // Compute widths for new columns while preserving manually resized columns.
+ computeColsWidth(table, 0, colEndForCompute);
}
- if (table.internalProps._heightResizedRowMap.size === 0) {
- // compute rows height in first screen
- computeRowsHeight(
- table,
- 0,
- table.options.canvasHeight === 'auto' || table.options.customConfig?.forceComputeAllRowHeight
- ? table.rowCount - 1
- : distRowForCompute ?? distRow
- ); //如果配置了 canvasHeight为 'auto', 则一次性将所有行高都计算出来才能满足后续赋值表格高度的使用
- if (table.heightMode === 'autoHeight') {
- bodyDistRow = fillVisibleBodyRows(proxy, bodyDistRow);
- syncVisibleBodyRows(proxy, bodyDistRow);
+ const rowEndForCompute =
+ table.options.canvasHeight === 'auto' || table.options.customConfig?.forceComputeAllRowHeight
+ ? table.rowCount - 1
+ : distRowForCompute ?? distRow;
+ let hasUnresizedRows = false;
+ for (let row = 0; row <= rowEndForCompute; row++) {
+ if (!table.internalProps._heightResizedRowMap.has(row)) {
+ hasUnresizedRows = true;
+ break;
}
}
+ if (hasUnresizedRows) {
+ // compute rows height in first screen
+ computeRowsHeight(table, 0, rowEndForCompute); //如果配置了 canvasHeight为 'auto', 则一次性将所有行高都计算出来才能满足后续赋值表格高度的使用
+ }
+ if (table.heightMode === 'autoHeight') {
+ bodyDistRow = fillVisibleBodyRows(proxy, bodyDistRow);
+ syncVisibleBodyRows(proxy, bodyDistRow);
+ }
if (distCol < table.colCount - table.rightFrozenColCount) {
// compute right frozen row height
diff --git a/packages/vtable/src/scenegraph/layout/compute-col-width.ts b/packages/vtable/src/scenegraph/layout/compute-col-width.ts
index b7940eefe0..0999948ec0 100644
--- a/packages/vtable/src/scenegraph/layout/compute-col-width.ts
+++ b/packages/vtable/src/scenegraph/layout/compute-col-width.ts
@@ -493,9 +493,7 @@ function computeCustomRenderWidth(col: number, row: number, table: BaseTableAPI)
function shouldSkipCustomRenderCellValueForComputation(col: number, row: number, table: BaseTableAPI) {
return (
- table.isListTable() &&
- !table.isHeader(col, row) &&
- !(table.internalProps.dataSource as any)?.dataSourceObj?.records
+ table.isListTable() && !table.isHeader(col, row) && !(table.internalProps.dataSource as any)?.dataSourceObj?.records
);
}
@@ -719,6 +717,10 @@ export function getAdaptiveWidth(
const minWidthColumnMap = new Map();
for (let col = startCol; col < endColPlus1; col++) {
const width = update ? newWidths[col] ?? table.getColWidth(col) : table.getColWidth(col);
+ if (table.widthMode === 'adaptive' && table.internalProps._widthResizedColMap.has(col)) {
+ totalDrawWidth -= width;
+ continue;
+ }
const maxWidth = table.getMaxColWidth(col);
const minWidth = table.getMinColWidth(col);
// if (width !== maxWidth && width !== minWidth) {
diff --git a/packages/vtable/src/scenegraph/layout/compute-row-height.ts b/packages/vtable/src/scenegraph/layout/compute-row-height.ts
index 7ae9074b67..0806eda809 100644
--- a/packages/vtable/src/scenegraph/layout/compute-row-height.ts
+++ b/packages/vtable/src/scenegraph/layout/compute-row-height.ts
@@ -139,14 +139,13 @@ export function computeRowsHeight(
) {
// check fixed style and no wrap situation, fill all row width single compute
// traspose table and row indicator pivot table cannot use single row height
- const height = computeRowHeight(table.columnHeaderLevelCount, 0, table.colCount - 1, table);
- fillRowsHeight(
- height,
- table.columnHeaderLevelCount,
- table.rowCount - 1 - table.bottomFrozenRowCount,
- table,
- update ? newHeights : undefined
- );
+ const fillStartRow = table.columnHeaderLevelCount;
+ const fillEndRow = table.rowCount - 1 - table.bottomFrozenRowCount;
+ const rowForCompute = getFirstUnresizedRow(table, fillStartRow, fillEndRow);
+ if (rowForCompute >= 0) {
+ const height = computeRowHeight(rowForCompute, 0, table.colCount - 1, table);
+ fillRowsHeight(height, fillStartRow, fillEndRow, table, update ? newHeights : undefined);
+ }
//底部冻结的行行高需要单独计算
for (let row = table.rowCount - table.bottomFrozenRowCount; row <= rowEnd; row++) {
const height = computeRowHeight(row, 0, table.colCount - 1, table);
@@ -352,6 +351,10 @@ export function computeRowsHeight(
}
export function computeRowHeight(row: number, startCol: number, endCol: number, table: BaseTableAPI): number {
+ if (table.internalProps._heightResizedRowMap.has(row)) {
+ return table.getRowHeight(row);
+ }
+
return computeRowHeightInternal(row, startCol, endCol, table, true);
}
@@ -607,6 +610,9 @@ function fillRowsHeight(
return;
}
for (let row = startRow; row <= endRow; row++) {
+ if (table.internalProps._heightResizedRowMap.has(row)) {
+ continue;
+ }
if (newHeights) {
newHeights[row] = height;
} else {
@@ -616,6 +622,15 @@ function fillRowsHeight(
table.internalProps.useOneRowHeightFillAll = true;
}
+function getFirstUnresizedRow(table: BaseTableAPI, startRow: number, endRow: number): number {
+ for (let row = startRow; row <= endRow; row++) {
+ if (!table.internalProps._heightResizedRowMap.has(row)) {
+ return row;
+ }
+ }
+ return -1;
+}
+
/**
* @description: compute customRender height
* @param {number} col
diff --git a/packages/vtable/src/scenegraph/layout/update-height.ts b/packages/vtable/src/scenegraph/layout/update-height.ts
index 55a70bc19b..517622040d 100644
--- a/packages/vtable/src/scenegraph/layout/update-height.ts
+++ b/packages/vtable/src/scenegraph/layout/update-height.ts
@@ -18,8 +18,25 @@ import type { IGraphic } from '@src/vrender';
import { getCellMergeRange } from '../../tools/merge-range';
import type { ColumnDefine, ListTableConstructorOptions } from '../../ts-types';
import { Factory } from '../../core/factory';
+import {
+ type CornerCustomMergeRangeUpdateMap,
+ createCornerCustomMergeContainer,
+ flushCornerCustomMergeContentUpdates,
+ isCornerCustomMergeRange,
+ queueCornerCustomMergeContentUpdate,
+ shouldRenderCornerCustomMergeContent
+} from '../utils/corner-custom-merge';
+
+export function updateRowHeight(
+ scene: Scenegraph,
+ row: number,
+ detaY: number,
+ skipTableHeightMap?: boolean,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
+) {
+ const shouldFlushCornerCustomMergeRanges = !pendingCornerCustomMergeRanges;
+ pendingCornerCustomMergeRanges ??= new Map();
-export function updateRowHeight(scene: Scenegraph, row: number, detaY: number, skipTableHeightMap?: boolean) {
// 更新table行高存储
if (!skipTableHeightMap && detaY) {
scene.table._setRowHeight(row, scene.table.getRowHeight(row) + detaY, true);
@@ -37,7 +54,16 @@ export function updateRowHeight(scene: Scenegraph, row: number, detaY: number, s
const height = cell.attribute.height;
// cell.setAttribute('height', height);
// (cell.firstChild as Rect).setAttribute('height', cell.attribute.height);
- updateCellHeightForRow(scene, cell, col, row, height + detaY, detaY, scene.table.isHeader(col, row));
+ updateCellHeightForRow(
+ scene,
+ cell,
+ col,
+ row,
+ height + detaY,
+ detaY,
+ scene.table.isHeader(col, row),
+ pendingCornerCustomMergeRanges
+ );
scene.updateCellContentWhileResize(col, row);
}
@@ -70,6 +96,10 @@ export function updateRowHeight(scene: Scenegraph, row: number, detaY: number, s
}
}
}
+
+ if (shouldFlushCornerCustomMergeRanges) {
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, scene.table);
+ }
}
/**
@@ -83,7 +113,8 @@ export function updateCellHeightForRow(
row: number,
height: number,
detaY: number,
- isHeader: boolean
+ isHeader: boolean,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
// autoRowHeight: boolean
) {
// cell.setAttribute('height', height);
@@ -95,7 +126,7 @@ export function updateCellHeightForRow(
return;
}
- updateCellHeight(scene, cellGroup, col, row, distHeight, detaY, isHeader);
+ updateCellHeight(scene, cellGroup, col, row, distHeight, detaY, isHeader, pendingCornerCustomMergeRanges);
}
export function updateCellHeightForColumn(
@@ -119,7 +150,8 @@ export function updateCellHeight(
row: number,
distHeight: number,
detaY: number,
- isHeader: boolean
+ isHeader: boolean,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
) {
if (cell.attribute.height === distHeight && !cell.needUpdateHeight) {
return;
@@ -168,7 +200,16 @@ export function updateCellHeight(
oldBarCell.release();
// deal with text
- updateMergeCellContentHeight(cell, distHeight, detaY, scene.table.isAutoRowHeight(row), true, scene.table);
+ updateMergeCellContentHeight(
+ cell,
+ distHeight,
+ detaY,
+ scene.table.isAutoRowHeight(row),
+ true,
+ scene.table,
+ true,
+ pendingCornerCustomMergeRanges
+ );
} else if (type === 'sparkline') {
// 目前先采用重新生成节点的方案
cell.removeAllChild();
@@ -199,9 +240,15 @@ export function updateCellHeight(
(cell.getChildByName(CUSTOM_CONTAINER_NAME) as Group) ||
(cell.getChildByName(CUSTOM_MERGE_CONTAINER_NAME) as Group);
if (customContainer) {
- // if (scene.table.reactCustomLayout) {
- // scene.table.reactCustomLayout.removeCustomCell(col, row);
- // }
+ if (scene.table.reactCustomLayout) {
+ const mergeRange = isMergeCellGroup(cell)
+ ? scene.table.getCellRange(cell.mergeStartCol, cell.mergeStartRow)
+ : undefined;
+ const shouldUseMergeStart = mergeRange && isCornerCustomMergeRange(mergeRange, scene.table);
+ const removeCol = shouldUseMergeStart ? mergeRange.start.col : col;
+ const removeRow = shouldUseMergeStart ? mergeRange.start.row : row;
+ scene.table.reactCustomLayout.removeCustomCell(removeCol, removeRow);
+ }
// customContainer.removeAllChild();
let customElementsGroup;
cell.removeChild(customContainer);
@@ -216,8 +263,10 @@ export function updateCellHeight(
const customContainer =
(mergedCell.getChildByName(CUSTOM_CONTAINER_NAME) as Group) ||
(mergedCell.getChildByName(CUSTOM_MERGE_CONTAINER_NAME) as Group);
- customContainer.removeAllChild();
- mergedCell.removeChild(customContainer);
+ if (customContainer) {
+ customContainer.removeAllChild();
+ mergedCell.removeChild(customContainer);
+ }
getCustomCellMergeCustom(col, mergeRow, mergedCell, scene.table);
}
} else {
@@ -248,7 +297,13 @@ export function updateCellHeight(
}
}
- if (customLayout || customRender) {
+ const mergeRange = isMergeCellGroup(cell)
+ ? scene.table.getCellRange(cell.mergeStartCol, cell.mergeStartRow)
+ : undefined;
+ const shouldRenderContent = shouldRenderCornerCustomMergeContent(col, row, mergeRange, scene.table);
+ if (!shouldRenderContent) {
+ renderDefault = false;
+ } else if (customLayout || customRender) {
// const { autoRowHeight } = table.internalProps;
const style = scene.table._getCellStyle(col, row) as ProgressBarStyle;
const padding = getQuadProps(getProp('padding', style, col, row, scene.table));
@@ -258,25 +313,32 @@ export function updateCellHeight(
width = scene.table.getColsWidth(cell.mergeStartCol, cell.mergeEndCol);
height = scene.table.getRowsHeight(cell.mergeStartRow, cell.mergeEndRow);
}
+ let customCol = col;
+ let customRow = row;
+ if (mergeRange && isCornerCustomMergeRange(mergeRange, scene.table)) {
+ customCol = mergeRange.start.col;
+ customRow = mergeRange.start.row;
+ }
const customResult = dealWithCustom(
customLayout,
customRender,
- col,
- row,
+ customCol,
+ customRow,
width,
height,
false,
scene.table.isAutoRowHeight(row),
padding,
- isMergeCellGroup(cell)
- ? {
- start: { col: cell.mergeStartCol, row: cell.mergeStartRow },
- end: { col: cell.mergeEndCol, row: cell.mergeEndRow }
- }
- : undefined,
+ mergeRange,
+ scene.table
+ );
+ customElementsGroup = createCornerCustomMergeContainer(
+ customResult.elementsGroup,
+ width,
+ height,
+ mergeRange,
scene.table
);
- customElementsGroup = customResult.elementsGroup;
renderDefault = customResult.renderDefault;
}
@@ -285,6 +347,12 @@ export function updateCellHeight(
} else if (customElementsGroup) {
cell.appendChild(customElementsGroup);
}
+ if (isMergeCellGroup(cell)) {
+ const mergeRange = scene.table.getCellRange(cell.mergeStartCol, cell.mergeStartRow);
+ if (isCornerCustomMergeRange(mergeRange, scene.table)) {
+ cell.setAttribute('clip', !shouldRenderCornerCustomMergeContent(col, row, mergeRange, scene.table));
+ }
+ }
}
}
// if (renderDefault) {
@@ -301,7 +369,16 @@ export function updateCellHeight(
// scene.table
// );
// }
- updateMergeCellContentHeight(cell, distHeight, detaY, scene.table.isAutoRowHeight(row), renderDefault, scene.table);
+ updateMergeCellContentHeight(
+ cell,
+ distHeight,
+ detaY,
+ scene.table.isAutoRowHeight(row),
+ renderDefault,
+ scene.table,
+ !customContainer,
+ pendingCornerCustomMergeRanges
+ );
}
}
@@ -311,7 +388,9 @@ function updateMergeCellContentHeight(
detaY: number,
autoRowHeight: boolean,
renderDefault: boolean,
- table: BaseTableAPI
+ table: BaseTableAPI,
+ refreshCornerCustomMergeContent = true,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
) {
if (isMergeCellGroup(cellGroup)) {
distHeight = 0;
@@ -375,6 +454,10 @@ function updateMergeCellContentHeight(
}
}
}
+ const mergeRange = table.getCellRange(cellGroup.mergeStartCol, cellGroup.mergeStartRow);
+ if (refreshCornerCustomMergeContent && isCornerCustomMergeRange(mergeRange, table)) {
+ queueCornerCustomMergeContentUpdate(mergeRange, table, pendingCornerCustomMergeRanges);
+ }
} else {
const style = table._getCellStyle(cellGroup.col, cellGroup.row);
const padding = getQuadProps(getProp('padding', style, cellGroup.col, cellGroup.row, table));
diff --git a/packages/vtable/src/scenegraph/layout/update-width.ts b/packages/vtable/src/scenegraph/layout/update-width.ts
index ac06b5a68e..55d156e3dd 100644
--- a/packages/vtable/src/scenegraph/layout/update-width.ts
+++ b/packages/vtable/src/scenegraph/layout/update-width.ts
@@ -17,6 +17,14 @@ import { getProp } from '../utils/get-prop';
import { isMergeCellGroup } from '../utils/is-merge-cell-group';
import { getQuadProps } from '../utils/padding';
import { updateCellContentWidth } from '../utils/text-icon-layout';
+import {
+ type CornerCustomMergeRangeUpdateMap,
+ createCornerCustomMergeContainer,
+ flushCornerCustomMergeContentUpdates,
+ isCornerCustomMergeRange,
+ queueCornerCustomMergeContentUpdate,
+ shouldRenderCornerCustomMergeContent
+} from '../utils/corner-custom-merge';
import { computeRowHeight } from './compute-row-height';
import { updateCellHeightForRow } from './update-height';
import { getHierarchyOffset } from '../utils/get-hierarchy-offset';
@@ -31,7 +39,16 @@ import type { ColumnDefine, ListTableConstructorOptions } from '../../ts-types';
* @param {number} detaX
* @return {*}
*/
-export function updateColWidth(scene: Scenegraph, col: number, detaX: number, skipTableWidthMap?: boolean) {
+export function updateColWidth(
+ scene: Scenegraph,
+ col: number,
+ detaX: number,
+ skipTableWidthMap?: boolean,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
+) {
+ const shouldFlushCornerCustomMergeRanges = !pendingCornerCustomMergeRanges;
+ pendingCornerCustomMergeRanges ??= new Map();
+
if (!skipTableWidthMap) {
scene.table._setColWidth(col, scene.table.getColWidth(col) + detaX, true);
}
@@ -40,33 +57,33 @@ export function updateColWidth(scene: Scenegraph, col: number, detaX: number, sk
const colOrCornerHeaderColumn = scene.getColGroup(col, true) as Group;
const rightTopColumn = scene.getColGroupInRightTopCorner(col);
if (colOrCornerHeaderColumn && !rightTopColumn) {
- updateColunmWidth(colOrCornerHeaderColumn, detaX, 'col-corner', scene);
+ updateColunmWidth(colOrCornerHeaderColumn, detaX, 'col-corner', scene, pendingCornerCustomMergeRanges);
}
// deal with right bottom frozen cells
if (rightTopColumn) {
- updateColunmWidth(rightTopColumn, detaX, 'right-top', scene);
+ updateColunmWidth(rightTopColumn, detaX, 'right-top', scene, pendingCornerCustomMergeRanges);
}
// deal with row header or body or right frozen cells
const rowHeaderOrBodyColumn = scene.getColGroup(col) as Group;
if (rowHeaderOrBodyColumn) {
- updateColunmWidth(rowHeaderOrBodyColumn, detaX, 'row-body', scene);
+ updateColunmWidth(rowHeaderOrBodyColumn, detaX, 'row-body', scene, pendingCornerCustomMergeRanges);
}
const leftBottomColumn = scene.getColGroupInLeftBottomCorner(col);
// deal with left bottom frozen cells
if (leftBottomColumn) {
- updateColunmWidth(leftBottomColumn, detaX, 'left-bottom', scene);
+ updateColunmWidth(leftBottomColumn, detaX, 'left-bottom', scene, pendingCornerCustomMergeRanges);
}
// deal with bottom frozen cells
const bottomColumn = scene.getColGroupInBottom(col);
if (bottomColumn) {
- updateColunmWidth(bottomColumn, detaX, 'bottom', scene);
+ updateColunmWidth(bottomColumn, detaX, 'bottom', scene, pendingCornerCustomMergeRanges);
}
// deal with right bottom frozen cells
const rightBottomColumn = scene.getColGroupInRightBottomCorner(col);
if (rightBottomColumn) {
- updateColunmWidth(rightBottomColumn, detaX, 'right-bottom', scene);
+ updateColunmWidth(rightBottomColumn, detaX, 'right-bottom', scene, pendingCornerCustomMergeRanges);
}
// 更新剩余列位置
@@ -116,6 +133,10 @@ export function updateColWidth(scene: Scenegraph, col: number, detaX: number, sk
});
}
+ if (shouldFlushCornerCustomMergeRanges) {
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, scene.table);
+ }
+
// scene.table.setColWidth(col, rowHeaderOrBodyColumn.attribute.width, true);
}
@@ -124,7 +145,8 @@ function updateColunmWidth(
detaX: number,
// autoRowHeight: boolean,
mode: 'col-corner' | 'row-body' | 'bottom' | 'left-bottom' | 'right-top' | 'right-bottom',
- scene: Scenegraph
+ scene: Scenegraph,
+ pendingCornerCustomMergeRanges: CornerCustomMergeRangeUpdateMap
) {
let needRerangeRow = false;
// const colOrCornerHeaderColumn = scene.getColGroup(col, true) as Group;
@@ -141,7 +163,8 @@ function updateColunmWidth(
oldColumnWidth + detaX,
detaX,
mode === 'row-body' ? cell.col < scene.table.rowHeaderLevelCount : true,
- scene.table.internalProps.autoWrapText
+ scene.table.internalProps.autoWrapText,
+ pendingCornerCustomMergeRanges
);
if (isHeightChange) {
const mergeInfo = getCellMergeInfo(scene.table, cell.col, cell.row);
@@ -246,7 +269,8 @@ function updateCellWidth(
detaX: number,
isHeader: boolean,
// autoColWidth: boolean,
- autoWrapText: boolean
+ autoWrapText: boolean,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
): boolean {
if (cell.attribute.width === distWidth && !cell.needUpdateWidth) {
return false;
@@ -308,7 +332,16 @@ function updateCellWidth(
oldBarCell.release();
// deal width text
- const cellChange = updateMergeCellContentWidth(cellGroup, distWidth, detaX, autoRowHeight, true, scene.table);
+ const cellChange = updateMergeCellContentWidth(
+ cellGroup,
+ distWidth,
+ detaX,
+ autoRowHeight,
+ true,
+ scene.table,
+ true,
+ pendingCornerCustomMergeRanges
+ );
isHeightChange = isHeightChange || cellChange;
} else if (type === 'sparkline') {
// 目前先采用重新生成节点的方案
@@ -362,9 +395,15 @@ function updateCellWidth(
(cell.getChildByName(CUSTOM_MERGE_CONTAINER_NAME) as Group);
if (customContainer) {
// fix for custom component flash
- // if (scene.table.reactCustomLayout) {
- // scene.table.reactCustomLayout.removeCustomCell(col, row);
- // }
+ if (scene.table.reactCustomLayout) {
+ const mergeRange = isMergeCellGroup(cellGroup)
+ ? scene.table.getCellRange(cellGroup.mergeStartCol, cellGroup.mergeStartRow)
+ : undefined;
+ const shouldUseMergeStart = mergeRange && isCornerCustomMergeRange(mergeRange, scene.table);
+ const removeCol = shouldUseMergeStart ? mergeRange.start.col : col;
+ const removeRow = shouldUseMergeStart ? mergeRange.start.row : row;
+ scene.table.reactCustomLayout.removeCustomCell(removeCol, removeRow);
+ }
// customContainer.removeAllChild();
let customElementsGroup;
cell.removeChild(customContainer);
@@ -379,8 +418,10 @@ function updateCellWidth(
const customContainer =
(mergedCell.getChildByName(CUSTOM_CONTAINER_NAME) as Group) ||
(mergedCell.getChildByName(CUSTOM_MERGE_CONTAINER_NAME) as Group);
- customContainer.removeAllChild();
- mergedCell.removeChild(customContainer);
+ if (customContainer) {
+ customContainer.removeAllChild();
+ mergedCell.removeChild(customContainer);
+ }
getCustomCellMergeCustom(mergeCol, row, mergedCell, scene.table);
}
} else {
@@ -411,7 +452,13 @@ function updateCellWidth(
}
}
- if (customLayout || customRender) {
+ const mergeRange = isMergeCellGroup(cellGroup)
+ ? scene.table.getCellRange(cellGroup.mergeStartCol, cellGroup.mergeStartRow)
+ : undefined;
+ const shouldRenderContent = shouldRenderCornerCustomMergeContent(col, row, mergeRange, scene.table);
+ if (!shouldRenderContent) {
+ renderDefault = false;
+ } else if (customLayout || customRender) {
// const { autoRowHeight } = table.internalProps;
const style = scene.table._getCellStyle(col, row) as ProgressBarStyle;
const padding = getQuadProps(getProp('padding', style, col, row, scene.table));
@@ -421,25 +468,32 @@ function updateCellWidth(
width = scene.table.getColsWidth(cellGroup.mergeStartCol, cellGroup.mergeEndCol);
height = scene.table.getRowsHeight(cellGroup.mergeStartRow, cellGroup.mergeEndRow);
}
+ let customCol = col;
+ let customRow = row;
+ if (mergeRange && isCornerCustomMergeRange(mergeRange, scene.table)) {
+ customCol = mergeRange.start.col;
+ customRow = mergeRange.start.row;
+ }
const customResult = dealWithCustom(
customLayout,
customRender,
- col,
- row,
+ customCol,
+ customRow,
width,
height,
false,
scene.table.isAutoRowHeight(row),
padding,
- isMergeCellGroup(cellGroup)
- ? {
- start: { col: cellGroup.mergeStartCol, row: cellGroup.mergeStartRow },
- end: { col: cellGroup.mergeEndCol, row: cellGroup.mergeEndRow }
- }
- : undefined,
+ mergeRange,
+ scene.table
+ );
+ customElementsGroup = createCornerCustomMergeContainer(
+ customResult.elementsGroup,
+ width,
+ height,
+ mergeRange,
scene.table
);
- customElementsGroup = customResult.elementsGroup;
renderDefault = customResult.renderDefault;
isHeightChange = true;
}
@@ -449,6 +503,12 @@ function updateCellWidth(
} else if (customElementsGroup) {
cell.appendChild(customElementsGroup);
}
+ if (isMergeCellGroup(cellGroup)) {
+ const mergeRange = scene.table.getCellRange(cellGroup.mergeStartCol, cellGroup.mergeStartRow);
+ if (isCornerCustomMergeRange(mergeRange, scene.table)) {
+ cell.setAttribute('clip', !shouldRenderCornerCustomMergeContent(col, row, mergeRange, scene.table));
+ }
+ }
}
}
const cellChange = updateMergeCellContentWidth(
@@ -457,7 +517,9 @@ function updateCellWidth(
detaX,
autoRowHeight,
renderDefault,
- scene.table
+ scene.table,
+ !customContainer,
+ pendingCornerCustomMergeRanges
);
isHeightChange = isHeightChange || cellChange;
}
@@ -474,7 +536,9 @@ function updateMergeCellContentWidth(
detaX: number,
autoRowHeight: boolean,
renderDefault: boolean,
- table: BaseTableAPI
+ table: BaseTableAPI,
+ refreshCornerCustomMergeContent = true,
+ pendingCornerCustomMergeRanges?: CornerCustomMergeRangeUpdateMap
) {
if (isMergeCellGroup(cellGroup)) {
distWidth = 0;
@@ -567,6 +631,10 @@ function updateMergeCellContentWidth(
isHeightChange = isHeightChange || changed;
}
}
+ const mergeRange = table.getCellRange(cellGroup.mergeStartCol, cellGroup.mergeStartRow);
+ if (refreshCornerCustomMergeContent && isCornerCustomMergeRange(mergeRange, table)) {
+ queueCornerCustomMergeContentUpdate(mergeRange, table, pendingCornerCustomMergeRanges);
+ }
return isHeightChange;
}
diff --git a/packages/vtable/src/scenegraph/scenegraph.ts b/packages/vtable/src/scenegraph/scenegraph.ts
index fb43b590b6..f2a18bbad8 100644
--- a/packages/vtable/src/scenegraph/scenegraph.ts
+++ b/packages/vtable/src/scenegraph/scenegraph.ts
@@ -512,6 +512,8 @@ export class Scenegraph {
}
releaseStage() {
+ this.clear = true;
+ this._needUpdateContainer = false;
const releaseAppRef = this.releaseVRenderAppRef;
this.releaseVRenderAppRef = undefined;
@@ -994,8 +996,14 @@ export class Scenegraph {
* @param {number} detaX 改变的宽度值
* @return {*}
*/
- updateColWidth(col: number, detaX: number, skipUpdateContainer?: boolean, skipTableWidthMap?: boolean) {
- updateColWidth(this, col, Math.round(detaX), skipTableWidthMap);
+ updateColWidth(
+ col: number,
+ detaX: number,
+ skipUpdateContainer?: boolean,
+ skipTableWidthMap?: boolean,
+ pendingCornerCustomMergeRanges?: Map
+ ) {
+ updateColWidth(this, col, Math.round(detaX), skipTableWidthMap, pendingCornerCustomMergeRanges);
// this.updateContainerWidth(col, detaX);
if (!skipUpdateContainer) {
// this.updateContainerAttrWidthAndX();
@@ -1538,9 +1546,14 @@ export class Scenegraph {
}
}
- updateRowHeight(row: number, detaY: number, skipTableHeightMap?: boolean) {
+ updateRowHeight(
+ row: number,
+ detaY: number,
+ skipTableHeightMap?: boolean,
+ pendingCornerCustomMergeRanges?: Map
+ ) {
detaY = Math.round(detaY);
- updateRowHeight(this, row, detaY, skipTableHeightMap);
+ updateRowHeight(this, row, detaY, skipTableHeightMap, pendingCornerCustomMergeRanges);
this.updateContainerHeight(row, detaY);
}
updateRowsHeight(rows: number[], detaYs: number[], skipTableHeightMap?: boolean) {
@@ -2223,6 +2236,10 @@ export class Scenegraph {
if (!this._needUpdateContainer) {
this._needUpdateContainer = true;
setTimeout(() => {
+ if (!this._needUpdateContainer || this.clear) {
+ this._needUpdateContainer = false;
+ return;
+ }
this.updateContainerSync(updateConfig.needUpdateCellY ?? false);
}, 0);
}
diff --git a/packages/vtable/src/scenegraph/utils/corner-custom-merge.ts b/packages/vtable/src/scenegraph/utils/corner-custom-merge.ts
new file mode 100644
index 0000000000..434eac5545
--- /dev/null
+++ b/packages/vtable/src/scenegraph/utils/corner-custom-merge.ts
@@ -0,0 +1,111 @@
+import type { Group as VGroup } from '@src/vrender';
+import type { CellRange } from '../../ts-types';
+import type { BaseTableAPI } from '../../ts-types/base-table';
+import { CUSTOM_CONTAINER_NAME, CUSTOM_MERGE_CONTAINER_NAME } from '../component/custom';
+import { Group } from '../graphic/group';
+
+export type CornerCustomMergeRangeUpdateMap = Map;
+
+export function isCornerCustomMergeRange(range: CellRange | undefined, table: BaseTableAPI): boolean {
+ return (
+ !!range?.isCustom &&
+ (range.start.col !== range.end.col || range.start.row !== range.end.row) &&
+ table.isCornerHeader(range.start.col, range.start.row) &&
+ table.isCornerHeader(range.end.col, range.end.row)
+ );
+}
+
+export function shouldRenderCornerCustomMergeContent(
+ col: number,
+ row: number,
+ range: CellRange | undefined,
+ table: BaseTableAPI
+): boolean {
+ return !isCornerCustomMergeRange(range, table) || (col === range.end.col && row === range.end.row);
+}
+
+export function createCornerCustomMergeContainer(
+ customElementsGroup: VGroup | undefined,
+ width: number,
+ height: number,
+ range: CellRange | undefined,
+ table: BaseTableAPI
+): VGroup | undefined {
+ if (!customElementsGroup || !isCornerCustomMergeRange(range, table)) {
+ return customElementsGroup;
+ }
+
+ const customContainer = new Group({
+ x: 0,
+ y: 0,
+ width,
+ height,
+ fill: false,
+ stroke: false,
+ pickable: false,
+ clip: true
+ });
+ customContainer.name = CUSTOM_CONTAINER_NAME;
+ customContainer.col = range.start.col;
+ customContainer.row = range.start.row;
+ customContainer.appendChild(customElementsGroup);
+
+ return customContainer as unknown as VGroup;
+}
+
+export function updateCornerCustomMergeContent(range: CellRange, table: BaseTableAPI): void {
+ refreshCornerCustomMergeContent(range, table);
+}
+
+export function queueCornerCustomMergeContentUpdate(
+ range: CellRange,
+ table: BaseTableAPI,
+ pendingRanges?: CornerCustomMergeRangeUpdateMap
+): void {
+ if (!pendingRanges) {
+ refreshCornerCustomMergeContent(range, table);
+ return;
+ }
+
+ pendingRanges.set(getCornerCustomMergeRangeKey(range), range);
+}
+
+export function flushCornerCustomMergeContentUpdates(
+ pendingRanges: CornerCustomMergeRangeUpdateMap,
+ table: BaseTableAPI
+): void {
+ pendingRanges.forEach(range => {
+ refreshCornerCustomMergeContentForResize(range, table);
+ });
+ pendingRanges.clear();
+}
+
+function getCornerCustomMergeRangeKey(range: CellRange): string {
+ return `${range.start.col}-${range.start.row}-${range.end.col}-${range.end.row}`;
+}
+
+function refreshCornerCustomMergeContent(range: CellRange, table: BaseTableAPI): void {
+ const cellGroup = table.scenegraph.getCell(range.end.col, range.end.row);
+ const customContainer =
+ cellGroup.getChildByName(CUSTOM_CONTAINER_NAME) || cellGroup.getChildByName(CUSTOM_MERGE_CONTAINER_NAME);
+
+ if (customContainer) {
+ table.reactCustomLayout?.removeCustomCell(range.start.col, range.start.row);
+ customContainer.removeAllChild();
+ cellGroup.removeChild(customContainer);
+ }
+
+ table.scenegraph.updateCellContent(range.end.col, range.end.row);
+}
+
+function refreshCornerCustomMergeContentForResize(range: CellRange, table: BaseTableAPI): void {
+ const cellGroup = table.scenegraph.getCell(range.end.col, range.end.row);
+ const customContainer =
+ cellGroup.getChildByName(CUSTOM_CONTAINER_NAME) || cellGroup.getChildByName(CUSTOM_MERGE_CONTAINER_NAME);
+
+ if (customContainer) {
+ cellGroup.removeChild(customContainer);
+ }
+
+ table.scenegraph.updateCellContent(range.end.col, range.end.row);
+}
diff --git a/packages/vtable/src/state/resize/update-resize-column.ts b/packages/vtable/src/state/resize/update-resize-column.ts
index 3c7add4245..7d7c7cf41e 100644
--- a/packages/vtable/src/state/resize/update-resize-column.ts
+++ b/packages/vtable/src/state/resize/update-resize-column.ts
@@ -1,5 +1,6 @@
import type { ListTable } from '../../ListTable';
import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout';
+import { flushCornerCustomMergeContentUpdates } from '../../scenegraph/utils/corner-custom-merge';
import type { IndicatorData } from '../../ts-types/list-table/layout-map/api';
import type { StateManager } from '../state';
@@ -133,26 +134,47 @@ export function updateResizeColumn(xInTable: number, yInTable: number, state: St
}
function updateResizeColForColumn(detaX: number, state: StateManager) {
+ const pendingCornerCustomMergeRanges = new Map();
if (state.table.widthMode === 'adaptive' && state.columnResize.col < state.table.colCount - 1) {
// in adaptive mode, the right column width can not be negative
// const rightColWidth = state.table.getColWidth(state.columnResize.col + 1);
// if (rightColWidth - detaX < 0) {
// detaX = rightColWidth;
// }
- state.table.scenegraph.updateColWidth(state.columnResize.col, detaX);
- state.table.scenegraph.updateColWidth(state.columnResize.col + 1, -detaX);
+ state.table.scenegraph.updateColWidth(
+ state.columnResize.col,
+ detaX,
+ undefined,
+ undefined,
+ pendingCornerCustomMergeRanges
+ );
+ state.table.scenegraph.updateColWidth(
+ state.columnResize.col + 1,
+ -detaX,
+ undefined,
+ undefined,
+ pendingCornerCustomMergeRanges
+ );
state.table.internalProps._widthResizedColMap.add(state.columnResize.col);
state.table.internalProps._widthResizedColMap.add(state.columnResize.col + 1);
} else {
- state.table.scenegraph.updateColWidth(state.columnResize.col, detaX);
+ state.table.scenegraph.updateColWidth(
+ state.columnResize.col,
+ detaX,
+ undefined,
+ undefined,
+ pendingCornerCustomMergeRanges
+ );
state.table.internalProps._widthResizedColMap.add(state.columnResize.col);
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
function updateResizeColForAll(detaX: number, state: StateManager) {
// 全列调整
const layout = state.table.internalProps.layoutMap as PivotHeaderLayoutMap;
+ const pendingCornerCustomMergeRanges = new Map();
for (let col = state.table.frozenColCount; col < state.table.colCount - state.table.rightFrozenColCount; col++) {
// 是否禁止调整列宽disableColumnResize 对应canResizeColumn的逻辑判断
if (!(state.table.internalProps.transpose || (state.table.isPivotTable() && !layout.indicatorsAsCol))) {
@@ -162,9 +184,10 @@ function updateResizeColForAll(detaX: number, state: StateManager) {
}
}
// state.table.setColWidth(col, afterSize);
- state.table.scenegraph.updateColWidth(col, detaX);
+ state.table.scenegraph.updateColWidth(col, detaX, undefined, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._widthResizedColMap.add(col);
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
function updateResizeColForIndicator(detaX: number, state: StateManager) {
@@ -180,20 +203,22 @@ function updateResizeColForIndicator(detaX: number, state: StateManager) {
resizeDimensionKey = headerPath.dimensionKey;
resizeDimensionValue = headerPath.value;
}
+ const pendingCornerCustomMergeRanges = new Map();
for (let col = state.table.rowHeaderLevelCount; col < state.table.colCount - state.table.rightFrozenColCount; col++) {
const indicatorKey = layout.getIndicatorKey(col, state.table.columnHeaderLevelCount);
if (layout.indicatorsAsCol && indicatorKey === resizeIndicatorKey) {
- state.table.scenegraph.updateColWidth(col, detaX);
+ state.table.scenegraph.updateColWidth(col, detaX, undefined, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._widthResizedColMap.add(col);
} else if (!layout.indicatorsAsCol) {
const headerPaths = layout.getCellHeaderPaths(col, state.table.columnHeaderLevelCount - 1);
const headerPath = headerPaths?.colHeaderPaths[headerPaths.colHeaderPaths.length - 1];
if (headerPath && resizeDimensionKey === headerPath.dimensionKey && resizeDimensionValue === headerPath.value) {
- state.table.scenegraph.updateColWidth(col, detaX);
+ state.table.scenegraph.updateColWidth(col, detaX, undefined, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._widthResizedColMap.add(col);
}
}
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
function updateResizeColForIndicatorGroup(detaX: number, state: StateManager) {
@@ -210,6 +235,7 @@ function updateResizeColForIndicatorGroup(detaX: number, state: StateManager) {
// 计算当前受影响列的总宽度 后面会利用这个计算比例
const totalColWidth = state.table.getColsWidth(startCol, endCol);
const moveX = detaX; // 纠正moveX 用于指标列均分该值
+ const pendingCornerCustomMergeRanges = new Map();
for (let col = startCol; col <= endCol; col++) {
// 是否禁止调整列宽disableColumnResize 对应canResizeColumn的逻辑判断
if (!(state.table.internalProps.transpose || (state.table.isPivotTable() && !layout.indicatorsAsCol))) {
@@ -227,7 +253,8 @@ function updateResizeColForIndicatorGroup(detaX: number, state: StateManager) {
} else if (deltaWidth < 0 && deltaWidth >= -0.5) {
deltaWidth = -0.5;
}
- state.table.scenegraph.updateColWidth(col, deltaWidth);
+ state.table.scenegraph.updateColWidth(col, deltaWidth, undefined, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._widthResizedColMap.add(col);
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
diff --git a/packages/vtable/src/state/resize/update-resize-row.ts b/packages/vtable/src/state/resize/update-resize-row.ts
index 3622b6f58d..0d61f43790 100644
--- a/packages/vtable/src/state/resize/update-resize-row.ts
+++ b/packages/vtable/src/state/resize/update-resize-row.ts
@@ -1,5 +1,6 @@
import type { ListTable } from '../../ListTable';
import type { PivotHeaderLayoutMap } from '../../layout/pivot-header-layout';
+import { flushCornerCustomMergeContentUpdates } from '../../scenegraph/utils/corner-custom-merge';
import type { IndicatorData } from '../../ts-types/list-table/layout-map/api';
import type { StateManager } from '../state';
@@ -69,21 +70,24 @@ export function updateResizeRow(xInTable: number, yInTable: number, state: State
}
function updateResizeColForRow(detaY: number, state: StateManager) {
+ const pendingCornerCustomMergeRanges = new Map();
if (state.table.heightMode === 'adaptive' && state.rowResize.row < state.table.rowCount - 1) {
- state.table.scenegraph.updateRowHeight(state.rowResize.row, detaY);
- state.table.scenegraph.updateRowHeight(state.rowResize.row + 1, -detaY);
+ state.table.scenegraph.updateRowHeight(state.rowResize.row, detaY, undefined, pendingCornerCustomMergeRanges);
+ state.table.scenegraph.updateRowHeight(state.rowResize.row + 1, -detaY, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._heightResizedRowMap.add(state.rowResize.row);
state.table.internalProps._heightResizedRowMap.add(state.rowResize.row + 1);
} else {
- state.table.scenegraph.updateRowHeight(state.rowResize.row, detaY);
+ state.table.scenegraph.updateRowHeight(state.rowResize.row, detaY, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._heightResizedRowMap.add(state.rowResize.row);
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
function updateResizeColForAll(detaY: number, state: StateManager) {
// 全列调整
// const layout = state.table.internalProps.layoutMap as PivotHeaderLayoutMap;
+ const pendingCornerCustomMergeRanges = new Map();
for (let row = state.table.frozenRowCount; row < state.table.rowCount - state.table.bottomFrozenRowCount; row++) {
// // 是否禁止调整列宽disableRowResize 对应canResizeRow的逻辑判断
// if (!(state.table.internalProps.transpose || (state.table.isPivotTable() && !layout.indicatorsAsCol))) {
@@ -92,9 +96,10 @@ function updateResizeColForAll(detaY: number, state: StateManager) {
// continue;
// }
// }
- state.table.scenegraph.updateRowHeight(row, detaY);
+ state.table.scenegraph.updateRowHeight(row, detaY, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._heightResizedRowMap.add(row);
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
function updateResizeColForIndicator(detaY: number, state: StateManager) {
@@ -110,6 +115,7 @@ function updateResizeColForIndicator(detaY: number, state: StateManager) {
resizeDimensionKey = headerPath?.dimensionKey;
resizeDimensionValue = headerPath?.value;
}
+ const pendingCornerCustomMergeRanges = new Map();
for (
let row = state.table.columnHeaderLevelCount;
row < state.table.rowCount - state.table.bottomFrozenRowCount;
@@ -117,17 +123,18 @@ function updateResizeColForIndicator(detaY: number, state: StateManager) {
) {
const indicatorKey = layout.getIndicatorKey(state.table.rowHeaderLevelCount, row);
if (!layout.indicatorsAsCol && indicatorKey === resizeIndicatorKey) {
- state.table.scenegraph.updateRowHeight(row, detaY);
+ state.table.scenegraph.updateRowHeight(row, detaY, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._heightResizedRowMap.add(row);
} else if (layout.indicatorsAsCol) {
const headerPaths = layout.getCellHeaderPaths(state.table.rowHeaderLevelCount - 1, row);
const headerPath = headerPaths?.rowHeaderPaths?.[headerPaths.rowHeaderPaths.length - 1];
if (headerPath && resizeDimensionKey === headerPath.dimensionKey && resizeDimensionValue === headerPath.value) {
- state.table.scenegraph.updateRowHeight(row, detaY);
+ state.table.scenegraph.updateRowHeight(row, detaY, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._heightResizedRowMap.add(row);
}
}
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
function updateResizeColForIndicatorGroup(detaY: number, state: StateManager) {
@@ -144,6 +151,7 @@ function updateResizeColForIndicatorGroup(detaY: number, state: StateManager) {
// 计算当前受影响列的总宽度 后面会利用这个计算比例
const totalRowHeight = state.table.getRowsHeight(startRow, endRow);
const moveY = detaY; // 纠正moveX 用于指标列均分该值
+ const pendingCornerCustomMergeRanges = new Map();
for (let row = startRow; row <= endRow; row++) {
// 是否禁止调整列宽disableRowResize 对应canResizeRow的逻辑判断
// if (!(state.table.internalProps.transpose || (state.table.isPivotTable() && !layout.indicatorsAsCol))) {
@@ -161,7 +169,8 @@ function updateResizeColForIndicatorGroup(detaY: number, state: StateManager) {
} else if (deltaHeight < 0 && deltaHeight >= -0.5) {
deltaHeight = -0.51;
}
- state.table.scenegraph.updateRowHeight(row, deltaHeight);
+ state.table.scenegraph.updateRowHeight(row, deltaHeight, undefined, pendingCornerCustomMergeRanges);
state.table.internalProps._heightResizedRowMap.add(row);
}
+ flushCornerCustomMergeContentUpdates(pendingCornerCustomMergeRanges, state.table);
}
diff --git a/packages/vtable/src/themes.ts b/packages/vtable/src/themes.ts
index 4b36d241af..1a5c1afa4c 100644
--- a/packages/vtable/src/themes.ts
+++ b/packages/vtable/src/themes.ts
@@ -11,7 +11,7 @@ import type { ITableThemeDefine } from './ts-types';
export const DARK = new TableTheme(darkTheme, darkTheme);
export const BRIGHT = new TableTheme(brightTheme, brightTheme);
export const ARCO = new TableTheme(arcoTheme, arcoTheme);
-export const DEFAULT = new TableTheme(defaultTheme, defaultTheme);
+export let DEFAULT = new TableTheme(defaultTheme, defaultTheme);
export const SIMPLIFY = new TableTheme(materialDesignTheme, materialDesignTheme);
const builtin: { [key: string]: TableTheme } = {
@@ -21,7 +21,16 @@ const builtin: { [key: string]: TableTheme } = {
DARK,
BRIGHT
};
-// let defTheme = DEFAULT;
+/**
+ * Replace the default theme used when no instance theme is specified.
+ * Existing instances and previously extended themes are not updated.
+ * The supplied theme replaces the default; use DEFAULT.extends() to merge styles.
+ */
+export function setDefaultTheme(theme: ITableThemeDefine): void {
+ DEFAULT = theme instanceof TableTheme ? theme.extends({}) : new TableTheme(theme, theme);
+ builtin.DEFAULT = DEFAULT;
+}
+
export const theme = { TableTheme };
export function of(value: ITableThemeDefine | string | undefined | null): TableTheme | null {
if (!value) {
@@ -52,9 +61,12 @@ export default {
DARK,
BRIGHT,
ARCO,
- DEFAULT,
+ get DEFAULT() {
+ return DEFAULT;
+ },
SIMPLIFY,
theme,
of,
- get
+ get,
+ setDefaultTheme
};
diff --git a/packages/vtable/src/tools/get-data-path/create-dataset.ts b/packages/vtable/src/tools/get-data-path/create-dataset.ts
index 30bf7fc551..3a5ed34d24 100644
--- a/packages/vtable/src/tools/get-data-path/create-dataset.ts
+++ b/packages/vtable/src/tools/get-data-path/create-dataset.ts
@@ -15,7 +15,7 @@ import type { IChartColumnIndicator } from '../../ts-types/pivot-table/indicator
export function createDataset(options: PivotChartConstructorOptions) {
const layoutNodeId = { seqId: 0 };
- const dataConfig: IPivotChartDataConfig = { isPivotChart: true };
+ const dataConfig: IPivotChartDataConfig = { ...options.dataConfig, isPivotChart: true };
let columnDimensionTree;
let rowDimensionTree;
let columnTree;
@@ -32,7 +32,7 @@ export function createDataset(options: PivotChartConstructorOptions) {
}
rowDimensionTree = new DimensionTree((options.rowTree as ITreeLayoutHeadNode[]) ?? [], layoutNodeId);
}
- const rowKeys = rowDimensionTree.dimensionKeys?.count
+ const rowKeys = rowDimensionTree?.dimensionKeys?.count
? rowDimensionTree.dimensionKeys.valueArr()
: options.rows?.reduce((keys, rowObj) => {
if (typeof rowObj === 'string') {
@@ -42,7 +42,7 @@ export function createDataset(options: PivotChartConstructorOptions) {
}
return keys;
}, []) ?? [];
- const columnKeys = columnDimensionTree.dimensionKeys?.count
+ const columnKeys = columnDimensionTree?.dimensionKeys?.count
? columnDimensionTree.dimensionKeys.valueArr()
: options.columns?.reduce((keys, columnObj) => {
if (typeof columnObj === 'string') {
diff --git a/packages/vtable/src/tools/get-data-path/index.ts b/packages/vtable/src/tools/get-data-path/index.ts
index dcf5eb4f17..b2662f2933 100644
--- a/packages/vtable/src/tools/get-data-path/index.ts
+++ b/packages/vtable/src/tools/get-data-path/index.ts
@@ -69,10 +69,10 @@ export function getDataCellPath(
const cellDimensionPath = layoutMap.getCellHeaderPaths(col, row);
const colKeys = cellDimensionPath.colHeaderPaths.map((colPath: any) => {
- return colPath.indicatorKey ?? colPath.value;
+ return colPath.indicatorKey ?? colPath.dataValue ?? colPath.value;
});
const rowKeys = cellDimensionPath.rowHeaderPaths.map((rowPath: any) => {
- return rowPath.indicatorKey ?? rowPath.value;
+ return rowPath.indicatorKey ?? rowPath.dataValue ?? rowPath.value;
});
// const aggregator = dataset.getAggregator(
// rowKey[rowKey.length - 1],
diff --git a/packages/vtable/src/tools/style.ts b/packages/vtable/src/tools/style.ts
index 1ebaa64b43..4fa7e3da7f 100644
--- a/packages/vtable/src/tools/style.ts
+++ b/packages/vtable/src/tools/style.ts
@@ -39,8 +39,8 @@ export function isZeroStyle(style: number | number[]) {
}
// 设置复制区域的状态 类似excel的虚线框
-export function setActiveCellRangeState(table: BaseTableAPI) {
- const selectRanges = table.stateManager.select.ranges;
+export function setActiveCellRangeState(table: BaseTableAPI, ranges = table.stateManager.select.ranges) {
+ const selectRanges = ranges;
const setRanges = [];
for (let i = 0; i < selectRanges.length; i++) {
const range = selectRanges[i];
diff --git a/packages/vtable/src/ts-types/common.ts b/packages/vtable/src/ts-types/common.ts
index a05a30cd9e..aea73b3083 100644
--- a/packages/vtable/src/ts-types/common.ts
+++ b/packages/vtable/src/ts-types/common.ts
@@ -104,6 +104,8 @@ export type IPivotTableCellHeaderPaths = {
export interface IDimensionInfo {
dimensionKey?: string;
value?: string;
+ /** Internal value used to locate aggregated data when it differs from the displayed value. */
+ dataValue?: string;
indicatorKey?: string;
isPivotCorner?: boolean;
virtual?: boolean;
diff --git a/packages/vtable/src/ts-types/dataset/aggregation.ts b/packages/vtable/src/ts-types/dataset/aggregation.ts
index e6e4df96d9..f1aad2d0ae 100644
--- a/packages/vtable/src/ts-types/dataset/aggregation.ts
+++ b/packages/vtable/src/ts-types/dataset/aggregation.ts
@@ -212,6 +212,7 @@ export class NoneAggregator extends Aggregator {
export class CustomAggregator extends Aggregator {
type: string = AggregationType.CUSTOM;
isRecord?: boolean = true;
+ recalculateFromChildren?: boolean;
declare field?: string;
aggregationFun?: Function;
values: (string | number)[] = [];
@@ -222,6 +223,19 @@ export class CustomAggregator extends Aggregator {
}
push(record: any): void {
if (record) {
+ if (this.recalculateFromChildren && record.isAggregator) {
+ if (this.isRecord && this.records) {
+ this.records.push(...record.records);
+ }
+ if (this.children) {
+ this.children.push(record);
+ }
+ if (this.field) {
+ this.values.push(...record.records.map((item: any) => item[this.field as string]));
+ }
+ this.clearCacheValue();
+ return;
+ }
if (this.isRecord && this.records) {
if (record.isAggregator) {
this.records.push(...record.records);
@@ -290,6 +304,24 @@ export class CustomAggregator extends Aggregator {
this.fieldValue = undefined;
}
recalculate() {
+ if (this.recalculateFromChildren && this.children?.length && this.field) {
+ this.values = [];
+ this.records = [];
+ this.children.forEach(child => {
+ if (isValid(child.changedValue)) {
+ const value = child.value();
+ this.values.push(value);
+ this.records.push({ [this.field as string]: value });
+ } else if (child instanceof CustomAggregator && child.recalculateFromChildren) {
+ this.values.push(...child.values);
+ this.records.push(...child.records);
+ } else {
+ const records = child.records;
+ this.values.push(...records.map(record => record[this.field as string]));
+ this.records.push(...records);
+ }
+ });
+ }
this.fieldValue = undefined;
this._formatedValue = undefined;
// do nothing
diff --git a/packages/vtable/src/ts-types/new-data-set.ts b/packages/vtable/src/ts-types/new-data-set.ts
index 22332c25c5..4b135291ef 100644
--- a/packages/vtable/src/ts-types/new-data-set.ts
+++ b/packages/vtable/src/ts-types/new-data-set.ts
@@ -48,6 +48,8 @@ export interface Total {
// calcSubTotals?: CalcTotals;
/** 小计汇总维度定义 */
subTotalsDimensions?: string[];
+ /** 总计中保留的下级维度,用于按维度分组显示总计 */
+ grandTotalDimensions?: string[];
/** 汇总节点显示名称 默认'总计' */
grandTotalLabel?: string;
/** 汇总节点显示名称 默认'小计' */
diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts
index ac4ce2c794..0bc3afebb5 100644
--- a/packages/vtable/src/ts-types/table-engine.ts
+++ b/packages/vtable/src/ts-types/table-engine.ts
@@ -399,7 +399,8 @@ export interface ListTableAPI extends BaseTableAPI {
values: (string | number)[][],
workOnEditableCell?: boolean,
triggerEvent?: boolean,
- noTriggerChangeCellValuesEvent?: boolean
+ noTriggerChangeCellValuesEvent?: boolean,
+ shouldCancel?: () => boolean
) => Promise | boolean[][];
/**
* 根据源数据 records 的 index + field 修改值。
@@ -630,6 +631,8 @@ export interface PivotChartConstructorOptions extends BaseTableConstructorOption
* 数据集合, 平坦数据集合。另外一种特殊方式是传入分组后的数据,分组依据为指标
*/
records?: any[] | Record;
+ /** 数据分析相关配置 */
+ dataConfig?: IPivotTableDataConfig;
/**
* @deprecated 请使用resize.columnResizeType
*/
@@ -750,7 +753,15 @@ export interface PivotTableAPI extends BaseTableAPI {
* @param row 粘贴数据的起始行号
* @param values 多个单元格的数据数组
*/
- changeCellValues: (col: number, row: number, values: (string | number)[][], workOnEditableCell: boolean) => void;
+ changeCellValues: (
+ col: number,
+ row: number,
+ values: (string | number)[][],
+ workOnEditableCell: boolean,
+ triggerEvent?: boolean,
+ noTriggerChangeCellValuesEvent?: boolean,
+ shouldCancel?: () => boolean
+ ) => boolean[][];
/**
* 获取行表头全路径
diff --git a/packages/vue-vtable/package.json b/packages/vue-vtable/package.json
index c9337f18ee..cbb9c469bc 100644
--- a/packages/vue-vtable/package.json
+++ b/packages/vue-vtable/package.json
@@ -1,6 +1,6 @@
{
"name": "@visactor/vue-vtable",
- "version": "1.26.7",
+ "version": "1.26.8",
"description": "The vue version of VTable",
"keywords": [
"vue",
@@ -59,7 +59,7 @@
"access": "public"
},
"dependencies": {
- "@visactor/vtable": "workspace:1.26.7",
+ "@visactor/vtable": "workspace:1.26.8",
"@visactor/vutils": "~1.0.17"
},
"devDependencies": {
diff --git a/tools/bugserver-trigger/package.json b/tools/bugserver-trigger/package.json
index a744555a17..97dabfa9a4 100644
--- a/tools/bugserver-trigger/package.json
+++ b/tools/bugserver-trigger/package.json
@@ -8,11 +8,11 @@
"ci": "ts-node --transpileOnly --skipProject ./scripts/trigger-test.ts"
},
"dependencies": {
- "@visactor/vtable": "workspace:1.26.7",
- "@visactor/vtable-gantt": "workspace:1.26.7",
- "@visactor/vtable-editors": "workspace:1.26.7",
- "@visactor/vtable-plugins": "workspace:1.26.7",
- "@visactor/vtable-sheet": "workspace:1.26.7"
+ "@visactor/vtable": "workspace:1.26.8",
+ "@visactor/vtable-gantt": "workspace:1.26.8",
+ "@visactor/vtable-editors": "workspace:1.26.8",
+ "@visactor/vtable-plugins": "workspace:1.26.8",
+ "@visactor/vtable-sheet": "workspace:1.26.8"
},
"devDependencies": {
"@rushstack/eslint-patch": "~1.1.4",
| | | |