@tldraw/editor 3.13.0-canary.faba5d60dae3 → 3.13.0

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 (142) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/dist-cjs/index.d.ts +99 -99
  3. package/dist-cjs/index.js +7 -22
  4. package/dist-cjs/index.js.map +2 -2
  5. package/dist-cjs/lib/components/Shape.js +2 -1
  6. package/dist-cjs/lib/components/Shape.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +10 -6
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  9. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
  10. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  11. package/dist-cjs/lib/editor/Editor.js +85 -24
  12. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  14. package/dist-cjs/lib/editor/managers/TextManager.js +10 -0
  15. package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
  16. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +1 -1
  17. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  18. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +0 -3
  19. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  20. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  21. package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
  22. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  23. package/dist-cjs/lib/hooks/useEditorComponents.js +1 -2
  24. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  25. package/dist-cjs/lib/primitives/Box.js +16 -0
  26. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  27. package/dist-cjs/lib/primitives/Mat.js +1 -1
  28. package/dist-cjs/lib/primitives/Mat.js.map +2 -2
  29. package/dist-cjs/lib/primitives/Vec.js +20 -0
  30. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  31. package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
  32. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  33. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  34. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  35. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  36. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  37. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  38. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  39. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  40. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  41. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
  42. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  43. package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
  44. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  45. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  46. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  47. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  48. package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
  49. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
  50. package/dist-cjs/lib/utils/debug-flags.js +5 -2
  51. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  52. package/dist-cjs/version.js +3 -3
  53. package/dist-cjs/version.js.map +1 -1
  54. package/dist-esm/index.d.mts +99 -99
  55. package/dist-esm/index.mjs +9 -41
  56. package/dist-esm/index.mjs.map +2 -2
  57. package/dist-esm/lib/components/Shape.mjs +2 -1
  58. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  59. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +10 -6
  60. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  61. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
  62. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  63. package/dist-esm/lib/editor/Editor.mjs +85 -24
  64. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  65. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  66. package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
  67. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  68. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +1 -1
  69. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  70. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +0 -3
  71. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  72. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  73. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
  74. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  75. package/dist-esm/lib/hooks/useEditorComponents.mjs +1 -4
  76. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  77. package/dist-esm/lib/primitives/Box.mjs +16 -0
  78. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  79. package/dist-esm/lib/primitives/Mat.mjs +1 -1
  80. package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
  81. package/dist-esm/lib/primitives/Vec.mjs +20 -0
  82. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  83. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  84. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  85. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
  86. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  87. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  88. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  89. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  90. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  91. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  92. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  93. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
  94. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  95. package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
  96. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  97. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  98. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  99. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  100. package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
  101. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
  102. package/dist-esm/lib/utils/debug-flags.mjs +5 -2
  103. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  104. package/dist-esm/version.mjs +3 -3
  105. package/dist-esm/version.mjs.map +1 -1
  106. package/editor.css +36 -4
  107. package/package.json +7 -7
  108. package/src/index.ts +16 -31
  109. package/src/lib/components/Shape.tsx +2 -4
  110. package/src/lib/components/default-components/DefaultCanvas.tsx +11 -6
  111. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
  112. package/src/lib/editor/Editor.test.ts +1 -1
  113. package/src/lib/editor/Editor.ts +96 -24
  114. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
  115. package/src/lib/editor/managers/TextManager.ts +12 -0
  116. package/src/lib/editor/shapes/ShapeUtil.ts +10 -2
  117. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +0 -4
  118. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
  119. package/src/lib/exports/getSvgJsx.tsx +16 -7
  120. package/src/lib/hooks/useEditorComponents.tsx +2 -5
  121. package/src/lib/primitives/Box.ts +20 -0
  122. package/src/lib/primitives/Mat.ts +5 -4
  123. package/src/lib/primitives/Vec.ts +23 -0
  124. package/src/lib/primitives/geometry/Arc2d.ts +5 -5
  125. package/src/lib/primitives/geometry/Circle2d.ts +4 -4
  126. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
  127. package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
  128. package/src/lib/primitives/geometry/Edge2d.ts +3 -3
  129. package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
  130. package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
  131. package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
  132. package/src/lib/primitives/geometry/Group2d.ts +70 -7
  133. package/src/lib/primitives/geometry/Point2d.ts +2 -2
  134. package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
  135. package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
  136. package/src/lib/test/currentToolIdMask.test.ts +1 -1
  137. package/src/lib/test/user.test.ts +1 -1
  138. package/src/lib/utils/areShapesContentEqual.ts +4 -0
  139. package/src/lib/utils/debug-flags.ts +7 -2
  140. package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
  141. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
  142. package/src/version.ts +3 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/debug-flags.ts"],
4
- "sourcesContent": ["import { Atom, atom, react } from '@tldraw/state'\nimport { deleteFromSessionStorage, getFromSessionStorage, setInSessionStorage } from '@tldraw/utils'\n\n// --- 1. DEFINE ---\n//\n// Define your debug values and feature flags here. Use `createDebugValue` to\n// create an arbitrary value with defaults for production, staging, and\n// development. Use `createFeatureFlag` to create a boolean flag which will be\n// `true` by default in development and staging, and `false` in production.\n/** @internal */\nexport const featureFlags: Record<string, DebugFlag<boolean>> = {}\n\n/** @internal */\nexport const pointerCaptureTrackingObject = createDebugValue(\n\t'pointerCaptureTrackingObject',\n\t// ideally we wouldn't store this mutable value in an atom but it's not\n\t// a big deal for debug values\n\t{\n\t\tdefaults: { all: new Map<Element, number>() },\n\t\tshouldStoreForSession: false,\n\t}\n)\n\n/** @internal */\nexport const debugFlags = {\n\t// --- DEBUG VALUES ---\n\tlogPreventDefaults: createDebugValue('logPreventDefaults', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogPointerCaptures: createDebugValue('logPointerCaptures', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogElementRemoves: createDebugValue('logElementRemoves', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugSvg: createDebugValue('debugSvg', {\n\t\tdefaults: { all: false },\n\t}),\n\tshowFps: createDebugValue('showFps', {\n\t\tdefaults: { all: false },\n\t}),\n\tmeasurePerformance: createDebugValue('measurePerformance', { defaults: { all: false } }),\n\tthrowToBlob: createDebugValue('throwToBlob', {\n\t\tdefaults: { all: false },\n\t}),\n\treconnectOnPing: createDebugValue('reconnectOnPing', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugCursors: createDebugValue('debugCursors', {\n\t\tdefaults: { all: false },\n\t}),\n\tforceSrgb: createDebugValue('forceSrgbColors', { defaults: { all: false } }),\n\tdebugGeometry: createDebugValue('debugGeometry', { defaults: { all: false } }),\n\thideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),\n\teditOnType: createDebugValue('editOnType', { defaults: { all: false } }),\n\ta11y: createDebugValue('a11y', { defaults: { all: false } }),\n} as const\n\ndeclare global {\n\tinterface Window {\n\t\ttldrawLog(message: any): void\n\t}\n}\n\n// --- 2. USE ---\n// In normal code, read from debug flags directly by calling .value on them:\n// if (debugFlags.preventDefaultLogging.value) { ... }\n//\n// In react, wrap your reads in `useValue` (or your component in `track`)\n// so they react to changes:\n// const shouldLog = useValue(debugFlags.preventDefaultLogging)\n\n// --- 3. GET FUNKY ---\n// If you need to do fun stuff like monkey-patching in response to flag changes,\n// add that here. Make sure you wrap your code in `react` so it runs\n// automatically when values change!\n\nif (typeof Element !== 'undefined') {\n\tconst nativeElementRemoveChild = Element.prototype.removeChild\n\treact('element removal logging', () => {\n\t\tif (debugFlags.logElementRemoves.get()) {\n\t\t\tElement.prototype.removeChild = function <T extends Node>(this: any, child: Node): T {\n\t\t\t\tconsole.warn('[tldraw] removing child:', child)\n\t\t\t\treturn nativeElementRemoveChild.call(this, child) as T\n\t\t\t}\n\t\t} else {\n\t\t\tElement.prototype.removeChild = nativeElementRemoveChild\n\t\t}\n\t})\n}\n\n// --- IMPLEMENTATION ---\n// you probably don't need to read this if you're just using the debug values system\nfunction createDebugValue<T>(\n\tname: string,\n\t{\n\t\tdefaults,\n\t\tshouldStoreForSession = true,\n\t}: { defaults: DebugFlagDefaults<T>; shouldStoreForSession?: boolean }\n) {\n\treturn createDebugValueBase({\n\t\tname,\n\t\tdefaults,\n\t\tshouldStoreForSession,\n\t})\n}\n\n// function createFeatureFlag<T>(\n// \tname: string,\n// \t{\n// \t\tdefaults,\n// \t\tshouldStoreForSession = true,\n// \t}: { defaults: DebugFlagDefaults<T>; shouldStoreForSession?: boolean }\n// ) {\n// \treturn createDebugValueBase({\n// \t\tname,\n// \t\tdefaults,\n// \t\tshouldStoreForSession,\n// \t})\n// }\n\nfunction createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {\n\tconst defaultValue = getDefaultValue(def)\n\tconst storedValue = def.shouldStoreForSession\n\t\t? (getStoredInitialValue(def.name) as T | null)\n\t\t: null\n\tconst valueAtom = atom(`debug:${def.name}`, storedValue ?? defaultValue)\n\n\tif (typeof window !== 'undefined') {\n\t\tif (def.shouldStoreForSession) {\n\t\t\treact(`debug:${def.name}`, () => {\n\t\t\t\tconst currentValue = valueAtom.get()\n\t\t\t\tif (currentValue === defaultValue) {\n\t\t\t\t\tdeleteFromSessionStorage(`tldraw_debug:${def.name}`)\n\t\t\t\t} else {\n\t\t\t\t\tsetInSessionStorage(`tldraw_debug:${def.name}`, JSON.stringify(currentValue))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tObject.defineProperty(window, `tldraw${def.name.replace(/^[a-z]/, (l) => l.toUpperCase())}`, {\n\t\t\tget() {\n\t\t\t\treturn valueAtom.get()\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalueAtom.set(newValue)\n\t\t\t},\n\t\t\tconfigurable: true,\n\t\t})\n\t}\n\n\treturn Object.assign(valueAtom, def)\n}\n\nfunction getStoredInitialValue(name: string) {\n\ttry {\n\t\treturn JSON.parse(getFromSessionStorage(`tldraw_debug:${name}`) ?? 'null')\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// process.env might not be defined, but we can't access it using optional\n// chaining because some bundlers search for `process.env.SOMETHING` as a string\n// and replace it with its value.\nfunction readEnv(fn: () => string | undefined) {\n\ttry {\n\t\treturn fn()\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction getDefaultValue<T>(def: DebugFlagDef<T>): T {\n\tconst env =\n\t\treadEnv(() => process.env.TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.VERCEL_PUBLIC_TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.NEXT_PUBLIC_TLDRAW_ENV) ??\n\t\t// default to production because if we don't have one of these, this is probably a library use\n\t\t'production'\n\n\tswitch (env) {\n\t\tcase 'production':\n\t\t\treturn def.defaults.production ?? def.defaults.all\n\t\tcase 'preview':\n\t\tcase 'staging':\n\t\t\treturn def.defaults.staging ?? def.defaults.all\n\t\tdefault:\n\t\t\treturn def.defaults.development ?? def.defaults.all\n\t}\n}\n\n/** @internal */\nexport interface DebugFlagDefaults<T> {\n\tdevelopment?: T\n\tstaging?: T\n\tproduction?: T\n\tall: T\n}\n\n/** @internal */\nexport interface DebugFlagDef<T> {\n\tname: string\n\tdefaults: DebugFlagDefaults<T>\n\tshouldStoreForSession: boolean\n}\n\n/** @internal */\nexport type DebugFlag<T> = DebugFlagDef<T> & Atom<T>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Atom, atom, react } from '@tldraw/state'\nimport { deleteFromSessionStorage, getFromSessionStorage, setInSessionStorage } from '@tldraw/utils'\n\n// --- 1. DEFINE ---\n//\n// Define your debug values and feature flags here. Use `createDebugValue` to\n// create an arbitrary value with defaults for production, staging, and\n// development. Use `createFeatureFlag` to create a boolean flag which will be\n// `true` by default in development and staging, and `false` in production.\n/** @internal */\nexport const featureFlags: Record<string, DebugFlag<boolean>> = {}\n\n/** @internal */\nexport const pointerCaptureTrackingObject = createDebugValue(\n\t'pointerCaptureTrackingObject',\n\t// ideally we wouldn't store this mutable value in an atom but it's not\n\t// a big deal for debug values\n\t{\n\t\tdefaults: { all: new Map<Element, number>() },\n\t\tshouldStoreForSession: false,\n\t}\n)\n\n/** @internal */\nexport const debugFlags = {\n\t// --- DEBUG VALUES ---\n\tlogPreventDefaults: createDebugValue('logPreventDefaults', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogPointerCaptures: createDebugValue('logPointerCaptures', {\n\t\tdefaults: { all: false },\n\t}),\n\tlogElementRemoves: createDebugValue('logElementRemoves', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugSvg: createDebugValue('debugSvg', {\n\t\tdefaults: { all: false },\n\t}),\n\tshowFps: createDebugValue('showFps', {\n\t\tdefaults: { all: false },\n\t}),\n\tmeasurePerformance: createDebugValue('measurePerformance', { defaults: { all: false } }),\n\tthrowToBlob: createDebugValue('throwToBlob', {\n\t\tdefaults: { all: false },\n\t}),\n\treconnectOnPing: createDebugValue('reconnectOnPing', {\n\t\tdefaults: { all: false },\n\t}),\n\tdebugCursors: createDebugValue('debugCursors', {\n\t\tdefaults: { all: false },\n\t}),\n\tforceSrgb: createDebugValue('forceSrgbColors', { defaults: { all: false } }),\n\tdebugGeometry: createDebugValue('debugGeometry', { defaults: { all: false } }),\n\thideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),\n\teditOnType: createDebugValue('editOnType', { defaults: { all: false } }),\n\ta11y: createDebugValue('a11y', { defaults: { all: false } }),\n\tdebugElbowArrows: createDebugValue('debugElbowArrows', { defaults: { all: false } }),\n} as const\n\ndeclare global {\n\tinterface Window {\n\t\ttldrawLog(message: any): void\n\t}\n}\n\n// --- 2. USE ---\n// In normal code, read from debug flags directly by calling .value on them:\n// if (debugFlags.preventDefaultLogging.value) { ... }\n//\n// In react, wrap your reads in `useValue` (or your component in `track`)\n// so they react to changes:\n// const shouldLog = useValue(debugFlags.preventDefaultLogging)\n\n// --- 3. GET FUNKY ---\n// If you need to do fun stuff like monkey-patching in response to flag changes,\n// add that here. Make sure you wrap your code in `react` so it runs\n// automatically when values change!\n\nif (typeof Element !== 'undefined') {\n\tconst nativeElementRemoveChild = Element.prototype.removeChild\n\treact('element removal logging', () => {\n\t\tif (debugFlags.logElementRemoves.get()) {\n\t\t\tElement.prototype.removeChild = function <T extends Node>(this: any, child: Node): T {\n\t\t\t\tconsole.warn('[tldraw] removing child:', child)\n\t\t\t\treturn nativeElementRemoveChild.call(this, child) as T\n\t\t\t}\n\t\t} else {\n\t\t\tElement.prototype.removeChild = nativeElementRemoveChild\n\t\t}\n\t})\n}\n\n// --- IMPLEMENTATION ---\n// you probably don't need to read this if you're just using the debug values system\nfunction createDebugValue<T>(\n\tname: string,\n\t{\n\t\tdefaults,\n\t\tshouldStoreForSession = true,\n\t}: { defaults: DebugFlagDefaults<T>; shouldStoreForSession?: boolean }\n) {\n\treturn createDebugValueBase({\n\t\tname,\n\t\tdefaults,\n\t\tshouldStoreForSession,\n\t})\n}\n\n// function createFeatureFlag<T>(\n// \tname: string,\n// \t{\n// \t\tdefaults,\n// \t\tshouldStoreForSession = true,\n// \t}: { defaults: DebugFlagDefaults<T>; shouldStoreForSession?: boolean }\n// ) {\n// \treturn createDebugValueBase({\n// \t\tname,\n// \t\tdefaults,\n// \t\tshouldStoreForSession,\n// \t})\n// }\n\nfunction createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {\n\tconst defaultValue = getDefaultValue(def)\n\tconst storedValue = def.shouldStoreForSession\n\t\t? (getStoredInitialValue(def.name) as T | null)\n\t\t: null\n\tconst valueAtom = atom(`debug:${def.name}`, storedValue ?? defaultValue)\n\n\tif (typeof window !== 'undefined') {\n\t\tif (def.shouldStoreForSession) {\n\t\t\treact(`debug:${def.name}`, () => {\n\t\t\t\tconst currentValue = valueAtom.get()\n\t\t\t\tif (currentValue === defaultValue) {\n\t\t\t\t\tdeleteFromSessionStorage(`tldraw_debug:${def.name}`)\n\t\t\t\t} else {\n\t\t\t\t\tsetInSessionStorage(`tldraw_debug:${def.name}`, JSON.stringify(currentValue))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tObject.defineProperty(window, `tldraw${def.name.replace(/^[a-z]/, (l) => l.toUpperCase())}`, {\n\t\t\tget() {\n\t\t\t\treturn valueAtom.get()\n\t\t\t},\n\t\t\tset(newValue) {\n\t\t\t\tvalueAtom.set(newValue)\n\t\t\t},\n\t\t\tconfigurable: true,\n\t\t})\n\t}\n\n\treturn Object.assign(valueAtom, def, {\n\t\treset: () => valueAtom.set(defaultValue),\n\t})\n}\n\nfunction getStoredInitialValue(name: string) {\n\ttry {\n\t\treturn JSON.parse(getFromSessionStorage(`tldraw_debug:${name}`) ?? 'null')\n\t} catch {\n\t\treturn null\n\t}\n}\n\n// process.env might not be defined, but we can't access it using optional\n// chaining because some bundlers search for `process.env.SOMETHING` as a string\n// and replace it with its value.\nfunction readEnv(fn: () => string | undefined) {\n\ttry {\n\t\treturn fn()\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction getDefaultValue<T>(def: DebugFlagDef<T>): T {\n\tconst env =\n\t\treadEnv(() => process.env.TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.VERCEL_PUBLIC_TLDRAW_ENV) ??\n\t\treadEnv(() => process.env.NEXT_PUBLIC_TLDRAW_ENV) ??\n\t\t// default to production because if we don't have one of these, this is probably a library use\n\t\t'production'\n\n\tswitch (env) {\n\t\tcase 'production':\n\t\t\treturn def.defaults.production ?? def.defaults.all\n\t\tcase 'preview':\n\t\tcase 'staging':\n\t\t\treturn def.defaults.staging ?? def.defaults.all\n\t\tdefault:\n\t\t\treturn def.defaults.development ?? def.defaults.all\n\t}\n}\n\n/** @internal */\nexport interface DebugFlagDefaults<T> {\n\tdevelopment?: T\n\tstaging?: T\n\tproduction?: T\n\tall: T\n}\n\n/** @internal */\nexport interface DebugFlagDef<T> {\n\tname: string\n\tdefaults: DebugFlagDefaults<T>\n\tshouldStoreForSession: boolean\n}\n\n/** @internal */\nexport interface DebugFlag<T> extends DebugFlagDef<T>, Atom<T> {\n\treset(): void\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "3.13.0-canary.faba5d60dae3";
1
+ const version = "3.13.0";
2
2
  const publishDates = {
3
3
  major: "2024-09-13T14:36:29.063Z",
4
- minor: "2025-05-01T15:09:57.860Z",
5
- patch: "2025-05-01T15:09:57.860Z"
4
+ minor: "2025-05-22T13:38:59.946Z",
5
+ patch: "2025-05-22T13:38:59.946Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.13.0-canary.faba5d60dae3'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-05-01T15:09:57.860Z',\n\tpatch: '2025-05-01T15:09:57.860Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.13.0'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-05-22T13:38:59.946Z',\n\tpatch: '2025-05-22T13:38:59.946Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/editor.css CHANGED
@@ -157,6 +157,7 @@
157
157
  --color-panel-contrast: hsl(0, 0%, 100%);
158
158
  --color-panel-overlay: hsl(0, 0%, 100%, 82%);
159
159
  --color-panel: hsl(0, 0%, 99%);
160
+ --color-panel-transparent: hsla(0, 0%, 99%, 0%);
160
161
  --color-focus: hsl(219, 65%, 50%);
161
162
  --color-selected: hsl(214, 84%, 56%);
162
163
  --color-selected-contrast: hsl(0, 0%, 100%);
@@ -208,6 +209,7 @@
208
209
  --color-panel-contrast: hsl(245, 12%, 23%);
209
210
  --color-panel: hsl(235, 6.8%, 13.5%);
210
211
  --color-panel-overlay: hsl(210, 10%, 24%, 82%);
212
+ --color-panel-transparent: hsla(235, 6.8%, 13.5%, 0%);
211
213
  --color-focus: hsl(217, 76%, 80%);
212
214
  --color-selected: hsl(217, 89%, 61%);
213
215
  --color-selected-contrast: hsl(0, 0%, 100%);
@@ -600,6 +602,36 @@ input,
600
602
  }
601
603
  }
602
604
 
605
+ .tl-rotate-corner:not(:hover),
606
+ .tl-resize-handle:not(:hover) {
607
+ cursor: none;
608
+ }
609
+
610
+ /* --------------------- Arrow Hints -------------------- */
611
+
612
+ .tl-arrow-hint-handle {
613
+ fill: var(--color-selected-contrast);
614
+ stroke: var(--color-selection-stroke);
615
+ stroke-width: calc(1.5px * var(--tl-scale));
616
+ r: calc(4px * var(--tl-scale));
617
+ }
618
+
619
+ .tl-arrow-hint-snap {
620
+ stroke: transparent;
621
+ fill: var(--color-selection-fill);
622
+ r: calc(12px * var(--tl-scale));
623
+ }
624
+
625
+ .tl-arrow-hint-snap__none,
626
+ .tl-arrow-hint-snap__center,
627
+ .tl-arrow-hint-snap__axis {
628
+ display: none;
629
+ }
630
+
631
+ .tl-arrow-hint-snap__edge {
632
+ r: calc(8px * var(--tl-scale));
633
+ }
634
+
603
635
  /* ------------------ Bounds Detail ----------------- */
604
636
 
605
637
  .tl-image,
@@ -969,6 +1001,8 @@ input,
969
1001
 
970
1002
  .tl-rich-text p {
971
1003
  margin: 0;
1004
+ /* Depending on the extensions, <p> tags can be empty, without a <br />. */
1005
+ min-height: 1lh;
972
1006
  }
973
1007
 
974
1008
  .tl-rich-text ul,
@@ -976,6 +1010,8 @@ input,
976
1010
  text-align: left;
977
1011
  margin: 0;
978
1012
  padding-left: 3.25ch;
1013
+ /* Some resets, like Tailwind, nix the list styling. */
1014
+ list-style: revert;
979
1015
  }
980
1016
 
981
1017
  .tl-rich-text ol:has(> li:nth-child(10)) {
@@ -1353,10 +1389,6 @@ input,
1353
1389
  opacity: 0;
1354
1390
  }
1355
1391
 
1356
- .tl-arrow-label[data-isediting='true'] > .tl-arrow-label__inner {
1357
- background-color: var(--color-background);
1358
- }
1359
-
1360
1392
  .tl-arrow-label__inner {
1361
1393
  border-radius: var(--radius-1);
1362
1394
  box-sizing: content-box;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "A tiny little drawing app (editor).",
4
- "version": "3.13.0-canary.faba5d60dae3",
4
+ "version": "3.13.0",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -48,12 +48,12 @@
48
48
  "@tiptap/core": "^2.9.1",
49
49
  "@tiptap/pm": "^2.9.1",
50
50
  "@tiptap/react": "^2.9.1",
51
- "@tldraw/state": "3.13.0-canary.faba5d60dae3",
52
- "@tldraw/state-react": "3.13.0-canary.faba5d60dae3",
53
- "@tldraw/store": "3.13.0-canary.faba5d60dae3",
54
- "@tldraw/tlschema": "3.13.0-canary.faba5d60dae3",
55
- "@tldraw/utils": "3.13.0-canary.faba5d60dae3",
56
- "@tldraw/validate": "3.13.0-canary.faba5d60dae3",
51
+ "@tldraw/state": "3.13.0",
52
+ "@tldraw/state-react": "3.13.0",
53
+ "@tldraw/store": "3.13.0",
54
+ "@tldraw/tlschema": "3.13.0",
55
+ "@tldraw/utils": "3.13.0",
56
+ "@tldraw/validate": "3.13.0",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
package/src/index.ts CHANGED
@@ -4,37 +4,11 @@ import 'core-js/stable/array/flat-map.js'
4
4
  import 'core-js/stable/array/flat.js'
5
5
  import 'core-js/stable/string/at.js'
6
6
  import 'core-js/stable/string/replace-all.js'
7
- export {
8
- EMPTY_ARRAY,
9
- EffectScheduler,
10
- atom,
11
- computed,
12
- react,
13
- transact,
14
- transaction,
15
- whyAmIRunning,
16
- type Atom,
17
- type Signal,
18
- } from '@tldraw/state'
19
- export {
20
- track,
21
- useAtom,
22
- useComputed,
23
- useQuickReactor,
24
- useReactor,
25
- useStateTracking,
26
- useValue,
27
- } from '@tldraw/state-react'
28
- export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
29
- export {
30
- getFontsFromRichText,
31
- type RichTextFontVisitor,
32
- type RichTextFontVisitorState,
33
- type TLTextOptions,
34
- type TiptapEditor,
35
- type TiptapNode,
36
- } from './lib/utils/richText'
37
- export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
7
+
8
+ // eslint-disable-next-line local/no-export-star
9
+ export * from '@tldraw/state'
10
+ // eslint-disable-next-line local/no-export-star
11
+ export * from '@tldraw/state-react'
38
12
  // eslint-disable-next-line local/no-export-star
39
13
  export * from '@tldraw/store'
40
14
  // eslint-disable-next-line local/no-export-star
@@ -43,6 +17,7 @@ export * from '@tldraw/tlschema'
43
17
  export * from '@tldraw/utils'
44
18
  // eslint-disable-next-line local/no-export-star
45
19
  export * from '@tldraw/validate'
20
+
46
21
  export {
47
22
  ErrorScreen,
48
23
  LoadingScreen,
@@ -212,6 +187,7 @@ export {
212
187
  export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
213
188
  export { getPerfectDashProps } from './lib/editor/shapes/shared/getPerfectDashProps'
214
189
  export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox'
190
+ export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
215
191
  export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
216
192
  export { maybeSnapToGrid } from './lib/editor/tools/BaseBoxShapeTool/children/Pointing'
217
193
  export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode'
@@ -459,12 +435,21 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
459
435
  export { isAccelKey } from './lib/utils/keyboard'
460
436
  export { normalizeWheel } from './lib/utils/normalizeWheel'
461
437
  export { refreshPage } from './lib/utils/refreshPage'
438
+ export {
439
+ getFontsFromRichText,
440
+ type RichTextFontVisitor,
441
+ type RichTextFontVisitorState,
442
+ type TLTextOptions,
443
+ type TiptapEditor,
444
+ type TiptapNode,
445
+ } from './lib/utils/richText'
462
446
  export {
463
447
  applyRotationToSnapshotShapes,
464
448
  getRotationSnapshot,
465
449
  type TLRotationSnapshot,
466
450
  } from './lib/utils/rotation'
467
451
  export { runtime, setRuntimeOverrides } from './lib/utils/runtime'
452
+ export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
468
453
  export { type TLStoreWithStatus } from './lib/utils/sync/StoreWithStatus'
469
454
  export { hardReset } from './lib/utils/sync/hardReset'
470
455
  export { uniq } from './lib/utils/uniq'
@@ -6,6 +6,7 @@ import { ShapeUtil } from '../editor/shapes/ShapeUtil'
6
6
  import { useEditor } from '../hooks/useEditor'
7
7
  import { useEditorComponents } from '../hooks/useEditorComponents'
8
8
  import { Mat } from '../primitives/Mat'
9
+ import { areShapesContentEqual } from '../utils/areShapesContentEqual'
9
10
  import { setStyleProperty } from '../utils/dom'
10
11
  import { OptionalErrorBoundary } from './ErrorBoundary'
11
12
 
@@ -190,10 +191,7 @@ export const InnerShape = memo(
190
191
  [util, shape.id]
191
192
  )
192
193
  },
193
- (prev, next) =>
194
- prev.shape.props === next.shape.props &&
195
- prev.shape.meta === next.shape.meta &&
196
- prev.util === next.util
194
+ (prev, next) => areShapesContentEqual(prev.shape, next.shape) && prev.util === next.util
197
195
  )
198
196
 
199
197
  export const InnerShapeBackground = memo(
@@ -37,7 +37,7 @@ export interface TLCanvasComponentProps {
37
37
  export function DefaultCanvas({ className }: TLCanvasComponentProps) {
38
38
  const editor = useEditor()
39
39
 
40
- const { Background, SvgDefs, ShapeIndicators } = useEditorComponents()
40
+ const { SelectionBackground, Background, SvgDefs, ShapeIndicators } = useEditorComponents()
41
41
 
42
42
  const rCanvas = useRef<HTMLDivElement>(null)
43
43
  const rHtmlLayer = useRef<HTMLDivElement>(null)
@@ -155,7 +155,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
155
155
  <GridWrapper />
156
156
  <div ref={rHtmlLayer} className="tl-html-layer tl-shapes" draggable={false}>
157
157
  <OnTheCanvasWrapper />
158
- <SelectionBackgroundWrapper />
158
+ {SelectionBackground && <SelectionBackgroundWrapper />}
159
159
  {hideShapes ? null : debugSvg ? <ShapesWithSVGs /> : <ShapesToDisplay />}
160
160
  </div>
161
161
  <div className="tl-overlays">
@@ -365,7 +365,8 @@ function HandleWrapper({
365
365
  return (
366
366
  <g
367
367
  role="button"
368
- aria-label="handle"
368
+ // TODO(mime): handle.label needs to be required in the future.
369
+ aria-label={handle.label || 'handle'}
369
370
  transform={`translate(${handle.x}, ${handle.y})`}
370
371
  {...events}
371
372
  >
@@ -575,9 +576,13 @@ function DebugSvgCopy({ id, mode }: { id: TLShapeId; mode: 'img' | 'iframe' }) {
575
576
 
576
577
  function SelectionForegroundWrapper() {
577
578
  const editor = useEditor()
578
- const selectionRotation = useValue('selection rotation', () => editor.getSelectionRotation(), [
579
- editor,
580
- ])
579
+ const selectionRotation = useValue(
580
+ 'selection rotation',
581
+ function getSelectionRotation() {
582
+ return editor.getSelectionRotation()
583
+ },
584
+ [editor]
585
+ )
581
586
  const selectionBounds = useValue(
582
587
  'selection bounds',
583
588
  () => editor.getSelectionRotatedPageBounds(),
@@ -9,13 +9,21 @@ import { useEditorComponents } from '../../hooks/useEditorComponents'
9
9
  import { OptionalErrorBoundary } from '../ErrorBoundary'
10
10
 
11
11
  // need an extra layer of indirection here to allow hooks to be used inside the indicator render
12
- const EvenInnererIndicator = memo(({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
13
- return useStateTracking('Indicator: ' + shape.type, () =>
14
- // always fetch the latest shape from the store even if the props/meta have not changed, to avoid
15
- // calling the render method with stale data.
16
- util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
17
- )
18
- })
12
+ const EvenInnererIndicator = memo(
13
+ ({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
14
+ return useStateTracking('Indicator: ' + shape.type, () =>
15
+ // always fetch the latest shape from the store even if the props/meta have not changed, to avoid
16
+ // calling the render method with stale data.
17
+ util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
18
+ )
19
+ },
20
+ (prevProps, nextProps) => {
21
+ return (
22
+ prevProps.shape.props === nextProps.shape.props &&
23
+ prevProps.shape.meta === nextProps.shape.meta
24
+ )
25
+ }
26
+ )
19
27
 
20
28
  const InnerIndicator = memo(({ editor, id }: { editor: Editor; id: TLShapeId }) => {
21
29
  const shape = useValue('shape for indicator', () => editor.store.get(id), [editor, id])
@@ -61,13 +69,14 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
61
69
  useQuickReactor(
62
70
  'indicator transform',
63
71
  () => {
72
+ if (hidden) return
64
73
  const elm = rIndicator.current
65
74
  if (!elm) return
66
75
  const pageTransform = editor.getShapePageTransform(shapeId)
67
76
  if (!pageTransform) return
68
77
  elm.style.setProperty('transform', pageTransform.toCssString())
69
78
  },
70
- [editor, shapeId]
79
+ [editor, shapeId, hidden]
71
80
  )
72
81
 
73
82
  useLayoutEffect(() => {
@@ -52,7 +52,7 @@ beforeEach(() => {
52
52
  shapeUtils: [CustomShape],
53
53
  bindingUtils: [],
54
54
  tools: [],
55
- store: createTLStore({ shapeUtils: [CustomShape] }),
55
+ store: createTLStore({ shapeUtils: [CustomShape], bindingUtils: [] }),
56
56
  getContainer: () => document.body,
57
57
  })
58
58
  editor.setCameraOptions({ isLocked: true })
@@ -129,6 +129,7 @@ import { Group2d } from '../primitives/geometry/Group2d'
129
129
  import { intersectPolygonPolygon } from '../primitives/intersect'
130
130
  import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
131
131
  import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
132
+ import { areShapesContentEqual } from '../utils/areShapesContentEqual'
132
133
  import { dataUrlToFile } from '../utils/assets'
133
134
  import { debugFlags } from '../utils/debug-flags'
134
135
  import {
@@ -325,7 +326,6 @@ export class Editor extends EventEmitter<TLEventMap> {
325
326
  this.options = { ...defaultTldrawOptions, ...options }
326
327
 
327
328
  this.store = store
328
- this.disposables.add(this.store.dispose.bind(this.store))
329
329
  this.history = new HistoryManager<TLRecord>({
330
330
  store,
331
331
  annotateError: (error) => {
@@ -955,6 +955,7 @@ export class Editor extends EventEmitter<TLEventMap> {
955
955
  dispose() {
956
956
  this.disposables.forEach((dispose) => dispose())
957
957
  this.disposables.clear()
958
+ this.store.dispose()
958
959
  this.isDisposed = true
959
960
  }
960
961
 
@@ -1814,9 +1815,28 @@ export class Editor extends EventEmitter<TLEventMap> {
1814
1815
  return this
1815
1816
  }
1816
1817
 
1818
+ /**
1819
+ * Select the next shape in the reading order or in cardinal order.
1820
+ *
1821
+ * @example
1822
+ * ```ts
1823
+ * editor.selectAdjacentShape('next')
1824
+ * ```
1825
+ *
1826
+ * @public
1827
+ */
1817
1828
  selectAdjacentShape(direction: TLAdjacentDirection) {
1818
- const readingOrderShapes = this.getCurrentPageShapesInReadingOrder()
1819
1829
  const selectedShapeIds = this.getSelectedShapeIds()
1830
+ const firstParentId = selectedShapeIds[0] ? this.getShape(selectedShapeIds[0])?.parentId : null
1831
+ const isSelectedWithinContainer =
1832
+ firstParentId &&
1833
+ selectedShapeIds.every((shapeId) => this.getShape(shapeId)?.parentId === firstParentId) &&
1834
+ !isPageId(firstParentId)
1835
+ const readingOrderShapes = isSelectedWithinContainer
1836
+ ? this._getShapesInReadingOrder(
1837
+ this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
1838
+ )
1839
+ : this.getCurrentPageShapesInReadingOrder()
1820
1840
  const currentShapeId: TLShapeId | undefined =
1821
1841
  selectedShapeIds.length === 1
1822
1842
  ? selectedShapeIds[0]
@@ -1838,13 +1858,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1838
1858
  const shape = this.getShape(adjacentShapeId)
1839
1859
  if (!shape) return
1840
1860
 
1841
- this.setSelectedShapes([shape.id])
1842
- this.zoomToSelectionIfOffscreen(256, {
1843
- animation: {
1844
- duration: this.options.animationMediumMs,
1845
- },
1846
- inset: 0,
1847
- })
1861
+ this._selectShapesAndZoom([shape.id])
1848
1862
  }
1849
1863
 
1850
1864
  /**
@@ -1854,10 +1868,14 @@ export class Editor extends EventEmitter<TLEventMap> {
1854
1868
  * @public
1855
1869
  */
1856
1870
  @computed getCurrentPageShapesInReadingOrder(): TLShape[] {
1871
+ const shapes = this.getCurrentPageShapes().filter((shape) => isPageId(shape.parentId))
1872
+ return this._getShapesInReadingOrder(shapes)
1873
+ }
1874
+
1875
+ private _getShapesInReadingOrder(shapes: TLShape[]): TLShape[] {
1857
1876
  const SHALLOW_ANGLE = 20
1858
1877
  const ROW_THRESHOLD = 100
1859
1878
 
1860
- const shapes = this.getCurrentPageShapes()
1861
1879
  const tabbableShapes = shapes.filter((shape) => this.getShapeUtil(shape).canTabTo(shape))
1862
1880
 
1863
1881
  if (tabbableShapes.length <= 1) return tabbableShapes
@@ -2003,6 +2021,36 @@ export class Editor extends EventEmitter<TLEventMap> {
2003
2021
  return lowestScoringShape!.shape.id
2004
2022
  }
2005
2023
 
2024
+ selectParentShape() {
2025
+ const selectedShape = this.getOnlySelectedShape()
2026
+ if (!selectedShape) return
2027
+ const parentShape = this.getShape(selectedShape.parentId)
2028
+ if (!parentShape) return
2029
+ this._selectShapesAndZoom([parentShape.id])
2030
+ }
2031
+
2032
+ selectFirstChildShape() {
2033
+ const selectedShapes = this.getSelectedShapes()
2034
+ if (!selectedShapes.length) return
2035
+ const selectedShape = selectedShapes[0]
2036
+ const children = this.getSortedChildIdsForParent(selectedShape.id)
2037
+ .map((id) => this.getShape(id))
2038
+ .filter((i) => i) as TLShape[]
2039
+ const sortedChildren = this._getShapesInReadingOrder(children)
2040
+ if (sortedChildren.length === 0) return
2041
+ this._selectShapesAndZoom([sortedChildren[0].id])
2042
+ }
2043
+
2044
+ private _selectShapesAndZoom(ids: TLShapeId[]) {
2045
+ this.setSelectedShapes(ids)
2046
+ this.zoomToSelectionIfOffscreen(256, {
2047
+ animation: {
2048
+ duration: this.options.animationMediumMs,
2049
+ },
2050
+ inset: 0,
2051
+ })
2052
+ }
2053
+
2006
2054
  /**
2007
2055
  * Clear the selection.
2008
2056
  *
@@ -2275,13 +2323,21 @@ export class Editor extends EventEmitter<TLEventMap> {
2275
2323
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2276
2324
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2277
2325
  this.setRichTextEditor(null)
2278
- if (id !== this.getEditingShapeId()) {
2326
+ const prevEditingShapeId = this.getEditingShapeId()
2327
+ if (id !== prevEditingShapeId) {
2279
2328
  if (id) {
2280
2329
  const shape = this.getShape(id)
2281
2330
  if (shape && this.getShapeUtil(shape).canEdit(shape)) {
2282
2331
  this.run(
2283
2332
  () => {
2284
2333
  this._updateCurrentPageState({ editingShapeId: id })
2334
+ if (prevEditingShapeId) {
2335
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2336
+ if (prevEditingShape) {
2337
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2338
+ }
2339
+ }
2340
+ this.getShapeUtil(shape).onEditStart?.(shape)
2285
2341
  },
2286
2342
  { history: 'ignore' }
2287
2343
  )
@@ -2294,6 +2350,12 @@ export class Editor extends EventEmitter<TLEventMap> {
2294
2350
  () => {
2295
2351
  this._updateCurrentPageState({ editingShapeId: null })
2296
2352
  this._currentRichTextEditor.set(null)
2353
+ if (prevEditingShapeId) {
2354
+ const prevEditingShape = this.getShape(prevEditingShapeId)
2355
+ if (prevEditingShape) {
2356
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
2357
+ }
2358
+ }
2297
2359
  },
2298
2360
  { history: 'ignore' }
2299
2361
  )
@@ -4574,7 +4636,7 @@ export class Editor extends EventEmitter<TLEventMap> {
4574
4636
  this.fonts.trackFontsForShape(shape)
4575
4637
  return this.getShapeUtil(shape).getGeometry(shape, opts)
4576
4638
  },
4577
- { areRecordsEqual: (a, b) => a.props === b.props }
4639
+ { areRecordsEqual: areShapesContentEqual }
4578
4640
  )
4579
4641
  }
4580
4642
  return this._shapeGeometryCaches[context].get(
@@ -4622,9 +4684,15 @@ export class Editor extends EventEmitter<TLEventMap> {
4622
4684
 
4623
4685
  /** @internal */
4624
4686
  @computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
4625
- return this.store.createComputedCache('handles', (shape) => {
4626
- return this.getShapeUtil(shape).getHandles?.(shape)
4627
- })
4687
+ return this.store.createComputedCache(
4688
+ 'handles',
4689
+ (shape) => {
4690
+ return this.getShapeUtil(shape).getHandles?.(shape)
4691
+ },
4692
+ {
4693
+ areRecordsEqual: areShapesContentEqual,
4694
+ }
4695
+ )
4628
4696
  }
4629
4697
 
4630
4698
  /**
@@ -5845,9 +5913,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5845
5913
  @computed
5846
5914
  private _getBindingsIndexCache() {
5847
5915
  const index = bindingsIndex(this)
5848
- return this.store.createComputedCache<TLBinding[], TLShape>('bindingsIndex', (shape) => {
5849
- return index.get().get(shape.id)
5850
- })
5916
+ return this.store.createComputedCache<TLBinding[], TLShape>(
5917
+ 'bindingsIndex',
5918
+ (shape) => {
5919
+ return index.get().get(shape.id)
5920
+ },
5921
+ // we can ignore the shape equality check here because the index is
5922
+ // computed incrementally based on what bindings are in the store
5923
+ { areRecordsEqual: () => true }
5924
+ )
5851
5925
  }
5852
5926
 
5853
5927
  /**
@@ -10214,7 +10288,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10214
10288
 
10215
10289
  // If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
10216
10290
  // If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
10217
- if (inputs.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
10291
+ if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
10218
10292
 
10219
10293
  switch (behavior) {
10220
10294
  case 'zoom': {
@@ -10330,12 +10404,10 @@ export class Editor extends EventEmitter<TLEventMap> {
10330
10404
  if (this.inputs.isPanning && this.inputs.isPointing) {
10331
10405
  // Handle spacebar / middle mouse button panning
10332
10406
  const { currentScreenPoint, previousScreenPoint } = this.inputs
10333
- const { panSpeed } = cameraOptions
10334
10407
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
10335
- this.setCamera(
10336
- new Vec(cx + (offset.x * panSpeed) / cz, cy + (offset.y * panSpeed) / cz, cz),
10337
- { immediate: true }
10338
- )
10408
+ this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
10409
+ immediate: true,
10410
+ })
10339
10411
  this.maybeTrackPerformance('Panning')
10340
10412
  return
10341
10413
  }
@@ -45,7 +45,6 @@ export interface HandleSnapGeometry {
45
45
 
46
46
  const defaultGetSelfSnapOutline = () => null
47
47
  const defaultGetSelfSnapPoints = () => []
48
-
49
48
  /** @public */
50
49
  export class HandleSnaps {
51
50
  readonly editor: Editor
@@ -32,6 +32,7 @@ export interface TLMeasureTextSpanOpts {
32
32
  fontStyle: string
33
33
  lineHeight: number
34
34
  textAlign: TLDefaultHorizontalAlignStyle
35
+ otherStyles?: Record<string, string>
35
36
  }
36
37
 
37
38
  const spaceCharacterRegex = /\s/
@@ -86,6 +87,7 @@ export class TextManager {
86
87
  */
87
88
  maxWidth: null | number
88
89
  minWidth?: null | number
90
+ otherStyles?: Record<string, string>
89
91
  padding: string
90
92
  disableOverflowWrapBreaking?: boolean
91
93
  }
@@ -112,6 +114,11 @@ export class TextManager {
112
114
  'overflow-wrap',
113
115
  opts.disableOverflowWrapBreaking ? 'normal' : 'break-word'
114
116
  )
117
+ if (opts.otherStyles) {
118
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
119
+ wrapperElm.style.setProperty(key, value)
120
+ }
121
+ }
115
122
 
116
123
  const scrollWidth = wrapperElm.scrollWidth
117
124
  const rect = wrapperElm.getBoundingClientRect()
@@ -256,6 +263,11 @@ export class TextManager {
256
263
  elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
257
264
  elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
258
265
  elm.style.setProperty('font-style', opts.fontStyle)
266
+ if (opts.otherStyles) {
267
+ for (const [key, value] of Object.entries(opts.otherStyles)) {
268
+ elm.style.setProperty(key, value)
269
+ }
270
+ }
259
271
 
260
272
  const shouldTruncateToFirstLine =
261
273
  opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'