kopia lustrzana https://github.com/Tldraw/Tldraw
Merge 071bcc36e3
into 91903c9761
commit
ccb69b8ff7
|
@ -67,7 +67,6 @@ export function BoardHistorySnapshot({
|
|||
}}
|
||||
overrides={[fileSystemUiOverrides]}
|
||||
inferDarkMode
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="board-history__restore">
|
||||
|
|
|
@ -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<any>(-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 <CursorChatInput value={value} setValue={setValue} chatMessage={chatMessage} />
|
||||
|
@ -101,7 +100,6 @@ const CursorChatInput = track(function CursorChatInput({
|
|||
}) {
|
||||
const editor = useEditor()
|
||||
const msg = useTranslation()
|
||||
const container = useContainer()
|
||||
|
||||
const ref = useRef<HTMLInputElement>(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(
|
||||
|
|
|
@ -102,7 +102,6 @@ export function LocalEditor() {
|
|||
assetUrls={assetUrls}
|
||||
persistenceKey={SCRATCH_PERSISTENCE_KEY}
|
||||
onMount={handleMount}
|
||||
autoFocus
|
||||
overrides={[sharingUiOverrides, fileSystemUiOverrides]}
|
||||
onUiEvent={handleUiEvent}
|
||||
components={components}
|
||||
|
|
|
@ -158,7 +158,6 @@ export function MultiplayerEditor({
|
|||
initialState={isReadonly ? 'hand' : 'select'}
|
||||
onUiEvent={handleUiEvent}
|
||||
components={components}
|
||||
autoFocus
|
||||
inferDarkMode
|
||||
>
|
||||
<UrlStateSync />
|
||||
|
|
|
@ -52,8 +52,8 @@ export function UserPresenceEditor() {
|
|||
onCancel={toggleEditingName}
|
||||
onBlur={handleBlur}
|
||||
shouldManuallyMaintainScrollPositionWhenFocused
|
||||
autofocus
|
||||
autoselect
|
||||
autoFocus
|
||||
autoSelect
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
@ -89,7 +89,6 @@ export function SnapshotsEditor(props: SnapshotEditorProps) {
|
|||
}}
|
||||
components={components}
|
||||
renderDebugMenuItems={() => <DebugMenuItems />}
|
||||
autoFocus
|
||||
inferDarkMode
|
||||
>
|
||||
<UrlStateSync />
|
||||
|
|
|
@ -175,4 +175,53 @@ 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') === document.activeElement
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -67,7 +67,7 @@ export default function ExternalContentSourcesExample() {
|
|||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw autoFocus onMount={handleMount} shapeUtils={[DangerousHtmlExample]} />
|
||||
<Tldraw onMount={handleMount} shapeUtils={[DangerousHtmlExample]} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<string | null>('A')
|
||||
const [focusedEditor, _setFocusedEditor] = useState<Editor | null>(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 (
|
||||
<div
|
||||
style={{
|
||||
|
@ -35,7 +46,7 @@ export default function MultipleExample() {
|
|||
onPointerDown={() => setFocusedEditor(null)}
|
||||
>
|
||||
<focusedEditorContext.Provider value={{ focusedEditor, setFocusedEditor }}>
|
||||
<h1>Focusing: {focusedEditor ?? 'none'}</h1>
|
||||
<h1>Focusing: {focusName}</h1>
|
||||
<EditorA />
|
||||
<textarea data-testid="textarea" placeholder="type in me" style={{ margin: 10 }} />
|
||||
<div
|
||||
|
@ -61,19 +72,23 @@ export default function MultipleExample() {
|
|||
|
||||
// [3]
|
||||
function EditorA() {
|
||||
const { focusedEditor, setFocusedEditor } = useContext(focusedEditorContext)
|
||||
const isFocused = focusedEditor === 'A'
|
||||
const { setFocusedEditor } = useContext(focusedEditorContext)
|
||||
|
||||
return (
|
||||
<div style={{ padding: 32 }}>
|
||||
<h2>A</h2>
|
||||
<div tabIndex={-1} onFocus={() => setFocusedEditor('A')} style={{ height: 600 }}>
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => setFocusedEditor((window as any).EDITOR_A)}
|
||||
style={{ height: 600 }}
|
||||
>
|
||||
<Tldraw
|
||||
persistenceKey="steve"
|
||||
className="A"
|
||||
autoFocus={isFocused}
|
||||
autoFocus={false}
|
||||
onMount={(editor) => {
|
||||
;(window as any).EDITOR_A = editor
|
||||
setFocusedEditor(editor)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -83,17 +98,20 @@ function EditorA() {
|
|||
|
||||
// [4]
|
||||
function EditorB() {
|
||||
const { focusedEditor, setFocusedEditor } = useContext(focusedEditorContext)
|
||||
const isFocused = focusedEditor === 'B'
|
||||
const { setFocusedEditor } = useContext(focusedEditorContext)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>B</h2>
|
||||
<div tabIndex={-1} onFocus={() => setFocusedEditor('B')} style={{ height: 600 }}>
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => setFocusedEditor((window as any).EDITOR_B)}
|
||||
style={{ height: 600 }}
|
||||
>
|
||||
<Tldraw
|
||||
persistenceKey="david"
|
||||
className="B"
|
||||
autoFocus={isFocused}
|
||||
autoFocus={false}
|
||||
onMount={(editor) => {
|
||||
;(window as any).EDITOR_B = editor
|
||||
}}
|
||||
|
@ -104,17 +122,20 @@ function EditorB() {
|
|||
}
|
||||
|
||||
function EditorC() {
|
||||
const { focusedEditor, setFocusedEditor } = useContext(focusedEditorContext)
|
||||
const isFocused = focusedEditor === 'C'
|
||||
const { setFocusedEditor } = useContext(focusedEditorContext)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>C</h2>
|
||||
<div tabIndex={-1} onFocus={() => setFocusedEditor('C')} style={{ height: 600 }}>
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => setFocusedEditor((window as any).EDITOR_C)}
|
||||
style={{ height: 600 }}
|
||||
>
|
||||
<Tldraw
|
||||
persistenceKey="david"
|
||||
className="C"
|
||||
autoFocus={isFocused}
|
||||
autoFocus={false}
|
||||
onMount={(editor) => {
|
||||
;(window as any).EDITOR_C = editor
|
||||
}}
|
||||
|
|
|
@ -14,10 +14,7 @@ export default function ScrollExample() {
|
|||
}}
|
||||
>
|
||||
<div style={{ width: '60vw', height: '80vh' }}>
|
||||
<Tldraw
|
||||
persistenceKey="scroll-example"
|
||||
// autoFocus={false}
|
||||
/>
|
||||
<Tldraw persistenceKey="scroll-example" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -126,7 +126,6 @@ function TldrawInner({ uri, assetSrc, isDarkMode, fileContents }: TLDrawInnerPro
|
|||
persistenceKey={uri}
|
||||
onMount={handleMount}
|
||||
components={components}
|
||||
autoFocus
|
||||
>
|
||||
{/* <DarkModeHandler themeKind={themeKind} /> */}
|
||||
|
||||
|
|
|
@ -663,7 +663,7 @@ export class Edge2d extends Geometry2d {
|
|||
|
||||
// @public (undocumented)
|
||||
export class Editor extends EventEmitter<TLEventMap> {
|
||||
constructor({ store, user, shapeUtils, bindingUtils, tools, getContainer, cameraOptions, initialState, inferDarkMode, }: TLEditorOptions);
|
||||
constructor({ store, user, shapeUtils, bindingUtils, tools, getContainer, cameraOptions, initialState, autoFocus, inferDarkMode, }: TLEditorOptions);
|
||||
addOpenMenu(id: string): this;
|
||||
alignShapes(shapes: TLShape[] | TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this;
|
||||
animateShape(partial: null | TLShapePartial | undefined, opts?: Partial<{
|
||||
|
@ -771,6 +771,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
findCommonAncestor(shapes: TLShape[] | TLShapeId[], predicate?: (shape: TLShape) => boolean): TLShapeId | undefined;
|
||||
findShapeAncestor(shape: TLShape | TLShapeId, predicate: (parent: TLShape) => boolean): TLShape | undefined;
|
||||
flipShapes(shapes: TLShape[] | TLShapeId[], operation: 'horizontal' | 'vertical'): this;
|
||||
focus(): this;
|
||||
readonly focusManager: FocusManager;
|
||||
getAncestorPageId(shape?: TLShape | TLShapeId): TLPageId | undefined;
|
||||
getAsset(asset: TLAsset | TLAssetId): TLAsset | undefined;
|
||||
getAssetForExternalContent(info: TLExternalAssetContent): Promise<TLAsset | undefined>;
|
||||
|
@ -1126,6 +1128,19 @@ export function extractSessionStateFromLegacySnapshot(store: Record<string, Unkn
|
|||
// @internal (undocumented)
|
||||
export const featureFlags: Record<string, DebugFlag<boolean>>;
|
||||
|
||||
// @public
|
||||
export class FocusManager {
|
||||
constructor(editor: Editor, autoFocus?: boolean);
|
||||
// (undocumented)
|
||||
blur(): void;
|
||||
// (undocumented)
|
||||
dispose(): void;
|
||||
// (undocumented)
|
||||
editor: Editor;
|
||||
// (undocumented)
|
||||
focus(): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type GapsSnapIndicator = {
|
||||
direction: 'horizontal' | 'vertical';
|
||||
|
@ -2261,6 +2276,7 @@ export type TLEditorComponents = Partial<{
|
|||
|
||||
// @public (undocumented)
|
||||
export interface TLEditorOptions {
|
||||
autoFocus?: boolean;
|
||||
bindingUtils: readonly TLBindingUtilConstructor<TLUnknownBinding>[];
|
||||
cameraOptions?: Partial<TLCameraOptions>;
|
||||
getContainer: () => HTMLElement;
|
||||
|
|
|
@ -132,6 +132,7 @@ export {
|
|||
type BindingOnShapeDeleteOptions,
|
||||
type TLBindingUtilConstructor,
|
||||
} from './lib/editor/bindings/BindingUtil'
|
||||
export type { FocusManager } from './lib/editor/managers/FocusManager'
|
||||
export { HistoryManager } from './lib/editor/managers/HistoryManager'
|
||||
export type {
|
||||
SideEffectManager,
|
||||
|
|
|
@ -30,10 +30,8 @@ import {
|
|||
useEditorComponents,
|
||||
} from './hooks/useEditorComponents'
|
||||
import { useEvent } from './hooks/useEvent'
|
||||
import { useFocusEvents } from './hooks/useFocusEvents'
|
||||
import { useForceUpdate } from './hooks/useForceUpdate'
|
||||
import { useLocalStore } from './hooks/useLocalStore'
|
||||
import { useSafariFocusOutFix } from './hooks/useSafariFocusOutFix'
|
||||
import { useZoomCss } from './hooks/useZoomCss'
|
||||
import { stopEventPropagation } from './utils/dom'
|
||||
import { TLStoreWithStatus } from './utils/sync/StoreWithStatus'
|
||||
|
@ -305,6 +303,7 @@ function TldrawEditorWithReadyStore({
|
|||
const { ErrorFallback } = useEditorComponents()
|
||||
const container = useContainer()
|
||||
const [editor, setEditor] = useState<Editor | null>(null)
|
||||
const [initialAutoFocus] = useState(autoFocus)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const editor = new Editor({
|
||||
|
@ -315,6 +314,7 @@ function TldrawEditorWithReadyStore({
|
|||
getContainer: () => container,
|
||||
user,
|
||||
initialState,
|
||||
autoFocus: initialAutoFocus,
|
||||
inferDarkMode,
|
||||
cameraOptions,
|
||||
})
|
||||
|
@ -331,6 +331,7 @@ function TldrawEditorWithReadyStore({
|
|||
store,
|
||||
user,
|
||||
initialState,
|
||||
initialAutoFocus,
|
||||
inferDarkMode,
|
||||
cameraOptions,
|
||||
])
|
||||
|
@ -374,30 +375,18 @@ function TldrawEditorWithReadyStore({
|
|||
<Crash crashingError={crashingError} />
|
||||
) : (
|
||||
<EditorContext.Provider value={editor}>
|
||||
<Layout autoFocus={autoFocus} onMount={onMount}>
|
||||
{children ?? (Canvas ? <Canvas /> : null)}
|
||||
</Layout>
|
||||
<Layout onMount={onMount}>{children ?? (Canvas ? <Canvas /> : null)}</Layout>
|
||||
</EditorContext.Provider>
|
||||
)}
|
||||
</OptionalErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
function Layout({
|
||||
children,
|
||||
onMount,
|
||||
autoFocus,
|
||||
}: {
|
||||
children: ReactNode
|
||||
autoFocus: boolean
|
||||
onMount?: TLOnMountHandler
|
||||
}) {
|
||||
function Layout({ children, onMount }: { children: ReactNode; onMount?: TLOnMountHandler }) {
|
||||
useZoomCss()
|
||||
useCursor()
|
||||
useDarkMode()
|
||||
useSafariFocusOutFix()
|
||||
useForceUpdate()
|
||||
useFocusEvents(autoFocus)
|
||||
useOnMount(onMount)
|
||||
|
||||
return (
|
||||
|
|
|
@ -125,6 +125,7 @@ import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage
|
|||
import { getSvgJsx } from './getSvgJsx'
|
||||
import { ClickManager } from './managers/ClickManager'
|
||||
import { EnvironmentManager } from './managers/EnvironmentManager'
|
||||
import { FocusManager } from './managers/FocusManager'
|
||||
import { HistoryManager } from './managers/HistoryManager'
|
||||
import { ScribbleManager } from './managers/ScribbleManager'
|
||||
import { SideEffectManager } from './managers/SideEffectManager'
|
||||
|
@ -198,6 +199,10 @@ export interface TLEditorOptions {
|
|||
* The editor's initial active tool (or other state node id).
|
||||
*/
|
||||
initialState?: string
|
||||
/**
|
||||
* Whether to automatically focus the editor when it mounts.
|
||||
*/
|
||||
autoFocus?: boolean
|
||||
/**
|
||||
* Whether to infer dark mode from the user's system preferences. Defaults to false.
|
||||
*/
|
||||
|
@ -219,6 +224,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getContainer,
|
||||
cameraOptions,
|
||||
initialState,
|
||||
autoFocus,
|
||||
inferDarkMode,
|
||||
}: TLEditorOptions) {
|
||||
super()
|
||||
|
@ -632,6 +638,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
this.root.enter(undefined, 'initial')
|
||||
|
||||
this.focusManager = new FocusManager(this, autoFocus)
|
||||
this.disposables.add(this.focusManager.dispose.bind(this.focusManager))
|
||||
|
||||
if (this.getInstanceState().followingUserId) {
|
||||
this.stopFollowingUser()
|
||||
}
|
||||
|
@ -711,6 +720,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*/
|
||||
readonly sideEffects: SideEffectManager<this>
|
||||
|
||||
/**
|
||||
* A manager for ensuring correct focus. See {@link FocusManager} for details.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
readonly focusManager: FocusManager
|
||||
|
||||
/**
|
||||
* The current HTML element containing the editor.
|
||||
*
|
||||
|
@ -8021,6 +8037,21 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a focus event.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.focus()
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
focus(): this {
|
||||
this.focusManager.focus()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* A manager for recording multiple click events.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import type { Editor } from '../Editor'
|
||||
|
||||
/**
|
||||
* A manager for ensuring correct focus across the editor.
|
||||
* It will listen for changes in the instance state to make sure the
|
||||
* container is focused when the editor is focused.
|
||||
* Also, it will make sure that the focus is on things like text
|
||||
* labels when the editor is in editing mode.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class FocusManager {
|
||||
private disposeSideEffectListener?: () => void
|
||||
|
||||
constructor(
|
||||
public editor: Editor,
|
||||
autoFocus?: boolean
|
||||
) {
|
||||
this.disposeSideEffectListener = editor.sideEffects.registerAfterChangeHandler(
|
||||
'instance',
|
||||
(prev, next) => {
|
||||
if (prev.isFocused !== next.isFocused) {
|
||||
next.isFocused ? this.focus() : this.blur()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const currentFocusState = editor.getInstanceState().isFocused
|
||||
if (autoFocus !== currentFocusState) {
|
||||
editor.updateInstanceState({ isFocused: !!autoFocus })
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.editor.getContainer().focus()
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.editor.complete() // stop any interaction
|
||||
this.editor.getContainer().blur() // blur the container
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposeSideEffectListener?.()
|
||||
}
|
||||
}
|
|
@ -40,15 +40,6 @@ export function useCanvasEvents() {
|
|||
name: 'pointer_down',
|
||||
...getPointerInfo(e),
|
||||
})
|
||||
|
||||
if (editor.getOpenMenus().length > 0) {
|
||||
editor.updateInstanceState({
|
||||
openMenus: [],
|
||||
})
|
||||
|
||||
document.body.click()
|
||||
editor.getContainer().focus()
|
||||
}
|
||||
}
|
||||
|
||||
function onPointerMove(e: React.PointerEvent) {
|
||||
|
@ -98,9 +89,6 @@ export function useCanvasEvents() {
|
|||
|
||||
function onTouchStart(e: React.TouchEvent) {
|
||||
;(e as any).isKilled = true
|
||||
// todo: investigate whether this effects keyboard shortcuts
|
||||
// god damn it, but necessary for long presses to open the context menu
|
||||
document.body.click()
|
||||
preventDefault(e)
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ export function useDocumentEvents() {
|
|||
// will break additional shortcuts. We need to
|
||||
// refocus the container in order to keep these
|
||||
// shortcuts working.
|
||||
container.focus()
|
||||
editor.focus()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import { useLayoutEffect } from 'react'
|
||||
import { useContainer } from './useContainer'
|
||||
import { useEditor } from './useEditor'
|
||||
|
||||
/** @internal */
|
||||
export function useFocusEvents(autoFocus: boolean) {
|
||||
const editor = useEditor()
|
||||
const container = useContainer()
|
||||
useLayoutEffect(() => {
|
||||
if (autoFocus) {
|
||||
// When autoFocus is true, update the editor state to be focused
|
||||
// unless it's already focused
|
||||
if (!editor.getInstanceState().isFocused) {
|
||||
editor.updateInstanceState({ isFocused: true })
|
||||
}
|
||||
|
||||
// Note: Focus is also handled by the side effect manager in tldraw.
|
||||
// Importantly, if a user manually sets isFocused to true (or if it
|
||||
// changes for any reason from false to true), the side effect manager
|
||||
// in tldraw will also take care of the focus. However, it may be that
|
||||
// on first mount the editor already has isFocused: true in the model,
|
||||
// so we also need to focus it here just to be sure.
|
||||
editor.getContainer().focus()
|
||||
} else {
|
||||
// When autoFocus is false, update the editor state to be not focused
|
||||
// unless it's already not focused
|
||||
if (editor.getInstanceState().isFocused) {
|
||||
editor.updateInstanceState({ isFocused: false })
|
||||
}
|
||||
}
|
||||
}, [editor, container, autoFocus])
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import { useEditor } from './useEditor'
|
||||
|
||||
let isMobileSafari = false
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const ua = window.navigator.userAgent
|
||||
const iOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i)
|
||||
const webkit = !!ua.match(/WebKit/i)
|
||||
isMobileSafari = iOS && webkit && !ua.match(/CriOS/i)
|
||||
}
|
||||
|
||||
export function useSafariFocusOutFix(): void {
|
||||
const editor = useEditor()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isMobileSafari) return
|
||||
|
||||
function handleFocusOut(e: FocusEvent) {
|
||||
if (
|
||||
(e.target instanceof HTMLInputElement && e.target.type === 'text') ||
|
||||
e.target instanceof HTMLTextAreaElement
|
||||
) {
|
||||
editor.complete()
|
||||
}
|
||||
}
|
||||
|
||||
// Send event on iOS when a user presses the "Done" key while editing a text element.
|
||||
document.addEventListener('focusout', handleFocusOut)
|
||||
return () => document.removeEventListener('focusout', handleFocusOut)
|
||||
}, [editor])
|
||||
}
|
|
@ -8,7 +8,7 @@ export type RecordTypeRecord<R extends RecordType<any, any>> = ReturnType<R['cre
|
|||
/**
|
||||
* Defines the scope of the record
|
||||
*
|
||||
* instance: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.
|
||||
* session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.
|
||||
* document: The record is persisted and synced. It is available to all store instances.
|
||||
* presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.
|
||||
*
|
||||
|
|
|
@ -2227,9 +2227,9 @@ export type TLUiIconType = 'align-bottom' | 'align-center-horizontal' | 'align-c
|
|||
// @public (undocumented)
|
||||
export interface TLUiInputProps {
|
||||
// (undocumented)
|
||||
autofocus?: boolean;
|
||||
autoFocus?: boolean;
|
||||
// (undocumented)
|
||||
autoselect?: boolean;
|
||||
autoSelect?: boolean;
|
||||
// (undocumented)
|
||||
children?: React_3.ReactNode;
|
||||
// (undocumented)
|
||||
|
@ -2618,7 +2618,7 @@ export function useDialogs(): TLUiDialogsContextType;
|
|||
|
||||
// @public (undocumented)
|
||||
export function useEditableText(id: TLShapeId, type: string, text: string): {
|
||||
handleBlur: () => void;
|
||||
handleBlur: typeof noop;
|
||||
handleChange: (e: React_2.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
handleDoubleClick: (e: any) => any;
|
||||
handleFocus: typeof noop;
|
||||
|
|
|
@ -2,16 +2,6 @@ import { Editor } from '@tldraw/editor'
|
|||
|
||||
export function registerDefaultSideEffects(editor: Editor) {
|
||||
return [
|
||||
editor.sideEffects.registerAfterChangeHandler('instance', (prev, next) => {
|
||||
if (prev.isFocused !== next.isFocused) {
|
||||
if (next.isFocused) {
|
||||
editor.getContainer().focus()
|
||||
} else {
|
||||
editor.complete() // stop any interaction
|
||||
editor.getContainer().blur() // blur the container
|
||||
}
|
||||
}
|
||||
}),
|
||||
editor.sideEffects.registerAfterChangeHandler('instance_page_state', (prev, next) => {
|
||||
if (prev.croppingShapeId !== next.croppingShapeId) {
|
||||
const isInCroppingState = editor.isInAny(
|
||||
|
|
|
@ -58,14 +58,6 @@ export const FrameHeading = function FrameHeading({
|
|||
// On iOS, we must focus here
|
||||
el.focus()
|
||||
el.select()
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// On desktop, the input may have lost focus, so try try try again!
|
||||
if (document.activeElement !== el) {
|
||||
el.focus()
|
||||
el.select()
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [rInput, isEditing])
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import { INDENT, TextHelpers } from './TextHelpers'
|
|||
export function useEditableText(id: TLShapeId, type: string, text: string) {
|
||||
const editor = useEditor()
|
||||
const rInput = useRef<HTMLTextAreaElement>(null)
|
||||
const rSelectionRanges = useRef<Range[] | null>()
|
||||
const isEditing = useValue('isEditing', () => editor.getEditingShapeId() === id, [editor])
|
||||
const isEditingAnything = useValue('isEditingAnything', () => !!editor.getEditingShapeId(), [
|
||||
editor,
|
||||
|
@ -21,98 +20,36 @@ export function useEditableText(id: TLShapeId, type: string, text: string) {
|
|||
|
||||
useEffect(() => {
|
||||
function selectAllIfEditing({ shapeId }: { shapeId: TLShapeId }) {
|
||||
// We wait a tick, because on iOS, the keyboard will not show if we focus immediately.
|
||||
requestAnimationFrame(() => {
|
||||
if (shapeId === id) {
|
||||
const elm = rInput.current
|
||||
if (elm) {
|
||||
if (document.activeElement !== elm) {
|
||||
elm.focus()
|
||||
}
|
||||
elm.select()
|
||||
}
|
||||
}
|
||||
})
|
||||
if (shapeId === id) {
|
||||
rInput.current?.select()
|
||||
}
|
||||
}
|
||||
|
||||
editor.on('select-all-text', selectAllIfEditing)
|
||||
return () => {
|
||||
editor.off('select-all-text', selectAllIfEditing)
|
||||
}
|
||||
}, [editor, id])
|
||||
}, [editor, id, isEditing])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) return
|
||||
|
||||
const elm = rInput.current
|
||||
if (!elm) return
|
||||
|
||||
// Focus if we're not already focused
|
||||
if (document.activeElement !== elm) {
|
||||
elm.focus()
|
||||
|
||||
// On mobile etc, just select all the text when we start focusing
|
||||
if (editor.getInstanceState().isCoarsePointer) {
|
||||
elm.select()
|
||||
}
|
||||
} else {
|
||||
// This fixes iOS not showing the cursor sometimes. This "shakes" the cursor
|
||||
// awake.
|
||||
if (editor.environment.isSafari) {
|
||||
elm.blur()
|
||||
elm.focus()
|
||||
}
|
||||
if (document.activeElement !== rInput.current) {
|
||||
rInput.current?.focus()
|
||||
}
|
||||
|
||||
// When the selection changes, save the selection ranges
|
||||
function updateSelection() {
|
||||
const selection = window.getSelection?.()
|
||||
if (selection && selection.type !== 'None') {
|
||||
const ranges: Range[] = []
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
ranges.push(selection.getRangeAt?.(i))
|
||||
}
|
||||
rSelectionRanges.current = ranges
|
||||
}
|
||||
if (editor.getInstanceState().isCoarsePointer) {
|
||||
rInput.current?.select()
|
||||
}
|
||||
|
||||
document.addEventListener('selectionchange', updateSelection)
|
||||
return () => {
|
||||
document.removeEventListener('selectionchange', updateSelection)
|
||||
// XXX(mime): This fixes iOS not showing the cursor sometimes.
|
||||
// This "shakes" the cursor awake.
|
||||
if (editor.environment.isSafari) {
|
||||
rInput.current?.blur()
|
||||
rInput.current?.focus()
|
||||
}
|
||||
}, [editor, isEditing])
|
||||
|
||||
// 2. Restore the selection changes (and focus) if the element blurs
|
||||
// When the label blurs, deselect all of the text and complete.
|
||||
// This makes it so that the canvas does not have to be focused
|
||||
// in order to exit the editing state and complete the editing state
|
||||
const handleBlur = useCallback(() => {
|
||||
const ranges = rSelectionRanges.current
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const elm = rInput.current
|
||||
const editingShapeId = editor.getEditingShapeId()
|
||||
|
||||
// Did we move to a different shape?
|
||||
if (editingShapeId) {
|
||||
// important! these ^v are two different things
|
||||
// is that shape OUR shape?
|
||||
if (elm && editingShapeId === id) {
|
||||
elm.focus()
|
||||
|
||||
if (ranges && ranges.length) {
|
||||
const selection = window.getSelection()
|
||||
if (selection) {
|
||||
ranges.forEach((range) => selection.addRange(range))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.getSelection()?.removeAllRanges()
|
||||
}
|
||||
})
|
||||
}, [editor, id])
|
||||
|
||||
// When the user presses ctrl / meta enter, complete the editing state.
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
|
@ -186,7 +123,7 @@ export function useEditableText(id: TLShapeId, type: string, text: string) {
|
|||
return {
|
||||
rInput,
|
||||
handleFocus: noop,
|
||||
handleBlur,
|
||||
handleBlur: noop,
|
||||
handleKeyDown,
|
||||
handleChange,
|
||||
handleInputPointerDown,
|
||||
|
|
|
@ -36,7 +36,6 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(function
|
|||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
autoSave="off"
|
||||
// autoFocus
|
||||
placeholder=""
|
||||
spellCheck="true"
|
||||
wrap="off"
|
||||
|
|
|
@ -217,7 +217,7 @@ export class PointingShape extends StateNode {
|
|||
if (this.editor.getInstanceState().isReadonly) return
|
||||
|
||||
// Re-focus the editor, just in case the text label of the shape has stolen focus
|
||||
this.editor.getContainer().focus()
|
||||
this.editor.focus()
|
||||
this.parent.transition('translating', info)
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({
|
|||
ref={rInput}
|
||||
className="tlui-edit-link-dialog__input"
|
||||
label="edit-link-dialog.url"
|
||||
autofocus
|
||||
autoFocus
|
||||
value={urlInputState.actual}
|
||||
onValueChange={handleChange}
|
||||
onComplete={handleComplete}
|
||||
|
|
|
@ -51,7 +51,7 @@ export const EmbedDialog = track(function EmbedDialog({ onClose }: TLUiDialogPro
|
|||
className="tlui-embed-dialog__input"
|
||||
label="embed-dialog.url"
|
||||
placeholder="http://example.com"
|
||||
autofocus
|
||||
autoFocus
|
||||
onValueChange={(value) => {
|
||||
// Set the url that the user has typed into the input
|
||||
setUrl(value)
|
||||
|
|
|
@ -34,8 +34,8 @@ export const PageItemInput = function PageItemInput({
|
|||
onValueChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
shouldManuallyMaintainScrollPositionWhenFocused
|
||||
autofocus={isCurrentPage}
|
||||
autoselect
|
||||
autoFocus={isCurrentPage}
|
||||
autoSelect
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { useEditor } from '@tldraw/editor'
|
||||
import classnames from 'classnames'
|
||||
import * as React from 'react'
|
||||
|
||||
|
@ -11,15 +10,6 @@ export interface TLUiButtonProps extends React.HTMLAttributes<HTMLButtonElement>
|
|||
/** @public */
|
||||
export const TldrawUiButton = React.forwardRef<HTMLButtonElement, TLUiButtonProps>(
|
||||
function TldrawUiButton({ children, disabled, type, ...props }, ref) {
|
||||
const editor = useEditor()
|
||||
|
||||
// If the button is getting disabled while it's focused, move focus to the editor
|
||||
// so that the user can continue using keyboard shortcuts
|
||||
const current = (ref as React.MutableRefObject<HTMLButtonElement | null>)?.current
|
||||
if (disabled && current === document.activeElement) {
|
||||
editor.getContainer().focus()
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
|
|
|
@ -40,6 +40,7 @@ function _TldrawUiButtonPicker<T extends string>(props: TLUiButtonPickerProps<T>
|
|||
const msg = useTranslation()
|
||||
|
||||
const rPointing = useRef(false)
|
||||
const rPointingOriginalActiveElement = useRef<HTMLElement | null>(null)
|
||||
|
||||
const {
|
||||
handleButtonClick,
|
||||
|
@ -50,6 +51,15 @@ function _TldrawUiButtonPicker<T extends string>(props: TLUiButtonPickerProps<T>
|
|||
const handlePointerUp = () => {
|
||||
rPointing.current = false
|
||||
window.removeEventListener('pointerup', handlePointerUp)
|
||||
|
||||
// This is fun little micro-optimization to make sure that the focus
|
||||
// is retained on a text label. That way, you can continue typing
|
||||
// after selecting a style.
|
||||
const origActiveEl = rPointingOriginalActiveElement.current
|
||||
if (origActiveEl && ['TEXTAREA', 'INPUT'].includes(origActiveEl.nodeName)) {
|
||||
origActiveEl.focus()
|
||||
}
|
||||
rPointingOriginalActiveElement.current = null
|
||||
}
|
||||
|
||||
const handleButtonClick = (e: React.PointerEvent<HTMLButtonElement>) => {
|
||||
|
@ -67,6 +77,7 @@ function _TldrawUiButtonPicker<T extends string>(props: TLUiButtonPickerProps<T>
|
|||
onValueChange(style, id as T)
|
||||
|
||||
rPointing.current = true
|
||||
rPointingOriginalActiveElement.current = document.activeElement as HTMLElement
|
||||
window.addEventListener('pointerup', handlePointerUp) // see TLD-658
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ export interface TLUiInputProps {
|
|||
label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
|
||||
icon?: TLUiIconType | Exclude<string, TLUiIconType>
|
||||
iconLeft?: TLUiIconType | Exclude<string, TLUiIconType>
|
||||
autofocus?: boolean
|
||||
autoselect?: boolean
|
||||
autoFocus?: boolean
|
||||
autoSelect?: boolean
|
||||
children?: React.ReactNode
|
||||
defaultValue?: string
|
||||
placeholder?: string
|
||||
|
@ -43,8 +43,8 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
|
|||
label,
|
||||
icon,
|
||||
iconLeft,
|
||||
autoselect = false,
|
||||
autofocus = false,
|
||||
autoSelect = false,
|
||||
autoFocus = false,
|
||||
defaultValue,
|
||||
placeholder,
|
||||
onComplete,
|
||||
|
@ -75,13 +75,13 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
|
|||
const elm = e.currentTarget as HTMLInputElement
|
||||
rCurrentValue.current = elm.value
|
||||
requestAnimationFrame(() => {
|
||||
if (autoselect) {
|
||||
if (autoSelect) {
|
||||
elm.select()
|
||||
}
|
||||
})
|
||||
onFocus?.()
|
||||
},
|
||||
[autoselect, onFocus]
|
||||
[autoSelect, onFocus]
|
||||
)
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
|
@ -159,7 +159,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
|
|||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
autoFocus={autofocus}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
/>
|
||||
|
|
|
@ -26,8 +26,6 @@ export function useKeyboardShortcuts() {
|
|||
useEffect(() => {
|
||||
if (!isFocused) return
|
||||
|
||||
const container = editor.getContainer()
|
||||
|
||||
hotkeys.setScope(editor.store.id)
|
||||
|
||||
const hot = (keys: string, callback: (event: KeyboardEvent) => void) => {
|
||||
|
@ -78,7 +76,7 @@ export function useKeyboardShortcuts() {
|
|||
if (editor.inputs.keys.has('Comma')) return
|
||||
|
||||
preventDefault(e) // prevent whatever would normally happen
|
||||
container.focus() // Focus if not already focused
|
||||
editor.focus() // Focus if not already focused
|
||||
|
||||
editor.inputs.keys.add('Comma')
|
||||
|
||||
|
|
|
@ -446,7 +446,9 @@ describe('isFocused', () => {
|
|||
expect(editor.getInstanceState().isFocused).toBe(false)
|
||||
})
|
||||
|
||||
it('becomes false when a child of the app container div receives a focusout event', () => {
|
||||
it.skip('becomes false when a child of the app container div receives a focusout event', () => {
|
||||
// This used to be true, but the focusout event doesn't actually bubble up anymore
|
||||
// after we reworked to have the focus manager handle things.
|
||||
const child = document.createElement('div')
|
||||
editor.elm.appendChild(child)
|
||||
|
||||
|
|
|
@ -20,10 +20,9 @@ function checkAllShapes(editor: Editor, shapes: string[]) {
|
|||
|
||||
describe('<TldrawEditor />', () => {
|
||||
it('Renders without crashing', async () => {
|
||||
await renderTldrawComponent(
|
||||
<TldrawEditor tools={defaultTools} autoFocus initialState="select" />,
|
||||
{ waitForPatterns: false }
|
||||
)
|
||||
await renderTldrawComponent(<TldrawEditor tools={defaultTools} initialState="select" />, {
|
||||
waitForPatterns: false,
|
||||
})
|
||||
await screen.findByTestId('canvas')
|
||||
})
|
||||
|
||||
|
@ -36,7 +35,6 @@ describe('<TldrawEditor />', () => {
|
|||
}}
|
||||
initialState="select"
|
||||
tools={defaultTools}
|
||||
autoFocus
|
||||
/>,
|
||||
{ waitForPatterns: false }
|
||||
)
|
||||
|
@ -53,7 +51,6 @@ describe('<TldrawEditor />', () => {
|
|||
onMount={(e) => {
|
||||
editor = e
|
||||
}}
|
||||
autoFocus
|
||||
/>,
|
||||
{ waitForPatterns: false }
|
||||
)
|
||||
|
@ -72,7 +69,6 @@ describe('<TldrawEditor />', () => {
|
|||
onMount={(editor) => {
|
||||
expect(editor.store).toBe(store)
|
||||
}}
|
||||
autoFocus
|
||||
/>,
|
||||
{ waitForPatterns: false }
|
||||
)
|
||||
|
@ -85,7 +81,6 @@ describe('<TldrawEditor />', () => {
|
|||
// <TldrawEditor
|
||||
// shapeUtils={[GroupShapeUtil]}
|
||||
// store={createTLStore({ shapeUtils: [] })}
|
||||
// autoFocus
|
||||
// components={{
|
||||
// ErrorFallback: ({ error }) => {
|
||||
// throw error
|
||||
|
@ -103,7 +98,6 @@ describe('<TldrawEditor />', () => {
|
|||
// render(
|
||||
// <TldrawEditor
|
||||
// store={createTLStore({ shapeUtils: [GroupShapeUtil] })}
|
||||
// autoFocus
|
||||
// components={{
|
||||
// ErrorFallback: ({ error }) => {
|
||||
// throw error
|
||||
|
@ -128,7 +122,6 @@ describe('<TldrawEditor />', () => {
|
|||
tools={defaultTools}
|
||||
store={initialStore}
|
||||
onMount={onMount}
|
||||
autoFocus
|
||||
/>
|
||||
)
|
||||
const initialEditor = onMount.mock.lastCall[0]
|
||||
|
@ -141,7 +134,6 @@ describe('<TldrawEditor />', () => {
|
|||
initialState="select"
|
||||
store={initialStore}
|
||||
onMount={onMount}
|
||||
autoFocus
|
||||
/>
|
||||
)
|
||||
// not called again:
|
||||
|
@ -149,13 +141,7 @@ describe('<TldrawEditor />', () => {
|
|||
// re-render with a new store:
|
||||
const newStore = createTLStore({ shapeUtils: [] })
|
||||
rendered.rerender(
|
||||
<TldrawEditor
|
||||
tools={defaultTools}
|
||||
initialState="select"
|
||||
store={newStore}
|
||||
onMount={onMount}
|
||||
autoFocus
|
||||
/>
|
||||
<TldrawEditor tools={defaultTools} initialState="select" store={newStore} onMount={onMount} />
|
||||
)
|
||||
expect(initialEditor.dispose).toHaveBeenCalledTimes(1)
|
||||
expect(onMount).toHaveBeenCalledTimes(2)
|
||||
|
@ -169,7 +155,6 @@ describe('<TldrawEditor />', () => {
|
|||
shapeUtils={[GeoShapeUtil]}
|
||||
initialState="select"
|
||||
tools={defaultTools}
|
||||
autoFocus
|
||||
onMount={(editorApp) => {
|
||||
editor = editorApp
|
||||
}}
|
||||
|
@ -285,7 +270,6 @@ describe('Custom shapes', () => {
|
|||
<TldrawEditor
|
||||
shapeUtils={shapeUtils}
|
||||
tools={[...defaultTools, ...tools]}
|
||||
autoFocus
|
||||
initialState="select"
|
||||
onMount={(editorApp) => {
|
||||
editor = editorApp
|
||||
|
|
Ładowanie…
Reference in New Issue