Skip to content

Fix CSS scale handling by passing through mjolnir.js event data#558

Merged
charlieforward9 merged 3 commits intovisgl:masterfrom
rjwats:bugfix/149-AddCSSTransformCompensation
Apr 9, 2026
Merged

Fix CSS scale handling by passing through mjolnir.js event data#558
charlieforward9 merged 3 commits intovisgl:masterfrom
rjwats:bugfix/149-AddCSSTransformCompensation

Conversation

@rjwats
Copy link
Copy Markdown
Contributor

@rjwats rjwats commented Mar 19, 2026

This PR fixes incorrect screen coordinate calculation in EditableLayer by using the offsetCenter provided by mjolnir.js gesture events, instead of recomputing coordinates from the DOM event (clientX/Y + canvas bounds).

I've also tightened up the types in editable-layer.ts, there are a lot of events typed as any which this tries to address too.

Some local manual testing done, no tests written yet - raising to get initial thoughts on this approach.

@rjwats
Copy link
Copy Markdown
Contributor Author

rjwats commented Mar 19, 2026

I've added a unit test for the abstract class which this PR updates. It's so heavily integrated with deck.gl and mjolnir.js that there's a lot of mocking needed to exercise the code.. maybe there's a better way to test this?

@rjwats
Copy link
Copy Markdown
Contributor Author

rjwats commented Mar 19, 2026

To locally test this you can add this div around the DeckGL element in advanced editor example:

<div
        style={{
          transform: 'scaleX(1.2) scaleY(0.8)',
          transformOrigin: 'center',
          width: '100%',
          height: '100%'
        }}
      >
        <DeckGL>.... </DeckGL>
</div>

Behavior before the fix:

before.mp4

Behavior after the fix:

after.mp4

@rjwats rjwats changed the title WIP: Fix CSS scale handling by passing through mjolnir.js event data Fix CSS scale handling by passing through mjolnir.js event data Mar 19, 2026
@charlieforward9
Copy link
Copy Markdown
Collaborator

Nice fix — using offsetCenter instead of clientX/Y + getBoundingClientRect() is the right call, and the types cleanup is a solid bonus.

Re: your question about the testing approach — the mocking level is appropriate here. You can't avoid mocking context.deck.pickMultipleObjects, context.viewport.unproject, or context.deck.eventManager without headless WebGL, so unit-testing at this boundary is the right strategy.

That said, the test could be leaner. Since all the event handlers now delegate to toBasePointerEvent, you're re-verifying the same transformation (offsetCenter → screenCoords → mapCoords → picks) across every handler. You could test toBasePointerEvent once directly, then just verify each handler delegates to it:

// Test the actual fix — one focused test
test('toBasePointerEvent uses offsetCenter, not clientX/Y', () => {
  const event = makeMockGestureEvent('click');
  const result = layer.toBasePointerEvent(event);
  expect(result.screenCoords).toEqual([event.offsetCenter.x, event.offsetCenter.y]);
  expect(result.mapCoords).toEqual(MOCK_EVENT_MAP_COORDS);
  expect(result.picks).toHaveLength(1);
  expect(result.sourceEvent).toBe(event.srcEvent);
});

// Then just verify wiring — each handler delegates correctly
test('event handlers delegate to toBasePointerEvent', () => {
  const spy = vi.spyOn(layer, 'toBasePointerEvent');
  layer.onLayerClick = vi.fn();
  layer._onclick(makeMockGestureEvent('click'));
  expect(spy).toHaveBeenCalledOnce();
});

That would cut the test file significantly while covering the same surface. The handler-specific tests (_onpanstart writing pointerDown state, _onpanend resetting it, cancelPan propagation) are still worth keeping — those test real handler logic, not just the shared transformation.

@charlieforward9 charlieforward9 linked an issue Apr 7, 2026 that may be closed by this pull request
8 tasks
@rjwats rjwats closed this Apr 9, 2026
@rjwats rjwats force-pushed the bugfix/149-AddCSSTransformCompensation branch from b924048 to 635bb23 Compare April 9, 2026 14:31
@rjwats rjwats reopened this Apr 9, 2026
@rjwats
Copy link
Copy Markdown
Contributor Author

rjwats commented Apr 9, 2026

Thanks for the helpful feedback @charlieforward9

The test does repeat itself a bit 👍. A targeted test for the fix is a really solid idea, what about a pattern like this:

const assertBasePointerEvent = (evt: BasePointerEvent, mockSrcEvent: any) => {
  expect(evt.screenCoords).toEqual(MOCK_EVENT_SCREEN_COORDS);
  expect(evt.mapCoords).toEqual(MOCK_EVENT_MAP_COORDS);
  expect(evt.sourceEvent).toBe(mockSrcEvent.srcEvent);
  expect(Array.isArray(evt.picks)).toBe(true);
  expect(evt.picks[0]).toMatchObject({ index: 0, object: { id: 1 } });
  return evt;
};

const expectBasePointerEvent = <E extends BasePointerEvent>(callback: any, mockSrcEvent: any): E => {
  expect(callback).toHaveBeenCalledOnce();

  return assertBasePointerEvent(callback.mock.calls[0][0], mockSrcEvent) as E;
};

// The targeted test for the new behaviour
test('toBasePointerEvent uses offsetCenter, not clientX/Y', () => {
  const event = makeMockGestureEvent('click');
  const result = layer.toBasePointerEvent(event);

  assertBasePointerEvent(result, event);
});

// Re-use the helpers to ensure that the events that get passed to the event handler functions look correct
test('_onpanstart calls onStartDragging with correct event', () => {
  const onStartDraggingSpy = vi.fn();
  layer.onStartDragging = onStartDraggingSpy;

  const mockEvent = makeMockGestureEvent('panstart');
  layer._onpanstart(mockEvent);

  // verify BasePointerEvent properties are correct
  const event: StartDraggingEvent = expectBasePointerEvent(onStartDraggingSpy, mockEvent);

  // verify DraggingEvent properties are correct (though, this is also common and could be in another shared helper)
  expect(event.pointerDownScreenCoords).toEqual(MOCK_EVENT_SCREEN_COORDS);
  expect(event.pointerDownMapCoords).toEqual(MOCK_EVENT_MAP_COORDS);
  expect(Array.isArray(event.pointerDownPicks)).toBe(true);
  expect(event.pointerDownPicks[0]).toMatchObject({ index: 0, object: { id: 1 } });
  expect(typeof event.cancelPan).toBe('function');
});

@charlieforward9
Copy link
Copy Markdown
Collaborator

Beautiful ✨

@charlieforward9 charlieforward9 merged commit f8f1989 into visgl:master Apr 9, 2026
2 of 3 checks passed
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.

[Bug] EditableGeoJsonLayer does not compensate for CSS transforms

2 participants