@tldraw/editor 3.14.0-canary.e7ebd007cb8c → 3.14.0-canary.e89be131fb69

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 (107) hide show
  1. package/dist-cjs/index.d.ts +155 -55
  2. package/dist-cjs/index.js +4 -3
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
  5. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
  6. package/dist-cjs/lib/editor/Editor.js +90 -30
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  9. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +1 -2
  10. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  11. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +3 -1
  12. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +73 -42
  14. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  15. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +0 -10
  16. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  17. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  18. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
  19. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +13 -6
  20. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
  21. package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
  22. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  23. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  24. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  25. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
  26. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  27. package/dist-cjs/lib/primitives/Box.js +0 -6
  28. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  29. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
  30. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  31. package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -6
  32. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  33. package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
  34. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
  35. package/dist-cjs/lib/utils/dom.js +1 -1
  36. package/dist-cjs/lib/utils/dom.js.map +2 -2
  37. package/dist-cjs/lib/utils/reparenting.js +232 -0
  38. package/dist-cjs/lib/utils/reparenting.js.map +7 -0
  39. package/dist-cjs/lib/utils/richText.js +7 -2
  40. package/dist-cjs/lib/utils/richText.js.map +2 -2
  41. package/dist-cjs/version.js +3 -3
  42. package/dist-cjs/version.js.map +1 -1
  43. package/dist-esm/index.d.mts +155 -55
  44. package/dist-esm/index.mjs +4 -3
  45. package/dist-esm/index.mjs.map +2 -2
  46. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  47. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  48. package/dist-esm/lib/editor/Editor.mjs +90 -30
  49. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  50. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  51. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +1 -2
  52. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  53. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +3 -1
  54. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  55. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +73 -42
  56. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  57. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
  58. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  59. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  60. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  61. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +13 -6
  62. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  63. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  64. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  65. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  66. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  67. package/dist-esm/lib/primitives/Box.mjs +0 -6
  68. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  69. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
  70. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  71. package/dist-esm/lib/primitives/geometry/Group2d.mjs +11 -6
  72. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  73. package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
  74. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
  75. package/dist-esm/lib/utils/dom.mjs +1 -1
  76. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  77. package/dist-esm/lib/utils/reparenting.mjs +216 -0
  78. package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
  79. package/dist-esm/lib/utils/richText.mjs +8 -3
  80. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  81. package/dist-esm/version.mjs +3 -3
  82. package/dist-esm/version.mjs.map +1 -1
  83. package/editor.css +446 -489
  84. package/package.json +8 -9
  85. package/src/index.ts +7 -1
  86. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  87. package/src/lib/editor/Editor.ts +107 -38
  88. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  89. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -2
  90. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +3 -1
  91. package/src/lib/editor/managers/TextManager/TextManager.test.ts +1 -5
  92. package/src/lib/editor/managers/TextManager/TextManager.ts +118 -86
  93. package/src/lib/editor/shapes/ShapeUtil.ts +47 -15
  94. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  95. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +25 -17
  96. package/src/lib/editor/tools/StateNode.ts +3 -3
  97. package/src/lib/editor/types/emit-types.ts +4 -0
  98. package/src/lib/editor/types/external-content.ts +11 -2
  99. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  100. package/src/lib/primitives/Box.ts +0 -8
  101. package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
  102. package/src/lib/primitives/geometry/Group2d.ts +11 -5
  103. package/src/lib/utils/areShapesContentEqual.ts +1 -2
  104. package/src/lib/utils/dom.ts +1 -1
  105. package/src/lib/utils/reparenting.ts +383 -0
  106. package/src/lib/utils/richText.ts +9 -3
  107. package/src/version.ts +3 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/editor/bindings/BindingUtil.ts"],
4
- "sourcesContent": ["import { RecordProps, TLPropsMigrations, TLShape, TLUnknownBinding } from '@tldraw/tlschema'\nimport { Editor } from '../Editor'\n\n/** @public */\nexport interface TLBindingUtilConstructor<\n\tT extends TLUnknownBinding,\n\tU extends BindingUtil<T> = BindingUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\t/** Validations for this binding's props. */\n\tprops?: RecordProps<T>\n\t/** Migrations for this binding's props. */\n\tmigrations?: TLPropsMigrations\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeCreate} and {@link BindingUtil.onAfterCreate},\n * describing a the creating a binding.\n *\n * @public\n */\nexport interface BindingOnCreateOptions<Binding extends TLUnknownBinding> {\n\t/** The binding being created. */\n\tbinding: Binding\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeChange} and {@link BindingUtil.onAfterChange},\n * describing the data associated with a binding being changed.\n *\n * @public\n */\nexport interface BindingOnChangeOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record before the change is made. */\n\tbindingBefore: Binding\n\t/** The binding record after the change is made. */\n\tbindingAfter: Binding\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeDelete} and {@link BindingUtil.onAfterDelete},\n * describing a binding being deleted.\n *\n * @public\n */\nexport interface BindingOnDeleteOptions<Binding extends TLUnknownBinding> {\n\t/** The binding being deleted. */\n\tbinding: Binding\n}\n\n/**\n * Options passed to {@link BindingUtil.onAfterChangeFromShape} and\n * {@link BindingUtil.onAfterChangeToShape}, describing a bound shape being changed.\n *\n * @public\n */\nexport interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record linking these two shapes. */\n\tbinding: Binding\n\t/** The shape record before the change is made. */\n\tshapeBefore: TLShape\n\t/** The shape record after the change is made. */\n\tshapeAfter: TLShape\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeIsolateFromShape} and\n * {@link BindingUtil.onBeforeIsolateToShape}, describing a shape that is about to be isolated from\n * the one that it's bound to.\n *\n * Isolation happens whenever two bound shapes are separated. For example\n * 1. One is deleted, but the other is not.\n * 1. One is copied, but the other is not.\n * 1. One is duplicated, but the other is not.\n *\n * In each of these cases, if the remaining shape depends on the binding for its rendering, it may\n * now be in an inconsistent state. For example, tldraw's arrow shape depends on the binding to know\n * where the end of the arrow is. If we removed the binding without doing anything else, the arrow\n * would suddenly be pointing to the wrong location. Instead, when the shape the arrow is pointing\n * to is deleted, or the arrow is copied/duplicated, we use an isolation callback. The callback\n * updates the arrow based on the binding that's about to be removed, so it doesn't end up pointing\n * to the wrong place.\n *\n * For this style of consistency update, use isolation callbacks. For actions specific to deletion\n * (like deleting a sticker when the shape it's bound to is removed), use the delete callbacks\n * ({@link BindingUtil.onBeforeDeleteFromShape} and {@link BindingUtil.onBeforeDeleteToShape})\n * instead.\n *\n * @public\n */\nexport interface BindingOnShapeIsolateOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record that refers to the shape in question. */\n\tbinding: Binding\n\t/**\n\t * The shape being removed. For deletion, this is the deleted shape. For copy/duplicate, this is\n\t * the shape that _isn't_ being copied/duplicated and is getting left behind.\n\t */\n\tremovedShape: TLShape\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeDeleteFromShape} and\n * {@link BindingUtil.onBeforeDeleteToShape}, describing a bound shape that is about to be deleted.\n *\n * See {@link BindingOnShapeIsolateOptions} for discussion on when to use the delete vs. the isolate\n * callbacks.\n *\n * @public\n */\nexport interface BindingOnShapeDeleteOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record that refers to the shape in question. */\n\tbinding: Binding\n\t/** The shape that is about to be deleted. */\n\tshape: TLShape\n}\n\n/** @public */\nexport abstract class BindingUtil<Binding extends TLUnknownBinding = TLUnknownBinding> {\n\tconstructor(public editor: Editor) {}\n\tstatic props?: RecordProps<TLUnknownBinding>\n\tstatic migrations?: TLPropsMigrations\n\n\t/**\n\t * The type of the binding util, which should match the binding's type.\n\t *\n\t * @public\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for a binding.\n\t *\n\t * @public\n\t */\n\tabstract getDefaultProps(): Partial<Binding['props']>\n\n\t/**\n\t * Called whenever a store operation involving this binding type has completed. This is useful\n\t * for working with networks of related bindings that may need to update together.\n\t *\n\t * @example\n\t * ```ts\n\t * class MyBindingUtil extends BindingUtil<MyBinding> {\n\t * changedBindingIds = new Set<TLBindingId>()\n\t *\n\t * onOperationComplete() {\n\t * doSomethingWithChangedBindings(this.changedBindingIds)\n\t * this.changedBindingIds.clear()\n\t * }\n\t *\n\t * onAfterChange({ bindingAfter }: BindingOnChangeOptions<MyBinding>) {\n\t * this.changedBindingIds.add(bindingAfter.id)\n\t * }\n\t * }\n\t * ```\n\t *\n\t * @public\n\t */\n\tonOperationComplete?(): void\n\n\t/**\n\t * Called when a binding is about to be created. See {@link BindingOnCreateOptions} for details.\n\t *\n\t * You can optionally return a new binding to replace the one being created - for example, to\n\t * set different initial props.\n\t *\n\t * @public\n\t */\n\tonBeforeCreate?(options: BindingOnCreateOptions<Binding>): Binding | void\n\n\t/**\n\t * Called after a binding has been created. See {@link BindingOnCreateOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterCreate?(options: BindingOnCreateOptions<Binding>): void\n\n\t/**\n\t * Called when a binding is about to be changed. See {@link BindingOnChangeOptions} for details.\n\t *\n\t * Note that this only fires when the binding record is changing, not when the shapes\n\t * associated change. Use {@link BindingUtil.onAfterChangeFromShape} and\n\t * {@link BindingUtil.onAfterChangeToShape} for that.\n\t *\n\t * You can optionally return a new binding to replace the one being changed - for example, to\n\t * enforce constraints on the binding's props.\n\t *\n\t * @public\n\t */\n\tonBeforeChange?(options: BindingOnChangeOptions<Binding>): Binding | void\n\n\t/**\n\t * Called after a binding has been changed. See {@link BindingOnChangeOptions} for details.\n\t *\n\t * Note that this only fires when the binding record is changing, not when the shapes\n\t * associated change. Use {@link BindingUtil.onAfterChangeFromShape} and\n\t * {@link BindingUtil.onAfterChangeToShape} for that.\n\t *\n\t * @public\n\t */\n\tonAfterChange?(options: BindingOnChangeOptions<Binding>): void\n\n\t/**\n\t * Called when a binding is about to be deleted. See {@link BindingOnDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonBeforeDelete?(options: BindingOnDeleteOptions<Binding>): void\n\n\t/**\n\t * Called after a binding has been deleted. See {@link BindingOnDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterDelete?(options: BindingOnDeleteOptions<Binding>): void\n\n\t/**\n\t * Called after the shape referenced in a binding's `fromId` is changed. Use this to propagate\n\t * any changes to the binding itself or the other shape as needed. See\n\t * {@link BindingOnShapeChangeOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterChangeFromShape?(options: BindingOnShapeChangeOptions<Binding>): void\n\n\t/**\n\t * Called after the shape referenced in a binding's `toId` is changed. Use this to propagate any\n\t * changes to the binding itself or the other shape as needed. See\n\t * {@link BindingOnShapeChangeOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterChangeToShape?(options: BindingOnShapeChangeOptions<Binding>): void\n\n\t/**\n\t * Called before the shape referenced in a binding's `fromId` is about to be deleted. Use this\n\t * with care - you may want to use {@link BindingUtil.onBeforeIsolateToShape} instead. See\n\t * {@link BindingOnShapeDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonBeforeDeleteFromShape?(options: BindingOnShapeDeleteOptions<Binding>): void\n\t/**\n\t * Called before the shape referenced in a binding's `toId` is about to be deleted. Use this\n\t * with care - you may want to use {@link BindingUtil.onBeforeIsolateFromShape} instead. See\n\t * {@link BindingOnShapeDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonBeforeDeleteToShape?(options: BindingOnShapeDeleteOptions<Binding>): void\n\n\t/**\n\t * Called before the shape referenced in a binding's `fromId` is about to be isolated from the\n\t * shape referenced in `toId`. See {@link BindingOnShapeIsolateOptions} for discussion on what\n\t * isolation means, and when/how to use this callback.\n\t */\n\tonBeforeIsolateFromShape?(options: BindingOnShapeIsolateOptions<Binding>): void\n\n\t/**\n\t * Called before the shape referenced in a binding's `toId` is about to be isolated from the\n\t * shape referenced in `fromId`. See {@link BindingOnShapeIsolateOptions} for discussion on what\n\t * isolation means, and when/how to use this callback.\n\t */\n\tonBeforeIsolateToShape?(options: BindingOnShapeIsolateOptions<Binding>): void\n}\n"],
5
- "mappings": "AAsHO,MAAe,YAAiE;AAAA,EACtF,YAAmB,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EACpC,OAAO;AAAA,EACP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,OAAO;AAyIR;",
4
+ "sourcesContent": ["import { RecordProps, TLPropsMigrations, TLShape, TLUnknownBinding } from '@tldraw/tlschema'\nimport { Editor } from '../Editor'\n\n/** @public */\nexport interface TLBindingUtilConstructor<\n\tT extends TLUnknownBinding,\n\tU extends BindingUtil<T> = BindingUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\t/** Validations for this binding's props. */\n\tprops?: RecordProps<T>\n\t/** Migrations for this binding's props. */\n\tmigrations?: TLPropsMigrations\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeCreate} and {@link BindingUtil.onAfterCreate},\n * describing a the creating a binding.\n *\n * @public\n */\nexport interface BindingOnCreateOptions<Binding extends TLUnknownBinding> {\n\t/** The binding being created. */\n\tbinding: Binding\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeChange} and {@link BindingUtil.onAfterChange},\n * describing the data associated with a binding being changed.\n *\n * @public\n */\nexport interface BindingOnChangeOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record before the change is made. */\n\tbindingBefore: Binding\n\t/** The binding record after the change is made. */\n\tbindingAfter: Binding\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeDelete} and {@link BindingUtil.onAfterDelete},\n * describing a binding being deleted.\n *\n * @public\n */\nexport interface BindingOnDeleteOptions<Binding extends TLUnknownBinding> {\n\t/** The binding being deleted. */\n\tbinding: Binding\n}\n\n/**\n * Options passed to {@link BindingUtil.onAfterChangeFromShape} and\n * {@link BindingUtil.onAfterChangeToShape}, describing a bound shape being changed.\n *\n * @public\n */\nexport interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record linking these two shapes. */\n\tbinding: Binding\n\t/** The shape record before the change is made. */\n\tshapeBefore: TLShape\n\t/** The shape record after the change is made. */\n\tshapeAfter: TLShape\n\t/**\n\t * Why did this shape change?\n\t * - 'self': the shape itself changed\n\t * - 'ancestry': the ancestry of the shape changed, but the shape itself may not have done\n\t */\n\treason: 'self' | 'ancestry'\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeIsolateFromShape} and\n * {@link BindingUtil.onBeforeIsolateToShape}, describing a shape that is about to be isolated from\n * the one that it's bound to.\n *\n * Isolation happens whenever two bound shapes are separated. For example\n * 1. One is deleted, but the other is not.\n * 1. One is copied, but the other is not.\n * 1. One is duplicated, but the other is not.\n *\n * In each of these cases, if the remaining shape depends on the binding for its rendering, it may\n * now be in an inconsistent state. For example, tldraw's arrow shape depends on the binding to know\n * where the end of the arrow is. If we removed the binding without doing anything else, the arrow\n * would suddenly be pointing to the wrong location. Instead, when the shape the arrow is pointing\n * to is deleted, or the arrow is copied/duplicated, we use an isolation callback. The callback\n * updates the arrow based on the binding that's about to be removed, so it doesn't end up pointing\n * to the wrong place.\n *\n * For this style of consistency update, use isolation callbacks. For actions specific to deletion\n * (like deleting a sticker when the shape it's bound to is removed), use the delete callbacks\n * ({@link BindingUtil.onBeforeDeleteFromShape} and {@link BindingUtil.onBeforeDeleteToShape})\n * instead.\n *\n * @public\n */\nexport interface BindingOnShapeIsolateOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record that refers to the shape in question. */\n\tbinding: Binding\n\t/**\n\t * The shape being removed. For deletion, this is the deleted shape. For copy/duplicate, this is\n\t * the shape that _isn't_ being copied/duplicated and is getting left behind.\n\t */\n\tremovedShape: TLShape\n}\n\n/**\n * Options passed to {@link BindingUtil.onBeforeDeleteFromShape} and\n * {@link BindingUtil.onBeforeDeleteToShape}, describing a bound shape that is about to be deleted.\n *\n * See {@link BindingOnShapeIsolateOptions} for discussion on when to use the delete vs. the isolate\n * callbacks.\n *\n * @public\n */\nexport interface BindingOnShapeDeleteOptions<Binding extends TLUnknownBinding> {\n\t/** The binding record that refers to the shape in question. */\n\tbinding: Binding\n\t/** The shape that is about to be deleted. */\n\tshape: TLShape\n}\n\n/** @public */\nexport abstract class BindingUtil<Binding extends TLUnknownBinding = TLUnknownBinding> {\n\tconstructor(public editor: Editor) {}\n\tstatic props?: RecordProps<TLUnknownBinding>\n\tstatic migrations?: TLPropsMigrations\n\n\t/**\n\t * The type of the binding util, which should match the binding's type.\n\t *\n\t * @public\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for a binding.\n\t *\n\t * @public\n\t */\n\tabstract getDefaultProps(): Partial<Binding['props']>\n\n\t/**\n\t * Called whenever a store operation involving this binding type has completed. This is useful\n\t * for working with networks of related bindings that may need to update together.\n\t *\n\t * @example\n\t * ```ts\n\t * class MyBindingUtil extends BindingUtil<MyBinding> {\n\t * changedBindingIds = new Set<TLBindingId>()\n\t *\n\t * onOperationComplete() {\n\t * doSomethingWithChangedBindings(this.changedBindingIds)\n\t * this.changedBindingIds.clear()\n\t * }\n\t *\n\t * onAfterChange({ bindingAfter }: BindingOnChangeOptions<MyBinding>) {\n\t * this.changedBindingIds.add(bindingAfter.id)\n\t * }\n\t * }\n\t * ```\n\t *\n\t * @public\n\t */\n\tonOperationComplete?(): void\n\n\t/**\n\t * Called when a binding is about to be created. See {@link BindingOnCreateOptions} for details.\n\t *\n\t * You can optionally return a new binding to replace the one being created - for example, to\n\t * set different initial props.\n\t *\n\t * @public\n\t */\n\tonBeforeCreate?(options: BindingOnCreateOptions<Binding>): Binding | void\n\n\t/**\n\t * Called after a binding has been created. See {@link BindingOnCreateOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterCreate?(options: BindingOnCreateOptions<Binding>): void\n\n\t/**\n\t * Called when a binding is about to be changed. See {@link BindingOnChangeOptions} for details.\n\t *\n\t * Note that this only fires when the binding record is changing, not when the shapes\n\t * associated change. Use {@link BindingUtil.onAfterChangeFromShape} and\n\t * {@link BindingUtil.onAfterChangeToShape} for that.\n\t *\n\t * You can optionally return a new binding to replace the one being changed - for example, to\n\t * enforce constraints on the binding's props.\n\t *\n\t * @public\n\t */\n\tonBeforeChange?(options: BindingOnChangeOptions<Binding>): Binding | void\n\n\t/**\n\t * Called after a binding has been changed. See {@link BindingOnChangeOptions} for details.\n\t *\n\t * Note that this only fires when the binding record is changing, not when the shapes\n\t * associated change. Use {@link BindingUtil.onAfterChangeFromShape} and\n\t * {@link BindingUtil.onAfterChangeToShape} for that.\n\t *\n\t * @public\n\t */\n\tonAfterChange?(options: BindingOnChangeOptions<Binding>): void\n\n\t/**\n\t * Called when a binding is about to be deleted. See {@link BindingOnDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonBeforeDelete?(options: BindingOnDeleteOptions<Binding>): void\n\n\t/**\n\t * Called after a binding has been deleted. See {@link BindingOnDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterDelete?(options: BindingOnDeleteOptions<Binding>): void\n\n\t/**\n\t * Called after the shape referenced in a binding's `fromId` is changed. Use this to propagate\n\t * any changes to the binding itself or the other shape as needed. See\n\t * {@link BindingOnShapeChangeOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterChangeFromShape?(options: BindingOnShapeChangeOptions<Binding>): void\n\n\t/**\n\t * Called after the shape referenced in a binding's `toId` is changed. Use this to propagate any\n\t * changes to the binding itself or the other shape as needed. See\n\t * {@link BindingOnShapeChangeOptions} for details.\n\t *\n\t * @public\n\t */\n\tonAfterChangeToShape?(options: BindingOnShapeChangeOptions<Binding>): void\n\n\t/**\n\t * Called before the shape referenced in a binding's `fromId` is about to be deleted. Use this\n\t * with care - you may want to use {@link BindingUtil.onBeforeIsolateToShape} instead. See\n\t * {@link BindingOnShapeDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonBeforeDeleteFromShape?(options: BindingOnShapeDeleteOptions<Binding>): void\n\t/**\n\t * Called before the shape referenced in a binding's `toId` is about to be deleted. Use this\n\t * with care - you may want to use {@link BindingUtil.onBeforeIsolateFromShape} instead. See\n\t * {@link BindingOnShapeDeleteOptions} for details.\n\t *\n\t * @public\n\t */\n\tonBeforeDeleteToShape?(options: BindingOnShapeDeleteOptions<Binding>): void\n\n\t/**\n\t * Called before the shape referenced in a binding's `fromId` is about to be isolated from the\n\t * shape referenced in `toId`. See {@link BindingOnShapeIsolateOptions} for discussion on what\n\t * isolation means, and when/how to use this callback.\n\t */\n\tonBeforeIsolateFromShape?(options: BindingOnShapeIsolateOptions<Binding>): void\n\n\t/**\n\t * Called before the shape referenced in a binding's `toId` is about to be isolated from the\n\t * shape referenced in `fromId`. See {@link BindingOnShapeIsolateOptions} for discussion on what\n\t * isolation means, and when/how to use this callback.\n\t */\n\tonBeforeIsolateToShape?(options: BindingOnShapeIsolateOptions<Binding>): void\n}\n"],
5
+ "mappings": "AA4HO,MAAe,YAAiE;AAAA,EACtF,YAAmB,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EACpC,OAAO;AAAA,EACP,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOP,OAAO;AAyIR;",
6
6
  "names": []
7
7
  }
@@ -19,8 +19,7 @@ class FontManager {
19
19
  },
20
20
  {
21
21
  areResultsEqual: areArraysShallowEqual,
22
- // @ts-expect-error
23
- areRecordsEqual: (a, b) => a.props.richText === b.props.richText
22
+ areRecordsEqual: (a, b) => a.props === b.props && a.meta === b.meta
24
23
  }
25
24
  );
26
25
  this.shapeFontLoadStateCache = editor.store.createCache(
@@ -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 { 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\t// @ts-expect-error\n\t\t\t\tareRecordsEqual: (a, b) => a.props.richText === b.props.richText,\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\tdocument.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\tfor (const existing of document.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\tdocument.fonts.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 { TLShape, TLShapeId } from '@tldraw/tlschema'\nimport {\n\tareArraysShallowEqual,\n\tcompact,\n\tFileHelpers,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n} from '@tldraw/utils'\nimport { 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\tdocument.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\tfor (const existing of document.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\tdocument.fonts.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
  }
@@ -189,7 +189,9 @@ class HistoryManager {
189
189
  return this;
190
190
  }
191
191
  bailToMark(id) {
192
- this._undo({ pushToRedoStack: false, toMark: id });
192
+ if (id) {
193
+ this._undo({ pushToRedoStack: false, toMark: id });
194
+ }
193
195
  return this;
194
196
  }
195
197
  squashToMark(id) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/HistoryManager/HistoryManager.ts"],
4
- "sourcesContent": ["import { atom, transact } from '@tldraw/state'\nimport {\n\tRecordsDiff,\n\tStore,\n\tUnknownRecord,\n\tcreateEmptyRecordsDiff,\n\tisRecordsDiffEmpty,\n\treverseRecordsDiff,\n\tsquashRecordDiffsMutable,\n} from '@tldraw/store'\nimport { exhaustiveSwitchError, noop } from '@tldraw/utils'\nimport { TLHistoryBatchOptions, TLHistoryEntry } from '../../types/history-types'\n\nenum HistoryRecorderState {\n\tRecording = 'recording',\n\tRecordingPreserveRedoStack = 'recordingPreserveRedoStack',\n\tPaused = 'paused',\n}\n\n/** @public */\nexport class HistoryManager<R extends UnknownRecord> {\n\tprivate readonly store: Store<R>\n\n\treadonly dispose: () => void\n\n\tprivate state: HistoryRecorderState = HistoryRecorderState.Recording\n\tprivate readonly pendingDiff = new PendingDiff<R>()\n\tprivate stacks = atom(\n\t\t'HistoryManager.stacks',\n\t\t{\n\t\t\tundos: stack<TLHistoryEntry<R>>(),\n\t\t\tredos: stack<TLHistoryEntry<R>>(),\n\t\t},\n\t\t{\n\t\t\tisEqual: (a, b) => a.undos === b.undos && a.redos === b.redos,\n\t\t}\n\t)\n\n\tprivate readonly annotateError: (error: unknown) => void\n\n\tconstructor(opts: { store: Store<R>; annotateError?(error: unknown): void }) {\n\t\tthis.store = opts.store\n\t\tthis.annotateError = opts.annotateError ?? noop\n\t\tthis.dispose = this.store.addHistoryInterceptor((entry, source) => {\n\t\t\tif (source !== 'user') return\n\n\t\t\tswitch (this.state) {\n\t\t\t\tcase HistoryRecorderState.Recording:\n\t\t\t\t\tthis.pendingDiff.apply(entry.changes)\n\t\t\t\t\tthis.stacks.update(({ undos }) => ({ undos, redos: stack() }))\n\t\t\t\t\tbreak\n\t\t\t\tcase HistoryRecorderState.RecordingPreserveRedoStack:\n\t\t\t\t\tthis.pendingDiff.apply(entry.changes)\n\t\t\t\t\tbreak\n\t\t\t\tcase HistoryRecorderState.Paused:\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\texhaustiveSwitchError(this.state)\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate flushPendingDiff() {\n\t\tif (this.pendingDiff.isEmpty()) return\n\n\t\tconst diff = this.pendingDiff.clear()\n\t\tthis.stacks.update(({ undos, redos }) => ({\n\t\t\tundos: undos.push({ type: 'diff', diff }),\n\t\t\tredos,\n\t\t}))\n\t}\n\n\tgetNumUndos() {\n\t\treturn this.stacks.get().undos.length + (this.pendingDiff.isEmpty() ? 0 : 1)\n\t}\n\n\tgetNumRedos() {\n\t\treturn this.stacks.get().redos.length\n\t}\n\n\t/** @internal */\n\t_isInBatch = false\n\n\tbatch(fn: () => void, opts?: TLHistoryBatchOptions) {\n\t\tconst previousState = this.state\n\n\t\t// we move to the new state only if we haven't explicitly paused\n\t\tif (previousState !== HistoryRecorderState.Paused && opts?.history) {\n\t\t\tthis.state = modeToState[opts.history]\n\t\t}\n\n\t\ttry {\n\t\t\tif (this._isInBatch) {\n\t\t\t\ttransact(fn)\n\t\t\t\treturn this\n\t\t\t}\n\n\t\t\tthis._isInBatch = true\n\t\t\ttry {\n\t\t\t\ttransact(fn)\n\t\t\t} catch (error) {\n\t\t\t\tthis.annotateError(error)\n\t\t\t\tthrow error\n\t\t\t} finally {\n\t\t\t\tthis._isInBatch = false\n\t\t\t}\n\n\t\t\treturn this\n\t\t} finally {\n\t\t\tthis.state = previousState\n\t\t}\n\t}\n\n\t// History\n\t_undo({ pushToRedoStack, toMark = undefined }: { pushToRedoStack: boolean; toMark?: string }) {\n\t\tconst previousState = this.state\n\t\tthis.state = HistoryRecorderState.Paused\n\t\ttry {\n\t\t\tlet { undos, redos } = this.stacks.get()\n\n\t\t\t// start by collecting the pending diff (everything since the last mark).\n\t\t\t// we'll accumulate the diff to undo in this variable so we can apply it atomically.\n\t\t\tconst pendingDiff = this.pendingDiff.clear()\n\t\t\tconst isPendingDiffEmpty = isRecordsDiffEmpty(pendingDiff)\n\t\t\tconst diffToUndo = reverseRecordsDiff(pendingDiff)\n\n\t\t\tif (pushToRedoStack && !isPendingDiffEmpty) {\n\t\t\t\tredos = redos.push({ type: 'diff', diff: pendingDiff })\n\t\t\t}\n\n\t\t\tlet didFindMark = false\n\t\t\tif (isPendingDiffEmpty) {\n\t\t\t\t// if nothing has happened since the last mark, pop any intermediate marks off the stack\n\t\t\t\twhile (undos.head?.type === 'stop') {\n\t\t\t\t\tconst mark = undos.head\n\t\t\t\t\tundos = undos.tail\n\t\t\t\t\tif (pushToRedoStack) {\n\t\t\t\t\t\tredos = redos.push(mark)\n\t\t\t\t\t}\n\t\t\t\t\tif (mark.id === toMark) {\n\t\t\t\t\t\tdidFindMark = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!didFindMark) {\n\t\t\t\tloop: while (undos.head) {\n\t\t\t\t\tconst undo = undos.head\n\t\t\t\t\tundos = undos.tail\n\n\t\t\t\t\tif (pushToRedoStack) {\n\t\t\t\t\t\tredos = redos.push(undo)\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch (undo.type) {\n\t\t\t\t\t\tcase 'diff':\n\t\t\t\t\t\t\tsquashRecordDiffsMutable(diffToUndo, [reverseRecordsDiff(undo.diff)])\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'stop':\n\t\t\t\t\t\t\tif (!toMark) break loop\n\t\t\t\t\t\t\tif (undo.id === toMark) {\n\t\t\t\t\t\t\t\tdidFindMark = true\n\t\t\t\t\t\t\t\tbreak loop\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\texhaustiveSwitchError(undo)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!didFindMark && toMark) {\n\t\t\t\t// whoops, we didn't find the mark we were looking for\n\t\t\t\t// don't do anything\n\t\t\t\treturn this\n\t\t\t}\n\n\t\t\tthis.store.applyDiff(diffToUndo, { ignoreEphemeralKeys: true })\n\t\t\tthis.store.ensureStoreIsUsable()\n\t\t\tthis.stacks.set({ undos, redos })\n\t\t} finally {\n\t\t\tthis.state = previousState\n\t\t}\n\n\t\treturn this\n\t}\n\n\tundo() {\n\t\tthis._undo({ pushToRedoStack: true })\n\n\t\treturn this\n\t}\n\n\tredo() {\n\t\tconst previousState = this.state\n\t\tthis.state = HistoryRecorderState.Paused\n\t\ttry {\n\t\t\tthis.flushPendingDiff()\n\n\t\t\tlet { undos, redos } = this.stacks.get()\n\t\t\tif (redos.length === 0) {\n\t\t\t\treturn this\n\t\t\t}\n\n\t\t\t// ignore any intermediate marks - this should take us to the first `diff` entry\n\t\t\twhile (redos.head?.type === 'stop') {\n\t\t\t\tundos = undos.push(redos.head)\n\t\t\t\tredos = redos.tail\n\t\t\t}\n\n\t\t\t// accumulate diffs to be redone so they can be applied atomically\n\t\t\tconst diffToRedo = createEmptyRecordsDiff<R>()\n\n\t\t\twhile (redos.head) {\n\t\t\t\tconst redo = redos.head\n\t\t\t\tundos = undos.push(redo)\n\t\t\t\tredos = redos.tail\n\n\t\t\t\tif (redo.type === 'diff') {\n\t\t\t\t\tsquashRecordDiffsMutable(diffToRedo, [redo.diff])\n\t\t\t\t} else {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.store.applyDiff(diffToRedo, { ignoreEphemeralKeys: true })\n\t\t\tthis.store.ensureStoreIsUsable()\n\t\t\tthis.stacks.set({ undos, redos })\n\t\t} finally {\n\t\t\tthis.state = previousState\n\t\t}\n\n\t\treturn this\n\t}\n\n\tbail() {\n\t\tthis._undo({ pushToRedoStack: false })\n\n\t\treturn this\n\t}\n\n\tbailToMark(id: string) {\n\t\tthis._undo({ pushToRedoStack: false, toMark: id })\n\n\t\treturn this\n\t}\n\n\tsquashToMark(id: string) {\n\t\t// remove marks between head and the mark\n\n\t\tlet top = this.stacks.get().undos\n\t\tconst popped: Array<RecordsDiff<R>> = []\n\n\t\twhile (top.head && !(top.head.type === 'stop' && top.head.id === id)) {\n\t\t\tif (top.head.type === 'diff') {\n\t\t\t\tpopped.push(top.head.diff)\n\t\t\t}\n\t\t\ttop = top.tail\n\t\t}\n\n\t\tif (!top.head || top.head?.id !== id) {\n\t\t\tconsole.error('Could not find mark to squash to: ', id)\n\t\t\treturn this\n\t\t}\n\t\tif (popped.length === 0) {\n\t\t\treturn this\n\t\t}\n\n\t\tconst diff = createEmptyRecordsDiff<R>()\n\t\tsquashRecordDiffsMutable(diff, popped.reverse())\n\n\t\tthis.stacks.update(({ redos }) => ({\n\t\t\tundos: top.push({\n\t\t\t\ttype: 'diff',\n\t\t\t\tdiff,\n\t\t\t}),\n\t\t\tredos,\n\t\t}))\n\n\t\treturn this\n\t}\n\n\t/** @internal */\n\t_mark(id: string) {\n\t\ttransact(() => {\n\t\t\tthis.flushPendingDiff()\n\t\t\tthis.stacks.update(({ undos, redos }) => ({ undos: undos.push({ type: 'stop', id }), redos }))\n\t\t})\n\t}\n\n\tclear() {\n\t\tthis.stacks.set({ undos: stack(), redos: stack() })\n\t\tthis.pendingDiff.clear()\n\t}\n\n\t/** @internal */\n\tgetMarkIdMatching(idSubstring: string) {\n\t\tlet top = this.stacks.get().undos\n\t\twhile (top.head) {\n\t\t\tif (top.head.type === 'stop' && top.head.id.includes(idSubstring)) {\n\t\t\t\treturn top.head.id\n\t\t\t}\n\t\t\ttop = top.tail\n\t\t}\n\t\treturn null\n\t}\n\n\t/** @internal */\n\tdebug() {\n\t\tconst { undos, redos } = this.stacks.get()\n\t\treturn {\n\t\t\tundos: undos.toArray(),\n\t\t\tredos: redos.toArray(),\n\t\t\tpendingDiff: this.pendingDiff.debug(),\n\t\t\tstate: this.state as string,\n\t\t}\n\t}\n}\n\nconst modeToState = {\n\trecord: HistoryRecorderState.Recording,\n\t'record-preserveRedoStack': HistoryRecorderState.RecordingPreserveRedoStack,\n\tignore: HistoryRecorderState.Paused,\n} as const\n\nclass PendingDiff<R extends UnknownRecord> {\n\tprivate diff = createEmptyRecordsDiff<R>()\n\tprivate isEmptyAtom = atom('PendingDiff.isEmpty', true)\n\n\tclear() {\n\t\tconst diff = this.diff\n\t\tthis.diff = createEmptyRecordsDiff<R>()\n\t\tthis.isEmptyAtom.set(true)\n\t\treturn diff\n\t}\n\n\tisEmpty() {\n\t\treturn this.isEmptyAtom.get()\n\t}\n\n\tapply(diff: RecordsDiff<R>) {\n\t\tsquashRecordDiffsMutable(this.diff, [diff])\n\t\tthis.isEmptyAtom.set(isRecordsDiffEmpty(this.diff))\n\t}\n\n\tdebug() {\n\t\treturn { diff: this.diff, isEmpty: this.isEmpty() }\n\t}\n}\n\nimport { EMPTY_ARRAY } from '@tldraw/state'\n\nexport type Stack<T> = StackItem<T> | EmptyStackItem<T>\n\nexport function stack<T>(items?: Array<T>): Stack<T> {\n\tif (items) {\n\t\tlet result = EMPTY_STACK_ITEM as Stack<T>\n\t\twhile (items.length) {\n\t\t\tresult = result.push(items.pop()!)\n\t\t}\n\t\treturn result\n\t}\n\treturn EMPTY_STACK_ITEM as any\n}\n\nclass EmptyStackItem<T> implements Iterable<T> {\n\treadonly length = 0\n\treadonly head = null\n\treadonly tail: Stack<T> = this\n\n\tpush(head: T): Stack<T> {\n\t\treturn new StackItem<T>(head, this)\n\t}\n\n\ttoArray() {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\t[Symbol.iterator]() {\n\t\treturn {\n\t\t\tnext() {\n\t\t\t\treturn { value: undefined, done: true as const }\n\t\t\t},\n\t\t}\n\t}\n}\n\nconst EMPTY_STACK_ITEM = new EmptyStackItem()\n\nclass StackItem<T> implements Iterable<T> {\n\tlength: number\n\tconstructor(\n\t\tpublic readonly head: T,\n\t\tpublic readonly tail: Stack<T>\n\t) {\n\t\tthis.length = tail.length + 1\n\t}\n\n\tpush(head: T): Stack<T> {\n\t\treturn new StackItem(head, this)\n\t}\n\n\ttoArray() {\n\t\treturn Array.from(this)\n\t}\n\n\t[Symbol.iterator]() {\n\t\tlet stack = this as Stack<T>\n\t\treturn {\n\t\t\tnext() {\n\t\t\t\tif (stack.length) {\n\t\t\t\t\tconst value = stack.head!\n\t\t\t\t\tstack = stack.tail\n\t\t\t\t\treturn { value, done: false as const }\n\t\t\t\t} else {\n\t\t\t\t\treturn { value: undefined, done: true as const }\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { atom, transact } from '@tldraw/state'\nimport {\n\tRecordsDiff,\n\tStore,\n\tUnknownRecord,\n\tcreateEmptyRecordsDiff,\n\tisRecordsDiffEmpty,\n\treverseRecordsDiff,\n\tsquashRecordDiffsMutable,\n} from '@tldraw/store'\nimport { exhaustiveSwitchError, noop } from '@tldraw/utils'\nimport { TLHistoryBatchOptions, TLHistoryEntry } from '../../types/history-types'\n\nenum HistoryRecorderState {\n\tRecording = 'recording',\n\tRecordingPreserveRedoStack = 'recordingPreserveRedoStack',\n\tPaused = 'paused',\n}\n\n/** @public */\nexport class HistoryManager<R extends UnknownRecord> {\n\tprivate readonly store: Store<R>\n\n\treadonly dispose: () => void\n\n\tprivate state: HistoryRecorderState = HistoryRecorderState.Recording\n\tprivate readonly pendingDiff = new PendingDiff<R>()\n\tprivate stacks = atom(\n\t\t'HistoryManager.stacks',\n\t\t{\n\t\t\tundos: stack<TLHistoryEntry<R>>(),\n\t\t\tredos: stack<TLHistoryEntry<R>>(),\n\t\t},\n\t\t{\n\t\t\tisEqual: (a, b) => a.undos === b.undos && a.redos === b.redos,\n\t\t}\n\t)\n\n\tprivate readonly annotateError: (error: unknown) => void\n\n\tconstructor(opts: { store: Store<R>; annotateError?(error: unknown): void }) {\n\t\tthis.store = opts.store\n\t\tthis.annotateError = opts.annotateError ?? noop\n\t\tthis.dispose = this.store.addHistoryInterceptor((entry, source) => {\n\t\t\tif (source !== 'user') return\n\n\t\t\tswitch (this.state) {\n\t\t\t\tcase HistoryRecorderState.Recording:\n\t\t\t\t\tthis.pendingDiff.apply(entry.changes)\n\t\t\t\t\tthis.stacks.update(({ undos }) => ({ undos, redos: stack() }))\n\t\t\t\t\tbreak\n\t\t\t\tcase HistoryRecorderState.RecordingPreserveRedoStack:\n\t\t\t\t\tthis.pendingDiff.apply(entry.changes)\n\t\t\t\t\tbreak\n\t\t\t\tcase HistoryRecorderState.Paused:\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\texhaustiveSwitchError(this.state)\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate flushPendingDiff() {\n\t\tif (this.pendingDiff.isEmpty()) return\n\n\t\tconst diff = this.pendingDiff.clear()\n\t\tthis.stacks.update(({ undos, redos }) => ({\n\t\t\tundos: undos.push({ type: 'diff', diff }),\n\t\t\tredos,\n\t\t}))\n\t}\n\n\tgetNumUndos() {\n\t\treturn this.stacks.get().undos.length + (this.pendingDiff.isEmpty() ? 0 : 1)\n\t}\n\n\tgetNumRedos() {\n\t\treturn this.stacks.get().redos.length\n\t}\n\n\t/** @internal */\n\t_isInBatch = false\n\n\tbatch(fn: () => void, opts?: TLHistoryBatchOptions) {\n\t\tconst previousState = this.state\n\n\t\t// we move to the new state only if we haven't explicitly paused\n\t\tif (previousState !== HistoryRecorderState.Paused && opts?.history) {\n\t\t\tthis.state = modeToState[opts.history]\n\t\t}\n\n\t\ttry {\n\t\t\tif (this._isInBatch) {\n\t\t\t\ttransact(fn)\n\t\t\t\treturn this\n\t\t\t}\n\n\t\t\tthis._isInBatch = true\n\t\t\ttry {\n\t\t\t\ttransact(fn)\n\t\t\t} catch (error) {\n\t\t\t\tthis.annotateError(error)\n\t\t\t\tthrow error\n\t\t\t} finally {\n\t\t\t\tthis._isInBatch = false\n\t\t\t}\n\n\t\t\treturn this\n\t\t} finally {\n\t\t\tthis.state = previousState\n\t\t}\n\t}\n\n\t// History\n\t_undo({ pushToRedoStack, toMark = undefined }: { pushToRedoStack: boolean; toMark?: string }) {\n\t\tconst previousState = this.state\n\t\tthis.state = HistoryRecorderState.Paused\n\t\ttry {\n\t\t\tlet { undos, redos } = this.stacks.get()\n\n\t\t\t// start by collecting the pending diff (everything since the last mark).\n\t\t\t// we'll accumulate the diff to undo in this variable so we can apply it atomically.\n\t\t\tconst pendingDiff = this.pendingDiff.clear()\n\t\t\tconst isPendingDiffEmpty = isRecordsDiffEmpty(pendingDiff)\n\t\t\tconst diffToUndo = reverseRecordsDiff(pendingDiff)\n\n\t\t\tif (pushToRedoStack && !isPendingDiffEmpty) {\n\t\t\t\tredos = redos.push({ type: 'diff', diff: pendingDiff })\n\t\t\t}\n\n\t\t\tlet didFindMark = false\n\t\t\tif (isPendingDiffEmpty) {\n\t\t\t\t// if nothing has happened since the last mark, pop any intermediate marks off the stack\n\t\t\t\twhile (undos.head?.type === 'stop') {\n\t\t\t\t\tconst mark = undos.head\n\t\t\t\t\tundos = undos.tail\n\t\t\t\t\tif (pushToRedoStack) {\n\t\t\t\t\t\tredos = redos.push(mark)\n\t\t\t\t\t}\n\t\t\t\t\tif (mark.id === toMark) {\n\t\t\t\t\t\tdidFindMark = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!didFindMark) {\n\t\t\t\tloop: while (undos.head) {\n\t\t\t\t\tconst undo = undos.head\n\t\t\t\t\tundos = undos.tail\n\n\t\t\t\t\tif (pushToRedoStack) {\n\t\t\t\t\t\tredos = redos.push(undo)\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch (undo.type) {\n\t\t\t\t\t\tcase 'diff':\n\t\t\t\t\t\t\tsquashRecordDiffsMutable(diffToUndo, [reverseRecordsDiff(undo.diff)])\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 'stop':\n\t\t\t\t\t\t\tif (!toMark) break loop\n\t\t\t\t\t\t\tif (undo.id === toMark) {\n\t\t\t\t\t\t\t\tdidFindMark = true\n\t\t\t\t\t\t\t\tbreak loop\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\texhaustiveSwitchError(undo)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!didFindMark && toMark) {\n\t\t\t\t// whoops, we didn't find the mark we were looking for\n\t\t\t\t// don't do anything\n\t\t\t\treturn this\n\t\t\t}\n\n\t\t\tthis.store.applyDiff(diffToUndo, { ignoreEphemeralKeys: true })\n\t\t\tthis.store.ensureStoreIsUsable()\n\t\t\tthis.stacks.set({ undos, redos })\n\t\t} finally {\n\t\t\tthis.state = previousState\n\t\t}\n\n\t\treturn this\n\t}\n\n\tundo() {\n\t\tthis._undo({ pushToRedoStack: true })\n\n\t\treturn this\n\t}\n\n\tredo() {\n\t\tconst previousState = this.state\n\t\tthis.state = HistoryRecorderState.Paused\n\t\ttry {\n\t\t\tthis.flushPendingDiff()\n\n\t\t\tlet { undos, redos } = this.stacks.get()\n\t\t\tif (redos.length === 0) {\n\t\t\t\treturn this\n\t\t\t}\n\n\t\t\t// ignore any intermediate marks - this should take us to the first `diff` entry\n\t\t\twhile (redos.head?.type === 'stop') {\n\t\t\t\tundos = undos.push(redos.head)\n\t\t\t\tredos = redos.tail\n\t\t\t}\n\n\t\t\t// accumulate diffs to be redone so they can be applied atomically\n\t\t\tconst diffToRedo = createEmptyRecordsDiff<R>()\n\n\t\t\twhile (redos.head) {\n\t\t\t\tconst redo = redos.head\n\t\t\t\tundos = undos.push(redo)\n\t\t\t\tredos = redos.tail\n\n\t\t\t\tif (redo.type === 'diff') {\n\t\t\t\t\tsquashRecordDiffsMutable(diffToRedo, [redo.diff])\n\t\t\t\t} else {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.store.applyDiff(diffToRedo, { ignoreEphemeralKeys: true })\n\t\t\tthis.store.ensureStoreIsUsable()\n\t\t\tthis.stacks.set({ undos, redos })\n\t\t} finally {\n\t\t\tthis.state = previousState\n\t\t}\n\n\t\treturn this\n\t}\n\n\tbail() {\n\t\tthis._undo({ pushToRedoStack: false })\n\n\t\treturn this\n\t}\n\n\tbailToMark(id: string) {\n\t\tif (id) {\n\t\t\tthis._undo({ pushToRedoStack: false, toMark: id })\n\t\t}\n\n\t\treturn this\n\t}\n\n\tsquashToMark(id: string) {\n\t\t// remove marks between head and the mark\n\n\t\tlet top = this.stacks.get().undos\n\t\tconst popped: Array<RecordsDiff<R>> = []\n\n\t\twhile (top.head && !(top.head.type === 'stop' && top.head.id === id)) {\n\t\t\tif (top.head.type === 'diff') {\n\t\t\t\tpopped.push(top.head.diff)\n\t\t\t}\n\t\t\ttop = top.tail\n\t\t}\n\n\t\tif (!top.head || top.head?.id !== id) {\n\t\t\tconsole.error('Could not find mark to squash to: ', id)\n\t\t\treturn this\n\t\t}\n\t\tif (popped.length === 0) {\n\t\t\treturn this\n\t\t}\n\n\t\tconst diff = createEmptyRecordsDiff<R>()\n\t\tsquashRecordDiffsMutable(diff, popped.reverse())\n\n\t\tthis.stacks.update(({ redos }) => ({\n\t\t\tundos: top.push({\n\t\t\t\ttype: 'diff',\n\t\t\t\tdiff,\n\t\t\t}),\n\t\t\tredos,\n\t\t}))\n\n\t\treturn this\n\t}\n\n\t/** @internal */\n\t_mark(id: string) {\n\t\ttransact(() => {\n\t\t\tthis.flushPendingDiff()\n\t\t\tthis.stacks.update(({ undos, redos }) => ({ undos: undos.push({ type: 'stop', id }), redos }))\n\t\t})\n\t}\n\n\tclear() {\n\t\tthis.stacks.set({ undos: stack(), redos: stack() })\n\t\tthis.pendingDiff.clear()\n\t}\n\n\t/** @internal */\n\tgetMarkIdMatching(idSubstring: string) {\n\t\tlet top = this.stacks.get().undos\n\t\twhile (top.head) {\n\t\t\tif (top.head.type === 'stop' && top.head.id.includes(idSubstring)) {\n\t\t\t\treturn top.head.id\n\t\t\t}\n\t\t\ttop = top.tail\n\t\t}\n\t\treturn null\n\t}\n\n\t/** @internal */\n\tdebug() {\n\t\tconst { undos, redos } = this.stacks.get()\n\t\treturn {\n\t\t\tundos: undos.toArray(),\n\t\t\tredos: redos.toArray(),\n\t\t\tpendingDiff: this.pendingDiff.debug(),\n\t\t\tstate: this.state as string,\n\t\t}\n\t}\n}\n\nconst modeToState = {\n\trecord: HistoryRecorderState.Recording,\n\t'record-preserveRedoStack': HistoryRecorderState.RecordingPreserveRedoStack,\n\tignore: HistoryRecorderState.Paused,\n} as const\n\nclass PendingDiff<R extends UnknownRecord> {\n\tprivate diff = createEmptyRecordsDiff<R>()\n\tprivate isEmptyAtom = atom('PendingDiff.isEmpty', true)\n\n\tclear() {\n\t\tconst diff = this.diff\n\t\tthis.diff = createEmptyRecordsDiff<R>()\n\t\tthis.isEmptyAtom.set(true)\n\t\treturn diff\n\t}\n\n\tisEmpty() {\n\t\treturn this.isEmptyAtom.get()\n\t}\n\n\tapply(diff: RecordsDiff<R>) {\n\t\tsquashRecordDiffsMutable(this.diff, [diff])\n\t\tthis.isEmptyAtom.set(isRecordsDiffEmpty(this.diff))\n\t}\n\n\tdebug() {\n\t\treturn { diff: this.diff, isEmpty: this.isEmpty() }\n\t}\n}\n\nimport { EMPTY_ARRAY } from '@tldraw/state'\n\nexport type Stack<T> = StackItem<T> | EmptyStackItem<T>\n\nexport function stack<T>(items?: Array<T>): Stack<T> {\n\tif (items) {\n\t\tlet result = EMPTY_STACK_ITEM as Stack<T>\n\t\twhile (items.length) {\n\t\t\tresult = result.push(items.pop()!)\n\t\t}\n\t\treturn result\n\t}\n\treturn EMPTY_STACK_ITEM as any\n}\n\nclass EmptyStackItem<T> implements Iterable<T> {\n\treadonly length = 0\n\treadonly head = null\n\treadonly tail: Stack<T> = this\n\n\tpush(head: T): Stack<T> {\n\t\treturn new StackItem<T>(head, this)\n\t}\n\n\ttoArray() {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\t[Symbol.iterator]() {\n\t\treturn {\n\t\t\tnext() {\n\t\t\t\treturn { value: undefined, done: true as const }\n\t\t\t},\n\t\t}\n\t}\n}\n\nconst EMPTY_STACK_ITEM = new EmptyStackItem()\n\nclass StackItem<T> implements Iterable<T> {\n\tlength: number\n\tconstructor(\n\t\tpublic readonly head: T,\n\t\tpublic readonly tail: Stack<T>\n\t) {\n\t\tthis.length = tail.length + 1\n\t}\n\n\tpush(head: T): Stack<T> {\n\t\treturn new StackItem(head, this)\n\t}\n\n\ttoArray() {\n\t\treturn Array.from(this)\n\t}\n\n\t[Symbol.iterator]() {\n\t\tlet stack = this as Stack<T>\n\t\treturn {\n\t\t\tnext() {\n\t\t\t\tif (stack.length) {\n\t\t\t\t\tconst value = stack.head!\n\t\t\t\t\tstack = stack.tail\n\t\t\t\t\treturn { value, done: false as const }\n\t\t\t\t} else {\n\t\t\t\t\treturn { value: undefined, done: true as const }\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["HistoryRecorderState", "stack"]
7
7
  }
@@ -14,44 +14,71 @@ const spaceCharacterRegex = /\s/;
14
14
  class TextManager {
15
15
  constructor(editor) {
16
16
  this.editor = editor;
17
- this.baseElem = document.createElement("div");
18
- this.baseElem.classList.add("tl-text");
19
- this.baseElem.classList.add("tl-text-measure");
20
- this.baseElem.tabIndex = -1;
17
+ const elm = document.createElement("div");
18
+ elm.classList.add("tl-text");
19
+ elm.classList.add("tl-text-measure");
20
+ elm.setAttribute("dir", "auto");
21
+ elm.tabIndex = -1;
22
+ this.editor.getContainer().appendChild(elm);
23
+ this.defaultStyles = {
24
+ "overflow-wrap": "break-word",
25
+ "word-break": "auto",
26
+ width: null,
27
+ height: null,
28
+ "max-width": null,
29
+ "min-width": null
30
+ };
31
+ this.elm = elm;
32
+ }
33
+ elm;
34
+ defaultStyles;
35
+ dispose() {
36
+ return this.elm.remove();
37
+ }
38
+ resetElmStyles() {
39
+ const { elm, defaultStyles } = this;
40
+ for (const key in defaultStyles) {
41
+ elm.style.setProperty(key, defaultStyles[key]);
42
+ }
21
43
  }
22
- baseElem;
23
44
  measureText(textToMeasure, opts) {
24
45
  const div = document.createElement("div");
25
46
  div.textContent = normalizeTextForDom(textToMeasure);
26
47
  return this.measureHtml(div.innerHTML, opts);
27
48
  }
28
49
  measureHtml(html, opts) {
29
- const wrapperElm = this.baseElem.cloneNode();
30
- this.editor.getContainer().appendChild(wrapperElm);
31
- wrapperElm.innerHTML = html;
32
- this.baseElem.insertAdjacentElement("afterend", wrapperElm);
33
- wrapperElm.setAttribute("dir", "auto");
34
- wrapperElm.style.setProperty("unicode-bidi", "plaintext");
35
- wrapperElm.style.setProperty("font-family", opts.fontFamily);
36
- wrapperElm.style.setProperty("font-style", opts.fontStyle);
37
- wrapperElm.style.setProperty("font-weight", opts.fontWeight);
38
- wrapperElm.style.setProperty("font-size", opts.fontSize + "px");
39
- wrapperElm.style.setProperty("line-height", opts.lineHeight * opts.fontSize + "px");
40
- wrapperElm.style.setProperty("max-width", opts.maxWidth === null ? null : opts.maxWidth + "px");
41
- wrapperElm.style.setProperty("min-width", opts.minWidth === null ? null : opts.minWidth + "px");
42
- wrapperElm.style.setProperty("padding", opts.padding);
43
- wrapperElm.style.setProperty(
44
- "overflow-wrap",
45
- opts.disableOverflowWrapBreaking ? "normal" : "break-word"
46
- );
50
+ const { elm } = this;
51
+ if (opts.otherStyles) {
52
+ for (const key in opts.otherStyles) {
53
+ if (!this.defaultStyles[key]) {
54
+ this.defaultStyles[key] = elm.style.getPropertyValue(key);
55
+ }
56
+ }
57
+ }
58
+ elm.innerHTML = html;
59
+ this.resetElmStyles();
60
+ elm.style.setProperty("font-family", opts.fontFamily);
61
+ elm.style.setProperty("font-style", opts.fontStyle);
62
+ elm.style.setProperty("font-weight", opts.fontWeight);
63
+ elm.style.setProperty("font-size", opts.fontSize + "px");
64
+ elm.style.setProperty("line-height", opts.lineHeight.toString());
65
+ elm.style.setProperty("padding", opts.padding);
66
+ if (opts.maxWidth) {
67
+ elm.style.setProperty("max-width", opts.maxWidth + "px");
68
+ }
69
+ if (opts.minWidth) {
70
+ elm.style.setProperty("min-width", opts.minWidth + "px");
71
+ }
72
+ if (opts.disableOverflowWrapBreaking) {
73
+ elm.style.setProperty("overflow-wrap", "normal");
74
+ }
47
75
  if (opts.otherStyles) {
48
76
  for (const [key, value] of Object.entries(opts.otherStyles)) {
49
- wrapperElm.style.setProperty(key, value);
77
+ elm.style.setProperty(key, value);
50
78
  }
51
79
  }
52
- const scrollWidth = wrapperElm.scrollWidth;
53
- const rect = wrapperElm.getBoundingClientRect();
54
- wrapperElm.remove();
80
+ const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0;
81
+ const rect = elm.getBoundingClientRect();
55
82
  return {
56
83
  x: 0,
57
84
  y: 0,
@@ -137,29 +164,34 @@ class TextManager {
137
164
  */
138
165
  measureTextSpans(textToMeasure, opts) {
139
166
  if (textToMeasure === "") return [];
140
- const elm = this.baseElem.cloneNode();
141
- this.editor.getContainer().appendChild(elm);
167
+ const { elm } = this;
168
+ if (opts.otherStyles) {
169
+ for (const key in opts.otherStyles) {
170
+ if (!this.defaultStyles[key]) {
171
+ this.defaultStyles[key] = elm.style.getPropertyValue(key);
172
+ }
173
+ }
174
+ }
175
+ this.resetElmStyles();
176
+ elm.style.setProperty("font-family", opts.fontFamily);
177
+ elm.style.setProperty("font-style", opts.fontStyle);
178
+ elm.style.setProperty("font-weight", opts.fontWeight);
179
+ elm.style.setProperty("font-size", opts.fontSize + "px");
180
+ elm.style.setProperty("line-height", opts.lineHeight.toString());
142
181
  const elementWidth = Math.ceil(opts.width - opts.padding * 2);
143
- elm.setAttribute("dir", "auto");
144
- elm.style.setProperty("unicode-bidi", "plaintext");
145
182
  elm.style.setProperty("width", `${elementWidth}px`);
146
183
  elm.style.setProperty("height", "min-content");
147
- elm.style.setProperty("font-size", `${opts.fontSize}px`);
148
- elm.style.setProperty("font-family", opts.fontFamily);
149
- elm.style.setProperty("font-weight", opts.fontWeight);
150
- elm.style.setProperty("line-height", `${opts.lineHeight * opts.fontSize}px`);
151
184
  elm.style.setProperty("text-align", textAlignmentsForLtr[opts.textAlign]);
152
- elm.style.setProperty("font-style", opts.fontStyle);
153
- if (opts.otherStyles) {
154
- for (const [key, value] of Object.entries(opts.otherStyles)) {
155
- elm.style.setProperty(key, value);
156
- }
157
- }
158
185
  const shouldTruncateToFirstLine = opts.overflow === "truncate-ellipsis" || opts.overflow === "truncate-clip";
159
186
  if (shouldTruncateToFirstLine) {
160
187
  elm.style.setProperty("overflow-wrap", "anywhere");
161
188
  elm.style.setProperty("word-break", "break-all");
162
189
  }
190
+ if (opts.otherStyles) {
191
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
192
+ elm.style.setProperty(key, value);
193
+ }
194
+ }
163
195
  const normalizedText = normalizeTextForDom(textToMeasure);
164
196
  elm.textContent = normalizedText;
165
197
  const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
@@ -185,7 +217,6 @@ class TextManager {
185
217
  });
186
218
  return truncatedSpans;
187
219
  }
188
- elm.remove();
189
220
  return spans;
190
221
  }
191
222
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/TextManager/TextManager.ts"],
4
- "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { Editor } from '../../Editor'\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n\totherStyles?: Record<string, string>\n}\n\nconst spaceCharacterRegex = /\\s/\n\n/** @public */\nexport class TextManager {\n\tprivate baseElem: HTMLDivElement\n\n\tconstructor(public editor: Editor) {\n\t\tthis.baseElem = document.createElement('div')\n\t\tthis.baseElem.classList.add('tl-text')\n\t\tthis.baseElem.classList.add('tl-text-measure')\n\t\tthis.baseElem.tabIndex = -1\n\t}\n\n\tmeasureText(\n\t\ttextToMeasure: string,\n\t\topts: {\n\t\t\tfontStyle: string\n\t\t\tfontWeight: string\n\t\t\tfontFamily: string\n\t\t\tfontSize: number\n\t\t\tlineHeight: number\n\t\t\t/**\n\t\t\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t\t\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t\t\t * space are preserved.\n\t\t\t */\n\t\t\tmaxWidth: null | number\n\t\t\tminWidth?: null | number\n\t\t\tpadding: string\n\t\t\tdisableOverflowWrapBreaking?: boolean\n\t\t}\n\t): BoxModel & { scrollWidth: number } {\n\t\tconst div = document.createElement('div')\n\t\tdiv.textContent = normalizeTextForDom(textToMeasure)\n\t\treturn this.measureHtml(div.innerHTML, opts)\n\t}\n\n\tmeasureHtml(\n\t\thtml: string,\n\t\topts: {\n\t\t\tfontStyle: string\n\t\t\tfontWeight: string\n\t\t\tfontFamily: string\n\t\t\tfontSize: number\n\t\t\tlineHeight: number\n\t\t\t/**\n\t\t\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t\t\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t\t\t * space are preserved.\n\t\t\t */\n\t\t\tmaxWidth: null | number\n\t\t\tminWidth?: null | number\n\t\t\totherStyles?: Record<string, string>\n\t\t\tpadding: string\n\t\t\tdisableOverflowWrapBreaking?: boolean\n\t\t}\n\t): BoxModel & { scrollWidth: number } {\n\t\t// Duplicate our base element; we don't need to clone deep\n\t\tconst wrapperElm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(wrapperElm)\n\t\twrapperElm.innerHTML = html\n\t\tthis.baseElem.insertAdjacentElement('afterend', wrapperElm)\n\n\t\twrapperElm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\twrapperElm.style.setProperty('unicode-bidi', 'plaintext')\n\t\twrapperElm.style.setProperty('font-family', opts.fontFamily)\n\t\twrapperElm.style.setProperty('font-style', opts.fontStyle)\n\t\twrapperElm.style.setProperty('font-weight', opts.fontWeight)\n\t\twrapperElm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\twrapperElm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')\n\t\twrapperElm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')\n\t\twrapperElm.style.setProperty('min-width', opts.minWidth === null ? null : opts.minWidth + 'px')\n\t\twrapperElm.style.setProperty('padding', opts.padding)\n\t\twrapperElm.style.setProperty(\n\t\t\t'overflow-wrap',\n\t\t\topts.disableOverflowWrapBreaking ? 'normal' : 'break-word'\n\t\t)\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\twrapperElm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst scrollWidth = wrapperElm.scrollWidth\n\t\tconst rect = wrapperElm.getBoundingClientRect()\n\t\twrapperElm.remove()\n\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\tw: rect.width,\n\t\t\th: rect.height,\n\t\t\tscrollWidth,\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const char of childNode.textContent ?? '') {\n\t\t\t\t// place the range around the characters we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + char.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]!\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(char)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: char,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += char\n\t\t\t\t}\n\n\t\t\t\tif (char === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += char.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst elm = this.baseElem.cloneNode() as HTMLDivElement\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\telm.setAttribute('dir', 'auto')\n\t\t// N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\")\n\t\t// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.\n\t\telm.style.setProperty('unicode-bidi', 'plaintext')\n\t\telm.style.setProperty('width', `${elementWidth}px`)\n\t\telm.style.setProperty('height', 'min-content')\n\t\telm.style.setProperty('font-size', `${opts.fontSize}px`)\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)\n\t\telm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\telm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\n\t\tif (shouldTruncateToFirstLine) {\n\t\t\telm.style.setProperty('overflow-wrap', 'anywhere')\n\t\t\telm.style.setProperty('word-break', 'break-all')\n\t\t}\n\n\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t// Render the text into the measurement element:\n\t\telm.textContent = normalizedText\n\n\t\t// actually measure the text:\n\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\tshouldTruncateToFirstLine,\n\t\t})\n\n\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\telm.textContent = '\u2026'\n\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\telm.textContent = normalizedText\n\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t}).spans\n\n\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t// browser.\n\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\ttruncatedSpans.push({\n\t\t\t\ttext: '\u2026',\n\t\t\t\tbox: {\n\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn truncatedSpans\n\t\t}\n\n\t\telm.remove()\n\n\t\treturn spans\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'\nimport { Editor } from '../../Editor'\n\nconst fixNewLines = /\\r?\\n|\\r/g\n\nfunction normalizeTextForDom(text: string) {\n\treturn text\n\t\t.replace(fixNewLines, '\\n')\n\t\t.split('\\n')\n\t\t.map((x) => x || ' ')\n\t\t.join('\\n')\n}\n\nconst textAlignmentsForLtr = {\n\tstart: 'left',\n\t'start-legacy': 'left',\n\tmiddle: 'center',\n\t'middle-legacy': 'center',\n\tend: 'right',\n\t'end-legacy': 'right',\n}\n\n/** @public */\nexport interface TLMeasureTextOpts {\n\tfontStyle: string\n\tfontWeight: string\n\tfontFamily: string\n\tfontSize: number\n\t/** This must be a number, e.g. 1.35, not a pixel value. */\n\tlineHeight: number\n\t/**\n\t * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth\n\t * is null, the text will be measured without wrapping, but explicit line breaks and\n\t * space are preserved.\n\t */\n\tmaxWidth: null | number\n\tminWidth?: null | number\n\t// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts\n\tpadding: string\n\totherStyles?: Record<string, string>\n\tdisableOverflowWrapBreaking?: boolean\n\tmeasureScrollWidth?: boolean\n}\n\n/** @public */\nexport interface TLMeasureTextSpanOpts {\n\toverflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'\n\twidth: number\n\theight: number\n\tpadding: number\n\tfontSize: number\n\tfontWeight: string\n\tfontFamily: string\n\tfontStyle: string\n\tlineHeight: number\n\ttextAlign: TLDefaultHorizontalAlignStyle\n\totherStyles?: Record<string, string>\n\tmeasureScrollWidth?: boolean\n}\n\nconst spaceCharacterRegex = /\\s/\n\n/** @public */\nexport class TextManager {\n\tprivate elm: HTMLDivElement\n\tprivate defaultStyles: Record<string, string | null>\n\n\tconstructor(public editor: Editor) {\n\t\tconst elm = document.createElement('div')\n\t\telm.classList.add('tl-text')\n\t\telm.classList.add('tl-text-measure')\n\t\telm.setAttribute('dir', 'auto')\n\t\telm.tabIndex = -1\n\t\tthis.editor.getContainer().appendChild(elm)\n\n\t\t// we need to save the default styles so that we can restore them when we're done\n\t\t// these must be the css names, not the js names for the styles\n\t\tthis.defaultStyles = {\n\t\t\t'overflow-wrap': 'break-word',\n\t\t\t'word-break': 'auto',\n\t\t\twidth: null,\n\t\t\theight: null,\n\t\t\t'max-width': null,\n\t\t\t'min-width': null,\n\t\t}\n\n\t\tthis.elm = elm\n\t}\n\n\tdispose() {\n\t\treturn this.elm.remove()\n\t}\n\n\tprivate resetElmStyles() {\n\t\tconst { elm, defaultStyles } = this\n\t\tfor (const key in defaultStyles) {\n\t\t\telm.style.setProperty(key, defaultStyles[key])\n\t\t}\n\t}\n\n\tmeasureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {\n\t\tconst div = document.createElement('div')\n\t\tdiv.textContent = normalizeTextForDom(textToMeasure)\n\t\treturn this.measureHtml(div.innerHTML, opts)\n\t}\n\n\tmeasureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {\n\t\tconst { elm } = this\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const key in opts.otherStyles) {\n\t\t\t\tif (!this.defaultStyles[key]) {\n\t\t\t\t\t// we need to save the original style so that we can restore it when we're done\n\t\t\t\t\tthis.defaultStyles[key] = elm.style.getPropertyValue(key)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\telm.innerHTML = html\n\n\t\t// Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)\n\t\tthis.resetElmStyles()\n\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\telm.style.setProperty('line-height', opts.lineHeight.toString())\n\t\telm.style.setProperty('padding', opts.padding)\n\n\t\tif (opts.maxWidth) {\n\t\t\telm.style.setProperty('max-width', opts.maxWidth + 'px')\n\t\t}\n\n\t\tif (opts.minWidth) {\n\t\t\telm.style.setProperty('min-width', opts.minWidth + 'px')\n\t\t}\n\n\t\tif (opts.disableOverflowWrapBreaking) {\n\t\t\telm.style.setProperty('overflow-wrap', 'normal')\n\t\t}\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\telm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0\n\t\tconst rect = elm.getBoundingClientRect()\n\n\t\treturn {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\tw: rect.width,\n\t\t\th: rect.height,\n\t\t\tscrollWidth,\n\t\t}\n\t}\n\n\t/**\n\t * Given an html element, measure the position of each span of unbroken\n\t * word/white-space characters within any text nodes it contains.\n\t */\n\tmeasureElementTextNodeSpans(\n\t\telement: HTMLElement,\n\t\t{ shouldTruncateToFirstLine = false }: { shouldTruncateToFirstLine?: boolean } = {}\n\t): { spans: { box: BoxModel; text: string }[]; didTruncate: boolean } {\n\t\tconst spans = []\n\n\t\t// Measurements of individual spans are relative to the containing element\n\t\tconst elmBounds = element.getBoundingClientRect()\n\t\tconst offsetX = -elmBounds.left\n\t\tconst offsetY = -elmBounds.top\n\n\t\t// we measure by creating a range that spans each character in the elements text node\n\t\tconst range = new Range()\n\t\tconst textNode = element.childNodes[0]\n\t\tlet idx = 0\n\n\t\tlet currentSpan = null\n\t\tlet prevCharWasSpaceCharacter = null\n\t\tlet prevCharTop = 0\n\t\tlet prevCharLeftForRTLTest = 0\n\t\tlet didTruncate = false\n\t\tfor (const childNode of element.childNodes) {\n\t\t\tif (childNode.nodeType !== Node.TEXT_NODE) continue\n\n\t\t\tfor (const char of childNode.textContent ?? '') {\n\t\t\t\t// place the range around the characters we're interested in\n\t\t\t\trange.setStart(textNode, idx)\n\t\t\t\trange.setEnd(textNode, idx + char.length)\n\t\t\t\t// measure the range. some browsers return multiple rects for the\n\t\t\t\t// first char in a new line - one for the line break, and one for\n\t\t\t\t// the character itself. we're only interested in the character.\n\t\t\t\tconst rects = range.getClientRects()\n\t\t\t\tconst rect = rects[rects.length - 1]!\n\n\t\t\t\t// calculate the position of the character relative to the element\n\t\t\t\tconst top = rect.top + offsetY\n\t\t\t\tconst left = rect.left + offsetX\n\t\t\t\tconst right = rect.right + offsetX\n\t\t\t\tconst isRTL = left < prevCharLeftForRTLTest\n\n\t\t\t\tconst isSpaceCharacter = spaceCharacterRegex.test(char)\n\t\t\t\tif (\n\t\t\t\t\t// If we're at a word boundary...\n\t\t\t\t\tisSpaceCharacter !== prevCharWasSpaceCharacter ||\n\t\t\t\t\t// ...or we're on a different line...\n\t\t\t\t\ttop !== prevCharTop ||\n\t\t\t\t\t// ...or we're at the start of the text and haven't created a span yet...\n\t\t\t\t\t!currentSpan\n\t\t\t\t) {\n\t\t\t\t\t// ...then we're at a span boundary!\n\n\t\t\t\t\tif (currentSpan) {\n\t\t\t\t\t\t// if we're truncating to a single line & we just finished the first line, stop there\n\t\t\t\t\t\tif (shouldTruncateToFirstLine && top !== prevCharTop) {\n\t\t\t\t\t\t\tdidTruncate = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// otherwise add the span to the list ready to start a new one\n\t\t\t\t\t\tspans.push(currentSpan)\n\t\t\t\t\t}\n\n\t\t\t\t\t// start a new span\n\t\t\t\t\tcurrentSpan = {\n\t\t\t\t\t\tbox: { x: left, y: top, w: rect.width, h: rect.height },\n\t\t\t\t\t\ttext: char,\n\t\t\t\t\t}\n\t\t\t\t\tprevCharLeftForRTLTest = left\n\t\t\t\t} else {\n\t\t\t\t\t// Looks like we're in RTL mode, so we need to adjust the left position.\n\t\t\t\t\tif (isRTL) {\n\t\t\t\t\t\tcurrentSpan.box.x = left\n\t\t\t\t\t}\n\n\t\t\t\t\t// otherwise we just need to extend the current span with the next character\n\t\t\t\t\tcurrentSpan.box.w = isRTL ? currentSpan.box.w + rect.width : right - currentSpan.box.x\n\t\t\t\t\tcurrentSpan.text += char\n\t\t\t\t}\n\n\t\t\t\tif (char === '\\n') {\n\t\t\t\t\tprevCharLeftForRTLTest = 0\n\t\t\t\t}\n\n\t\t\t\tprevCharWasSpaceCharacter = isSpaceCharacter\n\t\t\t\tprevCharTop = top\n\t\t\t\tidx += char.length\n\t\t\t}\n\t\t}\n\n\t\t// Add the last span\n\t\tif (currentSpan) {\n\t\t\tspans.push(currentSpan)\n\t\t}\n\n\t\treturn { spans, didTruncate }\n\t}\n\n\t/**\n\t * Measure text into individual spans. Spans are created by rendering the\n\t * text, then dividing it up according to line breaks and word boundaries.\n\t *\n\t * It works by having the browser render the text, then measuring the\n\t * position of each character. You can use this to replicate the text-layout\n\t * algorithm of the current browser in e.g. an SVG export.\n\t */\n\tmeasureTextSpans(\n\t\ttextToMeasure: string,\n\t\topts: TLMeasureTextSpanOpts\n\t): { text: string; box: BoxModel }[] {\n\t\tif (textToMeasure === '') return []\n\n\t\tconst { elm } = this\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const key in opts.otherStyles) {\n\t\t\t\tif (!this.defaultStyles[key]) {\n\t\t\t\t\t// we need to save the original style so that we can restore it when we're done\n\t\t\t\t\tthis.defaultStyles[key] = elm.style.getPropertyValue(key)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.resetElmStyles()\n\n\t\telm.style.setProperty('font-family', opts.fontFamily)\n\t\telm.style.setProperty('font-style', opts.fontStyle)\n\t\telm.style.setProperty('font-weight', opts.fontWeight)\n\t\telm.style.setProperty('font-size', opts.fontSize + 'px')\n\t\telm.style.setProperty('line-height', opts.lineHeight.toString())\n\n\t\tconst elementWidth = Math.ceil(opts.width - opts.padding * 2)\n\t\telm.style.setProperty('width', `${elementWidth}px`)\n\t\telm.style.setProperty('height', 'min-content')\n\t\telm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])\n\n\t\tconst shouldTruncateToFirstLine =\n\t\t\topts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'\n\n\t\tif (shouldTruncateToFirstLine) {\n\t\t\telm.style.setProperty('overflow-wrap', 'anywhere')\n\t\t\telm.style.setProperty('word-break', 'break-all')\n\t\t}\n\n\t\tif (opts.otherStyles) {\n\t\t\tfor (const [key, value] of Object.entries(opts.otherStyles)) {\n\t\t\t\telm.style.setProperty(key, value)\n\t\t\t}\n\t\t}\n\n\t\tconst normalizedText = normalizeTextForDom(textToMeasure)\n\n\t\t// Render the text into the measurement element:\n\t\telm.textContent = normalizedText\n\n\t\t// actually measure the text:\n\t\tconst { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {\n\t\t\tshouldTruncateToFirstLine,\n\t\t})\n\n\t\tif (opts.overflow === 'truncate-ellipsis' && didTruncate) {\n\t\t\t// we need to measure the ellipsis to know how much space it takes up\n\t\t\telm.textContent = '\u2026'\n\t\t\tconst ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)\n\n\t\t\t// then, we need to subtract that space from the width we have and measure again:\n\t\t\telm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)\n\t\t\telm.textContent = normalizedText\n\t\t\tconst truncatedSpans = this.measureElementTextNodeSpans(elm, {\n\t\t\t\tshouldTruncateToFirstLine: true,\n\t\t\t}).spans\n\n\t\t\t// Finally, we add in our ellipsis at the end of the last span. We\n\t\t\t// have to do this after measuring, not before, because adding the\n\t\t\t// ellipsis changes how whitespace might be getting collapsed by the\n\t\t\t// browser.\n\t\t\tconst lastSpan = truncatedSpans[truncatedSpans.length - 1]!\n\t\t\ttruncatedSpans.push({\n\t\t\t\ttext: '\u2026',\n\t\t\t\tbox: {\n\t\t\t\t\tx: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),\n\t\t\t\t\ty: lastSpan.box.y,\n\t\t\t\t\tw: ellipsisWidth,\n\t\t\t\t\th: lastSpan.box.h,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\treturn truncatedSpans\n\t\t}\n\n\t\treturn spans\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -214,16 +214,6 @@ class ShapeUtil {
214
214
  canReceiveNewChildrenOfType(_shape, _type) {
215
215
  return false;
216
216
  }
217
- /**
218
- * Get whether the shape can receive children of a given type.
219
- *
220
- * @param shape - The shape type.
221
- * @param shapes - The shapes that are being dropped.
222
- * @public
223
- */
224
- canDropShapes(_shape, _shapes) {
225
- return false;
226
- }
227
217
  /** @internal */
228
218
  expandSelectionOutlinePx(shape) {
229
219
  return 0;