@tldraw/editor 5.5.0-canary.db5d20befa38 → 5.5.0-canary.ddcb7ca958fa

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.
@@ -4579,6 +4579,8 @@ export declare const EditorContext: React_3.Context<Editor | null>;
4579
4579
  * - Reaction (`react(...)` from `@tldraw/state`): `this.register(react(...))`
4580
4580
  * - DOM listener: `this.register(() => el.removeEventListener(...))`
4581
4581
  * - Other resource (cache, index, child manager): `this.register(() => x.dispose())`
4582
+ * - Resource held only part of the time (a listener attached while subscribers exist):
4583
+ * `this.register(...)` on attach and `this.unregister(...)` on detach, see `PerformanceManager`
4582
4584
  *
4583
4585
  * For timeouts, intervals, and animation frames prefer `editor.timers` (context-grouped
4584
4586
  * and auto-disposed) over raw `setTimeout`. For cleanup on the editor itself rather than a
@@ -4598,11 +4600,18 @@ export declare abstract class EditorManager {
4598
4600
  * make a resource "see itself out". Returns the same function for convenience.
4599
4601
  */
4600
4602
  protected register(dispose: () => void): () => void;
4603
+ /**
4604
+ * Run a registered teardown now instead of at `dispose()`. A teardown that is no longer
4605
+ * registered (already run by `dispose()`, or never registered) is skipped, so nothing is
4606
+ * torn down twice.
4607
+ */
4608
+ protected unregister(dispose: () => void): void;
4601
4609
  /**
4602
4610
  * Subscribe to an editor bus event and register the matching unsubscribe, so the listener
4603
- * is removed automatically on `dispose()`.
4611
+ * is removed automatically on `dispose()`. Returns that unsubscribe, for passing to
4612
+ * {@link EditorManager.unregister} to detach it before `dispose()`.
4604
4613
  */
4605
- protected addEditorEvent<E extends keyof TLEventMap>(event: E, fn: (...args: TLEventMap[E]) => void): void;
4614
+ protected addEditorEvent<E extends keyof TLEventMap>(event: E, fn: (...args: TLEventMap[E]) => void): () => void;
4606
4615
  /* Excluded from this release type: dispose */
4607
4616
  }
4608
4617
 
@@ -5990,17 +5999,11 @@ export declare class PerformanceApiAdapter {
5990
5999
  *
5991
6000
  * @public
5992
6001
  */
5993
- export declare class PerformanceManager {
6002
+ export declare class PerformanceManager extends EditorManager {
5994
6003
  /* Excluded from this release type: emitter */
5995
- private editor;
5996
6004
  private activeInteraction;
5997
6005
  private activeCamera;
5998
- private frameCleanup;
5999
- private shapeCreatedCleanup;
6000
- private shapeEditedCleanup;
6001
- private shapeDeletedCleanup;
6002
- private loafObserver;
6003
- constructor(editor: Editor);
6006
+ private readonly lazyCleanups;
6004
6007
  /**
6005
6008
  * Subscribe to a performance event. Returns an unsubscribe function.
6006
6009
  *
@@ -6034,11 +6037,11 @@ export declare class PerformanceManager {
6034
6037
  private _onShapesEdited;
6035
6038
  private _onShapesDeleted;
6036
6039
  private _startLoafObserver;
6037
- private _stopLoafObserver;
6038
6040
  private _needsFrameListener;
6039
6041
  private _needsLoafObserver;
6040
6042
  private _maybeAttachLazyListeners;
6041
6043
  private _maybeDetachLazyListeners;
6044
+ private _detachLazy;
6042
6045
  }
6043
6046
 
6044
6047
  /**
package/dist-cjs/index.js CHANGED
@@ -392,7 +392,7 @@ var import_uniq = require("./lib/utils/uniq");
392
392
  var import_defaultThemes2 = require("./lib/editor/managers/ThemeManager/defaultThemes");
393
393
  (0, import_utils.registerTldrawLibraryVersion)(
394
394
  "@tldraw/editor",
395
- "5.5.0-canary.db5d20befa38",
395
+ "5.5.0-canary.ddcb7ca958fa",
396
396
  "cjs"
397
397
  );
398
398
  //# sourceMappingURL=index.js.map
@@ -35,13 +35,22 @@ class EditorManager {
35
35
  this.disposables.add(dispose);
36
36
  return dispose;
37
37
  }
38
+ /**
39
+ * Run a registered teardown now instead of at `dispose()`. A teardown that is no longer
40
+ * registered (already run by `dispose()`, or never registered) is skipped, so nothing is
41
+ * torn down twice.
42
+ */
43
+ unregister(dispose) {
44
+ if (this.disposables.delete(dispose)) dispose();
45
+ }
38
46
  /**
39
47
  * Subscribe to an editor bus event and register the matching unsubscribe, so the listener
40
- * is removed automatically on `dispose()`.
48
+ * is removed automatically on `dispose()`. Returns that unsubscribe, for passing to
49
+ * {@link EditorManager.unregister} to detach it before `dispose()`.
41
50
  */
42
51
  addEditorEvent(event, fn) {
43
52
  this.editor.on(event, fn);
44
- this.register(() => this.editor.off(event, fn));
53
+ return this.register(() => this.editor.off(event, fn));
45
54
  }
46
55
  /** @internal */
47
56
  dispose() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/managers/EditorManager.ts"],
4
- "sourcesContent": ["import type { Editor } from '../Editor'\nimport type { TLEventMap } from '../types/emit-types'\n\n/**\n * Base class for editor-attached managers that need to clean up after themselves.\n *\n * Extending `EditorManager` gives a manager one consistent teardown contract: register\n * anything that needs undoing, and it runs automatically on `dispose()`. This keeps\n * subscription lifecycle symmetric \u2014 whatever sets a thing up is what tears it down \u2014 so\n * it can't be forgotten (the failure mode behind the strict-mode camera bug, #8892).\n *\n * Register cleanup by what you're creating:\n *\n * - Editor bus event (`tick`, `frame`, `change`, \u2026): {@link EditorManager.addEditorEvent}\n * - Store side effect (`editor.sideEffects.register\u2026`): `this.register(disposer)`\n * - Reaction (`react(...)` from `@tldraw/state`): `this.register(react(...))`\n * - DOM listener: `this.register(() => el.removeEventListener(...))`\n * - Other resource (cache, index, child manager): `this.register(() => x.dispose())`\n *\n * For timeouts, intervals, and animation frames prefer `editor.timers` (context-grouped\n * and auto-disposed) over raw `setTimeout`. For cleanup on the editor itself rather than a\n * manager, use `editor.disposables`.\n *\n * If a manager needs ordered teardown (e.g. pause a loop before cancelling it), override\n * `dispose()` and call `super.dispose()` last \u2014 see `TickManager`.\n *\n * @public\n */\nexport abstract class EditorManager {\n\tprotected readonly disposables = new Set<() => void>()\n\n\tconstructor(protected readonly editor: Editor) {}\n\n\t/**\n\t * Register a teardown function to run on `dispose()`. The universal way for a manager to\n\t * make a resource \"see itself out\". Returns the same function for convenience.\n\t */\n\tprotected register(dispose: () => void): () => void {\n\t\tthis.disposables.add(dispose)\n\t\treturn dispose\n\t}\n\n\t/**\n\t * Subscribe to an editor bus event and register the matching unsubscribe, so the listener\n\t * is removed automatically on `dispose()`.\n\t */\n\tprotected addEditorEvent<E extends keyof TLEventMap>(\n\t\tevent: E,\n\t\tfn: (...args: TLEventMap[E]) => void\n\t): void {\n\t\tthis.editor.on(event, fn as any)\n\t\tthis.register(() => this.editor.off(event, fn as any))\n\t}\n\n\t/** @internal */\n\tdispose(): void {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.disposables.clear()\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BO,MAAe,cAAc;AAAA,EAGnC,YAA+B,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EAFZ,cAAc,oBAAI,IAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3C,SAAS,SAAiC;AACnD,SAAK,YAAY,IAAI,OAAO;AAC5B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,eACT,OACA,IACO;AACP,SAAK,OAAO,GAAG,OAAO,EAAS;AAC/B,SAAK,SAAS,MAAM,KAAK,OAAO,IAAI,OAAO,EAAS,CAAC;AAAA,EACtD;AAAA;AAAA,EAGA,UAAgB;AACf,SAAK,YAAY,QAAQ,CAAC,MAAM,EAAE,CAAC;AACnC,SAAK,YAAY,MAAM;AAAA,EACxB;AACD;",
4
+ "sourcesContent": ["import type { Editor } from '../Editor'\nimport type { TLEventMap } from '../types/emit-types'\n\n/**\n * Base class for editor-attached managers that need to clean up after themselves.\n *\n * Extending `EditorManager` gives a manager one consistent teardown contract: register\n * anything that needs undoing, and it runs automatically on `dispose()`. This keeps\n * subscription lifecycle symmetric \u2014 whatever sets a thing up is what tears it down \u2014 so\n * it can't be forgotten (the failure mode behind the strict-mode camera bug, #8892).\n *\n * Register cleanup by what you're creating:\n *\n * - Editor bus event (`tick`, `frame`, `change`, \u2026): {@link EditorManager.addEditorEvent}\n * - Store side effect (`editor.sideEffects.register\u2026`): `this.register(disposer)`\n * - Reaction (`react(...)` from `@tldraw/state`): `this.register(react(...))`\n * - DOM listener: `this.register(() => el.removeEventListener(...))`\n * - Other resource (cache, index, child manager): `this.register(() => x.dispose())`\n * - Resource held only part of the time (a listener attached while subscribers exist):\n * `this.register(...)` on attach and `this.unregister(...)` on detach, see `PerformanceManager`\n *\n * For timeouts, intervals, and animation frames prefer `editor.timers` (context-grouped\n * and auto-disposed) over raw `setTimeout`. For cleanup on the editor itself rather than a\n * manager, use `editor.disposables`.\n *\n * If a manager needs ordered teardown (e.g. pause a loop before cancelling it), override\n * `dispose()` and call `super.dispose()` last \u2014 see `TickManager`.\n *\n * @public\n */\nexport abstract class EditorManager {\n\tprotected readonly disposables = new Set<() => void>()\n\n\tconstructor(protected readonly editor: Editor) {}\n\n\t/**\n\t * Register a teardown function to run on `dispose()`. The universal way for a manager to\n\t * make a resource \"see itself out\". Returns the same function for convenience.\n\t */\n\tprotected register(dispose: () => void): () => void {\n\t\tthis.disposables.add(dispose)\n\t\treturn dispose\n\t}\n\n\t/**\n\t * Run a registered teardown now instead of at `dispose()`. A teardown that is no longer\n\t * registered (already run by `dispose()`, or never registered) is skipped, so nothing is\n\t * torn down twice.\n\t */\n\tprotected unregister(dispose: () => void): void {\n\t\tif (this.disposables.delete(dispose)) dispose()\n\t}\n\n\t/**\n\t * Subscribe to an editor bus event and register the matching unsubscribe, so the listener\n\t * is removed automatically on `dispose()`. Returns that unsubscribe, for passing to\n\t * {@link EditorManager.unregister} to detach it before `dispose()`.\n\t */\n\tprotected addEditorEvent<E extends keyof TLEventMap>(\n\t\tevent: E,\n\t\tfn: (...args: TLEventMap[E]) => void\n\t): () => void {\n\t\tthis.editor.on(event, fn as any)\n\t\treturn this.register(() => this.editor.off(event, fn as any))\n\t}\n\n\t/** @internal */\n\tdispose(): void {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.disposables.clear()\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BO,MAAe,cAAc;AAAA,EAGnC,YAA+B,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EAFZ,cAAc,oBAAI,IAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3C,SAAS,SAAiC;AACnD,SAAK,YAAY,IAAI,OAAO;AAC5B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,WAAW,SAA2B;AAC/C,QAAI,KAAK,YAAY,OAAO,OAAO,EAAG,SAAQ;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,eACT,OACA,IACa;AACb,SAAK,OAAO,GAAG,OAAO,EAAS;AAC/B,WAAO,KAAK,SAAS,MAAM,KAAK,OAAO,IAAI,OAAO,EAAS,CAAC;AAAA,EAC7D;AAAA;AAAA,EAGA,UAAgB;AACf,SAAK,YAAY,QAAQ,CAAC,MAAM,EAAE,CAAC;AACnC,SAAK,YAAY,MAAM;AAAA,EACxB;AACD;",
6
6
  "names": []
7
7
  }
@@ -41,6 +41,7 @@ __export(PerformanceManager_exports, {
41
41
  module.exports = __toCommonJS(PerformanceManager_exports);
42
42
  var import_utils = require("@tldraw/utils");
43
43
  var import_eventemitter3 = __toESM(require("eventemitter3"), 1);
44
+ var import_EditorManager = require("../EditorManager");
44
45
  function percentile(sorted, p) {
45
46
  const idx = Math.ceil(p * sorted.length) - 1;
46
47
  return sorted[Math.max(0, idx)];
@@ -72,24 +73,15 @@ function toLoafEntry(entry) {
72
73
  }))
73
74
  };
74
75
  }
75
- class PerformanceManager {
76
+ class PerformanceManager extends import_EditorManager.EditorManager {
76
77
  /** @internal */
77
78
  emitter = new import_eventemitter3.default();
78
- editor;
79
79
  // Active interaction tracking
80
80
  activeInteraction = null;
81
81
  // Active camera tracking
82
82
  activeCamera = null;
83
- // Lazy listener cleanup functions
84
- frameCleanup = null;
85
- shapeCreatedCleanup = null;
86
- shapeEditedCleanup = null;
87
- shapeDeletedCleanup = null;
88
- // LoAF observer
89
- loafObserver = null;
90
- constructor(editor) {
91
- this.editor = editor;
92
- }
83
+ // Cleanups for whatever is currently attached, keyed so a detach unregisters only its own
84
+ lazyCleanups = /* @__PURE__ */ new Map();
93
85
  /**
94
86
  * Subscribe to a performance event. Returns an unsubscribe function.
95
87
  *
@@ -135,16 +127,9 @@ class PerformanceManager {
135
127
  if (this.activeCamera?.timeout) this.editor.timers.clearTimeout(this.activeCamera.timeout);
136
128
  this.activeInteraction = null;
137
129
  this.activeCamera = null;
138
- this.frameCleanup?.();
139
- this.frameCleanup = null;
140
- this.shapeCreatedCleanup?.();
141
- this.shapeCreatedCleanup = null;
142
- this.shapeEditedCleanup?.();
143
- this.shapeEditedCleanup = null;
144
- this.shapeDeletedCleanup?.();
145
- this.shapeDeletedCleanup = null;
146
- this._stopLoafObserver();
130
+ this.lazyCleanups.clear();
147
131
  this.emitter.removeAllListeners();
132
+ super.dispose();
148
133
  }
149
134
  // --- Internal notification methods ---
150
135
  /** @internal */
@@ -370,7 +355,7 @@ class PerformanceManager {
370
355
  } catch {
371
356
  return;
372
357
  }
373
- this.loafObserver = new PerformanceObserver((list) => {
358
+ const observer = new PerformanceObserver((list) => {
374
359
  const isInteractionActive = this.activeInteraction !== null;
375
360
  const isCameraActive = this.activeCamera !== null;
376
361
  if (!isInteractionActive && !isCameraActive) return;
@@ -385,13 +370,11 @@ class PerformanceManager {
385
370
  }
386
371
  }
387
372
  });
388
- this.loafObserver.observe({ type: "long-animation-frame", buffered: false });
389
- }
390
- _stopLoafObserver() {
391
- if (this.loafObserver) {
392
- this.loafObserver.disconnect();
393
- this.loafObserver = null;
394
- }
373
+ observer.observe({ type: "long-animation-frame", buffered: false });
374
+ this.lazyCleanups.set(
375
+ "loaf",
376
+ this.register(() => observer.disconnect())
377
+ );
395
378
  }
396
379
  // --- Lazy listener management ---
397
380
  _needsFrameListener() {
@@ -401,55 +384,58 @@ class PerformanceManager {
401
384
  return this.emitter.listenerCount("interaction-end") > 0 || this.emitter.listenerCount("camera-end") > 0;
402
385
  }
403
386
  _maybeAttachLazyListeners(event) {
404
- if (!this.frameCleanup && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
387
+ if (!this.lazyCleanups.has("frame") && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
405
388
  if (this._needsFrameListener()) {
406
- this.editor.on("frame", this._onFrame);
407
- this.frameCleanup = () => this.editor.off("frame", this._onFrame);
389
+ this.lazyCleanups.set("frame", this.addEditorEvent("frame", this._onFrame));
408
390
  }
409
391
  }
410
- if (!this.loafObserver && (event === "interaction-end" || event === "camera-end")) {
392
+ if (!this.lazyCleanups.has("loaf") && (event === "interaction-end" || event === "camera-end")) {
411
393
  if (this._needsLoafObserver()) {
412
394
  this._startLoafObserver();
413
395
  }
414
396
  }
415
- if (!this.shapeCreatedCleanup && event === "shapes-created") {
416
- this.editor.on("created-shapes", this._onShapesCreated);
417
- this.shapeCreatedCleanup = () => this.editor.off("created-shapes", this._onShapesCreated);
397
+ if (!this.lazyCleanups.has("shapes-created") && event === "shapes-created") {
398
+ this.lazyCleanups.set(
399
+ "shapes-created",
400
+ this.addEditorEvent("created-shapes", this._onShapesCreated)
401
+ );
418
402
  }
419
- if (!this.shapeEditedCleanup && event === "shapes-updated") {
420
- this.editor.on("edited-shapes", this._onShapesEdited);
421
- this.shapeEditedCleanup = () => this.editor.off("edited-shapes", this._onShapesEdited);
403
+ if (!this.lazyCleanups.has("shapes-updated") && event === "shapes-updated") {
404
+ this.lazyCleanups.set(
405
+ "shapes-updated",
406
+ this.addEditorEvent("edited-shapes", this._onShapesEdited)
407
+ );
422
408
  }
423
- if (!this.shapeDeletedCleanup && event === "shapes-deleted") {
424
- this.editor.on("deleted-shapes", this._onShapesDeleted);
425
- this.shapeDeletedCleanup = () => this.editor.off("deleted-shapes", this._onShapesDeleted);
409
+ if (!this.lazyCleanups.has("shapes-deleted") && event === "shapes-deleted") {
410
+ this.lazyCleanups.set(
411
+ "shapes-deleted",
412
+ this.addEditorEvent("deleted-shapes", this._onShapesDeleted)
413
+ );
426
414
  }
427
415
  }
428
416
  _maybeDetachLazyListeners(event) {
429
- if (this.frameCleanup && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
430
- if (!this._needsFrameListener()) {
431
- this.frameCleanup();
432
- this.frameCleanup = null;
433
- }
417
+ if ((event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end") && !this._needsFrameListener()) {
418
+ this._detachLazy("frame");
434
419
  }
435
- if (this.loafObserver && (event === "interaction-end" || event === "camera-end")) {
436
- if (!this._needsLoafObserver()) {
437
- this._stopLoafObserver();
438
- }
420
+ if ((event === "interaction-end" || event === "camera-end") && !this._needsLoafObserver()) {
421
+ this._detachLazy("loaf");
439
422
  }
440
- if (this.shapeCreatedCleanup && event === "shapes-created" && this.emitter.listenerCount("shapes-created") === 0) {
441
- this.shapeCreatedCleanup();
442
- this.shapeCreatedCleanup = null;
423
+ if (event === "shapes-created" && this.emitter.listenerCount("shapes-created") === 0) {
424
+ this._detachLazy("shapes-created");
443
425
  }
444
- if (this.shapeEditedCleanup && event === "shapes-updated" && this.emitter.listenerCount("shapes-updated") === 0) {
445
- this.shapeEditedCleanup();
446
- this.shapeEditedCleanup = null;
426
+ if (event === "shapes-updated" && this.emitter.listenerCount("shapes-updated") === 0) {
427
+ this._detachLazy("shapes-updated");
447
428
  }
448
- if (this.shapeDeletedCleanup && event === "shapes-deleted" && this.emitter.listenerCount("shapes-deleted") === 0) {
449
- this.shapeDeletedCleanup();
450
- this.shapeDeletedCleanup = null;
429
+ if (event === "shapes-deleted" && this.emitter.listenerCount("shapes-deleted") === 0) {
430
+ this._detachLazy("shapes-deleted");
451
431
  }
452
432
  }
433
+ _detachLazy(key) {
434
+ const cleanup = this.lazyCleanups.get(key);
435
+ if (!cleanup) return;
436
+ this.unregister(cleanup);
437
+ this.lazyCleanups.delete(key);
438
+ }
453
439
  }
454
440
  __decorateClass([
455
441
  import_utils.bind
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/PerformanceManager/PerformanceManager.ts"],
4
- "sourcesContent": ["import type { TLRecord, TLShapeId } from '@tldraw/tlschema'\nimport { bind } from '@tldraw/utils'\nimport EventEmitter from 'eventemitter3'\nimport type { Editor } from '../../Editor'\nimport type {\n\tTLCameraEndPerfEvent,\n\tTLCameraStartPerfEvent,\n\tTLFramePerfEvent,\n\tTLInteractionEndPerfEvent,\n\tTLInteractionStartPerfEvent,\n\tTLPerfEventMap,\n\tTLPerfLongAnimationFrame,\n\tTLShapeOperationPerfEvent,\n\tTLUndoRedoPerfEvent,\n} from './perf-types'\n\nfunction percentile(sorted: number[], p: number): number {\n\tconst idx = Math.ceil(p * sorted.length) - 1\n\treturn sorted[Math.max(0, idx)]\n}\n\nfunction computeFrameTimeStats(frameTimes: number[]) {\n\tif (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 }\n\tconst sorted = [...frameTimes].sort((a, b) => a - b)\n\tconst sum = sorted.reduce((a, b) => a + b, 0)\n\treturn {\n\t\tavg: sum / sorted.length,\n\t\tmedian: percentile(sorted, 0.5),\n\t\tp95: percentile(sorted, 0.95),\n\t\tp99: percentile(sorted, 0.99),\n\t\tmin: sorted[0],\n\t\tmax: sorted[sorted.length - 1],\n\t}\n}\n\nfunction toLoafEntry(entry: PerformanceEntry): TLPerfLongAnimationFrame | null {\n\t// LoAF entries have these properties but TypeScript doesn't know about them yet\n\tconst e = entry as PerformanceEntry & {\n\t\tblockingDuration?: number\n\t\tscripts?: ReadonlyArray<{\n\t\t\tsourceURL?: string\n\t\t\tinvoker?: string\n\t\t\tduration?: number\n\t\t}>\n\t}\n\tif (typeof e.duration !== 'number') return null\n\treturn {\n\t\tstartTime: e.startTime,\n\t\tduration: e.duration,\n\t\tblockingDuration: e.blockingDuration ?? 0,\n\t\tscripts: (e.scripts ?? []).map((s) => ({\n\t\t\tsourceURL: s.sourceURL ?? '',\n\t\t\tinvoker: s.invoker ?? '',\n\t\t\tduration: s.duration ?? 0,\n\t\t})),\n\t}\n}\n\n/**\n * Manages performance event subscriptions for the editor. Available as `editor.performance`.\n *\n * Listeners are lazy \u2014 internal editor hooks (frame, shape events) are only attached while\n * at least one subscriber exists, so there is zero overhead when unused.\n *\n * @example\n * ```ts\n * const unsub = editor.performance.on('interaction-end', (event) => {\n * console.log(`${event.name}: ${event.fps.toFixed(1)} fps, p95=${event.p95FrameTime.toFixed(1)}ms`)\n * })\n * ```\n *\n * @public\n */\nexport class PerformanceManager {\n\t/** @internal */\n\treadonly emitter = new EventEmitter<TLPerfEventMap>()\n\n\tprivate editor: Editor\n\n\t// Active interaction tracking\n\tprivate activeInteraction: {\n\t\tname: string\n\t\tpath: string\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\tselectedShapeTypes: Record<string, number>\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Active camera tracking\n\tprivate activeCamera: {\n\t\ttype: 'panning' | 'zooming'\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\ttimeout: number | null\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Lazy listener cleanup functions\n\tprivate frameCleanup: (() => void) | null = null\n\tprivate shapeCreatedCleanup: (() => void) | null = null\n\tprivate shapeEditedCleanup: (() => void) | null = null\n\tprivate shapeDeletedCleanup: (() => void) | null = null\n\n\t// LoAF observer\n\tprivate loafObserver: PerformanceObserver | null = null\n\n\tconstructor(editor: Editor) {\n\t\tthis.editor = editor\n\t}\n\n\t/**\n\t * Subscribe to a performance event. Returns an unsubscribe function.\n\t *\n\t * @example\n\t * ```ts\n\t * const unsub = editor.performance.on('interaction-end', (event) => {\n\t * sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })\n\t * })\n\t * // later: unsub()\n\t * ```\n\t *\n\t * @public\n\t */\n\ton<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tthis.emitter.on(event, fn as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, fn as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to a performance event once. The listener is removed after the first invocation.\n\t * Returns an unsubscribe function for early removal.\n\t *\n\t * @public\n\t */\n\tonce<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tconst wrapped = (...args: TLPerfEventMap[K]) => {\n\t\t\t;(fn as any)(...args)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t\tthis.emitter.once(event, wrapped as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, wrapped as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/** @internal */\n\tdispose() {\n\t\tif (this.activeCamera?.timeout) this.editor.timers.clearTimeout(this.activeCamera.timeout)\n\t\tthis.activeInteraction = null\n\t\tthis.activeCamera = null\n\t\tthis.frameCleanup?.()\n\t\tthis.frameCleanup = null\n\t\tthis.shapeCreatedCleanup?.()\n\t\tthis.shapeCreatedCleanup = null\n\t\tthis.shapeEditedCleanup?.()\n\t\tthis.shapeEditedCleanup = null\n\t\tthis.shapeDeletedCleanup?.()\n\t\tthis.shapeDeletedCleanup = null\n\t\tthis._stopLoafObserver()\n\t\tthis.emitter.removeAllListeners()\n\t}\n\n\t// --- Internal notification methods ---\n\n\t/** @internal */\n\t_notifyInteractionStart(name: string, path: string) {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('interaction-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('interaction-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeInteraction) {\n\t\t\tconsole.warn(\n\t\t\t\t`[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`\n\t\t\t)\n\t\t}\n\n\t\t// Capture selected shape types at start\n\t\tconst selectedShapeTypes: Record<string, number> = {}\n\t\tfor (const shape of this.editor.getSelectedShapes()) {\n\t\t\tselectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1\n\t\t}\n\n\t\tthis.activeInteraction = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\tselectedShapeTypes,\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tconst event: TLInteractionStartPerfEvent = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-start', event)\n\t}\n\n\t/** @internal */\n\t_notifyInteractionEnd() {\n\t\tconst interaction = this.activeInteraction\n\t\tif (!interaction) return\n\t\tthis.activeInteraction = null\n\n\t\tif (this.emitter.listenerCount('interaction-end') === 0) return\n\n\t\tconst duration = performance.now() - interaction.startTime\n\t\tconst stats = computeFrameTimeStats(interaction.frameTimes)\n\n\t\tconst event: TLInteractionEndPerfEvent = {\n\t\t\tname: interaction.name,\n\t\t\tpath: interaction.path,\n\t\t\tduration,\n\t\t\tfps:\n\t\t\t\tinteraction.frameTimes.length > 0 ? (interaction.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: interaction.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: interaction.frameTimes,\n\t\t\tshapeCount: this.editor.getCurrentPageShapeIds().size,\n\t\t\tselectedShapeTypes: interaction.selectedShapeTypes,\n\t\t\tlongAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : undefined,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-end', event)\n\t}\n\n\t/** @internal */\n\t_notifyCameraOperation(type: 'panning' | 'zooming') {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('camera-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('camera-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeCamera) {\n\t\t\t// Extend existing camera session\n\t\t\tif (this.activeCamera.timeout) {\n\t\t\t\tthis.editor.timers.clearTimeout(this.activeCamera.timeout)\n\t\t\t}\n\t\t\t// If type changed, end old and start new\n\t\t\tif (this.activeCamera.type !== type) {\n\t\t\t\tthis._endCameraSession()\n\t\t\t\tthis._startCameraSession(type)\n\t\t\t} else {\n\t\t\t\t// Reset timeout\n\t\t\t\tthis.activeCamera.timeout = this.editor.timers.setTimeout(\n\t\t\t\t\t() => this._endCameraSession(),\n\t\t\t\t\t50\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tthis._startCameraSession(type)\n\t\t}\n\t}\n\n\t/** @internal */\n\t_notifyUndoRedo(type: 'undo' | 'redo', undoDepth: number, redoDepth: number) {\n\t\tif (this.emitter.listenerCount(type) === 0) return\n\n\t\tconst event: TLUndoRedoPerfEvent = {\n\t\t\ttype,\n\t\t\tundoDepth,\n\t\t\tredoDepth,\n\t\t}\n\t\tthis.emitter.emit(type, event)\n\t}\n\n\t// --- Private helpers ---\n\n\tprivate _startCameraSession(type: 'panning' | 'zooming') {\n\t\tthis.activeCamera = {\n\t\t\ttype,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\ttimeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tif (this.emitter.listenerCount('camera-start') > 0) {\n\t\t\tconst event: TLCameraStartPerfEvent = {\n\t\t\t\ttype,\n\t\t\t\ttimestamp: performance.now(),\n\t\t\t}\n\t\t\tthis.emitter.emit('camera-start', event)\n\t\t}\n\t}\n\n\tprivate _endCameraSession() {\n\t\tconst camera = this.activeCamera\n\t\tif (!camera) return\n\t\tthis.activeCamera = null\n\t\tif (camera.timeout) this.editor.timers.clearTimeout(camera.timeout)\n\n\t\tif (this.emitter.listenerCount('camera-end') === 0) return\n\n\t\tconst duration = performance.now() - camera.startTime\n\t\tconst stats = computeFrameTimeStats(camera.frameTimes)\n\t\tconst viewportBounds = this.editor.getViewportScreenBounds()\n\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\tconst culledShapeCount = this.editor.getCulledShapes().size\n\n\t\tconst event: TLCameraEndPerfEvent = {\n\t\t\ttype: camera.type,\n\t\t\tduration,\n\t\t\tfps: camera.frameTimes.length > 0 ? (camera.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: camera.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: camera.frameTimes,\n\t\t\tshapeCount: totalShapes,\n\t\t\tviewportWidth: viewportBounds.w,\n\t\t\tviewportHeight: viewportBounds.h,\n\t\t\tlongAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : undefined,\n\t\t\tvisibleShapeCount: totalShapes - culledShapeCount,\n\t\t\tculledShapeCount,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('camera-end', event)\n\t}\n\n\t@bind\n\tprivate _onFrame(elapsed: number) {\n\t\t// Record frame time for active interaction/camera\n\t\tif (this.activeInteraction) {\n\t\t\tthis.activeInteraction.frameTimes.push(elapsed)\n\t\t}\n\t\tif (this.activeCamera) {\n\t\t\tthis.activeCamera.frameTimes.push(elapsed)\n\t\t}\n\n\t\t// Emit standalone frame event if listeners exist\n\t\tif (this.emitter.listenerCount('frame') > 0) {\n\t\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\t\tconst culledShapes = this.editor.getCulledShapes()\n\t\t\tconst culledCount = culledShapes.size\n\t\t\tconst event: TLFramePerfEvent = {\n\t\t\t\telapsed,\n\t\t\t\tshapeCount: totalShapes,\n\t\t\t\tculledShapeCount: culledCount,\n\t\t\t\tvisibleShapeCount: totalShapes - culledCount,\n\t\t\t}\n\t\t\tthis.emitter.emit('frame', event)\n\t\t}\n\t}\n\n\t@bind\n\tprivate _onShapesCreated(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-created') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'create',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-created', event)\n\t}\n\n\t@bind\n\tprivate _onShapesEdited(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-updated') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'update',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-updated', event)\n\t}\n\n\t@bind\n\tprivate _onShapesDeleted(ids: TLShapeId[]) {\n\t\tif (this.emitter.listenerCount('shapes-deleted') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const id of ids) {\n\t\t\t// Works because 'deleted-shapes' fires before store.remove() in Editor.deleteShapes\n\t\t\tconst shape = this.editor.getShape(id)\n\t\t\tif (shape) {\n\t\t\t\tshapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'delete',\n\t\t\tcount: ids.length,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-deleted', event)\n\t}\n\n\t// --- LoAF observer ---\n\n\tprivate _startLoafObserver() {\n\t\tif (typeof PerformanceObserver === 'undefined') return\n\n\t\ttry {\n\t\t\tconst supported = PerformanceObserver.supportedEntryTypes\n\t\t\tif (!supported?.includes('long-animation-frame')) return\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\n\t\tthis.loafObserver = new PerformanceObserver((list) => {\n\t\t\tconst isInteractionActive = this.activeInteraction !== null\n\t\t\tconst isCameraActive = this.activeCamera !== null\n\n\t\t\tif (!isInteractionActive && !isCameraActive) return\n\n\t\t\tfor (const entry of list.getEntries()) {\n\t\t\t\tconst loaf = toLoafEntry(entry)\n\t\t\t\tif (!loaf) continue\n\n\t\t\t\tif (isInteractionActive) {\n\t\t\t\t\tthis.activeInteraction!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t\tif (isCameraActive) {\n\t\t\t\t\tthis.activeCamera!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tthis.loafObserver.observe({ type: 'long-animation-frame', buffered: false })\n\t}\n\n\tprivate _stopLoafObserver() {\n\t\tif (this.loafObserver) {\n\t\t\tthis.loafObserver.disconnect()\n\t\t\tthis.loafObserver = null\n\t\t}\n\t}\n\n\t// --- Lazy listener management ---\n\n\tprivate _needsFrameListener(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('frame') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _needsLoafObserver(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _maybeAttachLazyListeners(event: keyof TLPerfEventMap) {\n\t\t// Frame listener needed for frame event + interaction/camera frame time tracking\n\t\tif (\n\t\t\t!this.frameCleanup &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (this._needsFrameListener()) {\n\t\t\t\tthis.editor.on('frame', this._onFrame)\n\t\t\t\tthis.frameCleanup = () => this.editor.off('frame', this._onFrame)\n\t\t\t}\n\t\t}\n\n\t\t// LoAF observer needed when interaction-end or camera-end listeners exist\n\t\tif (!this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (this._needsLoafObserver()) {\n\t\t\t\tthis._startLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (!this.shapeCreatedCleanup && event === 'shapes-created') {\n\t\t\tthis.editor.on('created-shapes', this._onShapesCreated)\n\t\t\tthis.shapeCreatedCleanup = () => this.editor.off('created-shapes', this._onShapesCreated)\n\t\t}\n\n\t\tif (!this.shapeEditedCleanup && event === 'shapes-updated') {\n\t\t\tthis.editor.on('edited-shapes', this._onShapesEdited)\n\t\t\tthis.shapeEditedCleanup = () => this.editor.off('edited-shapes', this._onShapesEdited)\n\t\t}\n\n\t\tif (!this.shapeDeletedCleanup && event === 'shapes-deleted') {\n\t\t\tthis.editor.on('deleted-shapes', this._onShapesDeleted)\n\t\t\tthis.shapeDeletedCleanup = () => this.editor.off('deleted-shapes', this._onShapesDeleted)\n\t\t}\n\t}\n\n\tprivate _maybeDetachLazyListeners(event: keyof TLPerfEventMap) {\n\t\tif (\n\t\t\tthis.frameCleanup &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (!this._needsFrameListener()) {\n\t\t\t\tthis.frameCleanup()\n\t\t\t\tthis.frameCleanup = null\n\t\t\t}\n\t\t}\n\n\t\t// Stop LoAF observer when no longer needed\n\t\tif (this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (!this._needsLoafObserver()) {\n\t\t\t\tthis._stopLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeCreatedCleanup &&\n\t\t\tevent === 'shapes-created' &&\n\t\t\tthis.emitter.listenerCount('shapes-created') === 0\n\t\t) {\n\t\t\tthis.shapeCreatedCleanup()\n\t\t\tthis.shapeCreatedCleanup = null\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeEditedCleanup &&\n\t\t\tevent === 'shapes-updated' &&\n\t\t\tthis.emitter.listenerCount('shapes-updated') === 0\n\t\t) {\n\t\t\tthis.shapeEditedCleanup()\n\t\t\tthis.shapeEditedCleanup = null\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeDeletedCleanup &&\n\t\t\tevent === 'shapes-deleted' &&\n\t\t\tthis.emitter.listenerCount('shapes-deleted') === 0\n\t\t) {\n\t\t\tthis.shapeDeletedCleanup()\n\t\t\tthis.shapeDeletedCleanup = null\n\t\t}\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { TLRecord, TLShapeId } from '@tldraw/tlschema'\nimport { bind } from '@tldraw/utils'\nimport EventEmitter from 'eventemitter3'\nimport { EditorManager } from '../EditorManager'\nimport type {\n\tTLCameraEndPerfEvent,\n\tTLCameraStartPerfEvent,\n\tTLFramePerfEvent,\n\tTLInteractionEndPerfEvent,\n\tTLInteractionStartPerfEvent,\n\tTLPerfEventMap,\n\tTLPerfLongAnimationFrame,\n\tTLShapeOperationPerfEvent,\n\tTLUndoRedoPerfEvent,\n} from './perf-types'\n\n// The editor listeners and observer that are attached only while a subscriber needs them\ntype LazyListener = 'frame' | 'loaf' | 'shapes-created' | 'shapes-updated' | 'shapes-deleted'\n\nfunction percentile(sorted: number[], p: number): number {\n\tconst idx = Math.ceil(p * sorted.length) - 1\n\treturn sorted[Math.max(0, idx)]\n}\n\nfunction computeFrameTimeStats(frameTimes: number[]) {\n\tif (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 }\n\tconst sorted = [...frameTimes].sort((a, b) => a - b)\n\tconst sum = sorted.reduce((a, b) => a + b, 0)\n\treturn {\n\t\tavg: sum / sorted.length,\n\t\tmedian: percentile(sorted, 0.5),\n\t\tp95: percentile(sorted, 0.95),\n\t\tp99: percentile(sorted, 0.99),\n\t\tmin: sorted[0],\n\t\tmax: sorted[sorted.length - 1],\n\t}\n}\n\nfunction toLoafEntry(entry: PerformanceEntry): TLPerfLongAnimationFrame | null {\n\t// LoAF entries have these properties but TypeScript doesn't know about them yet\n\tconst e = entry as PerformanceEntry & {\n\t\tblockingDuration?: number\n\t\tscripts?: ReadonlyArray<{\n\t\t\tsourceURL?: string\n\t\t\tinvoker?: string\n\t\t\tduration?: number\n\t\t}>\n\t}\n\tif (typeof e.duration !== 'number') return null\n\treturn {\n\t\tstartTime: e.startTime,\n\t\tduration: e.duration,\n\t\tblockingDuration: e.blockingDuration ?? 0,\n\t\tscripts: (e.scripts ?? []).map((s) => ({\n\t\t\tsourceURL: s.sourceURL ?? '',\n\t\t\tinvoker: s.invoker ?? '',\n\t\t\tduration: s.duration ?? 0,\n\t\t})),\n\t}\n}\n\n/**\n * Manages performance event subscriptions for the editor. Available as `editor.performance`.\n *\n * Listeners are lazy \u2014 internal editor hooks (frame, shape events) are only attached while\n * at least one subscriber exists, so there is zero overhead when unused.\n *\n * @example\n * ```ts\n * const unsub = editor.performance.on('interaction-end', (event) => {\n * console.log(`${event.name}: ${event.fps.toFixed(1)} fps, p95=${event.p95FrameTime.toFixed(1)}ms`)\n * })\n * ```\n *\n * @public\n */\nexport class PerformanceManager extends EditorManager {\n\t/** @internal */\n\treadonly emitter = new EventEmitter<TLPerfEventMap>()\n\n\t// Active interaction tracking\n\tprivate activeInteraction: {\n\t\tname: string\n\t\tpath: string\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\tselectedShapeTypes: Record<string, number>\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Active camera tracking\n\tprivate activeCamera: {\n\t\ttype: 'panning' | 'zooming'\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\ttimeout: number | null\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Cleanups for whatever is currently attached, keyed so a detach unregisters only its own\n\tprivate readonly lazyCleanups = new Map<LazyListener, () => void>()\n\n\t/**\n\t * Subscribe to a performance event. Returns an unsubscribe function.\n\t *\n\t * @example\n\t * ```ts\n\t * const unsub = editor.performance.on('interaction-end', (event) => {\n\t * sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })\n\t * })\n\t * // later: unsub()\n\t * ```\n\t *\n\t * @public\n\t */\n\ton<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tthis.emitter.on(event, fn as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, fn as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to a performance event once. The listener is removed after the first invocation.\n\t * Returns an unsubscribe function for early removal.\n\t *\n\t * @public\n\t */\n\tonce<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tconst wrapped = (...args: TLPerfEventMap[K]) => {\n\t\t\t;(fn as any)(...args)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t\tthis.emitter.once(event, wrapped as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, wrapped as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/** @internal */\n\tdispose() {\n\t\tif (this.activeCamera?.timeout) this.editor.timers.clearTimeout(this.activeCamera.timeout)\n\t\tthis.activeInteraction = null\n\t\tthis.activeCamera = null\n\t\tthis.lazyCleanups.clear()\n\t\tthis.emitter.removeAllListeners()\n\t\tsuper.dispose()\n\t}\n\n\t// --- Internal notification methods ---\n\n\t/** @internal */\n\t_notifyInteractionStart(name: string, path: string) {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('interaction-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('interaction-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeInteraction) {\n\t\t\tconsole.warn(\n\t\t\t\t`[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`\n\t\t\t)\n\t\t}\n\n\t\t// Capture selected shape types at start\n\t\tconst selectedShapeTypes: Record<string, number> = {}\n\t\tfor (const shape of this.editor.getSelectedShapes()) {\n\t\t\tselectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1\n\t\t}\n\n\t\tthis.activeInteraction = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\tselectedShapeTypes,\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tconst event: TLInteractionStartPerfEvent = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-start', event)\n\t}\n\n\t/** @internal */\n\t_notifyInteractionEnd() {\n\t\tconst interaction = this.activeInteraction\n\t\tif (!interaction) return\n\t\tthis.activeInteraction = null\n\n\t\tif (this.emitter.listenerCount('interaction-end') === 0) return\n\n\t\tconst duration = performance.now() - interaction.startTime\n\t\tconst stats = computeFrameTimeStats(interaction.frameTimes)\n\n\t\tconst event: TLInteractionEndPerfEvent = {\n\t\t\tname: interaction.name,\n\t\t\tpath: interaction.path,\n\t\t\tduration,\n\t\t\tfps:\n\t\t\t\tinteraction.frameTimes.length > 0 ? (interaction.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: interaction.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: interaction.frameTimes,\n\t\t\tshapeCount: this.editor.getCurrentPageShapeIds().size,\n\t\t\tselectedShapeTypes: interaction.selectedShapeTypes,\n\t\t\tlongAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : undefined,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-end', event)\n\t}\n\n\t/** @internal */\n\t_notifyCameraOperation(type: 'panning' | 'zooming') {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('camera-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('camera-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeCamera) {\n\t\t\t// Extend existing camera session\n\t\t\tif (this.activeCamera.timeout) {\n\t\t\t\tthis.editor.timers.clearTimeout(this.activeCamera.timeout)\n\t\t\t}\n\t\t\t// If type changed, end old and start new\n\t\t\tif (this.activeCamera.type !== type) {\n\t\t\t\tthis._endCameraSession()\n\t\t\t\tthis._startCameraSession(type)\n\t\t\t} else {\n\t\t\t\t// Reset timeout\n\t\t\t\tthis.activeCamera.timeout = this.editor.timers.setTimeout(\n\t\t\t\t\t() => this._endCameraSession(),\n\t\t\t\t\t50\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tthis._startCameraSession(type)\n\t\t}\n\t}\n\n\t/** @internal */\n\t_notifyUndoRedo(type: 'undo' | 'redo', undoDepth: number, redoDepth: number) {\n\t\tif (this.emitter.listenerCount(type) === 0) return\n\n\t\tconst event: TLUndoRedoPerfEvent = {\n\t\t\ttype,\n\t\t\tundoDepth,\n\t\t\tredoDepth,\n\t\t}\n\t\tthis.emitter.emit(type, event)\n\t}\n\n\t// --- Private helpers ---\n\n\tprivate _startCameraSession(type: 'panning' | 'zooming') {\n\t\tthis.activeCamera = {\n\t\t\ttype,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\ttimeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tif (this.emitter.listenerCount('camera-start') > 0) {\n\t\t\tconst event: TLCameraStartPerfEvent = {\n\t\t\t\ttype,\n\t\t\t\ttimestamp: performance.now(),\n\t\t\t}\n\t\t\tthis.emitter.emit('camera-start', event)\n\t\t}\n\t}\n\n\tprivate _endCameraSession() {\n\t\tconst camera = this.activeCamera\n\t\tif (!camera) return\n\t\tthis.activeCamera = null\n\t\tif (camera.timeout) this.editor.timers.clearTimeout(camera.timeout)\n\n\t\tif (this.emitter.listenerCount('camera-end') === 0) return\n\n\t\tconst duration = performance.now() - camera.startTime\n\t\tconst stats = computeFrameTimeStats(camera.frameTimes)\n\t\tconst viewportBounds = this.editor.getViewportScreenBounds()\n\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\tconst culledShapeCount = this.editor.getCulledShapes().size\n\n\t\tconst event: TLCameraEndPerfEvent = {\n\t\t\ttype: camera.type,\n\t\t\tduration,\n\t\t\tfps: camera.frameTimes.length > 0 ? (camera.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: camera.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: camera.frameTimes,\n\t\t\tshapeCount: totalShapes,\n\t\t\tviewportWidth: viewportBounds.w,\n\t\t\tviewportHeight: viewportBounds.h,\n\t\t\tlongAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : undefined,\n\t\t\tvisibleShapeCount: totalShapes - culledShapeCount,\n\t\t\tculledShapeCount,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('camera-end', event)\n\t}\n\n\t@bind\n\tprivate _onFrame(elapsed: number) {\n\t\t// Record frame time for active interaction/camera\n\t\tif (this.activeInteraction) {\n\t\t\tthis.activeInteraction.frameTimes.push(elapsed)\n\t\t}\n\t\tif (this.activeCamera) {\n\t\t\tthis.activeCamera.frameTimes.push(elapsed)\n\t\t}\n\n\t\t// Emit standalone frame event if listeners exist\n\t\tif (this.emitter.listenerCount('frame') > 0) {\n\t\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\t\tconst culledShapes = this.editor.getCulledShapes()\n\t\t\tconst culledCount = culledShapes.size\n\t\t\tconst event: TLFramePerfEvent = {\n\t\t\t\telapsed,\n\t\t\t\tshapeCount: totalShapes,\n\t\t\t\tculledShapeCount: culledCount,\n\t\t\t\tvisibleShapeCount: totalShapes - culledCount,\n\t\t\t}\n\t\t\tthis.emitter.emit('frame', event)\n\t\t}\n\t}\n\n\t@bind\n\tprivate _onShapesCreated(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-created') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'create',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-created', event)\n\t}\n\n\t@bind\n\tprivate _onShapesEdited(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-updated') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'update',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-updated', event)\n\t}\n\n\t@bind\n\tprivate _onShapesDeleted(ids: TLShapeId[]) {\n\t\tif (this.emitter.listenerCount('shapes-deleted') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const id of ids) {\n\t\t\t// Works because 'deleted-shapes' fires before store.remove() in Editor.deleteShapes\n\t\t\tconst shape = this.editor.getShape(id)\n\t\t\tif (shape) {\n\t\t\t\tshapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'delete',\n\t\t\tcount: ids.length,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-deleted', event)\n\t}\n\n\t// --- LoAF observer ---\n\n\tprivate _startLoafObserver() {\n\t\tif (typeof PerformanceObserver === 'undefined') return\n\n\t\ttry {\n\t\t\tconst supported = PerformanceObserver.supportedEntryTypes\n\t\t\tif (!supported?.includes('long-animation-frame')) return\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\n\t\tconst observer = new PerformanceObserver((list) => {\n\t\t\tconst isInteractionActive = this.activeInteraction !== null\n\t\t\tconst isCameraActive = this.activeCamera !== null\n\n\t\t\tif (!isInteractionActive && !isCameraActive) return\n\n\t\t\tfor (const entry of list.getEntries()) {\n\t\t\t\tconst loaf = toLoafEntry(entry)\n\t\t\t\tif (!loaf) continue\n\n\t\t\t\tif (isInteractionActive) {\n\t\t\t\t\tthis.activeInteraction!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t\tif (isCameraActive) {\n\t\t\t\t\tthis.activeCamera!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tobserver.observe({ type: 'long-animation-frame', buffered: false })\n\t\tthis.lazyCleanups.set(\n\t\t\t'loaf',\n\t\t\tthis.register(() => observer.disconnect())\n\t\t)\n\t}\n\n\t// --- Lazy listener management ---\n\n\tprivate _needsFrameListener(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('frame') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _needsLoafObserver(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _maybeAttachLazyListeners(event: keyof TLPerfEventMap) {\n\t\t// Frame listener needed for frame event + interaction/camera frame time tracking\n\t\tif (\n\t\t\t!this.lazyCleanups.has('frame') &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (this._needsFrameListener()) {\n\t\t\t\tthis.lazyCleanups.set('frame', this.addEditorEvent('frame', this._onFrame))\n\t\t\t}\n\t\t}\n\n\t\t// LoAF observer needed when interaction-end or camera-end listeners exist\n\t\tif (!this.lazyCleanups.has('loaf') && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (this._needsLoafObserver()) {\n\t\t\t\tthis._startLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (!this.lazyCleanups.has('shapes-created') && event === 'shapes-created') {\n\t\t\tthis.lazyCleanups.set(\n\t\t\t\t'shapes-created',\n\t\t\t\tthis.addEditorEvent('created-shapes', this._onShapesCreated)\n\t\t\t)\n\t\t}\n\n\t\tif (!this.lazyCleanups.has('shapes-updated') && event === 'shapes-updated') {\n\t\t\tthis.lazyCleanups.set(\n\t\t\t\t'shapes-updated',\n\t\t\t\tthis.addEditorEvent('edited-shapes', this._onShapesEdited)\n\t\t\t)\n\t\t}\n\n\t\tif (!this.lazyCleanups.has('shapes-deleted') && event === 'shapes-deleted') {\n\t\t\tthis.lazyCleanups.set(\n\t\t\t\t'shapes-deleted',\n\t\t\t\tthis.addEditorEvent('deleted-shapes', this._onShapesDeleted)\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate _maybeDetachLazyListeners(event: keyof TLPerfEventMap) {\n\t\tif (\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end') &&\n\t\t\t!this._needsFrameListener()\n\t\t) {\n\t\t\tthis._detachLazy('frame')\n\t\t}\n\n\t\t// Stop LoAF observer when no longer needed\n\t\tif ((event === 'interaction-end' || event === 'camera-end') && !this._needsLoafObserver()) {\n\t\t\tthis._detachLazy('loaf')\n\t\t}\n\n\t\tif (event === 'shapes-created' && this.emitter.listenerCount('shapes-created') === 0) {\n\t\t\tthis._detachLazy('shapes-created')\n\t\t}\n\n\t\tif (event === 'shapes-updated' && this.emitter.listenerCount('shapes-updated') === 0) {\n\t\t\tthis._detachLazy('shapes-updated')\n\t\t}\n\n\t\tif (event === 'shapes-deleted' && this.emitter.listenerCount('shapes-deleted') === 0) {\n\t\t\tthis._detachLazy('shapes-deleted')\n\t\t}\n\t}\n\n\tprivate _detachLazy(key: LazyListener) {\n\t\tconst cleanup = this.lazyCleanups.get(key)\n\t\tif (!cleanup) return\n\t\tthis.unregister(cleanup)\n\t\tthis.lazyCleanups.delete(key)\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["EventEmitter"]
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "5.5.0-canary.db5d20befa38";
25
+ const version = "5.5.0-canary.ddcb7ca958fa";
26
26
  const publishDates = {
27
27
  major: "2026-05-06T16:28:18.473Z",
28
- minor: "2026-09-22T16:34:56.345Z",
29
- patch: "2026-09-22T16:34:56.345Z"
28
+ minor: "2026-09-23T12:53:21.553Z",
29
+ patch: "2026-09-23T12:53:21.553Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.5.0-canary.db5d20befa38'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-22T16:34:56.345Z',\n\tpatch: '2026-09-22T16:34:56.345Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.5.0-canary.ddcb7ca958fa'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-23T12:53:21.553Z',\n\tpatch: '2026-09-23T12:53:21.553Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -4579,6 +4579,8 @@ export declare const EditorContext: React_3.Context<Editor | null>;
4579
4579
  * - Reaction (`react(...)` from `@tldraw/state`): `this.register(react(...))`
4580
4580
  * - DOM listener: `this.register(() => el.removeEventListener(...))`
4581
4581
  * - Other resource (cache, index, child manager): `this.register(() => x.dispose())`
4582
+ * - Resource held only part of the time (a listener attached while subscribers exist):
4583
+ * `this.register(...)` on attach and `this.unregister(...)` on detach, see `PerformanceManager`
4582
4584
  *
4583
4585
  * For timeouts, intervals, and animation frames prefer `editor.timers` (context-grouped
4584
4586
  * and auto-disposed) over raw `setTimeout`. For cleanup on the editor itself rather than a
@@ -4598,11 +4600,18 @@ export declare abstract class EditorManager {
4598
4600
  * make a resource "see itself out". Returns the same function for convenience.
4599
4601
  */
4600
4602
  protected register(dispose: () => void): () => void;
4603
+ /**
4604
+ * Run a registered teardown now instead of at `dispose()`. A teardown that is no longer
4605
+ * registered (already run by `dispose()`, or never registered) is skipped, so nothing is
4606
+ * torn down twice.
4607
+ */
4608
+ protected unregister(dispose: () => void): void;
4601
4609
  /**
4602
4610
  * Subscribe to an editor bus event and register the matching unsubscribe, so the listener
4603
- * is removed automatically on `dispose()`.
4611
+ * is removed automatically on `dispose()`. Returns that unsubscribe, for passing to
4612
+ * {@link EditorManager.unregister} to detach it before `dispose()`.
4604
4613
  */
4605
- protected addEditorEvent<E extends keyof TLEventMap>(event: E, fn: (...args: TLEventMap[E]) => void): void;
4614
+ protected addEditorEvent<E extends keyof TLEventMap>(event: E, fn: (...args: TLEventMap[E]) => void): () => void;
4606
4615
  /* Excluded from this release type: dispose */
4607
4616
  }
4608
4617
 
@@ -5990,17 +5999,11 @@ export declare class PerformanceApiAdapter {
5990
5999
  *
5991
6000
  * @public
5992
6001
  */
5993
- export declare class PerformanceManager {
6002
+ export declare class PerformanceManager extends EditorManager {
5994
6003
  /* Excluded from this release type: emitter */
5995
- private editor;
5996
6004
  private activeInteraction;
5997
6005
  private activeCamera;
5998
- private frameCleanup;
5999
- private shapeCreatedCleanup;
6000
- private shapeEditedCleanup;
6001
- private shapeDeletedCleanup;
6002
- private loafObserver;
6003
- constructor(editor: Editor);
6006
+ private readonly lazyCleanups;
6004
6007
  /**
6005
6008
  * Subscribe to a performance event. Returns an unsubscribe function.
6006
6009
  *
@@ -6034,11 +6037,11 @@ export declare class PerformanceManager {
6034
6037
  private _onShapesEdited;
6035
6038
  private _onShapesDeleted;
6036
6039
  private _startLoafObserver;
6037
- private _stopLoafObserver;
6038
6040
  private _needsFrameListener;
6039
6041
  private _needsLoafObserver;
6040
6042
  private _maybeAttachLazyListeners;
6041
6043
  private _maybeDetachLazyListeners;
6044
+ private _detachLazy;
6042
6045
  }
6043
6046
 
6044
6047
  /**
@@ -312,7 +312,7 @@ import { LocalIndexedDb, Table } from "./lib/utils/sync/LocalIndexedDb.mjs";
312
312
  import { uniq } from "./lib/utils/uniq.mjs";
313
313
  registerTldrawLibraryVersion(
314
314
  "@tldraw/editor",
315
- "5.5.0-canary.db5d20befa38",
315
+ "5.5.0-canary.ddcb7ca958fa",
316
316
  "esm"
317
317
  );
318
318
  import { getColorValue } from "./lib/editor/managers/ThemeManager/defaultThemes.mjs";
@@ -12,13 +12,22 @@ class EditorManager {
12
12
  this.disposables.add(dispose);
13
13
  return dispose;
14
14
  }
15
+ /**
16
+ * Run a registered teardown now instead of at `dispose()`. A teardown that is no longer
17
+ * registered (already run by `dispose()`, or never registered) is skipped, so nothing is
18
+ * torn down twice.
19
+ */
20
+ unregister(dispose) {
21
+ if (this.disposables.delete(dispose)) dispose();
22
+ }
15
23
  /**
16
24
  * Subscribe to an editor bus event and register the matching unsubscribe, so the listener
17
- * is removed automatically on `dispose()`.
25
+ * is removed automatically on `dispose()`. Returns that unsubscribe, for passing to
26
+ * {@link EditorManager.unregister} to detach it before `dispose()`.
18
27
  */
19
28
  addEditorEvent(event, fn) {
20
29
  this.editor.on(event, fn);
21
- this.register(() => this.editor.off(event, fn));
30
+ return this.register(() => this.editor.off(event, fn));
22
31
  }
23
32
  /** @internal */
24
33
  dispose() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/managers/EditorManager.ts"],
4
- "sourcesContent": ["import type { Editor } from '../Editor'\nimport type { TLEventMap } from '../types/emit-types'\n\n/**\n * Base class for editor-attached managers that need to clean up after themselves.\n *\n * Extending `EditorManager` gives a manager one consistent teardown contract: register\n * anything that needs undoing, and it runs automatically on `dispose()`. This keeps\n * subscription lifecycle symmetric \u2014 whatever sets a thing up is what tears it down \u2014 so\n * it can't be forgotten (the failure mode behind the strict-mode camera bug, #8892).\n *\n * Register cleanup by what you're creating:\n *\n * - Editor bus event (`tick`, `frame`, `change`, \u2026): {@link EditorManager.addEditorEvent}\n * - Store side effect (`editor.sideEffects.register\u2026`): `this.register(disposer)`\n * - Reaction (`react(...)` from `@tldraw/state`): `this.register(react(...))`\n * - DOM listener: `this.register(() => el.removeEventListener(...))`\n * - Other resource (cache, index, child manager): `this.register(() => x.dispose())`\n *\n * For timeouts, intervals, and animation frames prefer `editor.timers` (context-grouped\n * and auto-disposed) over raw `setTimeout`. For cleanup on the editor itself rather than a\n * manager, use `editor.disposables`.\n *\n * If a manager needs ordered teardown (e.g. pause a loop before cancelling it), override\n * `dispose()` and call `super.dispose()` last \u2014 see `TickManager`.\n *\n * @public\n */\nexport abstract class EditorManager {\n\tprotected readonly disposables = new Set<() => void>()\n\n\tconstructor(protected readonly editor: Editor) {}\n\n\t/**\n\t * Register a teardown function to run on `dispose()`. The universal way for a manager to\n\t * make a resource \"see itself out\". Returns the same function for convenience.\n\t */\n\tprotected register(dispose: () => void): () => void {\n\t\tthis.disposables.add(dispose)\n\t\treturn dispose\n\t}\n\n\t/**\n\t * Subscribe to an editor bus event and register the matching unsubscribe, so the listener\n\t * is removed automatically on `dispose()`.\n\t */\n\tprotected addEditorEvent<E extends keyof TLEventMap>(\n\t\tevent: E,\n\t\tfn: (...args: TLEventMap[E]) => void\n\t): void {\n\t\tthis.editor.on(event, fn as any)\n\t\tthis.register(() => this.editor.off(event, fn as any))\n\t}\n\n\t/** @internal */\n\tdispose(): void {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.disposables.clear()\n\t}\n}\n"],
5
- "mappings": "AA4BO,MAAe,cAAc;AAAA,EAGnC,YAA+B,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EAFZ,cAAc,oBAAI,IAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3C,SAAS,SAAiC;AACnD,SAAK,YAAY,IAAI,OAAO;AAC5B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,eACT,OACA,IACO;AACP,SAAK,OAAO,GAAG,OAAO,EAAS;AAC/B,SAAK,SAAS,MAAM,KAAK,OAAO,IAAI,OAAO,EAAS,CAAC;AAAA,EACtD;AAAA;AAAA,EAGA,UAAgB;AACf,SAAK,YAAY,QAAQ,CAAC,MAAM,EAAE,CAAC;AACnC,SAAK,YAAY,MAAM;AAAA,EACxB;AACD;",
4
+ "sourcesContent": ["import type { Editor } from '../Editor'\nimport type { TLEventMap } from '../types/emit-types'\n\n/**\n * Base class for editor-attached managers that need to clean up after themselves.\n *\n * Extending `EditorManager` gives a manager one consistent teardown contract: register\n * anything that needs undoing, and it runs automatically on `dispose()`. This keeps\n * subscription lifecycle symmetric \u2014 whatever sets a thing up is what tears it down \u2014 so\n * it can't be forgotten (the failure mode behind the strict-mode camera bug, #8892).\n *\n * Register cleanup by what you're creating:\n *\n * - Editor bus event (`tick`, `frame`, `change`, \u2026): {@link EditorManager.addEditorEvent}\n * - Store side effect (`editor.sideEffects.register\u2026`): `this.register(disposer)`\n * - Reaction (`react(...)` from `@tldraw/state`): `this.register(react(...))`\n * - DOM listener: `this.register(() => el.removeEventListener(...))`\n * - Other resource (cache, index, child manager): `this.register(() => x.dispose())`\n * - Resource held only part of the time (a listener attached while subscribers exist):\n * `this.register(...)` on attach and `this.unregister(...)` on detach, see `PerformanceManager`\n *\n * For timeouts, intervals, and animation frames prefer `editor.timers` (context-grouped\n * and auto-disposed) over raw `setTimeout`. For cleanup on the editor itself rather than a\n * manager, use `editor.disposables`.\n *\n * If a manager needs ordered teardown (e.g. pause a loop before cancelling it), override\n * `dispose()` and call `super.dispose()` last \u2014 see `TickManager`.\n *\n * @public\n */\nexport abstract class EditorManager {\n\tprotected readonly disposables = new Set<() => void>()\n\n\tconstructor(protected readonly editor: Editor) {}\n\n\t/**\n\t * Register a teardown function to run on `dispose()`. The universal way for a manager to\n\t * make a resource \"see itself out\". Returns the same function for convenience.\n\t */\n\tprotected register(dispose: () => void): () => void {\n\t\tthis.disposables.add(dispose)\n\t\treturn dispose\n\t}\n\n\t/**\n\t * Run a registered teardown now instead of at `dispose()`. A teardown that is no longer\n\t * registered (already run by `dispose()`, or never registered) is skipped, so nothing is\n\t * torn down twice.\n\t */\n\tprotected unregister(dispose: () => void): void {\n\t\tif (this.disposables.delete(dispose)) dispose()\n\t}\n\n\t/**\n\t * Subscribe to an editor bus event and register the matching unsubscribe, so the listener\n\t * is removed automatically on `dispose()`. Returns that unsubscribe, for passing to\n\t * {@link EditorManager.unregister} to detach it before `dispose()`.\n\t */\n\tprotected addEditorEvent<E extends keyof TLEventMap>(\n\t\tevent: E,\n\t\tfn: (...args: TLEventMap[E]) => void\n\t): () => void {\n\t\tthis.editor.on(event, fn as any)\n\t\treturn this.register(() => this.editor.off(event, fn as any))\n\t}\n\n\t/** @internal */\n\tdispose(): void {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.disposables.clear()\n\t}\n}\n"],
5
+ "mappings": "AA8BO,MAAe,cAAc;AAAA,EAGnC,YAA+B,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EAFZ,cAAc,oBAAI,IAAgB;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3C,SAAS,SAAiC;AACnD,SAAK,YAAY,IAAI,OAAO;AAC5B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,WAAW,SAA2B;AAC/C,QAAI,KAAK,YAAY,OAAO,OAAO,EAAG,SAAQ;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,eACT,OACA,IACa;AACb,SAAK,OAAO,GAAG,OAAO,EAAS;AAC/B,WAAO,KAAK,SAAS,MAAM,KAAK,OAAO,IAAI,OAAO,EAAS,CAAC;AAAA,EAC7D;AAAA;AAAA,EAGA,UAAgB;AACf,SAAK,YAAY,QAAQ,CAAC,MAAM,EAAE,CAAC;AACnC,SAAK,YAAY,MAAM;AAAA,EACxB;AACD;",
6
6
  "names": []
7
7
  }