@tldraw/editor 4.6.0-next.20de11b7e238 → 4.6.0-next.241e87d4700a

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 (156) hide show
  1. package/dist-cjs/index.d.ts +668 -96
  2. package/dist-cjs/index.js +16 -3
  3. package/dist-cjs/index.js.map +3 -3
  4. package/dist-cjs/lib/TldrawEditor.js +55 -12
  5. package/dist-cjs/lib/TldrawEditor.js.map +3 -3
  6. package/dist-cjs/lib/components/MenuClickCapture.js +16 -1
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +3 -3
  9. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
  10. package/dist-cjs/lib/config/createTLStore.js +7 -0
  11. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  12. package/dist-cjs/lib/config/defaultAssets.js +36 -0
  13. package/dist-cjs/lib/config/defaultAssets.js.map +7 -0
  14. package/dist-cjs/lib/editor/Editor.js +215 -5
  15. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  16. package/dist-cjs/lib/editor/assets/AssetUtil.js +66 -0
  17. package/dist-cjs/lib/editor/assets/AssetUtil.js.map +7 -0
  18. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js +80 -0
  20. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js.map +7 -0
  21. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js +466 -0
  22. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js.map +7 -0
  23. package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js +17 -0
  24. package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js.map +7 -0
  25. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +106 -0
  26. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +7 -0
  27. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js +586 -0
  28. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js.map +7 -0
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +6 -4
  30. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +11 -2
  32. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  33. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  34. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  35. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +6 -0
  36. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  37. package/dist-cjs/lib/editor/tools/StateNode.js +14 -17
  38. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  39. package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
  40. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  41. package/dist-cjs/lib/exports/getSvgJsx.js +12 -7
  42. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  43. package/dist-cjs/lib/globals/environment.js +18 -1
  44. package/dist-cjs/lib/globals/environment.js.map +2 -2
  45. package/dist-cjs/lib/hooks/{useIsDarkMode.js → useColorMode.js} +14 -10
  46. package/dist-cjs/lib/hooks/useColorMode.js.map +7 -0
  47. package/dist-cjs/lib/hooks/useCursor.js +3 -7
  48. package/dist-cjs/lib/hooks/useCursor.js.map +2 -2
  49. package/dist-cjs/lib/hooks/useDarkMode.js +4 -4
  50. package/dist-cjs/lib/hooks/useDarkMode.js.map +2 -2
  51. package/dist-cjs/lib/utils/reparenting.js +2 -1
  52. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  53. package/dist-cjs/lib/utils/richText.js.map +2 -2
  54. package/dist-cjs/lib/utils/runtime.js +2 -1
  55. package/dist-cjs/lib/utils/runtime.js.map +2 -2
  56. package/dist-cjs/lib/utils/sync/hardReset.js +0 -8
  57. package/dist-cjs/lib/utils/sync/hardReset.js.map +2 -2
  58. package/dist-cjs/version.js +3 -3
  59. package/dist-cjs/version.js.map +1 -1
  60. package/dist-esm/index.d.mts +668 -96
  61. package/dist-esm/index.mjs +17 -6
  62. package/dist-esm/index.mjs.map +2 -2
  63. package/dist-esm/lib/TldrawEditor.mjs +58 -12
  64. package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
  65. package/dist-esm/lib/components/MenuClickCapture.mjs +16 -1
  66. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  67. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +3 -3
  68. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
  69. package/dist-esm/lib/config/createTLStore.mjs +10 -1
  70. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  71. package/dist-esm/lib/config/defaultAssets.mjs +16 -0
  72. package/dist-esm/lib/config/defaultAssets.mjs.map +7 -0
  73. package/dist-esm/lib/editor/Editor.mjs +215 -5
  74. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  75. package/dist-esm/lib/editor/assets/AssetUtil.mjs +46 -0
  76. package/dist-esm/lib/editor/assets/AssetUtil.mjs.map +7 -0
  77. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  78. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs +60 -0
  79. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs.map +7 -0
  80. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs +438 -0
  81. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs.map +7 -0
  82. package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs +1 -0
  83. package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs.map +7 -0
  84. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +88 -0
  85. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +7 -0
  86. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs +568 -0
  87. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs.map +7 -0
  88. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +6 -4
  89. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  90. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +11 -2
  91. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  92. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  93. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  94. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +6 -0
  95. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  96. package/dist-esm/lib/editor/tools/StateNode.mjs +14 -17
  97. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  98. package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
  99. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -10
  100. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  101. package/dist-esm/lib/globals/environment.mjs +18 -1
  102. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  103. package/dist-esm/lib/hooks/useColorMode.mjs +19 -0
  104. package/dist-esm/lib/hooks/useColorMode.mjs.map +7 -0
  105. package/dist-esm/lib/hooks/useCursor.mjs +3 -7
  106. package/dist-esm/lib/hooks/useCursor.mjs.map +2 -2
  107. package/dist-esm/lib/hooks/useDarkMode.mjs +4 -4
  108. package/dist-esm/lib/hooks/useDarkMode.mjs.map +2 -2
  109. package/dist-esm/lib/utils/reparenting.mjs +2 -1
  110. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  111. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  112. package/dist-esm/lib/utils/runtime.mjs +2 -1
  113. package/dist-esm/lib/utils/runtime.mjs.map +2 -2
  114. package/dist-esm/lib/utils/sync/hardReset.mjs +0 -8
  115. package/dist-esm/lib/utils/sync/hardReset.mjs.map +2 -2
  116. package/dist-esm/version.mjs +3 -3
  117. package/dist-esm/version.mjs.map +1 -1
  118. package/editor.css +0 -33
  119. package/package.json +7 -7
  120. package/src/index.ts +23 -6
  121. package/src/lib/TldrawEditor.tsx +90 -13
  122. package/src/lib/components/MenuClickCapture.tsx +20 -0
  123. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +3 -3
  124. package/src/lib/config/createTLStore.ts +22 -1
  125. package/src/lib/config/defaultAssets.ts +19 -0
  126. package/src/lib/editor/Editor.ts +301 -27
  127. package/src/lib/editor/assets/AssetUtil.ts +85 -0
  128. package/src/lib/editor/managers/FontManager/FontManager.test.ts +9 -2
  129. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -67
  130. package/src/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.ts +82 -0
  131. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.test.ts +522 -0
  132. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.ts +583 -0
  133. package/src/lib/editor/managers/PerformanceManager/perf-types.ts +196 -0
  134. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +116 -0
  135. package/src/lib/editor/managers/ThemeManager/defaultThemes.ts +605 -0
  136. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +23 -29
  137. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +5 -3
  138. package/src/lib/editor/shapes/ShapeUtil.ts +28 -3
  139. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  140. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +7 -0
  141. package/src/lib/editor/tools/StateNode.ts +16 -18
  142. package/src/lib/editor/types/SvgExportContext.tsx +5 -0
  143. package/src/lib/editor/types/external-content.ts +1 -0
  144. package/src/lib/exports/getSvgJsx.tsx +21 -15
  145. package/src/lib/globals/environment.ts +18 -0
  146. package/src/lib/hooks/{useIsDarkMode.ts → useColorMode.ts} +9 -5
  147. package/src/lib/hooks/useCursor.ts +3 -7
  148. package/src/lib/hooks/useDarkMode.ts +4 -4
  149. package/src/lib/utils/reparenting.ts +6 -2
  150. package/src/lib/utils/richText.ts +1 -1
  151. package/src/lib/utils/runtime.ts +3 -1
  152. package/src/lib/utils/sync/hardReset.ts +0 -8
  153. package/src/version.ts +3 -3
  154. package/dist-cjs/lib/hooks/useIsDarkMode.js.map +0 -7
  155. package/dist-esm/lib/hooks/useIsDarkMode.mjs +0 -15
  156. package/dist-esm/lib/hooks/useIsDarkMode.mjs.map +0 -7
@@ -0,0 +1,46 @@
1
+ class AssetUtil {
2
+ constructor(editor) {
3
+ this.editor = editor;
4
+ }
5
+ /** Configure this asset util's {@link AssetUtil.options | `options`}. */
6
+ static configure(options) {
7
+ return class extends this {
8
+ // @ts-expect-error
9
+ options = { ...this.options, ...options };
10
+ };
11
+ }
12
+ /**
13
+ * Options for this asset util. Override this to provide customization options for your asset.
14
+ * Use {@link AssetUtil.configure} to customize existing asset utils.
15
+ */
16
+ options = {};
17
+ static props;
18
+ static migrations;
19
+ /**
20
+ * The type of the asset util, which should match the asset's type.
21
+ */
22
+ static type;
23
+ /**
24
+ * Get the MIME types that this asset type supports.
25
+ * Return an empty array if this asset type doesn't support files (e.g. bookmarks).
26
+ */
27
+ getSupportedMimeTypes() {
28
+ return [];
29
+ }
30
+ /**
31
+ * Check whether this asset type accepts a given MIME type.
32
+ */
33
+ acceptsMimeType(mimeType) {
34
+ return this.getSupportedMimeTypes().includes(mimeType);
35
+ }
36
+ /**
37
+ * Create an asset from a file. Return null if this asset type can't handle the file.
38
+ */
39
+ async getAssetFromFile(_file, _assetId) {
40
+ return null;
41
+ }
42
+ }
43
+ export {
44
+ AssetUtil
45
+ };
46
+ //# sourceMappingURL=AssetUtil.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/lib/editor/assets/AssetUtil.ts"],
4
+ "sourcesContent": ["import { LegacyMigrations, MigrationSequence } from '@tldraw/store'\nimport {\n\tRecordProps,\n\tTLAsset,\n\tTLAssetId,\n\tTLPropsMigrations,\n\tTLUnknownAsset,\n} from '@tldraw/tlschema'\nimport type { Editor } from '../Editor'\n\n/** @public */\nexport interface TLAssetUtilConstructor<\n\tT extends TLAsset = TLAsset,\n\tU extends AssetUtil<T> = AssetUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\tprops?: RecordProps<T>\n\tmigrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n}\n\n/**\n * Abstract base class for defining asset-type-specific behavior.\n *\n * Each asset type (image, video, bookmark, etc.) has a corresponding AssetUtil that handles\n * type-specific operations like determining supported MIME types and creating assets from files.\n *\n * @public\n */\nexport abstract class AssetUtil<Asset extends TLAsset = TLAsset> {\n\t/** Configure this asset util's {@link AssetUtil.options | `options`}. */\n\tstatic configure<T extends TLAssetUtilConstructor<any, any>>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- typescript has no idea what's going on here but it's fine\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = { ...this.options, ...options }\n\t\t}\n\t}\n\n\tconstructor(public editor: Editor) {}\n\n\t/**\n\t * Options for this asset util. Override this to provide customization options for your asset.\n\t * Use {@link AssetUtil.configure} to customize existing asset utils.\n\t */\n\toptions = {}\n\n\tstatic props?: RecordProps<TLUnknownAsset>\n\tstatic migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n\n\t/**\n\t * The type of the asset util, which should match the asset's type.\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for an asset of this type.\n\t */\n\tabstract getDefaultProps(): Asset['props']\n\n\t/**\n\t * Get the MIME types that this asset type supports.\n\t * Return an empty array if this asset type doesn't support files (e.g. bookmarks).\n\t */\n\tgetSupportedMimeTypes(): readonly string[] {\n\t\treturn []\n\t}\n\n\t/**\n\t * Check whether this asset type accepts a given MIME type.\n\t */\n\tacceptsMimeType(mimeType: string): boolean {\n\t\treturn this.getSupportedMimeTypes().includes(mimeType)\n\t}\n\n\t/**\n\t * Create an asset from a file. Return null if this asset type can't handle the file.\n\t */\n\tasync getAssetFromFile(_file: File, _assetId: TLAssetId): Promise<Asset | null> {\n\t\treturn null\n\t}\n}\n"],
5
+ "mappings": "AA6BO,MAAe,UAA2C;AAAA,EAahE,YAAmB,QAAgB;AAAhB;AAAA,EAAiB;AAAA;AAAA,EAXpC,OAAO,UAEN,SACI;AAEJ,WAAO,cAAc,KAAK;AAAA;AAAA,MAEzB,UAAU,EAAE,GAAG,KAAK,SAAS,GAAG,QAAQ;AAAA,IACzC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAU,CAAC;AAAA,EAEX,OAAO;AAAA,EACP,OAAO;AAAA;AAAA;AAAA;AAAA,EAKP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAWP,wBAA2C;AAC1C,WAAO,CAAC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,UAA2B;AAC1C,WAAO,KAAK,sBAAsB,EAAE,SAAS,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAiB,OAAa,UAA4C;AAC/E,WAAO;AAAA,EACR;AACD;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/FontManager/FontManager.ts"],
4
- "sourcesContent": ["import { computed, EMPTY_ARRAY, transact } from '@tldraw/state'\nimport { AtomMap } from '@tldraw/store'\nimport { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport {\n\tareArraysShallowEqual,\n\tcompact,\n\tFileHelpers,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n} from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\n\n/**\n * Represents the `src` property of a {@link TLFontFace}.\n * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src | `src`} for details of the properties here.\n * @public\n */\nexport interface TLFontFaceSource {\n\t/**\n\t * A URL from which to load the font. If the value here is a key in\n\t * {@link tldraw#TLEditorAssetUrls.fonts}, the value from there will be used instead.\n\t */\n\turl: string\n\tformat?: string\n\ttech?: string\n}\n\n/**\n * A font face that can be used in the editor. The properties of this are largely the same as the\n * ones in the\n * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face | css `@font-face` rule}.\n * @public\n */\nexport interface TLFontFace {\n\t/**\n\t * How this font can be referred to in CSS.\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-family | `font-family`}.\n\t */\n\treadonly family: string\n\t/**\n\t * The source of the font. This\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src | `src`}.\n\t */\n\treadonly src: TLFontFaceSource\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/ascent-override | `ascent-override`}.\n\t */\n\treadonly ascentOverride?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/descent-override | `descent-override`}.\n\t */\n\treadonly descentOverride?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-stretch | `font-stretch`}.\n\t */\n\treadonly stretch?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-style | `font-style`}.\n\t */\n\treadonly style?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight | `font-weight`}.\n\t */\n\treadonly weight?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-feature-settings | `font-feature-settings`}.\n\t */\n\treadonly featureSettings?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/line-gap-override | `line-gap-override`}.\n\t */\n\treadonly lineGapOverride?: string\n\t/**\n\t * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range | `unicode-range`}.\n\t */\n\treadonly unicodeRange?: string\n}\n\ninterface FontState {\n\treadonly state: 'loading' | 'ready' | 'error'\n\treadonly instance: FontFace\n\treadonly loadingPromise: Promise<void>\n}\n\n/** @public */\nexport class FontManager {\n\tconstructor(\n\t\tprivate readonly editor: Editor,\n\t\tprivate readonly assetUrls?: { [key: string]: string | undefined }\n\t) {\n\t\tthis.shapeFontFacesCache = editor.store.createComputedCache(\n\t\t\t'shape font faces',\n\t\t\t(shape: TLShape) => {\n\t\t\t\tconst shapeUtil = this.editor.getShapeUtil(shape)\n\t\t\t\treturn shapeUtil.getFontFaces(shape)\n\t\t\t},\n\t\t\t{\n\t\t\t\tareResultsEqual: areArraysShallowEqual,\n\t\t\t\tareRecordsEqual: (a, b) => a.props === b.props && a.meta === b.meta,\n\t\t\t}\n\t\t)\n\n\t\tthis.shapeFontLoadStateCache = editor.store.createCache<(FontState | null)[], TLShape>(\n\t\t\t(id: TLShapeId) => {\n\t\t\t\tconst fontFacesComputed = computed('font faces', () => this.getShapeFontFaces(id))\n\t\t\t\treturn computed(\n\t\t\t\t\t'font load state',\n\t\t\t\t\t() => {\n\t\t\t\t\t\tconst states = fontFacesComputed.get().map((face) => this.getFontState(face))\n\t\t\t\t\t\treturn states\n\t\t\t\t\t},\n\t\t\t\t\t{ isEqual: areArraysShallowEqual }\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\t}\n\n\tprivate readonly shapeFontFacesCache\n\tprivate readonly shapeFontLoadStateCache\n\n\tgetShapeFontFaces(shape: TLShape | TLShapeId): TLFontFace[] {\n\t\tconst shapeId = typeof shape === 'string' ? shape : shape.id\n\t\treturn this.shapeFontFacesCache.get(shapeId) ?? EMPTY_ARRAY\n\t}\n\n\ttrackFontsForShape(shape: TLShape | TLShapeId) {\n\t\tconst shapeId = typeof shape === 'string' ? shape : shape.id\n\t\tthis.shapeFontLoadStateCache.get(shapeId)\n\t}\n\n\tasync loadRequiredFontsForCurrentPage(limit = Infinity) {\n\t\tconst neededFonts = new Set<TLFontFace>()\n\t\tfor (const shapeId of this.editor.getCurrentPageShapeIds()) {\n\t\t\tfor (const font of this.getShapeFontFaces(this.editor.getShape(shapeId)!)) {\n\t\t\t\tneededFonts.add(font)\n\t\t\t}\n\t\t}\n\n\t\tif (neededFonts.size > limit) {\n\t\t\treturn\n\t\t}\n\n\t\tconst promises = Array.from(neededFonts, (font) => this.ensureFontIsLoaded(font))\n\t\tawait Promise.all(promises)\n\t}\n\n\tprivate readonly fontStates = new AtomMap<TLFontFace, FontState>('font states')\n\tprivate getFontState(font: TLFontFace): FontState | null {\n\t\treturn this.fontStates.get(font) ?? null\n\t}\n\n\tensureFontIsLoaded(font: TLFontFace): Promise<void> {\n\t\tconst existingState = this.getFontState(font)\n\t\tif (existingState) return existingState.loadingPromise\n\n\t\tconst instance = this.findOrCreateFontFace(font)\n\t\tconst state: FontState = {\n\t\t\tstate: 'loading',\n\t\t\tinstance,\n\t\t\tloadingPromise: instance\n\t\t\t\t.load()\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.editor.getContainerDocument().fonts.add(instance)\n\t\t\t\t\tthis.fontStates.update(font, (s) => ({ ...s, state: 'ready' }))\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(err)\n\t\t\t\t\tthis.fontStates.update(font, (s) => ({ ...s, state: 'error' }))\n\t\t\t\t}),\n\t\t}\n\n\t\tthis.fontStates.set(font, state)\n\t\treturn state.loadingPromise\n\t}\n\n\tprivate fontsToLoad = new Set<TLFontFace>()\n\trequestFonts(fonts: TLFontFace[]) {\n\t\tif (!this.fontsToLoad.size) {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.editor.isDisposed) return\n\t\t\t\tconst toLoad = this.fontsToLoad\n\t\t\t\tthis.fontsToLoad = new Set()\n\t\t\t\ttransact(() => {\n\t\t\t\t\tfor (const font of toLoad) {\n\t\t\t\t\t\tthis.ensureFontIsLoaded(font)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t\tfor (const font of fonts) {\n\t\t\tthis.fontsToLoad.add(font)\n\t\t}\n\t}\n\n\tprivate findOrCreateFontFace(font: TLFontFace) {\n\t\tconst fonts = this.editor.getContainerDocument().fonts\n\t\tfor (const existing of fonts) {\n\t\t\tif (\n\t\t\t\texisting.family === font.family &&\n\t\t\t\tobjectMapEntries(defaultFontFaceDescriptors).every(\n\t\t\t\t\t([key, defaultValue]) => existing[key] === (font[key] ?? defaultValue)\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn existing\n\t\t\t}\n\t\t}\n\n\t\tconst url = this.assetUrls?.[font.src.url] ?? font.src.url\n\t\tconst instance = new FontFace(font.family, `url(${JSON.stringify(url)})`, {\n\t\t\t...mapObjectMapValues(defaultFontFaceDescriptors, (key) => font[key]),\n\t\t\tdisplay: 'swap',\n\t\t})\n\n\t\tfonts.add(instance)\n\n\t\treturn instance\n\t}\n\n\tasync toEmbeddedCssDeclaration(font: TLFontFace) {\n\t\tconst url = this.assetUrls?.[font.src.url] ?? font.src.url\n\t\tconst dataUrl = await FileHelpers.urlToDataUrl(url)\n\n\t\tconst src = compact([\n\t\t\t`url(\"${dataUrl}\")`,\n\t\t\tfont.src.format ? `format(${font.src.format})` : null,\n\t\t\tfont.src.tech ? `tech(${font.src.tech})` : null,\n\t\t]).join(' ')\n\t\treturn compact([\n\t\t\t`@font-face {`,\n\t\t\t` font-family: \"${font.family}\";`,\n\t\t\tfont.ascentOverride ? ` ascent-override: ${font.ascentOverride};` : null,\n\t\t\tfont.descentOverride ? ` descent-override: ${font.descentOverride};` : null,\n\t\t\tfont.stretch ? ` font-stretch: ${font.stretch};` : null,\n\t\t\tfont.style ? ` font-style: ${font.style};` : null,\n\t\t\tfont.weight ? ` font-weight: ${font.weight};` : null,\n\t\t\tfont.featureSettings ? ` font-feature-settings: ${font.featureSettings};` : null,\n\t\t\tfont.lineGapOverride ? ` line-gap-override: ${font.lineGapOverride};` : null,\n\t\t\tfont.unicodeRange ? ` unicode-range: ${font.unicodeRange};` : null,\n\t\t\t` src: ${src};`,\n\t\t\t`}`,\n\t\t]).join('\\n')\n\t}\n}\n\n// From https://drafts.csswg.org/css-font-loading/#fontface-interface\nconst defaultFontFaceDescriptors = {\n\tstyle: 'normal',\n\tweight: 'normal',\n\tstretch: 'normal',\n\tunicodeRange: 'U+0-10FFFF',\n\tfeatureSettings: 'normal',\n\tascentOverride: 'normal',\n\tdescentOverride: 'normal',\n\tlineGapOverride: 'normal',\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { computed, EMPTY_ARRAY, transact } from '@tldraw/state'\nimport { AtomMap } from '@tldraw/store'\nimport { TLFontFace, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport {\n\tareArraysShallowEqual,\n\tcompact,\n\tFileHelpers,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n} from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\n\ninterface FontState {\n\treadonly state: 'loading' | 'ready' | 'error'\n\treadonly instance: FontFace\n\treadonly loadingPromise: Promise<void>\n}\n\n/** @public */\nexport class FontManager {\n\tconstructor(\n\t\tprivate readonly editor: Editor,\n\t\tprivate readonly assetUrls?: { [key: string]: string | undefined }\n\t) {\n\t\tthis.shapeFontFacesCache = editor.store.createComputedCache(\n\t\t\t'shape font faces',\n\t\t\t(shape: TLShape) => {\n\t\t\t\tconst shapeUtil = this.editor.getShapeUtil(shape)\n\t\t\t\treturn shapeUtil.getFontFaces(shape)\n\t\t\t},\n\t\t\t{\n\t\t\t\tareResultsEqual: areArraysShallowEqual,\n\t\t\t\tareRecordsEqual: (a, b) => a.props === b.props && a.meta === b.meta,\n\t\t\t}\n\t\t)\n\n\t\tthis.shapeFontLoadStateCache = editor.store.createCache<(FontState | null)[], TLShape>(\n\t\t\t(id: TLShapeId) => {\n\t\t\t\tconst fontFacesComputed = computed('font faces', () => this.getShapeFontFaces(id))\n\t\t\t\treturn computed(\n\t\t\t\t\t'font load state',\n\t\t\t\t\t() => {\n\t\t\t\t\t\tconst states = fontFacesComputed.get().map((face) => this.getFontState(face))\n\t\t\t\t\t\treturn states\n\t\t\t\t\t},\n\t\t\t\t\t{ isEqual: areArraysShallowEqual }\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\t}\n\n\tprivate readonly shapeFontFacesCache\n\tprivate readonly shapeFontLoadStateCache\n\n\tgetShapeFontFaces(shape: TLShape | TLShapeId): TLFontFace[] {\n\t\tconst shapeId = typeof shape === 'string' ? shape : shape.id\n\t\treturn this.shapeFontFacesCache.get(shapeId) ?? EMPTY_ARRAY\n\t}\n\n\ttrackFontsForShape(shape: TLShape | TLShapeId) {\n\t\tconst shapeId = typeof shape === 'string' ? shape : shape.id\n\t\tthis.shapeFontLoadStateCache.get(shapeId)\n\t}\n\n\tasync loadRequiredFontsForCurrentPage(limit = Infinity) {\n\t\tconst neededFonts = new Set<TLFontFace>()\n\t\tfor (const shapeId of this.editor.getCurrentPageShapeIds()) {\n\t\t\tfor (const font of this.getShapeFontFaces(this.editor.getShape(shapeId)!)) {\n\t\t\t\tneededFonts.add(font)\n\t\t\t}\n\t\t}\n\n\t\tif (neededFonts.size > limit) {\n\t\t\treturn\n\t\t}\n\n\t\tconst promises = Array.from(neededFonts, (font) => this.ensureFontIsLoaded(font))\n\t\tawait Promise.all(promises)\n\t}\n\n\tprivate readonly fontStates = new AtomMap<TLFontFace, FontState>('font states')\n\tprivate getFontState(font: TLFontFace): FontState | null {\n\t\treturn this.fontStates.get(font) ?? null\n\t}\n\n\tensureFontIsLoaded(font: TLFontFace): Promise<void> {\n\t\tconst existingState = this.getFontState(font)\n\t\tif (existingState) return existingState.loadingPromise\n\n\t\tconst instance = this.findOrCreateFontFace(font)\n\t\tconst state: FontState = {\n\t\t\tstate: 'loading',\n\t\t\tinstance,\n\t\t\tloadingPromise: instance\n\t\t\t\t.load()\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.editor.getContainerDocument().fonts.add(instance)\n\t\t\t\t\tthis.fontStates.update(font, (s) => ({ ...s, state: 'ready' }))\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tconsole.error(err)\n\t\t\t\t\tthis.fontStates.update(font, (s) => ({ ...s, state: 'error' }))\n\t\t\t\t}),\n\t\t}\n\n\t\tthis.fontStates.set(font, state)\n\t\treturn state.loadingPromise\n\t}\n\n\tprivate fontsToLoad = new Set<TLFontFace>()\n\trequestFonts(fonts: TLFontFace[]) {\n\t\tif (!this.fontsToLoad.size) {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (this.editor.isDisposed) return\n\t\t\t\tconst toLoad = this.fontsToLoad\n\t\t\t\tthis.fontsToLoad = new Set()\n\t\t\t\ttransact(() => {\n\t\t\t\t\tfor (const font of toLoad) {\n\t\t\t\t\t\tthis.ensureFontIsLoaded(font)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t\tfor (const font of fonts) {\n\t\t\tthis.fontsToLoad.add(font)\n\t\t}\n\t}\n\n\tprivate findOrCreateFontFace(font: TLFontFace) {\n\t\tconst fonts = this.editor.getContainerDocument().fonts\n\t\tfor (const existing of fonts) {\n\t\t\tif (\n\t\t\t\texisting.family === font.family &&\n\t\t\t\tobjectMapEntries(defaultFontFaceDescriptors).every(\n\t\t\t\t\t([key, defaultValue]) => existing[key] === (font[key] ?? defaultValue)\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn existing\n\t\t\t}\n\t\t}\n\n\t\tconst url = this.assetUrls?.[font.src.url] ?? font.src.url\n\t\tconst instance = new FontFace(font.family, `url(${JSON.stringify(url)})`, {\n\t\t\t...mapObjectMapValues(defaultFontFaceDescriptors, (key) => font[key]),\n\t\t\tdisplay: 'swap',\n\t\t})\n\n\t\tfonts.add(instance)\n\n\t\treturn instance\n\t}\n\n\tasync toEmbeddedCssDeclaration(font: TLFontFace) {\n\t\tconst url = this.assetUrls?.[font.src.url] ?? font.src.url\n\t\tconst dataUrl = await FileHelpers.urlToDataUrl(url)\n\n\t\tconst src = compact([\n\t\t\t`url(\"${dataUrl}\")`,\n\t\t\tfont.src.format ? `format(${font.src.format})` : null,\n\t\t\tfont.src.tech ? `tech(${font.src.tech})` : null,\n\t\t]).join(' ')\n\t\treturn compact([\n\t\t\t`@font-face {`,\n\t\t\t` font-family: \"${font.family}\";`,\n\t\t\tfont.ascentOverride ? ` ascent-override: ${font.ascentOverride};` : null,\n\t\t\tfont.descentOverride ? ` descent-override: ${font.descentOverride};` : null,\n\t\t\tfont.stretch ? ` font-stretch: ${font.stretch};` : null,\n\t\t\tfont.style ? ` font-style: ${font.style};` : null,\n\t\t\tfont.weight ? ` font-weight: ${font.weight};` : null,\n\t\t\tfont.featureSettings ? ` font-feature-settings: ${font.featureSettings};` : null,\n\t\t\tfont.lineGapOverride ? ` line-gap-override: ${font.lineGapOverride};` : null,\n\t\t\tfont.unicodeRange ? ` unicode-range: ${font.unicodeRange};` : null,\n\t\t\t` src: ${src};`,\n\t\t\t`}`,\n\t\t]).join('\\n')\n\t}\n}\n\n// From https://drafts.csswg.org/css-font-loading/#fontface-interface\nconst defaultFontFaceDescriptors = {\n\tstyle: 'normal',\n\tweight: 'normal',\n\tstretch: 'normal',\n\tunicodeRange: 'U+0-10FFFF',\n\tfeatureSettings: 'normal',\n\tascentOverride: 'normal',\n\tdescentOverride: 'normal',\n\tlineGapOverride: 'normal',\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,60 @@
1
+ function safeMark(name, detail) {
2
+ try {
3
+ performance.mark(name, detail ? { detail } : void 0);
4
+ } catch {
5
+ performance.mark(name);
6
+ }
7
+ }
8
+ class PerformanceApiAdapter {
9
+ cleanups = [];
10
+ constructor(perfManager) {
11
+ this.cleanups.push(
12
+ perfManager.on("interaction-start", (event) => {
13
+ safeMark(`tldraw:interaction:${event.name}:start`, { path: event.path });
14
+ })
15
+ );
16
+ this.cleanups.push(
17
+ perfManager.on("interaction-end", (event) => {
18
+ const startMark = `tldraw:interaction:${event.name}:start`;
19
+ const endMark = `tldraw:interaction:${event.name}:end`;
20
+ safeMark(endMark, {
21
+ path: event.path,
22
+ fps: event.fps,
23
+ frameCount: event.frameCount,
24
+ shapeCount: event.shapeCount
25
+ });
26
+ try {
27
+ performance.measure(`tldraw:interaction:${event.name}`, startMark, endMark);
28
+ } catch {
29
+ }
30
+ })
31
+ );
32
+ this.cleanups.push(
33
+ perfManager.on("camera-start", (event) => {
34
+ safeMark(`tldraw:camera:${event.type}:start`);
35
+ })
36
+ );
37
+ this.cleanups.push(
38
+ perfManager.on("camera-end", (event) => {
39
+ const startMark = `tldraw:camera:${event.type}:start`;
40
+ const endMark = `tldraw:camera:${event.type}:end`;
41
+ safeMark(endMark, { fps: event.fps, shapeCount: event.shapeCount });
42
+ try {
43
+ performance.measure(`tldraw:camera:${event.type}`, startMark, endMark);
44
+ } catch {
45
+ }
46
+ })
47
+ );
48
+ }
49
+ /** Remove all listeners and stop piping events. @public */
50
+ dispose() {
51
+ for (const cleanup of this.cleanups) {
52
+ cleanup();
53
+ }
54
+ this.cleanups.length = 0;
55
+ }
56
+ }
57
+ export {
58
+ PerformanceApiAdapter
59
+ };
60
+ //# sourceMappingURL=PerformanceApiAdapter.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.ts"],
4
+ "sourcesContent": ["import type { PerformanceManager } from './PerformanceManager'\n\n/** Wrap performance.mark with try/catch \u2014 `detail` option may throw in Safari/Firefox. */\nfunction safeMark(name: string, detail?: Record<string, unknown>) {\n\ttry {\n\t\tperformance.mark(name, detail ? { detail } : undefined)\n\t} catch {\n\t\tperformance.mark(name)\n\t}\n}\n\n/**\n * Optional adapter that pipes PerformanceManager events into browser\n * `performance.mark()` / `performance.measure()` for DevTools integration.\n *\n * Tree-shakeable \u2014 only included if imported.\n *\n * @example\n * ```ts\n * const adapter = new PerformanceApiAdapter(editor.performance)\n * // ... later\n * adapter.dispose()\n * ```\n *\n * @public\n */\nexport class PerformanceApiAdapter {\n\tprivate cleanups: (() => void)[] = []\n\n\tconstructor(perfManager: PerformanceManager) {\n\t\tthis.cleanups.push(\n\t\t\tperfManager.on('interaction-start', (event) => {\n\t\t\t\tsafeMark(`tldraw:interaction:${event.name}:start`, { path: event.path })\n\t\t\t})\n\t\t)\n\n\t\tthis.cleanups.push(\n\t\t\tperfManager.on('interaction-end', (event) => {\n\t\t\t\tconst startMark = `tldraw:interaction:${event.name}:start`\n\t\t\t\tconst endMark = `tldraw:interaction:${event.name}:end`\n\t\t\t\tsafeMark(endMark, {\n\t\t\t\t\tpath: event.path,\n\t\t\t\t\tfps: event.fps,\n\t\t\t\t\tframeCount: event.frameCount,\n\t\t\t\t\tshapeCount: event.shapeCount,\n\t\t\t\t})\n\t\t\t\ttry {\n\t\t\t\t\tperformance.measure(`tldraw:interaction:${event.name}`, startMark, endMark)\n\t\t\t\t} catch {\n\t\t\t\t\t// start mark may not exist if adapter attached mid-interaction\n\t\t\t\t}\n\t\t\t})\n\t\t)\n\n\t\tthis.cleanups.push(\n\t\t\tperfManager.on('camera-start', (event) => {\n\t\t\t\tsafeMark(`tldraw:camera:${event.type}:start`)\n\t\t\t})\n\t\t)\n\n\t\tthis.cleanups.push(\n\t\t\tperfManager.on('camera-end', (event) => {\n\t\t\t\tconst startMark = `tldraw:camera:${event.type}:start`\n\t\t\t\tconst endMark = `tldraw:camera:${event.type}:end`\n\t\t\t\tsafeMark(endMark, { fps: event.fps, shapeCount: event.shapeCount })\n\t\t\t\ttry {\n\t\t\t\t\tperformance.measure(`tldraw:camera:${event.type}`, startMark, endMark)\n\t\t\t\t} catch {\n\t\t\t\t\t// start mark may not exist\n\t\t\t\t}\n\t\t\t})\n\t\t)\n\t}\n\n\t/** Remove all listeners and stop piping events. @public */\n\tdispose() {\n\t\tfor (const cleanup of this.cleanups) {\n\t\t\tcleanup()\n\t\t}\n\t\tthis.cleanups.length = 0\n\t}\n}\n"],
5
+ "mappings": "AAGA,SAAS,SAAS,MAAc,QAAkC;AACjE,MAAI;AACH,gBAAY,KAAK,MAAM,SAAS,EAAE,OAAO,IAAI,MAAS;AAAA,EACvD,QAAQ;AACP,gBAAY,KAAK,IAAI;AAAA,EACtB;AACD;AAiBO,MAAM,sBAAsB;AAAA,EAC1B,WAA2B,CAAC;AAAA,EAEpC,YAAY,aAAiC;AAC5C,SAAK,SAAS;AAAA,MACb,YAAY,GAAG,qBAAqB,CAAC,UAAU;AAC9C,iBAAS,sBAAsB,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,KAAK,CAAC;AAAA,MACxE,CAAC;AAAA,IACF;AAEA,SAAK,SAAS;AAAA,MACb,YAAY,GAAG,mBAAmB,CAAC,UAAU;AAC5C,cAAM,YAAY,sBAAsB,MAAM,IAAI;AAClD,cAAM,UAAU,sBAAsB,MAAM,IAAI;AAChD,iBAAS,SAAS;AAAA,UACjB,MAAM,MAAM;AAAA,UACZ,KAAK,MAAM;AAAA,UACX,YAAY,MAAM;AAAA,UAClB,YAAY,MAAM;AAAA,QACnB,CAAC;AACD,YAAI;AACH,sBAAY,QAAQ,sBAAsB,MAAM,IAAI,IAAI,WAAW,OAAO;AAAA,QAC3E,QAAQ;AAAA,QAER;AAAA,MACD,CAAC;AAAA,IACF;AAEA,SAAK,SAAS;AAAA,MACb,YAAY,GAAG,gBAAgB,CAAC,UAAU;AACzC,iBAAS,iBAAiB,MAAM,IAAI,QAAQ;AAAA,MAC7C,CAAC;AAAA,IACF;AAEA,SAAK,SAAS;AAAA,MACb,YAAY,GAAG,cAAc,CAAC,UAAU;AACvC,cAAM,YAAY,iBAAiB,MAAM,IAAI;AAC7C,cAAM,UAAU,iBAAiB,MAAM,IAAI;AAC3C,iBAAS,SAAS,EAAE,KAAK,MAAM,KAAK,YAAY,MAAM,WAAW,CAAC;AAClE,YAAI;AACH,sBAAY,QAAQ,iBAAiB,MAAM,IAAI,IAAI,WAAW,OAAO;AAAA,QACtE,QAAQ;AAAA,QAER;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD;AAAA;AAAA,EAGA,UAAU;AACT,eAAW,WAAW,KAAK,UAAU;AACpC,cAAQ;AAAA,IACT;AACA,SAAK,SAAS,SAAS;AAAA,EACxB;AACD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,438 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { bind } from "@tldraw/utils";
12
+ import EventEmitter from "eventemitter3";
13
+ function percentile(sorted, p) {
14
+ const idx = Math.ceil(p * sorted.length) - 1;
15
+ return sorted[Math.max(0, idx)];
16
+ }
17
+ function computeFrameTimeStats(frameTimes) {
18
+ if (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 };
19
+ const sorted = [...frameTimes].sort((a, b) => a - b);
20
+ const sum = sorted.reduce((a, b) => a + b, 0);
21
+ return {
22
+ avg: sum / sorted.length,
23
+ median: percentile(sorted, 0.5),
24
+ p95: percentile(sorted, 0.95),
25
+ p99: percentile(sorted, 0.99),
26
+ min: sorted[0],
27
+ max: sorted[sorted.length - 1]
28
+ };
29
+ }
30
+ function toLoafEntry(entry) {
31
+ const e = entry;
32
+ if (typeof e.duration !== "number") return null;
33
+ return {
34
+ startTime: e.startTime,
35
+ duration: e.duration,
36
+ blockingDuration: e.blockingDuration ?? 0,
37
+ scripts: (e.scripts ?? []).map((s) => ({
38
+ sourceURL: s.sourceURL ?? "",
39
+ invoker: s.invoker ?? "",
40
+ duration: s.duration ?? 0
41
+ }))
42
+ };
43
+ }
44
+ class PerformanceManager {
45
+ /** @internal */
46
+ emitter = new EventEmitter();
47
+ editor;
48
+ // Active interaction tracking
49
+ activeInteraction = null;
50
+ // Active camera tracking
51
+ activeCamera = null;
52
+ // Lazy listener cleanup functions
53
+ frameCleanup = null;
54
+ shapeCreatedCleanup = null;
55
+ shapeEditedCleanup = null;
56
+ shapeDeletedCleanup = null;
57
+ // LoAF observer
58
+ loafObserver = null;
59
+ constructor(editor) {
60
+ this.editor = editor;
61
+ }
62
+ /**
63
+ * Subscribe to a performance event. Returns an unsubscribe function.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const unsub = editor.performance.on('interaction-end', (event) => {
68
+ * sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })
69
+ * })
70
+ * // later: unsub()
71
+ * ```
72
+ *
73
+ * @public
74
+ */
75
+ on(event, fn) {
76
+ this.emitter.on(event, fn);
77
+ this._maybeAttachLazyListeners(event);
78
+ return () => {
79
+ this.emitter.off(event, fn);
80
+ this._maybeDetachLazyListeners(event);
81
+ };
82
+ }
83
+ /**
84
+ * Subscribe to a performance event once. The listener is removed after the first invocation.
85
+ * Returns an unsubscribe function for early removal.
86
+ *
87
+ * @public
88
+ */
89
+ once(event, fn) {
90
+ const wrapped = (...args) => {
91
+ ;
92
+ fn(...args);
93
+ this._maybeDetachLazyListeners(event);
94
+ };
95
+ this.emitter.once(event, wrapped);
96
+ this._maybeAttachLazyListeners(event);
97
+ return () => {
98
+ this.emitter.off(event, wrapped);
99
+ this._maybeDetachLazyListeners(event);
100
+ };
101
+ }
102
+ /** @internal */
103
+ dispose() {
104
+ if (this.activeCamera?.timeout) clearTimeout(this.activeCamera.timeout);
105
+ this.activeInteraction = null;
106
+ this.activeCamera = null;
107
+ this.frameCleanup?.();
108
+ this.frameCleanup = null;
109
+ this.shapeCreatedCleanup?.();
110
+ this.shapeCreatedCleanup = null;
111
+ this.shapeEditedCleanup?.();
112
+ this.shapeEditedCleanup = null;
113
+ this.shapeDeletedCleanup?.();
114
+ this.shapeDeletedCleanup = null;
115
+ this._stopLoafObserver();
116
+ this.emitter.removeAllListeners();
117
+ }
118
+ // --- Internal notification methods ---
119
+ /** @internal */
120
+ _notifyInteractionStart(name, path) {
121
+ if (this.emitter.listenerCount("interaction-start") === 0 && this.emitter.listenerCount("interaction-end") === 0) {
122
+ return;
123
+ }
124
+ if (this.activeInteraction) {
125
+ console.warn(
126
+ `[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`
127
+ );
128
+ }
129
+ const selectedShapeTypes = {};
130
+ for (const shape of this.editor.getSelectedShapes()) {
131
+ selectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1;
132
+ }
133
+ this.activeInteraction = {
134
+ name,
135
+ path,
136
+ startTime: performance.now(),
137
+ frameTimes: [],
138
+ selectedShapeTypes,
139
+ loafEntries: []
140
+ };
141
+ const event = {
142
+ name,
143
+ path,
144
+ timestamp: performance.now()
145
+ };
146
+ this.emitter.emit("interaction-start", event);
147
+ }
148
+ /** @internal */
149
+ _notifyInteractionEnd() {
150
+ const interaction = this.activeInteraction;
151
+ if (!interaction) return;
152
+ this.activeInteraction = null;
153
+ if (this.emitter.listenerCount("interaction-end") === 0) return;
154
+ const duration = performance.now() - interaction.startTime;
155
+ const stats = computeFrameTimeStats(interaction.frameTimes);
156
+ const event = {
157
+ name: interaction.name,
158
+ path: interaction.path,
159
+ duration,
160
+ fps: interaction.frameTimes.length > 0 ? interaction.frameTimes.length / duration * 1e3 : 0,
161
+ frameCount: interaction.frameTimes.length,
162
+ avgFrameTime: stats.avg,
163
+ medianFrameTime: stats.median,
164
+ p95FrameTime: stats.p95,
165
+ p99FrameTime: stats.p99,
166
+ minFrameTime: stats.min,
167
+ maxFrameTime: stats.max,
168
+ frameTimes: interaction.frameTimes,
169
+ shapeCount: this.editor.getCurrentPageShapeIds().size,
170
+ selectedShapeTypes: interaction.selectedShapeTypes,
171
+ longAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : void 0,
172
+ zoomLevel: this.editor.getCamera().z,
173
+ timestamp: performance.now()
174
+ };
175
+ this.emitter.emit("interaction-end", event);
176
+ }
177
+ /** @internal */
178
+ _notifyCameraOperation(type) {
179
+ if (this.emitter.listenerCount("camera-start") === 0 && this.emitter.listenerCount("camera-end") === 0) {
180
+ return;
181
+ }
182
+ if (this.activeCamera) {
183
+ if (this.activeCamera.timeout) {
184
+ clearTimeout(this.activeCamera.timeout);
185
+ }
186
+ if (this.activeCamera.type !== type) {
187
+ this._endCameraSession();
188
+ this._startCameraSession(type);
189
+ } else {
190
+ this.activeCamera.timeout = this.editor.timers.setTimeout(
191
+ () => this._endCameraSession(),
192
+ 50
193
+ );
194
+ }
195
+ } else {
196
+ this._startCameraSession(type);
197
+ }
198
+ }
199
+ /** @internal */
200
+ _notifyUndoRedo(type, undoDepth, redoDepth) {
201
+ if (this.emitter.listenerCount(type) === 0) return;
202
+ const event = {
203
+ type,
204
+ undoDepth,
205
+ redoDepth
206
+ };
207
+ this.emitter.emit(type, event);
208
+ }
209
+ // --- Private helpers ---
210
+ _startCameraSession(type) {
211
+ this.activeCamera = {
212
+ type,
213
+ startTime: performance.now(),
214
+ frameTimes: [],
215
+ timeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),
216
+ loafEntries: []
217
+ };
218
+ if (this.emitter.listenerCount("camera-start") > 0) {
219
+ const event = {
220
+ type,
221
+ timestamp: performance.now()
222
+ };
223
+ this.emitter.emit("camera-start", event);
224
+ }
225
+ }
226
+ _endCameraSession() {
227
+ const camera = this.activeCamera;
228
+ if (!camera) return;
229
+ this.activeCamera = null;
230
+ if (camera.timeout) clearTimeout(camera.timeout);
231
+ if (this.emitter.listenerCount("camera-end") === 0) return;
232
+ const duration = performance.now() - camera.startTime;
233
+ const stats = computeFrameTimeStats(camera.frameTimes);
234
+ const viewportBounds = this.editor.getViewportScreenBounds();
235
+ const totalShapes = this.editor.getCurrentPageShapeIds().size;
236
+ const culledShapeCount = this.editor.getCulledShapes().size;
237
+ const event = {
238
+ type: camera.type,
239
+ duration,
240
+ fps: camera.frameTimes.length > 0 ? camera.frameTimes.length / duration * 1e3 : 0,
241
+ frameCount: camera.frameTimes.length,
242
+ avgFrameTime: stats.avg,
243
+ medianFrameTime: stats.median,
244
+ p95FrameTime: stats.p95,
245
+ p99FrameTime: stats.p99,
246
+ minFrameTime: stats.min,
247
+ maxFrameTime: stats.max,
248
+ frameTimes: camera.frameTimes,
249
+ shapeCount: totalShapes,
250
+ viewportWidth: viewportBounds.w,
251
+ viewportHeight: viewportBounds.h,
252
+ longAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : void 0,
253
+ visibleShapeCount: totalShapes - culledShapeCount,
254
+ culledShapeCount,
255
+ zoomLevel: this.editor.getCamera().z,
256
+ timestamp: performance.now()
257
+ };
258
+ this.emitter.emit("camera-end", event);
259
+ }
260
+ _onFrame(elapsed) {
261
+ if (this.activeInteraction) {
262
+ this.activeInteraction.frameTimes.push(elapsed);
263
+ }
264
+ if (this.activeCamera) {
265
+ this.activeCamera.frameTimes.push(elapsed);
266
+ }
267
+ if (this.emitter.listenerCount("frame") > 0) {
268
+ const totalShapes = this.editor.getCurrentPageShapeIds().size;
269
+ const culledShapes = this.editor.getCulledShapes();
270
+ const culledCount = culledShapes.size;
271
+ const event = {
272
+ elapsed,
273
+ shapeCount: totalShapes,
274
+ culledShapeCount: culledCount,
275
+ visibleShapeCount: totalShapes - culledCount
276
+ };
277
+ this.emitter.emit("frame", event);
278
+ }
279
+ }
280
+ _onShapesCreated(records) {
281
+ if (this.emitter.listenerCount("shapes-created") === 0) return;
282
+ const shapeTypes = {};
283
+ for (const record of records) {
284
+ if (record.typeName === "shape") {
285
+ shapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1;
286
+ }
287
+ }
288
+ const count = Object.values(shapeTypes).reduce((a, b) => a + b, 0);
289
+ if (count === 0) return;
290
+ const event = {
291
+ operation: "create",
292
+ count,
293
+ shapeTypes,
294
+ timestamp: performance.now()
295
+ };
296
+ this.emitter.emit("shapes-created", event);
297
+ }
298
+ _onShapesEdited(records) {
299
+ if (this.emitter.listenerCount("shapes-updated") === 0) return;
300
+ const shapeTypes = {};
301
+ for (const record of records) {
302
+ if (record.typeName === "shape") {
303
+ shapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1;
304
+ }
305
+ }
306
+ const count = Object.values(shapeTypes).reduce((a, b) => a + b, 0);
307
+ if (count === 0) return;
308
+ const event = {
309
+ operation: "update",
310
+ count,
311
+ shapeTypes,
312
+ timestamp: performance.now()
313
+ };
314
+ this.emitter.emit("shapes-updated", event);
315
+ }
316
+ _onShapesDeleted(ids) {
317
+ if (this.emitter.listenerCount("shapes-deleted") === 0) return;
318
+ const shapeTypes = {};
319
+ for (const id of ids) {
320
+ const shape = this.editor.getShape(id);
321
+ if (shape) {
322
+ shapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1;
323
+ }
324
+ }
325
+ const event = {
326
+ operation: "delete",
327
+ count: ids.length,
328
+ shapeTypes,
329
+ timestamp: performance.now()
330
+ };
331
+ this.emitter.emit("shapes-deleted", event);
332
+ }
333
+ // --- LoAF observer ---
334
+ _startLoafObserver() {
335
+ if (typeof PerformanceObserver === "undefined") return;
336
+ try {
337
+ const supported = PerformanceObserver.supportedEntryTypes;
338
+ if (!supported?.includes("long-animation-frame")) return;
339
+ } catch {
340
+ return;
341
+ }
342
+ this.loafObserver = new PerformanceObserver((list) => {
343
+ const isInteractionActive = this.activeInteraction !== null;
344
+ const isCameraActive = this.activeCamera !== null;
345
+ if (!isInteractionActive && !isCameraActive) return;
346
+ for (const entry of list.getEntries()) {
347
+ const loaf = toLoafEntry(entry);
348
+ if (!loaf) continue;
349
+ if (isInteractionActive) {
350
+ this.activeInteraction.loafEntries.push(loaf);
351
+ }
352
+ if (isCameraActive) {
353
+ this.activeCamera.loafEntries.push(loaf);
354
+ }
355
+ }
356
+ });
357
+ this.loafObserver.observe({ type: "long-animation-frame", buffered: false });
358
+ }
359
+ _stopLoafObserver() {
360
+ if (this.loafObserver) {
361
+ this.loafObserver.disconnect();
362
+ this.loafObserver = null;
363
+ }
364
+ }
365
+ // --- Lazy listener management ---
366
+ _needsFrameListener() {
367
+ return this.emitter.listenerCount("frame") > 0 || this.emitter.listenerCount("interaction-start") > 0 || this.emitter.listenerCount("interaction-end") > 0 || this.emitter.listenerCount("camera-start") > 0 || this.emitter.listenerCount("camera-end") > 0;
368
+ }
369
+ _needsLoafObserver() {
370
+ return this.emitter.listenerCount("interaction-end") > 0 || this.emitter.listenerCount("camera-end") > 0;
371
+ }
372
+ _maybeAttachLazyListeners(event) {
373
+ if (!this.frameCleanup && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
374
+ if (this._needsFrameListener()) {
375
+ this.editor.on("frame", this._onFrame);
376
+ this.frameCleanup = () => this.editor.off("frame", this._onFrame);
377
+ }
378
+ }
379
+ if (!this.loafObserver && (event === "interaction-end" || event === "camera-end")) {
380
+ if (this._needsLoafObserver()) {
381
+ this._startLoafObserver();
382
+ }
383
+ }
384
+ if (!this.shapeCreatedCleanup && event === "shapes-created") {
385
+ this.editor.on("created-shapes", this._onShapesCreated);
386
+ this.shapeCreatedCleanup = () => this.editor.off("created-shapes", this._onShapesCreated);
387
+ }
388
+ if (!this.shapeEditedCleanup && event === "shapes-updated") {
389
+ this.editor.on("edited-shapes", this._onShapesEdited);
390
+ this.shapeEditedCleanup = () => this.editor.off("edited-shapes", this._onShapesEdited);
391
+ }
392
+ if (!this.shapeDeletedCleanup && event === "shapes-deleted") {
393
+ this.editor.on("deleted-shapes", this._onShapesDeleted);
394
+ this.shapeDeletedCleanup = () => this.editor.off("deleted-shapes", this._onShapesDeleted);
395
+ }
396
+ }
397
+ _maybeDetachLazyListeners(event) {
398
+ if (this.frameCleanup && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
399
+ if (!this._needsFrameListener()) {
400
+ this.frameCleanup();
401
+ this.frameCleanup = null;
402
+ }
403
+ }
404
+ if (this.loafObserver && (event === "interaction-end" || event === "camera-end")) {
405
+ if (!this._needsLoafObserver()) {
406
+ this._stopLoafObserver();
407
+ }
408
+ }
409
+ if (this.shapeCreatedCleanup && event === "shapes-created" && this.emitter.listenerCount("shapes-created") === 0) {
410
+ this.shapeCreatedCleanup();
411
+ this.shapeCreatedCleanup = null;
412
+ }
413
+ if (this.shapeEditedCleanup && event === "shapes-updated" && this.emitter.listenerCount("shapes-updated") === 0) {
414
+ this.shapeEditedCleanup();
415
+ this.shapeEditedCleanup = null;
416
+ }
417
+ if (this.shapeDeletedCleanup && event === "shapes-deleted" && this.emitter.listenerCount("shapes-deleted") === 0) {
418
+ this.shapeDeletedCleanup();
419
+ this.shapeDeletedCleanup = null;
420
+ }
421
+ }
422
+ }
423
+ __decorateClass([
424
+ bind
425
+ ], PerformanceManager.prototype, "_onFrame", 1);
426
+ __decorateClass([
427
+ bind
428
+ ], PerformanceManager.prototype, "_onShapesCreated", 1);
429
+ __decorateClass([
430
+ bind
431
+ ], PerformanceManager.prototype, "_onShapesEdited", 1);
432
+ __decorateClass([
433
+ bind
434
+ ], PerformanceManager.prototype, "_onShapesDeleted", 1);
435
+ export {
436
+ PerformanceManager
437
+ };
438
+ //# sourceMappingURL=PerformanceManager.mjs.map