Fix keyboard shortcuts bugs (#2936)

This PR moves the focus 

### Change Type

- [x] `minor` 

### Test Plan

1. Select an element.
2. Press the delete quick action menu button.
3. Undo the delete with a keyboard shortcut.

1. Create a geo shape
2. Use the style panel to change the geo type
3. Undo so that it deletes
4. Try to redo

### Release Notes

- [Fix] Keyboard shortcut focus bug

---------

Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
This commit is contained in:
Steve Ruiz 2024-02-23 15:35:13 +00:00 committed by GitHub
parent fcf97958e8
commit 37bd92ef60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 67 additions and 3 deletions

View file

@ -0,0 +1,44 @@
import { Editor, Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
import { useEffect, useRef, useState } from 'react'
export default function EditorFocusExample() {
const [focused, setFocused] = useState(false)
const rEditorRef = useRef<Editor | null>(null)
useEffect(() => {
const editor = rEditorRef.current
if (!editor) return
editor.updateInstanceState({ isFocused: focused })
}, [focused])
return (
<div style={{ padding: 32 }}>
<div style={{ display: 'flex', gap: 4 }}>
<input
id={'focus'}
type={'checkbox'}
onChange={(e) => {
setFocused(e.target.checked)
}}
/>
<label htmlFor={'focus'}>Focus</label>
</div>
<p>
The checkbox controls the editor's <code>instanceState.isFocused</code> property.
</p>
<p>
When the editor is "focused", its keyboard shortcuts will work. When it is not focused, the
keyboard shortcuts will not work.
</p>
<div style={{ width: 800, maxWidth: '100%', height: 500 }}>
<Tldraw
autoFocus={false}
onMount={(editor) => {
rEditorRef.current = editor
}}
/>
</div>
</div>
)
}

View file

@ -0,0 +1,18 @@
---
title: Editor focus
component: ./EditorFocusExample.tsx
category: basic
priority: 7
---
The editor's keyboard shortcuts only work when the editor is "focused".
---
In this example, we drive the editor's focus in order to turn on and off keyboard shortcuts.
The editor's focus is different from—but usually corresponds to—the browser's concept of "focus", which is related to the document's [active element](https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement).
Unfortunately, the browser's focus cannot be relied on to determine whether the editor's keyboard shortcuts should work. While its possible to detect whether the document's active element is a descendant of the Tldraw component's own element, it's not 100% reliable. For example, iframes are not considered descendants of their parents, and many menus are portalled into different parts of the document tree.
For these reasons, the responsibility falls to you, dear developer, to manage focus for your Tldraw editor, especially in cases where there are more than one editor on the same page.

View file

@ -31,6 +31,8 @@ export default function MultipleExample() {
backgroundColor: '#fff', backgroundColor: '#fff',
padding: 32, padding: 32,
}} }}
// Sorry you need to do this yourself
onPointerDown={() => setFocusedEditor(null)}
> >
<focusedEditorContext.Provider value={{ focusedEditor, setFocusedEditor }}> <focusedEditorContext.Provider value={{ focusedEditor, setFocusedEditor }}>
<h1>Focusing: {focusedEditor ?? 'none'}</h1> <h1>Focusing: {focusedEditor ?? 'none'}</h1>

View file

@ -2,7 +2,7 @@
title: Multiple editors title: Multiple editors
component: ./MultipleExample.tsx component: ./MultipleExample.tsx
category: basic category: basic
priority: 7 priority: 8
--- ---
Use multiple `<Tldraw/>` components on the same page. Use multiple `<Tldraw/>` components on the same page.

View file

@ -33,13 +33,13 @@ export function useKeyboardShortcuts() {
hotkeys.setScope(editor.store.id) hotkeys.setScope(editor.store.id)
const hot = (keys: string, callback: (event: KeyboardEvent) => void) => { const hot = (keys: string, callback: (event: KeyboardEvent) => void) => {
hotkeys(keys, { element: container, scope: editor.store.id }, callback) hotkeys(keys, { element: document.body, scope: editor.store.id }, callback)
} }
const hotUp = (keys: string, callback: (event: KeyboardEvent) => void) => { const hotUp = (keys: string, callback: (event: KeyboardEvent) => void) => {
hotkeys( hotkeys(
keys, keys,
{ element: container, keyup: true, keydown: false, scope: editor.store.id }, { element: document.body, keyup: true, keydown: false, scope: editor.store.id },
callback callback
) )
} }