From 72b66ad5c49077a9481563e1d63252aaa8029606 Mon Sep 17 00:00:00 2001 From: Matt Dawkins Date: Thu, 17 Sep 2026 16:39:27 -0400 Subject: [PATCH] Re-hover edit handles after the edit annotation is rebuilt under the cursor --- client/src/layers/EditAnnotationLayer.spec.ts | 18 ++++++++++++++++-- client/src/layers/EditAnnotationLayer.ts | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/client/src/layers/EditAnnotationLayer.spec.ts b/client/src/layers/EditAnnotationLayer.spec.ts index 4bb2bd498..a61f737b7 100644 --- a/client/src/layers/EditAnnotationLayer.spec.ts +++ b/client/src/layers/EditAnnotationLayer.spec.ts @@ -20,6 +20,7 @@ function harness() { const handlers = new Map void>>(); let mode: string | null = null; let annotation: any; + const handles = { _clearSelectedFeatures: vi.fn() }; const featureLayer: any = { annotations: () => (annotation ? [annotation] : []), mode: (value?: string | null, edited?: any) => { @@ -36,6 +37,7 @@ function harness() { geoOff: (name: string, fn: (e: any) => void) => handlers.get(name)?.delete(fn), removeAllAnnotations: () => { annotation = undefined; }, draw: vi.fn(), + features: () => [handles], geojson: (feature: GeoJSON.Feature) => { let vertices = feature.geometry.coordinates.map(([x, y]) => ({ x, y })); annotation = { @@ -53,7 +55,7 @@ function harness() { }; featureLayer.geoOn('mouseclick', featureLayer._handleMouseClick); const arrow: any = { style: vi.fn(), draw: vi.fn(), data: () => arrow }; - const interactor = { mouse: () => ({ buttons: { left: false } }) }; + const interactor = { mouse: () => ({ buttons: { left: false } }), retriggerMouseMove: vi.fn() }; const project = (p: { x: number; y: number }) => ({ x: p.x * 10, y: p.y * 10 }); const map = { createLayer: (type: string) => (type === 'annotation' ? featureLayer : { createFeature: () => arrow }), @@ -83,7 +85,7 @@ function harness() { handlers.get('mouseclick')!.forEach((fn) => fn(event)); }; return { - layer, track, reopen, update, click, featureLayer, + layer, track, reopen, update, click, featureLayer, handles, interactor, }; } @@ -130,3 +132,15 @@ it('synchronizes right-click exits and reopens the saved line repeatedly', async } expect(h.track.getFeatureGeometry(0, { key: 'HeadTails' })[0].geometry.coordinates).toHaveLength(5); }); + +it('re-hovers the handle under a stationary cursor after the edit annotation is rebuilt', async () => { + vi.useFakeTimers(); + const h = harness(); await h.reopen(); + vi.runAllTimers(); + expect(h.handles._clearSelectedFeatures).toHaveBeenCalledTimes(1); + expect(h.interactor.retriggerMouseMove).toHaveBeenCalledTimes(1); + h.layer.disable(); await h.layer.changeData([]); + vi.runAllTimers(); + expect(h.interactor.retriggerMouseMove).toHaveBeenCalledTimes(1); + vi.useRealTimers(); +}); diff --git a/client/src/layers/EditAnnotationLayer.ts b/client/src/layers/EditAnnotationLayer.ts index 6b11a9675..85b0eb74b 100644 --- a/client/src/layers/EditAnnotationLayer.ts +++ b/client/src/layers/EditAnnotationLayer.ts @@ -764,6 +764,7 @@ export default class EditAnnotationLayer extends BaseLayer { // disable resets things before we load a new/different shape or mode this.disable(); this.formattedData = this.formatData(frameData); + this.rehoverEditHandles(); } } } else { @@ -778,6 +779,22 @@ export default class EditAnnotationLayer extends BaseLayer { this.redraw(); } + /** + * GeoJS only fires mouseon when the handle under the cursor changes, so + * handles rebuilt beneath a stationary cursor stay inert until the mouse + * leaves and returns. Forget the stale hover and replay the mouse position. + */ + rehoverEditHandles() { + if (this.getMode() !== 'editing') return; + window.setTimeout(() => { + if (this.getMode() !== 'editing') return; + this.featureLayer.features().forEach( + (feature: { _clearSelectedFeatures?: () => void }) => feature._clearSelectedFeatures?.(), + ); + this.annotator.geoViewerRef.value.interactor().retriggerMouseMove(); + }, 0); + } + /** * * @param frameData a single FrameDataTrack Array that is the editing item