@tldraw/editor 5.2.0-next.e2b8d10bf10e → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist-cjs/index.d.ts +112 -53
- package/dist-cjs/index.js +7 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +8 -5
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +47 -1
- package/dist-cjs/lib/components/Shape.js.map +3 -3
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -16
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +97 -45
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +28 -4
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +3 -3
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +24 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +14 -3
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +7 -3
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +0 -1
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +4 -0
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/{hooks/useTransform.js → editor/tools/cancelShapeCreationOnLongPress.js} +8 -23
- package/dist-cjs/lib/editor/tools/cancelShapeCreationOnLongPress.js.map +7 -0
- package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +6 -1
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +17 -8
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js +0 -1
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
- package/dist-cjs/lib/hooks/useScreenBounds.js +1 -0
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +5 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +3 -2
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +12 -0
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.js +2 -1
- package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
- package/dist-cjs/lib/utils/pointer.js +37 -0
- package/dist-cjs/lib/utils/pointer.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +112 -53
- package/dist-esm/index.mjs +9 -13
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +8 -5
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +48 -2
- package/dist-esm/lib/components/Shape.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -16
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +97 -45
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +28 -4
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +3 -3
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +24 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +14 -3
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +0 -1
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +4 -0
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs +8 -0
- package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs.map +7 -0
- package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +6 -1
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +17 -8
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +0 -1
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
- package/dist-esm/lib/hooks/useScreenBounds.mjs +1 -0
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +5 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +3 -2
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +12 -0
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs +2 -1
- package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
- package/dist-esm/lib/utils/pointer.mjs +17 -0
- package/dist-esm/lib/utils/pointer.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +15 -3
- package/package.json +11 -8
- package/src/index.ts +4 -10
- package/src/lib/components/MenuClickCapture.tsx +8 -4
- package/src/lib/components/Shape.tsx +88 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -17
- package/src/lib/editor/Editor.test.ts +36 -0
- package/src/lib/editor/Editor.ts +145 -55
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +37 -10
- package/src/lib/editor/managers/FontManager/FontManager.ts +46 -1
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +32 -0
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +34 -4
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +61 -0
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +16 -4
- package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +9 -2
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
- package/src/lib/editor/managers/TextManager/TextManager.ts +17 -2
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +0 -40
- package/src/lib/editor/managers/TickManager/TickManager.ts +0 -1
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +41 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +5 -0
- package/src/lib/editor/tools/cancelShapeCreationOnLongPress.ts +31 -0
- package/src/lib/editor/types/event-types.ts +7 -0
- package/src/lib/globals/environment.ts +7 -0
- package/src/lib/hooks/EditorComponentsContext.tsx +0 -2
- package/src/lib/hooks/useCanvasEvents.ts +24 -12
- package/src/lib/hooks/useEditorComponents.tsx +0 -1
- package/src/lib/hooks/usePresence.ts +2 -2
- package/src/lib/hooks/useScreenBounds.ts +3 -0
- package/src/lib/license/LicenseManager.test.ts +24 -16
- package/src/lib/license/LicenseManager.ts +8 -2
- package/src/lib/primitives/Vec.ts +5 -2
- package/src/lib/test/ContentElementSlot.test.tsx +201 -0
- package/src/lib/utils/dom.ts +34 -0
- package/src/lib/utils/getPointerInfo.ts +2 -1
- package/src/lib/utils/pointer.test.ts +76 -0
- package/src/lib/utils/pointer.ts +42 -0
- package/src/version.ts +3 -3
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js +0 -49
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js.map +0 -7
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
- package/dist-cjs/lib/hooks/useTransform.js.map +0 -7
- package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs +0 -19
- package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs.map +0 -7
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
- package/dist-esm/lib/hooks/useTransform.mjs +0 -23
- package/dist-esm/lib/hooks/useTransform.mjs.map +0 -7
- package/src/lib/components/default-components/DefaultSelectionBackground.tsx +0 -25
- package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
- package/src/lib/hooks/useTransform.ts +0 -30
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { react } from '@tldraw/state'
|
|
2
2
|
import { useQuickReactor, useStateTracking } from '@tldraw/state-react'
|
|
3
3
|
import { TLShape, TLShapeId } from '@tldraw/tlschema'
|
|
4
|
+
import { warnOnce } from '@tldraw/utils'
|
|
4
5
|
import { memo, useCallback, useEffect, useLayoutEffect, useRef } from 'react'
|
|
6
|
+
import { Editor } from '../editor/Editor'
|
|
5
7
|
import { ShapeUtil } from '../editor/shapes/ShapeUtil'
|
|
6
8
|
import { useEditorComponents } from '../hooks/EditorComponentsContext'
|
|
7
9
|
import { useEditor } from '../hooks/useEditor'
|
|
8
10
|
import { useShapeCulling } from '../hooks/useShapeCulling'
|
|
9
11
|
import { Mat } from '../primitives/Mat'
|
|
10
12
|
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
11
|
-
import { setStyleProperty } from '../utils/dom'
|
|
13
|
+
import { moveElementInto, setStyleProperty } from '../utils/dom'
|
|
12
14
|
import { OptionalErrorBoundary } from './ErrorBoundary'
|
|
13
15
|
|
|
14
16
|
/*
|
|
@@ -153,11 +155,96 @@ export const Shape = memo(function Shape({
|
|
|
153
155
|
<OptionalErrorBoundary fallback={ShapeErrorFallback as any} onError={annotateError}>
|
|
154
156
|
<InnerShape shape={shape} util={util} />
|
|
155
157
|
</OptionalErrorBoundary>
|
|
158
|
+
{util.getAppOwnedElement && <ContentElementSlot id={id} util={util} />}
|
|
156
159
|
</ShapeWrapper>
|
|
157
160
|
</>
|
|
158
161
|
)
|
|
159
162
|
})
|
|
160
163
|
|
|
164
|
+
/*
|
|
165
|
+
The content element slot hosts an app-owned element provided by the shape util's
|
|
166
|
+
getAppOwnedElement method. The contract: while the shape is mounted, tldraw never
|
|
167
|
+
unmounts, recreates, or relocates the element (the canvas renders shapes in stable
|
|
168
|
+
id-sorted DOM order, so reorders and reparenting never move DOM nodes), and before
|
|
169
|
+
the slot is destroyed — shape deletion, page change, error teardown, or the whole
|
|
170
|
+
editor unmounting — onReleaseAppOwnedElement is called while the slot is still
|
|
171
|
+
connected to the document, so the app can move the element to a new parent with
|
|
172
|
+
Node.moveBefore without resetting its state. That last guarantee relies on this
|
|
173
|
+
being a layout effect: React runs layout effect cleanups before it detaches the
|
|
174
|
+
host nodes of a deleted subtree, which is not true of passive effects.
|
|
175
|
+
*/
|
|
176
|
+
const ContentElementSlot = memo(function ContentElementSlot({
|
|
177
|
+
id,
|
|
178
|
+
util,
|
|
179
|
+
}: {
|
|
180
|
+
id: TLShapeId
|
|
181
|
+
util: ShapeUtil
|
|
182
|
+
}) {
|
|
183
|
+
const editor = useEditor()
|
|
184
|
+
const slotRef = useRef<HTMLDivElement>(null)
|
|
185
|
+
|
|
186
|
+
useLayoutEffect(() => {
|
|
187
|
+
const slot = slotRef.current
|
|
188
|
+
if (!slot) return
|
|
189
|
+
|
|
190
|
+
const shape = editor.getShape(id)
|
|
191
|
+
if (!shape) return
|
|
192
|
+
|
|
193
|
+
// getAppOwnedElement is called once per shape mount (the effect deps below are
|
|
194
|
+
// [editor, id, util], none of which change on a prop edit), so the adopted element is
|
|
195
|
+
// not refreshed when the shape's props change. A util whose element identity depends on
|
|
196
|
+
// props must mutate the existing element in place rather than return a new one. Likewise,
|
|
197
|
+
// if this returns null now and an element only later, the element is never adopted for
|
|
198
|
+
// this mount — return a stable element up front and populate it asynchronously instead.
|
|
199
|
+
const element = util.getAppOwnedElement?.(shape)
|
|
200
|
+
if (!element) return
|
|
201
|
+
|
|
202
|
+
// An element that was connected before adoption (e.g. parked off-canvas by the
|
|
203
|
+
// app between editor sessions) should survive the move without reloading.
|
|
204
|
+
const wasConnectedBeforeAdoption = element.isConnected
|
|
205
|
+
|
|
206
|
+
if (element.parentNode !== slot) {
|
|
207
|
+
moveElementInto(slot, element)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (process.env.NODE_ENV !== 'production' && wasConnectedBeforeAdoption) {
|
|
211
|
+
warnIfContentElementReloads(editor, slot, element)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return () => {
|
|
215
|
+
// Note: under React StrictMode (dev only) this effect runs mount → cleanup → mount,
|
|
216
|
+
// so onReleaseAppOwnedElement fires once even though the shape was not removed, and on
|
|
217
|
+
// the appendChild fallback path the element is detached and re-adopted (reloading it).
|
|
218
|
+
// onReleaseAppOwnedElement must therefore be safe to call when the shape still exists;
|
|
219
|
+
// don't treat it as a definitive "shape removed" signal (check the store if you need
|
|
220
|
+
// to know). With Node.moveBefore the re-adoption preserves state, so this is benign.
|
|
221
|
+
const latestShape = editor.getShape(id) ?? shape
|
|
222
|
+
util.onReleaseAppOwnedElement?.(latestShape, element)
|
|
223
|
+
if (process.env.NODE_ENV !== 'production' && element.parentNode === slot) {
|
|
224
|
+
warnOnce(
|
|
225
|
+
`The content element for shape "${id}" was not reclaimed by onReleaseAppOwnedElement and will be destroyed along with its slot, losing any state it holds.`
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}, [editor, id, util])
|
|
230
|
+
|
|
231
|
+
return <div ref={slotRef} className="tl-content-slot" draggable={false} />
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// Dev-mode assertion that adoption kept the platform's state-preservation promise: a
|
|
235
|
+
// load event firing on a previously-connected adopted element means the embed reloaded.
|
|
236
|
+
// load doesn't bubble, but capture-phase listeners on ancestors still observe it.
|
|
237
|
+
function warnIfContentElementReloads(editor: Editor, slot: HTMLElement, element: HTMLElement) {
|
|
238
|
+
const onLoad = (event: Event) => {
|
|
239
|
+
if (event.target !== element && !element.contains(event.target as Node)) return
|
|
240
|
+
warnOnce(
|
|
241
|
+
'A load event fired on an adopted content element. The element reloaded when it was moved into the shape, losing its state. State-preserving moves need Node.moveBefore (Chromium 133+, Firefox 144+) and a continuously connected element.'
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
slot.addEventListener('load', onLoad, { capture: true })
|
|
245
|
+
editor.timers.setTimeout(() => slot.removeEventListener('load', onLoad, { capture: true }), 1000)
|
|
246
|
+
}
|
|
247
|
+
|
|
161
248
|
export const InnerShape = memo(
|
|
162
249
|
function InnerShape<T extends TLShape>({ shape, util }: { shape: T; util: ShapeUtil<T> }) {
|
|
163
250
|
return useStateTracking(
|
|
@@ -32,7 +32,7 @@ export interface TLCanvasComponentProps {
|
|
|
32
32
|
export function DefaultCanvas({ className }: TLCanvasComponentProps) {
|
|
33
33
|
const editor = useEditor()
|
|
34
34
|
|
|
35
|
-
const {
|
|
35
|
+
const { Background, SvgDefs } = useEditorComponents()
|
|
36
36
|
|
|
37
37
|
const rCanvas = useRef<HTMLDivElement>(null)
|
|
38
38
|
const rHtmlLayer = useRef<HTMLDivElement>(null)
|
|
@@ -160,7 +160,6 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
|
|
|
160
160
|
{isGridMode && Grid && <GridWrapper />}
|
|
161
161
|
<div ref={rHtmlLayer} className="tl-html-layer tl-shapes" draggable={false}>
|
|
162
162
|
<OnTheCanvasWrapper />
|
|
163
|
-
{SelectionBackground && <SelectionBackgroundWrapper />}
|
|
164
163
|
{hideShapes ? null : <ShapesLayer canvasRef={rCanvas} />}
|
|
165
164
|
</div>
|
|
166
165
|
<CanvasOverlays />
|
|
@@ -345,21 +344,6 @@ function DebugSvgCopy({ id, mode }: { id: TLShapeId; mode: 'img' | 'iframe' }) {
|
|
|
345
344
|
)
|
|
346
345
|
}
|
|
347
346
|
|
|
348
|
-
function SelectionBackgroundWrapper() {
|
|
349
|
-
const editor = useEditor()
|
|
350
|
-
const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
|
|
351
|
-
editor,
|
|
352
|
-
])
|
|
353
|
-
const selectionBounds = useValue(
|
|
354
|
-
'selection bounds',
|
|
355
|
-
() => editor.getSelectionRotatedPageBounds(),
|
|
356
|
-
[editor]
|
|
357
|
-
)
|
|
358
|
-
const { SelectionBackground } = useEditorComponents()
|
|
359
|
-
if (!selectionBounds || !SelectionBackground) return null
|
|
360
|
-
return <SelectionBackground bounds={selectionBounds} rotation={selectionRotation} />
|
|
361
|
-
}
|
|
362
|
-
|
|
363
347
|
function OnTheCanvasWrapper() {
|
|
364
348
|
const { OnTheCanvas } = useEditorComponents()
|
|
365
349
|
if (!OnTheCanvas) return null
|
|
@@ -96,6 +96,42 @@ describe('updateShape', () => {
|
|
|
96
96
|
})
|
|
97
97
|
})
|
|
98
98
|
|
|
99
|
+
describe('updateViewportScreenBounds', () => {
|
|
100
|
+
it('bails without crashing or updating bounds when the container document has no body', () => {
|
|
101
|
+
const documentSpy = vi
|
|
102
|
+
.spyOn(editor, 'getContainerDocument')
|
|
103
|
+
.mockReturnValue({ body: null } as unknown as Document)
|
|
104
|
+
|
|
105
|
+
const prevScreenBounds = editor.getInstanceState().screenBounds
|
|
106
|
+
const prevInsets = editor.getInstanceState().insets
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
expect(() => editor.updateViewportScreenBounds(new Box(10, 20, 100, 200))).not.toThrow()
|
|
110
|
+
// A torn-down document is not measurable, so the bounds are left untouched.
|
|
111
|
+
expect(editor.getInstanceState().screenBounds).toEqual(prevScreenBounds)
|
|
112
|
+
expect(editor.getInstanceState().insets).toEqual(prevInsets)
|
|
113
|
+
} finally {
|
|
114
|
+
documentSpy.mockRestore()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('uses the container document body to calculate right and bottom insets', () => {
|
|
119
|
+
const documentSpy = vi.spyOn(editor, 'getContainerDocument').mockReturnValue({
|
|
120
|
+
body: {
|
|
121
|
+
scrollWidth: 500,
|
|
122
|
+
scrollHeight: 400,
|
|
123
|
+
},
|
|
124
|
+
} as unknown as Document)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
editor.updateViewportScreenBounds(new Box(10, 20, 100, 200))
|
|
128
|
+
expect(editor.getInstanceState().insets).toEqual([true, true, true, true])
|
|
129
|
+
} finally {
|
|
130
|
+
documentSpy.mockRestore()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
99
135
|
describe('zoomToFit', () => {
|
|
100
136
|
it('no-op when isLocked is set', () => {
|
|
101
137
|
editor.getCurrentPageShapeIds = vi.fn(() => new Set([createShapeId('box1')]))
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -409,9 +409,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
409
409
|
this._tickManager = new TickManager(this)
|
|
410
410
|
this.disposables.add(() => this._tickManager.dispose())
|
|
411
411
|
this.disposables.add(() => {
|
|
412
|
-
// Reset camera state to 'idle' so the store isn't left stuck at 'moving'
|
|
413
|
-
// when tick events stop (e.g. React strict mode disposes while camera is moving)
|
|
414
|
-
this.off('tick', this._decayCameraStateTimeout)
|
|
415
412
|
this._setCameraState('idle')
|
|
416
413
|
})
|
|
417
414
|
|
|
@@ -419,6 +416,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
419
416
|
this.disposables.add(() => this.fonts.dispose())
|
|
420
417
|
|
|
421
418
|
this.inputs = new InputsManager(this)
|
|
419
|
+
this.disposables.add(() => this.inputs.dispose())
|
|
422
420
|
this.performance = new PerformanceManager(this)
|
|
423
421
|
this.disposables.add(() => this.performance.dispose())
|
|
424
422
|
this.collaborators = new CollaboratorsManager(this)
|
|
@@ -1179,6 +1177,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1179
1177
|
this.store.dispose()
|
|
1180
1178
|
this.isDisposed = true
|
|
1181
1179
|
this.emit('dispose')
|
|
1180
|
+
this.removeAllListeners()
|
|
1182
1181
|
}
|
|
1183
1182
|
|
|
1184
1183
|
/* ------------------ Themes (shadowing the theme manager) ------------------ */
|
|
@@ -1551,6 +1550,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1551
1550
|
return this.history.getMarkIdMatching(idSubstring)
|
|
1552
1551
|
}
|
|
1553
1552
|
|
|
1553
|
+
/**
|
|
1554
|
+
* Whether the editor is currently replaying history (i.e. an undo or redo is being applied).
|
|
1555
|
+
*
|
|
1556
|
+
* @internal
|
|
1557
|
+
*/
|
|
1558
|
+
isReplayingHistory(): boolean {
|
|
1559
|
+
return this.history.isReplaying()
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1554
1562
|
/**
|
|
1555
1563
|
* Coalesces all changes since the given mark into a single change, removing any intermediate marks.
|
|
1556
1564
|
*
|
|
@@ -3068,8 +3076,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3068
3076
|
return baseCamera
|
|
3069
3077
|
}
|
|
3070
3078
|
|
|
3071
|
-
private _getFollowingPresence(targetUserId:
|
|
3072
|
-
const visited = [this.user.
|
|
3079
|
+
private _getFollowingPresence(targetUserId: TLUserId | null) {
|
|
3080
|
+
const visited = [this.user.getRecordId()]
|
|
3073
3081
|
const collaborators = this.getCollaborators()
|
|
3074
3082
|
let leaderPresence = null as null | TLInstancePresence
|
|
3075
3083
|
while (targetUserId && !visited.includes(targetUserId)) {
|
|
@@ -4029,16 +4037,34 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4029
4037
|
|
|
4030
4038
|
this.once('stop-camera-animation', cancel)
|
|
4031
4039
|
|
|
4040
|
+
const dirZ = direction.z ?? 0
|
|
4041
|
+
|
|
4032
4042
|
const moveCamera = (elapsed: number) => {
|
|
4033
4043
|
const { x: cx, y: cy, z: cz } = this.getCamera()
|
|
4034
|
-
|
|
4044
|
+
|
|
4045
|
+
// Pan movement from x/y direction
|
|
4046
|
+
const dx = (direction.x * (currentSpeed * elapsed)) / cz
|
|
4047
|
+
const dy = (direction.y * (currentSpeed * elapsed)) / cz
|
|
4048
|
+
|
|
4049
|
+
let newCx = cx + dx
|
|
4050
|
+
let newCy = cy + dy
|
|
4051
|
+
let newCz = cz
|
|
4052
|
+
|
|
4053
|
+
// animate zoom if z direction is passed in
|
|
4054
|
+
if (dirZ !== 0) {
|
|
4055
|
+
newCz = cz * (1 + dirZ * currentSpeed * elapsed)
|
|
4056
|
+
// Adjust x/y to keep the viewport center fixed while zooming
|
|
4057
|
+
const center = this.getViewportScreenCenter()
|
|
4058
|
+
newCx += center.x / newCz - center.x / cz
|
|
4059
|
+
newCy += center.y / newCz - center.y / cz
|
|
4060
|
+
}
|
|
4035
4061
|
|
|
4036
4062
|
// Apply friction
|
|
4037
4063
|
currentSpeed *= 1 - friction
|
|
4038
4064
|
if (currentSpeed < speedThreshold) {
|
|
4039
4065
|
cancel()
|
|
4040
4066
|
} else {
|
|
4041
|
-
this._setCamera(new Vec(
|
|
4067
|
+
this._setCamera(new Vec(newCx, newCy, newCz))
|
|
4042
4068
|
}
|
|
4043
4069
|
}
|
|
4044
4070
|
|
|
@@ -4060,7 +4086,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4060
4086
|
* @param opts - The camera move options.
|
|
4061
4087
|
* @public
|
|
4062
4088
|
*/
|
|
4063
|
-
zoomToUser(userId:
|
|
4089
|
+
zoomToUser(userId: TLUserId, opts: TLCameraMoveOptions = { animation: { duration: 500 } }): this {
|
|
4064
4090
|
const presence = this.getCollaborators().find((c) => c.userId === userId)
|
|
4065
4091
|
|
|
4066
4092
|
if (!presence) return this
|
|
@@ -4139,6 +4165,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4139
4165
|
}
|
|
4140
4166
|
|
|
4141
4167
|
const doc = this.getContainerDocument()
|
|
4168
|
+
// If the container's document has been torn down (e.g. an iframe being
|
|
4169
|
+
// removed), its body is null and there's nothing meaningful to measure.
|
|
4170
|
+
if (!doc.body) return this
|
|
4171
|
+
|
|
4142
4172
|
const insets = [
|
|
4143
4173
|
// top
|
|
4144
4174
|
screenBounds.minY !== 0,
|
|
@@ -4400,7 +4430,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4400
4430
|
|
|
4401
4431
|
/**
|
|
4402
4432
|
* Collect user IDs referenced by a set of shapes via shape-specific props
|
|
4403
|
-
* (e.g. `
|
|
4433
|
+
* (e.g. `textLastEditedBy` on notes).
|
|
4404
4434
|
*
|
|
4405
4435
|
* @internal
|
|
4406
4436
|
*/
|
|
@@ -4432,11 +4462,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4432
4462
|
*
|
|
4433
4463
|
* @public
|
|
4434
4464
|
*/
|
|
4435
|
-
startFollowingUser(userId:
|
|
4465
|
+
startFollowingUser(userId: TLUserId): this {
|
|
4436
4466
|
// if we were already following someone, stop following them
|
|
4437
4467
|
this.stopFollowingUser()
|
|
4438
4468
|
|
|
4439
|
-
const thisUserId = this.user.
|
|
4469
|
+
const thisUserId = this.user.getExternalId()
|
|
4440
4470
|
|
|
4441
4471
|
if (!thisUserId) {
|
|
4442
4472
|
console.warn('You should set the userId for the current instance before following a user')
|
|
@@ -8084,32 +8114,53 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8084
8114
|
* @public
|
|
8085
8115
|
*/
|
|
8086
8116
|
resizeShape(shape: TLShapeId | TLShape, scale: VecLike, opts: TLResizeShapeOptions = {}): this {
|
|
8117
|
+
const partial = this.getResizeShapePartial(shape, scale, opts)
|
|
8118
|
+
if (partial) this.updateShapes([partial])
|
|
8119
|
+
return this
|
|
8120
|
+
}
|
|
8121
|
+
|
|
8122
|
+
/**
|
|
8123
|
+
* Get the update for a resized shape without committing it to the store. Interactions that
|
|
8124
|
+
* resize many shapes at once use this to collect all of the updates and commit them in a
|
|
8125
|
+
* single batch. Returns null when there is nothing to update.
|
|
8126
|
+
*
|
|
8127
|
+
* Shapes that are rotated out of alignment with the scale axis cannot be resized with a
|
|
8128
|
+
* single update; those shapes are resized immediately (as `resizeShape` would do) and null
|
|
8129
|
+
* is returned.
|
|
8130
|
+
*
|
|
8131
|
+
* @internal
|
|
8132
|
+
*/
|
|
8133
|
+
getResizeShapePartial(
|
|
8134
|
+
shape: TLShapeId | TLShape,
|
|
8135
|
+
scale: VecLike,
|
|
8136
|
+
opts: TLResizeShapeOptions = {}
|
|
8137
|
+
): TLShapePartial | null {
|
|
8087
8138
|
const id = typeof shape === 'string' ? shape : shape.id
|
|
8088
|
-
if (this.getIsReadonly()) return
|
|
8139
|
+
if (this.getIsReadonly()) return null
|
|
8089
8140
|
|
|
8090
8141
|
if (!Number.isFinite(scale.x)) scale = new Vec(1, scale.y)
|
|
8091
8142
|
if (!Number.isFinite(scale.y)) scale = new Vec(scale.x, 1)
|
|
8092
8143
|
|
|
8093
8144
|
const initialShape = opts.initialShape ?? this.getShape(id)
|
|
8094
|
-
if (!initialShape) return
|
|
8145
|
+
if (!initialShape) return null
|
|
8095
8146
|
|
|
8096
8147
|
const scaleOrigin = opts.scaleOrigin ?? this.getShapePageBounds(id)?.center
|
|
8097
|
-
if (!scaleOrigin) return
|
|
8148
|
+
if (!scaleOrigin) return null
|
|
8098
8149
|
|
|
8099
8150
|
const pageTransform = opts.initialPageTransform
|
|
8100
8151
|
? Mat.Cast(opts.initialPageTransform)
|
|
8101
8152
|
: this.getShapePageTransform(id)
|
|
8102
|
-
if (!pageTransform) return
|
|
8153
|
+
if (!pageTransform) return null
|
|
8103
8154
|
|
|
8104
8155
|
const pageRotation = pageTransform.rotation()
|
|
8105
8156
|
|
|
8106
|
-
if (pageRotation == null) return
|
|
8157
|
+
if (pageRotation == null) return null
|
|
8107
8158
|
|
|
8108
8159
|
const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation
|
|
8109
8160
|
|
|
8110
8161
|
const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds
|
|
8111
8162
|
|
|
8112
|
-
if (!initialBounds) return
|
|
8163
|
+
if (!initialBounds) return null
|
|
8113
8164
|
|
|
8114
8165
|
const isAspectRatioLocked =
|
|
8115
8166
|
opts.isAspectRatioLocked ?? this.getShapeUtil(initialShape).isAspectRatioLocked(initialShape)
|
|
@@ -8119,7 +8170,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8119
8170
|
// from whichever axis is being scaled the least, to avoid the shape getting bigger
|
|
8120
8171
|
// than the bounds of the selection
|
|
8121
8172
|
// const minScale = Math.min(Math.abs(scale.x), Math.abs(scale.y))
|
|
8122
|
-
|
|
8173
|
+
this._resizeUnalignedShape(id, scale, {
|
|
8123
8174
|
...opts,
|
|
8124
8175
|
initialBounds,
|
|
8125
8176
|
scaleOrigin,
|
|
@@ -8128,6 +8179,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8128
8179
|
isAspectRatioLocked,
|
|
8129
8180
|
initialShape,
|
|
8130
8181
|
})
|
|
8182
|
+
return null
|
|
8131
8183
|
}
|
|
8132
8184
|
|
|
8133
8185
|
const util = this.getShapeUtil(initialShape)
|
|
@@ -8140,7 +8192,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8140
8192
|
}
|
|
8141
8193
|
}
|
|
8142
8194
|
|
|
8143
|
-
let
|
|
8195
|
+
let workingShape: TLShape | null = null
|
|
8144
8196
|
|
|
8145
8197
|
if (util.onResize && util.canResize(initialShape)) {
|
|
8146
8198
|
// get the model changes from the shape util
|
|
@@ -8172,7 +8224,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8172
8224
|
// need to adjust the shape's x and y points in case the parent has moved since start of resizing
|
|
8173
8225
|
const { x, y } = this.getPointInParentSpace(initialShape.id, initialPagePoint)
|
|
8174
8226
|
|
|
8175
|
-
|
|
8227
|
+
workingShape = initialShape
|
|
8176
8228
|
if (!opts.skipStartAndEndCallbacks) {
|
|
8177
8229
|
workingShape = applyPartialToRecordWithProps(
|
|
8178
8230
|
initialShape,
|
|
@@ -8194,10 +8246,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8194
8246
|
}
|
|
8195
8247
|
)
|
|
8196
8248
|
|
|
8197
|
-
if (resizedShape) {
|
|
8198
|
-
didResize = true
|
|
8199
|
-
}
|
|
8200
|
-
|
|
8201
8249
|
workingShape = applyPartialToRecordWithProps(workingShape, {
|
|
8202
8250
|
id,
|
|
8203
8251
|
type: initialShape.type as any,
|
|
@@ -8213,40 +8261,47 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8213
8261
|
)
|
|
8214
8262
|
}
|
|
8215
8263
|
|
|
8216
|
-
|
|
8264
|
+
if (resizedShape) {
|
|
8265
|
+
return workingShape
|
|
8266
|
+
}
|
|
8217
8267
|
}
|
|
8218
8268
|
|
|
8219
|
-
|
|
8220
|
-
|
|
8269
|
+
// the shape was not resized by its util, so reposition it (rather than resizing it)
|
|
8270
|
+
// based on where its resized center would be
|
|
8221
8271
|
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8272
|
+
const initialPageCenter = Mat.applyToPoint(pageTransform, initialBounds.center)
|
|
8273
|
+
// get the model changes from the shape util
|
|
8274
|
+
const newPageCenter = this._scalePagePoint(
|
|
8275
|
+
initialPageCenter,
|
|
8276
|
+
scaleOrigin,
|
|
8277
|
+
scale,
|
|
8278
|
+
scaleAxisRotation
|
|
8279
|
+
)
|
|
8230
8280
|
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8281
|
+
const initialPageCenterInParentSpace = this.getPointInParentSpace(
|
|
8282
|
+
initialShape.id,
|
|
8283
|
+
initialPageCenter
|
|
8284
|
+
)
|
|
8285
|
+
const newPageCenterInParentSpace = this.getPointInParentSpace(initialShape.id, newPageCenter)
|
|
8236
8286
|
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8287
|
+
const delta = Vec.Sub(newPageCenterInParentSpace, initialPageCenterInParentSpace)
|
|
8288
|
+
|
|
8289
|
+
if (workingShape) {
|
|
8290
|
+
// the util's onResize ran but returned no change; keep the working update (which may
|
|
8291
|
+
// include changes from onResizeStart / onResizeEnd) and reposition the shape
|
|
8292
|
+
return {
|
|
8293
|
+
...workingShape,
|
|
8294
|
+
x: initialShape.x + delta.x,
|
|
8295
|
+
y: initialShape.y + delta.y,
|
|
8296
|
+
}
|
|
8247
8297
|
}
|
|
8248
8298
|
|
|
8249
|
-
return
|
|
8299
|
+
return {
|
|
8300
|
+
id,
|
|
8301
|
+
type: initialShape.type as any,
|
|
8302
|
+
x: initialShape.x + delta.x,
|
|
8303
|
+
y: initialShape.y + delta.y,
|
|
8304
|
+
}
|
|
8250
8305
|
}
|
|
8251
8306
|
|
|
8252
8307
|
/** @internal */
|
|
@@ -10126,6 +10181,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10126
10181
|
|
|
10127
10182
|
if (ids.length === 0) return undefined
|
|
10128
10183
|
|
|
10184
|
+
// Text geometry is measured from the loaded font, so the export's bounds - and the
|
|
10185
|
+
// layout of any text within it - depend on the right fonts having loaded. Wait for them
|
|
10186
|
+
// before we measure; otherwise an export taken before fonts finish loading (e.g. right
|
|
10187
|
+
// after the editor mounts) is sized and laid out with fallback-font metrics.
|
|
10188
|
+
await this.fonts.loadRequiredFontsForCurrentPage(this.options.maxFontsToLoadBeforeRender)
|
|
10189
|
+
|
|
10129
10190
|
return exportToSvg(this, ids, opts)
|
|
10130
10191
|
}
|
|
10131
10192
|
|
|
@@ -11172,8 +11233,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11172
11233
|
inputs.setIsPointing(true)
|
|
11173
11234
|
inputs.setIsDragging(false)
|
|
11174
11235
|
|
|
11175
|
-
// If pen mode is off
|
|
11176
|
-
|
|
11236
|
+
// If pen mode is off, turn it on for direct-display pen input only (e.g. Apple
|
|
11237
|
+
// Pencil on an iPad or a Surface Pen on a touchscreen). Indirect desktop tablet
|
|
11238
|
+
// styluses still draw as pens, but should not auto-enable pen mode.
|
|
11239
|
+
if (!isPenMode && info.isPenDirect) {
|
|
11240
|
+
this.updateInstanceState({ isPenMode: true })
|
|
11241
|
+
// Once pen mode is on, touch input is ignored, so we discard the
|
|
11242
|
+
// in-progress touch interaction .
|
|
11243
|
+
this.interrupt()
|
|
11244
|
+
}
|
|
11177
11245
|
|
|
11178
11246
|
// On devices with erasers (like the Surface Pen or Wacom Pen), button 5 is the eraser
|
|
11179
11247
|
if (info.button === STYLUS_ERASER_BUTTON) {
|
|
@@ -11323,7 +11391,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11323
11391
|
// Don't pass right-click panning events to the state chart
|
|
11324
11392
|
// as it causes unintended shape selection on release
|
|
11325
11393
|
if (slideSpeed > 0) {
|
|
11326
|
-
this.slideCamera({
|
|
11394
|
+
this.slideCamera({
|
|
11395
|
+
speed: slideSpeed,
|
|
11396
|
+
direction: { x: slideDirection.x, y: slideDirection.y, z: 0 },
|
|
11397
|
+
})
|
|
11327
11398
|
}
|
|
11328
11399
|
this._selectedShapeIdsAtPointerDown = []
|
|
11329
11400
|
this._didCaptureSelectionAtPointerDown = false
|
|
@@ -11332,7 +11403,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11332
11403
|
}
|
|
11333
11404
|
|
|
11334
11405
|
if (slideSpeed > 0) {
|
|
11335
|
-
this.slideCamera({
|
|
11406
|
+
this.slideCamera({
|
|
11407
|
+
speed: slideSpeed,
|
|
11408
|
+
direction: { x: slideDirection.x, y: slideDirection.y, z: 0 },
|
|
11409
|
+
})
|
|
11336
11410
|
}
|
|
11337
11411
|
} else {
|
|
11338
11412
|
if (info.button === STYLUS_ERASER_BUTTON) {
|
|
@@ -11447,6 +11521,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11447
11521
|
info.name = 'right_click'
|
|
11448
11522
|
}
|
|
11449
11523
|
|
|
11524
|
+
// The context menu is a select-tool surface: every item acts on the
|
|
11525
|
+
// current selection. Route a right-click through the select tool before
|
|
11526
|
+
// the event reaches the state chart, so the select tool's idle resolves
|
|
11527
|
+
// the selection at the click point (select the shape under the pointer,
|
|
11528
|
+
// or clear it) no matter which tool was active. Without this, opening the
|
|
11529
|
+
// menu from another tool leaves a stale selection that doesn't match the
|
|
11530
|
+
// click (#8828). Guarded on the select tool existing, since the bare
|
|
11531
|
+
// editor can be configured without it.
|
|
11532
|
+
if (
|
|
11533
|
+
info.name === 'right_click' &&
|
|
11534
|
+
this.getCurrentToolId() !== 'select' &&
|
|
11535
|
+
this.getStateDescendant('select')
|
|
11536
|
+
) {
|
|
11537
|
+
this.setCurrentTool('select')
|
|
11538
|
+
}
|
|
11539
|
+
|
|
11450
11540
|
// If a left click pointer event, send the event to the click manager.
|
|
11451
11541
|
const { isPenMode } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
11452
11542
|
if (info.isPen === isPenMode) {
|