@tldraw/editor 3.8.0-canary.9dece73524ae → 3.8.0-canary.9ff4b63deedf
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 +263 -62
- package/dist-cjs/index.js +15 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +2 -5
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +3 -1
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +85 -21
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager.js +1 -0
- package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +13 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js +66 -0
- package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js.map +7 -0
- package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
- package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +83 -0
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +7 -0
- package/dist-cjs/lib/exports/getSvgJsx.js +16 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +1 -3
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +1 -1
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -0
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +3 -3
- package/dist-cjs/lib/options.js +2 -2
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js +75 -0
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +7 -0
- package/dist-cjs/lib/utils/dom.js +6 -0
- 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 +263 -62
- package/dist-esm/index.mjs +9 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +2 -5
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +3 -1
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +85 -21
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager.mjs +1 -0
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +13 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs +46 -0
- package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs.map +7 -0
- package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +63 -0
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +7 -0
- package/dist-esm/lib/exports/getSvgJsx.mjs +16 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +2 -4
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +1 -1
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -0
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +3 -3
- package/dist-esm/lib/options.mjs +2 -2
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +45 -0
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +7 -0
- package/dist-esm/lib/utils/dom.mjs +6 -0
- 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/editor.css +2 -1
- package/package.json +22 -20
- package/src/index.ts +20 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +2 -5
- package/src/lib/config/TLSessionStateSnapshot.ts +3 -1
- package/src/lib/config/createTLStore.ts +3 -1
- package/src/lib/editor/Editor.ts +134 -44
- package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +4 -4
- package/src/lib/editor/managers/TextManager.ts +1 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +49 -1
- package/src/lib/editor/shapes/shared/resizeScaled.ts +61 -0
- package/src/lib/editor/types/SvgExportContext.tsx +21 -0
- package/src/lib/editor/types/emit-types.ts +1 -0
- package/src/lib/editor/types/external-content.ts +90 -50
- package/src/lib/editor/types/misc-types.ts +55 -2
- package/src/lib/exports/StyleEmbedder.ts +1 -1
- package/src/lib/exports/exportToSvg.tsx +2 -2
- package/src/lib/exports/getSvgAsImage.ts +92 -0
- package/src/lib/exports/getSvgJsx.tsx +17 -2
- package/src/lib/hooks/useDocumentEvents.ts +2 -11
- package/src/lib/hooks/useLocalStore.ts +1 -1
- package/src/lib/hooks/usePassThroughWheelEvents.ts +7 -0
- package/src/lib/options.ts +5 -2
- package/src/lib/utils/browserCanvasMaxSize.ts +65 -0
- package/src/lib/utils/dom.ts +12 -0
- package/src/version.ts +3 -3
|
@@ -2,57 +2,97 @@ import { TLAssetId } from '@tldraw/tlschema'
|
|
|
2
2
|
import { VecLike } from '../../primitives/Vec'
|
|
3
3
|
import { TLContent } from './clipboard-types'
|
|
4
4
|
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface TLTldrawExternalContentSource {
|
|
7
|
+
type: 'tldraw'
|
|
8
|
+
data: TLContent
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @public */
|
|
12
|
+
export interface TLExcalidrawExternalContentSource {
|
|
13
|
+
type: 'excalidraw'
|
|
14
|
+
data: any
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @public */
|
|
18
|
+
export interface TLTextExternalContentSource {
|
|
19
|
+
type: 'text'
|
|
20
|
+
data: string
|
|
21
|
+
subtype: 'json' | 'html' | 'text' | 'url'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @public */
|
|
25
|
+
export interface TLErrorExternalContentSource {
|
|
26
|
+
type: 'error'
|
|
27
|
+
data: string | null
|
|
28
|
+
reason: string
|
|
29
|
+
}
|
|
30
|
+
|
|
5
31
|
/** @public */
|
|
6
32
|
export type TLExternalContentSource =
|
|
7
|
-
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
| {
|
|
16
|
-
type: 'text'
|
|
17
|
-
data: string
|
|
18
|
-
subtype: 'json' | 'html' | 'text' | 'url'
|
|
19
|
-
}
|
|
20
|
-
| {
|
|
21
|
-
type: 'error'
|
|
22
|
-
data: string | null
|
|
23
|
-
reason: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** @public */
|
|
27
|
-
export type TLExternalContent<EmbedDefinition> = {
|
|
33
|
+
| TLTldrawExternalContentSource
|
|
34
|
+
| TLExcalidrawExternalContentSource
|
|
35
|
+
| TLTextExternalContentSource
|
|
36
|
+
| TLErrorExternalContentSource
|
|
37
|
+
|
|
38
|
+
/** @public */
|
|
39
|
+
export interface TLBaseExternalContent {
|
|
28
40
|
sources?: TLExternalContentSource[]
|
|
29
41
|
point?: VecLike
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @public */
|
|
45
|
+
export interface TLTextExternalContent extends TLBaseExternalContent {
|
|
46
|
+
type: 'text'
|
|
47
|
+
text: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @public */
|
|
51
|
+
export interface TLFilesExternalContent extends TLBaseExternalContent {
|
|
52
|
+
type: 'files'
|
|
53
|
+
files: File[]
|
|
54
|
+
ignoreParent: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @public */
|
|
58
|
+
export interface TLUrlExternalContent extends TLBaseExternalContent {
|
|
59
|
+
type: 'url'
|
|
60
|
+
url: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @public */
|
|
64
|
+
export interface TLSvgTextExternalContent extends TLBaseExternalContent {
|
|
65
|
+
type: 'svg-text'
|
|
66
|
+
text: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @public */
|
|
70
|
+
export interface TLEmbedExternalContent<EmbedDefinition> extends TLBaseExternalContent {
|
|
71
|
+
type: 'embed'
|
|
72
|
+
url: string
|
|
73
|
+
embed: EmbedDefinition
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @public */
|
|
77
|
+
export type TLExternalContent<EmbedDefinition> =
|
|
78
|
+
| TLTextExternalContent
|
|
79
|
+
| TLFilesExternalContent
|
|
80
|
+
| TLUrlExternalContent
|
|
81
|
+
| TLSvgTextExternalContent
|
|
82
|
+
| TLEmbedExternalContent<EmbedDefinition>
|
|
83
|
+
|
|
84
|
+
/** @public */
|
|
85
|
+
export interface TLFileExternalAsset {
|
|
86
|
+
type: 'file'
|
|
87
|
+
file: File
|
|
88
|
+
assetId?: TLAssetId
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @public */
|
|
92
|
+
export interface TLUrlExternalAsset {
|
|
93
|
+
type: 'url'
|
|
94
|
+
url: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @public */
|
|
98
|
+
export type TLExternalAsset = TLFileExternalAsset | TLUrlExternalAsset
|
|
@@ -8,17 +8,70 @@ export type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T,
|
|
|
8
8
|
export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
|
9
9
|
|
|
10
10
|
/** @public */
|
|
11
|
-
export
|
|
11
|
+
export type TLExportType = 'svg' | 'png' | 'jpeg' | 'webp'
|
|
12
|
+
|
|
13
|
+
/** @public */
|
|
14
|
+
export interface TLSvgExportOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The bounding box, in page coordinates, of the area being exported.
|
|
17
|
+
*/
|
|
12
18
|
bounds?: Box
|
|
19
|
+
/**
|
|
20
|
+
* The logical scale of the export. This scales the resulting size of the SVG being generated.
|
|
21
|
+
*/
|
|
13
22
|
scale?: number
|
|
14
|
-
|
|
23
|
+
/**
|
|
24
|
+
* When exporting an SVG, the expected pixel ratio of the export will be passed in to
|
|
25
|
+
* {@link @tldraw/tlschema#TLAssetStore.resolve} as the `dpr` property, so that assets can be
|
|
26
|
+
* downscaled to the appropriate resolution.
|
|
27
|
+
*
|
|
28
|
+
* When exporting to a bitmap image format, the size of the resulting image will be multiplied
|
|
29
|
+
* by this number.
|
|
30
|
+
*
|
|
31
|
+
* For SVG exports, this defaults to undefined - which means we'll request original-quality
|
|
32
|
+
* assets. For bitmap exports, this defaults to 2.
|
|
33
|
+
*/
|
|
15
34
|
pixelRatio?: number
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Should the background color be included in the export? If false, the generated image will be
|
|
38
|
+
* transparent (if exporting to a format that supports transparency).
|
|
39
|
+
*/
|
|
16
40
|
background?: boolean
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* How much padding to include around the bounds of exports? Defaults to 32px.
|
|
44
|
+
*/
|
|
17
45
|
padding?: number
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Should the export be rendered in dark mode (true) or light mode (false)? Defaults to the
|
|
49
|
+
* current instance's dark mode setting.
|
|
50
|
+
*/
|
|
18
51
|
darkMode?: boolean
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The
|
|
55
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio | `preserveAspectRatio` }
|
|
56
|
+
* attribute of the SVG element.
|
|
57
|
+
*/
|
|
19
58
|
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
|
|
20
59
|
}
|
|
21
60
|
|
|
61
|
+
/** @public */
|
|
62
|
+
export interface TLImageExportOptions extends TLSvgExportOptions {
|
|
63
|
+
/**
|
|
64
|
+
* If the export is being converted to a lossy bitmap format (e.g. jpeg), this is the quality of
|
|
65
|
+
* the export. This is a number between 0 and 1.
|
|
66
|
+
*/
|
|
67
|
+
quality?: number
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The format to export as. Defaults to 'png'.
|
|
71
|
+
*/
|
|
72
|
+
format?: TLExportType
|
|
73
|
+
}
|
|
74
|
+
|
|
22
75
|
/**
|
|
23
76
|
* @public
|
|
24
77
|
* @deprecated use {@link TLImageExportOptions} instead
|
|
@@ -54,7 +54,7 @@ export class StyleEmbedder {
|
|
|
54
54
|
: NO_STYLES
|
|
55
55
|
|
|
56
56
|
const parentStyles = shouldSkipInheritedParentStyles
|
|
57
|
-
? this.styles.get(element.parentElement as Element)?.self ?? NO_STYLES
|
|
57
|
+
? (this.styles.get(element.parentElement as Element)?.self ?? NO_STYLES)
|
|
58
58
|
: NO_STYLES
|
|
59
59
|
|
|
60
60
|
const info: ElementStyleInfo = {
|
|
@@ -3,7 +3,7 @@ import { assert } from '@tldraw/utils'
|
|
|
3
3
|
import { flushSync } from 'react-dom'
|
|
4
4
|
import { createRoot } from 'react-dom/client'
|
|
5
5
|
import { Editor } from '../editor/Editor'
|
|
6
|
-
import {
|
|
6
|
+
import { TLSvgExportOptions } from '../editor/types/misc-types'
|
|
7
7
|
import { StyleEmbedder } from './StyleEmbedder'
|
|
8
8
|
import { embedMedia } from './embedMedia'
|
|
9
9
|
import { getSvgJsx } from './getSvgJsx'
|
|
@@ -13,7 +13,7 @@ let idCounter = 1
|
|
|
13
13
|
export async function exportToSvg(
|
|
14
14
|
editor: Editor,
|
|
15
15
|
shapeIds: TLShapeId[],
|
|
16
|
-
opts:
|
|
16
|
+
opts: TLSvgExportOptions = {}
|
|
17
17
|
) {
|
|
18
18
|
// when rendering to SVG, we start by creating a JSX representation of the SVG that we can
|
|
19
19
|
// render with react. Hopefully elements will have a `toSvg` method that renders them to SVG,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { FileHelpers, Image, PngHelpers, sleep } from '@tldraw/utils'
|
|
2
|
+
import { tlenv } from '../globals/environment'
|
|
3
|
+
import { clampToBrowserMaxCanvasSize } from '../utils/browserCanvasMaxSize'
|
|
4
|
+
import { debugFlags } from '../utils/debug-flags'
|
|
5
|
+
|
|
6
|
+
/** @public */
|
|
7
|
+
export async function getSvgAsImage(
|
|
8
|
+
svgString: string,
|
|
9
|
+
options: {
|
|
10
|
+
type: 'png' | 'jpeg' | 'webp'
|
|
11
|
+
width: number
|
|
12
|
+
height: number
|
|
13
|
+
quality?: number
|
|
14
|
+
pixelRatio?: number
|
|
15
|
+
}
|
|
16
|
+
) {
|
|
17
|
+
const { type, width, height, quality = 1, pixelRatio = 2 } = options
|
|
18
|
+
|
|
19
|
+
let [clampedWidth, clampedHeight] = await clampToBrowserMaxCanvasSize(
|
|
20
|
+
width * pixelRatio,
|
|
21
|
+
height * pixelRatio
|
|
22
|
+
)
|
|
23
|
+
clampedWidth = Math.floor(clampedWidth)
|
|
24
|
+
clampedHeight = Math.floor(clampedHeight)
|
|
25
|
+
const effectiveScale = clampedWidth / width
|
|
26
|
+
|
|
27
|
+
// usually we would use `URL.createObjectURL` here, but chrome has a bug where `blob:` URLs of
|
|
28
|
+
// SVGs that use <foreignObject> mark the canvas as tainted, where data: ones do not.
|
|
29
|
+
// https://issues.chromium.org/issues/41054640
|
|
30
|
+
const svgUrl = await FileHelpers.blobToDataUrl(new Blob([svgString], { type: 'image/svg+xml' }))
|
|
31
|
+
|
|
32
|
+
const canvas = await new Promise<HTMLCanvasElement | null>((resolve) => {
|
|
33
|
+
const image = Image()
|
|
34
|
+
image.crossOrigin = 'anonymous'
|
|
35
|
+
|
|
36
|
+
image.onload = async () => {
|
|
37
|
+
// safari will fire `onLoad` before the fonts in the SVG are
|
|
38
|
+
// actually loaded. just waiting around a while is brittle, but
|
|
39
|
+
// there doesn't seem to be any better solution for now :( see
|
|
40
|
+
// https://bugs.webkit.org/show_bug.cgi?id=219770
|
|
41
|
+
if (tlenv.isSafari) {
|
|
42
|
+
await sleep(250)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const canvas = document.createElement('canvas') as HTMLCanvasElement
|
|
46
|
+
const ctx = canvas.getContext('2d')!
|
|
47
|
+
|
|
48
|
+
canvas.width = clampedWidth
|
|
49
|
+
canvas.height = clampedHeight
|
|
50
|
+
|
|
51
|
+
ctx.imageSmoothingEnabled = true
|
|
52
|
+
ctx.imageSmoothingQuality = 'high'
|
|
53
|
+
ctx.drawImage(image, 0, 0, clampedWidth, clampedHeight)
|
|
54
|
+
|
|
55
|
+
URL.revokeObjectURL(svgUrl)
|
|
56
|
+
|
|
57
|
+
resolve(canvas)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
image.onerror = () => {
|
|
61
|
+
resolve(null)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
image.src = svgUrl
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (!canvas) return null
|
|
68
|
+
|
|
69
|
+
const blob = await new Promise<Blob | null>((resolve) =>
|
|
70
|
+
canvas.toBlob(
|
|
71
|
+
(blob) => {
|
|
72
|
+
if (!blob || debugFlags.throwToBlob.get()) {
|
|
73
|
+
resolve(null)
|
|
74
|
+
}
|
|
75
|
+
resolve(blob)
|
|
76
|
+
},
|
|
77
|
+
'image/' + type,
|
|
78
|
+
quality
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if (!blob) return null
|
|
83
|
+
|
|
84
|
+
if (type === 'png') {
|
|
85
|
+
const view = new DataView(await blob.arrayBuffer())
|
|
86
|
+
return PngHelpers.setPhysChunk(view, effectiveScale, {
|
|
87
|
+
type: 'image/' + type,
|
|
88
|
+
})
|
|
89
|
+
} else {
|
|
90
|
+
return blob
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -41,7 +41,7 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
|
|
|
41
41
|
const {
|
|
42
42
|
scale = 1,
|
|
43
43
|
// should we include the background in the export? or is it transparent?
|
|
44
|
-
background =
|
|
44
|
+
background = editor.getInstanceState().exportBackground,
|
|
45
45
|
padding = editor.options.defaultSvgPadding,
|
|
46
46
|
preserveAspectRatio,
|
|
47
47
|
} = opts
|
|
@@ -102,6 +102,7 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
|
|
|
102
102
|
editor={editor}
|
|
103
103
|
preserveAspectRatio={preserveAspectRatio}
|
|
104
104
|
scale={scale}
|
|
105
|
+
pixelRatio={opts.pixelRatio ?? null}
|
|
105
106
|
bbox={bbox}
|
|
106
107
|
background={background}
|
|
107
108
|
singleFrameShapeId={singleFrameShapeId}
|
|
@@ -121,6 +122,7 @@ function SvgExport({
|
|
|
121
122
|
editor,
|
|
122
123
|
preserveAspectRatio,
|
|
123
124
|
scale,
|
|
125
|
+
pixelRatio,
|
|
124
126
|
bbox,
|
|
125
127
|
background,
|
|
126
128
|
singleFrameShapeId,
|
|
@@ -132,6 +134,7 @@ function SvgExport({
|
|
|
132
134
|
editor: Editor
|
|
133
135
|
preserveAspectRatio?: string
|
|
134
136
|
scale: number
|
|
137
|
+
pixelRatio: number | null
|
|
135
138
|
bbox: Box
|
|
136
139
|
background: boolean
|
|
137
140
|
singleFrameShapeId: TLShapeId | null
|
|
@@ -177,8 +180,20 @@ function SvgExport({
|
|
|
177
180
|
isDarkMode,
|
|
178
181
|
waitUntil,
|
|
179
182
|
addExportDef,
|
|
183
|
+
scale,
|
|
184
|
+
pixelRatio,
|
|
185
|
+
async resolveAssetUrl(assetId, width) {
|
|
186
|
+
const asset = editor.getAsset(assetId)
|
|
187
|
+
if (!asset || (asset.type !== 'image' && asset.type !== 'video')) return null
|
|
188
|
+
|
|
189
|
+
return await editor.resolveAssetUrl(assetId, {
|
|
190
|
+
screenScale: scale * (width / asset.props.w),
|
|
191
|
+
shouldResolveToOriginal: pixelRatio === null,
|
|
192
|
+
dpr: pixelRatio ?? undefined,
|
|
193
|
+
})
|
|
194
|
+
},
|
|
180
195
|
}),
|
|
181
|
-
[isDarkMode, waitUntil, addExportDef]
|
|
196
|
+
[isDarkMode, waitUntil, addExportDef, scale, pixelRatio, editor]
|
|
182
197
|
)
|
|
183
198
|
|
|
184
199
|
const didRenderRef = useRef(false)
|
|
@@ -2,7 +2,7 @@ import { useValue } from '@tldraw/state-react'
|
|
|
2
2
|
import { useEffect } from 'react'
|
|
3
3
|
import { Editor } from '../editor/Editor'
|
|
4
4
|
import { TLKeyboardEventInfo } from '../editor/types/event-types'
|
|
5
|
-
import { preventDefault, stopEventPropagation } from '../utils/dom'
|
|
5
|
+
import { activeElementShouldCaptureKeys, preventDefault, stopEventPropagation } from '../utils/dom'
|
|
6
6
|
import { isAccelKey } from '../utils/keyboard'
|
|
7
7
|
import { useContainer } from './useContainer'
|
|
8
8
|
import { useEditor } from './useEditor'
|
|
@@ -274,15 +274,6 @@ export function useDocumentEvents() {
|
|
|
274
274
|
}, [editor, container, isAppFocused])
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
const INPUTS = ['input', 'select', 'button', 'textarea']
|
|
278
|
-
|
|
279
277
|
function areShortcutsDisabled(editor: Editor) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return (
|
|
283
|
-
editor.menus.hasOpenMenus() ||
|
|
284
|
-
(activeElement &&
|
|
285
|
-
(activeElement.getAttribute('contenteditable') ||
|
|
286
|
-
INPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1))
|
|
287
|
-
)
|
|
278
|
+
return editor.menus.hasOpenMenus() || activeElementShouldCaptureKeys()
|
|
288
279
|
}
|
|
@@ -37,7 +37,7 @@ export function useLocalStore(
|
|
|
37
37
|
const assets: TLAssetStore = {
|
|
38
38
|
upload: async (asset, file) => {
|
|
39
39
|
await client.db.storeAsset(asset.id, file)
|
|
40
|
-
return asset.id
|
|
40
|
+
return { src: asset.id }
|
|
41
41
|
},
|
|
42
42
|
resolve: async (asset) => {
|
|
43
43
|
if (!asset.props.src) return null
|
|
@@ -11,6 +11,13 @@ export function usePassThroughWheelEvents(ref: RefObject<HTMLElement>) {
|
|
|
11
11
|
useEffect(() => {
|
|
12
12
|
function onWheel(e: WheelEvent) {
|
|
13
13
|
if ((e as any).isSpecialRedispatchedEvent) return
|
|
14
|
+
|
|
15
|
+
// if the element is scrollable, don't redispatch the event
|
|
16
|
+
const elm = ref.current
|
|
17
|
+
if (elm && elm.scrollHeight > elm.clientHeight) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
preventDefault(e)
|
|
15
22
|
const cvs = container.querySelector('.tl-canvas')
|
|
16
23
|
if (!cvs) return
|
package/src/lib/options.ts
CHANGED
|
@@ -29,7 +29,6 @@ export interface TldrawOptions {
|
|
|
29
29
|
readonly dragDistanceSquared: number
|
|
30
30
|
readonly defaultSvgPadding: number
|
|
31
31
|
readonly cameraSlideFriction: number
|
|
32
|
-
readonly maxPointsPerDrawShape: number
|
|
33
32
|
readonly gridSteps: readonly {
|
|
34
33
|
readonly min: number
|
|
35
34
|
readonly mid: number
|
|
@@ -66,6 +65,10 @@ export interface TldrawOptions {
|
|
|
66
65
|
* external context providers. By default, this is `React.Fragment`.
|
|
67
66
|
*/
|
|
68
67
|
readonly exportProvider: ComponentType<{ children: React.ReactNode }>
|
|
68
|
+
/**
|
|
69
|
+
* By default, the toolbar items are accessible via number shortcuts according to their order. To disable this, set this option to false.
|
|
70
|
+
*/
|
|
71
|
+
readonly enableToolbarKeyboardShortcuts: boolean
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/** @public */
|
|
@@ -81,7 +84,6 @@ export const defaultTldrawOptions = {
|
|
|
81
84
|
dragDistanceSquared: 16, // 4 squared
|
|
82
85
|
defaultSvgPadding: 32,
|
|
83
86
|
cameraSlideFriction: 0.09,
|
|
84
|
-
maxPointsPerDrawShape: 500,
|
|
85
87
|
gridSteps: [
|
|
86
88
|
{ min: -1, mid: 0.15, step: 64 },
|
|
87
89
|
{ min: 0.05, mid: 0.375, step: 16 },
|
|
@@ -111,4 +113,5 @@ export const defaultTldrawOptions = {
|
|
|
111
113
|
actionShortcutsLocation: 'swap',
|
|
112
114
|
createTextOnCanvasDoubleClick: true,
|
|
113
115
|
exportProvider: Fragment,
|
|
116
|
+
enableToolbarKeyboardShortcuts: true,
|
|
114
117
|
} as const satisfies TldrawOptions
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import canvasSize from 'canvas-size'
|
|
2
|
+
|
|
3
|
+
/** @internal */
|
|
4
|
+
export interface CanvasMaxSize {
|
|
5
|
+
maxWidth: number
|
|
6
|
+
maxHeight: number
|
|
7
|
+
maxArea: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let maxSizePromise: Promise<CanvasMaxSize> | null = null
|
|
11
|
+
|
|
12
|
+
function getBrowserCanvasMaxSize() {
|
|
13
|
+
if (!maxSizePromise) {
|
|
14
|
+
maxSizePromise = calculateBrowserCanvasMaxSize()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return maxSizePromise
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function calculateBrowserCanvasMaxSize(): Promise<CanvasMaxSize> {
|
|
21
|
+
const maxWidth = await canvasSize.maxWidth({ usePromise: true })
|
|
22
|
+
const maxHeight = await canvasSize.maxHeight({ usePromise: true })
|
|
23
|
+
const maxArea = await canvasSize.maxArea({ usePromise: true })
|
|
24
|
+
return {
|
|
25
|
+
maxWidth: maxWidth.width,
|
|
26
|
+
maxHeight: maxHeight.height,
|
|
27
|
+
maxArea: maxArea.width * maxArea.height,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// https://github.com/jhildenbiddle/canvas-size?tab=readme-ov-file#test-results
|
|
32
|
+
const MAX_SAFE_CANVAS_DIMENSION = 8192
|
|
33
|
+
const MAX_SAFE_CANVAS_AREA = 4096 * 4096
|
|
34
|
+
|
|
35
|
+
/** @internal */
|
|
36
|
+
export async function clampToBrowserMaxCanvasSize(width: number, height: number) {
|
|
37
|
+
if (
|
|
38
|
+
width <= MAX_SAFE_CANVAS_DIMENSION &&
|
|
39
|
+
height <= MAX_SAFE_CANVAS_DIMENSION &&
|
|
40
|
+
width * height <= MAX_SAFE_CANVAS_AREA
|
|
41
|
+
) {
|
|
42
|
+
return [width, height]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { maxWidth, maxHeight, maxArea } = await getBrowserCanvasMaxSize()
|
|
46
|
+
const aspectRatio = width / height
|
|
47
|
+
|
|
48
|
+
if (width > maxWidth) {
|
|
49
|
+
width = maxWidth
|
|
50
|
+
height = width / aspectRatio
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (height > maxHeight) {
|
|
54
|
+
height = maxHeight
|
|
55
|
+
width = height * aspectRatio
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (width * height > maxArea) {
|
|
59
|
+
const ratio = Math.sqrt(maxArea / (width * height))
|
|
60
|
+
width *= ratio
|
|
61
|
+
height *= ratio
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return [width, height]
|
|
65
|
+
}
|
package/src/lib/utils/dom.ts
CHANGED
|
@@ -90,3 +90,15 @@ export const setStyleProperty = (
|
|
|
90
90
|
if (!elm) return
|
|
91
91
|
elm.style.setProperty(property, value as string)
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
const INPUTS = ['input', 'select', 'button', 'textarea']
|
|
95
|
+
|
|
96
|
+
/** @internal */
|
|
97
|
+
export function activeElementShouldCaptureKeys() {
|
|
98
|
+
const { activeElement } = document
|
|
99
|
+
return !!(
|
|
100
|
+
activeElement &&
|
|
101
|
+
(activeElement.getAttribute('contenteditable') ||
|
|
102
|
+
INPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1)
|
|
103
|
+
)
|
|
104
|
+
}
|
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 = '3.8.0-canary.
|
|
4
|
+
export const version = '3.8.0-canary.9ff4b63deedf'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-02-12T11:58:37.581Z',
|
|
8
|
+
patch: '2025-02-12T11:58:37.581Z',
|
|
9
9
|
}
|