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, + } + }, + }, }, })