From f342876d255dccc34f34233ea2700590b66948e1 Mon Sep 17 00:00:00 2001 From: qiufeihong2018 <15058301288@163.com> Date: Wed, 10 Jan 2024 15:48:00 +0800 Subject: [PATCH 1/5] feat: Automatically adjust column width - add long text column #316 --- src/test/TestGrid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/TestGrid.tsx b/src/test/TestGrid.tsx index 0a8b0aea..6b5d0343 100644 --- a/src/test/TestGrid.tsx +++ b/src/test/TestGrid.tsx @@ -94,7 +94,7 @@ export const TestGrid: React.FC = (props) => { reorderable: true, height: config.cellHeight, cells: columns.map((_, ci) => { - if (ri === 0) return { type: firstRowType, text: `${ri} - ${ci}` }; + if (ri === 0) return { type: firstRowType, text: `${ri} - ${ci} longlonglonglonglonglonglonglonglonglonglonglonglonglonglong` }; if (ri === 2 && ci === 8) return { type: "text", From 8434fb7158e1442ad43712c46aa9016e72df5fbe Mon Sep 17 00:00:00 2001 From: qiufeihong2018 <15058301288@163.com> Date: Thu, 11 Jan 2024 11:11:49 +0800 Subject: [PATCH 2/5] feat: Automatically adjust column width - add doubleclick #316 --- src/lib/Behaviors/ResizeColumnBehavior.tsx | 59 ++++++++++++++++++++++ src/lib/Model/PointerEventsController.ts | 47 +++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/lib/Behaviors/ResizeColumnBehavior.tsx b/src/lib/Behaviors/ResizeColumnBehavior.tsx index 574f8c95..d05685ef 100644 --- a/src/lib/Behaviors/ResizeColumnBehavior.tsx +++ b/src/lib/Behaviors/ResizeColumnBehavior.tsx @@ -85,6 +85,7 @@ export class ResizeColumnBehavior extends Behavior { return { ...state, linePosition: -1, focusedLocation }; } + //should render ResizeHint on pane which has got the highest priority renderPanePart(state: State, pane: Range): React.ReactNode { const offset = this.getLinePositionOffset(state); @@ -127,4 +128,62 @@ export class ResizeColumnBehavior extends Behavior { } return offset; } + + handleDoubleClick(event: PointerEvent, location: PointerLocation, state: State): State { + // 可能需要实现一个方法来计算当前列的最佳宽度,例如: + // const newWidth = this.calculateOptimalColumnWidth(this.resizedColumn.columnId, state); + const newWidth = 200 + + // 更新列宽 + if (state.props?.onColumnResized) { + state.props.onColumnResized( + this.resizedColumn.columnId, + newWidth, + state.selectedIds + ); + } + + let focusedLocation = state.focusedLocation; + if ( + focusedLocation !== undefined && + this.resizedColumn.columnId === focusedLocation.column.idx + ) { + const column = { ...focusedLocation.column, width: newWidth }; + focusedLocation = { ...focusedLocation, column }; + } + + return { ...state, linePosition: -1, focusedLocation }; + } + + // calculateOptimalColumnWidth(columnId: number, state: State): number { + // // 这里需要实现逻辑以便根据列中的内容计算最佳列宽 + // // 例如,遍历当前列的所有单元格并找到内容最长的单元格的尺寸 + // // 为简化起见,这里使用伪代码表示: + + // let maxWidth = 0; + + // // 假设getCellContent是一个获取单元格内容的函数 + // state.cellMatrix.columns[columnId].cells.forEach(cell => { + // const contentWidth = this.measureTextWidth(getCellContent(cell)); + // if (contentWidth > maxWidth) { + // maxWidth = contentWidth; + // } + // }); + + // // 添加一些额外的空间以防文本被截断 + // return maxWidth + 10; // 假设添加10px的空间 + // } + + // measureTextWidth(text: string): number { + // // 这里需要实现逻辑以测量文本的宽度 + // // 这可能需要一个临时的HTML元素或使用Canvas API来准确测量 + // // 由于文本的宽度跟字体有关,需要确保测量时使用的字体与表格单元格中的字体相同 + // // 为简化起见,这里使用伪代码表示: + + // const context = document.createElement('canvas').getContext('2d'); + // // 设置与表格单元格中相同的字体样式 + // context.font = '14px Arial'; + // return context.measureText(text).width; + // } + } diff --git a/src/lib/Model/PointerEventsController.ts b/src/lib/Model/PointerEventsController.ts index 9d8000d6..560ec2eb 100644 --- a/src/lib/Model/PointerEventsController.ts +++ b/src/lib/Model/PointerEventsController.ts @@ -49,6 +49,7 @@ export class PointerEventsController extends AbstractPointerEventsController { window.addEventListener("pointermove", this.handlePointerMove); window.addEventListener("pointerup", this.handlePointerUp); + window.addEventListener("dblclick", this.handleDoubleClicks); const currentLocation = getLocationFromClient( state as State, event.clientX, @@ -230,4 +231,50 @@ export class PointerEventsController extends AbstractPointerEventsController { return state; }); }; + + private handleDoubleClicks = (event: PointerEvent): void => { + this.updateState((state) => { + const currentLocation = getLocationFromClient( + state as State, + event.clientX, + event.clientY + ); + const currentTimestamp = new Date().valueOf(); + const secondLastTimestamp = this.eventTimestamps[1 - this.currentIndex]; + state = state.currentBehavior.handleDoubleClick( + event, + currentLocation, + state + ); + if ( + this.shouldHandleCellSelectionOnMobile( + event, + currentLocation, + currentTimestamp + ) + ) { + state = state.currentBehavior.handlePointerDown( + event, + currentLocation, + state + ); + } + state = { ...state, currentBehavior: new DefaultBehavior() }; + if ( + this.shouldHandleDoubleClick( + currentLocation, + currentTimestamp, + secondLastTimestamp + ) + ) { + state = state.currentBehavior.handleDoubleClick( + event, + currentLocation, + state + ); + } + state.hiddenFocusElement?.focus(); + return state; + }); + }; } \ No newline at end of file From 1f01f97cc53363578335adbffbfa1a4285048224 Mon Sep 17 00:00:00 2001 From: qiufeihong2018 <15058301288@163.com> Date: Thu, 11 Jan 2024 20:47:05 +0800 Subject: [PATCH 3/5] feat: Automatically adjust column width - add calculateOptimalColumnWidth and measureTextWidth function #316 --- src/lib/Behaviors/ResizeColumnBehavior.tsx | 112 ++++++++++++--------- src/lib/Model/PointerEventsController.ts | 37 +------ 2 files changed, 67 insertions(+), 82 deletions(-) diff --git a/src/lib/Behaviors/ResizeColumnBehavior.tsx b/src/lib/Behaviors/ResizeColumnBehavior.tsx index d05685ef..a57e638f 100644 --- a/src/lib/Behaviors/ResizeColumnBehavior.tsx +++ b/src/lib/Behaviors/ResizeColumnBehavior.tsx @@ -9,12 +9,16 @@ import { getStickyOffset, getVisibleSizeOfReactGrid, CellMatrix, + Id, + Cell, + TextCell, } from "../../core"; import { PointerEvent } from "../Model/domEventsTypes"; import { State } from "../Model/State"; import { Behavior } from "../Model/Behavior"; import { ResizeHint } from "../Components/ResizeHint"; + export class ResizeColumnBehavior extends Behavior { // TODO min / max column with on column object private resizedColumn!: GridColumn; @@ -85,7 +89,6 @@ export class ResizeColumnBehavior extends Behavior { return { ...state, linePosition: -1, focusedLocation }; } - //should render ResizeHint on pane which has got the highest priority renderPanePart(state: State, pane: Range): React.ReactNode { const offset = this.getLinePositionOffset(state); @@ -130,60 +133,71 @@ export class ResizeColumnBehavior extends Behavior { } handleDoubleClick(event: PointerEvent, location: PointerLocation, state: State): State { - // 可能需要实现一个方法来计算当前列的最佳宽度,例如: - // const newWidth = this.calculateOptimalColumnWidth(this.resizedColumn.columnId, state); - const newWidth = 200 - - // 更新列宽 + this.initialLocation = location; + this.resizedColumn = location.column; + // You may need to implement a method to calculate the optimal width of the current column, for example: + const newWidth = this.calculateOptimalColumnWidth(this.resizedColumn.columnId, state); + // const newWidth = 500; + if (state.props?.onColumnResized) { - state.props.onColumnResized( - this.resizedColumn.columnId, - newWidth, - state.selectedIds - ); + const newColWidth = + newWidth >= (state.props?.minColumnWidth ?? CellMatrix.MIN_COLUMN_WIDTH) + ? newWidth + : state.props?.minColumnWidth ?? CellMatrix.MIN_COLUMN_WIDTH; + state.props.onColumnResized(this.resizedColumn.columnId, newColWidth, state.selectedIds); } - let focusedLocation = state.focusedLocation; - if ( - focusedLocation !== undefined && - this.resizedColumn.columnId === focusedLocation.column.idx - ) { + if (focusedLocation !== undefined && this.resizedColumn.columnId === focusedLocation.column.idx) { const column = { ...focusedLocation.column, width: newWidth }; focusedLocation = { ...focusedLocation, column }; } - return { ...state, linePosition: -1, focusedLocation }; } - - // calculateOptimalColumnWidth(columnId: number, state: State): number { - // // 这里需要实现逻辑以便根据列中的内容计算最佳列宽 - // // 例如,遍历当前列的所有单元格并找到内容最长的单元格的尺寸 - // // 为简化起见,这里使用伪代码表示: - - // let maxWidth = 0; - - // // 假设getCellContent是一个获取单元格内容的函数 - // state.cellMatrix.columns[columnId].cells.forEach(cell => { - // const contentWidth = this.measureTextWidth(getCellContent(cell)); - // if (contentWidth > maxWidth) { - // maxWidth = contentWidth; - // } - // }); - - // // 添加一些额外的空间以防文本被截断 - // return maxWidth + 10; // 假设添加10px的空间 - // } - - // measureTextWidth(text: string): number { - // // 这里需要实现逻辑以测量文本的宽度 - // // 这可能需要一个临时的HTML元素或使用Canvas API来准确测量 - // // 由于文本的宽度跟字体有关,需要确保测量时使用的字体与表格单元格中的字体相同 - // // 为简化起见,这里使用伪代码表示: - - // const context = document.createElement('canvas').getContext('2d'); - // // 设置与表格单元格中相同的字体样式 - // context.font = '14px Arial'; - // return context.measureText(text).width; - // } - + + /** + * Iterate through all the cells in the column, finding the widest content, and return the new column width. + * @param columnId + * @param state + * @returns + */ + calculateOptimalColumnWidth(columnId: Id, state: State): number { + let maxWidth = 0; + + state.cellMatrix.rows.forEach((row) => { + row.cells.forEach((cell: Cell) => { + const contentWidth = this.measureTextWidth((cell as TextCell)?.text); + if (contentWidth > maxWidth) { + maxWidth = contentWidth; + } + }); + }); + + // Add some extra space in case the text gets truncated + return maxWidth + 10; + } + + /** + * Use the Canvas API to calculate the width of a given text and font style + * @param text + * @returns + */ + measureTextWidth(text: string): number { + const bodyElement = document.querySelector("body"); + if (bodyElement) { + const style = getComputedStyle(bodyElement); + const fontSize = style?.fontSize; + const fontFamily = style?.fontFamily; + const context = document.createElement("canvas").getContext("2d"); + if (context) { + context.font = `${fontSize} ${fontFamily}`; + return context.measureText(text).width; + } else { + console.error('document.createElement("canvas").getContext("2d") returns a null value'); + return CellMatrix.DEFAULT_COLUMN_WIDTH; + } + } else { + console.error('document.querySelector("body") returns a null value'); + return CellMatrix.DEFAULT_COLUMN_WIDTH; + } + } } diff --git a/src/lib/Model/PointerEventsController.ts b/src/lib/Model/PointerEventsController.ts index 560ec2eb..db04041a 100644 --- a/src/lib/Model/PointerEventsController.ts +++ b/src/lib/Model/PointerEventsController.ts @@ -49,7 +49,7 @@ export class PointerEventsController extends AbstractPointerEventsController { window.addEventListener("pointermove", this.handlePointerMove); window.addEventListener("pointerup", this.handlePointerUp); - window.addEventListener("dblclick", this.handleDoubleClicks); + window.addEventListener("dblclick", this.handleDoubleClick); const currentLocation = getLocationFromClient( state as State, event.clientX, @@ -232,48 +232,19 @@ export class PointerEventsController extends AbstractPointerEventsController { }); }; - private handleDoubleClicks = (event: PointerEvent): void => { + private handleDoubleClick = (event: MouseEvent): void => { this.updateState((state) => { const currentLocation = getLocationFromClient( state as State, event.clientX, event.clientY ); - const currentTimestamp = new Date().valueOf(); - const secondLastTimestamp = this.eventTimestamps[1 - this.currentIndex]; + state = { ...state, currentBehavior: new ResizeColumnBehavior() }; state = state.currentBehavior.handleDoubleClick( - event, + event as PointerEvent, currentLocation, state ); - if ( - this.shouldHandleCellSelectionOnMobile( - event, - currentLocation, - currentTimestamp - ) - ) { - state = state.currentBehavior.handlePointerDown( - event, - currentLocation, - state - ); - } - state = { ...state, currentBehavior: new DefaultBehavior() }; - if ( - this.shouldHandleDoubleClick( - currentLocation, - currentTimestamp, - secondLastTimestamp - ) - ) { - state = state.currentBehavior.handleDoubleClick( - event, - currentLocation, - state - ); - } - state.hiddenFocusElement?.focus(); return state; }); }; From 54cb91c5e335127bff38a47ae1dc8b3f53615748 Mon Sep 17 00:00:00 2001 From: qiufeihong2018 <15058301288@163.com> Date: Thu, 11 Jan 2024 21:04:56 +0800 Subject: [PATCH 4/5] feat: Automatically adjust column width - rg-resize-handle triggers handleDoubleClick #316 --- src/lib/Model/PointerEventsController.ts | 22 ++++++++-------------- src/test/TestGrid.tsx | 2 +- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/lib/Model/PointerEventsController.ts b/src/lib/Model/PointerEventsController.ts index db04041a..bc672ebe 100644 --- a/src/lib/Model/PointerEventsController.ts +++ b/src/lib/Model/PointerEventsController.ts @@ -233,19 +233,13 @@ export class PointerEventsController extends AbstractPointerEventsController { }; private handleDoubleClick = (event: MouseEvent): void => { - this.updateState((state) => { - const currentLocation = getLocationFromClient( - state as State, - event.clientX, - event.clientY - ); - state = { ...state, currentBehavior: new ResizeColumnBehavior() }; - state = state.currentBehavior.handleDoubleClick( - event as PointerEvent, - currentLocation, - state - ); - return state; - }); + if ((event.target as HTMLDivElement).className === "rg-resize-handle") { + this.updateState((state) => { + const currentLocation = getLocationFromClient(state as State, event.clientX, event.clientY); + state = { ...state, currentBehavior: new ResizeColumnBehavior() }; + state = state.currentBehavior.handleDoubleClick(event as PointerEvent, currentLocation, state); + return state; + }); + } }; } \ No newline at end of file diff --git a/src/test/TestGrid.tsx b/src/test/TestGrid.tsx index 6b5d0343..0a8b0aea 100644 --- a/src/test/TestGrid.tsx +++ b/src/test/TestGrid.tsx @@ -94,7 +94,7 @@ export const TestGrid: React.FC = (props) => { reorderable: true, height: config.cellHeight, cells: columns.map((_, ci) => { - if (ri === 0) return { type: firstRowType, text: `${ri} - ${ci} longlonglonglonglonglonglonglonglonglonglonglonglonglonglong` }; + if (ri === 0) return { type: firstRowType, text: `${ri} - ${ci}` }; if (ri === 2 && ci === 8) return { type: "text", From ec94169ae049f11d332c7f22d4542f52645e413d Mon Sep 17 00:00:00 2001 From: qiufeihong2018 <15058301288@163.com> Date: Fri, 12 Jan 2024 15:12:34 +0800 Subject: [PATCH 5/5] feat: Automatically adjust column width - Optimize your code to improve performance #316 --- src/lib/Behaviors/ResizeColumnBehavior.tsx | 54 +++++++++------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/lib/Behaviors/ResizeColumnBehavior.tsx b/src/lib/Behaviors/ResizeColumnBehavior.tsx index a57e638f..4e37dce6 100644 --- a/src/lib/Behaviors/ResizeColumnBehavior.tsx +++ b/src/lib/Behaviors/ResizeColumnBehavior.tsx @@ -9,9 +9,6 @@ import { getStickyOffset, getVisibleSizeOfReactGrid, CellMatrix, - Id, - Cell, - TextCell, } from "../../core"; import { PointerEvent } from "../Model/domEventsTypes"; import { State } from "../Model/State"; @@ -23,9 +20,25 @@ export class ResizeColumnBehavior extends Behavior { // TODO min / max column with on column object private resizedColumn!: GridColumn; private initialLocation!: PointerLocation; + private canvasContext = this.createCanvasContext(); autoScrollDirection: Direction = "horizontal"; isInScrollableRange!: boolean; + constructor() { + super(); + const style = getComputedStyle(document.body); + this.canvasContext.font = `${style.fontSize} ${style.fontFamily}`; + } + + createCanvasContext(): CanvasRenderingContext2D { + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + if (!context) { + throw new Error("2D context not supported or canvas already initialized"); + } + return context; + } + handlePointerDown( event: PointerEvent, location: PointerLocation, @@ -135,9 +148,7 @@ export class ResizeColumnBehavior extends Behavior { handleDoubleClick(event: PointerEvent, location: PointerLocation, state: State): State { this.initialLocation = location; this.resizedColumn = location.column; - // You may need to implement a method to calculate the optimal width of the current column, for example: - const newWidth = this.calculateOptimalColumnWidth(this.resizedColumn.columnId, state); - // const newWidth = 500; + const newWidth = this.calculateOptimalColumnWidth(this.resizedColumn.idx, state); if (state.props?.onColumnResized) { const newColWidth = @@ -160,20 +171,17 @@ export class ResizeColumnBehavior extends Behavior { * @param state * @returns */ - calculateOptimalColumnWidth(columnId: Id, state: State): number { + calculateOptimalColumnWidth(idx: number, state: State): number { let maxWidth = 0; state.cellMatrix.rows.forEach((row) => { - row.cells.forEach((cell: Cell) => { - const contentWidth = this.measureTextWidth((cell as TextCell)?.text); - if (contentWidth > maxWidth) { - maxWidth = contentWidth; - } - }); + const cell = row.cells[idx]; + const contentWidth = this.measureTextWidth(state.cellTemplates[cell.type].getCompatibleCell(cell).text); + maxWidth = Math.max(maxWidth, contentWidth); }); // Add some extra space in case the text gets truncated - return maxWidth + 10; + return maxWidth + 20; } /** @@ -182,22 +190,6 @@ export class ResizeColumnBehavior extends Behavior { * @returns */ measureTextWidth(text: string): number { - const bodyElement = document.querySelector("body"); - if (bodyElement) { - const style = getComputedStyle(bodyElement); - const fontSize = style?.fontSize; - const fontFamily = style?.fontFamily; - const context = document.createElement("canvas").getContext("2d"); - if (context) { - context.font = `${fontSize} ${fontFamily}`; - return context.measureText(text).width; - } else { - console.error('document.createElement("canvas").getContext("2d") returns a null value'); - return CellMatrix.DEFAULT_COLUMN_WIDTH; - } - } else { - console.error('document.querySelector("body") returns a null value'); - return CellMatrix.DEFAULT_COLUMN_WIDTH; - } + return this.canvasContext.measureText(text).width; } }