@tldraw/editor 4.5.2 → 4.6.0-canary.4ec045c286e1
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 +37 -6
- package/dist-cjs/index.js +6 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +7 -5
- package/dist-cjs/lib/TldrawEditor.js.map +3 -3
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +3 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +8 -5
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js +8 -5
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +3 -2
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +1 -0
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +52 -16
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +62 -6
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +4 -3
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +5 -0
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +3 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/exports/FontEmbedder.js +9 -8
- package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/StyleEmbedder.js +27 -15
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +3 -3
- package/dist-cjs/lib/exports/domUtils.js +15 -0
- package/dist-cjs/lib/exports/domUtils.js.map +2 -2
- package/dist-cjs/lib/exports/embedMedia.js +15 -12
- package/dist-cjs/lib/exports/embedMedia.js.map +2 -2
- package/dist-cjs/lib/exports/exportToSvg.js +8 -7
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +181 -29
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +3 -3
- package/dist-cjs/lib/exports/getSvgJsx.js +21 -9
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +4 -3
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +13 -11
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +3 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useScreenBounds.js +10 -6
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/hooks/useViewportHeight.js +13 -11
- package/dist-cjs/lib/hooks/useViewportHeight.js.map +3 -3
- package/dist-cjs/lib/license/Watermark.js +10 -0
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +35 -22
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +6 -13
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +31 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +9 -0
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +9 -0
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +32 -18
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +12 -0
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js +51 -12
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js +12 -0
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/geometry.bench.js +133 -0
- package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +7 -0
- package/dist-cjs/lib/primitives/intersect.js +16 -15
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +0 -1
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js +3 -2
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +15 -2
- package/dist-cjs/lib/utils/dom.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 +37 -6
- package/dist-esm/index.mjs +8 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +7 -5
- package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +2 -1
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +8 -5
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +8 -5
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +3 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +1 -0
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +53 -17
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +64 -6
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +4 -3
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +5 -0
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +3 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/exports/FontEmbedder.mjs +9 -8
- package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +29 -16
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +3 -3
- package/dist-esm/lib/exports/domUtils.mjs +15 -0
- package/dist-esm/lib/exports/domUtils.mjs.map +2 -2
- package/dist-esm/lib/exports/embedMedia.mjs +16 -13
- package/dist-esm/lib/exports/embedMedia.mjs.map +2 -2
- package/dist-esm/lib/exports/exportToSvg.mjs +8 -7
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +181 -29
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +3 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs +21 -9
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +4 -3
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +13 -11
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +3 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useScreenBounds.mjs +10 -6
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/hooks/useViewportHeight.mjs +13 -11
- package/dist-esm/lib/hooks/useViewportHeight.mjs.map +3 -3
- package/dist-esm/lib/license/Watermark.mjs +10 -0
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +35 -22
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +6 -13
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +31 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +9 -0
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +9 -0
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +32 -18
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +13 -1
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +51 -12
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +13 -1
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +132 -0
- package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +7 -0
- package/dist-esm/lib/primitives/intersect.mjs +17 -16
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +0 -1
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +3 -2
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +15 -2
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +3 -0
- package/src/lib/TldrawEditor.tsx +7 -5
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +2 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +8 -5
- package/src/lib/config/TLSessionStateSnapshot.ts +8 -5
- package/src/lib/config/TLUserPreferences.ts +3 -2
- package/src/lib/config/createTLStore.ts +3 -0
- package/src/lib/editor/Editor.ts +53 -15
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +7 -6
- package/src/lib/editor/managers/FocusManager/FocusManager.ts +10 -7
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +1 -0
- package/src/lib/editor/managers/FontManager/FontManager.ts +4 -3
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +16 -0
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +7 -2
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +4 -5
- package/src/lib/editor/managers/TextManager/TextManager.ts +2 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +3 -2
- package/src/lib/editor/types/misc-types.ts +8 -2
- package/src/lib/exports/FontEmbedder.ts +10 -9
- package/src/lib/exports/StyleEmbedder.ts +33 -15
- package/src/lib/exports/domUtils.ts +20 -0
- package/src/lib/exports/embedMedia.ts +23 -17
- package/src/lib/exports/exportToSvg.tsx +8 -7
- package/src/lib/exports/getSvgAsImage.ts +292 -32
- package/src/lib/exports/getSvgJsx.test.ts +103 -101
- package/src/lib/exports/getSvgJsx.tsx +33 -10
- package/src/lib/globals/environment.ts +4 -3
- package/src/lib/hooks/useCanvasEvents.ts +2 -3
- package/src/lib/hooks/useDocumentEvents.ts +16 -11
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +3 -3
- package/src/lib/hooks/useScreenBounds.ts +10 -6
- package/src/lib/hooks/useViewportHeight.ts +13 -11
- package/src/lib/license/Watermark.tsx +10 -0
- package/src/lib/primitives/Vec.ts +51 -24
- package/src/lib/primitives/geometry/Arc2d.ts +10 -15
- package/src/lib/primitives/geometry/Circle2d.ts +40 -2
- package/src/lib/primitives/geometry/CubicBezier2d.ts +10 -0
- package/src/lib/primitives/geometry/CubicSpline2d.ts +10 -0
- package/src/lib/primitives/geometry/Edge2d.ts +41 -18
- package/src/lib/primitives/geometry/Ellipse2d.ts +14 -1
- package/src/lib/primitives/geometry/Polyline2d.ts +60 -12
- package/src/lib/primitives/geometry/Stadium2d.ts +14 -1
- package/src/lib/primitives/geometry/geometry.bench.ts +179 -0
- package/src/lib/primitives/intersect.ts +27 -27
- package/src/lib/primitives/utils.ts +4 -4
- package/src/lib/test/TestEditor.ts +1 -0
- package/src/lib/utils/browserCanvasMaxSize.ts +4 -2
- package/src/lib/utils/dom.ts +34 -2
- package/src/version.ts +3 -3
|
@@ -63,9 +63,9 @@ beforeEach(() => {
|
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
describe('getExportDefaultBounds', () => {
|
|
66
|
-
it('returns null when no rendering shapes provided', () => {
|
|
66
|
+
it('returns null box when no rendering shapes provided', () => {
|
|
67
67
|
const result = getExportDefaultBounds(editor, [], 32, null)
|
|
68
|
-
expect(result).toBeNull()
|
|
68
|
+
expect(result.box).toBeNull()
|
|
69
69
|
})
|
|
70
70
|
|
|
71
71
|
it('returns bounds for single shape with padding', () => {
|
|
@@ -83,12 +83,13 @@ describe('getExportDefaultBounds', () => {
|
|
|
83
83
|
|
|
84
84
|
const result = getExportDefaultBounds(editor, [testShape], 32, null)
|
|
85
85
|
|
|
86
|
-
expect(result).toBeInstanceOf(Box)
|
|
86
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
87
|
+
expect(result.paddingApplied).toBe(true)
|
|
87
88
|
// Bounds should include 32px padding on all sides
|
|
88
|
-
expect(result?.x).toBe(10 - 32) // -22
|
|
89
|
-
expect(result?.y).toBe(20 - 32) // -12
|
|
90
|
-
expect(result?.w).toBe(100 + 64) // 164 (32px on each side)
|
|
91
|
-
expect(result?.h).toBe(80 + 64) // 144 (32px on each side)
|
|
89
|
+
expect(result.box?.x).toBe(10 - 32) // -22
|
|
90
|
+
expect(result.box?.y).toBe(20 - 32) // -12
|
|
91
|
+
expect(result.box?.w).toBe(100 + 64) // 164 (32px on each side)
|
|
92
|
+
expect(result.box?.h).toBe(80 + 64) // 144 (32px on each side)
|
|
92
93
|
})
|
|
93
94
|
|
|
94
95
|
it('returns union bounds for multiple shapes with padding', () => {
|
|
@@ -116,12 +117,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
116
117
|
|
|
117
118
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
118
119
|
|
|
119
|
-
expect(result).toBeInstanceOf(Box)
|
|
120
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
120
121
|
// Raw bounds would be (0,0) to (90,90), with 32px padding on all sides
|
|
121
|
-
expect(result?.x).toBe(0 - 32) // -32
|
|
122
|
-
expect(result?.y).toBe(0 - 32) // -32
|
|
123
|
-
expect(result?.w).toBe(90 + 64) // 154
|
|
124
|
-
expect(result?.h).toBe(90 + 64) // 154
|
|
122
|
+
expect(result.box?.x).toBe(0 - 32) // -32
|
|
123
|
+
expect(result.box?.y).toBe(0 - 32) // -32
|
|
124
|
+
expect(result.box?.w).toBe(90 + 64) // 154
|
|
125
|
+
expect(result.box?.h).toBe(90 + 64) // 154
|
|
125
126
|
})
|
|
126
127
|
|
|
127
128
|
it('handles shapes with transforms correctly', () => {
|
|
@@ -142,10 +143,10 @@ describe('getExportDefaultBounds', () => {
|
|
|
142
143
|
|
|
143
144
|
const result = getExportDefaultBounds(editor, [testShape], 32, null)
|
|
144
145
|
|
|
145
|
-
expect(result).toBeInstanceOf(Box)
|
|
146
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
146
147
|
// The rotated shape should have expanded bounds, plus padding
|
|
147
|
-
expect(result!.w).toBeGreaterThan(50 + 64)
|
|
148
|
-
expect(result!.h).toBeGreaterThan(40 + 64)
|
|
148
|
+
expect(result.box!.w).toBeGreaterThan(50 + 64)
|
|
149
|
+
expect(result.box!.h).toBeGreaterThan(40 + 64)
|
|
149
150
|
})
|
|
150
151
|
|
|
151
152
|
it('handles multiple overlapping shapes correctly', () => {
|
|
@@ -183,12 +184,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
183
184
|
|
|
184
185
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
185
186
|
|
|
186
|
-
expect(result).toBeInstanceOf(Box)
|
|
187
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
187
188
|
// Raw bounds would be (0,0) to (60,60), with 32px padding on all sides
|
|
188
|
-
expect(result?.x).toBe(0 - 32) // -32
|
|
189
|
-
expect(result?.y).toBe(0 - 32) // -32
|
|
190
|
-
expect(result?.w).toBe(60 + 64) // 124 (32px on each side)
|
|
191
|
-
expect(result?.h).toBe(60 + 64) // 124 (32px on each side)
|
|
189
|
+
expect(result.box?.x).toBe(0 - 32) // -32
|
|
190
|
+
expect(result.box?.y).toBe(0 - 32) // -32
|
|
191
|
+
expect(result.box?.w).toBe(60 + 64) // 124 (32px on each side)
|
|
192
|
+
expect(result.box?.h).toBe(60 + 64) // 124 (32px on each side)
|
|
192
193
|
})
|
|
193
194
|
|
|
194
195
|
it('handles complex geometry with multiple shapes', () => {
|
|
@@ -226,17 +227,17 @@ describe('getExportDefaultBounds', () => {
|
|
|
226
227
|
|
|
227
228
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
228
229
|
|
|
229
|
-
expect(result).toBeInstanceOf(Box)
|
|
230
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
230
231
|
|
|
231
232
|
// The bounds should encompass:
|
|
232
233
|
// - shape1: (0, 0) to (50, 50)
|
|
233
234
|
// - shape2: (100, 100) to (160, 140)
|
|
234
235
|
// - shape3: (200, 50) to (240, 130)
|
|
235
236
|
// Raw total bounds: (0, 0) to (240, 140), with 32px padding on all sides
|
|
236
|
-
expect(result!.x).toBe(0 - 32) // -32 (leftmost edge with padding)
|
|
237
|
-
expect(result!.y).toBe(0 - 32) // -32 (topmost edge with padding)
|
|
238
|
-
expect(result!.w).toBe(240 + 64) // 304 (width + 32px on each side)
|
|
239
|
-
expect(result!.h).toBe(140 + 64) // 204 (height + 32px on each side)
|
|
237
|
+
expect(result.box!.x).toBe(0 - 32) // -32 (leftmost edge with padding)
|
|
238
|
+
expect(result.box!.y).toBe(0 - 32) // -32 (topmost edge with padding)
|
|
239
|
+
expect(result.box!.w).toBe(240 + 64) // 304 (width + 32px on each side)
|
|
240
|
+
expect(result.box!.h).toBe(140 + 64) // 204 (height + 32px on each side)
|
|
240
241
|
})
|
|
241
242
|
|
|
242
243
|
it('handles empty rendering shapes array after filtering', () => {
|
|
@@ -253,7 +254,7 @@ describe('getExportDefaultBounds', () => {
|
|
|
253
254
|
// Pass empty array to simulate filtered out shapes
|
|
254
255
|
const result = getExportDefaultBounds(editor, [], 32, null)
|
|
255
256
|
|
|
256
|
-
expect(result).toBeNull()
|
|
257
|
+
expect(result.box).toBeNull()
|
|
257
258
|
})
|
|
258
259
|
|
|
259
260
|
it('does not apply padding when exporting single frame shape', () => {
|
|
@@ -272,12 +273,13 @@ describe('getExportDefaultBounds', () => {
|
|
|
272
273
|
// Pass the shape ID as singleFrameShapeId to simulate single frame export
|
|
273
274
|
const result = getExportDefaultBounds(editor, [testShape], 32, shapeId)
|
|
274
275
|
|
|
275
|
-
expect(result).toBeInstanceOf(Box)
|
|
276
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
277
|
+
expect(result.paddingApplied).toBe(false)
|
|
276
278
|
// No padding should be applied
|
|
277
|
-
expect(result?.x).toBe(10)
|
|
278
|
-
expect(result?.y).toBe(20)
|
|
279
|
-
expect(result?.w).toBe(100)
|
|
280
|
-
expect(result?.h).toBe(80)
|
|
279
|
+
expect(result.box?.x).toBe(10)
|
|
280
|
+
expect(result.box?.y).toBe(20)
|
|
281
|
+
expect(result.box?.w).toBe(100)
|
|
282
|
+
expect(result.box?.h).toBe(80)
|
|
281
283
|
})
|
|
282
284
|
|
|
283
285
|
describe('isExportBoundsContainer behavior', () => {
|
|
@@ -306,12 +308,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
306
308
|
|
|
307
309
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
308
310
|
|
|
309
|
-
expect(result).toBeInstanceOf(Box)
|
|
311
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
310
312
|
// Raw bounds: (0,0) to (50,50), with padding
|
|
311
|
-
expect(result?.x).toBe(-32)
|
|
312
|
-
expect(result?.y).toBe(-32)
|
|
313
|
-
expect(result?.w).toBe(50 + 64) // 114
|
|
314
|
-
expect(result?.h).toBe(50 + 64) // 114
|
|
313
|
+
expect(result.box?.x).toBe(-32)
|
|
314
|
+
expect(result.box?.y).toBe(-32)
|
|
315
|
+
expect(result.box?.w).toBe(50 + 64) // 114
|
|
316
|
+
expect(result.box?.h).toBe(50 + 64) // 114
|
|
315
317
|
})
|
|
316
318
|
|
|
317
319
|
it('skips padding when container shape contains all other shapes', () => {
|
|
@@ -352,12 +354,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
352
354
|
|
|
353
355
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
354
356
|
|
|
355
|
-
expect(result).toBeInstanceOf(Box)
|
|
357
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
356
358
|
// Should use container bounds without padding: (0,0) to (100,100)
|
|
357
|
-
expect(result?.x).toBe(0)
|
|
358
|
-
expect(result?.y).toBe(0)
|
|
359
|
-
expect(result?.w).toBe(100)
|
|
360
|
-
expect(result?.h).toBe(100)
|
|
359
|
+
expect(result.box?.x).toBe(0)
|
|
360
|
+
expect(result.box?.y).toBe(0)
|
|
361
|
+
expect(result.box?.w).toBe(100)
|
|
362
|
+
expect(result.box?.h).toBe(100)
|
|
361
363
|
})
|
|
362
364
|
|
|
363
365
|
it('applies padding when container does not contain all shapes', () => {
|
|
@@ -399,12 +401,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
399
401
|
|
|
400
402
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
401
403
|
|
|
402
|
-
expect(result).toBeInstanceOf(Box)
|
|
404
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
403
405
|
// Total bounds: (0,0) to (100,100), with padding applied
|
|
404
|
-
expect(result?.x).toBe(-32)
|
|
405
|
-
expect(result?.y).toBe(-32)
|
|
406
|
-
expect(result?.w).toBe(100 + 64) // 164
|
|
407
|
-
expect(result?.h).toBe(100 + 64) // 164
|
|
406
|
+
expect(result.box?.x).toBe(-32)
|
|
407
|
+
expect(result.box?.y).toBe(-32)
|
|
408
|
+
expect(result.box?.w).toBe(100 + 64) // 164
|
|
409
|
+
expect(result.box?.h).toBe(100 + 64) // 164
|
|
408
410
|
})
|
|
409
411
|
|
|
410
412
|
it('works with multiple containers where one contains all', () => {
|
|
@@ -446,12 +448,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
446
448
|
|
|
447
449
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
448
450
|
|
|
449
|
-
expect(result).toBeInstanceOf(Box)
|
|
451
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
450
452
|
// Should use the large container's bounds without padding
|
|
451
|
-
expect(result?.x).toBe(0)
|
|
452
|
-
expect(result?.y).toBe(0)
|
|
453
|
-
expect(result?.w).toBe(100)
|
|
454
|
-
expect(result?.h).toBe(100)
|
|
453
|
+
expect(result.box?.x).toBe(0)
|
|
454
|
+
expect(result.box?.y).toBe(0)
|
|
455
|
+
expect(result.box?.w).toBe(100)
|
|
456
|
+
expect(result.box?.h).toBe(100)
|
|
455
457
|
})
|
|
456
458
|
|
|
457
459
|
it('container behavior is overridden by single frame shape', () => {
|
|
@@ -482,12 +484,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
482
484
|
// Single frame shape logic takes precedence over container logic
|
|
483
485
|
const result = getExportDefaultBounds(editor, testShapes, 32, containerId)
|
|
484
486
|
|
|
485
|
-
expect(result).toBeInstanceOf(Box)
|
|
487
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
486
488
|
// Should use total bounds without padding (single frame overrides container)
|
|
487
|
-
expect(result?.x).toBe(0)
|
|
488
|
-
expect(result?.y).toBe(0)
|
|
489
|
-
expect(result?.w).toBe(100)
|
|
490
|
-
expect(result?.h).toBe(100)
|
|
489
|
+
expect(result.box?.x).toBe(0)
|
|
490
|
+
expect(result.box?.y).toBe(0)
|
|
491
|
+
expect(result.box?.w).toBe(100)
|
|
492
|
+
expect(result.box?.h).toBe(100)
|
|
491
493
|
})
|
|
492
494
|
|
|
493
495
|
it('handles containers with inner shapes correctly', () => {
|
|
@@ -517,13 +519,13 @@ describe('getExportDefaultBounds', () => {
|
|
|
517
519
|
|
|
518
520
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
519
521
|
|
|
520
|
-
expect(result).toBeInstanceOf(Box)
|
|
522
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
521
523
|
// Container (0,0,200,120) should contain inner shape bounds,
|
|
522
524
|
// so no padding should be applied
|
|
523
|
-
expect(result?.x).toBe(0)
|
|
524
|
-
expect(result?.y).toBe(0)
|
|
525
|
-
expect(result?.w).toBe(200)
|
|
526
|
-
expect(result?.h).toBe(120)
|
|
525
|
+
expect(result.box?.x).toBe(0)
|
|
526
|
+
expect(result.box?.y).toBe(0)
|
|
527
|
+
expect(result.box?.w).toBe(200)
|
|
528
|
+
expect(result.box?.h).toBe(120)
|
|
527
529
|
})
|
|
528
530
|
|
|
529
531
|
it('handles order sensitivity - container processed first', () => {
|
|
@@ -553,12 +555,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
553
555
|
|
|
554
556
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
555
557
|
|
|
556
|
-
expect(result).toBeInstanceOf(Box)
|
|
558
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
557
559
|
// Container should contain regular shape, no padding applied
|
|
558
|
-
expect(result?.x).toBe(0)
|
|
559
|
-
expect(result?.y).toBe(0)
|
|
560
|
-
expect(result?.w).toBe(100)
|
|
561
|
-
expect(result?.h).toBe(100)
|
|
560
|
+
expect(result.box?.x).toBe(0)
|
|
561
|
+
expect(result.box?.y).toBe(0)
|
|
562
|
+
expect(result.box?.w).toBe(100)
|
|
563
|
+
expect(result.box?.h).toBe(100)
|
|
562
564
|
})
|
|
563
565
|
|
|
564
566
|
it('handles order sensitivity - regular shape processed first', () => {
|
|
@@ -588,12 +590,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
588
590
|
|
|
589
591
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
590
592
|
|
|
591
|
-
expect(result).toBeInstanceOf(Box)
|
|
593
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
592
594
|
// Container should still contain regular shape, no padding applied
|
|
593
|
-
expect(result?.x).toBe(0)
|
|
594
|
-
expect(result?.y).toBe(0)
|
|
595
|
-
expect(result?.w).toBe(100)
|
|
596
|
-
expect(result?.h).toBe(100)
|
|
595
|
+
expect(result.box?.x).toBe(0)
|
|
596
|
+
expect(result.box?.y).toBe(0)
|
|
597
|
+
expect(result.box?.w).toBe(100)
|
|
598
|
+
expect(result.box?.h).toBe(100)
|
|
597
599
|
})
|
|
598
600
|
|
|
599
601
|
it('multiple containers - only one that contains all others skips padding', () => {
|
|
@@ -635,12 +637,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
635
637
|
|
|
636
638
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
637
639
|
|
|
638
|
-
expect(result).toBeInstanceOf(Box)
|
|
640
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
639
641
|
// Large container contains everything (including small container), no padding
|
|
640
|
-
expect(result?.x).toBe(0)
|
|
641
|
-
expect(result?.y).toBe(0)
|
|
642
|
-
expect(result?.w).toBe(100)
|
|
643
|
-
expect(result?.h).toBe(100)
|
|
642
|
+
expect(result.box?.x).toBe(0)
|
|
643
|
+
expect(result.box?.y).toBe(0)
|
|
644
|
+
expect(result.box?.w).toBe(100)
|
|
645
|
+
expect(result.box?.h).toBe(100)
|
|
644
646
|
})
|
|
645
647
|
|
|
646
648
|
it('multiple containers - none contains all others, padding applied', () => {
|
|
@@ -692,13 +694,13 @@ describe('getExportDefaultBounds', () => {
|
|
|
692
694
|
|
|
693
695
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
694
696
|
|
|
695
|
-
expect(result).toBeInstanceOf(Box)
|
|
697
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
696
698
|
// No single container contains all others, padding should be applied
|
|
697
699
|
// Total bounds: (0,0) to (100,100), with padding
|
|
698
|
-
expect(result?.x).toBe(-32)
|
|
699
|
-
expect(result?.y).toBe(-32)
|
|
700
|
-
expect(result?.w).toBe(100 + 64) // 164
|
|
701
|
-
expect(result?.h).toBe(100 + 64) // 164
|
|
700
|
+
expect(result.box?.x).toBe(-32)
|
|
701
|
+
expect(result.box?.y).toBe(-32)
|
|
702
|
+
expect(result.box?.w).toBe(100 + 64) // 164
|
|
703
|
+
expect(result.box?.h).toBe(100 + 64) // 164
|
|
702
704
|
})
|
|
703
705
|
|
|
704
706
|
it('container covers most but not all shapes - padding applied', () => {
|
|
@@ -740,13 +742,13 @@ describe('getExportDefaultBounds', () => {
|
|
|
740
742
|
|
|
741
743
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
742
744
|
|
|
743
|
-
expect(result).toBeInstanceOf(Box)
|
|
745
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
744
746
|
// Container doesn't contain all shapes, padding applied
|
|
745
747
|
// Total bounds: (0,0) to (90,90), with padding
|
|
746
|
-
expect(result?.x).toBe(-32)
|
|
747
|
-
expect(result?.y).toBe(-32)
|
|
748
|
-
expect(result?.w).toBe(90 + 64) // 154
|
|
749
|
-
expect(result?.h).toBe(90 + 64) // 154
|
|
748
|
+
expect(result.box?.x).toBe(-32)
|
|
749
|
+
expect(result.box?.y).toBe(-32)
|
|
750
|
+
expect(result.box?.w).toBe(90 + 64) // 154
|
|
751
|
+
expect(result.box?.h).toBe(90 + 64) // 154
|
|
750
752
|
})
|
|
751
753
|
|
|
752
754
|
it('nested containers - inner container processed first', () => {
|
|
@@ -788,12 +790,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
788
790
|
|
|
789
791
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
790
792
|
|
|
791
|
-
expect(result).toBeInstanceOf(Box)
|
|
793
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
792
794
|
// Outer container contains everything, should use outer bounds without padding
|
|
793
|
-
expect(result?.x).toBe(0)
|
|
794
|
-
expect(result?.y).toBe(0)
|
|
795
|
-
expect(result?.w).toBe(100)
|
|
796
|
-
expect(result?.h).toBe(100)
|
|
795
|
+
expect(result.box?.x).toBe(0)
|
|
796
|
+
expect(result.box?.y).toBe(0)
|
|
797
|
+
expect(result.box?.w).toBe(100)
|
|
798
|
+
expect(result.box?.h).toBe(100)
|
|
797
799
|
})
|
|
798
800
|
|
|
799
801
|
it('container-only shapes should not skip padding', () => {
|
|
@@ -822,13 +824,13 @@ describe('getExportDefaultBounds', () => {
|
|
|
822
824
|
|
|
823
825
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
824
826
|
|
|
825
|
-
expect(result).toBeInstanceOf(Box)
|
|
827
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
826
828
|
// Neither container fully contains the other, padding should be applied
|
|
827
829
|
// Total bounds: (0,0) to (80,80), with padding
|
|
828
|
-
expect(result?.x).toBe(-32)
|
|
829
|
-
expect(result?.y).toBe(-32)
|
|
830
|
-
expect(result?.w).toBe(80 + 64) // 144
|
|
831
|
-
expect(result?.h).toBe(80 + 64) // 144
|
|
830
|
+
expect(result.box?.x).toBe(-32)
|
|
831
|
+
expect(result.box?.y).toBe(-32)
|
|
832
|
+
expect(result.box?.w).toBe(80 + 64) // 144
|
|
833
|
+
expect(result.box?.h).toBe(80 + 64) // 144
|
|
832
834
|
})
|
|
833
835
|
|
|
834
836
|
it('single container with only itself skips padding', () => {
|
|
@@ -848,12 +850,12 @@ describe('getExportDefaultBounds', () => {
|
|
|
848
850
|
|
|
849
851
|
const result = getExportDefaultBounds(editor, testShapes, 32, null)
|
|
850
852
|
|
|
851
|
-
expect(result).toBeInstanceOf(Box)
|
|
853
|
+
expect(result.box).toBeInstanceOf(Box)
|
|
852
854
|
// Single container should skip padding (it trivially contains "all other shapes")
|
|
853
|
-
expect(result?.x).toBe(10)
|
|
854
|
-
expect(result?.y).toBe(20)
|
|
855
|
-
expect(result?.w).toBe(100)
|
|
856
|
-
expect(result?.h).toBe(80)
|
|
855
|
+
expect(result.box?.x).toBe(10)
|
|
856
|
+
expect(result.box?.y).toBe(20)
|
|
857
|
+
expect(result.box?.w).toBe(100)
|
|
858
|
+
expect(result.box?.h).toBe(80)
|
|
857
859
|
})
|
|
858
860
|
})
|
|
859
861
|
})
|
|
@@ -37,16 +37,23 @@ import { Mat } from '../primitives/Mat'
|
|
|
37
37
|
import { ExportDelay } from './ExportDelay'
|
|
38
38
|
|
|
39
39
|
export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportOptions = {}) {
|
|
40
|
-
|
|
40
|
+
const editorDocument = editor.getContainerDocument()
|
|
41
|
+
if (!editorDocument) throw Error('No document')
|
|
41
42
|
|
|
42
43
|
const {
|
|
43
44
|
scale = 1,
|
|
44
45
|
// should we include the background in the export? or is it transparent?
|
|
45
46
|
background = editor.getInstanceState().exportBackground,
|
|
46
|
-
padding = editor.options.defaultSvgPadding,
|
|
47
47
|
preserveAspectRatio,
|
|
48
48
|
} = opts
|
|
49
49
|
|
|
50
|
+
// Resolve the padding mode:
|
|
51
|
+
// - 'auto' (or undefined): render with default padding, then trim to actual visual content
|
|
52
|
+
// - number: fixed padding, no trimming
|
|
53
|
+
const isAutoTrim = typeof opts.padding !== 'number'
|
|
54
|
+
const renderPadding =
|
|
55
|
+
typeof opts.padding === 'number' ? opts.padding : editor.options.defaultSvgPadding
|
|
56
|
+
|
|
50
57
|
const isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode()
|
|
51
58
|
|
|
52
59
|
// ---Figure out which shapes we need to include
|
|
@@ -60,21 +67,36 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
|
|
|
60
67
|
ids.length === 1 && editor.isShapeOfType(editor.getShape(ids[0])!, 'frame') ? ids[0] : null
|
|
61
68
|
|
|
62
69
|
let bbox: null | Box = null
|
|
70
|
+
let paddingWasApplied = false
|
|
63
71
|
if (opts.bounds) {
|
|
64
|
-
|
|
72
|
+
// Explicit bounds: use exact bounds when auto, expand by padding when fixed
|
|
73
|
+
bbox = isAutoTrim ? opts.bounds.clone() : opts.bounds.clone().expandBy(renderPadding)
|
|
65
74
|
} else {
|
|
66
|
-
|
|
75
|
+
const result = getExportDefaultBounds(
|
|
76
|
+
editor,
|
|
77
|
+
renderingShapes,
|
|
78
|
+
renderPadding,
|
|
79
|
+
singleFrameShapeId
|
|
80
|
+
)
|
|
81
|
+
bbox = result.box
|
|
82
|
+
paddingWasApplied = result.paddingApplied
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
// no unmasked shapes to export
|
|
70
86
|
if (!bbox) return
|
|
71
87
|
|
|
88
|
+
// When auto-trim is active and padding was applied by getExportDefaultBounds,
|
|
89
|
+
// the padding region is trimmable: exports will scan pixels from each edge inward
|
|
90
|
+
// and trim to the actual visual content bounds. This ensures visual overflow
|
|
91
|
+
// (strokes, arrowheads) is captured without unnecessary whitespace.
|
|
92
|
+
const trimPadding = isAutoTrim && paddingWasApplied ? renderPadding : 0
|
|
93
|
+
|
|
72
94
|
// We want the svg image to be BIGGER THAN USUAL to account for image quality
|
|
73
95
|
const w = bbox.width * scale
|
|
74
96
|
const h = bbox.height * scale
|
|
75
97
|
|
|
76
98
|
try {
|
|
77
|
-
|
|
99
|
+
editorDocument.body.focus?.() // weird but necessary
|
|
78
100
|
} catch {
|
|
79
101
|
// not implemented
|
|
80
102
|
}
|
|
@@ -102,7 +124,7 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
|
|
|
102
124
|
</SvgExport>
|
|
103
125
|
)
|
|
104
126
|
|
|
105
|
-
return { jsx: svg, width: w, height: h, exportDelay }
|
|
127
|
+
return { jsx: svg, width: w, height: h, exportDelay, trimPadding }
|
|
106
128
|
}
|
|
107
129
|
|
|
108
130
|
/**
|
|
@@ -126,7 +148,7 @@ export function getExportDefaultBounds(
|
|
|
126
148
|
renderingShapes: TLRenderingShape[],
|
|
127
149
|
padding: number,
|
|
128
150
|
singleFrameShapeId: TLShapeId | null
|
|
129
|
-
) {
|
|
151
|
+
): { box: Box; paddingApplied: boolean } | { box: null; paddingApplied: false } {
|
|
130
152
|
let isBoundedByContainer = false
|
|
131
153
|
let bbox: null | Box = null
|
|
132
154
|
|
|
@@ -162,16 +184,17 @@ export function getExportDefaultBounds(
|
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
// No unmasked shapes to export
|
|
165
|
-
if (!bbox) return null
|
|
187
|
+
if (!bbox) return { box: null, paddingApplied: false }
|
|
166
188
|
|
|
167
189
|
// Only apply padding if:
|
|
168
190
|
// - Not exporting a single frame (frames have their own padding rules)
|
|
169
191
|
// - Not bounded by a container (containers define their own bounds precisely)
|
|
170
|
-
|
|
192
|
+
const paddingApplied = !singleFrameShapeId && !isBoundedByContainer
|
|
193
|
+
if (paddingApplied) {
|
|
171
194
|
bbox.expandBy(padding)
|
|
172
195
|
}
|
|
173
196
|
|
|
174
|
-
return bbox
|
|
197
|
+
return { box: bbox, paddingApplied }
|
|
175
198
|
}
|
|
176
199
|
|
|
177
200
|
function SvgExport({
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { atom } from '@tldraw/state'
|
|
2
|
+
import { getGlobalWindow } from '../utils/dom'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* An object that contains information about the current device and environment.
|
|
@@ -27,7 +28,7 @@ if (typeof window !== 'undefined') {
|
|
|
27
28
|
tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
|
|
28
29
|
tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
|
|
29
30
|
tlenv.isAndroid = /android/i.test(navigator.userAgent)
|
|
30
|
-
tlenv.isDarwin =
|
|
31
|
+
tlenv.isDarwin = getGlobalWindow().navigator.userAgent.toLowerCase().indexOf('mac') > -1
|
|
31
32
|
}
|
|
32
33
|
tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
|
|
33
34
|
isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
|
|
@@ -48,7 +49,7 @@ const tlenvReactive = atom('tlenvReactive', {
|
|
|
48
49
|
})
|
|
49
50
|
|
|
50
51
|
if (typeof window !== 'undefined' && !isForcedFinePointer) {
|
|
51
|
-
const mql =
|
|
52
|
+
const mql = getGlobalWindow().matchMedia && getGlobalWindow().matchMedia('(any-pointer: coarse)')
|
|
52
53
|
|
|
53
54
|
const isCurrentCoarsePointer = () => tlenvReactive.__unsafe__getWithoutCapture().isCoarsePointer
|
|
54
55
|
|
|
@@ -66,7 +67,7 @@ if (typeof window !== 'undefined' && !isForcedFinePointer) {
|
|
|
66
67
|
|
|
67
68
|
// 2. Also update the coarse pointer state when a pointer down event occurs. We need `capture: true`
|
|
68
69
|
// here because the tldraw component itself stops propagation on pointer events it receives.
|
|
69
|
-
|
|
70
|
+
getGlobalWindow().addEventListener(
|
|
70
71
|
'pointerdown',
|
|
71
72
|
(e: PointerEvent) => {
|
|
72
73
|
// when the user interacts with a mouse, we assume they have a fine pointer.
|
|
@@ -13,7 +13,7 @@ import { useEditor } from './useEditor'
|
|
|
13
13
|
|
|
14
14
|
export function useCanvasEvents() {
|
|
15
15
|
const editor = useEditor()
|
|
16
|
-
const ownerDocument = editor.
|
|
16
|
+
const ownerDocument = editor.getContainerDocument()
|
|
17
17
|
const currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])
|
|
18
18
|
|
|
19
19
|
const events = useMemo(
|
|
@@ -80,8 +80,7 @@ export function useCanvasEvents() {
|
|
|
80
80
|
function onTouchEnd(e: React.TouchEvent) {
|
|
81
81
|
if (editor.wasEventAlreadyHandled(e)) return
|
|
82
82
|
editor.markEventAsHandled(e)
|
|
83
|
-
|
|
84
|
-
if (!(e.target instanceof HTMLElement)) return
|
|
83
|
+
if (!(e.target instanceof editor.getContainerWindow().HTMLElement)) return
|
|
85
84
|
|
|
86
85
|
const editingShapeId = editor.getEditingShapeId()
|
|
87
86
|
if (
|
|
@@ -46,7 +46,8 @@ export function useDocumentEvents() {
|
|
|
46
46
|
}, [container])
|
|
47
47
|
|
|
48
48
|
useEffect(() => {
|
|
49
|
-
|
|
49
|
+
const win = editor.getContainerWindow()
|
|
50
|
+
if (!('matchMedia' in win)) return
|
|
50
51
|
|
|
51
52
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#monitoring_screen_resolution_or_zoom_level_changes
|
|
52
53
|
let remove: (() => void) | null = null
|
|
@@ -54,8 +55,8 @@ export function useDocumentEvents() {
|
|
|
54
55
|
if (remove != null) {
|
|
55
56
|
remove()
|
|
56
57
|
}
|
|
57
|
-
const mqString = `(resolution: ${
|
|
58
|
-
const media = matchMedia(mqString)
|
|
58
|
+
const mqString = `(resolution: ${win.devicePixelRatio}dppx)`
|
|
59
|
+
const media = win.matchMedia(mqString)
|
|
59
60
|
// Safari only started supporting `addEventListener('change',...) in version 14
|
|
60
61
|
// https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event
|
|
61
62
|
const safariCb = (ev: any) => {
|
|
@@ -79,7 +80,7 @@ export function useDocumentEvents() {
|
|
|
79
80
|
media.removeListener(safariCb)
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
|
-
editor.updateInstanceState({ devicePixelRatio:
|
|
83
|
+
editor.updateInstanceState({ devicePixelRatio: win.devicePixelRatio })
|
|
83
84
|
}
|
|
84
85
|
updatePixelRatio()
|
|
85
86
|
return () => {
|
|
@@ -275,9 +276,10 @@ export function useDocumentEvents() {
|
|
|
275
276
|
|
|
276
277
|
container.addEventListener('wheel', handleWheel, { passive: false })
|
|
277
278
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
const ownerDoc = container.ownerDocument
|
|
280
|
+
ownerDoc.addEventListener('gesturestart', preventDefault)
|
|
281
|
+
ownerDoc.addEventListener('gesturechange', preventDefault)
|
|
282
|
+
ownerDoc.addEventListener('gestureend', preventDefault)
|
|
281
283
|
|
|
282
284
|
container.addEventListener('keydown', handleKeyDown)
|
|
283
285
|
container.addEventListener('keyup', handleKeyUp)
|
|
@@ -287,9 +289,9 @@ export function useDocumentEvents() {
|
|
|
287
289
|
|
|
288
290
|
container.removeEventListener('wheel', handleWheel)
|
|
289
291
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
292
|
+
ownerDoc.removeEventListener('gesturestart', preventDefault)
|
|
293
|
+
ownerDoc.removeEventListener('gesturechange', preventDefault)
|
|
294
|
+
ownerDoc.removeEventListener('gestureend', preventDefault)
|
|
293
295
|
|
|
294
296
|
container.removeEventListener('keydown', handleKeyDown)
|
|
295
297
|
container.removeEventListener('keyup', handleKeyUp)
|
|
@@ -298,5 +300,8 @@ export function useDocumentEvents() {
|
|
|
298
300
|
}
|
|
299
301
|
|
|
300
302
|
function areShortcutsDisabled(editor: Editor) {
|
|
301
|
-
return
|
|
303
|
+
return (
|
|
304
|
+
editor.menus.hasOpenMenus() ||
|
|
305
|
+
activeElementShouldCaptureKeys(true, editor.getContainerDocument())
|
|
306
|
+
)
|
|
302
307
|
}
|
|
@@ -15,14 +15,14 @@ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLE
|
|
|
15
15
|
|
|
16
16
|
if (!elm) return
|
|
17
17
|
|
|
18
|
+
const win = editor.getContainerWindow()
|
|
18
19
|
const handleEvent = (e: PointerEvent | TouchEvent) => {
|
|
19
|
-
if (e instanceof PointerEvent && e.pointerType === 'pen') {
|
|
20
|
+
if (e instanceof win.PointerEvent && e.pointerType === 'pen') {
|
|
20
21
|
editor.markEventAsHandled(e)
|
|
21
22
|
const { target } = e
|
|
22
23
|
|
|
23
|
-
// Allow events to propagate if the app is editing a shape, or if the event is occurring in a text area or input
|
|
24
24
|
if (
|
|
25
|
-
elementShouldCaptureKeys(target instanceof Element ? target : null, false) ||
|
|
25
|
+
elementShouldCaptureKeys(target instanceof win.Element ? target : null, false) ||
|
|
26
26
|
editor.isIn('select.editing_shape')
|
|
27
27
|
) {
|
|
28
28
|
return
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { throttle } from '@tldraw/utils'
|
|
2
2
|
import { useLayoutEffect } from 'react'
|
|
3
|
+
import { getOwnerWindow } from '../exports/domUtils'
|
|
3
4
|
import { useEditor } from './useEditor'
|
|
4
5
|
|
|
5
6
|
export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
|
|
@@ -21,7 +22,8 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
|
|
|
21
22
|
// Rather than running getClientRects on every frame, we'll
|
|
22
23
|
// run it once a second or when the window resizes.
|
|
23
24
|
const interval = editor.timers.setInterval(updateBounds, 1000)
|
|
24
|
-
|
|
25
|
+
const win = editor.getContainerWindow()
|
|
26
|
+
win.addEventListener('resize', updateBounds)
|
|
25
27
|
|
|
26
28
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
27
29
|
if (!entries[0].contentRect) return
|
|
@@ -42,7 +44,7 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
|
|
|
42
44
|
|
|
43
45
|
return () => {
|
|
44
46
|
clearInterval(interval)
|
|
45
|
-
|
|
47
|
+
win.removeEventListener('resize', updateBounds)
|
|
46
48
|
resizeObserver.disconnect()
|
|
47
49
|
scrollingParent?.removeEventListener('scroll', updateBounds)
|
|
48
50
|
updateBounds.cancel()
|
|
@@ -56,12 +58,14 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
|
|
|
56
58
|
* https://github.com/excalidraw/excalidraw/blob/48c3465b19f10ec755b3eb84e21a01a468e96e43/packages/excalidraw/utils.ts#L600
|
|
57
59
|
*/
|
|
58
60
|
const getNearestScrollableContainer = (element: HTMLElement): HTMLElement | Document => {
|
|
61
|
+
const doc = element.ownerDocument
|
|
62
|
+
const win = getOwnerWindow(element)
|
|
59
63
|
let parent = element.parentElement
|
|
60
64
|
while (parent) {
|
|
61
|
-
if (parent ===
|
|
62
|
-
return
|
|
65
|
+
if (parent === doc.body) {
|
|
66
|
+
return doc
|
|
63
67
|
}
|
|
64
|
-
const { overflowY } =
|
|
68
|
+
const { overflowY } = win.getComputedStyle(parent)
|
|
65
69
|
const hasScrollableContent = parent.scrollHeight > parent.clientHeight
|
|
66
70
|
if (
|
|
67
71
|
hasScrollableContent &&
|
|
@@ -71,5 +75,5 @@ const getNearestScrollableContainer = (element: HTMLElement): HTMLElement | Docu
|
|
|
71
75
|
}
|
|
72
76
|
parent = parent.parentElement
|
|
73
77
|
}
|
|
74
|
-
return
|
|
78
|
+
return doc
|
|
75
79
|
}
|