diff --git a/.changeset/slimy-fishes-tease.md b/.changeset/slimy-fishes-tease.md new file mode 100644 index 000000000..f10e8d4d1 --- /dev/null +++ b/.changeset/slimy-fishes-tease.md @@ -0,0 +1,5 @@ +--- +"@logicflow/core": patch +--- + +fix(core): prevent unnecessary node re-renders during canvas zoom diff --git a/packages/core/__tests__/view/base-node.test.tsx b/packages/core/__tests__/view/base-node.test.tsx new file mode 100644 index 000000000..00348124e --- /dev/null +++ b/packages/core/__tests__/view/base-node.test.tsx @@ -0,0 +1,109 @@ +import { createElement as h } from 'preact/compat' + +jest.mock('../../src/LogicFlow', () => ({ __esModule: true, default: {} })) +jest.mock('../../src/model', () => ({})) +jest.mock('../../src/view/Anchor', () => ({ + __esModule: true, + default: () => null, +})) +jest.mock('../../src/view/text', () => ({ BaseText: () => null })) +jest.mock('../../src/view/Rotate', () => ({ + __esModule: true, + default: () => null, +})) +jest.mock('../../src/view/Control', () => ({ + __esModule: true, + default: () => null, +})) +jest.mock('../../src/constant', () => ({ + ElementState: { + ALLOW_CONNECT: 'ALLOW_CONNECT', + NOT_ALLOW_CONNECT: 'NOT_ALLOW_CONNECT', + }, + EventType: {}, + TextMode: { TEXT: 'TEXT' }, +})) +jest.mock('../../src/util', () => ({ + StepDrag: class { + setStep = jest.fn() + handleMouseDown = jest.fn() + setModel = jest.fn() + }, + snapToGrid: jest.fn((value) => value), + isIe: jest.fn(() => false), + isMultipleSelect: jest.fn(() => false), + cancelRaf: jest.fn(), + createRaf: jest.fn(), +})) + +import BaseNode from '../../src/view/node/BaseNode' + +class TestNode extends BaseNode { + getShape() { + return + } +} + +const createBaseNode = () => { + const model = { + id: 'node-1', + anchors: [], + autoToFront: false, + draggable: true, + isDragging: false, + isHitable: true, + isHovered: false, + isSelected: false, + isShowAnchor: false, + transform: '', + getData: jest.fn(() => ({ id: 'node-1' })), + getOuterGAttributes: jest.fn(() => ({})), + } + const graphModel = { + gridSize: 10, + eventCenter: { emit: jest.fn() }, + transformModel: { SCALE_X: 2 }, + editConfigModel: { + adjustNodePosition: true, + allowResize: false, + allowRotate: false, + hideAnchors: false, + }, + } + const node = new TestNode({ + model, + graphModel, + } as any) + ;(node as any).props = { model, graphModel } + + jest.spyOn(node.stepDrag, 'setStep') + jest.spyOn(node.stepDrag, 'handleMouseDown').mockImplementation(jest.fn()) + + return { node } +} + +describe('BaseNode drag step', () => { + test('updates scaled drag step on pointer down instead of render', () => { + const { node } = createBaseNode() + + node.render() + + expect(node.stepDrag.setStep).not.toHaveBeenCalled() + + const ev = { + clientX: 12, + clientY: 24, + pointerType: 'mouse', + } as PointerEvent + + node.handleMouseDown(ev) + + expect(node.stepDrag.setStep).toHaveBeenCalledWith(20) + expect(node.stepDrag.handleMouseDown).toHaveBeenCalledWith(ev) + expect( + (node.stepDrag.setStep as jest.Mock).mock.invocationCallOrder[0], + ).toBeLessThan( + (node.stepDrag.handleMouseDown as jest.Mock).mock.invocationCallOrder[0], + ) + }) +}) diff --git a/packages/core/src/view/node/BaseNode.tsx b/packages/core/src/view/node/BaseNode.tsx index c4847f353..41ab21b2c 100644 --- a/packages/core/src/view/node/BaseNode.tsx +++ b/packages/core/src/view/node/BaseNode.tsx @@ -453,9 +453,10 @@ export abstract class BaseNode

extends Component< const { model, graphModel } = this.props this.mouseDownPosition = { x: ev.clientX, y: ev.clientY } this.startTime = new Date().getTime() - const { editConfigModel } = graphModel + const { editConfigModel, gridSize, transformModel } = graphModel if (editConfigModel.adjustNodePosition && model.draggable) { - this.stepDrag && this.stepDrag.handleMouseDown(ev) + this.stepDrag.setStep(gridSize * transformModel.SCALE_X) + this.stepDrag.handleMouseDown(ev) } if (this.longPressTimer) { clearTimeout(this.longPressTimer) @@ -522,16 +523,9 @@ export abstract class BaseNode

extends Component< render() { const { model, graphModel } = this.props const { - editConfigModel: { - hideAnchors, - adjustNodePosition, - allowRotate, - allowResize, - }, - gridSize, - transformModel: { SCALE_X }, + editConfigModel: { hideAnchors, allowRotate, allowResize }, } = graphModel - const { isHitable, draggable, transform } = model + const { isHitable, transform } = model const { className = '', ...restAttributes } = model.getOuterGAttributes() const nodeShapeInner = ( @@ -555,9 +549,6 @@ export abstract class BaseNode

extends Component< ) } else { - if (adjustNodePosition && draggable) { - this.stepDrag.setStep(gridSize * SCALE_X) - } nodeShape = (