- {!isReadOnly && (
+ {!isReadonly && (
)
})}
- {isReadOnly && laserTool && (
+ {isReadonly && laserTool && (
- {breakpoint < 5 && !isReadOnly && (
+ {breakpoint < 5 && !isReadonly && (
diff --git a/packages/tldraw/src/lib/ui/components/TrashButton.tsx b/packages/tldraw/src/lib/ui/components/TrashButton.tsx
index b6e94c118..7c3702124 100644
--- a/packages/tldraw/src/lib/ui/components/TrashButton.tsx
+++ b/packages/tldraw/src/lib/ui/components/TrashButton.tsx
@@ -1,6 +1,6 @@
import { track, useEditor } from '@tldraw/editor'
import { useActions } from '../hooks/useActions'
-import { useReadOnly } from '../hooks/useReadOnly'
+import { useReadonly } from '../hooks/useReadonly'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { Button } from './primitives/Button'
import { kbdStr } from './primitives/shared'
@@ -11,7 +11,7 @@ export const TrashButton = track(function TrashButton() {
const msg = useTranslation()
const action = actions['delete']
- const isReadonly = useReadOnly()
+ const isReadonly = useReadonly()
if (isReadonly) return null
diff --git a/packages/tldraw/src/lib/ui/hooks/useKeyboardShortcuts.ts b/packages/tldraw/src/lib/ui/hooks/useKeyboardShortcuts.ts
index 581e533ca..42b5ecfb4 100644
--- a/packages/tldraw/src/lib/ui/hooks/useKeyboardShortcuts.ts
+++ b/packages/tldraw/src/lib/ui/hooks/useKeyboardShortcuts.ts
@@ -1,8 +1,8 @@
-import { preventDefault, useEditor } from '@tldraw/editor'
+import { preventDefault, useEditor, useValue } from '@tldraw/editor'
import hotkeys from 'hotkeys-js'
import { useEffect } from 'react'
import { useActions } from './useActions'
-import { useReadOnly } from './useReadOnly'
+import { useReadonly } from './useReadonly'
import { useTools } from './useTools'
const SKIP_KBDS = [
@@ -18,11 +18,14 @@ const SKIP_KBDS = [
export function useKeyboardShortcuts() {
const editor = useEditor()
- const isReadonly = useReadOnly()
+ const isReadonly = useReadonly()
const actions = useActions()
const tools = useTools()
+ const isFocused = useValue('is focused', () => editor.instanceState.isFocused, [editor])
useEffect(() => {
+ if (!isFocused) return
+
const container = editor.getContainer()
hotkeys.setScope(editor.store.id)
@@ -34,9 +37,7 @@ export function useKeyboardShortcuts() {
// Add hotkeys for actions and tools.
// Except those that in SKIP_KBDS!
const areShortcutsDisabled = () =>
- (editor.instanceState.isFocused && editor.isMenuOpen) ||
- editor.editingId !== null ||
- editor.crashingError
+ editor.isMenuOpen || editor.editingId !== null || editor.crashingError
for (const action of Object.values(actions)) {
if (!action.kbd) continue
@@ -51,7 +52,9 @@ export function useKeyboardShortcuts() {
}
for (const tool of Object.values(tools)) {
- if (!tool.kbd || (!tool.readonlyOk && editor.instanceState.isReadOnly)) continue
+ if (!tool.kbd || (!tool.readonlyOk && editor.instanceState.isReadonly)) {
+ continue
+ }
if (SKIP_KBDS.includes(tool.id)) continue
@@ -65,7 +68,7 @@ export function useKeyboardShortcuts() {
return () => {
hotkeys.deleteScope(editor.store.id)
}
- }, [actions, tools, isReadonly, editor])
+ }, [actions, tools, isReadonly, editor, isFocused])
}
function getHotkeysStringFromKbd(kbd: string) {
diff --git a/packages/tldraw/src/lib/ui/hooks/useReadOnly.ts b/packages/tldraw/src/lib/ui/hooks/useReadOnly.ts
deleted file mode 100644
index 4dbcf5c1c..000000000
--- a/packages/tldraw/src/lib/ui/hooks/useReadOnly.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { useEditor, useValue } from '@tldraw/editor'
-
-/** @public */
-export function useReadOnly() {
- const editor = useEditor()
- return useValue('isReadOnlyMode', () => editor.instanceState.isReadOnly, [editor])
-}
diff --git a/packages/tldraw/src/lib/ui/hooks/useReadonly.ts b/packages/tldraw/src/lib/ui/hooks/useReadonly.ts
new file mode 100644
index 000000000..f6aeaf798
--- /dev/null
+++ b/packages/tldraw/src/lib/ui/hooks/useReadonly.ts
@@ -0,0 +1,7 @@
+import { useEditor, useValue } from '@tldraw/editor'
+
+/** @public */
+export function useReadonly() {
+ const editor = useEditor()
+ return useValue('isReadonlyMode', () => editor.instanceState.isReadonly, [editor])
+}
diff --git a/packages/tldraw/src/test/Editor.test.tsx b/packages/tldraw/src/test/Editor.test.tsx
index b1f7a3148..09b19b15b 100644
--- a/packages/tldraw/src/test/Editor.test.tsx
+++ b/packages/tldraw/src/test/Editor.test.tsx
@@ -285,7 +285,7 @@ describe("App's default tool", () => {
})
it('Is hand for readonly mode', () => {
editor = new TestEditor()
- editor.updateInstanceState({ isReadOnly: true })
+ editor.updateInstanceState({ isReadonly: true })
editor.setCurrentTool('hand')
expect(editor.currentToolId).toBe('hand')
})
@@ -369,37 +369,42 @@ describe('isFocused', () => {
})
it('becomes true when the container div receives a focus event', () => {
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(false)
editor.elm.focus()
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(true)
})
it('becomes false when the container div receives a blur event', () => {
editor.elm.focus()
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(true)
editor.elm.blur()
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(false)
})
- it('becomes true when a child of the app container div receives a focusin event', () => {
+ it.skip('becomes true when a child of the app container div receives a focusin event', () => {
+ // We need to skip this one because it's not actually true: the focusin event will bubble
+ // to the document.body, resulting in that being the active element. In reality, the editor's
+ // container would also have received a focus event, and after the editor's debounce ends,
+ // the container (or one of its descendants) will be the focused element.
editor.elm.blur()
-
const child = document.createElement('div')
editor.elm.appendChild(child)
-
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(false)
-
child.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
-
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(true)
-
child.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
-
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(false)
})
@@ -413,6 +418,7 @@ describe('isFocused', () => {
child.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
+ jest.advanceTimersByTime(100)
expect(editor.instanceState.isFocused).toBe(false)
})
diff --git a/packages/tldraw/src/test/SelectTool.test.ts b/packages/tldraw/src/test/SelectTool.test.ts
index 0484d89f9..ceb386a3b 100644
--- a/packages/tldraw/src/test/SelectTool.test.ts
+++ b/packages/tldraw/src/test/SelectTool.test.ts
@@ -412,7 +412,7 @@ describe('When in readonly mode', () => {
props: { w: 100, h: 100, url: '' },
},
])
- editor.updateInstanceState({ isReadOnly: true })
+ editor.updateInstanceState({ isReadonly: true })
editor.setCurrentTool('hand')
editor.setCurrentTool('select')
})
@@ -420,7 +420,7 @@ describe('When in readonly mode', () => {
it('Begins editing embed when double clicked', () => {
expect(editor.editingId).toBe(null)
expect(editor.selectedIds.length).toBe(0)
- expect(editor.instanceState.isReadOnly).toBe(true)
+ expect(editor.instanceState.isReadonly).toBe(true)
const shape = editor.getShapeById(ids.embed1)
editor.doubleClick(100, 100, { target: 'shape', shape })
@@ -430,7 +430,7 @@ describe('When in readonly mode', () => {
it('Begins editing embed when pressing Enter on a selected embed', () => {
expect(editor.editingId).toBe(null)
expect(editor.selectedIds.length).toBe(0)
- expect(editor.instanceState.isReadOnly).toBe(true)
+ expect(editor.instanceState.isReadonly).toBe(true)
editor.setSelectedIds([ids.embed1])
expect(editor.selectedIds.length).toBe(1)
diff --git a/packages/tldraw/src/test/groups.test.ts b/packages/tldraw/src/test/groups.test.ts
index 7487f8c25..5607bcd53 100644
--- a/packages/tldraw/src/test/groups.test.ts
+++ b/packages/tldraw/src/test/groups.test.ts
@@ -279,7 +279,7 @@ describe('creating groups', () => {
// │ A │ │ B │ │ C │
// └───┘ └───┘ └───┘
editor.createShapes([box(ids.boxA, 0, 0), box(ids.boxB, 20, 0), box(ids.boxC, 40, 0)])
- editor.updateInstanceState({ isReadOnly: true })
+ editor.updateInstanceState({ isReadonly: true })
editor.setCurrentTool('hand')
editor.selectAll()
expect(editor.selectedIds.length).toBe(3)
@@ -491,7 +491,7 @@ describe('ungrouping shapes', () => {
expect(editor.selectedIds.length).toBe(3)
editor.groupShapes()
expect(editor.selectedIds.length).toBe(1)
- editor.updateInstanceState({ isReadOnly: true })
+ editor.updateInstanceState({ isReadonly: true })
editor.setCurrentTool('hand')
editor.ungroupShapes()
diff --git a/packages/tlschema/api-report.md b/packages/tlschema/api-report.md
index 12ad0ed3a..ee3c9f3b2 100644
--- a/packages/tlschema/api-report.md
+++ b/packages/tlschema/api-report.md
@@ -1020,7 +1020,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
// (undocumented)
isPenMode: boolean;
// (undocumented)
- isReadOnly: boolean;
+ isReadonly: boolean;
// (undocumented)
isToolLocked: boolean;
// (undocumented)
diff --git a/packages/tlschema/src/migrations.test.ts b/packages/tlschema/src/migrations.test.ts
index a0e8d1397..697da7439 100644
--- a/packages/tlschema/src/migrations.test.ts
+++ b/packages/tlschema/src/migrations.test.ts
@@ -1341,6 +1341,22 @@ describe('adds lonely properties', () => {
})
})
+describe('rename isReadOnly to isReadonly', () => {
+ const { up, down } = instanceMigrations.migrators[instanceVersions.ReadOnlyReadonly]
+
+ test('up works as expected', () => {
+ expect(up({ isReadOnly: false })).toStrictEqual({
+ isReadonly: false,
+ })
+ })
+
+ test('down works as expected', () => {
+ expect(down({ isReadonly: false })).toStrictEqual({
+ isReadOnly: false,
+ })
+ })
+})
+
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
for (const migrator of allMigrators) {
diff --git a/packages/tlschema/src/records/TLInstance.ts b/packages/tlschema/src/records/TLInstance.ts
index 5f9730b0a..6d294869f 100644
--- a/packages/tlschema/src/records/TLInstance.ts
+++ b/packages/tlschema/src/records/TLInstance.ts
@@ -42,7 +42,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
isCoarsePointer: boolean
openMenus: string[]
isChangingStyle: boolean
- isReadOnly: boolean
+ isReadonly: boolean
meta: JsonObject
}
@@ -87,7 +87,7 @@ export function createInstanceRecordType(stylesById: Map
,
})
)
@@ -124,7 +124,7 @@ export function createInstanceRecordType(stylesById: Map {
@@ -438,6 +439,20 @@ export const instanceMigrations = defineMigrations({
}
},
},
+ [instanceVersions.ReadOnlyReadonly]: {
+ up: ({ isReadOnly: _isReadOnly, ...record }) => {
+ return {
+ ...record,
+ isReadonly: _isReadOnly,
+ }
+ },
+ down: ({ isReadonly: _isReadonly, ...record }) => {
+ return {
+ ...record,
+ isReadOnly: _isReadonly,
+ }
+ },
+ },
},
})