From a007c66b7897a5a38283b68d310a2cf92f24aefb Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 9 Oct 2023 07:34:48 +0100 Subject: [PATCH] prevent hover indicator from showing when pointer isn't over the canvas (#2023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before the geometry change, we'd rely on the browser to tell us which element was hovered, which meant that when the pointer left the canvas we'd automatically clear the hovered shape. Currently, we don't know whether the pointer is over the canvas or not - so we keep showing the hover indicator for the last shape you had your pointer over. This diff adds an `isHoveringCanvas` prop to the instance state (true, false, or null if the current pointer doesn't support hovering) that we can use to track this and disable the hover indicator appropriately. ![Kapture 2023-10-05 at 12 00 00](https://github.com/tldraw/tldraw/assets/1489520/236b9459-878b-47e2-bcaa-10d245581347) ### Change Type - [x] `minor` — New feature ### Test Plan 1. Create some shapes that go below the UI 2. Move the mouse from the shape to the UI 3. Hover indicator should disappear --- packages/editor/src/lib/components/Canvas.tsx | 7 +++++- .../editor/src/lib/hooks/useCanvasEvents.ts | 16 +++++++++++++ packages/tlschema/api-report.md | 1 + packages/tlschema/src/migrations.test.ts | 12 ++++++++++ packages/tlschema/src/records/TLInstance.ts | 23 ++++++++++++++++++- 5 files changed, 57 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/lib/components/Canvas.tsx b/packages/editor/src/lib/components/Canvas.tsx index 197463f6e..6f99bdad1 100644 --- a/packages/editor/src/lib/components/Canvas.tsx +++ b/packages/editor/src/lib/components/Canvas.tsx @@ -355,11 +355,16 @@ const HoveredShapeIndicator = function HoveredShapeIndicator() { const isCoarsePointer = useValue('coarse pointer', () => editor.instanceState.isCoarsePointer, [ editor, ]) + const isHoveringCanvas = useValue( + 'hovering canvas', + () => editor.instanceState.isHoveringCanvas, + [editor] + ) const hoveredShapeId = useValue('hovered id', () => editor.currentPageState.hoveredShapeId, [ editor, ]) - if (isCoarsePointer || !hoveredShapeId || !HoveredShapeIndicator) return null + if (isCoarsePointer || !isHoveringCanvas || !hoveredShapeId || !HoveredShapeIndicator) return null return } diff --git a/packages/editor/src/lib/hooks/useCanvasEvents.ts b/packages/editor/src/lib/hooks/useCanvasEvents.ts index 4eb93ea78..bd4c029bb 100644 --- a/packages/editor/src/lib/hooks/useCanvasEvents.ts +++ b/packages/editor/src/lib/hooks/useCanvasEvents.ts @@ -74,6 +74,20 @@ export function useCanvasEvents() { }) } + function onPointerEnter(e: React.PointerEvent) { + if ((e as any).isKilled) return + if (editor.instanceState.isPenMode && e.pointerType !== 'pen') return + const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen' + editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null }) + } + + function onPointerLeave(e: React.PointerEvent) { + if ((e as any).isKilled) return + if (editor.instanceState.isPenMode && e.pointerType !== 'pen') return + const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen' + editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null }) + } + function onTouchStart(e: React.TouchEvent) { ;(e as any).isKilled = true // todo: investigate whether this effects keyboard shortcuts @@ -118,6 +132,8 @@ export function useCanvasEvents() { onPointerDown, onPointerMove, onPointerUp, + onPointerEnter, + onPointerLeave, onDragOver, onDrop, onTouchStart, diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md index 190712b85..5ba13e082 100644 --- a/packages/tlschema/api-report.md +++ b/packages/tlschema/api-report.md @@ -1033,6 +1033,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> { isFocusMode: boolean; // (undocumented) isGridMode: boolean; + isHoveringCanvas: boolean | null; // (undocumented) isPenMode: boolean; // (undocumented) diff --git a/packages/tlschema/src/migrations.test.ts b/packages/tlschema/src/migrations.test.ts index ce642475c..8a629f20a 100644 --- a/packages/tlschema/src/migrations.test.ts +++ b/packages/tlschema/src/migrations.test.ts @@ -1535,6 +1535,18 @@ describe('Adding canSnap to line handles', () => { }) }) +describe('add isHoveringCanvas to TLInstance', () => { + const { up, down } = instanceMigrations.migrators[instanceVersions.AddHoveringCanvas] + + test('up works as expected', () => { + expect(up({})).toEqual({ isHoveringCanvas: null }) + }) + + test('down works as expected', () => { + expect(down({ isHoveringCanvas: null })).toEqual({}) + }) +}) + /* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */ for (const migrator of allMigrators) { diff --git a/packages/tlschema/src/records/TLInstance.ts b/packages/tlschema/src/records/TLInstance.ts index 6d294869f..4717b8acc 100644 --- a/packages/tlschema/src/records/TLInstance.ts +++ b/packages/tlschema/src/records/TLInstance.ts @@ -40,6 +40,11 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> { isFocused: boolean devicePixelRatio: number isCoarsePointer: boolean + /** + * Will be null if the pointer doesn't support hovering (e.g. touch), but true or false + * otherwise + */ + isHoveringCanvas: boolean | null openMenus: string[] isChangingStyle: boolean isReadonly: boolean @@ -85,6 +90,7 @@ export function createInstanceRecordType(stylesById: Map { @@ -453,6 +461,19 @@ export const instanceMigrations = defineMigrations({ } }, }, + [instanceVersions.AddHoveringCanvas]: { + up: (record) => { + return { + ...record, + isHoveringCanvas: null, + } + }, + down: ({ isHoveringCanvas: _, ...record }) => { + return { + ...record, + } + }, + }, }, })