Skip to content

fix(draw): restore table cell resize origin#1160

Open
pubuzhixing8 wants to merge 3 commits into
developfrom
xingxing/table-resize-origin-interaction-test
Open

fix(draw): restore table cell resize origin#1160
pubuzhixing8 wants to merge 3 commits into
developfrom
xingxing/table-resize-origin-interaction-test

Conversation

@pubuzhixing8

Copy link
Copy Markdown
Collaborator

Summary

Replaces the closed draft #1159 with a clean branch based on the latest develop after #1151 landed.

This fixes the table cell resize-origin case: when a cell edge is dragged away from its original size and then the pointer returns to the pointer-down position within the same resize cycle, the table should restore the original row/column size and points.

Root Cause

resizeRef.element is captured on pointer down and remains the pointer-down element reference during the resize cycle. The board update flow is immer-backed: set_node mutates a draft and then replaces board.children, so resizeRef.element still represents the original resize baseline.

When the cumulative pointer offset returns to 0, the computed target size is the original size and appliedOffset becomes 0. The previous appliedOffset !== 0 guard skipped that write, leaving the board at the previous non-zero resize frame.

Change

Remove the appliedOffset !== 0 guard so the resize path applies the computed row/column state even when the effective offset is zero.

Test Coverage

Added an interaction-style spec for withTableResize that uses the real withDraw plugin chain and simulated pointer events:

  1. Select a table.
  2. Pointer down on the first column right edge.
  3. Pointer move from 100 to 120 and verify the first column expands to 120.
  4. Pointer move back to the original pointer-down position and verify the first column and table points restore to the original state.

Verification

  • npm run build:draw

Attempted locally but blocked by existing/local test environment issues:

  • npx tsc -p packages/draw/tsconfig.spec.json --noEmit still fails on existing swimlane spec issues unrelated to this change:
    • packages/draw/src/transforms/swimlane.spec.ts: flatMap lib / implicit any errors
    • packages/draw/src/utils/swimlane.spec.ts: BOARD_TO_TEMPORARY_POINTER export error
  • npm run test:draw -- --watch=false --browsers=ChromeHeadlessCI --progress=false --include=packages/draw/src/plugins/with-table-resize.spec.ts exits locally during the Karma building phase with code 134 before running specs.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploying plait with  Cloudflare Pages  Cloudflare Pages

Latest commit: 976c1d0
Status: ✅  Deploy successful!
Preview URL: https://a15a0b6c.plait.pages.dev
Branch Preview URL: https://xingxing-table-resize-origin-t7hs.plait.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploying plait-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 976c1d0
Status: ✅  Deploy successful!
Preview URL: https://38acd7c6.plait-docs.pages.dev
Branch Preview URL: https://xingxing-table-resize-origin-nfs4.plait-docs.pages.dev

View logs

@pubuzhixing8
pubuzhixing8 marked this pull request as ready for review July 25, 2026 09:00
@pubuzhixing8

Copy link
Copy Markdown
Collaborator Author

Hi @nightt5879 , Please help review, I also improve text fixture in this PR!

@nightt5879 nightt5879 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up. The overall direction looks good, and I left the higher-priority behavior and fixture notes inline. Two small, non-blocking cleanup notes:

  • [P3] fixture.destroy() only traverses the current board.children, so initial nodes replaced by Immer can retain NODE_TO_PARENT / NODE_TO_INDEX entries. Keeping the initial snapshot (or all mapped nodes) for cleanup would improve test isolation.
  • [P3] fill-style.spec.ts calls the same GeometryShapeGenerator.draw() twice at lines 61-62; one of the calls can be removed.

[220, 100]
]);
fixture.board.pointerMove(createPointerEvent('pointermove', 100, 50));
tick(16);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Could we also cover the case where the move back to x=100 and pointerUp happen within the same RAF? In that order, globalPointerUp clears startPoint / resizeRef before the queued resize callback runs, so the table can remain at width 120 (points ending at 220). Reordering this part to move(100) -> pointerUp -> tick reproduces it. It may be worth applying or flushing the pending final resize before clearing the resize state.

appliedOffset,
edge
);
Transforms.setNode(board, { rows, points }, path);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Could we avoid setNode when clamping produces no live change? For a cell already at MIN_CELL_SIZE, an inward drag has targetSize === currentSize, but the new arrays still create a set_node operation. With withHistory this clears redo and can add a no-op undo entry. Comparing the result with the live node would preserve the origin restore while skipping a true no-op.

);
Transforms.setNode(board, { rows, points }, path);
} else {
const { columns, points } = updateColumns(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Returning to the origin restores the numeric size, but not the original auto-size representation: a column like { id } becomes { id, width: 100 } (and similarly for row height). That changes later remaining-space distribution, especially for swimlanes. Could the zero-offset restore reuse the pointer-down rows / columns / points snapshot exactly?

rectangle: () => createG(),
linearPath: () => createG()
};
return roughSVG as unknown as RoughSVG;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] This object is cast to the full RoughSVG interface, but it is missing methods already used by repository drawing paths, including line, curve, and ellipse. A fixture exercising those paths will fail at runtime. Could we implement the used RoughSVG surface here, or narrow the declared mock type?


export const fakeBoardHost = (board: PlaitBoard) => {
const host = createSVG();
Object.defineProperty(host, 'viewBox', {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Defining viewBox as a fixed own property means a later setAttribute('viewBox', ...) does not update baseVal. getViewBox / toViewBoxPoint will therefore continue reading 0,0,1000,1000 after viewport changes. Could we initialize the live SVG viewBox instead, or keep this mock value synchronized with attribute updates?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants