@tldraw/editor 3.9.0-canary.6beea33123dd → 3.9.0-canary.6f51d9267136

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist-cjs/index.d.ts +39 -7
  4. package/dist-cjs/index.js +1 -1
  5. package/dist-cjs/index.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  7. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +430 -248
  9. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  10. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +7 -2
  11. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/exports/getSvgAsImage.js +1 -1
  13. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  14. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  15. package/dist-cjs/lib/globals/environment.js +3 -1
  16. package/dist-cjs/lib/globals/environment.js.map +2 -2
  17. package/dist-cjs/lib/license/LicenseManager.js +1 -1
  18. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  19. package/dist-cjs/lib/utils/browserCanvasMaxSize.js +104 -28
  20. package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +3 -3
  21. package/dist-cjs/version.js +3 -3
  22. package/dist-cjs/version.js.map +1 -1
  23. package/dist-esm/index.d.mts +39 -7
  24. package/dist-esm/index.mjs +1 -1
  25. package/dist-esm/index.mjs.map +2 -2
  26. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  27. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  28. package/dist-esm/lib/editor/Editor.mjs +426 -244
  29. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  30. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +7 -2
  31. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  32. package/dist-esm/lib/exports/getSvgAsImage.mjs +1 -1
  33. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  34. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  35. package/dist-esm/lib/globals/environment.mjs +3 -1
  36. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  37. package/dist-esm/lib/license/LicenseManager.mjs +1 -1
  38. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  39. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +104 -18
  40. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +2 -2
  41. package/dist-esm/version.mjs +3 -3
  42. package/dist-esm/version.mjs.map +1 -1
  43. package/package.json +7 -9
  44. package/src/index.ts +2 -0
  45. package/src/lib/components/default-components/DefaultErrorFallback.tsx +5 -3
  46. package/src/lib/editor/Editor.ts +555 -272
  47. package/src/lib/editor/shapes/ShapeUtil.ts +32 -5
  48. package/src/lib/exports/getSvgAsImage.ts +1 -1
  49. package/src/lib/exports/getSvgJsx.tsx +1 -0
  50. package/src/lib/globals/environment.ts +3 -0
  51. package/src/lib/license/LicenseManager.test.ts +16 -13
  52. package/src/lib/license/LicenseManager.ts +2 -2
  53. package/src/lib/utils/browserCanvasMaxSize.ts +121 -21
  54. package/src/version.ts +3 -3
@@ -36,7 +36,7 @@ export interface TLShapeUtilConstructor<
36
36
  *
37
37
  * @public
38
38
  */
39
- export interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLShape> {
39
+ export interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLUnknownShape> {
40
40
  /** The type of shape referenced by the `fromId` of the binding. */
41
41
  fromShapeType: string
42
42
  /** The type of shape referenced by the `toId` of the binding. */
@@ -45,6 +45,27 @@ export interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLShape>
45
45
  bindingType: string
46
46
  }
47
47
 
48
+ /**
49
+ * Options passed to {@link ShapeUtil.canBeLaidOut}.
50
+ *
51
+ * @public
52
+ */
53
+ export interface TLShapeUtilCanBeLaidOutOpts {
54
+ /** The type of action causing the layout. */
55
+ type?: 'align' | 'distribute' | 'pack' | 'stack' | 'flip' | 'stretch'
56
+ /** The other shapes being laid out */
57
+ shapes?: TLShape[]
58
+ }
59
+
60
+ /** Additional options for the {@link ShapeUtil.getGeometry} method.
61
+ *
62
+ * @public
63
+ */
64
+ export interface TLGeometryOpts {
65
+ /** The context in which the geometry is being requested. */
66
+ context?: string
67
+ }
68
+
48
69
  /** @public */
49
70
  export interface TLShapeUtilCanvasSvgDef {
50
71
  key: string
@@ -128,9 +149,10 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
128
149
  * Get the shape's geometry.
129
150
  *
130
151
  * @param shape - The shape.
152
+ * @param opts - Additional options for the request.
131
153
  * @public
132
154
  */
133
- abstract getGeometry(shape: Shape): Geometry2d
155
+ abstract getGeometry(shape: Shape, opts?: TLGeometryOpts): Geometry2d
134
156
 
135
157
  /**
136
158
  * Get a JSX element for the shape (as an HTML element).
@@ -151,6 +173,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
151
173
  /**
152
174
  * Whether the shape can be snapped to by another shape.
153
175
  *
176
+ * @param shape - The shape.
154
177
  * @public
155
178
  */
156
179
  canSnap(_shape: Shape): boolean {
@@ -171,7 +194,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
171
194
  *
172
195
  * @public
173
196
  */
174
- canBind(_opts: TLShapeUtilCanBindOpts<Shape>): boolean {
197
+ canBind(_opts: TLShapeUtilCanBindOpts): boolean {
175
198
  return true
176
199
  }
177
200
 
@@ -212,11 +235,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
212
235
  }
213
236
 
214
237
  /**
215
- * Whether the shape participates in stacking, aligning, and distributing.
238
+ * Whether the shape can participate in layout functions such as alignment or distribution.
239
+ *
240
+ * @param shape - The shape.
241
+ * @param info - Additional context information: the type of action causing the layout and the
242
+ * @public
216
243
  *
217
244
  * @public
218
245
  */
219
- canBeLaidOut(_shape: Shape): boolean {
246
+ canBeLaidOut(_shape: Shape, _info: TLShapeUtilCanBeLaidOutOpts): boolean {
220
247
  return true
221
248
  }
222
249
 
@@ -16,7 +16,7 @@ export async function getSvgAsImage(
16
16
  ) {
17
17
  const { type, width, height, quality = 1, pixelRatio = 2 } = options
18
18
 
19
- let [clampedWidth, clampedHeight] = await clampToBrowserMaxCanvasSize(
19
+ let [clampedWidth, clampedHeight] = clampToBrowserMaxCanvasSize(
20
20
  width * pixelRatio,
21
21
  height * pixelRatio
22
22
  )
@@ -205,6 +205,7 @@ function SvgExport({
205
205
  ;(async () => {
206
206
  const shapeDefs: Record<string, { pending: false; element: ReactElement }> = {}
207
207
 
208
+ // Then render everything. The shapes with assets should all hit the cache
208
209
  const unorderedShapeElementPromises = renderingShapes.map(
209
210
  async ({ id, opacity, index, backgroundIndex }) => {
210
211
  // Don't render the frame if we're only exporting a single frame and it's children
@@ -11,6 +11,7 @@ const tlenv = {
11
11
  isAndroid: false,
12
12
  isWebview: false,
13
13
  isDarwin: false,
14
+ hasCanvasSupport: false,
14
15
  }
15
16
 
16
17
  if (typeof window !== 'undefined' && 'navigator' in window) {
@@ -20,6 +21,8 @@ if (typeof window !== 'undefined' && 'navigator' in window) {
20
21
  tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
21
22
  tlenv.isAndroid = /android/i.test(navigator.userAgent)
22
23
  tlenv.isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
24
+ tlenv.hasCanvasSupport =
25
+ typeof window !== 'undefined' && 'Promise' in window && 'HTMLCanvasElement' in window
23
26
  }
24
27
 
25
28
  export { tlenv }
@@ -56,19 +56,22 @@ describe('LicenseManager', () => {
56
56
  })
57
57
 
58
58
  it('Signals that it is development mode when appropriate', async () => {
59
- // @ts-ignore
60
- delete window.location
61
- // @ts-ignore
62
- window.location = new URL('http://localhost:3000')
63
-
64
- const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey, 'development')
65
- const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
66
- const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
67
- expect(result).toMatchObject({
68
- isLicenseParseable: true,
69
- isDomainValid: false,
70
- isDevelopment: true,
71
- })
59
+ const schemes = ['http', 'https']
60
+ for (const scheme of schemes) {
61
+ // @ts-ignore
62
+ delete window.location
63
+ // @ts-ignore
64
+ window.location = new URL(`${scheme}://localhost:3000`)
65
+
66
+ const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey, 'development')
67
+ const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
68
+ const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
69
+ expect(result).toMatchObject({
70
+ isLicenseParseable: true,
71
+ isDomainValid: false,
72
+ isDevelopment: true,
73
+ })
74
+ }
72
75
  })
73
76
 
74
77
  it('Cleanses out valid keys that accidentally have zero-width characters or newlines', async () => {
@@ -110,8 +110,8 @@ export class LicenseManager {
110
110
  if (testEnvironment === 'development') return true
111
111
  if (testEnvironment === 'production') return false
112
112
 
113
- // If we are using https we assume it's a production env and a development one otherwise
114
- return window.location.protocol !== 'https:'
113
+ // If we are using https on a non-localhost domain we assume it's a production env and a development one otherwise
114
+ return window.location.protocol !== 'https:' || window.location.hostname === 'localhost'
115
115
  }
116
116
 
117
117
  private async extractLicenseKey(licenseKey: string): Promise<LicenseInfo> {
@@ -1,5 +1,3 @@
1
- import canvasSize from 'canvas-size'
2
-
3
1
  /** @internal */
4
2
  export interface CanvasMaxSize {
5
3
  maxWidth: number
@@ -7,33 +5,135 @@ export interface CanvasMaxSize {
7
5
  maxArea: number
8
6
  }
9
7
 
10
- let maxSizePromise: Promise<CanvasMaxSize> | null = null
8
+ // Cache this, only want to do this once per browser session
9
+ let maxCanvasSizes: CanvasMaxSize | null = null
11
10
 
12
- function getBrowserCanvasMaxSize() {
13
- if (!maxSizePromise) {
14
- maxSizePromise = calculateBrowserCanvasMaxSize()
11
+ function getBrowserCanvasMaxSize(): CanvasMaxSize {
12
+ if (!maxCanvasSizes) {
13
+ maxCanvasSizes = {
14
+ maxWidth: getCanvasSize('width'), // test very wide but 1 pixel tall canvases
15
+ maxHeight: getCanvasSize('height'), // test very tall but 1 pixel wide canvases
16
+ maxArea: getCanvasSize('area'), // test square canvases
17
+ }
15
18
  }
16
-
17
- return maxSizePromise
19
+ return maxCanvasSizes
18
20
  }
19
21
 
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
- }
22
+ // Extracted from https://github.com/jhildenbiddle/canvas-size
23
+ // MIT License: https://github.com/jhildenbiddle/canvas-size/blob/master/LICENSE
24
+ // Copyright (c) John Hildenbiddle
30
25
 
31
- // https://github.com/jhildenbiddle/canvas-size?tab=readme-ov-file#test-results
32
26
  const MAX_SAFE_CANVAS_DIMENSION = 8192
33
27
  const MAX_SAFE_CANVAS_AREA = 4096 * 4096
34
28
 
29
+ const TEST_SIZES = {
30
+ area: [
31
+ // Chrome 70 (Mac, Win)
32
+ // Chrome 68 (Android 4.4)
33
+ // Edge 17 (Win)
34
+ // Safari 7-12 (Mac)
35
+ 16384,
36
+ // Chrome 68 (Android 7.1-9)
37
+ 14188,
38
+ // Chrome 68 (Android 5)
39
+ 11402,
40
+ // Firefox 63 (Mac, Win)
41
+ 11180,
42
+ // Chrome 68 (Android 6)
43
+ 10836,
44
+ // IE 9-11 (Win)
45
+ 8192,
46
+ // IE Mobile (Windows Phone 8.x)
47
+ // Safari (iOS 9 - 12)
48
+ 4096,
49
+ ],
50
+ height: [
51
+ // Safari 7-12 (Mac)
52
+ // Safari (iOS 9-12)
53
+ 8388607,
54
+ // Chrome 83 (Mac, Win)
55
+ 65535,
56
+ // Chrome 70 (Mac, Win)
57
+ // Chrome 68 (Android 4.4-9)
58
+ // Firefox 63 (Mac, Win)
59
+ 32767,
60
+ // Edge 17 (Win)
61
+ // IE11 (Win)
62
+ 16384,
63
+ // IE 9-10 (Win)
64
+ 8192,
65
+ // IE Mobile (Windows Phone 8.x)
66
+ 4096,
67
+ ],
68
+ width: [
69
+ // Safari 7-12 (Mac)
70
+ // Safari (iOS 9-12)
71
+ 4194303,
72
+ // Chrome 83 (Mac, Win)
73
+ 65535,
74
+ // Chrome 70 (Mac, Win)
75
+ // Chrome 68 (Android 4.4-9)
76
+ // Firefox 63 (Mac, Win)
77
+ 32767,
78
+ // Edge 17 (Win)
79
+ // IE11 (Win)
80
+ 16384,
81
+ // IE 9-10 (Win)
82
+ 8192,
83
+ // IE Mobile (Windows Phone 8.x)
84
+ 4096,
85
+ ],
86
+ } as const
87
+
88
+ /**
89
+ * Tests ability to read pixel data from canvas elements of various dimensions
90
+ * by decreasing canvas height and/or width until a test succeeds.
91
+ */
92
+ export function getCanvasSize(dimension: 'width' | 'height' | 'area') {
93
+ const cropCvs = document.createElement('canvas')
94
+ cropCvs.width = 1
95
+ cropCvs.height = 1
96
+ const cropCtx = cropCvs.getContext('2d')!
97
+
98
+ for (const size of TEST_SIZES[dimension]) {
99
+ const w = dimension === 'height' ? 1 : size
100
+ const h = dimension === 'width' ? 1 : size
101
+
102
+ const testCvs = document.createElement('canvas')
103
+ testCvs.width = w
104
+ testCvs.height = h
105
+ const testCtx = testCvs.getContext('2d')!
106
+
107
+ testCtx.fillRect(w - 1, h - 1, 1, 1)
108
+ cropCtx.drawImage(testCvs, w - 1, h - 1, 1, 1, 0, 0, 1, 1)
109
+
110
+ const isTestPassed = cropCtx.getImageData(0, 0, 1, 1).data[3] !== 0
111
+ // release memory
112
+ testCvs.width = 0
113
+ testCvs.height = 0
114
+
115
+ if (isTestPassed) {
116
+ // release memory
117
+ cropCvs.width = 0
118
+ cropCvs.height = 0
119
+
120
+ if (dimension === 'area') {
121
+ return size * size
122
+ } else {
123
+ return size
124
+ }
125
+ }
126
+ }
127
+
128
+ // didn't find a good size, release memory and error
129
+ cropCvs.width = 0
130
+ cropCvs.height = 0
131
+
132
+ throw Error('Failed to determine maximum canvas dimension')
133
+ }
134
+
35
135
  /** @internal */
36
- export async function clampToBrowserMaxCanvasSize(width: number, height: number) {
136
+ export function clampToBrowserMaxCanvasSize(width: number, height: number) {
37
137
  if (
38
138
  width <= MAX_SAFE_CANVAS_DIMENSION &&
39
139
  height <= MAX_SAFE_CANVAS_DIMENSION &&
@@ -42,7 +142,7 @@ export async function clampToBrowserMaxCanvasSize(width: number, height: number)
42
142
  return [width, height]
43
143
  }
44
144
 
45
- const { maxWidth, maxHeight, maxArea } = await getBrowserCanvasMaxSize()
145
+ const { maxWidth, maxHeight, maxArea } = getBrowserCanvasMaxSize()
46
146
  const aspectRatio = width / height
47
147
 
48
148
  if (width > maxWidth) {
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.9.0-canary.6beea33123dd'
4
+ export const version = '3.9.0-canary.6f51d9267136'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-02-25T08:23:37.972Z',
8
- patch: '2025-02-25T08:23:37.972Z',
7
+ minor: '2025-02-27T14:15:31.085Z',
8
+ patch: '2025-02-27T14:15:31.085Z',
9
9
  }