@tldraw/utils 4.1.0-next.b6dfe9bccde9 → 4.1.0-next.b73a0d46b63f

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 (160) hide show
  1. package/dist-cjs/index.d.ts +1350 -80
  2. package/dist-cjs/index.js +5 -5
  3. package/dist-cjs/lib/ExecutionQueue.js +79 -0
  4. package/dist-cjs/lib/ExecutionQueue.js.map +2 -2
  5. package/dist-cjs/lib/PerformanceTracker.js +43 -0
  6. package/dist-cjs/lib/PerformanceTracker.js.map +2 -2
  7. package/dist-cjs/lib/array.js +3 -1
  8. package/dist-cjs/lib/array.js.map +2 -2
  9. package/dist-cjs/lib/bind.js.map +2 -2
  10. package/dist-cjs/lib/cache.js +27 -5
  11. package/dist-cjs/lib/cache.js.map +2 -2
  12. package/dist-cjs/lib/control.js +12 -0
  13. package/dist-cjs/lib/control.js.map +2 -2
  14. package/dist-cjs/lib/debounce.js.map +2 -2
  15. package/dist-cjs/lib/error.js.map +2 -2
  16. package/dist-cjs/lib/file.js +76 -11
  17. package/dist-cjs/lib/file.js.map +2 -2
  18. package/dist-cjs/lib/function.js.map +2 -2
  19. package/dist-cjs/lib/hash.js.map +2 -2
  20. package/dist-cjs/lib/id.js.map +2 -2
  21. package/dist-cjs/lib/iterable.js.map +2 -2
  22. package/dist-cjs/lib/json-value.js.map +1 -1
  23. package/dist-cjs/lib/media/apng.js.map +2 -2
  24. package/dist-cjs/lib/media/avif.js.map +2 -2
  25. package/dist-cjs/lib/media/gif.js.map +2 -2
  26. package/dist-cjs/lib/media/media.js +130 -4
  27. package/dist-cjs/lib/media/media.js.map +2 -2
  28. package/dist-cjs/lib/media/png.js +141 -0
  29. package/dist-cjs/lib/media/png.js.map +2 -2
  30. package/dist-cjs/lib/media/webp.js +1 -0
  31. package/dist-cjs/lib/media/webp.js.map +2 -2
  32. package/dist-cjs/lib/network.js.map +2 -2
  33. package/dist-cjs/lib/number.js.map +2 -2
  34. package/dist-cjs/lib/object.js +1 -1
  35. package/dist-cjs/lib/object.js.map +2 -2
  36. package/dist-cjs/lib/perf.js.map +2 -2
  37. package/dist-cjs/lib/reordering.js.map +2 -2
  38. package/dist-cjs/lib/retry.js.map +2 -2
  39. package/dist-cjs/lib/sort.js.map +2 -2
  40. package/dist-cjs/lib/storage.js.map +2 -2
  41. package/dist-cjs/lib/stringEnum.js.map +2 -2
  42. package/dist-cjs/lib/throttle.js.map +2 -2
  43. package/dist-cjs/lib/timers.js +103 -4
  44. package/dist-cjs/lib/timers.js.map +2 -2
  45. package/dist-cjs/lib/types.js.map +1 -1
  46. package/dist-cjs/lib/url.js.map +2 -2
  47. package/dist-cjs/lib/value.js.map +2 -2
  48. package/dist-cjs/lib/version.js.map +2 -2
  49. package/dist-cjs/lib/warn.js.map +2 -2
  50. package/dist-esm/index.d.mts +1350 -80
  51. package/dist-esm/index.mjs +1 -1
  52. package/dist-esm/lib/ExecutionQueue.mjs +79 -0
  53. package/dist-esm/lib/ExecutionQueue.mjs.map +2 -2
  54. package/dist-esm/lib/PerformanceTracker.mjs +43 -0
  55. package/dist-esm/lib/PerformanceTracker.mjs.map +2 -2
  56. package/dist-esm/lib/array.mjs +3 -1
  57. package/dist-esm/lib/array.mjs.map +2 -2
  58. package/dist-esm/lib/bind.mjs.map +2 -2
  59. package/dist-esm/lib/cache.mjs +27 -5
  60. package/dist-esm/lib/cache.mjs.map +2 -2
  61. package/dist-esm/lib/control.mjs +12 -0
  62. package/dist-esm/lib/control.mjs.map +2 -2
  63. package/dist-esm/lib/debounce.mjs.map +2 -2
  64. package/dist-esm/lib/error.mjs.map +2 -2
  65. package/dist-esm/lib/file.mjs +76 -11
  66. package/dist-esm/lib/file.mjs.map +2 -2
  67. package/dist-esm/lib/function.mjs.map +2 -2
  68. package/dist-esm/lib/hash.mjs.map +2 -2
  69. package/dist-esm/lib/id.mjs.map +2 -2
  70. package/dist-esm/lib/iterable.mjs.map +2 -2
  71. package/dist-esm/lib/media/apng.mjs.map +2 -2
  72. package/dist-esm/lib/media/avif.mjs.map +2 -2
  73. package/dist-esm/lib/media/gif.mjs.map +2 -2
  74. package/dist-esm/lib/media/media.mjs +130 -4
  75. package/dist-esm/lib/media/media.mjs.map +2 -2
  76. package/dist-esm/lib/media/png.mjs +141 -0
  77. package/dist-esm/lib/media/png.mjs.map +2 -2
  78. package/dist-esm/lib/media/webp.mjs +1 -0
  79. package/dist-esm/lib/media/webp.mjs.map +2 -2
  80. package/dist-esm/lib/network.mjs.map +2 -2
  81. package/dist-esm/lib/number.mjs.map +2 -2
  82. package/dist-esm/lib/object.mjs.map +2 -2
  83. package/dist-esm/lib/perf.mjs.map +2 -2
  84. package/dist-esm/lib/reordering.mjs.map +2 -2
  85. package/dist-esm/lib/retry.mjs.map +2 -2
  86. package/dist-esm/lib/sort.mjs.map +2 -2
  87. package/dist-esm/lib/storage.mjs.map +2 -2
  88. package/dist-esm/lib/stringEnum.mjs.map +2 -2
  89. package/dist-esm/lib/throttle.mjs.map +2 -2
  90. package/dist-esm/lib/timers.mjs +103 -4
  91. package/dist-esm/lib/timers.mjs.map +2 -2
  92. package/dist-esm/lib/url.mjs.map +2 -2
  93. package/dist-esm/lib/value.mjs.map +2 -2
  94. package/dist-esm/lib/version.mjs.map +2 -2
  95. package/dist-esm/lib/warn.mjs.map +2 -2
  96. package/package.json +1 -1
  97. package/src/lib/ExecutionQueue.test.ts +162 -20
  98. package/src/lib/ExecutionQueue.ts +110 -1
  99. package/src/lib/PerformanceTracker.test.ts +124 -0
  100. package/src/lib/PerformanceTracker.ts +63 -1
  101. package/src/lib/array.test.ts +263 -1
  102. package/src/lib/array.ts +183 -14
  103. package/src/lib/bind.test.ts +47 -0
  104. package/src/lib/bind.ts +69 -4
  105. package/src/lib/cache.test.ts +73 -0
  106. package/src/lib/cache.ts +47 -6
  107. package/src/lib/control.test.ts +50 -0
  108. package/src/lib/control.ts +198 -9
  109. package/src/lib/debounce.ts +28 -3
  110. package/src/lib/error.test.ts +60 -0
  111. package/src/lib/error.ts +27 -1
  112. package/src/lib/file.test.ts +49 -0
  113. package/src/lib/file.ts +117 -12
  114. package/src/lib/function.ts +11 -0
  115. package/src/lib/hash.test.ts +99 -0
  116. package/src/lib/hash.ts +69 -2
  117. package/src/lib/id.test.ts +32 -0
  118. package/src/lib/id.ts +53 -5
  119. package/src/lib/iterable.test.ts +25 -0
  120. package/src/lib/iterable.ts +4 -5
  121. package/src/lib/json-value.ts +71 -4
  122. package/src/lib/media/apng.test.ts +67 -0
  123. package/src/lib/media/apng.ts +38 -21
  124. package/src/lib/media/avif.test.ts +26 -0
  125. package/src/lib/media/avif.ts +34 -0
  126. package/src/lib/media/gif.test.ts +52 -0
  127. package/src/lib/media/gif.ts +25 -2
  128. package/src/lib/media/media.test.ts +58 -0
  129. package/src/lib/media/media.ts +220 -11
  130. package/src/lib/media/png.ts +162 -1
  131. package/src/lib/media/webp.test.ts +81 -0
  132. package/src/lib/media/webp.ts +33 -1
  133. package/src/lib/network.test.ts +38 -0
  134. package/src/lib/network.ts +6 -0
  135. package/src/lib/number.test.ts +74 -0
  136. package/src/lib/number.ts +29 -5
  137. package/src/lib/object.test.ts +236 -0
  138. package/src/lib/object.ts +194 -14
  139. package/src/lib/perf.ts +75 -3
  140. package/src/lib/reordering.test.ts +168 -0
  141. package/src/lib/reordering.ts +62 -4
  142. package/src/lib/retry.test.ts +77 -0
  143. package/src/lib/retry.ts +47 -1
  144. package/src/lib/sort.test.ts +36 -0
  145. package/src/lib/sort.ts +22 -1
  146. package/src/lib/storage.test.ts +130 -0
  147. package/src/lib/storage.tsx +54 -8
  148. package/src/lib/stringEnum.ts +20 -1
  149. package/src/lib/throttle.ts +46 -8
  150. package/src/lib/timers.test.ts +75 -0
  151. package/src/lib/timers.ts +124 -5
  152. package/src/lib/types.ts +126 -4
  153. package/src/lib/url.test.ts +44 -0
  154. package/src/lib/url.ts +40 -1
  155. package/src/lib/value.test.ts +102 -0
  156. package/src/lib/value.ts +67 -3
  157. package/src/lib/version.test.ts +494 -56
  158. package/src/lib/version.ts +36 -1
  159. package/src/lib/warn.test.ts +64 -0
  160. package/src/lib/warn.ts +43 -2
@@ -2,34 +2,98 @@ class Timers {
2
2
  timeouts = /* @__PURE__ */ new Map();
3
3
  intervals = /* @__PURE__ */ new Map();
4
4
  rafs = /* @__PURE__ */ new Map();
5
+ /**
6
+ * Creates a new Timers instance with bound methods for safe callback usage.
7
+ * @example
8
+ * ```ts
9
+ * const timers = new Timers()
10
+ * // Methods are pre-bound, safe to use as callbacks
11
+ * element.addEventListener('click', timers.dispose)
12
+ * ```
13
+ */
5
14
  constructor() {
6
15
  this.setTimeout = this.setTimeout.bind(this);
7
16
  this.setInterval = this.setInterval.bind(this);
8
17
  this.requestAnimationFrame = this.requestAnimationFrame.bind(this);
9
18
  this.dispose = this.dispose.bind(this);
10
19
  }
11
- /** @public */
20
+ /**
21
+ * Creates a timeout that will be tracked under the specified context.
22
+ * @param contextId - The context identifier to group this timer under.
23
+ * @param handler - The function to execute when the timeout expires.
24
+ * @param timeout - The delay in milliseconds (default: 0).
25
+ * @param args - Additional arguments to pass to the handler.
26
+ * @returns The timer ID that can be used with clearTimeout.
27
+ * @example
28
+ * ```ts
29
+ * const timers = new Timers()
30
+ * const id = timers.setTimeout('autosave', () => save(), 5000)
31
+ * // Timer will be automatically cleared when 'autosave' context is disposed
32
+ * ```
33
+ * @public
34
+ */
12
35
  setTimeout(contextId, handler, timeout, ...args) {
13
36
  const id = window.setTimeout(handler, timeout, args);
14
37
  const current = this.timeouts.get(contextId) ?? [];
15
38
  this.timeouts.set(contextId, [...current, id]);
16
39
  return id;
17
40
  }
18
- /** @public */
41
+ /**
42
+ * Creates an interval that will be tracked under the specified context.
43
+ * @param contextId - The context identifier to group this timer under.
44
+ * @param handler - The function to execute repeatedly.
45
+ * @param timeout - The delay in milliseconds between executions (default: 0).
46
+ * @param args - Additional arguments to pass to the handler.
47
+ * @returns The interval ID that can be used with clearInterval.
48
+ * @example
49
+ * ```ts
50
+ * const timers = new Timers()
51
+ * const id = timers.setInterval('refresh', () => updateData(), 1000)
52
+ * // Interval will be automatically cleared when 'refresh' context is disposed
53
+ * ```
54
+ * @public
55
+ */
19
56
  setInterval(contextId, handler, timeout, ...args) {
20
57
  const id = window.setInterval(handler, timeout, args);
21
58
  const current = this.intervals.get(contextId) ?? [];
22
59
  this.intervals.set(contextId, [...current, id]);
23
60
  return id;
24
61
  }
25
- /** @public */
62
+ /**
63
+ * Requests an animation frame that will be tracked under the specified context.
64
+ * @param contextId - The context identifier to group this animation frame under.
65
+ * @param callback - The function to execute on the next animation frame.
66
+ * @returns The request ID that can be used with cancelAnimationFrame.
67
+ * @example
68
+ * ```ts
69
+ * const timers = new Timers()
70
+ * const id = timers.requestAnimationFrame('render', () => draw())
71
+ * // Animation frame will be automatically cancelled when 'render' context is disposed
72
+ * ```
73
+ * @public
74
+ */
26
75
  requestAnimationFrame(contextId, callback) {
27
76
  const id = window.requestAnimationFrame(callback);
28
77
  const current = this.rafs.get(contextId) ?? [];
29
78
  this.rafs.set(contextId, [...current, id]);
30
79
  return id;
31
80
  }
32
- /** @public */
81
+ /**
82
+ * Disposes of all timers associated with the specified context.
83
+ * Clears all timeouts, intervals, and animation frames for the given context ID.
84
+ * @param contextId - The context identifier whose timers should be cleared.
85
+ * @returns void
86
+ * @example
87
+ * ```ts
88
+ * const timers = new Timers()
89
+ * timers.setTimeout('ui', () => console.log('timeout'), 1000)
90
+ * timers.setInterval('ui', () => console.log('interval'), 500)
91
+ *
92
+ * // Clear all 'ui' context timers
93
+ * timers.dispose('ui')
94
+ * ```
95
+ * @public
96
+ */
33
97
  dispose(contextId) {
34
98
  this.timeouts.get(contextId)?.forEach((id) => clearTimeout(id));
35
99
  this.intervals.get(contextId)?.forEach((id) => clearInterval(id));
@@ -38,11 +102,46 @@ class Timers {
38
102
  this.intervals.delete(contextId);
39
103
  this.rafs.delete(contextId);
40
104
  }
105
+ /**
106
+ * Disposes of all timers across all contexts.
107
+ * Clears every timeout, interval, and animation frame managed by this instance.
108
+ * @returns void
109
+ * @example
110
+ * ```ts
111
+ * const timers = new Timers()
112
+ * timers.setTimeout('ui', () => console.log('ui'), 1000)
113
+ * timers.setTimeout('background', () => console.log('bg'), 2000)
114
+ *
115
+ * // Clear everything
116
+ * timers.disposeAll()
117
+ * ```
118
+ * @public
119
+ */
41
120
  disposeAll() {
42
121
  for (const contextId of this.timeouts.keys()) {
43
122
  this.dispose(contextId);
44
123
  }
45
124
  }
125
+ /**
126
+ * Returns an object with timer methods bound to a specific context.
127
+ * Convenient for getting context-specific timer functions without repeatedly passing the contextId.
128
+ * @param contextId - The context identifier to bind the returned methods to.
129
+ * @returns An object with setTimeout, setInterval, requestAnimationFrame, and dispose methods bound to the context.
130
+ * @example
131
+ * ```ts
132
+ * const timers = new Timers()
133
+ * const uiTimers = timers.forContext('ui')
134
+ *
135
+ * // These are equivalent to calling timers.setTimeout('ui', ...)
136
+ * uiTimers.setTimeout(() => console.log('timeout'), 1000)
137
+ * uiTimers.setInterval(() => console.log('interval'), 500)
138
+ * uiTimers.requestAnimationFrame(() => console.log('frame'))
139
+ *
140
+ * // Dispose only this context
141
+ * uiTimers.dispose()
142
+ * ```
143
+ * @public
144
+ */
46
145
  forContext(contextId) {
47
146
  return {
48
147
  setTimeout: (handler, timeout, ...args) => this.setTimeout(contextId, handler, timeout, args),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/timers.ts"],
4
- "sourcesContent": ["/* eslint-disable no-restricted-properties */\n\n/** @public */\nexport class Timers {\n\tprivate timeouts = new Map<string, number[]>()\n\tprivate intervals = new Map<string, number[]>()\n\tprivate rafs = new Map<string, number[]>()\n\n\tconstructor() {\n\t\tthis.setTimeout = this.setTimeout.bind(this)\n\t\tthis.setInterval = this.setInterval.bind(this)\n\t\tthis.requestAnimationFrame = this.requestAnimationFrame.bind(this)\n\t\tthis.dispose = this.dispose.bind(this)\n\t}\n\n\t/** @public */\n\tsetTimeout(contextId: string, handler: TimerHandler, timeout?: number, ...args: any[]): number {\n\t\tconst id = window.setTimeout(handler, timeout, args)\n\t\tconst current = this.timeouts.get(contextId) ?? []\n\t\tthis.timeouts.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/** @public */\n\tsetInterval(contextId: string, handler: TimerHandler, timeout?: number, ...args: any[]): number {\n\t\tconst id = window.setInterval(handler, timeout, args)\n\t\tconst current = this.intervals.get(contextId) ?? []\n\t\tthis.intervals.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/** @public */\n\trequestAnimationFrame(contextId: string, callback: FrameRequestCallback): number {\n\t\tconst id = window.requestAnimationFrame(callback)\n\t\tconst current = this.rafs.get(contextId) ?? []\n\t\tthis.rafs.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/** @public */\n\tdispose(contextId: string) {\n\t\tthis.timeouts.get(contextId)?.forEach((id) => clearTimeout(id))\n\t\tthis.intervals.get(contextId)?.forEach((id) => clearInterval(id))\n\t\tthis.rafs.get(contextId)?.forEach((id) => cancelAnimationFrame(id))\n\n\t\tthis.timeouts.delete(contextId)\n\t\tthis.intervals.delete(contextId)\n\t\tthis.rafs.delete(contextId)\n\t}\n\n\tdisposeAll() {\n\t\tfor (const contextId of this.timeouts.keys()) {\n\t\t\tthis.dispose(contextId)\n\t\t}\n\t}\n\n\tforContext(contextId: string) {\n\t\treturn {\n\t\t\tsetTimeout: (handler: TimerHandler, timeout?: number, ...args: any[]) =>\n\t\t\t\tthis.setTimeout(contextId, handler, timeout, args),\n\t\t\tsetInterval: (handler: TimerHandler, timeout?: number, ...args: any[]) =>\n\t\t\t\tthis.setInterval(contextId, handler, timeout, args),\n\t\t\trequestAnimationFrame: (callback: FrameRequestCallback) =>\n\t\t\t\tthis.requestAnimationFrame(contextId, callback),\n\t\t\tdispose: () => this.dispose(contextId),\n\t\t}\n\t}\n}\n"],
5
- "mappings": "AAGO,MAAM,OAAO;AAAA,EACX,WAAW,oBAAI,IAAsB;AAAA,EACrC,YAAY,oBAAI,IAAsB;AAAA,EACtC,OAAO,oBAAI,IAAsB;AAAA,EAEzC,cAAc;AACb,SAAK,aAAa,KAAK,WAAW,KAAK,IAAI;AAC3C,SAAK,cAAc,KAAK,YAAY,KAAK,IAAI;AAC7C,SAAK,wBAAwB,KAAK,sBAAsB,KAAK,IAAI;AACjE,SAAK,UAAU,KAAK,QAAQ,KAAK,IAAI;AAAA,EACtC;AAAA;AAAA,EAGA,WAAW,WAAmB,SAAuB,YAAqB,MAAqB;AAC9F,UAAM,KAAK,OAAO,WAAW,SAAS,SAAS,IAAI;AACnD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS,KAAK,CAAC;AACjD,SAAK,SAAS,IAAI,WAAW,CAAC,GAAG,SAAS,EAAE,CAAC;AAC7C,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,YAAY,WAAmB,SAAuB,YAAqB,MAAqB;AAC/F,UAAM,KAAK,OAAO,YAAY,SAAS,SAAS,IAAI;AACpD,UAAM,UAAU,KAAK,UAAU,IAAI,SAAS,KAAK,CAAC;AAClD,SAAK,UAAU,IAAI,WAAW,CAAC,GAAG,SAAS,EAAE,CAAC;AAC9C,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,sBAAsB,WAAmB,UAAwC;AAChF,UAAM,KAAK,OAAO,sBAAsB,QAAQ;AAChD,UAAM,UAAU,KAAK,KAAK,IAAI,SAAS,KAAK,CAAC;AAC7C,SAAK,KAAK,IAAI,WAAW,CAAC,GAAG,SAAS,EAAE,CAAC;AACzC,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,QAAQ,WAAmB;AAC1B,SAAK,SAAS,IAAI,SAAS,GAAG,QAAQ,CAAC,OAAO,aAAa,EAAE,CAAC;AAC9D,SAAK,UAAU,IAAI,SAAS,GAAG,QAAQ,CAAC,OAAO,cAAc,EAAE,CAAC;AAChE,SAAK,KAAK,IAAI,SAAS,GAAG,QAAQ,CAAC,OAAO,qBAAqB,EAAE,CAAC;AAElE,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,UAAU,OAAO,SAAS;AAC/B,SAAK,KAAK,OAAO,SAAS;AAAA,EAC3B;AAAA,EAEA,aAAa;AACZ,eAAW,aAAa,KAAK,SAAS,KAAK,GAAG;AAC7C,WAAK,QAAQ,SAAS;AAAA,IACvB;AAAA,EACD;AAAA,EAEA,WAAW,WAAmB;AAC7B,WAAO;AAAA,MACN,YAAY,CAAC,SAAuB,YAAqB,SACxD,KAAK,WAAW,WAAW,SAAS,SAAS,IAAI;AAAA,MAClD,aAAa,CAAC,SAAuB,YAAqB,SACzD,KAAK,YAAY,WAAW,SAAS,SAAS,IAAI;AAAA,MACnD,uBAAuB,CAAC,aACvB,KAAK,sBAAsB,WAAW,QAAQ;AAAA,MAC/C,SAAS,MAAM,KAAK,QAAQ,SAAS;AAAA,IACtC;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["/* eslint-disable no-restricted-properties */\n\n/**\n * A utility class for managing timeouts, intervals, and animation frames with context-based organization and automatic cleanup.\n * Helps prevent memory leaks by organizing timers into named contexts that can be cleared together.\n * @example\n * ```ts\n * const timers = new Timers()\n *\n * // Set timers with context organization\n * timers.setTimeout('ui', () => console.log('Auto save'), 5000)\n * timers.setInterval('ui', () => console.log('Refresh'), 1000)\n * timers.requestAnimationFrame('ui', () => console.log('Render'))\n *\n * // Clear all timers for a context\n * timers.dispose('ui')\n *\n * // Or get context-bound functions\n * const uiTimers = timers.forContext('ui')\n * uiTimers.setTimeout(() => console.log('Contextual timeout'), 1000)\n * ```\n * @public\n */\nexport class Timers {\n\tprivate timeouts = new Map<string, number[]>()\n\tprivate intervals = new Map<string, number[]>()\n\tprivate rafs = new Map<string, number[]>()\n\n\t/**\n\t * Creates a new Timers instance with bound methods for safe callback usage.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * // Methods are pre-bound, safe to use as callbacks\n\t * element.addEventListener('click', timers.dispose)\n\t * ```\n\t */\n\tconstructor() {\n\t\tthis.setTimeout = this.setTimeout.bind(this)\n\t\tthis.setInterval = this.setInterval.bind(this)\n\t\tthis.requestAnimationFrame = this.requestAnimationFrame.bind(this)\n\t\tthis.dispose = this.dispose.bind(this)\n\t}\n\n\t/**\n\t * Creates a timeout that will be tracked under the specified context.\n\t * @param contextId - The context identifier to group this timer under.\n\t * @param handler - The function to execute when the timeout expires.\n\t * @param timeout - The delay in milliseconds (default: 0).\n\t * @param args - Additional arguments to pass to the handler.\n\t * @returns The timer ID that can be used with clearTimeout.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const id = timers.setTimeout('autosave', () => save(), 5000)\n\t * // Timer will be automatically cleared when 'autosave' context is disposed\n\t * ```\n\t * @public\n\t */\n\tsetTimeout(contextId: string, handler: TimerHandler, timeout?: number, ...args: any[]): number {\n\t\tconst id = window.setTimeout(handler, timeout, args)\n\t\tconst current = this.timeouts.get(contextId) ?? []\n\t\tthis.timeouts.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/**\n\t * Creates an interval that will be tracked under the specified context.\n\t * @param contextId - The context identifier to group this timer under.\n\t * @param handler - The function to execute repeatedly.\n\t * @param timeout - The delay in milliseconds between executions (default: 0).\n\t * @param args - Additional arguments to pass to the handler.\n\t * @returns The interval ID that can be used with clearInterval.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const id = timers.setInterval('refresh', () => updateData(), 1000)\n\t * // Interval will be automatically cleared when 'refresh' context is disposed\n\t * ```\n\t * @public\n\t */\n\tsetInterval(contextId: string, handler: TimerHandler, timeout?: number, ...args: any[]): number {\n\t\tconst id = window.setInterval(handler, timeout, args)\n\t\tconst current = this.intervals.get(contextId) ?? []\n\t\tthis.intervals.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/**\n\t * Requests an animation frame that will be tracked under the specified context.\n\t * @param contextId - The context identifier to group this animation frame under.\n\t * @param callback - The function to execute on the next animation frame.\n\t * @returns The request ID that can be used with cancelAnimationFrame.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const id = timers.requestAnimationFrame('render', () => draw())\n\t * // Animation frame will be automatically cancelled when 'render' context is disposed\n\t * ```\n\t * @public\n\t */\n\trequestAnimationFrame(contextId: string, callback: FrameRequestCallback): number {\n\t\tconst id = window.requestAnimationFrame(callback)\n\t\tconst current = this.rafs.get(contextId) ?? []\n\t\tthis.rafs.set(contextId, [...current, id])\n\t\treturn id\n\t}\n\n\t/**\n\t * Disposes of all timers associated with the specified context.\n\t * Clears all timeouts, intervals, and animation frames for the given context ID.\n\t * @param contextId - The context identifier whose timers should be cleared.\n\t * @returns void\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * timers.setTimeout('ui', () => console.log('timeout'), 1000)\n\t * timers.setInterval('ui', () => console.log('interval'), 500)\n\t *\n\t * // Clear all 'ui' context timers\n\t * timers.dispose('ui')\n\t * ```\n\t * @public\n\t */\n\tdispose(contextId: string) {\n\t\tthis.timeouts.get(contextId)?.forEach((id) => clearTimeout(id))\n\t\tthis.intervals.get(contextId)?.forEach((id) => clearInterval(id))\n\t\tthis.rafs.get(contextId)?.forEach((id) => cancelAnimationFrame(id))\n\n\t\tthis.timeouts.delete(contextId)\n\t\tthis.intervals.delete(contextId)\n\t\tthis.rafs.delete(contextId)\n\t}\n\n\t/**\n\t * Disposes of all timers across all contexts.\n\t * Clears every timeout, interval, and animation frame managed by this instance.\n\t * @returns void\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * timers.setTimeout('ui', () => console.log('ui'), 1000)\n\t * timers.setTimeout('background', () => console.log('bg'), 2000)\n\t *\n\t * // Clear everything\n\t * timers.disposeAll()\n\t * ```\n\t * @public\n\t */\n\tdisposeAll() {\n\t\tfor (const contextId of this.timeouts.keys()) {\n\t\t\tthis.dispose(contextId)\n\t\t}\n\t}\n\n\t/**\n\t * Returns an object with timer methods bound to a specific context.\n\t * Convenient for getting context-specific timer functions without repeatedly passing the contextId.\n\t * @param contextId - The context identifier to bind the returned methods to.\n\t * @returns An object with setTimeout, setInterval, requestAnimationFrame, and dispose methods bound to the context.\n\t * @example\n\t * ```ts\n\t * const timers = new Timers()\n\t * const uiTimers = timers.forContext('ui')\n\t *\n\t * // These are equivalent to calling timers.setTimeout('ui', ...)\n\t * uiTimers.setTimeout(() => console.log('timeout'), 1000)\n\t * uiTimers.setInterval(() => console.log('interval'), 500)\n\t * uiTimers.requestAnimationFrame(() => console.log('frame'))\n\t *\n\t * // Dispose only this context\n\t * uiTimers.dispose()\n\t * ```\n\t * @public\n\t */\n\tforContext(contextId: string) {\n\t\treturn {\n\t\t\tsetTimeout: (handler: TimerHandler, timeout?: number, ...args: any[]) =>\n\t\t\t\tthis.setTimeout(contextId, handler, timeout, args),\n\t\t\tsetInterval: (handler: TimerHandler, timeout?: number, ...args: any[]) =>\n\t\t\t\tthis.setInterval(contextId, handler, timeout, args),\n\t\t\trequestAnimationFrame: (callback: FrameRequestCallback) =>\n\t\t\t\tthis.requestAnimationFrame(contextId, callback),\n\t\t\tdispose: () => this.dispose(contextId),\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/url.ts"],
4
- "sourcesContent": ["/** @public */\nexport const safeParseUrl = (url: string, baseUrl?: string | URL) => {\n\ttry {\n\t\treturn new URL(url, baseUrl)\n\t} catch {\n\t\treturn\n\t}\n}\n"],
5
- "mappings": "AACO,MAAM,eAAe,CAAC,KAAa,YAA2B;AACpE,MAAI;AACH,WAAO,IAAI,IAAI,KAAK,OAAO;AAAA,EAC5B,QAAQ;AACP;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["/**\n * Safely parses a URL string without throwing exceptions on invalid input.\n * Returns a URL object for valid URLs or undefined for invalid ones.\n *\n * @param url - The URL string to parse\n * @param baseUrl - Optional base URL to resolve relative URLs against\n * @returns A URL object if parsing succeeds, undefined if it fails\n *\n * @example\n * ```ts\n * // Valid absolute URL\n * const url1 = safeParseUrl('https://example.com')\n * if (url1) {\n * console.log(`Valid URL: ${url1.href}`) // \"Valid URL: https://example.com/\"\n * }\n *\n * // Invalid URL\n * const url2 = safeParseUrl('not-a-url')\n * console.log(url2) // undefined\n *\n * // Relative URL with base\n * const url3 = safeParseUrl('/path', 'https://example.com')\n * if (url3) {\n * console.log(url3.href) // \"https://example.com/path\"\n * }\n *\n * // Error handling\n * function handleUserUrl(input: string) {\n * const url = safeParseUrl(input)\n * if (url) {\n * return url\n * } else {\n * console.log('Invalid URL provided')\n * return null\n * }\n * }\n * ```\n *\n * @public\n */\nexport const safeParseUrl = (url: string, baseUrl?: string | URL) => {\n\ttry {\n\t\treturn new URL(url, baseUrl)\n\t} catch {\n\t\treturn\n\t}\n}\n"],
5
+ "mappings": "AAwCO,MAAM,eAAe,CAAC,KAAa,YAA2B;AACpE,MAAI;AACH,WAAO,IAAI,IAAI,KAAK,OAAO;AAAA,EAC5B,QAAQ;AACP;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/value.ts"],
4
- "sourcesContent": ["/**\n * Get whether a value is not undefined.\n *\n * @param value - The value to check.\n * @public\n */\nexport function isDefined<T>(value: T): value is typeof value extends undefined ? never : T {\n\treturn value !== undefined\n}\n\n/**\n * Get whether a value is null\n *\n * @param value - The value to check.\n * @public\n */\nexport function isNonNull<T>(value: T): value is typeof value extends null ? never : T {\n\treturn value !== null\n}\n\n/**\n * Get whether a value is nullish (null, undefined).\n *\n * @param value - The value to check.\n * @public\n */\nexport function isNonNullish<T>(\n\tvalue: T\n): value is typeof value extends undefined ? never : typeof value extends null ? never : T {\n\treturn value !== null && value !== undefined\n}\n\nfunction getStructuredClone(): [<T>(i: T) => T, boolean] {\n\tif (typeof globalThis !== 'undefined' && (globalThis as any).structuredClone) {\n\t\treturn [globalThis.structuredClone as <T>(i: T) => T, true]\n\t}\n\n\tif (typeof global !== 'undefined' && (global as any).structuredClone) {\n\t\treturn [global.structuredClone as <T>(i: T) => T, true]\n\t}\n\n\tif (typeof window !== 'undefined' && (window as any).structuredClone) {\n\t\treturn [window.structuredClone as <T>(i: T) => T, true]\n\t}\n\n\treturn [<T>(i: T): T => (i ? JSON.parse(JSON.stringify(i)) : i), false]\n}\n\nconst _structuredClone = getStructuredClone()\n\n/**\n * Create a deep copy of a value. Uses the structuredClone API if available, otherwise uses JSON.parse(JSON.stringify()).\n *\n * @param i - The value to clone.\n * @public */\nexport const structuredClone = _structuredClone[0]\n\n/**\n * @internal\n */\nexport const isNativeStructuredClone = _structuredClone[1]\n\n/**\n * When we patch structuredClone in jsdom for testing (see https://github.com/jsdom/jsdom/issues/3363),\n * the Object that is used as a prototype for the cloned object is not the same as the Object in\n * the code under test (that comes from jsdom's fake global context). This constant is used in\n * our code to work around this case.\n *\n * This is also the case for Array prototype, but that problem can be worked around with an\n * Array.isArray() check.\n * @internal\n */\nexport const STRUCTURED_CLONE_OBJECT_PROTOTYPE = Object.getPrototypeOf(structuredClone({}))\n"],
5
- "mappings": "AAMO,SAAS,UAAa,OAA+D;AAC3F,SAAO,UAAU;AAClB;AAQO,SAAS,UAAa,OAA0D;AACtF,SAAO,UAAU;AAClB;AAQO,SAAS,aACf,OAC0F;AAC1F,SAAO,UAAU,QAAQ,UAAU;AACpC;AAEA,SAAS,qBAAgD;AACxD,MAAI,OAAO,eAAe,eAAgB,WAAmB,iBAAiB;AAC7E,WAAO,CAAC,WAAW,iBAAmC,IAAI;AAAA,EAC3D;AAEA,MAAI,OAAO,WAAW,eAAgB,OAAe,iBAAiB;AACrE,WAAO,CAAC,OAAO,iBAAmC,IAAI;AAAA,EACvD;AAEA,MAAI,OAAO,WAAW,eAAgB,OAAe,iBAAiB;AACrE,WAAO,CAAC,OAAO,iBAAmC,IAAI;AAAA,EACvD;AAEA,SAAO,CAAC,CAAI,MAAa,IAAI,KAAK,MAAM,KAAK,UAAU,CAAC,CAAC,IAAI,GAAI,KAAK;AACvE;AAEA,MAAM,mBAAmB,mBAAmB;AAOrC,MAAM,kBAAkB,iBAAiB,CAAC;AAK1C,MAAM,0BAA0B,iBAAiB,CAAC;AAYlD,MAAM,oCAAoC,OAAO,eAAe,gBAAgB,CAAC,CAAC,CAAC;",
4
+ "sourcesContent": ["/**\n * Get whether a value is not undefined.\n *\n * @param value - The value to check.\n * @returns True if the value is not undefined, with proper type narrowing.\n * @example\n * ```ts\n * const maybeString: string | undefined = getValue()\n *\n * if (isDefined(maybeString)) {\n * // TypeScript knows maybeString is string, not undefined\n * console.log(maybeString.toUpperCase())\n * }\n *\n * // Filter undefined values from arrays\n * const values = [1, undefined, 2, undefined, 3]\n * const definedValues = values.filter(isDefined) // [1, 2, 3]\n * ```\n * @public\n */\nexport function isDefined<T>(value: T): value is typeof value extends undefined ? never : T {\n\treturn value !== undefined\n}\n\n/**\n * Get whether a value is not null.\n *\n * @param value - The value to check.\n * @returns True if the value is not null, with proper type narrowing.\n * @example\n * ```ts\n * const maybeString: string | null = getValue()\n *\n * if (isNonNull(maybeString)) {\n * // TypeScript knows maybeString is string, not null\n * console.log(maybeString.length)\n * }\n *\n * // Filter null values from arrays\n * const values = [\"a\", null, \"b\", null, \"c\"]\n * const nonNullValues = values.filter(isNonNull) // [\"a\", \"b\", \"c\"]\n * ```\n * @public\n */\nexport function isNonNull<T>(value: T): value is typeof value extends null ? never : T {\n\treturn value !== null\n}\n\n/**\n * Get whether a value is not nullish (not null and not undefined).\n *\n * @param value - The value to check.\n * @returns True if the value is neither null nor undefined, with proper type narrowing.\n * @example\n * ```ts\n * const maybeString: string | null | undefined = getValue()\n *\n * if (isNonNullish(maybeString)) {\n * // TypeScript knows maybeString is string, not null or undefined\n * console.log(maybeString.charAt(0))\n * }\n *\n * // Filter nullish values from arrays\n * const values = [\"hello\", null, \"world\", undefined, \"!\"]\n * const cleanValues = values.filter(isNonNullish) // [\"hello\", \"world\", \"!\"]\n * ```\n * @public\n */\nexport function isNonNullish<T>(\n\tvalue: T\n): value is typeof value extends undefined ? never : typeof value extends null ? never : T {\n\treturn value !== null && value !== undefined\n}\n\nfunction getStructuredClone(): [<T>(i: T) => T, boolean] {\n\tif (typeof globalThis !== 'undefined' && (globalThis as any).structuredClone) {\n\t\treturn [globalThis.structuredClone as <T>(i: T) => T, true]\n\t}\n\n\tif (typeof global !== 'undefined' && (global as any).structuredClone) {\n\t\treturn [global.structuredClone as <T>(i: T) => T, true]\n\t}\n\n\tif (typeof window !== 'undefined' && (window as any).structuredClone) {\n\t\treturn [window.structuredClone as <T>(i: T) => T, true]\n\t}\n\n\treturn [<T>(i: T): T => (i ? JSON.parse(JSON.stringify(i)) : i), false]\n}\n\nconst _structuredClone = getStructuredClone()\n\n/**\n * Create a deep copy of a value. Uses the structuredClone API if available, otherwise uses JSON.parse(JSON.stringify()).\n *\n * @param i - The value to clone.\n * @returns A deep copy of the input value.\n * @example\n * ```ts\n * const original = { a: 1, b: { c: 2 } }\n * const copy = structuredClone(original)\n *\n * copy.b.c = 3\n * console.log(original.b.c) // 2 (unchanged)\n * console.log(copy.b.c) // 3\n *\n * // Works with complex objects\n * const complexObject = {\n * date: new Date(),\n * array: [1, 2, 3],\n * nested: { deep: { value: \"test\" } }\n * }\n * const cloned = structuredClone(complexObject)\n * ```\n * @public\n */\nexport const structuredClone = _structuredClone[0]\n\n/**\n * Whether the current environment has native structuredClone support.\n * @returns True if using native structuredClone, false if using JSON fallback.\n * @internal\n */\nexport const isNativeStructuredClone = _structuredClone[1]\n\n/**\n * The prototype object used by structuredClone for cloned objects.\n * When we patch structuredClone in jsdom for testing (see https://github.com/jsdom/jsdom/issues/3363),\n * the Object that is used as a prototype for the cloned object is not the same as the Object in\n * the code under test (that comes from jsdom's fake global context). This constant is used in\n * our code to work around this case.\n *\n * This is also the case for Array prototype, but that problem can be worked around with an\n * Array.isArray() check.\n * @internal\n */\nexport const STRUCTURED_CLONE_OBJECT_PROTOTYPE = Object.getPrototypeOf(structuredClone({}))\n"],
5
+ "mappings": "AAoBO,SAAS,UAAa,OAA+D;AAC3F,SAAO,UAAU;AAClB;AAsBO,SAAS,UAAa,OAA0D;AACtF,SAAO,UAAU;AAClB;AAsBO,SAAS,aACf,OAC0F;AAC1F,SAAO,UAAU,QAAQ,UAAU;AACpC;AAEA,SAAS,qBAAgD;AACxD,MAAI,OAAO,eAAe,eAAgB,WAAmB,iBAAiB;AAC7E,WAAO,CAAC,WAAW,iBAAmC,IAAI;AAAA,EAC3D;AAEA,MAAI,OAAO,WAAW,eAAgB,OAAe,iBAAiB;AACrE,WAAO,CAAC,OAAO,iBAAmC,IAAI;AAAA,EACvD;AAEA,MAAI,OAAO,WAAW,eAAgB,OAAe,iBAAiB;AACrE,WAAO,CAAC,OAAO,iBAAmC,IAAI;AAAA,EACvD;AAEA,SAAO,CAAC,CAAI,MAAa,IAAI,KAAK,MAAM,KAAK,UAAU,CAAC,CAAC,IAAI,GAAI,KAAK;AACvE;AAEA,MAAM,mBAAmB,mBAAmB;AA0BrC,MAAM,kBAAkB,iBAAiB,CAAC;AAO1C,MAAM,0BAA0B,iBAAiB,CAAC;AAalD,MAAM,oCAAoC,OAAO,eAAe,gBAAgB,CAAC,CAAC,CAAC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["interface TldrawLibraryVersion {\n\tname: string\n\tversion: string\n\tmodules: string\n}\n\ninterface TldrawLibraryVersionInfo {\n\tversions: TldrawLibraryVersion[]\n\tdidWarn: boolean\n\tscheduledNotice: number | NodeJS.Timeout | null\n}\n\nconst TLDRAW_LIBRARY_VERSION_KEY = '__TLDRAW_LIBRARY_VERSIONS__' as const\n\n// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace\ndeclare module globalThis {\n\texport const __TLDRAW_LIBRARY_VERSIONS__: TldrawLibraryVersionInfo\n}\n\nfunction getLibraryVersions(): TldrawLibraryVersionInfo {\n\tif (globalThis[TLDRAW_LIBRARY_VERSION_KEY]) {\n\t\treturn globalThis[TLDRAW_LIBRARY_VERSION_KEY]\n\t}\n\n\tconst info: TldrawLibraryVersionInfo = {\n\t\tversions: [],\n\t\tdidWarn: false,\n\t\tscheduledNotice: null,\n\t}\n\n\tObject.defineProperty(globalThis, TLDRAW_LIBRARY_VERSION_KEY, {\n\t\tvalue: info,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n\n\treturn info\n}\n\nexport function clearRegisteredVersionsForTests() {\n\tconst info = getLibraryVersions()\n\tinfo.versions = []\n\tinfo.didWarn = false\n\tif (info.scheduledNotice) {\n\t\tclearTimeout(info.scheduledNotice)\n\t\tinfo.scheduledNotice = null\n\t}\n}\n\n/** @internal */\nexport function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {\n\tif (!name || !version || !modules) {\n\t\tif ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {\n\t\t\tthrow new Error('Missing name/version/module system in built version of tldraw library')\n\t\t}\n\t\treturn\n\t}\n\n\tconst info = getLibraryVersions()\n\tinfo.versions.push({ name, version, modules })\n\n\tif (!info.scheduledNotice) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\tinfo.scheduledNotice = setTimeout(() => {\n\t\t\t\tinfo.scheduledNotice = null\n\t\t\t\tcheckLibraryVersions(info)\n\t\t\t}, 100)\n\t\t} catch {\n\t\t\t// some environments (e.g. cloudflare workers) don't support setTimeout immediately, only in a handler.\n\t\t\t// in this case, we'll just check immediately.\n\t\t\tcheckLibraryVersions(info)\n\t\t}\n\t}\n}\n\nfunction checkLibraryVersions(info: TldrawLibraryVersionInfo) {\n\tif (!info.versions.length) return\n\tif (info.didWarn) return\n\n\tconst sorted = info.versions.sort((a, b) => compareVersions(a.version, b.version))\n\tconst latestVersion = sorted[sorted.length - 1].version\n\n\tconst matchingVersions = new Set<string>()\n\tconst nonMatchingVersions = new Map<string, Set<string>>()\n\tfor (const lib of sorted) {\n\t\tif (nonMatchingVersions.has(lib.name)) {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (lib.version === latestVersion) {\n\t\t\tmatchingVersions.add(lib.name)\n\t\t} else {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t}\n\t}\n\n\tif (nonMatchingVersions.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t`The latest version you have installed is ${format(`v${latestVersion}`, ['bold', 'textBlue'])}. The following libraries are on the latest version:`,\n\t\t\t...Array.from(matchingVersions, (name) => ` \u2022 \u2705 ${format(name, ['bold'])}`),\n\t\t\t'',\n\t\t\t`The following libraries are not on the latest version, or have multiple versions installed:`,\n\t\t\t...Array.from(nonMatchingVersions, ([name, versions]) => {\n\t\t\t\tconst sortedVersions = Array.from(versions)\n\t\t\t\t\t.sort(compareVersions)\n\t\t\t\t\t.map((v) => format(`v${v}`, v === latestVersion ? ['textGreen'] : ['textRed']))\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} (${sortedVersions.join(', ')})`\n\t\t\t}),\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n\n\t// at this point, we know that everything has the same version. there may still be duplicates though!\n\tconst potentialDuplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const lib of sorted) {\n\t\tentry(potentialDuplicates, lib.name, { version: lib.version, modules: [] }).modules.push(\n\t\t\tlib.modules\n\t\t)\n\t}\n\n\tconst duplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const [name, lib] of potentialDuplicates) {\n\t\tif (lib.modules.length > 1) duplicates.set(name, lib)\n\t}\n\n\tif (duplicates.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. ', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t'This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.',\n\t\t\t'',\n\t\t\t'The following libraries have been imported multiple times:',\n\t\t\t...Array.from(duplicates, ([name, lib]) => {\n\t\t\t\tconst modules = lib.modules\n\t\t\t\t\t.map((m, i) => (m === 'esm' ? ` ${i + 1}. ES Modules` : ` ${i + 1}. CommonJS`))\n\t\t\t\t\t.join('\\n')\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} v${lib.version}: \\n${modules}`\n\t\t\t}),\n\t\t\t'',\n\t\t\t'You should configure your bundler to only import one version of each library.',\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n}\n\nfunction compareVersions(a: string, b: string) {\n\tconst aMatch = a.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\tconst bMatch = b.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\n\tif (!aMatch || !bMatch) return a.localeCompare(b)\n\tif (aMatch[1] !== bMatch[1]) return Number(aMatch[1]) - Number(bMatch[1])\n\tif (aMatch[2] !== bMatch[2]) return Number(aMatch[2]) - Number(bMatch[2])\n\tif (aMatch[3] !== bMatch[3]) return Number(aMatch[3]) - Number(bMatch[3])\n\tif (aMatch[4] && bMatch[4]) return aMatch[4].localeCompare(bMatch[4])\n\tif (aMatch[4]) return 1\n\tif (bMatch[4]) return -1\n\treturn 0\n}\n\nconst formats = {\n\tbold: '1',\n\ttextBlue: '94',\n\ttextRed: '31',\n\ttextGreen: '32',\n\tbgRed: '41',\n\ttextWhite: '97',\n} as const\nfunction format(value: string, formatters: (keyof typeof formats)[] = []) {\n\treturn `\\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\\x1B[m`\n}\n\nfunction entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {\n\tif (map.has(key)) {\n\t\treturn map.get(key)!\n\t}\n\tmap.set(key, defaultValue)\n\treturn defaultValue\n}\n"],
5
- "mappings": "AAYA,MAAM,6BAA6B;AAOnC,SAAS,qBAA+C;AACvD,MAAI,WAAW,0BAA0B,GAAG;AAC3C,WAAO,WAAW,0BAA0B;AAAA,EAC7C;AAEA,QAAM,OAAiC;AAAA,IACtC,UAAU,CAAC;AAAA,IACX,SAAS;AAAA,IACT,iBAAiB;AAAA,EAClB;AAEA,SAAO,eAAe,YAAY,4BAA4B;AAAA,IAC7D,OAAO;AAAA,IACP,UAAU;AAAA,IACV,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC;AAED,SAAO;AACR;AAEO,SAAS,kCAAkC;AACjD,QAAM,OAAO,mBAAmB;AAChC,OAAK,WAAW,CAAC;AACjB,OAAK,UAAU;AACf,MAAI,KAAK,iBAAiB;AACzB,iBAAa,KAAK,eAAe;AACjC,SAAK,kBAAkB;AAAA,EACxB;AACD;AAGO,SAAS,6BAA6B,MAAe,SAAkB,SAAkB;AAC/F,MAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS;AAClC,QAAK,MAA4C;AAChD,YAAM,IAAI,MAAM,uEAAuE;AAAA,IACxF;AACA;AAAA,EACD;AAEA,QAAM,OAAO,mBAAmB;AAChC,OAAK,SAAS,KAAK,EAAE,MAAM,SAAS,QAAQ,CAAC;AAE7C,MAAI,CAAC,KAAK,iBAAiB;AAC1B,QAAI;AAEH,WAAK,kBAAkB,WAAW,MAAM;AACvC,aAAK,kBAAkB;AACvB,6BAAqB,IAAI;AAAA,MAC1B,GAAG,GAAG;AAAA,IACP,QAAQ;AAGP,2BAAqB,IAAI;AAAA,IAC1B;AAAA,EACD;AACD;AAEA,SAAS,qBAAqB,MAAgC;AAC7D,MAAI,CAAC,KAAK,SAAS,OAAQ;AAC3B,MAAI,KAAK,QAAS;AAElB,QAAM,SAAS,KAAK,SAAS,KAAK,CAAC,GAAG,MAAM,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC;AACjF,QAAM,gBAAgB,OAAO,OAAO,SAAS,CAAC,EAAE;AAEhD,QAAM,mBAAmB,oBAAI,IAAY;AACzC,QAAM,sBAAsB,oBAAI,IAAyB;AACzD,aAAW,OAAO,QAAQ;AACzB,QAAI,oBAAoB,IAAI,IAAI,IAAI,GAAG;AACtC,uBAAiB,OAAO,IAAI,IAAI;AAChC,YAAM,qBAAqB,IAAI,MAAM,oBAAI,IAAI,CAAC,EAAE,IAAI,IAAI,OAAO;AAC/D;AAAA,IACD;AAEA,QAAI,IAAI,YAAY,eAAe;AAClC,uBAAiB,IAAI,IAAI,IAAI;AAAA,IAC9B,OAAO;AACN,uBAAiB,OAAO,IAAI,IAAI;AAChC,YAAM,qBAAqB,IAAI,MAAM,oBAAI,IAAI,CAAC,EAAE,IAAI,IAAI,OAAO;AAAA,IAChE;AAAA,EACD;AAEA,MAAI,oBAAoB,OAAO,GAAG;AACjC,UAAM,UAAU;AAAA,MACf,GAAG,OAAO,YAAY,CAAC,QAAQ,SAAS,WAAW,CAAC,CAAC,IAAI,OAAO,4GAA4G,CAAC,WAAW,MAAM,CAAC,CAAC;AAAA,MAChM;AAAA,MACA,4CAA4C,OAAO,IAAI,aAAa,IAAI,CAAC,QAAQ,UAAU,CAAC,CAAC;AAAA,MAC7F,GAAG,MAAM,KAAK,kBAAkB,CAAC,SAAS,mBAAS,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE;AAAA,MAC3E;AAAA,MACA;AAAA,MACA,GAAG,MAAM,KAAK,qBAAqB,CAAC,CAAC,MAAM,QAAQ,MAAM;AACxD,cAAM,iBAAiB,MAAM,KAAK,QAAQ,EACxC,KAAK,eAAe,EACpB,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,IAAI,MAAM,gBAAgB,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,CAAC;AAC/E,eAAO,mBAAS,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,eAAe,KAAK,IAAI,CAAC;AAAA,MACrE,CAAC;AAAA,IACF;AAGA,YAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC;AAC9B,SAAK,UAAU;AACf;AAAA,EACD;AAGA,QAAM,sBAAsB,oBAAI,IAAoD;AACpF,aAAW,OAAO,QAAQ;AACzB,UAAM,qBAAqB,IAAI,MAAM,EAAE,SAAS,IAAI,SAAS,SAAS,CAAC,EAAE,CAAC,EAAE,QAAQ;AAAA,MACnF,IAAI;AAAA,IACL;AAAA,EACD;AAEA,QAAM,aAAa,oBAAI,IAAoD;AAC3E,aAAW,CAAC,MAAM,GAAG,KAAK,qBAAqB;AAC9C,QAAI,IAAI,QAAQ,SAAS,EAAG,YAAW,IAAI,MAAM,GAAG;AAAA,EACrD;AAEA,MAAI,WAAW,OAAO,GAAG;AACxB,UAAM,UAAU;AAAA,MACf,GAAG,OAAO,YAAY,CAAC,QAAQ,SAAS,WAAW,CAAC,CAAC,IAAI,OAAO,gHAAgH,CAAC,WAAW,MAAM,CAAC,CAAC;AAAA,MACpM;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM,GAAG,MAAM;AAC1C,cAAM,UAAU,IAAI,QAClB,IAAI,CAAC,GAAG,MAAO,MAAM,QAAQ,SAAS,IAAI,CAAC,iBAAiB,SAAS,IAAI,CAAC,YAAa,EACvF,KAAK,IAAI;AACX,eAAO,mBAAS,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,OAAO;AAAA,EAAO,OAAO;AAAA,MACrE,CAAC;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAGA,YAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC;AAC9B,SAAK,UAAU;AACf;AAAA,EACD;AACD;AAEA,SAAS,gBAAgB,GAAW,GAAW;AAC9C,QAAM,SAAS,EAAE,MAAM,kCAAkC;AACzD,QAAM,SAAS,EAAE,MAAM,kCAAkC;AAEzD,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO,EAAE,cAAc,CAAC;AAChD,MAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxE,MAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxE,MAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO,OAAO,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxE,MAAI,OAAO,CAAC,KAAK,OAAO,CAAC,EAAG,QAAO,OAAO,CAAC,EAAE,cAAc,OAAO,CAAC,CAAC;AACpE,MAAI,OAAO,CAAC,EAAG,QAAO;AACtB,MAAI,OAAO,CAAC,EAAG,QAAO;AACtB,SAAO;AACR;AAEA,MAAM,UAAU;AAAA,EACf,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AAAA,EACP,WAAW;AACZ;AACA,SAAS,OAAO,OAAe,aAAuC,CAAC,GAAG;AACzE,SAAO,QAAQ,WAAW,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,IAAI,KAAK;AACpE;AAEA,SAAS,MAAY,KAAgB,KAAQ,cAAoB;AAChE,MAAI,IAAI,IAAI,GAAG,GAAG;AACjB,WAAO,IAAI,IAAI,GAAG;AAAA,EACnB;AACA,MAAI,IAAI,KAAK,YAAY;AACzB,SAAO;AACR;",
4
+ "sourcesContent": ["interface TldrawLibraryVersion {\n\tname: string\n\tversion: string\n\tmodules: string\n}\n\ninterface TldrawLibraryVersionInfo {\n\tversions: TldrawLibraryVersion[]\n\tdidWarn: boolean\n\tscheduledNotice: number | NodeJS.Timeout | null\n}\n\nconst TLDRAW_LIBRARY_VERSION_KEY = '__TLDRAW_LIBRARY_VERSIONS__' as const\n\n// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace\ndeclare module globalThis {\n\texport const __TLDRAW_LIBRARY_VERSIONS__: TldrawLibraryVersionInfo\n}\n\nfunction getLibraryVersions(): TldrawLibraryVersionInfo {\n\tif (globalThis[TLDRAW_LIBRARY_VERSION_KEY]) {\n\t\treturn globalThis[TLDRAW_LIBRARY_VERSION_KEY]\n\t}\n\n\tconst info: TldrawLibraryVersionInfo = {\n\t\tversions: [],\n\t\tdidWarn: false,\n\t\tscheduledNotice: null,\n\t}\n\n\tObject.defineProperty(globalThis, TLDRAW_LIBRARY_VERSION_KEY, {\n\t\tvalue: info,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t\tenumerable: false,\n\t})\n\n\treturn info\n}\n\n/**\n * Clears all registered library versions and resets warning state.\n * This function is intended for testing purposes only to reset the global version tracking state.\n * @returns void\n * @example\n * ```ts\n * // In a test setup\n * beforeEach(() => {\n * clearRegisteredVersionsForTests()\n * })\n *\n * // Now version tracking starts fresh for each test\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * ```\n * @internal\n */\nexport function clearRegisteredVersionsForTests() {\n\tconst info = getLibraryVersions()\n\tinfo.versions = []\n\tinfo.didWarn = false\n\tif (info.scheduledNotice) {\n\t\tclearTimeout(info.scheduledNotice)\n\t\tinfo.scheduledNotice = null\n\t}\n}\n\n/**\n * Registers a tldraw library version for conflict detection.\n * This function tracks different tldraw library versions to warn about potential conflicts\n * when multiple versions are loaded simultaneously.\n * @param name - The name of the tldraw library package (e.g., '\\@tldraw/editor').\n * @param version - The semantic version string (e.g., '2.0.0').\n * @param modules - The module system being used ('esm' or 'cjs').\n * @returns void\n * @example\n * ```ts\n * // Register a library version during package initialization\n * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')\n * registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')\n *\n * // If conflicting versions are detected, warnings will be logged:\n * registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'cjs')\n * // Console warning about version mismatch will appear\n * ```\n * @internal\n */\nexport function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {\n\tif (!name || !version || !modules) {\n\t\tif ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {\n\t\t\tthrow new Error('Missing name/version/module system in built version of tldraw library')\n\t\t}\n\t\treturn\n\t}\n\n\tconst info = getLibraryVersions()\n\tinfo.versions.push({ name, version, modules })\n\n\tif (!info.scheduledNotice) {\n\t\ttry {\n\t\t\t// eslint-disable-next-line no-restricted-globals\n\t\t\tinfo.scheduledNotice = setTimeout(() => {\n\t\t\t\tinfo.scheduledNotice = null\n\t\t\t\tcheckLibraryVersions(info)\n\t\t\t}, 100)\n\t\t} catch {\n\t\t\t// some environments (e.g. cloudflare workers) don't support setTimeout immediately, only in a handler.\n\t\t\t// in this case, we'll just check immediately.\n\t\t\tcheckLibraryVersions(info)\n\t\t}\n\t}\n}\n\nfunction checkLibraryVersions(info: TldrawLibraryVersionInfo) {\n\tif (!info.versions.length) return\n\tif (info.didWarn) return\n\n\tconst sorted = info.versions.sort((a, b) => compareVersions(a.version, b.version))\n\tconst latestVersion = sorted[sorted.length - 1].version\n\n\tconst matchingVersions = new Set<string>()\n\tconst nonMatchingVersions = new Map<string, Set<string>>()\n\tfor (const lib of sorted) {\n\t\tif (nonMatchingVersions.has(lib.name)) {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (lib.version === latestVersion) {\n\t\t\tmatchingVersions.add(lib.name)\n\t\t} else {\n\t\t\tmatchingVersions.delete(lib.name)\n\t\t\tentry(nonMatchingVersions, lib.name, new Set()).add(lib.version)\n\t\t}\n\t}\n\n\tif (nonMatchingVersions.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t`The latest version you have installed is ${format(`v${latestVersion}`, ['bold', 'textBlue'])}. The following libraries are on the latest version:`,\n\t\t\t...Array.from(matchingVersions, (name) => ` \u2022 \u2705 ${format(name, ['bold'])}`),\n\t\t\t'',\n\t\t\t`The following libraries are not on the latest version, or have multiple versions installed:`,\n\t\t\t...Array.from(nonMatchingVersions, ([name, versions]) => {\n\t\t\t\tconst sortedVersions = Array.from(versions)\n\t\t\t\t\t.sort(compareVersions)\n\t\t\t\t\t.map((v) => format(`v${v}`, v === latestVersion ? ['textGreen'] : ['textRed']))\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} (${sortedVersions.join(', ')})`\n\t\t\t}),\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n\n\t// at this point, we know that everything has the same version. there may still be duplicates though!\n\tconst potentialDuplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const lib of sorted) {\n\t\tentry(potentialDuplicates, lib.name, { version: lib.version, modules: [] }).modules.push(\n\t\t\tlib.modules\n\t\t)\n\t}\n\n\tconst duplicates = new Map<string, { version: string; modules: string[] }>()\n\tfor (const [name, lib] of potentialDuplicates) {\n\t\tif (lib.modules.length > 1) duplicates.set(name, lib)\n\t}\n\n\tif (duplicates.size > 0) {\n\t\tconst message = [\n\t\t\t`${format('[tldraw]', ['bold', 'bgRed', 'textWhite'])} ${format('You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. ', ['textRed', 'bold'])}`,\n\t\t\t'',\n\t\t\t'This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.',\n\t\t\t'',\n\t\t\t'The following libraries have been imported multiple times:',\n\t\t\t...Array.from(duplicates, ([name, lib]) => {\n\t\t\t\tconst modules = lib.modules\n\t\t\t\t\t.map((m, i) => (m === 'esm' ? ` ${i + 1}. ES Modules` : ` ${i + 1}. CommonJS`))\n\t\t\t\t\t.join('\\n')\n\t\t\t\treturn ` \u2022 \u274C ${format(name, ['bold'])} v${lib.version}: \\n${modules}`\n\t\t\t}),\n\t\t\t'',\n\t\t\t'You should configure your bundler to only import one version of each library.',\n\t\t]\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log(message.join('\\n'))\n\t\tinfo.didWarn = true\n\t\treturn\n\t}\n}\n\nfunction compareVersions(a: string, b: string) {\n\tconst aMatch = a.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\tconst bMatch = b.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(\\w+))?$/)\n\n\tif (!aMatch || !bMatch) return a.localeCompare(b)\n\tif (aMatch[1] !== bMatch[1]) return Number(aMatch[1]) - Number(bMatch[1])\n\tif (aMatch[2] !== bMatch[2]) return Number(aMatch[2]) - Number(bMatch[2])\n\tif (aMatch[3] !== bMatch[3]) return Number(aMatch[3]) - Number(bMatch[3])\n\tif (aMatch[4] && bMatch[4]) return aMatch[4].localeCompare(bMatch[4])\n\tif (aMatch[4]) return 1\n\tif (bMatch[4]) return -1\n\treturn 0\n}\n\nconst formats = {\n\tbold: '1',\n\ttextBlue: '94',\n\ttextRed: '31',\n\ttextGreen: '32',\n\tbgRed: '41',\n\ttextWhite: '97',\n} as const\nfunction format(value: string, formatters: (keyof typeof formats)[] = []) {\n\treturn `\\x1B[${formatters.map((f) => formats[f]).join(';')}m${value}\\x1B[m`\n}\n\nfunction entry<K, V>(map: Map<K, V>, key: K, defaultValue: V): V {\n\tif (map.has(key)) {\n\t\treturn map.get(key)!\n\t}\n\tmap.set(key, defaultValue)\n\treturn defaultValue\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/warn.ts"],
4
- "sourcesContent": ["const usedWarnings = new Set<string>()\n\n/** @internal */\nexport function warnDeprecatedGetter(name: string) {\n\twarnOnce(\n\t\t`Using '${name}' is deprecated and will be removed in the near future. Please refactor to use 'get${name[0].toLocaleUpperCase()}${name.slice(\n\t\t\t1\n\t\t)}' instead.`\n\t)\n}\n\n/** @internal */\nexport function warnOnce(message: string) {\n\tif (usedWarnings.has(message)) return\n\n\tusedWarnings.add(message)\n\tconsole.warn(`[tldraw] ${message}`)\n}\n"],
5
- "mappings": "AAAA,MAAM,eAAe,oBAAI,IAAY;AAG9B,SAAS,qBAAqB,MAAc;AAClD;AAAA,IACC,UAAU,IAAI,sFAAsF,KAAK,CAAC,EAAE,kBAAkB,CAAC,GAAG,KAAK;AAAA,MACtI;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAGO,SAAS,SAAS,SAAiB;AACzC,MAAI,aAAa,IAAI,OAAO,EAAG;AAE/B,eAAa,IAAI,OAAO;AACxB,UAAQ,KAAK,YAAY,OAAO,EAAE;AACnC;",
4
+ "sourcesContent": ["const usedWarnings = new Set<string>()\n\n/**\n * Issues a deprecation warning for deprecated getter properties, advising users to use\n * the equivalent getter method instead. The warning is shown only once per property name.\n *\n * @param name - The name of the deprecated property (e.g., 'viewport')\n *\n * @example\n * ```ts\n * // Inside a class with deprecated property access\n * get viewport() {\n * warnDeprecatedGetter('viewport')\n * return this.getViewport()\n * }\n *\n * // Usage will show: \"[tldraw] Using 'viewport' is deprecated and will be removed...\"\n * // But only the first time it's accessed\n * ```\n *\n * @internal\n */\nexport function warnDeprecatedGetter(name: string) {\n\twarnOnce(\n\t\t`Using '${name}' is deprecated and will be removed in the near future. Please refactor to use 'get${name[0].toLocaleUpperCase()}${name.slice(\n\t\t\t1\n\t\t)}' instead.`\n\t)\n}\n\n/**\n * Issues a warning message to the console, but only once per unique message.\n * Subsequent calls with the same message are ignored, preventing console spam.\n * All messages are prefixed with \"[tldraw]\".\n *\n * @param message - The warning message to display\n *\n * @example\n * ```ts\n * // Warn about deprecated usage\n * function oldFunction() {\n * warnOnce('oldFunction is deprecated, use newFunction instead')\n * // Continue with implementation...\n * }\n *\n * // First call logs: \"[tldraw] oldFunction is deprecated, use newFunction instead\"\n * oldFunction() // Shows warning\n * oldFunction() // No warning (already shown)\n * oldFunction() // No warning (already shown)\n * ```\n *\n * @internal\n */\nexport function warnOnce(message: string) {\n\tif (usedWarnings.has(message)) return\n\n\tusedWarnings.add(message)\n\tconsole.warn(`[tldraw] ${message}`)\n}\n"],
5
+ "mappings": "AAAA,MAAM,eAAe,oBAAI,IAAY;AAsB9B,SAAS,qBAAqB,MAAc;AAClD;AAAA,IACC,UAAU,IAAI,sFAAsF,KAAK,CAAC,EAAE,kBAAkB,CAAC,GAAG,KAAK;AAAA,MACtI;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAyBO,SAAS,SAAS,SAAiB;AACzC,MAAI,aAAa,IAAI,OAAO,EAAG;AAE/B,eAAa,IAAI,OAAO;AACxB,UAAQ,KAAK,YAAY,OAAO,EAAE;AACnC;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/utils",
3
3
  "description": "tldraw infinite canvas SDK (private utilities).",
4
- "version": "4.1.0-next.b6dfe9bccde9",
4
+ "version": "4.1.0-next.b73a0d46b63f",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -1,45 +1,40 @@
1
1
  import { ExecutionQueue } from './ExecutionQueue'
2
- import { promiseWithResolve } from './control'
2
+ import { promiseWithResolve, sleep } from './control'
3
3
 
4
4
  const tick = () => Promise.resolve()
5
5
 
6
6
  describe('ExecutionQueue', () => {
7
- it('creates a function that runs some async function when invoked', async () => {
8
- const queue = new ExecutionQueue(10)
7
+ it('executes tasks sequentially', async () => {
8
+ const queue = new ExecutionQueue()
9
9
  let numExecutions = 0
10
10
 
11
11
  await queue.push(async () => {
12
+ await sleep(1)
12
13
  numExecutions++
13
14
  })
14
15
 
16
+ //number of exeutions is 1 because we await the first task
15
17
  expect(numExecutions).toBe(1)
16
18
 
17
19
  // push without awaiting
18
20
  queue.push(async () => {
21
+ await sleep(1)
19
22
  numExecutions++
20
23
  })
21
24
 
25
+ // number of exeutions is still 1 because we didn't await the second task
22
26
  expect(numExecutions).toBe(1)
23
27
 
24
- // push a new task and wait for it to finish
25
- await queue.push(tick)
28
+ await sleep(5) // wait for the second task to finish
26
29
 
30
+ // number of exeutions is 2 because we waited for the second task to finish
27
31
  expect(numExecutions).toBe(2)
28
32
  })
29
33
 
30
- it('allows returning a value', async () => {
31
- const queue = new ExecutionQueue(10)
32
-
33
- const value = await queue.push(async () => {
34
- return 'ok'
35
- })
36
- expect(value).toBe('ok')
37
- })
38
-
39
- it('will queue up a second invocation if invoked while executing', async () => {
34
+ it('queues tasks when one is already executing', async () => {
40
35
  const completions: string[] = []
41
36
  const promise = promiseWithResolve()
42
- const queue = new ExecutionQueue(10)
37
+ const queue = new ExecutionQueue()
43
38
 
44
39
  const persistPromiseA = queue.push(async () => {
45
40
  await promise
@@ -50,12 +45,9 @@ describe('ExecutionQueue', () => {
50
45
  completions.push('B')
51
46
  })
52
47
 
53
- await tick()
54
- expect(completions).toEqual([])
55
48
  await tick()
56
49
  expect(completions).toEqual([])
57
50
 
58
- // nothing happens until we resolve the promise
59
51
  promise.resolve(undefined)
60
52
  await persistPromiseA
61
53
  expect(completions).toEqual(['A'])
@@ -63,7 +55,7 @@ describe('ExecutionQueue', () => {
63
55
  expect(completions).toEqual(['A', 'B'])
64
56
  })
65
57
 
66
- it('will throw in the event of an error', async () => {
58
+ it('propagates errors from tasks', async () => {
67
59
  const queue = new ExecutionQueue()
68
60
  const promise = promiseWithResolve()
69
61
 
@@ -75,4 +67,154 @@ describe('ExecutionQueue', () => {
75
67
 
76
68
  await expect(result).rejects.toThrow('oh no')
77
69
  })
70
+
71
+ it('handles constructor with timeout', async () => {
72
+ const queue = new ExecutionQueue(10) // 10ms delay
73
+ const executionOrder: string[] = []
74
+
75
+ const startTime = Date.now()
76
+
77
+ // Add first task
78
+ queue.push(async () => {
79
+ executionOrder.push('A')
80
+ })
81
+
82
+ // Add second task
83
+ queue.push(async () => {
84
+ executionOrder.push('B')
85
+ })
86
+
87
+ // Wait for both tasks to complete
88
+ await new Promise((resolve) => setTimeout(resolve, 50))
89
+
90
+ expect(executionOrder).toEqual(['A', 'B'])
91
+ // Should have taken at least 10ms due to timeout delay
92
+ expect(Date.now() - startTime).toBeGreaterThanOrEqual(10)
93
+ })
94
+
95
+ it('isEmpty returns correct state', async () => {
96
+ const queue = new ExecutionQueue()
97
+
98
+ // Initially empty
99
+ expect(queue.isEmpty()).toBe(true)
100
+
101
+ // After adding a task, should not be empty
102
+ const taskPromise = queue.push(async () => {
103
+ await sleep(1)
104
+ })
105
+
106
+ expect(queue.isEmpty()).toBe(false)
107
+
108
+ // Resolve the task and wait a tick for it to finish
109
+ await taskPromise
110
+ await tick()
111
+
112
+ // Should be empty again
113
+ expect(queue.isEmpty()).toBe(true)
114
+ })
115
+
116
+ it('close clears pending tasks', async () => {
117
+ const queue = new ExecutionQueue()
118
+ const executionOrder: string[] = []
119
+ const promise = promiseWithResolve()
120
+
121
+ // Add first task that will run
122
+ queue.push(async () => {
123
+ await promise
124
+ executionOrder.push('A')
125
+ })
126
+
127
+ // Add second task that should be cleared
128
+ queue.push(async () => {
129
+ executionOrder.push('B')
130
+ })
131
+
132
+ // Add third task that should be cleared
133
+ queue.push(async () => {
134
+ executionOrder.push('C')
135
+ })
136
+
137
+ // Close the queue - this should clear pending tasks
138
+ queue.close()
139
+
140
+ // Resolve the first task
141
+ promise.resolve(undefined)
142
+ await sleep(10)
143
+
144
+ // Only the first task should have executed
145
+ expect(executionOrder).toEqual(['A'])
146
+ })
147
+
148
+ it('allows adding tasks after close', async () => {
149
+ const queue = new ExecutionQueue()
150
+ const executionOrder: string[] = []
151
+
152
+ // Close the queue
153
+ queue.close()
154
+
155
+ // Add tasks after closing
156
+ queue.push(async () => {
157
+ executionOrder.push('A')
158
+ })
159
+
160
+ queue.push(async () => {
161
+ executionOrder.push('B')
162
+ })
163
+
164
+ await sleep(10)
165
+
166
+ // Tasks should still execute
167
+ expect(executionOrder).toEqual(['A', 'B'])
168
+ })
169
+
170
+ it('handles multiple rapid push calls', async () => {
171
+ const queue = new ExecutionQueue()
172
+ const executionOrder: number[] = []
173
+
174
+ // Add multiple tasks rapidly without awaiting
175
+ const promises = []
176
+ for (let i = 0; i < 5; i++) {
177
+ promises.push(
178
+ queue.push(async () => {
179
+ executionOrder.push(i)
180
+ })
181
+ )
182
+ }
183
+
184
+ // Wait for all tasks to complete
185
+ await Promise.all(promises)
186
+
187
+ // Should execute in order
188
+ expect(executionOrder).toEqual([0, 1, 2, 3, 4])
189
+ })
190
+
191
+ it('handles close while task is running', async () => {
192
+ const queue = new ExecutionQueue()
193
+ const executionOrder: string[] = []
194
+ const promise = promiseWithResolve()
195
+
196
+ // Start a task that will take time
197
+ const runningTask = queue.push(async () => {
198
+ await promise
199
+ executionOrder.push('running')
200
+ })
201
+
202
+ // Add a task that will be queued
203
+ queue.push(async () => {
204
+ executionOrder.push('queued')
205
+ })
206
+
207
+ // Close while first task is still running
208
+ queue.close()
209
+
210
+ // Resolve the running task
211
+ promise.resolve(undefined)
212
+ await runningTask
213
+
214
+ // Wait a bit to ensure no other tasks run
215
+ await sleep(10)
216
+
217
+ // Only the running task should have executed
218
+ expect(executionOrder).toEqual(['running'])
219
+ })
78
220
  })