-
Notifications
You must be signed in to change notification settings - Fork 57
fix(draw): adjust table cell edge resize #1151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@plait/draw': patch | ||
| --- | ||
|
|
||
| Adjust table cell edge resize behavior |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { PlaitBoard, Point, RectangleClient, Transforms, isSelectedElement, getSelectedElements, hasValidAngle } from '@plait/core'; | ||
| import { PlaitBaseTable, PlaitTableBoard, PlaitTableCellWithPoints } from '../interfaces/table'; | ||
| import { PlaitBoard, Point, RectangleClient, Transforms, getSelectedElements, hasValidAngle } from '@plait/core'; | ||
| import { PlaitBaseTable, PlaitTableBoard, PlaitTableCell, PlaitTableCellWithPoints } from '../interfaces/table'; | ||
| import { | ||
| getIndexByResizeHandle, | ||
| isCornerHandle, | ||
|
|
@@ -11,7 +11,7 @@ import { | |
| WithResizeOptions, | ||
| normalizeShapePoints | ||
| } from '@plait/common'; | ||
| import { getCellsWithPoints, updateColumns, updateRows } from '../utils/table'; | ||
| import { getCellsWithPoints, getCurrentRowOrColumnSize, ResizeEdge, updateColumns, updateRows } from '../utils/table'; | ||
| import { getHitRectangleResizeHandleRef } from '../utils/position/geometry'; | ||
| import { getResizeOriginPointAndHandlePoint, getResizeZoom, movePointByZoomAndOriginPoint } from './with-draw-resize'; | ||
| import { getSnapResizingRef, getSnapResizingRefOptions } from '../utils/snap-resizing'; | ||
|
|
@@ -41,25 +41,10 @@ export function withTableResize(board: PlaitTableBoard) { | |
| const hitElement = selectedElements[0]; | ||
| // debugGenerator.clear(); | ||
| if (hitElement && PlaitDrawElement.isElementByTable(hitElement)) { | ||
| let rectangle = board.getRectangle(hitElement) as RectangleClient; | ||
| // debugGenerator.drawRectangle(board, rectangle); | ||
| // debugGenerator.drawCircles(board, [point], 5); | ||
| let handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, hitElement.angle); | ||
| if (handleRef) { | ||
| const selectElement = isSelectedElement(board, hitElement); | ||
| if ((selectElement && isSingleSelectTable(board)) || (!selectElement && !isCornerHandle(board, handleRef.handle))) { | ||
| return { | ||
| element: hitElement, | ||
| handle: handleRef.handle, | ||
| cursorClass: handleRef.cursorClass, | ||
| rectangle | ||
| }; | ||
| } | ||
| } | ||
| const cells = getCellsWithPoints(board, hitElement); | ||
| for (let i = 0; i < cells.length; i++) { | ||
| rectangle = RectangleClient.getRectangleByPoints(cells[i].points); | ||
| handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, 0); | ||
| const rectangle = RectangleClient.getRectangleByPoints(cells[i].points); | ||
| const handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, 0); | ||
| if (handleRef && !isCornerHandle(board, handleRef.handle)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At grid intersections this helper returns the cell corner first, then we discard it without checking the side handle. That makes the drag fall through instead of resizing the row or column. I can reproduce it on the swimlane edge: dragging there moves the whole swimlane. Could we handle side hits explicitly here?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good eye, thanks! I updated the hit testing to resolve table dividers directly after preserving table corner handles. Grid intersections now choose the nearest divider axis, so side drags no longer fall through to whole swimlane/table movement. |
||
| return { | ||
| element: hitElement, | ||
|
|
@@ -72,6 +57,19 @@ export function withTableResize(board: PlaitTableBoard) { | |
| }; | ||
| } | ||
| } | ||
|
|
||
| const rectangle = board.getRectangle(hitElement) as RectangleClient; | ||
| // debugGenerator.drawRectangle(board, rectangle); | ||
| // debugGenerator.drawCircles(board, [point], 5); | ||
| const handleRef = getHitRectangleResizeHandleRef(board, rectangle, point, hitElement.angle); | ||
| if (handleRef && isCornerHandle(board, handleRef.handle)) { | ||
| return { | ||
| element: hitElement, | ||
| handle: handleRef.handle, | ||
| cursorClass: handleRef.cursorClass, | ||
| rectangle | ||
| }; | ||
| } | ||
| } | ||
| return null; | ||
| }, | ||
|
|
@@ -80,26 +78,47 @@ export function withTableResize(board: PlaitTableBoard) { | |
| const path = PlaitBoard.findPath(board, resizeRef.element); | ||
| if (resizeRef.options?.cell && resizeRef.rectangle) { | ||
| const handleIndex = getIndexByResizeHandle(resizeRef.handle); | ||
| const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle!); | ||
| const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle); | ||
| const resizePoints: [Point, Point] = [resizeState.startPoint, resizeState.endPoint]; | ||
| const { xZoom, yZoom } = getResizeZoom(resizePoints, originPoint, handlePoint, false, false); | ||
| const originPoints = resizeRef.options?.cell.points; | ||
| const originPoints = resizeRef.options.cell.points; | ||
| const targetPoints = originPoints.map((p) => { | ||
| return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom); | ||
| }) as [Point, Point]; | ||
| const offsetX = targetPoints[1][0] - originPoints[1][0]; | ||
| const offsetY = targetPoints[1][1] - originPoints[1][1]; | ||
| const width = targetPoints[1][0] - targetPoints[0][0]; | ||
| const height = targetPoints[1][1] - targetPoints[0][1]; | ||
| if (offsetX !== 0 && width >= MIN_CELL_SIZE) { | ||
| const { columns, points } = updateColumns(resizeRef.element, resizeRef.options?.cell.columnId, width, offsetX); | ||
| Transforms.setNode(board, { columns, points }, path); | ||
| } else if (offsetY !== 0 && height >= MIN_CELL_SIZE) { | ||
| const { rows, points } = updateRows(resizeRef.element, resizeRef.options?.cell.rowId, height, offsetY); | ||
| Transforms.setNode(board, { rows, points }, path); | ||
|
|
||
| const originRect = RectangleClient.getRectangleByPoints(originPoints); | ||
| const targetRect = RectangleClient.getRectangleByPoints(targetPoints); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good eye, thanks! I replaced the normalized-rectangle delta with a signed pointer delta and clamp the target size at MIN_CELL_SIZE before applying the offset, so crossing the fixed edge no longer flips and grows in reverse. |
||
| const offsetWidth = targetRect.width - originRect.width; | ||
| const offsetHeight = targetRect.height - originRect.height; | ||
| const edge = getResizeEdge(handleIndex); | ||
| if (offsetWidth !== 0 && edge) { | ||
| const columnIndex = getResizeTargetRowOrColumnIndex(resizeRef.element, resizeRef.options.cell, edge, false); | ||
| const width = getCurrentRowOrColumnSize(resizeRef.element, columnIndex, false) + offsetWidth; | ||
| if (width >= MIN_CELL_SIZE) { | ||
| const { columns, points } = updateColumns( | ||
| resizeRef.element, | ||
| resizeRef.element.columns[columnIndex].id, | ||
| width, | ||
| offsetWidth, | ||
| edge | ||
| ); | ||
| Transforms.setNode(board, { columns, points }, path); | ||
| } | ||
| } else if (offsetHeight !== 0 && edge) { | ||
| const rowIndex = getResizeTargetRowOrColumnIndex(resizeRef.element, resizeRef.options.cell, edge, true); | ||
| const height = getCurrentRowOrColumnSize(resizeRef.element, rowIndex, true) + offsetHeight; | ||
| if (height >= MIN_CELL_SIZE) { | ||
| const { rows, points } = updateRows( | ||
| resizeRef.element, | ||
| resizeRef.element.rows[rowIndex].id, | ||
| height, | ||
| offsetHeight, | ||
| edge | ||
| ); | ||
| Transforms.setNode(board, { rows, points }, path); | ||
| } | ||
| } | ||
| } else { | ||
| const isFromCorner = isCornerHandle(board, resizeRef.handle); | ||
| } else if (isCornerHandle(board, resizeRef.handle)) { | ||
| const isAspectRatio = resizeState.isShift; | ||
| const handleIndex = getIndexByResizeHandle(resizeRef.handle); | ||
| const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle!); | ||
|
|
@@ -112,7 +131,7 @@ export function withTableResize(board: PlaitTableBoard) { | |
| handlePoint | ||
| }, | ||
| isAspectRatio, | ||
| isFromCorner | ||
| true | ||
| ); | ||
| const resizeSnapRef = getSnapResizingRef(board, [resizeRef.element], resizeSnapRefOptions); | ||
| snapG = resizeSnapRef.snapG; | ||
|
|
@@ -126,26 +145,10 @@ export function withTableResize(board: PlaitTableBoard) { | |
| let columns = [...resizeRef.element.columns]; | ||
| let rows = [...resizeRef.element.rows]; | ||
| if (offsetWidth !== 0) { | ||
| columns = columns.map((item) => { | ||
| if (item.width) { | ||
| return { | ||
| ...item, | ||
| width: item.width + offsetWidth * (item.width / originRect.width) | ||
| }; | ||
| } | ||
| return item; | ||
| }); | ||
| columns = scaleRowsOrColumns(columns, offsetWidth, originRect.width, false); | ||
| } | ||
| if (offsetHeight !== 0) { | ||
| rows = rows.map((item) => { | ||
| if (item.height) { | ||
| return { | ||
| ...item, | ||
| height: item.height + offsetHeight * (item.height / originRect.height) | ||
| }; | ||
| } | ||
| return item; | ||
| }); | ||
| rows = scaleRowsOrColumns(rows, offsetHeight, originRect.height, true); | ||
| } | ||
| Transforms.setNode(board, { points: normalizeShapePoints(points), columns, rows }, path); | ||
| } | ||
|
|
@@ -160,3 +163,37 @@ export function withTableResize(board: PlaitTableBoard) { | |
|
|
||
| return board; | ||
| } | ||
|
|
||
| function scaleRowsOrColumns(data: { id: string; width?: number; height?: number }[], offset: number, originSize: number, isRow: boolean) { | ||
| const dimension = isRow ? 'height' : 'width'; | ||
| return data.map((item) => { | ||
| if (item[dimension]) { | ||
| return { | ||
| ...item, | ||
| [dimension]: item[dimension]! + offset * (item[dimension]! / originSize) | ||
| }; | ||
| } | ||
| return item; | ||
| }); | ||
| } | ||
|
|
||
| function getResizeEdge(handleIndex: number): ResizeEdge | undefined { | ||
| if ([Number(ResizeHandle.s), Number(ResizeHandle.e)].includes(handleIndex)) { | ||
| return 'end'; | ||
| } | ||
| if ([Number(ResizeHandle.n), Number(ResizeHandle.w)].includes(handleIndex)) { | ||
| return 'start'; | ||
| } | ||
| return undefined; | ||
| } | ||
|
|
||
| function getResizeTargetRowOrColumnIndex(element: PlaitBaseTable, resizeCell: PlaitTableCell, edge: ResizeEdge, isRow: boolean) { | ||
| const data = isRow ? element.rows : element.columns; | ||
| const id = isRow ? resizeCell.rowId : resizeCell.columnId; | ||
| const span = isRow ? resizeCell.rowspan : resizeCell.colspan; | ||
| let index = data.findIndex((item) => item.id === id); | ||
| if (edge === 'end' && span && span !== 1) { | ||
| index += span - 1; | ||
| } | ||
| return index; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A shared divider hits both adjacent cells, so this picks whichever cell comes first in
cells.addSwimlaneRow/Column(index)can leavecellsin a different order from the rows and columns, which means the same divider may resize the opposite side. Should the target be resolved from the row or column indexes instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good eye, thanks! I removed the cell-order-based target selection. Divider hits are now resolved from the row/column divider index: the outer start divider maps to the first row/column start, and internal/end dividers map to the previous row/column end. This keeps the same divider stable regardless of cell order after row/column insertion.