prevent hover indicator from showing when pointer isn't over the canvas (#2023)
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
This commit is contained in:
parent
e2a6f3ed40
commit
a007c66b78
5 changed files with 57 additions and 2 deletions
|
@ -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 <HoveredShapeIndicator shapeId={hoveredShapeId} />
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1033,6 +1033,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
|||
isFocusMode: boolean;
|
||||
// (undocumented)
|
||||
isGridMode: boolean;
|
||||
isHoveringCanvas: boolean | null;
|
||||
// (undocumented)
|
||||
isPenMode: boolean;
|
||||
// (undocumented)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<string, StyleProp<unkno
|
|||
isFocused: T.boolean,
|
||||
devicePixelRatio: T.number,
|
||||
isCoarsePointer: T.boolean,
|
||||
isHoveringCanvas: T.boolean.nullable(),
|
||||
openMenus: T.arrayOf(T.string),
|
||||
isChangingStyle: T.boolean,
|
||||
isReadonly: T.boolean,
|
||||
|
@ -122,6 +128,7 @@ export function createInstanceRecordType(stylesById: Map<string, StyleProp<unkno
|
|||
isFocused: false,
|
||||
devicePixelRatio: typeof window === 'undefined' ? 1 : window.devicePixelRatio,
|
||||
isCoarsePointer: false,
|
||||
isHoveringCanvas: null,
|
||||
openMenus: [] as string[],
|
||||
isChangingStyle: false,
|
||||
isReadonly: false,
|
||||
|
@ -152,11 +159,12 @@ export const instanceVersions = {
|
|||
RemoveCursorColor: 18,
|
||||
AddLonelyProperties: 19,
|
||||
ReadOnlyReadonly: 20,
|
||||
AddHoveringCanvas: 21,
|
||||
} as const
|
||||
|
||||
/** @public */
|
||||
export const instanceMigrations = defineMigrations({
|
||||
currentVersion: instanceVersions.ReadOnlyReadonly,
|
||||
currentVersion: instanceVersions.AddHoveringCanvas,
|
||||
migrators: {
|
||||
[instanceVersions.AddTransparentExportBgs]: {
|
||||
up: (instance: TLInstance) => {
|
||||
|
@ -453,6 +461,19 @@ export const instanceMigrations = defineMigrations({
|
|||
}
|
||||
},
|
||||
},
|
||||
[instanceVersions.AddHoveringCanvas]: {
|
||||
up: (record) => {
|
||||
return {
|
||||
...record,
|
||||
isHoveringCanvas: null,
|
||||
}
|
||||
},
|
||||
down: ({ isHoveringCanvas: _, ...record }) => {
|
||||
return {
|
||||
...record,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue