@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
|
@@ -172,8 +172,8 @@ export class HistoryManager<R extends UnknownRecord> {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
if (!didFindMark && toMark) {
|
|
175
|
-
//
|
|
176
|
-
|
|
175
|
+
// we didn't find the mark we were looking for — restore state and bail
|
|
176
|
+
this.pendingDiff.restore(pendingDiff)
|
|
177
177
|
return this
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -338,6 +338,11 @@ class PendingDiff<R extends UnknownRecord> {
|
|
|
338
338
|
return diff
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
restore(diff: RecordsDiff<R>) {
|
|
342
|
+
this.diff = diff
|
|
343
|
+
this.isEmptyAtom.set(isRecordsDiffEmpty(diff))
|
|
344
|
+
}
|
|
345
|
+
|
|
341
346
|
isEmpty() {
|
|
342
347
|
return this.isEmptyAtom.get()
|
|
343
348
|
}
|
|
@@ -58,17 +58,16 @@ const mockCreateElement = vi.fn(() => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
// Mock editor
|
|
61
|
+
const mockDocument = {
|
|
62
|
+
createElement: mockCreateElement,
|
|
63
|
+
}
|
|
61
64
|
const mockEditor = {
|
|
62
65
|
getContainer: vi.fn(() => ({
|
|
63
66
|
appendChild: vi.fn(),
|
|
64
67
|
})),
|
|
68
|
+
getContainerDocument: vi.fn(() => mockDocument),
|
|
65
69
|
} as unknown as Editor
|
|
66
70
|
|
|
67
|
-
// Setup global mocks
|
|
68
|
-
global.document = {
|
|
69
|
-
createElement: mockCreateElement,
|
|
70
|
-
} as any
|
|
71
|
-
|
|
72
71
|
global.Range = vi.fn(() => ({
|
|
73
72
|
setStart: vi.fn(),
|
|
74
73
|
setEnd: vi.fn(),
|
|
@@ -75,7 +75,7 @@ export class TextManager {
|
|
|
75
75
|
private elm: HTMLDivElement
|
|
76
76
|
|
|
77
77
|
constructor(public editor: Editor) {
|
|
78
|
-
const elm =
|
|
78
|
+
const elm = editor.getContainerDocument().createElement('div')
|
|
79
79
|
elm.classList.add('tl-text')
|
|
80
80
|
elm.classList.add('tl-text-measure')
|
|
81
81
|
elm.setAttribute('dir', 'auto')
|
|
@@ -111,7 +111,7 @@ export class TextManager {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
114
|
-
const div =
|
|
114
|
+
const div = this.editor.getContainerDocument().createElement('div')
|
|
115
115
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
116
116
|
return this.measureHtml(div.innerHTML, opts)
|
|
117
117
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { atom, computed } from '@tldraw/state'
|
|
2
2
|
import { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'
|
|
3
3
|
import { TLUser } from '../../../config/createTLUser'
|
|
4
|
+
import { getGlobalWindow } from '../../../utils/dom'
|
|
4
5
|
|
|
5
6
|
/** @public */
|
|
6
7
|
export class UserPreferencesManager {
|
|
@@ -13,9 +14,9 @@ export class UserPreferencesManager {
|
|
|
13
14
|
private readonly user: TLUser,
|
|
14
15
|
private readonly inferDarkMode: boolean
|
|
15
16
|
) {
|
|
16
|
-
if (typeof window === 'undefined' || !
|
|
17
|
+
if (typeof window === 'undefined' || !getGlobalWindow().matchMedia) return
|
|
17
18
|
|
|
18
|
-
const darkModeMediaQuery =
|
|
19
|
+
const darkModeMediaQuery = getGlobalWindow().matchMedia('(prefers-color-scheme: dark)')
|
|
19
20
|
if (darkModeMediaQuery?.matches) {
|
|
20
21
|
this.systemColorScheme.set('dark')
|
|
21
22
|
}
|
|
@@ -40,9 +40,15 @@ export interface TLSvgExportOptions {
|
|
|
40
40
|
background?: boolean
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* How much padding to include around the bounds of exports
|
|
43
|
+
* How much padding to include around the bounds of exports.
|
|
44
|
+
*
|
|
45
|
+
* - `'auto'` (default) — trim to visual content bounds, capturing overflow (like thick
|
|
46
|
+
* strokes or arrowheads) without extra whitespace.
|
|
47
|
+
* - `number` (e.g. `32`) — fixed padding in px. No trimming; overflow beyond the padding
|
|
48
|
+
* region is clipped.
|
|
49
|
+
* - `0` — no padding, no trimming, overflow is clipped.
|
|
44
50
|
*/
|
|
45
|
-
padding?: number
|
|
51
|
+
padding?: number | 'auto'
|
|
46
52
|
|
|
47
53
|
/**
|
|
48
54
|
* Should the export be rendered in dark mode (true) or light mode (false)? Defaults to the
|
|
@@ -11,7 +11,7 @@ export const SVG_EXPORT_CLASSNAME = 'tldraw-svg-export'
|
|
|
11
11
|
* SVG.
|
|
12
12
|
*
|
|
13
13
|
* It works in three steps:
|
|
14
|
-
* 1. `
|
|
14
|
+
* 1. `startFindingDocumentFontFaces` - this traverses the given document, finding all the
|
|
15
15
|
* stylesheets in use (including those imported via `@import` rules etc) and extracting the
|
|
16
16
|
* @font-face declarations from them.
|
|
17
17
|
* 2. `onFontFamilyValue` - as `StyleEmbedder` traverses the SVG, it will call this method with the
|
|
@@ -26,9 +26,9 @@ export class FontEmbedder {
|
|
|
26
26
|
private readonly fontFacesToEmbed = new Set<ParsedFontFace>()
|
|
27
27
|
private readonly pendingPromises: Promise<void>[] = []
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
startFindingDocumentFontFaces(doc: Document) {
|
|
30
30
|
assert(!this.fontFacesPromise, 'FontEmbedder already started')
|
|
31
|
-
this.fontFacesPromise =
|
|
31
|
+
this.fontFacesPromise = getDocumentFontFaces(doc)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
@bind onFontFamilyValue(fontFamilyValue: string) {
|
|
@@ -80,7 +80,8 @@ export class FontEmbedder {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
async function
|
|
83
|
+
async function getDocumentFontFaces(doc: Document) {
|
|
84
|
+
const win = doc.defaultView ?? globalThis
|
|
84
85
|
const fontFaces: (ParsedFontFace[] | Promise<ParsedFontFace[] | null>)[] = []
|
|
85
86
|
|
|
86
87
|
// In exportToSvg we add the exported node to the DOM temporarily.
|
|
@@ -88,7 +89,7 @@ async function getCurrentDocumentFontFaces() {
|
|
|
88
89
|
// DOM, when looking at document.styleSheets the number of nodes and stylesheets
|
|
89
90
|
// can grow unbounded (especially when using "Debug svg" and moving shapes around).
|
|
90
91
|
// To avoid this, we filter out the stylesheets that are part of the SVG export.
|
|
91
|
-
const styleSheetsWithoutSvgExports = Array.from(
|
|
92
|
+
const styleSheetsWithoutSvgExports = Array.from(doc.styleSheets).filter(
|
|
92
93
|
(styleSheet) =>
|
|
93
94
|
!(styleSheet.ownerNode as HTMLElement | null)?.closest(`.${SVG_EXPORT_CLASSNAME}`)
|
|
94
95
|
)
|
|
@@ -103,10 +104,10 @@ async function getCurrentDocumentFontFaces() {
|
|
|
103
104
|
|
|
104
105
|
if (cssRules) {
|
|
105
106
|
for (const rule of styleSheet.cssRules) {
|
|
106
|
-
if (rule instanceof CSSFontFaceRule) {
|
|
107
|
-
fontFaces.push(parseCssFontFaces(rule.cssText, styleSheet.href ??
|
|
108
|
-
} else if (rule instanceof CSSImportRule) {
|
|
109
|
-
const absoluteUrl = new URL(rule.href, rule.parentStyleSheet?.href ??
|
|
107
|
+
if (rule instanceof win.CSSFontFaceRule) {
|
|
108
|
+
fontFaces.push(parseCssFontFaces(rule.cssText, styleSheet.href ?? doc.baseURI))
|
|
109
|
+
} else if (rule instanceof win.CSSImportRule) {
|
|
110
|
+
const absoluteUrl = new URL(rule.href, rule.parentStyleSheet?.href ?? doc.baseURI)
|
|
110
111
|
fontFaces.push(fetchCssFontFaces(absoluteUrl.href))
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getComputedStyle,
|
|
7
7
|
getRenderedChildNodes,
|
|
8
8
|
getRenderedChildren,
|
|
9
|
+
isElement,
|
|
9
10
|
} from './domUtils'
|
|
10
11
|
import { resourceToDataUrl } from './fetchCache'
|
|
11
12
|
import { parseCssValueUrls, shouldIncludeCssProperty } from './parseCss'
|
|
@@ -49,7 +50,7 @@ export class StyleEmbedder {
|
|
|
49
50
|
{ shouldRespectDefaults = true, shouldSkipInheritedParentStyles = true }
|
|
50
51
|
) {
|
|
51
52
|
const defaultStyles = shouldRespectDefaults
|
|
52
|
-
? getDefaultStylesForTagName(element.tagName.toLowerCase())
|
|
53
|
+
? getDefaultStylesForTagName(element.ownerDocument, element.tagName.toLowerCase())
|
|
53
54
|
: NO_STYLES
|
|
54
55
|
|
|
55
56
|
const parentStyles = Object.assign({}, NO_STYLES) as Styles
|
|
@@ -116,14 +117,14 @@ export class StyleEmbedder {
|
|
|
116
117
|
const shadowRoot = element.shadowRoot
|
|
117
118
|
|
|
118
119
|
if (shadowRoot) {
|
|
119
|
-
const clonedCustomEl =
|
|
120
|
+
const clonedCustomEl = element.ownerDocument.createElement('div')
|
|
120
121
|
this.styles.set(clonedCustomEl, this.styles.get(element)!)
|
|
121
122
|
|
|
122
123
|
clonedCustomEl.setAttribute('data-tl-custom-element', element.tagName)
|
|
123
124
|
;(clonedParent ?? element.parentElement!).appendChild(clonedCustomEl)
|
|
124
125
|
|
|
125
126
|
for (const child of shadowRoot.childNodes) {
|
|
126
|
-
if (child
|
|
127
|
+
if (isElement(child)) {
|
|
127
128
|
visit(child, clonedCustomEl)
|
|
128
129
|
} else {
|
|
129
130
|
clonedCustomEl.appendChild(child.cloneNode(true))
|
|
@@ -144,7 +145,7 @@ export class StyleEmbedder {
|
|
|
144
145
|
clonedParent.appendChild(clonedEl)
|
|
145
146
|
|
|
146
147
|
for (const child of getRenderedChildNodes(element)) {
|
|
147
|
-
if (child
|
|
148
|
+
if (isElement(child)) {
|
|
148
149
|
visit(child, clonedEl)
|
|
149
150
|
} else {
|
|
150
151
|
clonedEl.appendChild(child.cloneNode(true))
|
|
@@ -295,36 +296,53 @@ function formatCss(style: ReadonlyStyles) {
|
|
|
295
296
|
// when we're figuring out the default values for a tag, we need read them from a separate document
|
|
296
297
|
// so they're not affected by the current document's styles
|
|
297
298
|
let defaultStyleFrame:
|
|
298
|
-
| {
|
|
299
|
+
| {
|
|
300
|
+
iframe: HTMLIFrameElement
|
|
301
|
+
foreignObject: SVGForeignObjectElement
|
|
302
|
+
document: Document
|
|
303
|
+
ownerDocument: Document
|
|
304
|
+
}
|
|
299
305
|
| undefined
|
|
300
306
|
const defaultStylesByTagName: Record<string, ReadonlyStyles> = {}
|
|
301
|
-
function getDefaultStyleFrame() {
|
|
302
|
-
if (!defaultStyleFrame) {
|
|
303
|
-
|
|
307
|
+
function getDefaultStyleFrame(ownerDoc: Document) {
|
|
308
|
+
if (!defaultStyleFrame || defaultStyleFrame.ownerDocument !== ownerDoc) {
|
|
309
|
+
destroyDefaultStyleFrame()
|
|
310
|
+
const frame = ownerDoc.createElement('iframe')
|
|
304
311
|
frame.style.display = 'none'
|
|
305
|
-
|
|
312
|
+
ownerDoc.body.appendChild(frame)
|
|
306
313
|
const frameDocument = assertExists(frame.contentDocument, 'frame must have a document')
|
|
307
|
-
const svg =
|
|
308
|
-
const foreignObject =
|
|
314
|
+
const svg = frameDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
315
|
+
const foreignObject = frameDocument.createElementNS(
|
|
316
|
+
'http://www.w3.org/2000/svg',
|
|
317
|
+
'foreignObject'
|
|
318
|
+
)
|
|
309
319
|
svg.appendChild(foreignObject)
|
|
310
320
|
frameDocument.body.appendChild(svg)
|
|
311
|
-
defaultStyleFrame = {
|
|
321
|
+
defaultStyleFrame = {
|
|
322
|
+
iframe: frame,
|
|
323
|
+
foreignObject,
|
|
324
|
+
document: frameDocument,
|
|
325
|
+
ownerDocument: ownerDoc,
|
|
326
|
+
}
|
|
312
327
|
}
|
|
313
328
|
return defaultStyleFrame
|
|
314
329
|
}
|
|
315
330
|
|
|
316
331
|
function destroyDefaultStyleFrame() {
|
|
317
332
|
if (defaultStyleFrame) {
|
|
318
|
-
|
|
333
|
+
defaultStyleFrame.iframe.remove()
|
|
319
334
|
defaultStyleFrame = undefined
|
|
320
335
|
}
|
|
336
|
+
for (const tagName in defaultStylesByTagName) {
|
|
337
|
+
delete defaultStylesByTagName[tagName]
|
|
338
|
+
}
|
|
321
339
|
}
|
|
322
340
|
|
|
323
341
|
const defaultStyleReadOptions: ReadStyleOpts = { defaultStyles: NO_STYLES, parentStyles: NO_STYLES }
|
|
324
|
-
function getDefaultStylesForTagName(tagName: string) {
|
|
342
|
+
function getDefaultStylesForTagName(ownerDoc: Document, tagName: string) {
|
|
325
343
|
let existing = defaultStylesByTagName[tagName]
|
|
326
344
|
if (!existing) {
|
|
327
|
-
const { foreignObject, document } = getDefaultStyleFrame()
|
|
345
|
+
const { foreignObject, document } = getDefaultStyleFrame(ownerDoc)
|
|
328
346
|
const element = document.createElement(tagName)
|
|
329
347
|
foreignObject.appendChild(element)
|
|
330
348
|
existing = element.computedStyleMap
|
|
@@ -22,6 +22,26 @@ export function* getRenderedChildren(node: Element) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** @internal */
|
|
26
|
+
export function getOwnerWindow(
|
|
27
|
+
nodeOrDocument: Node | Document | null | undefined
|
|
28
|
+
): Window & typeof globalThis {
|
|
29
|
+
if (!nodeOrDocument) return globalThis as Window & typeof globalThis
|
|
30
|
+
const doc = isDocument(nodeOrDocument) ? nodeOrDocument : nodeOrDocument.ownerDocument
|
|
31
|
+
return (doc?.defaultView ?? globalThis) as Window & typeof globalThis
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @internal */
|
|
35
|
+
export function getOwnerDocument(nodeOrDocument: Node | Document | null | undefined): Document {
|
|
36
|
+
if (!nodeOrDocument) return globalThis.document
|
|
37
|
+
if (isDocument(nodeOrDocument)) return nodeOrDocument
|
|
38
|
+
return nodeOrDocument.ownerDocument ?? globalThis.document
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isDocument(node: Node | Document): node is Document {
|
|
42
|
+
return node.nodeType === Node.DOCUMENT_NODE
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
function getWindow(node: Node) {
|
|
26
46
|
return node.ownerDocument?.defaultView ?? globalThis
|
|
27
47
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MediaHelpers } from '@tldraw/utils'
|
|
2
|
-
import { getRenderedChildren } from './domUtils'
|
|
2
|
+
import { getOwnerWindow, getRenderedChildren } from './domUtils'
|
|
3
3
|
import { resourceToDataUrl } from './fetchCache'
|
|
4
4
|
|
|
5
5
|
function copyAttrs(source: Element, target: Element) {
|
|
@@ -14,8 +14,12 @@ function replace(original: HTMLElement, replacement: HTMLElement) {
|
|
|
14
14
|
return replacement
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async function createImage(
|
|
18
|
-
|
|
17
|
+
async function createImage(
|
|
18
|
+
doc: Document,
|
|
19
|
+
dataUrl: string | null,
|
|
20
|
+
cloneAttributesFrom?: HTMLElement
|
|
21
|
+
) {
|
|
22
|
+
const image = doc.createElement('img')
|
|
19
23
|
|
|
20
24
|
if (cloneAttributesFrom) {
|
|
21
25
|
copyAttrs(cloneAttributesFrom, image)
|
|
@@ -34,52 +38,54 @@ async function createImage(dataUrl: string | null, cloneAttributesFrom?: HTMLEle
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
async function getCanvasReplacement(canvas: HTMLCanvasElement) {
|
|
41
|
+
const doc = canvas.ownerDocument
|
|
37
42
|
try {
|
|
38
43
|
const dataURL = canvas.toDataURL()
|
|
39
|
-
return await createImage(dataURL, canvas)
|
|
44
|
+
return await createImage(doc, dataURL, canvas)
|
|
40
45
|
} catch {
|
|
41
|
-
return await createImage(null, canvas)
|
|
46
|
+
return await createImage(doc, null, canvas)
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
async function getVideoReplacement(video: HTMLVideoElement) {
|
|
51
|
+
const doc = video.ownerDocument
|
|
46
52
|
try {
|
|
47
53
|
const dataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video)
|
|
48
|
-
return createImage(dataUrl, video)
|
|
54
|
+
return createImage(doc, dataUrl, video)
|
|
49
55
|
} catch (err) {
|
|
50
56
|
console.error('Could not get video frame', err)
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
if (video.poster) {
|
|
54
60
|
const dataUrl = await resourceToDataUrl(video.poster)
|
|
55
|
-
return createImage(dataUrl, video)
|
|
61
|
+
return createImage(doc, dataUrl, video)
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
return createImage(null, video)
|
|
64
|
+
return createImage(doc, null, video)
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
export async function embedMedia(node: HTMLElement) {
|
|
62
|
-
|
|
68
|
+
const win = getOwnerWindow(node)
|
|
69
|
+
if (node instanceof win.HTMLCanvasElement) {
|
|
63
70
|
return replace(node, await getCanvasReplacement(node))
|
|
64
|
-
} else if (node instanceof HTMLVideoElement) {
|
|
71
|
+
} else if (node instanceof win.HTMLVideoElement) {
|
|
65
72
|
return replace(node, await getVideoReplacement(node))
|
|
66
|
-
} else if (node instanceof HTMLImageElement) {
|
|
73
|
+
} else if (node instanceof win.HTMLImageElement) {
|
|
67
74
|
const src = node.currentSrc || node.src
|
|
68
75
|
const dataUrl = await resourceToDataUrl(src)
|
|
69
76
|
node.setAttribute('src', dataUrl ?? 'data:')
|
|
70
77
|
node.setAttribute('decoding', 'sync')
|
|
71
78
|
node.setAttribute('loading', 'eager')
|
|
72
79
|
try {
|
|
73
|
-
await node.decode()
|
|
80
|
+
await (node as HTMLImageElement).decode()
|
|
74
81
|
} catch {
|
|
75
82
|
// this is fine
|
|
76
83
|
}
|
|
77
84
|
return node
|
|
78
|
-
} else if (node instanceof HTMLInputElement) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
node.textContent = node.value
|
|
85
|
+
} else if (node instanceof win.HTMLInputElement) {
|
|
86
|
+
node.setAttribute('value', (node as HTMLInputElement).value)
|
|
87
|
+
} else if (node instanceof win.HTMLTextAreaElement) {
|
|
88
|
+
node.textContent = (node as HTMLTextAreaElement).value
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
await Promise.all(
|
|
@@ -4,10 +4,11 @@ import { flushSync } from 'react-dom'
|
|
|
4
4
|
import { createRoot } from 'react-dom/client'
|
|
5
5
|
import type { Editor } from '../editor/Editor'
|
|
6
6
|
import { TLSvgExportOptions } from '../editor/types/misc-types'
|
|
7
|
-
import {
|
|
8
|
-
import { StyleEmbedder } from './StyleEmbedder'
|
|
7
|
+
import { getOwnerWindow } from './domUtils'
|
|
9
8
|
import { embedMedia } from './embedMedia'
|
|
9
|
+
import { SVG_EXPORT_CLASSNAME } from './FontEmbedder'
|
|
10
10
|
import { getSvgJsx } from './getSvgJsx'
|
|
11
|
+
import { StyleEmbedder } from './StyleEmbedder'
|
|
11
12
|
|
|
12
13
|
let idCounter = 1
|
|
13
14
|
|
|
@@ -26,7 +27,7 @@ export async function exportToSvg(
|
|
|
26
27
|
// without this CSS and layout aren't computed correctly, which we need to make sure any
|
|
27
28
|
// <foreignObject> elements have their styles and content inlined correctly.
|
|
28
29
|
const container = editor.getContainer()
|
|
29
|
-
const renderTarget =
|
|
30
|
+
const renderTarget = container.ownerDocument.createElement('div')
|
|
30
31
|
renderTarget.className = SVG_EXPORT_CLASSNAME
|
|
31
32
|
// we hide the element visually, but we don't want it to be focusable or interactive in any way either
|
|
32
33
|
renderTarget.inert = true
|
|
@@ -60,7 +61,7 @@ export async function exportToSvg(
|
|
|
60
61
|
|
|
61
62
|
// Extract the rendered SVG element from the react root
|
|
62
63
|
const svg = renderTarget.firstElementChild
|
|
63
|
-
assert(svg instanceof SVGSVGElement, 'Expected an SVG element')
|
|
64
|
+
assert(svg instanceof getOwnerWindow(container).SVGSVGElement, 'Expected an SVG element')
|
|
64
65
|
|
|
65
66
|
// And apply any changes to <foreignObject> elements that we need to make. while we're in
|
|
66
67
|
// the document, these elements work exactly as we'd expect from other dom elements - they
|
|
@@ -70,7 +71,7 @@ export async function exportToSvg(
|
|
|
70
71
|
// apply any styles directly to the elements themselves.
|
|
71
72
|
await applyChangesToForeignObjects(svg)
|
|
72
73
|
|
|
73
|
-
return { svg, width: result.width, height: result.height }
|
|
74
|
+
return { svg, width: result.width, height: result.height, trimPadding: result.trimPadding }
|
|
74
75
|
} finally {
|
|
75
76
|
// eslint-disable-next-line no-restricted-globals
|
|
76
77
|
setTimeout(() => {
|
|
@@ -95,7 +96,7 @@ async function applyChangesToForeignObjects(svg: SVGSVGElement) {
|
|
|
95
96
|
|
|
96
97
|
try {
|
|
97
98
|
// begin traversing stylesheets to find @font-face declarations we might need to embed
|
|
98
|
-
styleEmbedder.fonts.
|
|
99
|
+
styleEmbedder.fonts.startFindingDocumentFontFaces(svg.ownerDocument)
|
|
99
100
|
|
|
100
101
|
// embed any media elements in the foreignObject children. images will get converted to data
|
|
101
102
|
// urls, and things like videos will be converted to images.
|
|
@@ -126,7 +127,7 @@ async function applyChangesToForeignObjects(svg: SVGSVGElement) {
|
|
|
126
127
|
|
|
127
128
|
// add the CSS to the SVG
|
|
128
129
|
if (fontCss || pseudoCss) {
|
|
129
|
-
const style =
|
|
130
|
+
const style = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'style')
|
|
130
131
|
style.textContent = `${fontCss}\n${pseudoCss}`
|
|
131
132
|
svg.prepend(style)
|
|
132
133
|
}
|