@tldraw/editor 3.14.0-canary.d15c939851b6 → 3.14.0-canary.d1b8a584b27c

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 (56) hide show
  1. package/dist-cjs/index.d.ts +13 -96
  2. package/dist-cjs/index.js +1 -4
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +24 -57
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +1 -3
  7. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  8. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +10 -0
  9. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +6 -13
  11. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
  12. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  13. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +2 -6
  14. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  15. package/dist-cjs/lib/primitives/geometry/Group2d.js +6 -11
  16. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  17. package/dist-cjs/lib/utils/dom.js +1 -1
  18. package/dist-cjs/lib/utils/dom.js.map +2 -2
  19. package/dist-cjs/version.js +3 -3
  20. package/dist-cjs/version.js.map +1 -1
  21. package/dist-esm/index.d.mts +13 -96
  22. package/dist-esm/index.mjs +1 -4
  23. package/dist-esm/index.mjs.map +2 -2
  24. package/dist-esm/lib/editor/Editor.mjs +24 -57
  25. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  26. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +1 -3
  27. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  28. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +10 -0
  29. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  30. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +6 -13
  31. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  32. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +2 -6
  33. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  34. package/dist-esm/lib/primitives/geometry/Group2d.mjs +6 -11
  35. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  36. package/dist-esm/lib/utils/dom.mjs +1 -1
  37. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  38. package/dist-esm/version.mjs +3 -3
  39. package/dist-esm/version.mjs.map +1 -1
  40. package/editor.css +11 -17
  41. package/package.json +7 -7
  42. package/src/index.ts +0 -5
  43. package/src/lib/editor/Editor.ts +35 -75
  44. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +1 -3
  45. package/src/lib/editor/shapes/ShapeUtil.ts +15 -46
  46. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +17 -25
  47. package/src/lib/editor/types/emit-types.ts +0 -4
  48. package/src/lib/primitives/geometry/Geometry2d.ts +2 -7
  49. package/src/lib/primitives/geometry/Group2d.ts +5 -11
  50. package/src/lib/utils/dom.ts +1 -1
  51. package/src/version.ts +3 -3
  52. package/dist-cjs/lib/utils/reparenting.js +0 -232
  53. package/dist-cjs/lib/utils/reparenting.js.map +0 -7
  54. package/dist-esm/lib/utils/reparenting.mjs +0 -216
  55. package/dist-esm/lib/utils/reparenting.mjs.map +0 -7
  56. package/src/lib/utils/reparenting.ts +0 -383
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/dom.ts"],
4
- "sourcesContent": ["/*\nThis is used to facilitate double clicking and pointer capture on elements.\n\nThe events in this file are possibly set on individual SVG elements, \nsuch as handles or corner handles, rather than on HTML elements or \nSVGSVGElements. Raw SVG elemnets do not support pointerCapture in \nmost cases, meaning that in order for pointer capture to work, we \nneed to crawl up the DOM tree to find the nearest HTML element. Then,\nin order for that element to also call the `onPointerUp` event from\nthis file, we need to manually set that event on that element and\nlater remove it when the pointerup occurs. This is a potential leak\nif the user clicks on a handle but the pointerup does not fire for\nwhatever reason.\n*/\n\nimport React from 'react'\nimport { debugFlags, pointerCaptureTrackingObject } from './debug-flags'\n\n/** @public */\nexport function loopToHtmlElement(elm: Element): HTMLElement {\n\tif (elm.nodeType === Node.ELEMENT_NODE) return elm as HTMLElement\n\tif (elm.parentElement) return loopToHtmlElement(elm.parentElement)\n\telse throw Error('Could not find a parent element of an HTML type!')\n}\n\n/**\n * This function calls `event.preventDefault()` for you. Why is that useful?\n *\n * Beacuase if you enable `window.preventDefaultLogging = true` it'll log out a message when it\n * happens. Because we use console.warn rather than (log) you'll get a stack trace in the inspector\n * telling you exactly where it happened. This is important because `e.preventDefault()` is the\n * source of many bugs, but unfortuantly it can't be avoided because it also stops a lot of default\n * behaviour which doesn't make sense in our UI\n *\n * @param event - To prevent default on\n * @public\n */\nexport function preventDefault(event: React.BaseSyntheticEvent | Event) {\n\tevent.preventDefault()\n\tif (debugFlags.logPreventDefaults.get()) {\n\t\tconsole.warn('preventDefault called on event:', event)\n\t}\n}\n\n/** @public */\nexport function setPointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\telement.setPointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\ttrackingObj.set(element, (trackingObj.get(element) ?? 0) + 1)\n\t\tconsole.warn('setPointerCapture called on element:', element, event)\n\t}\n}\n\n/** @public */\nexport function releasePointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\tif (!element.hasPointerCapture(event.pointerId)) {\n\t\treturn\n\t}\n\n\telement.releasePointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\tif (trackingObj.get(element) === 1) {\n\t\t\ttrackingObj.delete(element)\n\t\t} else if (trackingObj.has(element)) {\n\t\t\ttrackingObj.set(element, trackingObj.get(element)! - 1)\n\t\t} else {\n\t\t\tconsole.warn('Release without capture')\n\t\t}\n\t\tconsole.warn('releasePointerCapture called on element:', element, event)\n\t}\n}\n\n/** @public */\nexport const stopEventPropagation = (e: any) => e.stopPropagation()\n\n/** @internal */\nexport const setStyleProperty = (\n\telm: HTMLElement | null,\n\tproperty: string,\n\tvalue: string | number\n) => {\n\tif (!elm) return\n\telm.style.setProperty(property, value as string)\n}\n\n/** @internal */\nexport function activeElementShouldCaptureKeys(allowButtons = false) {\n\tconst { activeElement } = document\n\tconst elements = allowButtons ? ['input', 'textarea'] : ['input', 'select', 'button', 'textarea']\n\treturn !!(\n\t\tactiveElement &&\n\t\t((activeElement as HTMLElement).isContentEditable ||\n\t\t\telements.indexOf(activeElement.tagName.toLowerCase()) > -1 ||\n\t\t\tactiveElement.classList.contains('tlui-slider__thumb'))\n\t)\n}\n"],
5
- "mappings": "AAgBA,SAAS,YAAY,oCAAoC;AAGlD,SAAS,kBAAkB,KAA2B;AAC5D,MAAI,IAAI,aAAa,KAAK,aAAc,QAAO;AAC/C,MAAI,IAAI,cAAe,QAAO,kBAAkB,IAAI,aAAa;AAAA,MAC5D,OAAM,MAAM,kDAAkD;AACpE;AAcO,SAAS,eAAe,OAAyC;AACvE,QAAM,eAAe;AACrB,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,YAAQ,KAAK,mCAAmC,KAAK;AAAA,EACtD;AACD;AAGO,SAAS,kBACf,SACA,OACC;AACD,UAAQ,kBAAkB,MAAM,SAAS;AACzC,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,gBAAY,IAAI,UAAU,YAAY,IAAI,OAAO,KAAK,KAAK,CAAC;AAC5D,YAAQ,KAAK,wCAAwC,SAAS,KAAK;AAAA,EACpE;AACD;AAGO,SAAS,sBACf,SACA,OACC;AACD,MAAI,CAAC,QAAQ,kBAAkB,MAAM,SAAS,GAAG;AAChD;AAAA,EACD;AAEA,UAAQ,sBAAsB,MAAM,SAAS;AAC7C,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,QAAI,YAAY,IAAI,OAAO,MAAM,GAAG;AACnC,kBAAY,OAAO,OAAO;AAAA,IAC3B,WAAW,YAAY,IAAI,OAAO,GAAG;AACpC,kBAAY,IAAI,SAAS,YAAY,IAAI,OAAO,IAAK,CAAC;AAAA,IACvD,OAAO;AACN,cAAQ,KAAK,yBAAyB;AAAA,IACvC;AACA,YAAQ,KAAK,4CAA4C,SAAS,KAAK;AAAA,EACxE;AACD;AAGO,MAAM,uBAAuB,CAAC,MAAW,EAAE,gBAAgB;AAG3D,MAAM,mBAAmB,CAC/B,KACA,UACA,UACI;AACJ,MAAI,CAAC,IAAK;AACV,MAAI,MAAM,YAAY,UAAU,KAAe;AAChD;AAGO,SAAS,+BAA+B,eAAe,OAAO;AACpE,QAAM,EAAE,cAAc,IAAI;AAC1B,QAAM,WAAW,eAAe,CAAC,SAAS,UAAU,IAAI,CAAC,SAAS,UAAU,UAAU,UAAU;AAChG,SAAO,CAAC,EACP,kBACE,cAA8B,qBAC/B,SAAS,QAAQ,cAAc,QAAQ,YAAY,CAAC,IAAI,MACxD,cAAc,UAAU,SAAS,oBAAoB;AAExD;",
4
+ "sourcesContent": ["/*\nThis is used to facilitate double clicking and pointer capture on elements.\n\nThe events in this file are possibly set on individual SVG elements, \nsuch as handles or corner handles, rather than on HTML elements or \nSVGSVGElements. Raw SVG elemnets do not support pointerCapture in \nmost cases, meaning that in order for pointer capture to work, we \nneed to crawl up the DOM tree to find the nearest HTML element. Then,\nin order for that element to also call the `onPointerUp` event from\nthis file, we need to manually set that event on that element and\nlater remove it when the pointerup occurs. This is a potential leak\nif the user clicks on a handle but the pointerup does not fire for\nwhatever reason.\n*/\n\nimport React from 'react'\nimport { debugFlags, pointerCaptureTrackingObject } from './debug-flags'\n\n/** @public */\nexport function loopToHtmlElement(elm: Element): HTMLElement {\n\tif (elm instanceof HTMLElement) return elm\n\tif (elm.parentElement) return loopToHtmlElement(elm.parentElement)\n\telse throw Error('Could not find a parent element of an HTML type!')\n}\n\n/**\n * This function calls `event.preventDefault()` for you. Why is that useful?\n *\n * Beacuase if you enable `window.preventDefaultLogging = true` it'll log out a message when it\n * happens. Because we use console.warn rather than (log) you'll get a stack trace in the inspector\n * telling you exactly where it happened. This is important because `e.preventDefault()` is the\n * source of many bugs, but unfortuantly it can't be avoided because it also stops a lot of default\n * behaviour which doesn't make sense in our UI\n *\n * @param event - To prevent default on\n * @public\n */\nexport function preventDefault(event: React.BaseSyntheticEvent | Event) {\n\tevent.preventDefault()\n\tif (debugFlags.logPreventDefaults.get()) {\n\t\tconsole.warn('preventDefault called on event:', event)\n\t}\n}\n\n/** @public */\nexport function setPointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\telement.setPointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\ttrackingObj.set(element, (trackingObj.get(element) ?? 0) + 1)\n\t\tconsole.warn('setPointerCapture called on element:', element, event)\n\t}\n}\n\n/** @public */\nexport function releasePointerCapture(\n\telement: Element,\n\tevent: React.PointerEvent<Element> | PointerEvent\n) {\n\tif (!element.hasPointerCapture(event.pointerId)) {\n\t\treturn\n\t}\n\n\telement.releasePointerCapture(event.pointerId)\n\tif (debugFlags.logPointerCaptures.get()) {\n\t\tconst trackingObj = pointerCaptureTrackingObject.get()\n\t\tif (trackingObj.get(element) === 1) {\n\t\t\ttrackingObj.delete(element)\n\t\t} else if (trackingObj.has(element)) {\n\t\t\ttrackingObj.set(element, trackingObj.get(element)! - 1)\n\t\t} else {\n\t\t\tconsole.warn('Release without capture')\n\t\t}\n\t\tconsole.warn('releasePointerCapture called on element:', element, event)\n\t}\n}\n\n/** @public */\nexport const stopEventPropagation = (e: any) => e.stopPropagation()\n\n/** @internal */\nexport const setStyleProperty = (\n\telm: HTMLElement | null,\n\tproperty: string,\n\tvalue: string | number\n) => {\n\tif (!elm) return\n\telm.style.setProperty(property, value as string)\n}\n\n/** @internal */\nexport function activeElementShouldCaptureKeys(allowButtons = false) {\n\tconst { activeElement } = document\n\tconst elements = allowButtons ? ['input', 'textarea'] : ['input', 'select', 'button', 'textarea']\n\treturn !!(\n\t\tactiveElement &&\n\t\t((activeElement as HTMLElement).isContentEditable ||\n\t\t\telements.indexOf(activeElement.tagName.toLowerCase()) > -1 ||\n\t\t\tactiveElement.classList.contains('tlui-slider__thumb'))\n\t)\n}\n"],
5
+ "mappings": "AAgBA,SAAS,YAAY,oCAAoC;AAGlD,SAAS,kBAAkB,KAA2B;AAC5D,MAAI,eAAe,YAAa,QAAO;AACvC,MAAI,IAAI,cAAe,QAAO,kBAAkB,IAAI,aAAa;AAAA,MAC5D,OAAM,MAAM,kDAAkD;AACpE;AAcO,SAAS,eAAe,OAAyC;AACvE,QAAM,eAAe;AACrB,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,YAAQ,KAAK,mCAAmC,KAAK;AAAA,EACtD;AACD;AAGO,SAAS,kBACf,SACA,OACC;AACD,UAAQ,kBAAkB,MAAM,SAAS;AACzC,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,gBAAY,IAAI,UAAU,YAAY,IAAI,OAAO,KAAK,KAAK,CAAC;AAC5D,YAAQ,KAAK,wCAAwC,SAAS,KAAK;AAAA,EACpE;AACD;AAGO,SAAS,sBACf,SACA,OACC;AACD,MAAI,CAAC,QAAQ,kBAAkB,MAAM,SAAS,GAAG;AAChD;AAAA,EACD;AAEA,UAAQ,sBAAsB,MAAM,SAAS;AAC7C,MAAI,WAAW,mBAAmB,IAAI,GAAG;AACxC,UAAM,cAAc,6BAA6B,IAAI;AACrD,QAAI,YAAY,IAAI,OAAO,MAAM,GAAG;AACnC,kBAAY,OAAO,OAAO;AAAA,IAC3B,WAAW,YAAY,IAAI,OAAO,GAAG;AACpC,kBAAY,IAAI,SAAS,YAAY,IAAI,OAAO,IAAK,CAAC;AAAA,IACvD,OAAO;AACN,cAAQ,KAAK,yBAAyB;AAAA,IACvC;AACA,YAAQ,KAAK,4CAA4C,SAAS,KAAK;AAAA,EACxE;AACD;AAGO,MAAM,uBAAuB,CAAC,MAAW,EAAE,gBAAgB;AAG3D,MAAM,mBAAmB,CAC/B,KACA,UACA,UACI;AACJ,MAAI,CAAC,IAAK;AACV,MAAI,MAAM,YAAY,UAAU,KAAe;AAChD;AAGO,SAAS,+BAA+B,eAAe,OAAO;AACpE,QAAM,EAAE,cAAc,IAAI;AAC1B,QAAM,WAAW,eAAe,CAAC,SAAS,UAAU,IAAI,CAAC,SAAS,UAAU,UAAU,UAAU;AAChG,SAAO,CAAC,EACP,kBACE,cAA8B,qBAC/B,SAAS,QAAQ,cAAc,QAAQ,YAAY,CAAC,IAAI,MACxD,cAAc,UAAU,SAAS,oBAAoB;AAExD;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "3.14.0-canary.d15c939851b6";
1
+ const version = "3.14.0-canary.d1b8a584b27c";
2
2
  const publishDates = {
3
3
  major: "2024-09-13T14:36:29.063Z",
4
- minor: "2025-07-01T13:15:12.958Z",
5
- patch: "2025-07-01T13:15:12.958Z"
4
+ minor: "2025-06-23T14:58:35.827Z",
5
+ patch: "2025-06-23T14:58:35.827Z"
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.14.0-canary.d15c939851b6'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-07-01T13:15:12.958Z',\n\tpatch: '2025-07-01T13:15:12.958Z',\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.14.0-canary.d1b8a584b27c'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-06-23T14:58:35.827Z',\n\tpatch: '2025-06-23T14:58:35.827Z',\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
@@ -176,7 +176,8 @@
176
176
  --color-success: hsl(123, 46%, 34%);
177
177
  --color-info: hsl(201, 98%, 41%);
178
178
  --color-warning: hsl(27, 98%, 47%);
179
- --color-danger: hsl(0, 90%, 43%);
179
+ --color-error: hsl(0, 65%, 51%);
180
+ --color-warn: hsl(0, 90%, 43%);
180
181
  --color-laser: hsl(0, 100%, 50%);
181
182
  /* Shadows */
182
183
  --shadow-1: 0px 1px 2px hsl(0, 0%, 0%, 25%), 0px 1px 3px hsl(0, 0%, 0%, 9%);
@@ -231,7 +232,8 @@
231
232
  --color-success: hsl(123, 38%, 57%);
232
233
  --color-info: hsl(199, 92%, 56%);
233
234
  --color-warning: hsl(36, 100%, 57%);
234
- --color-danger: hsl(0, 82%, 66%);
235
+ --color-error: hsl(4, 90%, 58%);
236
+ --color-warn: hsl(0, 81%, 66%);
235
237
  --color-laser: hsl(0, 100%, 50%);
236
238
  /* Shadows */
237
239
  --shadow-1:
@@ -245,13 +247,6 @@
245
247
  inset 0px 0px 0px 1px var(--color-panel-contrast);
246
248
  }
247
249
 
248
- .tl-counter-scaled {
249
- transform: scale(var(--tl-scale));
250
- transform-origin: top left;
251
- width: calc(100% * var(--tl-zoom));
252
- height: calc(100% * var(--tl-zoom));
253
- }
254
-
255
250
  .tl-container,
256
251
  .tl-container * {
257
252
  -webkit-touch-callout: none;
@@ -1062,8 +1057,8 @@ input,
1062
1057
  }
1063
1058
 
1064
1059
  .tl-hyperlink__icon {
1065
- width: 15px;
1066
- height: 15px;
1060
+ width: 16px;
1061
+ height: 16px;
1067
1062
  background-color: currentColor;
1068
1063
  pointer-events: none;
1069
1064
  }
@@ -1162,7 +1157,7 @@ input,
1162
1157
  stroke-linejoin: round;
1163
1158
  /* content-visibility: auto; */
1164
1159
  transform-origin: top left;
1165
- color: var(--color-text-1);
1160
+ color: inherit;
1166
1161
  }
1167
1162
 
1168
1163
  /* -------------------- Group shape ------------------ */
@@ -1277,7 +1272,6 @@ input,
1277
1272
  display: flex;
1278
1273
  justify-content: flex-end;
1279
1274
  align-items: flex-start;
1280
- box-shadow: inset 0px 0px 0px 1px var(--color-divider);
1281
1275
  }
1282
1276
 
1283
1277
  .tl-bookmark__image_container > .tl-hyperlink-button::after {
@@ -1300,7 +1294,7 @@ input,
1300
1294
  }
1301
1295
 
1302
1296
  .tl-bookmark__copy_container {
1303
- background-color: var(--color-muted-0);
1297
+ background-color: var(--color-muted);
1304
1298
  padding: var(--space-4);
1305
1299
  pointer-events: all;
1306
1300
  display: flex;
@@ -1319,11 +1313,11 @@ input,
1319
1313
 
1320
1314
  .tl-bookmark__heading {
1321
1315
  font-size: 16px;
1322
- line-height: 1.6;
1316
+ line-height: 1.5;
1323
1317
  font-weight: bold;
1324
1318
  padding-bottom: var(--space-2);
1325
1319
  overflow: hidden;
1326
- max-height: calc((16px * 1.6) * 2);
1320
+ max-height: calc((16px * 1.5) * 2);
1327
1321
  -webkit-box-orient: vertical;
1328
1322
  -webkit-line-clamp: 2;
1329
1323
  line-clamp: 2;
@@ -1712,7 +1706,7 @@ it from receiving any pointer events or affecting the cursor. */
1712
1706
  gap: var(--space-4);
1713
1707
  }
1714
1708
  .tl-error-boundary__content .tl-error-boundary__reset {
1715
- color: var(--color-danger);
1709
+ color: var(--color-warn);
1716
1710
  }
1717
1711
  .tl-error-boundary__content .tl-error-boundary__refresh {
1718
1712
  background-color: var(--color-primary);
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.14.0-canary.d15c939851b6",
4
+ "version": "3.14.0-canary.d1b8a584b27c",
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.14.0-canary.d15c939851b6",
52
- "@tldraw/state-react": "3.14.0-canary.d15c939851b6",
53
- "@tldraw/store": "3.14.0-canary.d15c939851b6",
54
- "@tldraw/tlschema": "3.14.0-canary.d15c939851b6",
55
- "@tldraw/utils": "3.14.0-canary.d15c939851b6",
56
- "@tldraw/validate": "3.14.0-canary.d15c939851b6",
51
+ "@tldraw/state": "3.14.0-canary.d1b8a584b27c",
52
+ "@tldraw/state-react": "3.14.0-canary.d1b8a584b27c",
53
+ "@tldraw/store": "3.14.0-canary.d1b8a584b27c",
54
+ "@tldraw/tlschema": "3.14.0-canary.d1b8a584b27c",
55
+ "@tldraw/utils": "3.14.0-canary.d1b8a584b27c",
56
+ "@tldraw/validate": "3.14.0-canary.d1b8a584b27c",
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
@@ -182,10 +182,6 @@ export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseB
182
182
  export {
183
183
  ShapeUtil,
184
184
  type TLCropInfo,
185
- type TLDragShapesInInfo,
186
- type TLDragShapesOutInfo,
187
- type TLDragShapesOverInfo,
188
- type TLDropShapesOverInfo,
189
185
  type TLGeometryOpts,
190
186
  type TLHandleDragInfo,
191
187
  type TLResizeInfo,
@@ -450,7 +446,6 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
450
446
  export { isAccelKey } from './lib/utils/keyboard'
451
447
  export { normalizeWheel } from './lib/utils/normalizeWheel'
452
448
  export { refreshPage } from './lib/utils/refreshPage'
453
- export { getDroppedShapesToNewParents, kickoutOccludedShapes } from './lib/utils/reparenting'
454
449
  export {
455
450
  getFontsFromRichText,
456
451
  type RichTextFontVisitor,
@@ -3662,7 +3662,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3662
3662
  * @public
3663
3663
  */
3664
3664
  updateViewportScreenBounds(screenBounds: Box | HTMLElement, center = false): this {
3665
- if (!(screenBounds instanceof Box)) {
3665
+ if (screenBounds instanceof HTMLElement) {
3666
3666
  const rect = screenBounds.getBoundingClientRect()
3667
3667
  screenBounds = new Box(
3668
3668
  rect.left || rect.x,
@@ -5528,7 +5528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5528
5528
  if (!id) return undefined
5529
5529
  const freshShape = this.getShape(id)
5530
5530
  if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
5531
- return this.getShape(freshShape.parentId)
5531
+ return this.store.get(freshShape.parentId)
5532
5532
  }
5533
5533
 
5534
5534
  /**
@@ -5711,10 +5711,6 @@ export class Editor extends EventEmitter<TLEventMap> {
5711
5711
  const newPoint = invertedParentTransform.applyToPoint(pagePoint)
5712
5712
  const newRotation = pageTransform.rotation() - parentPageRotation
5713
5713
 
5714
- if (shape.id === parentId) {
5715
- throw Error('Attempted to reparent a shape to itself!')
5716
- }
5717
-
5718
5714
  changes.push({
5719
5715
  id: shape.id,
5720
5716
  type: shape.type,
@@ -5818,11 +5814,6 @@ export class Editor extends EventEmitter<TLEventMap> {
5818
5814
  return shapeIds
5819
5815
  }
5820
5816
 
5821
- /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5822
- getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5823
- return this.getDraggingOverShape(point, droppingShapes)
5824
- }
5825
-
5826
5817
  /**
5827
5818
  * Get the shape that some shapes should be dropped on at a given point.
5828
5819
  *
@@ -5833,33 +5824,35 @@ export class Editor extends EventEmitter<TLEventMap> {
5833
5824
  *
5834
5825
  * @public
5835
5826
  */
5836
- getDraggingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5837
- // get fresh moving shapes
5838
- const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
5839
- (s) => !s.isLocked && !this.isShapeHidden(s)
5840
- )
5827
+ getDroppingOverShape(point: VecLike, droppingShapes: TLShape[] = []) {
5828
+ // starting from the top...
5829
+ const currentPageShapesSorted = this.getCurrentPageShapesSorted()
5830
+ for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
5831
+ const shape = currentPageShapesSorted[i]
5841
5832
 
5842
- const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
5843
- hitInside: true,
5844
- margin: 0,
5845
- }).filter(
5846
- (s) =>
5847
- !droppingShapes.includes(s) &&
5848
- !s.isLocked &&
5849
- !this.isShapeHidden(s) &&
5850
- !draggingShapes.includes(s)
5851
- )
5833
+ if (
5834
+ // ignore hidden shapes
5835
+ this.isShapeHidden(shape) ||
5836
+ // don't allow dropping on selected shapes
5837
+ this.getSelectedShapeIds().includes(shape.id) ||
5838
+ // only allow shapes that can receive children
5839
+ !this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
5840
+ // don't allow dropping a shape on itself or one of it's children
5841
+ droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
5842
+ ) {
5843
+ continue
5844
+ }
5845
+
5846
+ // Only allow dropping into the masked page bounds of the shape, e.g. when a frame is
5847
+ // partially clipped by its own parent frame
5848
+ const maskedPageBounds = this.getShapeMaskedPageBounds(shape.id)
5852
5849
 
5853
- for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
5854
- const shapeUtil = this.getShapeUtil(maybeDraggingOverShape)
5855
- // Any shape that can handle any dragging interactions is a valid target
5856
5850
  if (
5857
- shapeUtil.onDragShapesOver ||
5858
- shapeUtil.onDragShapesIn ||
5859
- shapeUtil.onDragShapesOut ||
5860
- shapeUtil.onDropShapesOver
5851
+ maskedPageBounds &&
5852
+ maskedPageBounds.containsPoint(point) &&
5853
+ this.getShapeGeometry(shape).hitTestPoint(this.getPointInShapeSpace(shape, point), 0, true)
5861
5854
  ) {
5862
- return maybeDraggingOverShape
5855
+ return shape
5863
5856
  }
5864
5857
  }
5865
5858
  }
@@ -6303,7 +6296,10 @@ export class Editor extends EventEmitter<TLEventMap> {
6303
6296
  })
6304
6297
  const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
6305
6298
 
6306
- if (!this.canCreateShapes(shapesToCreate)) {
6299
+ const maxShapesReached =
6300
+ shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
6301
+
6302
+ if (maxShapesReached) {
6307
6303
  alertMaxShapes(this)
6308
6304
  return
6309
6305
  }
@@ -7732,32 +7728,6 @@ export class Editor extends EventEmitter<TLEventMap> {
7732
7728
  return {}
7733
7729
  }
7734
7730
 
7735
- /**
7736
- * Get whether the provided shape can be created.
7737
- *
7738
- * @param shape - The shape or shape IDs to check.
7739
- *
7740
- * @public
7741
- */
7742
- canCreateShape<T extends TLUnknownShape>(
7743
- shape: OptionalKeys<TLShapePartial<T>, 'id'> | T['id']
7744
- ): boolean {
7745
- return this.canCreateShapes([shape])
7746
- }
7747
-
7748
- /**
7749
- * Get whether the provided shapes can be created.
7750
- *
7751
- * @param shapes - The shapes or shape IDs to create.
7752
- *
7753
- * @public
7754
- */
7755
- canCreateShapes<T extends TLUnknownShape>(
7756
- shapes: (T['id'] | OptionalKeys<TLShapePartial<T>, 'id'>)[]
7757
- ): boolean {
7758
- return shapes.length + this.getCurrentPageShapeIds().size <= this.options.maxShapesPerPage
7759
- }
7760
-
7761
7731
  /**
7762
7732
  * Create a single shape.
7763
7733
  *
@@ -7804,7 +7774,6 @@ export class Editor extends EventEmitter<TLEventMap> {
7804
7774
  if (maxShapesReached) {
7805
7775
  // can't create more shapes than fit on the page
7806
7776
  alertMaxShapes(this)
7807
- // todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
7808
7777
  return this
7809
7778
  }
7810
7779
 
@@ -7837,10 +7806,9 @@ export class Editor extends EventEmitter<TLEventMap> {
7837
7806
 
7838
7807
  for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7839
7808
  const parent = currentPageShapesSorted[i]
7840
- const util = this.getShapeUtil(parent)
7841
7809
  if (
7842
- util.canReceiveNewChildrenOfType(parent, partial.type) &&
7843
7810
  !this.isShapeHidden(parent) &&
7811
+ this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
7844
7812
  this.isPointInShape(
7845
7813
  parent,
7846
7814
  // If no parent is provided, then we can treat the
@@ -7859,7 +7827,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7859
7827
 
7860
7828
  const prevParentId = partial.parentId
7861
7829
 
7862
- // a shape cannot be its own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7830
+ // a shape cannot be it's own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7863
7831
  if (parentId === partial.id) {
7864
7832
  parentId = focusedGroupId
7865
7833
  }
@@ -7969,8 +7937,6 @@ export class Editor extends EventEmitter<TLEventMap> {
7969
7937
  }
7970
7938
  })
7971
7939
 
7972
- this.emit('created-shapes', shapeRecordsToCreate)
7973
- this.emit('edit')
7974
7940
  this.store.put(shapeRecordsToCreate)
7975
7941
  })
7976
7942
 
@@ -8365,8 +8331,6 @@ export class Editor extends EventEmitter<TLEventMap> {
8365
8331
  updates.push(updated)
8366
8332
  }
8367
8333
 
8368
- this.emit('edited-shapes', updates)
8369
- this.emit('edit')
8370
8334
  this.store.put(updates)
8371
8335
  })
8372
8336
  }
@@ -8416,8 +8380,6 @@ export class Editor extends EventEmitter<TLEventMap> {
8416
8380
  })
8417
8381
  }
8418
8382
 
8419
- this.emit('deleted-shapes', [...allShapeIdsToDelete])
8420
- this.emit('edit')
8421
8383
  return this.run(() => this.store.remove([...allShapeIdsToDelete]))
8422
8384
  }
8423
8385
 
@@ -9544,8 +9506,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9544
9506
  previousPagePoint,
9545
9507
  currentScreenPoint,
9546
9508
  currentPagePoint,
9547
- originScreenPoint,
9548
- originPagePoint,
9549
9509
  } = this.inputs
9550
9510
 
9551
9511
  const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -9574,8 +9534,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9574
9534
  // Reset velocity on pointer down, or when a pinch starts or ends
9575
9535
  if (info.name === 'pointer_down' || this.inputs.isPinching) {
9576
9536
  pointerVelocity.set(0, 0)
9577
- originScreenPoint.setTo(currentScreenPoint)
9578
- originPagePoint.setTo(currentPagePoint)
9537
+ this.inputs.originScreenPoint.setTo(currentScreenPoint)
9538
+ this.inputs.originPagePoint.setTo(currentPagePoint)
9579
9539
  }
9580
9540
 
9581
9541
  // todo: We only have to do this if there are multiple users in the document
@@ -241,9 +241,7 @@ export class HistoryManager<R extends UnknownRecord> {
241
241
  }
242
242
 
243
243
  bailToMark(id: string) {
244
- if (id) {
245
- this._undo({ pushToRedoStack: false, toMark: id })
246
- }
244
+ this._undo({ pushToRedoStack: false, toMark: id })
247
245
 
248
246
  return this
249
247
  }
@@ -4,15 +4,12 @@ import { LegacyMigrations, MigrationSequence } from '@tldraw/store'
4
4
  import {
5
5
  RecordProps,
6
6
  TLHandle,
7
- TLParentId,
8
7
  TLPropsMigrations,
9
8
  TLShape,
10
9
  TLShapeCrop,
11
- TLShapeId,
12
10
  TLShapePartial,
13
11
  TLUnknownShape,
14
12
  } from '@tldraw/tlschema'
15
- import { IndexKey } from '@tldraw/utils'
16
13
  import { ReactElement } from 'react'
17
14
  import { Box, SelectionHandle } from '../../primitives/Box'
18
15
  import { Vec } from '../../primitives/Vec'
@@ -390,6 +387,17 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
390
387
  return false
391
388
  }
392
389
 
390
+ /**
391
+ * Get whether the shape can receive children of a given type.
392
+ *
393
+ * @param shape - The shape type.
394
+ * @param shapes - The shapes that are being dropped.
395
+ * @public
396
+ */
397
+ canDropShapes(_shape: Shape, _shapes: TLShape[]) {
398
+ return false
399
+ }
400
+
393
401
  /**
394
402
  * Get the shape as an SVG object.
395
403
  *
@@ -509,16 +517,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
509
517
  ): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void
510
518
 
511
519
  /**
512
- * A callback called when some other shapes are dragged into this one. This fires when the shapes are dragged over the shape for the first time.
513
- *
514
- * @param shape - The shape.
515
- * @param shapes - The shapes that are being dragged in.
516
- * @public
517
- */
518
- onDragShapesIn?(shape: Shape, shapes: TLShape[], info: TLDragShapesInInfo): void
519
-
520
- /**
521
- * A callback called when some other shapes are dragged over this one. This fires when the shapes are dragged over the shape for the first time (after the onDragShapesIn callback), and again on every update while the shapes are being dragged.
520
+ * A callback called when some other shapes are dragged over this one.
522
521
  *
523
522
  * @example
524
523
  *
@@ -532,7 +531,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
532
531
  * @param shapes - The shapes that are being dragged over this one.
533
532
  * @public
534
533
  */
535
- onDragShapesOver?(shape: Shape, shapes: TLShape[], info: TLDragShapesOverInfo): void
534
+ onDragShapesOver?(shape: Shape, shapes: TLShape[]): void
536
535
 
537
536
  /**
538
537
  * A callback called when some other shapes are dragged out of this one.
@@ -541,7 +540,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
541
540
  * @param shapes - The shapes that are being dragged out.
542
541
  * @public
543
542
  */
544
- onDragShapesOut?(shape: Shape, shapes: TLShape[], info: TLDragShapesOutInfo): void
543
+ onDragShapesOut?(shape: Shape, shapes: TLShape[]): void
545
544
 
546
545
  /**
547
546
  * A callback called when some other shapes are dropped over this one.
@@ -550,7 +549,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
550
549
  * @param shapes - The shapes that are being dropped over this one.
551
550
  * @public
552
551
  */
553
- onDropShapesOver?(shape: Shape, shapes: TLShape[], info: TLDropShapesOverInfo): void
552
+ onDropShapesOver?(shape: Shape, shapes: TLShape[]): void
554
553
 
555
554
  /**
556
555
  * A callback called when a shape starts being resized.
@@ -749,36 +748,6 @@ export interface TLCropInfo<T extends TLShape> {
749
748
  aspectRatioLocked?: boolean
750
749
  }
751
750
 
752
- /** @public */
753
- export interface TLDragShapesInInfo {
754
- initialDraggingOverShapeId: TLShapeId | null
755
- prevDraggingOverShapeId: TLShapeId | null
756
- initialParentIds: Map<TLShapeId, TLParentId>
757
- initialIndices: Map<TLShapeId, IndexKey>
758
- }
759
-
760
- /** @public */
761
- export interface TLDragShapesOverInfo {
762
- initialDraggingOverShapeId: TLShapeId | null
763
- initialParentIds: Map<TLShapeId, TLParentId>
764
- initialIndices: Map<TLShapeId, IndexKey>
765
- }
766
-
767
- /** @public */
768
- export interface TLDragShapesOutInfo {
769
- nextDraggingOverShapeId: TLShapeId | null
770
- initialDraggingOverShapeId: TLShapeId | null
771
- initialParentIds: Map<TLShapeId, TLParentId>
772
- initialIndices: Map<TLShapeId, IndexKey>
773
- }
774
-
775
- /** @public */
776
- export interface TLDropShapesOverInfo {
777
- initialDraggingOverShapeId: TLShapeId | null
778
- initialParentIds: Map<TLShapeId, TLParentId>
779
- initialIndices: Map<TLShapeId, IndexKey>
780
- }
781
-
782
751
  /**
783
752
  * The type of resize.
784
753
  *
@@ -11,36 +11,29 @@ export class Pointing extends StateNode {
11
11
  static override id = 'pointing'
12
12
 
13
13
  override onPointerMove(info: TLPointerEventInfo) {
14
- const { editor } = this
15
- if (editor.inputs.isDragging) {
16
- const { originPagePoint } = editor.inputs
14
+ if (this.editor.inputs.isDragging) {
15
+ const { originPagePoint } = this.editor.inputs
17
16
 
18
17
  const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType
19
18
 
20
19
  const id = createShapeId()
21
20
 
22
- const creatingMarkId = editor.markHistoryStoppingPoint(`creating_box:${id}`)
23
- const newPoint = maybeSnapToGrid(originPagePoint, editor)
24
-
25
- // Allow this to trigger the max shapes reached alert
26
- this.editor.createShapes<TLBaseBoxShape>([
27
- {
28
- id,
29
- type: shapeType,
30
- x: newPoint.x,
31
- y: newPoint.y,
32
- props: {
33
- w: 1,
34
- h: 1,
21
+ const creatingMarkId = this.editor.markHistoryStoppingPoint(`creating_box:${id}`)
22
+ const newPoint = maybeSnapToGrid(originPagePoint, this.editor)
23
+ this.editor
24
+ .createShapes<TLBaseBoxShape>([
25
+ {
26
+ id,
27
+ type: shapeType,
28
+ x: newPoint.x,
29
+ y: newPoint.y,
30
+ props: {
31
+ w: 1,
32
+ h: 1,
33
+ },
35
34
  },
36
- },
37
- ])
38
- const shape = editor.getShape(id)
39
- if (!shape) {
40
- this.cancel()
41
- return
42
- }
43
- editor.select(id)
35
+ ])
36
+ .select(id)
44
37
 
45
38
  const parent = this.parent as BaseBoxShapeTool
46
39
  this.editor.setCurrentTool(
@@ -86,7 +79,6 @@ export class Pointing extends StateNode {
86
79
 
87
80
  this.editor.markHistoryStoppingPoint(`creating_box:${id}`)
88
81
 
89
- // Allow this to trigger the max shapes reached alert
90
82
  // todo: add scale here when dynamic size is enabled (is this still needed?)
91
83
  this.editor.createShapes<TLBaseBoxShape>([
92
84
  {
@@ -18,10 +18,6 @@ export interface TLEventMap {
18
18
  frame: [number]
19
19
  'select-all-text': [{ shapeId: TLShapeId }]
20
20
  'place-caret': [{ shapeId: TLShapeId; point: { x: number; y: number } }]
21
- 'created-shapes': [TLRecord[]]
22
- 'edited-shapes': [TLRecord[]]
23
- 'deleted-shapes': [TLShapeId[]]
24
- edit: []
25
21
  }
26
22
 
27
23
  /** @public */
@@ -44,7 +44,6 @@ export const Geometry2dFilters: {
44
44
  /** @public */
45
45
  export interface TransformedGeometry2dOptions {
46
46
  isLabel?: boolean
47
- isEmptyLabel?: boolean
48
47
  isInternal?: boolean
49
48
  debugColor?: string
50
49
  ignore?: boolean
@@ -58,24 +57,20 @@ export interface Geometry2dOptions extends TransformedGeometry2dOptions {
58
57
 
59
58
  /** @public */
60
59
  export abstract class Geometry2d {
61
- // todo: consider making accessors for these too, so that they can be overridden in subclasses by geometries with more complex logic
62
60
  isFilled = false
63
61
  isClosed = true
64
62
  isLabel = false
65
- isEmptyLabel = false
66
63
  isInternal = false
67
64
  debugColor?: string
68
65
  ignore?: boolean
69
66
 
70
67
  constructor(opts: Geometry2dOptions) {
71
- const { isLabel = false, isEmptyLabel = false, isInternal = false } = opts
72
68
  this.isFilled = opts.isFilled
73
69
  this.isClosed = opts.isClosed
70
+ this.isLabel = opts.isLabel ?? false
71
+ this.isInternal = opts.isInternal ?? false
74
72
  this.debugColor = opts.debugColor
75
73
  this.ignore = opts.ignore
76
- this.isLabel = isLabel
77
- this.isEmptyLabel = isEmptyLabel
78
- this.isInternal = isInternal
79
74
  }
80
75
 
81
76
  isExcludedByFilter(filters?: Geometry2dFilters) {