@tldraw/editor 5.2.0-canary.2e7bbb68abd7 → 5.2.0-canary.2f9bfdac00de
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/dist-cjs/index.d.ts +8 -13
- package/dist-cjs/index.js +1 -5
- package/dist-cjs/index.js.map +2 -2
- 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 +3 -1
- package/dist-cjs/lib/editor/Editor.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/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 +3 -1
- 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/useScreenBounds.js +1 -0
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/utils/pointer.js +5 -0
- package/dist-cjs/lib/utils/pointer.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +8 -13
- package/dist-esm/index.mjs +1 -7
- package/dist-esm/index.mjs.map +2 -2
- 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 +3 -1
- package/dist-esm/lib/editor/Editor.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/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 +4 -2
- 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/useScreenBounds.mjs +1 -0
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/utils/pointer.mjs +5 -0
- package/dist-esm/lib/utils/pointer.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +10 -7
- package/src/index.ts +0 -5
- 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 +14 -2
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -1
- package/src/lib/editor/managers/FontManager/FontManager.ts +46 -1
- 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 +6 -1
- package/src/lib/hooks/useEditorComponents.tsx +0 -1
- package/src/lib/hooks/useScreenBounds.ts +3 -0
- package/src/lib/utils/pointer.test.ts +29 -1
- package/src/lib/utils/pointer.ts +24 -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/hooks/useTransform.js +0 -43
- 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/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/hooks/useTransform.ts +0 -30
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -4165,6 +4165,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4165
4165
|
}
|
|
4166
4166
|
|
|
4167
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
|
+
|
|
4168
4172
|
const insets = [
|
|
4169
4173
|
// top
|
|
4170
4174
|
screenBounds.minY !== 0,
|
|
@@ -10177,6 +10181,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10177
10181
|
|
|
10178
10182
|
if (ids.length === 0) return undefined
|
|
10179
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
|
+
|
|
10180
10190
|
return exportToSvg(this, ids, opts)
|
|
10181
10191
|
}
|
|
10182
10192
|
|
|
@@ -11223,8 +11233,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11223
11233
|
inputs.setIsPointing(true)
|
|
11224
11234
|
inputs.setIsDragging(false)
|
|
11225
11235
|
|
|
11226
|
-
// If pen mode is off
|
|
11227
|
-
|
|
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) {
|
|
11228
11240
|
this.updateInstanceState({ isPenMode: true })
|
|
11229
11241
|
// Once pen mode is on, touch input is ignored, so we discard the
|
|
11230
11242
|
// in-progress touch interaction .
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { IndexKey } from '@tldraw/utils'
|
|
10
10
|
import { Mock, Mocked, vi } from 'vitest'
|
|
11
11
|
import { Editor } from '../../Editor'
|
|
12
|
-
import { FontManager } from './FontManager'
|
|
12
|
+
import { clearFontFaceCacheForTests, FontManager } from './FontManager'
|
|
13
13
|
|
|
14
14
|
// Mock the Editor class
|
|
15
15
|
vi.mock('../../Editor')
|
|
@@ -73,6 +73,7 @@ describe('FontManager', () => {
|
|
|
73
73
|
|
|
74
74
|
beforeEach(() => {
|
|
75
75
|
vi.clearAllMocks()
|
|
76
|
+
clearFontFaceCacheForTests()
|
|
76
77
|
|
|
77
78
|
mockAssetUrls = {
|
|
78
79
|
'test-font.woff2': 'https://example.com/fonts/test-font.woff2',
|
|
@@ -314,4 +315,26 @@ describe('FontManager', () => {
|
|
|
314
315
|
await expect(fontManager.ensureFontIsLoaded(minimalFont)).resolves.toBeUndefined()
|
|
315
316
|
})
|
|
316
317
|
})
|
|
318
|
+
|
|
319
|
+
describe('findOrCreateFontFace caching', () => {
|
|
320
|
+
it('reuses a font face across manager instances instead of recreating it on remount', async () => {
|
|
321
|
+
const font = createMockFont()
|
|
322
|
+
await fontManager.ensureFontIsLoaded(font)
|
|
323
|
+
expect(global.FontFace).toHaveBeenCalledTimes(1)
|
|
324
|
+
|
|
325
|
+
// A fresh manager on the same document simulates an editor remount.
|
|
326
|
+
const remounted = new FontManager(editor, mockAssetUrls)
|
|
327
|
+
await remounted.ensureFontIsLoaded(font)
|
|
328
|
+
|
|
329
|
+
// The per-document cache lets the second manager reuse the existing face
|
|
330
|
+
// rather than re-scanning document.fonts and creating a new one.
|
|
331
|
+
expect(global.FontFace).toHaveBeenCalledTimes(1)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('creates separate font faces for fonts with different descriptors', async () => {
|
|
335
|
+
await fontManager.ensureFontIsLoaded(createMockFont({ weight: 'normal' }))
|
|
336
|
+
await fontManager.ensureFontIsLoaded(createMockFont({ weight: 'bold' }))
|
|
337
|
+
expect(global.FontFace).toHaveBeenCalledTimes(2)
|
|
338
|
+
})
|
|
339
|
+
})
|
|
317
340
|
})
|
|
@@ -145,7 +145,26 @@ export class FontManager {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
private findOrCreateFontFace(font: TLFontFace) {
|
|
148
|
-
const
|
|
148
|
+
const containerDocument = this.editor.getContainerDocument()
|
|
149
|
+
|
|
150
|
+
// `findOrCreateFontFace` runs for every font on every editor mount, and a fresh
|
|
151
|
+
// editor (e.g. switching documents) gets a fresh FontManager with no memory of the
|
|
152
|
+
// previous one. The dedup below is an O(n) scan of the document's whole FontFaceSet,
|
|
153
|
+
// so without a cache that scan re-ran on every mount (measurably expensive on mobile
|
|
154
|
+
// Safari). Cache the resolved FontFace per document so repeated lookups - and
|
|
155
|
+
// remounts - are O(1). Keyed per document for cross-window embedding.
|
|
156
|
+
let cache = fontFaceCacheByDocument.get(containerDocument)
|
|
157
|
+
if (!cache) {
|
|
158
|
+
cache = new Map()
|
|
159
|
+
fontFaceCacheByDocument.set(containerDocument, cache)
|
|
160
|
+
}
|
|
161
|
+
const key = getFontFaceCacheKey(font)
|
|
162
|
+
const cached = cache.get(key)
|
|
163
|
+
if (cached) return cached
|
|
164
|
+
|
|
165
|
+
const fonts = containerDocument.fonts
|
|
166
|
+
// On a cache miss we still scan once, so font faces added outside this manager
|
|
167
|
+
// (e.g. preloaded fonts) are reused rather than duplicated.
|
|
149
168
|
for (const existing of fonts) {
|
|
150
169
|
if (
|
|
151
170
|
existing.family === font.family &&
|
|
@@ -153,6 +172,7 @@ export class FontManager {
|
|
|
153
172
|
([key, defaultValue]) => existing[key] === (font[key] ?? defaultValue)
|
|
154
173
|
)
|
|
155
174
|
) {
|
|
175
|
+
cache.set(key, existing)
|
|
156
176
|
return existing
|
|
157
177
|
}
|
|
158
178
|
}
|
|
@@ -164,6 +184,7 @@ export class FontManager {
|
|
|
164
184
|
})
|
|
165
185
|
|
|
166
186
|
fonts.add(instance)
|
|
187
|
+
cache.set(key, instance)
|
|
167
188
|
|
|
168
189
|
return instance
|
|
169
190
|
}
|
|
@@ -205,3 +226,27 @@ const defaultFontFaceDescriptors = {
|
|
|
205
226
|
descentOverride: 'normal',
|
|
206
227
|
lineGapOverride: 'normal',
|
|
207
228
|
}
|
|
229
|
+
|
|
230
|
+
// A FontFace is fully determined by its family and descriptors, so resolved faces can be
|
|
231
|
+
// cached per document and reused across FontManager instances (e.g. editor remounts),
|
|
232
|
+
// turning the per-lookup FontFaceSet scan into an O(1) map lookup. Faces are never removed
|
|
233
|
+
// from `document.fonts` (FontManager.dispose leaves them), so the cache stays valid for the
|
|
234
|
+
// document's lifetime. Keyed per document for cross-window embedding.
|
|
235
|
+
let fontFaceCacheByDocument = new WeakMap<Document, Map<string, FontFace>>()
|
|
236
|
+
|
|
237
|
+
function getFontFaceCacheKey(font: TLFontFace): string {
|
|
238
|
+
return JSON.stringify([
|
|
239
|
+
font.family,
|
|
240
|
+
...objectMapEntries(defaultFontFaceDescriptors).map(
|
|
241
|
+
([key, defaultValue]) => font[key] ?? defaultValue
|
|
242
|
+
),
|
|
243
|
+
])
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Resets the per-document font-face cache. Only intended for tests.
|
|
248
|
+
* @internal
|
|
249
|
+
*/
|
|
250
|
+
export function clearFontFaceCacheForTests() {
|
|
251
|
+
fontFaceCacheByDocument = new WeakMap()
|
|
252
|
+
}
|
|
@@ -63,6 +63,13 @@ export type TLPointerEventInfo = TLBaseEventInfo & {
|
|
|
63
63
|
pointerId: number
|
|
64
64
|
button: number
|
|
65
65
|
isPen: boolean
|
|
66
|
+
/**
|
|
67
|
+
* Whether this pen event appears to be direct manipulation on the display (e.g. Apple Pencil on
|
|
68
|
+
* an iPad or a Surface Pen on a touchscreen) rather than indirect input from a desktop graphics
|
|
69
|
+
* tablet (e.g. a Wacom Intuos). Only direct-display pens should auto-enable pen mode. Drawing and
|
|
70
|
+
* pressure behavior is driven by `isPen` and applies to all pens regardless of this flag.
|
|
71
|
+
*/
|
|
72
|
+
isPenDirect?: boolean
|
|
66
73
|
} & TLPointerEventTarget
|
|
67
74
|
|
|
68
75
|
/** @public */
|
|
@@ -17,6 +17,10 @@ const tlenv = {
|
|
|
17
17
|
isWebview: false,
|
|
18
18
|
isDarwin: false,
|
|
19
19
|
hasCanvasSupport: false,
|
|
20
|
+
// Whether the device has a touch screen (an integrated coarse pointer, e.g. an iPad or a
|
|
21
|
+
// touchscreen laptop). Unlike `isCoarsePointer`, this reflects the device's hardware rather than
|
|
22
|
+
// the pointer currently in use, so it stays stable when a pen or mouse is used.
|
|
23
|
+
isTouchDevice: false,
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
let isForcedFinePointer = false
|
|
@@ -32,6 +36,9 @@ if (typeof window !== 'undefined') {
|
|
|
32
36
|
}
|
|
33
37
|
tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
|
|
34
38
|
isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
|
|
39
|
+
tlenv.isTouchDevice =
|
|
40
|
+
('navigator' in window && window.navigator.maxTouchPoints > 0) ||
|
|
41
|
+
(typeof window.matchMedia === 'function' && window.matchMedia('(any-pointer: coarse)').matches)
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
/**
|
|
@@ -2,7 +2,6 @@ import { ComponentType, RefAttributes, createContext, useContext } from 'react'
|
|
|
2
2
|
import type { TLCanvasComponentProps } from '../components/default-components/DefaultCanvas'
|
|
3
3
|
import type { TLErrorFallbackComponent } from '../components/default-components/DefaultErrorFallback'
|
|
4
4
|
import type { TLGridProps } from '../components/default-components/DefaultGrid'
|
|
5
|
-
import type { TLSelectionBackgroundProps } from '../components/default-components/DefaultSelectionBackground'
|
|
6
5
|
import type { TLShapeErrorFallbackComponent } from '../components/default-components/DefaultShapeErrorFallback'
|
|
7
6
|
import type { TLShapeWrapperProps } from '../components/default-components/DefaultShapeWrapper'
|
|
8
7
|
|
|
@@ -14,7 +13,6 @@ export interface TLEditorComponents {
|
|
|
14
13
|
InFrontOfTheCanvas?: ComponentType | null
|
|
15
14
|
LoadingScreen?: ComponentType | null
|
|
16
15
|
OnTheCanvas?: ComponentType | null
|
|
17
|
-
SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
|
|
18
16
|
ShapeWrapper?: ComponentType<TLShapeWrapperProps & RefAttributes<HTMLDivElement>> | null
|
|
19
17
|
Spinner?: ComponentType<React.SVGProps<SVGSVGElement>> | null
|
|
20
18
|
SvgDefs?: ComponentType | null
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
setPointerCapture,
|
|
9
9
|
} from '../utils/dom'
|
|
10
10
|
import { getPointerInfo } from '../utils/getPointerInfo'
|
|
11
|
-
import { getPointerEventButton, isSecondaryClickEvent } from '../utils/pointer'
|
|
11
|
+
import { getPointerEventButton, isDirectDisplayPen, isSecondaryClickEvent } from '../utils/pointer'
|
|
12
12
|
import { useEditor } from './useEditor'
|
|
13
13
|
|
|
14
14
|
export function useCanvasEvents() {
|
|
@@ -39,6 +39,10 @@ export function useCanvasEvents() {
|
|
|
39
39
|
|
|
40
40
|
if (button !== 0 && button !== 1 && button !== 2 && button !== 5) return
|
|
41
41
|
|
|
42
|
+
// Detect direct-display pen input (Apple Pencil, Surface Pen on a touchscreen) so we
|
|
43
|
+
// only auto-enable pen mode for it, not for an indirect desktop tablet stylus.
|
|
44
|
+
const isPenDirect = isDirectDisplayPen(e)
|
|
45
|
+
|
|
42
46
|
setPointerCapture(e.currentTarget, e)
|
|
43
47
|
|
|
44
48
|
editor.dispatch({
|
|
@@ -46,6 +50,7 @@ export function useCanvasEvents() {
|
|
|
46
50
|
target: 'canvas',
|
|
47
51
|
name: 'pointer_down',
|
|
48
52
|
...getPointerInfo(editor, e),
|
|
53
|
+
isPenDirect,
|
|
49
54
|
})
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -11,6 +11,9 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
|
|
|
11
11
|
const updateBounds = throttle(
|
|
12
12
|
() => {
|
|
13
13
|
if (!ref.current) return
|
|
14
|
+
// If the container's document has been torn down (e.g. an iframe
|
|
15
|
+
// being removed), there's nothing to measure — bail.
|
|
16
|
+
if (!ref.current.ownerDocument.body) return
|
|
14
17
|
editor.updateViewportScreenBounds(ref.current)
|
|
15
18
|
},
|
|
16
19
|
200,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tlenv } from '../globals/environment'
|
|
2
2
|
import { TestEditor } from '../test/TestEditor'
|
|
3
3
|
import { getPointerInfo } from './getPointerInfo'
|
|
4
|
-
import { isSecondaryClickEvent } from './pointer'
|
|
4
|
+
import { isDirectDisplayPen, isSecondaryClickEvent } from './pointer'
|
|
5
5
|
|
|
6
6
|
const originalIsDarwin = tlenv.isDarwin
|
|
7
7
|
|
|
@@ -21,6 +21,34 @@ describe('isSecondaryClickEvent', () => {
|
|
|
21
21
|
})
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
describe('isDirectDisplayPen', () => {
|
|
25
|
+
const originalIsTouchDevice = tlenv.isTouchDevice
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
tlenv.isTouchDevice = originalIsTouchDevice
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function event(pointerType: string) {
|
|
32
|
+
return { pointerType, pointerId: 1 } as unknown as PointerEvent
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('treats a pen on a touch-capable device as a direct-display pen', () => {
|
|
36
|
+
tlenv.isTouchDevice = true
|
|
37
|
+
expect(isDirectDisplayPen(event('pen'))).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('treats a pen on a non-touch device as indirect', () => {
|
|
41
|
+
tlenv.isTouchDevice = false
|
|
42
|
+
expect(isDirectDisplayPen(event('pen'))).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('is never true for mouse or touch input, even on a touch-capable device', () => {
|
|
46
|
+
tlenv.isTouchDevice = true
|
|
47
|
+
expect(isDirectDisplayPen(event('mouse'))).toBe(false)
|
|
48
|
+
expect(isDirectDisplayPen(event('touch'))).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
24
52
|
describe('ctrl + left-click on macOS fires as a right-click (regression)', () => {
|
|
25
53
|
// Regression test for https://github.com/tldraw/tldraw/issues/8217
|
|
26
54
|
// On macOS, a ctrl + left-click should be treated as a right-click
|
package/src/lib/utils/pointer.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
import type React from 'react'
|
|
1
2
|
import { tlenv } from '../globals/environment'
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Decide whether a pen pointer event looks like direct manipulation on the display (e.g. Apple
|
|
6
|
+
* Pencil on an iPad or a Surface Pen on a touchscreen) rather than indirect input from a desktop
|
|
7
|
+
* graphics tablet (e.g. a Wacom Intuos).
|
|
8
|
+
*
|
|
9
|
+
* We can't tell the two apart from the pointer event itself: both report `pointerType: 'pen'`, and
|
|
10
|
+
* implicit pointer capture — which in theory distinguishes direct-manipulation pointers — isn't
|
|
11
|
+
* reliably observable across browsers (notably WebKit/iPad). Instead we key off the device: a
|
|
12
|
+
* direct-display pen draws on a touch-capable screen, while an indirect graphics tablet is used on
|
|
13
|
+
* a non-touch desktop alongside a mouse. A device with no touch input therefore can't host a
|
|
14
|
+
* direct-display pen.
|
|
15
|
+
*
|
|
16
|
+
* Note this uses {@link tlenv.isTouchDevice} — the device's fixed touch capability — not the
|
|
17
|
+
* editor's dynamic `isCoarsePointer` state, which a pen `pointerdown` flips to coarse regardless
|
|
18
|
+
* of device.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export function isDirectDisplayPen(e: React.PointerEvent | PointerEvent): boolean {
|
|
23
|
+
if (e.pointerType !== 'pen') return false
|
|
24
|
+
return tlenv.isTouchDevice
|
|
25
|
+
}
|
|
26
|
+
|
|
3
27
|
/** @internal */
|
|
4
28
|
interface PointerLike {
|
|
5
29
|
button: number
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '5.2.0-canary.
|
|
4
|
+
export const version = '5.2.0-canary.2f9bfdac00de'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2026-05-06T16:28:18.473Z',
|
|
7
|
-
minor: '2026-06-
|
|
8
|
-
patch: '2026-06-
|
|
7
|
+
minor: '2026-06-22T14:24:17.659Z',
|
|
8
|
+
patch: '2026-06-22T14:24:17.659Z',
|
|
9
9
|
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var DefaultSelectionBackground_exports = {};
|
|
30
|
-
__export(DefaultSelectionBackground_exports, {
|
|
31
|
-
DefaultSelectionBackground: () => DefaultSelectionBackground
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(DefaultSelectionBackground_exports);
|
|
34
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
-
var React = __toESM(require("react"), 1);
|
|
36
|
-
var import_useTransform = require("../../hooks/useTransform");
|
|
37
|
-
var import_utils = require("../../primitives/utils");
|
|
38
|
-
function DefaultSelectionBackground({ bounds, rotation }) {
|
|
39
|
-
const rDiv = React.useRef(null);
|
|
40
|
-
(0, import_useTransform.useTransform)(rDiv, bounds.x, bounds.y, 1, rotation);
|
|
41
|
-
React.useLayoutEffect(() => {
|
|
42
|
-
const div = rDiv.current;
|
|
43
|
-
if (!div) return;
|
|
44
|
-
div.style.width = (0, import_utils.toDomPrecision)(Math.max(1, bounds.width)) + "px";
|
|
45
|
-
div.style.height = (0, import_utils.toDomPrecision)(Math.max(1, bounds.height)) + "px";
|
|
46
|
-
}, [bounds.width, bounds.height]);
|
|
47
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: rDiv, className: "tl-selection__bg", draggable: false });
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=DefaultSelectionBackground.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/lib/components/default-components/DefaultSelectionBackground.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport { useTransform } from '../../hooks/useTransform'\nimport { Box } from '../../primitives/Box'\nimport { toDomPrecision } from '../../primitives/utils'\n\n/** @public */\nexport interface TLSelectionBackgroundProps {\n\tbounds: Box\n\trotation: number\n}\n\n/** @public @react */\nexport function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps) {\n\tconst rDiv = React.useRef<HTMLDivElement>(null)\n\tuseTransform(rDiv, bounds.x, bounds.y, 1, rotation)\n\n\tReact.useLayoutEffect(() => {\n\t\tconst div = rDiv.current\n\t\tif (!div) return\n\t\tdiv.style.width = toDomPrecision(Math.max(1, bounds.width)) + 'px'\n\t\tdiv.style.height = toDomPrecision(Math.max(1, bounds.height)) + 'px'\n\t}, [bounds.width, bounds.height])\n\n\treturn <div ref={rDiv} className=\"tl-selection__bg\" draggable={false} />\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBQ;AAvBR,YAAuB;AACvB,0BAA6B;AAE7B,mBAA+B;AASxB,SAAS,2BAA2B,EAAE,QAAQ,SAAS,GAA+B;AAC5F,QAAM,OAAO,MAAM,OAAuB,IAAI;AAC9C,wCAAa,MAAM,OAAO,GAAG,OAAO,GAAG,GAAG,QAAQ;AAElD,QAAM,gBAAgB,MAAM;AAC3B,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,YAAQ,6BAAe,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI;AAC9D,QAAI,MAAM,aAAS,6BAAe,KAAK,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI;AAAA,EACjE,GAAG,CAAC,OAAO,OAAO,OAAO,MAAM,CAAC;AAEhC,SAAO,4CAAC,SAAI,KAAK,MAAM,WAAU,oBAAmB,WAAW,OAAO;AACvE;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var useTransform_exports = {};
|
|
20
|
-
__export(useTransform_exports, {
|
|
21
|
-
useTransform: () => useTransform
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(useTransform_exports);
|
|
24
|
-
var import_react = require("react");
|
|
25
|
-
function useTransform(ref, x, y, scale, rotate, additionalOffset) {
|
|
26
|
-
(0, import_react.useLayoutEffect)(() => {
|
|
27
|
-
const elm = ref.current;
|
|
28
|
-
if (!elm) return;
|
|
29
|
-
if (x === void 0) return;
|
|
30
|
-
let trans = `translate(${x}px, ${y}px)`;
|
|
31
|
-
if (scale !== void 0) {
|
|
32
|
-
trans += ` scale(${scale})`;
|
|
33
|
-
}
|
|
34
|
-
if (rotate !== void 0) {
|
|
35
|
-
trans += ` rotate(${rotate}rad)`;
|
|
36
|
-
}
|
|
37
|
-
if (additionalOffset) {
|
|
38
|
-
trans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`;
|
|
39
|
-
}
|
|
40
|
-
elm.style.transform = trans;
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=useTransform.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/hooks/useTransform.ts"],
|
|
4
|
-
"sourcesContent": ["import { useLayoutEffect } from 'react'\nimport { VecLike } from '../primitives/Vec'\n\n/** @public */\nexport function useTransform(\n\tref: React.RefObject<HTMLElement | SVGElement | null>,\n\tx?: number,\n\ty?: number,\n\tscale?: number,\n\trotate?: number,\n\tadditionalOffset?: VecLike\n) {\n\tuseLayoutEffect(() => {\n\t\tconst elm = ref.current\n\t\tif (!elm) return\n\t\tif (x === undefined) return\n\n\t\tlet trans = `translate(${x}px, ${y}px)`\n\t\tif (scale !== undefined) {\n\t\t\ttrans += ` scale(${scale})`\n\t\t}\n\t\tif (rotate !== undefined) {\n\t\t\ttrans += ` rotate(${rotate}rad)`\n\t\t}\n\t\tif (additionalOffset) {\n\t\t\ttrans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`\n\t\t}\n\t\telm.style.transform = trans\n\t})\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAgC;AAIzB,SAAS,aACf,KACA,GACA,GACA,OACA,QACA,kBACC;AACD,oCAAgB,MAAM;AACrB,UAAM,MAAM,IAAI;AAChB,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,OAAW;AAErB,QAAI,QAAQ,aAAa,CAAC,OAAO,CAAC;AAClC,QAAI,UAAU,QAAW;AACxB,eAAS,UAAU,KAAK;AAAA,IACzB;AACA,QAAI,WAAW,QAAW;AACzB,eAAS,WAAW,MAAM;AAAA,IAC3B;AACA,QAAI,kBAAkB;AACrB,eAAS,cAAc,iBAAiB,CAAC,OAAO,iBAAiB,CAAC;AAAA,IACnE;AACA,QAAI,MAAM,YAAY;AAAA,EACvB,CAAC;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { useTransform } from "../../hooks/useTransform.mjs";
|
|
4
|
-
import { toDomPrecision } from "../../primitives/utils.mjs";
|
|
5
|
-
function DefaultSelectionBackground({ bounds, rotation }) {
|
|
6
|
-
const rDiv = React.useRef(null);
|
|
7
|
-
useTransform(rDiv, bounds.x, bounds.y, 1, rotation);
|
|
8
|
-
React.useLayoutEffect(() => {
|
|
9
|
-
const div = rDiv.current;
|
|
10
|
-
if (!div) return;
|
|
11
|
-
div.style.width = toDomPrecision(Math.max(1, bounds.width)) + "px";
|
|
12
|
-
div.style.height = toDomPrecision(Math.max(1, bounds.height)) + "px";
|
|
13
|
-
}, [bounds.width, bounds.height]);
|
|
14
|
-
return /* @__PURE__ */ jsx("div", { ref: rDiv, className: "tl-selection__bg", draggable: false });
|
|
15
|
-
}
|
|
16
|
-
export {
|
|
17
|
-
DefaultSelectionBackground
|
|
18
|
-
};
|
|
19
|
-
//# sourceMappingURL=DefaultSelectionBackground.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/lib/components/default-components/DefaultSelectionBackground.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport { useTransform } from '../../hooks/useTransform'\nimport { Box } from '../../primitives/Box'\nimport { toDomPrecision } from '../../primitives/utils'\n\n/** @public */\nexport interface TLSelectionBackgroundProps {\n\tbounds: Box\n\trotation: number\n}\n\n/** @public @react */\nexport function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps) {\n\tconst rDiv = React.useRef<HTMLDivElement>(null)\n\tuseTransform(rDiv, bounds.x, bounds.y, 1, rotation)\n\n\tReact.useLayoutEffect(() => {\n\t\tconst div = rDiv.current\n\t\tif (!div) return\n\t\tdiv.style.width = toDomPrecision(Math.max(1, bounds.width)) + 'px'\n\t\tdiv.style.height = toDomPrecision(Math.max(1, bounds.height)) + 'px'\n\t}, [bounds.width, bounds.height])\n\n\treturn <div ref={rDiv} className=\"tl-selection__bg\" draggable={false} />\n}\n"],
|
|
5
|
-
"mappings": "AAuBQ;AAvBR,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB;AASxB,SAAS,2BAA2B,EAAE,QAAQ,SAAS,GAA+B;AAC5F,QAAM,OAAO,MAAM,OAAuB,IAAI;AAC9C,eAAa,MAAM,OAAO,GAAG,OAAO,GAAG,GAAG,QAAQ;AAElD,QAAM,gBAAgB,MAAM;AAC3B,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,QAAQ,eAAe,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI;AAC9D,QAAI,MAAM,SAAS,eAAe,KAAK,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI;AAAA,EACjE,GAAG,CAAC,OAAO,OAAO,OAAO,MAAM,CAAC;AAEhC,SAAO,oBAAC,SAAI,KAAK,MAAM,WAAU,oBAAmB,WAAW,OAAO;AACvE;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { useLayoutEffect } from "react";
|
|
2
|
-
function useTransform(ref, x, y, scale, rotate, additionalOffset) {
|
|
3
|
-
useLayoutEffect(() => {
|
|
4
|
-
const elm = ref.current;
|
|
5
|
-
if (!elm) return;
|
|
6
|
-
if (x === void 0) return;
|
|
7
|
-
let trans = `translate(${x}px, ${y}px)`;
|
|
8
|
-
if (scale !== void 0) {
|
|
9
|
-
trans += ` scale(${scale})`;
|
|
10
|
-
}
|
|
11
|
-
if (rotate !== void 0) {
|
|
12
|
-
trans += ` rotate(${rotate}rad)`;
|
|
13
|
-
}
|
|
14
|
-
if (additionalOffset) {
|
|
15
|
-
trans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`;
|
|
16
|
-
}
|
|
17
|
-
elm.style.transform = trans;
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
export {
|
|
21
|
-
useTransform
|
|
22
|
-
};
|
|
23
|
-
//# sourceMappingURL=useTransform.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/hooks/useTransform.ts"],
|
|
4
|
-
"sourcesContent": ["import { useLayoutEffect } from 'react'\nimport { VecLike } from '../primitives/Vec'\n\n/** @public */\nexport function useTransform(\n\tref: React.RefObject<HTMLElement | SVGElement | null>,\n\tx?: number,\n\ty?: number,\n\tscale?: number,\n\trotate?: number,\n\tadditionalOffset?: VecLike\n) {\n\tuseLayoutEffect(() => {\n\t\tconst elm = ref.current\n\t\tif (!elm) return\n\t\tif (x === undefined) return\n\n\t\tlet trans = `translate(${x}px, ${y}px)`\n\t\tif (scale !== undefined) {\n\t\t\ttrans += ` scale(${scale})`\n\t\t}\n\t\tif (rotate !== undefined) {\n\t\t\ttrans += ` rotate(${rotate}rad)`\n\t\t}\n\t\tif (additionalOffset) {\n\t\t\ttrans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`\n\t\t}\n\t\telm.style.transform = trans\n\t})\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,uBAAuB;AAIzB,SAAS,aACf,KACA,GACA,GACA,OACA,QACA,kBACC;AACD,kBAAgB,MAAM;AACrB,UAAM,MAAM,IAAI;AAChB,QAAI,CAAC,IAAK;AACV,QAAI,MAAM,OAAW;AAErB,QAAI,QAAQ,aAAa,CAAC,OAAO,CAAC;AAClC,QAAI,UAAU,QAAW;AACxB,eAAS,UAAU,KAAK;AAAA,IACzB;AACA,QAAI,WAAW,QAAW;AACzB,eAAS,WAAW,MAAM;AAAA,IAC3B;AACA,QAAI,kBAAkB;AACrB,eAAS,cAAc,iBAAiB,CAAC,OAAO,iBAAiB,CAAC;AAAA,IACnE;AACA,QAAI,MAAM,YAAY;AAAA,EACvB,CAAC;AACF;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { useTransform } from '../../hooks/useTransform'
|
|
3
|
-
import { Box } from '../../primitives/Box'
|
|
4
|
-
import { toDomPrecision } from '../../primitives/utils'
|
|
5
|
-
|
|
6
|
-
/** @public */
|
|
7
|
-
export interface TLSelectionBackgroundProps {
|
|
8
|
-
bounds: Box
|
|
9
|
-
rotation: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** @public @react */
|
|
13
|
-
export function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps) {
|
|
14
|
-
const rDiv = React.useRef<HTMLDivElement>(null)
|
|
15
|
-
useTransform(rDiv, bounds.x, bounds.y, 1, rotation)
|
|
16
|
-
|
|
17
|
-
React.useLayoutEffect(() => {
|
|
18
|
-
const div = rDiv.current
|
|
19
|
-
if (!div) return
|
|
20
|
-
div.style.width = toDomPrecision(Math.max(1, bounds.width)) + 'px'
|
|
21
|
-
div.style.height = toDomPrecision(Math.max(1, bounds.height)) + 'px'
|
|
22
|
-
}, [bounds.width, bounds.height])
|
|
23
|
-
|
|
24
|
-
return <div ref={rDiv} className="tl-selection__bg" draggable={false} />
|
|
25
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { useLayoutEffect } from 'react'
|
|
2
|
-
import { VecLike } from '../primitives/Vec'
|
|
3
|
-
|
|
4
|
-
/** @public */
|
|
5
|
-
export function useTransform(
|
|
6
|
-
ref: React.RefObject<HTMLElement | SVGElement | null>,
|
|
7
|
-
x?: number,
|
|
8
|
-
y?: number,
|
|
9
|
-
scale?: number,
|
|
10
|
-
rotate?: number,
|
|
11
|
-
additionalOffset?: VecLike
|
|
12
|
-
) {
|
|
13
|
-
useLayoutEffect(() => {
|
|
14
|
-
const elm = ref.current
|
|
15
|
-
if (!elm) return
|
|
16
|
-
if (x === undefined) return
|
|
17
|
-
|
|
18
|
-
let trans = `translate(${x}px, ${y}px)`
|
|
19
|
-
if (scale !== undefined) {
|
|
20
|
-
trans += ` scale(${scale})`
|
|
21
|
-
}
|
|
22
|
-
if (rotate !== undefined) {
|
|
23
|
-
trans += ` rotate(${rotate}rad)`
|
|
24
|
-
}
|
|
25
|
-
if (additionalOffset) {
|
|
26
|
-
trans += ` translate(${additionalOffset.x}px, ${additionalOffset.y}px)`
|
|
27
|
-
}
|
|
28
|
-
elm.style.transform = trans
|
|
29
|
-
})
|
|
30
|
-
}
|