@tldraw/editor 4.1.1 → 4.2.0-canary.025846da88b7

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 (63) hide show
  1. package/dist-cjs/index.d.ts +54 -6
  2. package/dist-cjs/index.js +2 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/MenuClickCapture.js +30 -15
  5. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +11 -11
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +27 -0
  9. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  10. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +3 -7
  11. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  12. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -1
  13. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  14. package/dist-cjs/lib/license/LicenseManager.js +3 -0
  15. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  16. package/dist-cjs/lib/license/Watermark.js +1 -1
  17. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  18. package/dist-cjs/lib/utils/debug-flags.js +1 -0
  19. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  20. package/dist-cjs/lib/utils/runtime.js +2 -2
  21. package/dist-cjs/lib/utils/runtime.js.map +2 -2
  22. package/dist-cjs/lib/utils/window-open.js +2 -2
  23. package/dist-cjs/lib/utils/window-open.js.map +2 -2
  24. package/dist-cjs/version.js +3 -3
  25. package/dist-cjs/version.js.map +1 -1
  26. package/dist-esm/index.d.mts +54 -6
  27. package/dist-esm/index.mjs +3 -1
  28. package/dist-esm/index.mjs.map +2 -2
  29. package/dist-esm/lib/components/MenuClickCapture.mjs +30 -15
  30. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  31. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +11 -11
  32. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  33. package/dist-esm/lib/editor/Editor.mjs +27 -0
  34. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  35. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +3 -7
  36. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  37. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -1
  38. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  39. package/dist-esm/lib/license/LicenseManager.mjs +3 -0
  40. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  41. package/dist-esm/lib/license/Watermark.mjs +1 -1
  42. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  43. package/dist-esm/lib/utils/debug-flags.mjs +1 -0
  44. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  45. package/dist-esm/lib/utils/runtime.mjs +2 -2
  46. package/dist-esm/lib/utils/runtime.mjs.map +2 -2
  47. package/dist-esm/lib/utils/window-open.mjs +2 -2
  48. package/dist-esm/lib/utils/window-open.mjs.map +2 -2
  49. package/dist-esm/version.mjs +3 -3
  50. package/dist-esm/version.mjs.map +1 -1
  51. package/package.json +10 -10
  52. package/src/index.ts +1 -0
  53. package/src/lib/components/MenuClickCapture.tsx +35 -17
  54. package/src/lib/components/default-components/DefaultCanvas.tsx +9 -9
  55. package/src/lib/editor/Editor.ts +29 -0
  56. package/src/lib/editor/derivations/parentsToChildren.ts +4 -10
  57. package/src/lib/hooks/useCanvasEvents.ts +8 -1
  58. package/src/lib/license/LicenseManager.ts +9 -0
  59. package/src/lib/license/Watermark.tsx +1 -1
  60. package/src/lib/utils/debug-flags.ts +5 -4
  61. package/src/lib/utils/runtime.ts +3 -3
  62. package/src/lib/utils/window-open.ts +13 -3
  63. package/src/version.ts +3 -3
@@ -3,6 +3,7 @@ import { PointerEvent, useCallback, useRef, useState } from 'react'
3
3
  import { useCanvasEvents } from '../hooks/useCanvasEvents'
4
4
  import { useEditor } from '../hooks/useEditor'
5
5
  import { Vec } from '../primitives/Vec'
6
+ import { getPointerInfo } from '../utils/getPointerInfo'
6
7
 
7
8
  /**
8
9
  * When a menu is open, this component prevents the user from interacting with the canvas.
@@ -39,35 +40,51 @@ export function MenuClickCapture() {
39
40
  isDragging: false,
40
41
  start: new Vec(e.clientX, e.clientY),
41
42
  }
43
+ rDidAPointerDownAndDragWhileMenuWasOpen.current = false
42
44
  }
43
45
  editor.menus.clearOpenMenus()
44
46
  },
45
47
  [editor]
46
48
  )
47
49
 
50
+ const rDidAPointerDownAndDragWhileMenuWasOpen = useRef(false)
51
+
48
52
  const handlePointerMove = useCallback(
49
53
  (e: PointerEvent) => {
50
54
  // Do nothing unless we're pointing
51
55
  if (!rPointerState.current.isDown) return
52
56
 
53
- if (
54
- // We're pointing, but are we dragging?
55
- Vec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >
56
- editor.options.dragDistanceSquared
57
- ) {
58
- // Wehaddaeventitsadrag
59
- rPointerState.current = {
60
- ...rPointerState.current,
61
- isDown: true,
62
- isDragging: true,
57
+ // call the onPointerDown with the original pointer position
58
+ const { x, y } = rPointerState.current.start
59
+
60
+ if (!rDidAPointerDownAndDragWhileMenuWasOpen.current) {
61
+ if (
62
+ // We're pointing, but are we dragging?
63
+ Vec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >
64
+ editor.options.dragDistanceSquared
65
+ ) {
66
+ rDidAPointerDownAndDragWhileMenuWasOpen.current = true
67
+ // Wehaddaeventitsadrag
68
+ rPointerState.current = {
69
+ ...rPointerState.current,
70
+ isDown: true,
71
+ isDragging: true,
72
+ }
73
+ canvasEvents.onPointerDown?.({
74
+ ...e,
75
+ clientX: x,
76
+ clientY: y,
77
+ button: 0,
78
+ })
63
79
  }
64
- // call the onPointerDown with the original pointer position
65
- const { x, y } = rPointerState.current.start
66
- canvasEvents.onPointerDown?.({
67
- ...e,
68
- clientX: x,
69
- clientY: y,
70
- button: 0,
80
+ }
81
+
82
+ if (rDidAPointerDownAndDragWhileMenuWasOpen.current) {
83
+ editor.dispatch({
84
+ type: 'pointer',
85
+ target: 'canvas',
86
+ name: 'pointer_move',
87
+ ...getPointerInfo(editor, e),
71
88
  })
72
89
  }
73
90
  },
@@ -86,6 +103,7 @@ export function MenuClickCapture() {
86
103
  isDragging: false,
87
104
  start: new Vec(e.clientX, e.clientY),
88
105
  }
106
+ rDidAPointerDownAndDragWhileMenuWasOpen.current = false
89
107
  },
90
108
  [canvasEvents]
91
109
  )
@@ -172,17 +172,17 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
172
172
  <LiveCollaborators />
173
173
  </div>
174
174
  </div>
175
- <div
176
- className="tl-canvas__in-front"
177
- onPointerDown={editor.markEventAsHandled}
178
- onPointerUp={editor.markEventAsHandled}
179
- onTouchStart={editor.markEventAsHandled}
180
- onTouchEnd={editor.markEventAsHandled}
181
- >
182
- <InFrontOfTheCanvasWrapper />
183
- </div>
184
175
  <MovingCameraHitTestBlocker />
185
176
  </div>
177
+ <div
178
+ className="tl-canvas__in-front"
179
+ onPointerDown={editor.markEventAsHandled}
180
+ onPointerUp={editor.markEventAsHandled}
181
+ onTouchStart={editor.markEventAsHandled}
182
+ onTouchEnd={editor.markEventAsHandled}
183
+ >
184
+ <InFrontOfTheCanvasWrapper />
185
+ </div>
186
186
  <MenuClickCapture />
187
187
  </>
188
188
  )
@@ -837,6 +837,35 @@ export class Editor extends EventEmitter<TLEventMap> {
837
837
  */
838
838
  readonly root: StateNode
839
839
 
840
+ /**
841
+ * Set a tool. Useful if you need to add a tool to the state chart on demand,
842
+ * after the editor has already been initialized.
843
+ *
844
+ * @param Tool - The tool to set.
845
+ *
846
+ * @public
847
+ */
848
+ setTool(Tool: TLStateNodeConstructor) {
849
+ if (hasOwnProperty(this.root.children!, Tool.id)) {
850
+ throw Error(`Can't override tool with id "${Tool.id}"`)
851
+ }
852
+ this.root.children![Tool.id] = new Tool(this, this.root)
853
+ }
854
+
855
+ /**
856
+ * Remove a tool. Useful if you need to remove a tool from the state chart on demand,
857
+ * after the editor has already been initialized.
858
+ *
859
+ * @param Tool - The tool to delete.
860
+ *
861
+ * @public
862
+ */
863
+ removeTool(Tool: TLStateNodeConstructor) {
864
+ if (hasOwnProperty(this.root.children!, Tool.id)) {
865
+ delete this.root.children![Tool.id]
866
+ }
867
+ }
868
+
840
869
  /**
841
870
  * A set of functions to call when the app is disposed.
842
871
  *
@@ -1,6 +1,6 @@
1
1
  import { Computed, computed, isUninitialized, RESET_VALUE } from '@tldraw/state'
2
2
  import { CollectionDiff, RecordsDiff } from '@tldraw/store'
3
- import { isShape, TLParentId, TLRecord, TLShape, TLShapeId, TLStore } from '@tldraw/tlschema'
3
+ import { isShape, TLParentId, TLRecord, TLShapeId, TLStore } from '@tldraw/tlschema'
4
4
  import { compact, sortByIndex } from '@tldraw/utils'
5
5
 
6
6
  type ParentShapeIdsToChildShapeIds = Record<TLParentId, TLShapeId[]>
@@ -11,17 +11,11 @@ function fromScratch(
11
11
  ) {
12
12
  const result: ParentShapeIdsToChildShapeIds = {}
13
13
  const shapeIds = shapeIdsQuery.get()
14
- const shapes = Array(shapeIds.size) as TLShape[]
15
- shapeIds.forEach((id) => shapes.push(store.get(id)!))
16
-
17
- // Sort the shapes by index
18
- shapes.sort(sortByIndex)
14
+ const sortedShapes = Array.from(shapeIds, (id) => store.get(id)!).sort(sortByIndex)
19
15
 
20
16
  // Populate the result object with an array for each parent.
21
- shapes.forEach((shape) => {
22
- if (!result[shape.parentId]) {
23
- result[shape.parentId] = []
24
- }
17
+ sortedShapes.forEach((shape) => {
18
+ result[shape.parentId] ??= []
25
19
  result[shape.parentId].push(shape.id)
26
20
  })
27
21
 
@@ -1,6 +1,7 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
2
  import React, { useEffect, useMemo } from 'react'
3
3
  import { RIGHT_MOUSE_BUTTON } from '../constants'
4
+ import { tlenv } from '../globals/environment'
4
5
  import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'
5
6
  import { getPointerInfo } from '../utils/getPointerInfo'
6
7
  import { useEditor } from './useEditor'
@@ -161,8 +162,14 @@ export function useCanvasEvents() {
161
162
  // For tools that benefit from a higher fidelity of events,
162
163
  // we dispatch the coalesced events.
163
164
  // N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
165
+ // Specifically, in local mode (non-https) mode, iOS does not `useCoalescedEvents`
166
+ // so it appears like the ink is working locally, when really it's just that `useCoalescedEvents`
167
+ // is disabled. The intent here is to have `useCoalescedEvents` disabled for iOS.
164
168
  const events =
165
- currentTool.useCoalescedEvents && e.getCoalescedEvents ? e.getCoalescedEvents() : [e]
169
+ !tlenv.isIos && currentTool.useCoalescedEvents && e.getCoalescedEvents
170
+ ? e.getCoalescedEvents()
171
+ : [e]
172
+
166
173
  for (const singleEvent of events) {
167
174
  editor.dispatch({
168
175
  type: 'pointer',
@@ -171,7 +171,16 @@ export class LicenseManager {
171
171
  url.searchParams.set('license_type', trackType)
172
172
  if ('license' in result) {
173
173
  url.searchParams.set('license_id', result.license.id)
174
+ const sku = this.isFlagEnabled(result.license.flags, FLAGS.EVALUATION_LICENSE)
175
+ ? 'evaluation'
176
+ : this.isFlagEnabled(result.license.flags, FLAGS.ANNUAL_LICENSE)
177
+ ? 'annual'
178
+ : this.isFlagEnabled(result.license.flags, FLAGS.PERPETUAL_LICENSE)
179
+ ? 'perpetual'
180
+ : 'unknown'
181
+ url.searchParams.set('sku', sku)
174
182
  }
183
+ url.searchParams.set('url', window.location.href)
175
184
  if (process.env.NODE_ENV) {
176
185
  url.searchParams.set('environment', process.env.NODE_ENV)
177
186
  }
@@ -70,7 +70,7 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark({
70
70
  preventDefault(e)
71
71
  }}
72
72
  title="The tldraw SDK requires a license key to work in production. You can get a free 100-day trial license at tldraw.dev/pricing."
73
- onClick={() => runtime.openWindow(url, '_blank')}
73
+ onClick={() => runtime.openWindow(url, '_blank', true)} // allow referrer
74
74
  >
75
75
  Get a license for production
76
76
  </button>
@@ -92,7 +92,8 @@ if (typeof Element !== 'undefined') {
92
92
 
93
93
  // --- IMPLEMENTATION ---
94
94
  // you probably don't need to read this if you're just using the debug values system
95
- function createDebugValue<T>(
95
+ /** @public */
96
+ export function createDebugValue<T>(
96
97
  name: string,
97
98
  {
98
99
  defaults,
@@ -193,7 +194,7 @@ function getDefaultValue<T>(def: DebugFlagDef<T>): T {
193
194
  }
194
195
  }
195
196
 
196
- /** @internal */
197
+ /** @public */
197
198
  export interface DebugFlagDefaults<T> {
198
199
  development?: T
199
200
  staging?: T
@@ -201,14 +202,14 @@ export interface DebugFlagDefaults<T> {
201
202
  all: T
202
203
  }
203
204
 
204
- /** @internal */
205
+ /** @public */
205
206
  export interface DebugFlagDef<T> {
206
207
  name: string
207
208
  defaults: DebugFlagDefaults<T>
208
209
  shouldStoreForSession: boolean
209
210
  }
210
211
 
211
- /** @internal */
212
+ /** @public */
212
213
  export interface DebugFlag<T> extends DebugFlagDef<T>, Atom<T> {
213
214
  reset(): void
214
215
  }
@@ -1,11 +1,11 @@
1
1
  /** @public */
2
2
  export const runtime: {
3
- openWindow(url: string, target: string): void
3
+ openWindow(url: string, target: string, allowReferrer?: boolean): void
4
4
  refreshPage(): void
5
5
  hardReset(): void
6
6
  } = {
7
- openWindow(url, target) {
8
- window.open(url, target, 'noopener noreferrer')
7
+ openWindow(url, target, allowReferrer = false) {
8
+ return window.open(url, target, allowReferrer ? 'noopener' : 'noopener noreferrer')
9
9
  },
10
10
  refreshPage() {
11
11
  window.location.reload()
@@ -1,6 +1,16 @@
1
1
  import { runtime } from './runtime'
2
2
 
3
- /** @public */
4
- export function openWindow(url: string, target = '_blank') {
5
- runtime.openWindow(url, target)
3
+ /**
4
+ * Open a new window with the given URL and target. Prefer this to the window.open function, as it
5
+ * will work more reliably in embedded scenarios, such as our VS Code extension. See the runtime
6
+ * object in tldraw/editor for more details.
7
+ *
8
+ * @param url - The URL to open.
9
+ * @param target - The target window to open the URL in.
10
+ * @param allowReferrer - Whether to allow the referrer to be sent to the new window.
11
+ * @returns The new window object.
12
+ * @public
13
+ */
14
+ export function openWindow(url: string, target = '_blank', allowReferrer?: boolean) {
15
+ return runtime.openWindow(url, target, allowReferrer)
6
16
  }
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 = '4.1.1'
4
+ export const version = '4.2.0-canary.025846da88b7'
5
5
  export const publishDates = {
6
6
  major: '2025-09-18T14:39:22.803Z',
7
- minor: '2025-10-15T12:23:29.168Z',
8
- patch: '2025-10-15T13:38:40.898Z',
7
+ minor: '2025-10-28T15:00:53.668Z',
8
+ patch: '2025-10-28T15:00:53.668Z',
9
9
  }