@tldraw/editor 3.7.0-internal.acaf9fbbd3cc → 3.8.0-canary.0bbbc9e0c959

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 (62) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist-cjs/index.d.ts +11 -2
  3. package/dist-cjs/index.js +5 -1
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/TldrawEditor.js +1 -1
  6. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  7. package/dist-cjs/lib/components/LiveCollaborators.js +2 -1
  8. package/dist-cjs/lib/components/LiveCollaborators.js.map +2 -2
  9. package/dist-cjs/lib/components/Shape.js +6 -4
  10. package/dist-cjs/lib/components/Shape.js.map +2 -2
  11. package/dist-cjs/lib/components/default-components/DefaultSelectionForeground.js +2 -1
  12. package/dist-cjs/lib/components/default-components/DefaultSelectionForeground.js.map +2 -2
  13. package/dist-cjs/lib/editor/Editor.js +10 -4
  14. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  15. package/dist-cjs/lib/editor/managers/TextManager.js +9 -15
  16. package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
  17. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  18. package/dist-cjs/lib/exports/FontEmbedder.js +2 -1
  19. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  20. package/dist-cjs/lib/hooks/useEvent.js +18 -1
  21. package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
  22. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +1 -0
  23. package/dist-cjs/lib/utils/sync/LocalIndexedDb.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 +11 -2
  27. package/dist-esm/index.mjs +6 -2
  28. package/dist-esm/index.mjs.map +2 -2
  29. package/dist-esm/lib/TldrawEditor.mjs +1 -1
  30. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  31. package/dist-esm/lib/components/LiveCollaborators.mjs +2 -1
  32. package/dist-esm/lib/components/LiveCollaborators.mjs.map +2 -2
  33. package/dist-esm/lib/components/Shape.mjs +6 -4
  34. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  35. package/dist-esm/lib/components/default-components/DefaultSelectionForeground.mjs +2 -1
  36. package/dist-esm/lib/components/default-components/DefaultSelectionForeground.mjs.map +2 -2
  37. package/dist-esm/lib/editor/Editor.mjs +11 -4
  38. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  39. package/dist-esm/lib/editor/managers/TextManager.mjs +9 -15
  40. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  41. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  42. package/dist-esm/lib/exports/FontEmbedder.mjs +2 -1
  43. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  44. package/dist-esm/lib/hooks/useEvent.mjs +18 -1
  45. package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
  46. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +1 -0
  47. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
  48. package/dist-esm/version.mjs +3 -3
  49. package/dist-esm/version.mjs.map +1 -1
  50. package/package.json +7 -7
  51. package/src/index.ts +2 -1
  52. package/src/lib/TldrawEditor.tsx +1 -1
  53. package/src/lib/components/LiveCollaborators.tsx +3 -1
  54. package/src/lib/components/Shape.tsx +22 -10
  55. package/src/lib/components/default-components/DefaultSelectionForeground.tsx +4 -1
  56. package/src/lib/editor/Editor.ts +14 -7
  57. package/src/lib/editor/managers/TextManager.ts +9 -17
  58. package/src/lib/editor/shapes/ShapeUtil.ts +1 -1
  59. package/src/lib/exports/FontEmbedder.ts +2 -1
  60. package/src/lib/hooks/useEvent.tsx +29 -0
  61. package/src/lib/utils/sync/LocalIndexedDb.ts +7 -5
  62. package/src/version.ts +3 -3
@@ -77,6 +77,7 @@ import {
77
77
  hasOwnProperty,
78
78
  last,
79
79
  lerp,
80
+ maxBy,
80
81
  sortById,
81
82
  sortByIndex,
82
83
  structuredClone,
@@ -2264,6 +2265,8 @@ export class Editor extends EventEmitter<TLEventMap> {
2264
2265
  const leaderPresence = this.getCollaborators().find((c) => c.userId === followingUserId)
2265
2266
  if (!leaderPresence) return null
2266
2267
 
2268
+ if (!leaderPresence.camera || !leaderPresence.screenBounds) return null
2269
+
2267
2270
  // Fit their viewport inside of our screen bounds
2268
2271
  // 1. calculate their viewport in page space
2269
2272
  const { w: lw, h: lh } = leaderPresence.screenBounds
@@ -3161,6 +3164,9 @@ export class Editor extends EventEmitter<TLEventMap> {
3161
3164
 
3162
3165
  if (!presence) return this
3163
3166
 
3167
+ const cursor = presence.cursor
3168
+ if (!cursor) return this
3169
+
3164
3170
  this.run(() => {
3165
3171
  // If we're following someone, stop following them
3166
3172
  if (this.getInstanceState().followingUserId !== null) {
@@ -3178,7 +3184,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3178
3184
  opts.animation = undefined
3179
3185
  }
3180
3186
 
3181
- this.centerOnPoint(presence.cursor, opts)
3187
+ this.centerOnPoint(cursor, opts)
3182
3188
 
3183
3189
  // Highlight the user's cursor
3184
3190
  const { highlightedUserIds } = this.getInstanceState()
@@ -3389,10 +3395,11 @@ export class Editor extends EventEmitter<TLEventMap> {
3389
3395
  if (!allPresenceRecords.length) return EMPTY_ARRAY
3390
3396
  const userIds = [...new Set(allPresenceRecords.map((c) => c.userId))].sort()
3391
3397
  return userIds.map((id) => {
3392
- const latestPresence = allPresenceRecords
3393
- .filter((c) => c.userId === id)
3394
- .sort((a, b) => b.lastActivityTimestamp - a.lastActivityTimestamp)[0]
3395
- return latestPresence
3398
+ const latestPresence = maxBy(
3399
+ allPresenceRecords.filter((c) => c.userId === id),
3400
+ (p) => p.lastActivityTimestamp ?? 0
3401
+ )
3402
+ return latestPresence!
3396
3403
  })
3397
3404
  }
3398
3405
 
@@ -4165,8 +4172,8 @@ export class Editor extends EventEmitter<TLEventMap> {
4165
4172
  * Upload an asset to the store's asset service, returning a URL that can be used to resolve the
4166
4173
  * asset.
4167
4174
  */
4168
- async uploadAsset(asset: TLAsset, file: File): Promise<string> {
4169
- return await this.store.props.assets.upload(asset, file)
4175
+ async uploadAsset(asset: TLAsset, file: File, abortSignal?: AbortSignal): Promise<string> {
4176
+ return await this.store.props.assets.upload(asset, file, abortSignal)
4170
4177
  }
4171
4178
 
4172
4179
  /* --------------------- Shapes --------------------- */
@@ -38,21 +38,13 @@ const spaceCharacterRegex = /\s/
38
38
 
39
39
  /** @public */
40
40
  export class TextManager {
41
- baseElm: HTMLDivElement
41
+ private baseElem: HTMLDivElement
42
42
 
43
43
  constructor(public editor: Editor) {
44
- const container = this.editor.getContainer()
45
-
46
- const elm = document.createElement('div')
47
- elm.classList.add('tl-text')
48
- elm.classList.add('tl-text-measure')
49
- elm.tabIndex = -1
50
- container.appendChild(elm)
51
-
52
- this.baseElm = elm
53
- editor.disposables.add(() => {
54
- elm.remove()
55
- })
44
+ this.baseElem = document.createElement('div')
45
+ this.baseElem.classList.add('tl-text')
46
+ this.baseElem.classList.add('tl-text-measure')
47
+ this.baseElem.tabIndex = -1
56
48
  }
57
49
 
58
50
  measureText(
@@ -75,8 +67,8 @@ export class TextManager {
75
67
  }
76
68
  ): BoxModel & { scrollWidth: number } {
77
69
  // Duplicate our base element; we don't need to clone deep
78
- const elm = this.baseElm?.cloneNode() as HTMLDivElement
79
- this.baseElm.insertAdjacentElement('afterend', elm)
70
+ const elm = this.baseElem.cloneNode() as HTMLDivElement
71
+ this.editor.getContainer().appendChild(elm)
80
72
 
81
73
  elm.setAttribute('dir', 'auto')
82
74
  // N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers")
@@ -223,8 +215,8 @@ export class TextManager {
223
215
  ): { text: string; box: BoxModel }[] {
224
216
  if (textToMeasure === '') return []
225
217
 
226
- const elm = this.baseElm?.cloneNode() as HTMLDivElement
227
- this.baseElm.insertAdjacentElement('afterend', elm)
218
+ const elm = this.baseElem.cloneNode() as HTMLDivElement
219
+ this.editor.getContainer().appendChild(elm)
228
220
 
229
221
  const elementWidth = Math.ceil(opts.width - opts.padding * 2)
230
222
  elm.setAttribute('dir', 'auto')
@@ -342,7 +342,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
342
342
  ): ReactElement | null | Promise<ReactElement | null>
343
343
 
344
344
  /** @internal */
345
- expandSelectionOutlinePx(shape: Shape): number {
345
+ expandSelectionOutlinePx(shape: Shape): number | Box {
346
346
  return 0
347
347
  }
348
348
 
@@ -94,7 +94,8 @@ async function getCurrentDocumentFontFaces() {
94
94
  if (rule instanceof CSSFontFaceRule) {
95
95
  fontFaces.push(parseCssFontFaces(rule.cssText, styleSheet.href ?? document.baseURI))
96
96
  } else if (rule instanceof CSSImportRule) {
97
- fontFaces.push(fetchCssFontFaces(rule.href))
97
+ const absoluteUrl = new URL(rule.href, rule.parentStyleSheet?.href ?? document.baseURI)
98
+ fontFaces.push(fetchCssFontFaces(absoluteUrl.href))
98
99
  }
99
100
  }
100
101
  } else if (styleSheet.href) {
@@ -1,3 +1,4 @@
1
+ import { useAtom } from '@tldraw/state-react'
1
2
  import { assert } from '@tldraw/utils'
2
3
  import { useCallback, useDebugValue, useLayoutEffect, useRef } from 'react'
3
4
 
@@ -42,3 +43,31 @@ export function useEvent<Args extends Array<unknown>, Result>(
42
43
  return fn(...args)
43
44
  }, [])
44
45
  }
46
+
47
+ /**
48
+ * like {@link useEvent}, but for use in reactive contexts - when the handler function changes, it
49
+ * will invalidate any reactive contexts that call it.
50
+ * @internal
51
+ */
52
+ export function useReactiveEvent<Args extends Array<unknown>, Result>(
53
+ handler: (...args: Args) => Result
54
+ ): (...args: Args) => Result {
55
+ const handlerAtom = useAtom<(...args: Args) => Result>('useReactiveEvent', () => handler)
56
+
57
+ // In a real implementation, this would run before layout effects
58
+ useLayoutEffect(() => {
59
+ handlerAtom.set(handler)
60
+ })
61
+
62
+ useDebugValue(handler)
63
+
64
+ return useCallback(
65
+ (...args: Args) => {
66
+ // In a real implementation, this would throw if called during render
67
+ const fn = handlerAtom.get()
68
+ assert(fn, 'fn does not exist')
69
+ return fn(...args)
70
+ },
71
+ [handlerAtom]
72
+ )
73
+ }
@@ -9,14 +9,16 @@ const STORE_PREFIX = 'TLDRAW_DOCUMENT_v2'
9
9
  const LEGACY_ASSET_STORE_PREFIX = 'TLDRAW_ASSET_STORE_v1'
10
10
  const dbNameIndexKey = 'TLDRAW_DB_NAME_INDEX_v2'
11
11
 
12
- const Table = {
12
+ /** @internal */
13
+ export const Table = {
13
14
  Records: 'records',
14
15
  Schema: 'schema',
15
16
  SessionState: 'session_state',
16
17
  Assets: 'assets',
17
18
  } as const
18
19
 
19
- type StoreName = (typeof Table)[keyof typeof Table]
20
+ /** @internal */
21
+ export type StoreName = (typeof Table)[keyof typeof Table]
20
22
 
21
23
  async function openLocalDb(persistenceKey: string) {
22
24
  const storeId = STORE_PREFIX + persistenceKey
@@ -109,7 +111,7 @@ export class LocalIndexedDb {
109
111
  })()
110
112
  }
111
113
 
112
- getDb() {
114
+ private getDb() {
113
115
  return this.getDbPromise
114
116
  }
115
117
 
@@ -288,14 +290,14 @@ export class LocalIndexedDb {
288
290
  })
289
291
  }
290
292
 
291
- async getAsset(assetId: string): Promise<Blob | undefined> {
293
+ async getAsset(assetId: string): Promise<File | undefined> {
292
294
  return await this.tx('readonly', [Table.Assets], async (tx) => {
293
295
  const assetsStore = tx.objectStore(Table.Assets)
294
296
  return await assetsStore.get(assetId)
295
297
  })
296
298
  }
297
299
 
298
- async storeAsset(assetId: string, blob: Blob) {
300
+ async storeAsset(assetId: string, blob: File) {
299
301
  await this.tx('readwrite', [Table.Assets], async (tx) => {
300
302
  const assetsStore = tx.objectStore(Table.Assets)
301
303
  await assetsStore.put(blob, assetId)
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.7.0-internal.acaf9fbbd3cc'
4
+ export const version = '3.8.0-canary.0bbbc9e0c959'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2024-12-05T12:26:25.860Z',
8
- patch: '2024-12-05T12:26:25.860Z',
7
+ minor: '2025-01-08T14:37:15.379Z',
8
+ patch: '2025-01-08T14:37:15.379Z',
9
9
  }