Remove focus management (#1953)
This PR removes the automatic focus events from the editor. The `autoFocus` prop is now true by default. When true, the editor will begin in a focused state (`editor.instanceState.isFocused` will be `true`) and the component will respond to keyboard shortcuts and other interactions. When false, the editor will begin in an unfocused state and not respond to keyboard interactions. **It's now up to the developer** using the component to update `isFocused` themselves. There's no predictable way to do that on our side, so we leave it to the developer to decide when to turn on or off focus for a container (for example, using an intersection observer to "unfocus" components that are off screen). ### Change Type - [x] `major` — Breaking change ### Test Plan 1. Open the multiple editors example. 2. Click to focus each editor. 3. Use the keyboard shortcuts to check that the correct editor is focused. 4. Start editing a shape, then select the other editor. The first editing shape should complete. - [x] Unit Tests - [x] End to end tests ### Release Notes - [editor] Make autofocus default, remove automatic blur / focus events. --------- Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
This commit is contained in:
parent
3fa7dd359d
commit
da33179a31
28 changed files with 196 additions and 195 deletions
|
@ -106,7 +106,7 @@ test.describe('Canvas events', () => {
|
|||
})
|
||||
|
||||
test.describe('Shape events', () => {
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await setupPage(page)
|
||||
await page.keyboard.press('r')
|
||||
|
@ -115,36 +115,34 @@ test.describe('Shape events', () => {
|
|||
await page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
test.describe('pointer events', () => {
|
||||
test('pointer down', async () => {
|
||||
await page.mouse.move(51, 51)
|
||||
await page.mouse.down()
|
||||
expect(await page.evaluate(() => __tldraw_editor_events.at(-1))).toMatchObject({
|
||||
target: 'canvas',
|
||||
type: 'pointer',
|
||||
name: 'pointer_down',
|
||||
})
|
||||
test('pointer down', async () => {
|
||||
await page.mouse.move(51, 51)
|
||||
await page.mouse.down()
|
||||
expect(await page.evaluate(() => __tldraw_editor_events.at(-1))).toMatchObject({
|
||||
target: 'canvas',
|
||||
type: 'pointer',
|
||||
name: 'pointer_down',
|
||||
})
|
||||
})
|
||||
|
||||
test('pointer move', async () => {
|
||||
await page.mouse.move(51, 51)
|
||||
await page.mouse.move(52, 52)
|
||||
expect(await page.evaluate(() => __tldraw_editor_events.at(-1))).toMatchObject({
|
||||
target: 'canvas',
|
||||
type: 'pointer',
|
||||
name: 'pointer_move',
|
||||
})
|
||||
test('pointer move', async () => {
|
||||
await page.mouse.move(51, 51)
|
||||
await page.mouse.move(52, 52)
|
||||
expect(await page.evaluate(() => __tldraw_editor_events.at(-1))).toMatchObject({
|
||||
target: 'canvas',
|
||||
type: 'pointer',
|
||||
name: 'pointer_move',
|
||||
})
|
||||
})
|
||||
|
||||
test('pointer up', async () => {
|
||||
await page.mouse.move(51, 51)
|
||||
await page.mouse.down()
|
||||
await page.mouse.up()
|
||||
expect(await page.evaluate(() => __tldraw_editor_events.at(-1))).toMatchObject({
|
||||
target: 'canvas',
|
||||
type: 'pointer',
|
||||
name: 'pointer_up',
|
||||
})
|
||||
test('pointer up', async () => {
|
||||
await page.mouse.move(51, 51)
|
||||
await page.mouse.down()
|
||||
await page.mouse.up()
|
||||
expect(await page.evaluate(() => __tldraw_editor_events.at(-1))).toMatchObject({
|
||||
target: 'canvas',
|
||||
type: 'pointer',
|
||||
name: 'pointer_up',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -38,12 +38,13 @@ test.describe('Focus', () => {
|
|||
|
||||
await EditorB.click()
|
||||
expect(await EditorA.evaluate((node) => document.activeElement === node)).toBe(false)
|
||||
expect(await EditorB.evaluate((node) => document.activeElement === node)).toBe(false)
|
||||
expect(await EditorA.evaluate((node) => node.contains(document.activeElement))).toBe(true)
|
||||
expect(await EditorB.evaluate((node) => document.activeElement === node)).toBe(true)
|
||||
expect(await EditorA.evaluate((node) => node.contains(document.activeElement))).toBe(false)
|
||||
|
||||
// Escape does not break focus
|
||||
await page.keyboard.press('Escape')
|
||||
expect(await EditorA.evaluate((node) => node.contains(document.activeElement))).toBe(true)
|
||||
expect(await EditorA.evaluate((node) => document.activeElement === node)).toBe(false)
|
||||
expect(await EditorB.evaluate((node) => node.contains(document.activeElement))).toBe(true)
|
||||
})
|
||||
|
||||
test('kbds when focused', async ({ page }) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import '@tldraw/tldraw/tldraw.css'
|
|||
export default function BasicExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw persistenceKey="tldraw_example" autoFocus />
|
||||
<Tldraw persistenceKey="tldraw_example" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ export default function AssetPropsExample() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
// only allow jpegs
|
||||
acceptedImageMimeTypes={['image/jpeg']}
|
||||
// don't allow any images
|
||||
|
|
|
@ -13,7 +13,6 @@ export default function CanvasEventsExample() {
|
|||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '50vw', height: '100vh' }}>
|
||||
<Tldraw
|
||||
autoFocus
|
||||
onMount={(editor) => {
|
||||
editor.on('event', (event) => handleEvent(event))
|
||||
}}
|
||||
|
|
|
@ -11,7 +11,6 @@ export default function CustomConfigExample() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
// Pass in the array of custom shape classes
|
||||
shapeUtils={customShapeUtils}
|
||||
// Pass in the array of custom tool classes
|
||||
|
|
|
@ -11,7 +11,6 @@ export default function CustomStylesExample() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
persistenceKey="custom-styles-example"
|
||||
shapeUtils={customShapeUtils}
|
||||
tools={customTools}
|
||||
|
|
|
@ -6,7 +6,7 @@ import './custom-ui.css'
|
|||
export default function CustomUiExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw hideUi autoFocus>
|
||||
<Tldraw hideUi>
|
||||
<Canvas />
|
||||
<CustomUi />
|
||||
</Tldraw>
|
||||
|
|
|
@ -15,7 +15,6 @@ export default function ExplodedExample() {
|
|||
initialState="select"
|
||||
shapeUtils={defaultShapeUtils}
|
||||
tools={defaultTools}
|
||||
autoFocus
|
||||
persistenceKey="exploded-example"
|
||||
>
|
||||
<TldrawUi>
|
||||
|
|
|
@ -4,7 +4,7 @@ import '@tldraw/tldraw/tldraw.css'
|
|||
export default function HideUiExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw persistenceKey="hide-ui-example" autoFocus hideUi />
|
||||
<Tldraw persistenceKey="hide-ui-example" hideUi />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export default function AssetPropsExample() {
|
|||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw autoFocus onMount={handleMount} />
|
||||
<Tldraw onMount={handleMount} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,26 @@
|
|||
import { Tldraw } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
import { createContext, useCallback, useContext, useState } from 'react'
|
||||
|
||||
const focusedEditorContext = createContext(
|
||||
{} as {
|
||||
focusedEditor: string
|
||||
setFocusedEditor: (id: string) => void
|
||||
}
|
||||
)
|
||||
|
||||
export default function MultipleExample() {
|
||||
const [focusedEditor, focusedEditorSetter] = useState('first')
|
||||
|
||||
const setFocusedEditor = useCallback(
|
||||
(id: string) => {
|
||||
if (focusedEditor !== id) {
|
||||
focusedEditorSetter(id)
|
||||
}
|
||||
},
|
||||
[focusedEditor]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
@ -9,64 +28,95 @@ export default function MultipleExample() {
|
|||
padding: 32,
|
||||
}}
|
||||
>
|
||||
<focusedEditorContext.Provider value={{ focusedEditor, setFocusedEditor }}>
|
||||
<h1>Focusing: "{focusedEditor}"</h1>
|
||||
<FirstEditor />
|
||||
<textarea defaultValue="type in me" style={{ margin: 10 }}></textarea>
|
||||
<SecondEditor />
|
||||
<ABunchOfText />
|
||||
</focusedEditorContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FirstEditor() {
|
||||
const { focusedEditor, setFocusedEditor } = useContext(focusedEditorContext)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>First Example</h2>
|
||||
<p>This is the second example.</p>
|
||||
<div style={{ width: '100%', height: '600px', padding: 32 }} tabIndex={-1}>
|
||||
<Tldraw persistenceKey="steve" className="A" autoFocus />
|
||||
</div>
|
||||
|
||||
<textarea defaultValue="type in me" style={{ margin: 10 }}></textarea>
|
||||
|
||||
<h2>Second Example</h2>
|
||||
<p>This is the second example.</p>
|
||||
<div style={{ width: '100%', height: '600px' }} tabIndex={-1}>
|
||||
<Tldraw persistenceKey="david" className="B" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{ width: '100%', display: 'flex', alignItems: 'center', flexDirection: 'column' }}
|
||||
tabIndex={-1}
|
||||
onFocus={() => setFocusedEditor('first')}
|
||||
style={{ width: '100%', height: '600px', padding: 32 }}
|
||||
>
|
||||
<article style={{ maxWidth: 600 }}>
|
||||
<h1>White Board</h1>
|
||||
<h2>Chapter 1: The First Strokes</h2>
|
||||
<p>
|
||||
The fluorescent lights flickered overhead as John sat hunched over his desk, his fingers
|
||||
tapping rhythmically on the keyboard. He was a software developer, and tonight, he had a
|
||||
peculiar mission. A mission that would take him deep into the labyrinthine world of web
|
||||
development. John had stumbled upon a new whiteboard library called "tldraw," a
|
||||
seemingly simple tool that promised to revolutionize collaborative drawing on the web.
|
||||
Little did he know that this discovery would set off a chain of events that would
|
||||
challenge his skills, test his perseverance, and blur the line between reality and
|
||||
imagination.
|
||||
</p>
|
||||
<p>
|
||||
With a newfound sense of excitement, John began integrating "tldraw" into his latest
|
||||
project. As lines of code danced across his screen, he imagined the possibilities that
|
||||
lay ahead. The potential to create virtual spaces where ideas could be shared, concepts
|
||||
could be visualized, and teams could collaborate seamlessly from different corners of
|
||||
the world. It was a dream that seemed within reach, a vision of a future where
|
||||
creativity and technology merged into a harmonious symphony.
|
||||
</p>
|
||||
<p>
|
||||
As the night wore on, John's mind became consumed with the whiteboard library. He
|
||||
couldn't help but marvel at its elegance and simplicity. With each stroke of his
|
||||
keyboard, he felt a surge of inspiration, a connection to something greater than
|
||||
himself. It was as if the lines of code he was writing were transforming into a digital
|
||||
canvas, waiting to be filled with the strokes of imagination. In that moment, John
|
||||
realized that he was not just building a tool, but breathing life into a new form of
|
||||
expression. The whiteboard was no longer just a blank slate; it had become a portal to a
|
||||
world where ideas could flourish and dreams could take shape.
|
||||
</p>
|
||||
<p>
|
||||
Little did John know, this integration of "tldraw" was only the beginning. It would lead
|
||||
him down a path filled with unforeseen challenges, where he would confront his own
|
||||
limitations and question the very nature of creation. The journey ahead would test his
|
||||
resolve, pushing him to the edge of his sanity. And as he embarked on this perilous
|
||||
adventure, he could not shake the feeling that the whiteboard held secrets far beyond
|
||||
his understanding. Secrets that would unfold before his eyes, one stroke at a time.
|
||||
</p>
|
||||
</article>
|
||||
<Tldraw persistenceKey="steve" className="A" autoFocus={focusedEditor === 'first'} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SecondEditor() {
|
||||
const { focusedEditor, setFocusedEditor } = useContext(focusedEditorContext)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Second Example</h2>
|
||||
<p>This is the second example.</p>
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => setFocusedEditor('second')}
|
||||
style={{ width: '100%', height: '600px' }}
|
||||
>
|
||||
<Tldraw persistenceKey="david" className="B" autoFocus={focusedEditor === 'second'} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ABunchOfText() {
|
||||
return (
|
||||
<div style={{ width: '100%', display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
|
||||
<article style={{ maxWidth: 600 }}>
|
||||
<h1>White Board</h1>
|
||||
<h2>Chapter 1: The First Strokes</h2>
|
||||
<p>
|
||||
The fluorescent lights flickered overhead as John sat hunched over his desk, his fingers
|
||||
tapping rhythmically on the keyboard. He was a software developer, and tonight, he had a
|
||||
peculiar mission. A mission that would take him deep into the labyrinthine world of web
|
||||
development. John had stumbled upon a new whiteboard library called "tldraw," a seemingly
|
||||
simple tool that promised to revolutionize collaborative drawing on the web. Little did he
|
||||
know that this discovery would set off a chain of events that would challenge his skills,
|
||||
test his perseverance, and blur the line between reality and imagination.
|
||||
</p>
|
||||
<p>
|
||||
With a newfound sense of excitement, John began integrating "tldraw" into his latest
|
||||
project. As lines of code danced across his screen, he imagined the possibilities that lay
|
||||
ahead. The potential to create virtual spaces where ideas could be shared, concepts could
|
||||
be visualized, and teams could collaborate seamlessly from different corners of the world.
|
||||
It was a dream that seemed within reach, a vision of a future where creativity and
|
||||
technology merged into a harmonious symphony.
|
||||
</p>
|
||||
<p>
|
||||
As the night wore on, John's mind became consumed with the whiteboard library. He couldn't
|
||||
help but marvel at its elegance and simplicity. With each stroke of his keyboard, he felt
|
||||
a surge of inspiration, a connection to something greater than himself. It was as if the
|
||||
lines of code he was writing were transforming into a digital canvas, waiting to be filled
|
||||
with the strokes of imagination. In that moment, John realized that he was not just
|
||||
building a tool, but breathing life into a new form of expression. The whiteboard was no
|
||||
longer just a blank slate; it had become a portal to a world where ideas could flourish
|
||||
and dreams could take shape.
|
||||
</p>
|
||||
<p>
|
||||
Little did John know, this integration of "tldraw" was only the beginning. It would lead
|
||||
him down a path filled with unforeseen challenges, where he would confront his own
|
||||
limitations and question the very nature of creation. The journey ahead would test his
|
||||
resolve, pushing him to the edge of his sanity. And as he embarked on this perilous
|
||||
adventure, he could not shake the feeling that the whiteboard held secrets far beyond his
|
||||
understanding. Secrets that would unfold before his eyes, one stroke at a time.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default function PersistenceExample() {
|
|||
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw store={store} autoFocus />
|
||||
<Tldraw store={store} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ export default function ScrollExample() {
|
|||
}}
|
||||
>
|
||||
<div style={{ width: '60vw', height: '80vh' }}>
|
||||
<Tldraw persistenceKey="scroll-example" autoFocus />
|
||||
<Tldraw persistenceKey="scroll-example" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -6,7 +6,6 @@ export default function ShapeMetaExample() {
|
|||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
persistenceKey="shape_meta_example"
|
||||
autoFocus
|
||||
onMount={(editor) => {
|
||||
editor.getInitialMetaForShape = (shape) => {
|
||||
return { label: `My ${shape.type} shape` }
|
||||
|
|
|
@ -26,7 +26,6 @@ export default function SnapshotExample() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
onMount={(editor) => {
|
||||
editor.store.loadSnapshot(jsonSnapshot)
|
||||
}}
|
||||
|
|
|
@ -58,7 +58,7 @@ export default function StoreEventsExample() {
|
|||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '60vw', height: '100vh' }}>
|
||||
<Tldraw autoFocus onMount={setAppToState} />
|
||||
<Tldraw onMount={setAppToState} />
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
|
|
|
@ -12,7 +12,7 @@ export default function UiEventsExample() {
|
|||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '60vw', height: '100vh' }}>
|
||||
<Tldraw autoFocus onUiEvent={handleUiEvent} />
|
||||
<Tldraw onUiEvent={handleUiEvent} />
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
|
|
|
@ -7,7 +7,6 @@ export default function EndToEnd() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
onMount={(editor) => {
|
||||
editor.on('event', (info) => {
|
||||
;(window as any).__tldraw_editor_events.push(info)
|
||||
|
|
|
@ -12,7 +12,6 @@ export default function OnlyEditorExample() {
|
|||
return (
|
||||
<div className="tldraw__editor">
|
||||
<TldrawEditor
|
||||
autoFocus
|
||||
tools={myTools}
|
||||
shapeUtils={myShapeUtils}
|
||||
initialState="select"
|
||||
|
|
|
@ -255,9 +255,9 @@ function TldrawEditorWithReadyStore({
|
|||
store,
|
||||
tools,
|
||||
shapeUtils,
|
||||
autoFocus,
|
||||
user,
|
||||
initialState,
|
||||
autoFocus = true,
|
||||
inferDarkMode,
|
||||
}: Required<
|
||||
TldrawEditorProps & {
|
||||
|
@ -338,11 +338,11 @@ function TldrawEditorWithReadyStore({
|
|||
function Layout({
|
||||
children,
|
||||
onMount,
|
||||
autoFocus = false,
|
||||
autoFocus,
|
||||
}: {
|
||||
children: any
|
||||
autoFocus: boolean
|
||||
onMount?: TLOnMountHandler
|
||||
autoFocus?: boolean
|
||||
}) {
|
||||
useZoomCss()
|
||||
useCursor()
|
||||
|
@ -353,6 +353,9 @@ function Layout({
|
|||
useOnMount(onMount)
|
||||
useDPRMultiple()
|
||||
|
||||
const editor = useEditor()
|
||||
editor.updateViewportScreenBounds()
|
||||
|
||||
return children ?? <Canvas />
|
||||
}
|
||||
|
||||
|
|
|
@ -1,56 +1,15 @@
|
|||
import { debounce } from '@tldraw/utils'
|
||||
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 (!container) return
|
||||
|
||||
// We need to debounce this because when focus changes, the body
|
||||
// becomes focused for a brief moment. Debouncing means that we
|
||||
// check only when focus stops changing: when it settles, what
|
||||
// has it settled on? If it's settled on the container or something
|
||||
// inside of the container, then focus or preserve the current focus;
|
||||
// if not, then turn off focus. Turning off focus is a trigger to
|
||||
// also turn off keyboard shortcuts and other things.
|
||||
const updateFocus = debounce(() => {
|
||||
const { activeElement } = document
|
||||
const { isFocused: wasFocused } = editor.instanceState
|
||||
const isFocused =
|
||||
document.hasFocus() && (container === activeElement || container.contains(activeElement))
|
||||
|
||||
if (wasFocused !== isFocused) {
|
||||
editor.updateInstanceState({ isFocused })
|
||||
editor.updateViewportScreenBounds()
|
||||
|
||||
if (!isFocused) {
|
||||
// When losing focus, run complete() to ensure that any interacts end
|
||||
editor.complete()
|
||||
}
|
||||
}
|
||||
}, 32)
|
||||
|
||||
container.addEventListener('focusin', updateFocus)
|
||||
container.addEventListener('focus', updateFocus)
|
||||
container.addEventListener('focusout', updateFocus)
|
||||
container.addEventListener('blur', updateFocus)
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('focusin', updateFocus)
|
||||
container.removeEventListener('focus', updateFocus)
|
||||
container.removeEventListener('focusout', updateFocus)
|
||||
container.removeEventListener('blur', updateFocus)
|
||||
}
|
||||
}, [container, editor])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (autoFocus) {
|
||||
if (autoFocus && !editor.instanceState.isFocused) {
|
||||
editor.updateInstanceState({ isFocused: true })
|
||||
editor.getContainer().focus()
|
||||
} else if (editor.instanceState.isFocused) {
|
||||
editor.updateInstanceState({ isFocused: false })
|
||||
}
|
||||
}, [editor, autoFocus])
|
||||
}
|
||||
|
|
|
@ -18,9 +18,10 @@ export function useScreenBounds() {
|
|||
}
|
||||
)
|
||||
|
||||
editor.updateViewportScreenBounds()
|
||||
|
||||
// Rather than running getClientRects on every frame, we'll
|
||||
// run it once a second or when the window resizes / scrolls.
|
||||
updateBounds()
|
||||
const interval = setInterval(updateBounds, 1000)
|
||||
window.addEventListener('resize', updateBounds)
|
||||
window.addEventListener('scroll', updateBounds)
|
||||
|
@ -30,5 +31,5 @@ export function useScreenBounds() {
|
|||
window.removeEventListener('resize', updateBounds)
|
||||
window.removeEventListener('scroll', updateBounds)
|
||||
}
|
||||
})
|
||||
}, [editor])
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ afterEach(() => {
|
|||
describe('<Tldraw />', () => {
|
||||
it('Renders without crashing', async () => {
|
||||
await act(async () => (
|
||||
<Tldraw autoFocus>
|
||||
<Tldraw>
|
||||
<div data-testid="canvas-1" />
|
||||
</Tldraw>
|
||||
))
|
||||
|
|
|
@ -139,7 +139,7 @@ function InsideOfEditorContext({
|
|||
const onMountEvent = useEvent((editor: Editor) => {
|
||||
const unsubs: (void | (() => void) | undefined)[] = []
|
||||
|
||||
unsubs.push(registerDefaultSideEffects(editor))
|
||||
unsubs.push(...registerDefaultSideEffects(editor))
|
||||
|
||||
// for content handling, first we register the default handlers...
|
||||
registerDefaultExternalContentHandlers(editor, {
|
||||
|
|
|
@ -1,34 +1,49 @@
|
|||
import { Editor } from '@tldraw/editor'
|
||||
|
||||
export function registerDefaultSideEffects(editor: Editor) {
|
||||
return editor.sideEffects.registerAfterChangeHandler('instance_page_state', (prev, next) => {
|
||||
if (prev.croppingShapeId !== next.croppingShapeId) {
|
||||
const isInCroppingState = editor.isInAny(
|
||||
'select.crop',
|
||||
'select.pointing_crop_handle',
|
||||
'select.cropping'
|
||||
)
|
||||
if (!prev.croppingShapeId && next.croppingShapeId) {
|
||||
if (!isInCroppingState) {
|
||||
editor.setCurrentTool('select.crop.idle')
|
||||
}
|
||||
} else if (prev.croppingShapeId && !next.croppingShapeId) {
|
||||
if (isInCroppingState) {
|
||||
editor.setCurrentTool('select.idle')
|
||||
return [
|
||||
editor.sideEffects.registerAfterChangeHandler('instance', (prev, next) => {
|
||||
if (prev.isFocused !== next.isFocused) {
|
||||
if (next.isFocused) {
|
||||
editor.complete() // stop any interaction
|
||||
editor.getContainer().focus()
|
||||
editor.updateViewportScreenBounds()
|
||||
} else {
|
||||
editor.complete() // stop any interaction
|
||||
editor.getContainer().blur() // blur the container
|
||||
editor.updateViewportScreenBounds()
|
||||
}
|
||||
}
|
||||
}),
|
||||
editor.sideEffects.registerAfterChangeHandler('instance_page_state', (prev, next) => {
|
||||
if (prev.croppingShapeId !== next.croppingShapeId) {
|
||||
const isInCroppingState = editor.isInAny(
|
||||
'select.crop',
|
||||
'select.pointing_crop_handle',
|
||||
'select.cropping'
|
||||
)
|
||||
if (!prev.croppingShapeId && next.croppingShapeId) {
|
||||
if (!isInCroppingState) {
|
||||
editor.setCurrentTool('select.crop.idle')
|
||||
}
|
||||
} else if (prev.croppingShapeId && !next.croppingShapeId) {
|
||||
if (isInCroppingState) {
|
||||
editor.setCurrentTool('select.idle')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prev.editingShapeId !== next.editingShapeId) {
|
||||
if (!prev.editingShapeId && next.editingShapeId) {
|
||||
if (!editor.isIn('select.editing_shape')) {
|
||||
editor.setCurrentTool('select.editing_shape')
|
||||
}
|
||||
} else if (prev.editingShapeId && !next.editingShapeId) {
|
||||
if (editor.isIn('select.editing_shape')) {
|
||||
editor.setCurrentTool('select.idle')
|
||||
if (prev.editingShapeId !== next.editingShapeId) {
|
||||
if (!prev.editingShapeId && next.editingShapeId) {
|
||||
if (!editor.isIn('select.editing_shape')) {
|
||||
editor.setCurrentTool('select.editing_shape')
|
||||
}
|
||||
} else if (prev.editingShapeId && !next.editingShapeId) {
|
||||
if (editor.isIn('select.editing_shape')) {
|
||||
editor.setCurrentTool('select.idle')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ export const Toolbar = memo(function Toolbar() {
|
|||
<div className="tlui-toolbar">
|
||||
<div className="tlui-toolbar__inner">
|
||||
<div className="tlui-toolbar__left">
|
||||
{!isReadonly && (
|
||||
{!isReadonly && breakpoint < 6 && !editor.isInAny('hand', 'zoom') && (
|
||||
<div
|
||||
className={classNames('tlui-toolbar__extras', {
|
||||
'tlui-toolbar__extras__hidden': !showExtraActions,
|
||||
|
|
|
@ -460,23 +460,6 @@ describe('isFocused', () => {
|
|||
jest.advanceTimersByTime(100)
|
||||
expect(editor.instanceState.isFocused).toBe(false)
|
||||
})
|
||||
|
||||
it('calls .focus() and .blur() on the container div when you call .focus() and .blur() on the editor', () => {
|
||||
const focusMock = jest.spyOn(editor.elm, 'focus').mockImplementation()
|
||||
const blurMock = jest.spyOn(editor.elm, 'blur').mockImplementation()
|
||||
|
||||
expect(focusMock).not.toHaveBeenCalled()
|
||||
expect(blurMock).not.toHaveBeenCalled()
|
||||
|
||||
editor.getContainer().focus()
|
||||
|
||||
expect(focusMock).toHaveBeenCalled()
|
||||
expect(blurMock).not.toHaveBeenCalled()
|
||||
|
||||
editor.getContainer().blur()
|
||||
|
||||
expect(blurMock).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getShapeUtil', () => {
|
||||
|
|
Loading…
Reference in a new issue