diff --git a/apps/dotcom/src/components/BoardHistorySnapshot/BoardHistorySnapshot.tsx b/apps/dotcom/src/components/BoardHistorySnapshot/BoardHistorySnapshot.tsx index e8a07d470..d9a57e759 100644 --- a/apps/dotcom/src/components/BoardHistorySnapshot/BoardHistorySnapshot.tsx +++ b/apps/dotcom/src/components/BoardHistorySnapshot/BoardHistorySnapshot.tsx @@ -67,7 +67,6 @@ export function BoardHistorySnapshot({ }} overrides={[fileSystemUiOverrides]} inferDarkMode - autoFocus />
diff --git a/apps/dotcom/src/components/CursorChatBubble.tsx b/apps/dotcom/src/components/CursorChatBubble.tsx index 45206863e..b4b6134fa 100644 --- a/apps/dotcom/src/components/CursorChatBubble.tsx +++ b/apps/dotcom/src/components/CursorChatBubble.tsx @@ -9,7 +9,7 @@ import { useRef, useState, } from 'react' -import { preventDefault, track, useContainer, useEditor, useTranslation } from 'tldraw' +import { preventDefault, track, useEditor, useTranslation } from 'tldraw' // todo: // - not cleaning up @@ -18,7 +18,6 @@ const CHAT_MESSAGE_TIMEOUT_CHATTING = 5000 export const CursorChatBubble = track(function CursorChatBubble() { const editor = useEditor() - const container = useContainer() const { isChatting, chatMessage } = editor.getInstanceState() const rTimeout = useRef(-1) @@ -31,14 +30,14 @@ export const CursorChatBubble = track(function CursorChatBubble() { rTimeout.current = setTimeout(() => { editor.updateInstanceState({ chatMessage: '', isChatting: false }) setValue('') - container.focus() + editor.focus() }, duration) } return () => { clearTimeout(rTimeout.current) } - }, [container, editor, chatMessage, isChatting]) + }, [editor, chatMessage, isChatting]) if (isChatting) return @@ -101,7 +100,6 @@ const CursorChatInput = track(function CursorChatInput({ }) { const editor = useEditor() const msg = useTranslation() - const container = useContainer() const ref = useRef(null) const placeholder = chatMessage || msg('cursor-chat.type-to-chat') @@ -126,11 +124,9 @@ const CursorChatInput = track(function CursorChatInput({ }, [editor, value, placeholder]) useLayoutEffect(() => { - // Focus the editor - let raf = requestAnimationFrame(() => { - raf = requestAnimationFrame(() => { - ref.current?.focus() - }) + // Focus the input + const raf = requestAnimationFrame(() => { + ref.current?.focus() }) return () => { @@ -140,8 +136,8 @@ const CursorChatInput = track(function CursorChatInput({ const stopChatting = useCallback(() => { editor.updateInstanceState({ isChatting: false }) - container.focus() - }, [editor, container]) + editor.focus() + }, [editor]) // Update the chat message as the user types const handleChange = useCallback( diff --git a/apps/dotcom/src/components/LocalEditor.tsx b/apps/dotcom/src/components/LocalEditor.tsx index fc42eb00a..43babc805 100644 --- a/apps/dotcom/src/components/LocalEditor.tsx +++ b/apps/dotcom/src/components/LocalEditor.tsx @@ -102,7 +102,6 @@ export function LocalEditor() { assetUrls={assetUrls} persistenceKey={SCRATCH_PERSISTENCE_KEY} onMount={handleMount} - autoFocus overrides={[sharingUiOverrides, fileSystemUiOverrides]} onUiEvent={handleUiEvent} components={components} diff --git a/apps/dotcom/src/components/MultiplayerEditor.tsx b/apps/dotcom/src/components/MultiplayerEditor.tsx index 10f53df4e..32e1f5765 100644 --- a/apps/dotcom/src/components/MultiplayerEditor.tsx +++ b/apps/dotcom/src/components/MultiplayerEditor.tsx @@ -158,7 +158,6 @@ export function MultiplayerEditor({ initialState={isReadonly ? 'hand' : 'select'} onUiEvent={handleUiEvent} components={components} - autoFocus inferDarkMode > diff --git a/apps/dotcom/src/components/PeopleMenu/UserPresenceEditor.tsx b/apps/dotcom/src/components/PeopleMenu/UserPresenceEditor.tsx index ab168473f..0cf47d336 100644 --- a/apps/dotcom/src/components/PeopleMenu/UserPresenceEditor.tsx +++ b/apps/dotcom/src/components/PeopleMenu/UserPresenceEditor.tsx @@ -52,8 +52,8 @@ export function UserPresenceEditor() { onCancel={toggleEditingName} onBlur={handleBlur} shouldManuallyMaintainScrollPositionWhenFocused - autofocus - autoselect + autoFocus + autoSelect /> ) : ( <> diff --git a/apps/dotcom/src/components/SnapshotsEditor.tsx b/apps/dotcom/src/components/SnapshotsEditor.tsx index 330cc875e..bc2cea9d1 100644 --- a/apps/dotcom/src/components/SnapshotsEditor.tsx +++ b/apps/dotcom/src/components/SnapshotsEditor.tsx @@ -89,7 +89,6 @@ export function SnapshotsEditor(props: SnapshotEditorProps) { }} components={components} renderDebugMenuItems={() => } - autoFocus inferDarkMode > diff --git a/apps/examples/e2e/tests/test-focus.spec.ts b/apps/examples/e2e/tests/test-focus.spec.ts index c2998f1f8..49708f702 100644 --- a/apps/examples/e2e/tests/test-focus.spec.ts +++ b/apps/examples/e2e/tests/test-focus.spec.ts @@ -175,4 +175,51 @@ test.describe('Focus', () => { null ) }) + + test('still focuses text after clicking on style button', async ({ page }) => { + await page.goto('http://localhost:5420/end-to-end') + await page.waitForSelector('.tl-canvas') + + const EditorA = (await page.$(`.tl-container`))! + expect(EditorA).toBeTruthy() + + // Create a new note, text should be focused + await page.keyboard.press('n') + await (await page.$('body'))?.click() + await page.waitForSelector('.tl-shape') + + const blueButton = await page.$('.tlui-button[data-testid="style.color.blue"]') + await blueButton?.dispatchEvent('pointerdown') + await blueButton?.click() + await blueButton?.dispatchEvent('pointerup') + + // Text should still be focused. + expect(await page.evaluate(() => document.activeElement?.nodeName === 'TEXTAREA')).toBe(true) + }) + + test('edit->edit, focus stays in the text areas when going from shape-to-shape', async ({ + page, + }) => { + await page.goto('http://localhost:5420/end-to-end') + await page.waitForSelector('.tl-canvas') + + const EditorA = (await page.$(`.tl-container`))! + expect(EditorA).toBeTruthy() + + // Create a new note, text should be focused + await page.keyboard.press('n') + await (await page.$('body'))?.click() + await page.waitForSelector('.tl-shape') + await page.keyboard.type('test') + + // create new note next to it + await page.keyboard.press('Tab') + + await (await page.$('body'))?.click() + + // First note's textarea should be focused. + expect(await EditorA.evaluate(() => !!document.querySelector('.tl-shape textarea:focus'))).toBe( + true + ) + }) }) diff --git a/apps/examples/src/examples/external-content-sources/ExternalContentSourcesExample.tsx b/apps/examples/src/examples/external-content-sources/ExternalContentSourcesExample.tsx index 619b79cd7..d8dd54836 100644 --- a/apps/examples/src/examples/external-content-sources/ExternalContentSourcesExample.tsx +++ b/apps/examples/src/examples/external-content-sources/ExternalContentSourcesExample.tsx @@ -67,7 +67,7 @@ export default function ExternalContentSourcesExample() { return (
- +
) } diff --git a/apps/examples/src/examples/multiple/MultipleExample.tsx b/apps/examples/src/examples/multiple/MultipleExample.tsx index 1eae026c8..490d7bf4b 100644 --- a/apps/examples/src/examples/multiple/MultipleExample.tsx +++ b/apps/examples/src/examples/multiple/MultipleExample.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useState } from 'react' -import { Tldraw } from 'tldraw' +import { Editor, Tldraw } from 'tldraw' import 'tldraw/tldraw.css' // There's a guide at the bottom of this page! @@ -7,24 +7,35 @@ import 'tldraw/tldraw.css' // [1] const focusedEditorContext = createContext( {} as { - focusedEditor: string | null - setFocusedEditor: (id: string | null) => void + focusedEditor: Editor | null + setFocusedEditor: (id: Editor | null) => void } ) // [2] export default function MultipleExample() { - const [focusedEditor, _setFocusedEditor] = useState('A') + const [focusedEditor, _setFocusedEditor] = useState(null) const setFocusedEditor = useCallback( - (id: string | null) => { - if (focusedEditor !== id) { - _setFocusedEditor(id) + (editor: Editor | null) => { + if (focusedEditor !== editor) { + focusedEditor?.updateInstanceState({ isFocused: false }) + _setFocusedEditor(editor) + editor?.updateInstanceState({ isFocused: true }) } }, [focusedEditor] ) + const focusName = + focusedEditor === (window as any).EDITOR_A + ? 'A' + : focusedEditor === (window as any).EDITOR_B + ? 'B' + : focusedEditor === (window as any).EDITOR_C + ? 'C' + : 'none' + return (
setFocusedEditor(null)} > -

Focusing: {focusedEditor ?? 'none'}

+

Focusing: {focusName}