@tldraw/editor 3.13.0-canary.ca982decc2f2 → 3.13.0-canary.ce8e6cffa809

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 (54) hide show
  1. package/dist-cjs/index.d.ts +18 -13
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/TldrawEditor.js +2 -1
  4. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +6 -0
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +8 -6
  8. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  9. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +1 -1
  10. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
  11. package/dist-cjs/lib/hooks/useDocumentEvents.js +3 -2
  12. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  13. package/dist-cjs/lib/hooks/useEditorComponents.js +16 -15
  14. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  15. package/dist-cjs/lib/options.js.map +2 -2
  16. package/dist-cjs/lib/utils/dom.js +3 -3
  17. package/dist-cjs/lib/utils/dom.js.map +2 -2
  18. package/dist-cjs/lib/utils/rotation.js +5 -5
  19. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  20. package/dist-cjs/version.js +3 -3
  21. package/dist-cjs/version.js.map +1 -1
  22. package/dist-esm/index.d.mts +18 -13
  23. package/dist-esm/index.mjs +1 -1
  24. package/dist-esm/lib/TldrawEditor.mjs +2 -1
  25. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  26. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +6 -0
  27. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  28. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +8 -6
  29. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  30. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +1 -1
  31. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  32. package/dist-esm/lib/hooks/useDocumentEvents.mjs +3 -2
  33. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  34. package/dist-esm/lib/hooks/useEditorComponents.mjs +16 -15
  35. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  36. package/dist-esm/lib/options.mjs.map +2 -2
  37. package/dist-esm/lib/utils/dom.mjs +3 -3
  38. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  39. package/dist-esm/lib/utils/rotation.mjs +5 -5
  40. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  41. package/dist-esm/version.mjs +3 -3
  42. package/dist-esm/version.mjs.map +1 -1
  43. package/editor.css +5 -0
  44. package/package.json +7 -7
  45. package/src/lib/TldrawEditor.tsx +6 -1
  46. package/src/lib/components/default-components/DefaultCanvas.tsx +11 -0
  47. package/src/lib/components/default-components/DefaultErrorFallback.tsx +15 -8
  48. package/src/lib/components/default-components/DefaultSpinner.tsx +1 -1
  49. package/src/lib/hooks/useDocumentEvents.ts +7 -2
  50. package/src/lib/hooks/useEditorComponents.tsx +32 -28
  51. package/src/lib/options.ts +4 -0
  52. package/src/lib/utils/dom.ts +4 -4
  53. package/src/lib/utils/rotation.ts +8 -6
  54. package/src/version.ts +3 -3
@@ -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 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\nconst INPUTS = ['input', 'select', 'button', 'textarea']\n\n/** @internal */\nexport function activeElementShouldCaptureKeys() {\n\tconst { activeElement } = document\n\treturn !!(\n\t\tactiveElement &&\n\t\t((activeElement as HTMLElement).isContentEditable ||\n\t\t\tINPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1)\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;AAEA,MAAM,SAAS,CAAC,SAAS,UAAU,UAAU,UAAU;AAGhD,SAAS,iCAAiC;AAChD,QAAM,EAAE,cAAc,IAAI;AAC1B,SAAO,CAAC,EACP,kBACE,cAA8B,qBAC/B,OAAO,QAAQ,cAAc,QAAQ,YAAY,CAAC,IAAI;AAEzD;",
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
  }
@@ -13,10 +13,10 @@ function getRotationSnapshot({
13
13
  if (!rotatedPageBounds) {
14
14
  return null;
15
15
  }
16
- const pageCenter = rotatedPageBounds.center.clone().rotWith(rotatedPageBounds.point, rotation);
16
+ const initialPageCenter = rotatedPageBounds.center.clone().rotWith(rotatedPageBounds.point, rotation);
17
17
  return {
18
- pageCenter,
19
- initialCursorAngle: pageCenter.angle(editor.inputs.originPagePoint),
18
+ initialPageCenter,
19
+ initialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),
20
20
  initialShapesRotation: rotation,
21
21
  shapeSnapshots: shapes.map((shape) => ({
22
22
  shape,
@@ -31,11 +31,11 @@ function applyRotationToSnapshotShapes({
31
31
  stage,
32
32
  centerOverride
33
33
  }) {
34
- const { pageCenter, shapeSnapshots } = snapshot;
34
+ const { initialPageCenter, shapeSnapshots } = snapshot;
35
35
  editor.updateShapes(
36
36
  shapeSnapshots.map(({ shape, initialPagePoint }) => {
37
37
  const parentTransform = isShapeId(shape.parentId) ? editor.getShapePageTransform(shape.parentId) : Mat.Identity();
38
- const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? pageCenter, delta);
38
+ const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta);
39
39
  const newLocalPoint = Mat.applyToPoint(
40
40
  // use the current parent transform in case it has moved/resized since the start
41
41
  // (e.g. if rotating a shape at the edge of a group)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/utils/rotation.ts"],
4
- "sourcesContent": ["import { isShapeId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { compact } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Mat } from '../primitives/Mat'\nimport { canonicalizeRotation } from '../primitives/utils'\nimport { Vec, VecLike } from '../primitives/Vec'\n\n/** @internal */\nexport function getRotationSnapshot({\n\teditor,\n\tids,\n}: {\n\teditor: Editor\n\tids: TLShapeId[]\n}): TLRotationSnapshot | null {\n\tconst shapes = compact(ids.map((id) => editor.getShape(id)))\n\tconst rotation = editor.getShapesSharedRotation(ids)\n\tconst rotatedPageBounds = editor.getShapesRotatedPageBounds(ids)\n\n\t// todo: this assumes we're rotating the selected shapes\n\t// if we try to rotate shapes that aren't selected, this\n\t// will produce the wrong results\n\n\t// Return null if there are no selected shapes\n\tif (!rotatedPageBounds) {\n\t\treturn null\n\t}\n\n\tconst pageCenter = rotatedPageBounds.center.clone().rotWith(rotatedPageBounds.point, rotation)\n\n\treturn {\n\t\tpageCenter,\n\t\tinitialCursorAngle: pageCenter.angle(editor.inputs.originPagePoint),\n\t\tinitialShapesRotation: rotation,\n\t\tshapeSnapshots: shapes.map((shape) => ({\n\t\t\tshape,\n\t\t\tinitialPagePoint: editor.getShapePageTransform(shape.id)!.point(),\n\t\t})),\n\t}\n}\n\n/**\n * @internal\n **/\nexport interface TLRotationSnapshot {\n\tpageCenter: Vec\n\tinitialCursorAngle: number\n\tinitialShapesRotation: number\n\tshapeSnapshots: {\n\t\tshape: TLShape\n\t\tinitialPagePoint: Vec\n\t}[]\n}\n\n/** @internal */\nexport function applyRotationToSnapshotShapes({\n\tdelta,\n\teditor,\n\tsnapshot,\n\tstage,\n\tcenterOverride,\n}: {\n\tdelta: number\n\tsnapshot: TLRotationSnapshot\n\teditor: Editor\n\tstage: 'start' | 'update' | 'end' | 'one-off'\n\tcenterOverride?: VecLike\n}) {\n\tconst { pageCenter, shapeSnapshots } = snapshot\n\n\teditor.updateShapes(\n\t\tshapeSnapshots.map(({ shape, initialPagePoint }) => {\n\t\t\t// We need to both rotate each shape individually and rotate the shapes\n\t\t\t// around the pivot point (the average center of all rotating shapes.)\n\n\t\t\tconst parentTransform = isShapeId(shape.parentId)\n\t\t\t\t? editor.getShapePageTransform(shape.parentId)!\n\t\t\t\t: Mat.Identity()\n\n\t\t\tconst newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? pageCenter, delta)\n\n\t\t\tconst newLocalPoint = Mat.applyToPoint(\n\t\t\t\t// use the current parent transform in case it has moved/resized since the start\n\t\t\t\t// (e.g. if rotating a shape at the edge of a group)\n\t\t\t\tMat.Inverse(parentTransform),\n\t\t\t\tnewPagePoint\n\t\t\t)\n\t\t\tconst newRotation = canonicalizeRotation(shape.rotation + delta)\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx: newLocalPoint.x,\n\t\t\t\ty: newLocalPoint.y,\n\t\t\t\trotation: newRotation,\n\t\t\t}\n\t\t})\n\t)\n\n\t// Handle change\n\n\tconst changes: TLShapePartial[] = []\n\n\tshapeSnapshots.forEach(({ shape }) => {\n\t\tconst current = editor.getShape(shape.id)\n\t\tif (!current) return\n\t\tconst util = editor.getShapeUtil(shape)\n\n\t\tif (stage === 'start' || stage === 'one-off') {\n\t\t\tconst changeStart = util.onRotateStart?.(shape)\n\t\t\tif (changeStart) changes.push(changeStart)\n\t\t}\n\n\t\tconst changeUpdate = util.onRotate?.(shape, current)\n\t\tif (changeUpdate) changes.push(changeUpdate)\n\n\t\tif (stage === 'end' || stage === 'one-off') {\n\t\t\tconst changeEnd = util.onRotateEnd?.(shape, current)\n\t\t\tif (changeEnd) changes.push(changeEnd)\n\t\t}\n\t})\n\n\tif (changes.length > 0) {\n\t\teditor.updateShapes(changes)\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,iBAAqD;AAC9D,SAAS,eAAe;AAExB,SAAS,WAAW;AACpB,SAAS,4BAA4B;AACrC,SAAS,WAAoB;AAGtB,SAAS,oBAAoB;AAAA,EACnC;AAAA,EACA;AACD,GAG8B;AAC7B,QAAM,SAAS,QAAQ,IAAI,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAC3D,QAAM,WAAW,OAAO,wBAAwB,GAAG;AACnD,QAAM,oBAAoB,OAAO,2BAA2B,GAAG;AAO/D,MAAI,CAAC,mBAAmB;AACvB,WAAO;AAAA,EACR;AAEA,QAAM,aAAa,kBAAkB,OAAO,MAAM,EAAE,QAAQ,kBAAkB,OAAO,QAAQ;AAE7F,SAAO;AAAA,IACN;AAAA,IACA,oBAAoB,WAAW,MAAM,OAAO,OAAO,eAAe;AAAA,IAClE,uBAAuB;AAAA,IACvB,gBAAgB,OAAO,IAAI,CAAC,WAAW;AAAA,MACtC;AAAA,MACA,kBAAkB,OAAO,sBAAsB,MAAM,EAAE,EAAG,MAAM;AAAA,IACjE,EAAE;AAAA,EACH;AACD;AAgBO,SAAS,8BAA8B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAMG;AACF,QAAM,EAAE,YAAY,eAAe,IAAI;AAEvC,SAAO;AAAA,IACN,eAAe,IAAI,CAAC,EAAE,OAAO,iBAAiB,MAAM;AAInD,YAAM,kBAAkB,UAAU,MAAM,QAAQ,IAC7C,OAAO,sBAAsB,MAAM,QAAQ,IAC3C,IAAI,SAAS;AAEhB,YAAM,eAAe,IAAI,QAAQ,kBAAkB,kBAAkB,YAAY,KAAK;AAEtF,YAAM,gBAAgB,IAAI;AAAA;AAAA;AAAA,QAGzB,IAAI,QAAQ,eAAe;AAAA,QAC3B;AAAA,MACD;AACA,YAAM,cAAc,qBAAqB,MAAM,WAAW,KAAK;AAE/D,aAAO;AAAA,QACN,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,GAAG,cAAc;AAAA,QACjB,GAAG,cAAc;AAAA,QACjB,UAAU;AAAA,MACX;AAAA,IACD,CAAC;AAAA,EACF;AAIA,QAAM,UAA4B,CAAC;AAEnC,iBAAe,QAAQ,CAAC,EAAE,MAAM,MAAM;AACrC,UAAM,UAAU,OAAO,SAAS,MAAM,EAAE;AACxC,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,OAAO,aAAa,KAAK;AAEtC,QAAI,UAAU,WAAW,UAAU,WAAW;AAC7C,YAAM,cAAc,KAAK,gBAAgB,KAAK;AAC9C,UAAI,YAAa,SAAQ,KAAK,WAAW;AAAA,IAC1C;AAEA,UAAM,eAAe,KAAK,WAAW,OAAO,OAAO;AACnD,QAAI,aAAc,SAAQ,KAAK,YAAY;AAE3C,QAAI,UAAU,SAAS,UAAU,WAAW;AAC3C,YAAM,YAAY,KAAK,cAAc,OAAO,OAAO;AACnD,UAAI,UAAW,SAAQ,KAAK,SAAS;AAAA,IACtC;AAAA,EACD,CAAC;AAED,MAAI,QAAQ,SAAS,GAAG;AACvB,WAAO,aAAa,OAAO;AAAA,EAC5B;AACD;",
4
+ "sourcesContent": ["import { isShapeId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { compact } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Mat } from '../primitives/Mat'\nimport { canonicalizeRotation } from '../primitives/utils'\nimport { Vec, VecLike } from '../primitives/Vec'\n\n/** @internal */\nexport function getRotationSnapshot({\n\teditor,\n\tids,\n}: {\n\teditor: Editor\n\tids: TLShapeId[]\n}): TLRotationSnapshot | null {\n\tconst shapes = compact(ids.map((id) => editor.getShape(id)))\n\tconst rotation = editor.getShapesSharedRotation(ids)\n\tconst rotatedPageBounds = editor.getShapesRotatedPageBounds(ids)\n\n\t// todo: this assumes we're rotating the selected shapes\n\t// if we try to rotate shapes that aren't selected, this\n\t// will produce the wrong results\n\n\t// Return null if there are no selected shapes\n\tif (!rotatedPageBounds) {\n\t\treturn null\n\t}\n\n\tconst initialPageCenter = rotatedPageBounds.center\n\t\t.clone()\n\t\t.rotWith(rotatedPageBounds.point, rotation)\n\n\treturn {\n\t\tinitialPageCenter,\n\t\tinitialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),\n\t\tinitialShapesRotation: rotation,\n\t\tshapeSnapshots: shapes.map((shape) => ({\n\t\t\tshape,\n\t\t\tinitialPagePoint: editor.getShapePageTransform(shape.id)!.point(),\n\t\t})),\n\t}\n}\n\n/**\n * @internal\n **/\nexport interface TLRotationSnapshot {\n\tinitialPageCenter: Vec\n\tinitialCursorAngle: number\n\tinitialShapesRotation: number\n\tshapeSnapshots: {\n\t\tshape: TLShape\n\t\tinitialPagePoint: Vec\n\t}[]\n}\n\n/** @internal */\nexport function applyRotationToSnapshotShapes({\n\tdelta,\n\teditor,\n\tsnapshot,\n\tstage,\n\tcenterOverride,\n}: {\n\tdelta: number\n\tsnapshot: TLRotationSnapshot\n\teditor: Editor\n\tstage: 'start' | 'update' | 'end' | 'one-off'\n\tcenterOverride?: VecLike\n}) {\n\tconst { initialPageCenter, shapeSnapshots } = snapshot\n\n\teditor.updateShapes(\n\t\tshapeSnapshots.map(({ shape, initialPagePoint }) => {\n\t\t\t// We need to both rotate each shape individually and rotate the shapes\n\t\t\t// around the pivot point (the average center of all rotating shapes.)\n\n\t\t\tconst parentTransform = isShapeId(shape.parentId)\n\t\t\t\t? editor.getShapePageTransform(shape.parentId)!\n\t\t\t\t: Mat.Identity()\n\n\t\t\tconst newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)\n\n\t\t\tconst newLocalPoint = Mat.applyToPoint(\n\t\t\t\t// use the current parent transform in case it has moved/resized since the start\n\t\t\t\t// (e.g. if rotating a shape at the edge of a group)\n\t\t\t\tMat.Inverse(parentTransform),\n\t\t\t\tnewPagePoint\n\t\t\t)\n\t\t\tconst newRotation = canonicalizeRotation(shape.rotation + delta)\n\n\t\t\treturn {\n\t\t\t\tid: shape.id,\n\t\t\t\ttype: shape.type,\n\t\t\t\tx: newLocalPoint.x,\n\t\t\t\ty: newLocalPoint.y,\n\t\t\t\trotation: newRotation,\n\t\t\t}\n\t\t})\n\t)\n\n\t// Handle change\n\n\tconst changes: TLShapePartial[] = []\n\n\tshapeSnapshots.forEach(({ shape }) => {\n\t\tconst current = editor.getShape(shape.id)\n\t\tif (!current) return\n\t\tconst util = editor.getShapeUtil(shape)\n\n\t\tif (stage === 'start' || stage === 'one-off') {\n\t\t\tconst changeStart = util.onRotateStart?.(shape)\n\t\t\tif (changeStart) changes.push(changeStart)\n\t\t}\n\n\t\tconst changeUpdate = util.onRotate?.(shape, current)\n\t\tif (changeUpdate) changes.push(changeUpdate)\n\n\t\tif (stage === 'end' || stage === 'one-off') {\n\t\t\tconst changeEnd = util.onRotateEnd?.(shape, current)\n\t\t\tif (changeEnd) changes.push(changeEnd)\n\t\t}\n\t})\n\n\tif (changes.length > 0) {\n\t\teditor.updateShapes(changes)\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAqD;AAC9D,SAAS,eAAe;AAExB,SAAS,WAAW;AACpB,SAAS,4BAA4B;AACrC,SAAS,WAAoB;AAGtB,SAAS,oBAAoB;AAAA,EACnC;AAAA,EACA;AACD,GAG8B;AAC7B,QAAM,SAAS,QAAQ,IAAI,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAC3D,QAAM,WAAW,OAAO,wBAAwB,GAAG;AACnD,QAAM,oBAAoB,OAAO,2BAA2B,GAAG;AAO/D,MAAI,CAAC,mBAAmB;AACvB,WAAO;AAAA,EACR;AAEA,QAAM,oBAAoB,kBAAkB,OAC1C,MAAM,EACN,QAAQ,kBAAkB,OAAO,QAAQ;AAE3C,SAAO;AAAA,IACN;AAAA,IACA,oBAAoB,kBAAkB,MAAM,OAAO,OAAO,eAAe;AAAA,IACzE,uBAAuB;AAAA,IACvB,gBAAgB,OAAO,IAAI,CAAC,WAAW;AAAA,MACtC;AAAA,MACA,kBAAkB,OAAO,sBAAsB,MAAM,EAAE,EAAG,MAAM;AAAA,IACjE,EAAE;AAAA,EACH;AACD;AAgBO,SAAS,8BAA8B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,GAMG;AACF,QAAM,EAAE,mBAAmB,eAAe,IAAI;AAE9C,SAAO;AAAA,IACN,eAAe,IAAI,CAAC,EAAE,OAAO,iBAAiB,MAAM;AAInD,YAAM,kBAAkB,UAAU,MAAM,QAAQ,IAC7C,OAAO,sBAAsB,MAAM,QAAQ,IAC3C,IAAI,SAAS;AAEhB,YAAM,eAAe,IAAI,QAAQ,kBAAkB,kBAAkB,mBAAmB,KAAK;AAE7F,YAAM,gBAAgB,IAAI;AAAA;AAAA;AAAA,QAGzB,IAAI,QAAQ,eAAe;AAAA,QAC3B;AAAA,MACD;AACA,YAAM,cAAc,qBAAqB,MAAM,WAAW,KAAK;AAE/D,aAAO;AAAA,QACN,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,GAAG,cAAc;AAAA,QACjB,GAAG,cAAc;AAAA,QACjB,UAAU;AAAA,MACX;AAAA,IACD,CAAC;AAAA,EACF;AAIA,QAAM,UAA4B,CAAC;AAEnC,iBAAe,QAAQ,CAAC,EAAE,MAAM,MAAM;AACrC,UAAM,UAAU,OAAO,SAAS,MAAM,EAAE;AACxC,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,OAAO,aAAa,KAAK;AAEtC,QAAI,UAAU,WAAW,UAAU,WAAW;AAC7C,YAAM,cAAc,KAAK,gBAAgB,KAAK;AAC9C,UAAI,YAAa,SAAQ,KAAK,WAAW;AAAA,IAC1C;AAEA,UAAM,eAAe,KAAK,WAAW,OAAO,OAAO;AACnD,QAAI,aAAc,SAAQ,KAAK,YAAY;AAE3C,QAAI,UAAU,SAAS,UAAU,WAAW;AAC3C,YAAM,YAAY,KAAK,cAAc,OAAO,OAAO;AACnD,UAAI,UAAW,SAAQ,KAAK,SAAS;AAAA,IACtC;AAAA,EACD,CAAC;AAED,MAAI,QAAQ,SAAS,GAAG;AACvB,WAAO,aAAa,OAAO;AAAA,EAC5B;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "3.13.0-canary.ca982decc2f2";
1
+ const version = "3.13.0-canary.ce8e6cffa809";
2
2
  const publishDates = {
3
3
  major: "2024-09-13T14:36:29.063Z",
4
- minor: "2025-04-22T12:56:35.074Z",
5
- patch: "2025-04-22T12:56:35.074Z"
4
+ minor: "2025-04-29T15:35:44.232Z",
5
+ patch: "2025-04-29T15:35:44.232Z"
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.ca982decc2f2'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-04-22T12:56:35.074Z',\n\tpatch: '2025-04-22T12:56:35.074Z',\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-canary.ce8e6cffa809'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-04-29T15:35:44.232Z',\n\tpatch: '2025-04-29T15:35:44.232Z',\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
@@ -44,6 +44,7 @@
44
44
  /* User handles need to be above selection edges / corners, matters for sticky note clone handles */
45
45
  --layer-overlays-user-handles: 105;
46
46
  --layer-overlays-user-indicator-hint: 110;
47
+ --layer-overlays-custom: 115;
47
48
  --layer-overlays-collaborator-cursor-hint: 120;
48
49
  --layer-overlays-collaborator-cursor: 130;
49
50
 
@@ -472,6 +473,10 @@ input,
472
473
  stroke-width: calc(2.5px * var(--tl-scale));
473
474
  }
474
475
 
476
+ .tl-custom-overlays {
477
+ z-index: var(--layer-overlays-custom);
478
+ }
479
+
475
480
  /* behind collaborator cursor */
476
481
  .tl-collaborator__cursor-hint {
477
482
  z-index: var(--layer-overlays-collaborator-cursor-hint);
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.ca982decc2f2",
4
+ "version": "3.13.0-canary.ce8e6cffa809",
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.ca982decc2f2",
52
- "@tldraw/state-react": "3.13.0-canary.ca982decc2f2",
53
- "@tldraw/store": "3.13.0-canary.ca982decc2f2",
54
- "@tldraw/tlschema": "3.13.0-canary.ca982decc2f2",
55
- "@tldraw/utils": "3.13.0-canary.ca982decc2f2",
56
- "@tldraw/validate": "3.13.0-canary.ca982decc2f2",
51
+ "@tldraw/state": "3.13.0-canary.ce8e6cffa809",
52
+ "@tldraw/state-react": "3.13.0-canary.ce8e6cffa809",
53
+ "@tldraw/store": "3.13.0-canary.ce8e6cffa809",
54
+ "@tldraw/tlschema": "3.13.0-canary.ce8e6cffa809",
55
+ "@tldraw/utils": "3.13.0-canary.ce8e6cffa809",
56
+ "@tldraw/validate": "3.13.0-canary.ce8e6cffa809",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
@@ -285,6 +285,7 @@ export const TldrawEditor = memo(function TldrawEditor({
285
285
  onPointerDown={stopEventPropagation}
286
286
  tabIndex={-1}
287
287
  role="application"
288
+ aria-label={_options?.branding ?? 'tldraw'}
288
289
  >
289
290
  <OptionalErrorBoundary
290
291
  fallback={ErrorFallback}
@@ -669,7 +670,11 @@ export interface LoadingScreenProps {
669
670
 
670
671
  /** @public @react */
671
672
  export function LoadingScreen({ children }: LoadingScreenProps) {
672
- return <div className="tl-loading">{children}</div>
673
+ return (
674
+ <div className="tl-loading" aria-busy="true" tabIndex={0}>
675
+ {children}
676
+ </div>
677
+ )
673
678
  }
674
679
 
675
680
  /** @public @react */
@@ -168,6 +168,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
168
168
  <SnapIndicatorWrapper />
169
169
  <SelectionForegroundWrapper />
170
170
  <HandlesWrapper />
171
+ <OverlaysWrapper />
171
172
  <LiveCollaborators />
172
173
  </div>
173
174
  </div>
@@ -372,6 +373,16 @@ function HandleWrapper({
372
373
  )
373
374
  }
374
375
 
376
+ function OverlaysWrapper() {
377
+ const { Overlays } = useEditorComponents()
378
+ if (!Overlays) return null
379
+ return (
380
+ <div className="tl-custom-overlays tl-overlays__item">
381
+ <Overlays />
382
+ </div>
383
+ )
384
+ }
385
+
375
386
  function ShapesWithSVGs() {
376
387
  const editor = useEditor()
377
388
 
@@ -168,16 +168,23 @@ My browser: ${navigator.userAgent}`
168
168
  ) : (
169
169
  <>
170
170
  <h2>Something went wrong</h2>
171
- <p>Please refresh the page to continue.</p>
171
+ <p>Please refresh your browser.</p>
172
172
  <p>
173
- If you keep seeing this screen, you can create a{' '}
174
- <a href={url.toString()}>GitHub issue</a> or ask for help on{' '}
175
- <a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
176
- Discord
177
- </a>
178
- . If you are still stuck, you can reset the tldraw data on your machine. This may
179
- erase the project you were working on, so try to get help first.
173
+ If the issue continues after refreshing, you may need to reset the tldraw data stored
174
+ on your device.
180
175
  </p>
176
+ <p>
177
+ <strong>Note:</strong> Resetting will erase your current project and any unsaved work.
178
+ </p>
179
+ {process.env.NODE_ENV !== 'production' && (
180
+ <p>
181
+ If you&apos;re developing with the SDK and need help, join us on{' '}
182
+ <a href="https://discord.tldraw.com/?utm_source=sdk&utm_medium=organic&utm_campaign=error-screen">
183
+ Discord
184
+ </a>
185
+ .
186
+ </p>
187
+ )}
181
188
  {shouldShowError && (
182
189
  <>
183
190
  Message:
@@ -1,7 +1,7 @@
1
1
  /** @public @react */
2
2
  export function DefaultSpinner() {
3
3
  return (
4
- <svg width={16} height={16} viewBox="0 0 16 16">
4
+ <svg width={16} height={16} viewBox="0 0 16 16" aria-hidden="false">
5
5
  <g strokeWidth={2} fill="none" fillRule="evenodd">
6
6
  <circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
7
7
  <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor">
@@ -11,6 +11,7 @@ export function useDocumentEvents() {
11
11
  const editor = useEditor()
12
12
  const container = useContainer()
13
13
 
14
+ const isEditing = useValue('isEditing', () => editor.getEditingShapeId(), [editor])
14
15
  const isAppFocused = useValue('isFocused', () => editor.getIsFocused(), [editor])
15
16
 
16
17
  // Prevent the browser's default drag and drop behavior on our container (UI, etc)
@@ -125,7 +126,11 @@ export function useDocumentEvents() {
125
126
  if (areShortcutsDisabled(editor)) {
126
127
  return
127
128
  }
128
- if (hasSelectedShapes) {
129
+ // isEditing here sounds like it's about text editing
130
+ // but more specifically, this is so you can tab into an
131
+ // embed that's being 'edited'. In our world,
132
+ // editing an embed, means it's interactive.
133
+ if (hasSelectedShapes && !isEditing) {
129
134
  // This is used in tandem with shape navigation.
130
135
  preventDefault(e)
131
136
  }
@@ -289,7 +294,7 @@ export function useDocumentEvents() {
289
294
  container.removeEventListener('keydown', handleKeyDown)
290
295
  container.removeEventListener('keyup', handleKeyUp)
291
296
  }
292
- }, [editor, container, isAppFocused])
297
+ }, [editor, container, isAppFocused, isEditing])
293
298
  }
294
299
 
295
300
  function areShortcutsDisabled(editor: Editor) {
@@ -51,29 +51,30 @@ import { useShallowObjectIdentity } from './useIdentity'
51
51
  /** @public */
52
52
  export interface TLEditorComponents {
53
53
  Background?: ComponentType | null
54
- SvgDefs?: ComponentType | null
55
54
  Brush?: ComponentType<TLBrushProps> | null
56
- ZoomBrush?: ComponentType<TLBrushProps> | null
57
- ShapeIndicators?: ComponentType | null
58
- ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
59
- Cursor?: ComponentType<TLCursorProps> | null
60
55
  Canvas?: ComponentType<TLCanvasComponentProps> | null
61
56
  CollaboratorBrush?: ComponentType<TLBrushProps> | null
62
57
  CollaboratorCursor?: ComponentType<TLCursorProps> | null
63
58
  CollaboratorHint?: ComponentType<TLCollaboratorHintProps> | null
59
+ CollaboratorScribble?: ComponentType<TLScribbleProps> | null
64
60
  CollaboratorShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
61
+ Cursor?: ComponentType<TLCursorProps> | null
65
62
  Grid?: ComponentType<TLGridProps> | null
66
- Scribble?: ComponentType<TLScribbleProps> | null
67
- CollaboratorScribble?: ComponentType<TLScribbleProps> | null
68
- SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
69
- Handles?: ComponentType<TLHandlesProps> | null
70
63
  Handle?: ComponentType<TLHandleProps> | null
71
- Spinner?: ComponentType | null
72
- SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
73
- SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
74
- OnTheCanvas?: ComponentType | null
64
+ Handles?: ComponentType<TLHandlesProps> | null
75
65
  InFrontOfTheCanvas?: ComponentType | null
76
66
  LoadingScreen?: ComponentType | null
67
+ OnTheCanvas?: ComponentType | null
68
+ Overlays?: ComponentType | null
69
+ Scribble?: ComponentType<TLScribbleProps> | null
70
+ SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
71
+ SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
72
+ ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
73
+ ShapeIndicators?: ComponentType | null
74
+ SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
75
+ Spinner?: ComponentType | null
76
+ SvgDefs?: ComponentType | null
77
+ ZoomBrush?: ComponentType<TLBrushProps> | null
77
78
 
78
79
  // These will always have defaults
79
80
  ErrorFallback?: TLErrorFallbackComponent
@@ -96,32 +97,35 @@ export function EditorComponentsProvider({
96
97
  const value = useMemo(
97
98
  (): Required<TLEditorComponents> => ({
98
99
  Background: DefaultBackground,
99
- SvgDefs: DefaultSvgDefs,
100
100
  Brush: DefaultBrush,
101
- ZoomBrush: DefaultBrush,
101
+ Canvas: DefaultCanvas,
102
102
  CollaboratorBrush: DefaultBrush,
103
- Cursor: DefaultCursor,
104
103
  CollaboratorCursor: DefaultCursor,
105
104
  CollaboratorHint: DefaultCollaboratorHint,
105
+ CollaboratorScribble: DefaultScribble,
106
106
  CollaboratorShapeIndicator: DefaultShapeIndicator,
107
+ Cursor: DefaultCursor,
107
108
  Grid: DefaultGrid,
109
+ Handle: DefaultHandle,
110
+ Handles: DefaultHandles,
111
+ InFrontOfTheCanvas: null,
112
+ LoadingScreen: DefaultLoadingScreen,
113
+ OnTheCanvas: null,
114
+ Overlays: null,
108
115
  Scribble: DefaultScribble,
116
+ SelectionBackground: DefaultSelectionBackground,
117
+ SelectionForeground: DefaultSelectionForeground,
118
+ ShapeIndicator: DefaultShapeIndicator,
119
+ ShapeIndicators: DefaultShapeIndicators,
109
120
  SnapIndicator: DefaultSnapIndicator,
110
- Handles: DefaultHandles,
111
- Handle: DefaultHandle,
112
- CollaboratorScribble: DefaultScribble,
121
+ Spinner: DefaultSpinner,
122
+ SvgDefs: DefaultSvgDefs,
123
+ ZoomBrush: DefaultBrush,
124
+
113
125
  ErrorFallback: DefaultErrorFallback,
114
126
  ShapeErrorFallback: DefaultShapeErrorFallback,
115
127
  ShapeIndicatorErrorFallback: DefaultShapeIndicatorErrorFallback,
116
- Spinner: DefaultSpinner,
117
- SelectionBackground: DefaultSelectionBackground,
118
- SelectionForeground: DefaultSelectionForeground,
119
- ShapeIndicators: DefaultShapeIndicators,
120
- ShapeIndicator: DefaultShapeIndicator,
121
- OnTheCanvas: null,
122
- InFrontOfTheCanvas: null,
123
- Canvas: DefaultCanvas,
124
- LoadingScreen: DefaultLoadingScreen,
128
+
125
129
  ..._overrides,
126
130
  }),
127
131
  [_overrides]
@@ -80,6 +80,10 @@ export interface TldrawOptions {
80
80
  * nonce to use in the editor's styles.
81
81
  */
82
82
  readonly nonce: string | undefined
83
+ /**
84
+ * Branding name of the app, currently only used for adding aria-label for the application.
85
+ */
86
+ readonly branding?: string
83
87
  }
84
88
 
85
89
  /** @public */
@@ -91,14 +91,14 @@ export const setStyleProperty = (
91
91
  elm.style.setProperty(property, value as string)
92
92
  }
93
93
 
94
- const INPUTS = ['input', 'select', 'button', 'textarea']
95
-
96
94
  /** @internal */
97
- export function activeElementShouldCaptureKeys() {
95
+ export function activeElementShouldCaptureKeys(allowButtons = false) {
98
96
  const { activeElement } = document
97
+ const elements = allowButtons ? ['input', 'textarea'] : ['input', 'select', 'button', 'textarea']
99
98
  return !!(
100
99
  activeElement &&
101
100
  ((activeElement as HTMLElement).isContentEditable ||
102
- INPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1)
101
+ elements.indexOf(activeElement.tagName.toLowerCase()) > -1 ||
102
+ activeElement.classList.contains('tlui-slider__thumb'))
103
103
  )
104
104
  }
@@ -26,11 +26,13 @@ export function getRotationSnapshot({
26
26
  return null
27
27
  }
28
28
 
29
- const pageCenter = rotatedPageBounds.center.clone().rotWith(rotatedPageBounds.point, rotation)
29
+ const initialPageCenter = rotatedPageBounds.center
30
+ .clone()
31
+ .rotWith(rotatedPageBounds.point, rotation)
30
32
 
31
33
  return {
32
- pageCenter,
33
- initialCursorAngle: pageCenter.angle(editor.inputs.originPagePoint),
34
+ initialPageCenter,
35
+ initialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),
34
36
  initialShapesRotation: rotation,
35
37
  shapeSnapshots: shapes.map((shape) => ({
36
38
  shape,
@@ -43,7 +45,7 @@ export function getRotationSnapshot({
43
45
  * @internal
44
46
  **/
45
47
  export interface TLRotationSnapshot {
46
- pageCenter: Vec
48
+ initialPageCenter: Vec
47
49
  initialCursorAngle: number
48
50
  initialShapesRotation: number
49
51
  shapeSnapshots: {
@@ -66,7 +68,7 @@ export function applyRotationToSnapshotShapes({
66
68
  stage: 'start' | 'update' | 'end' | 'one-off'
67
69
  centerOverride?: VecLike
68
70
  }) {
69
- const { pageCenter, shapeSnapshots } = snapshot
71
+ const { initialPageCenter, shapeSnapshots } = snapshot
70
72
 
71
73
  editor.updateShapes(
72
74
  shapeSnapshots.map(({ shape, initialPagePoint }) => {
@@ -77,7 +79,7 @@ export function applyRotationToSnapshotShapes({
77
79
  ? editor.getShapePageTransform(shape.parentId)!
78
80
  : Mat.Identity()
79
81
 
80
- const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? pageCenter, delta)
82
+ const newPagePoint = Vec.RotWith(initialPagePoint, centerOverride ?? initialPageCenter, delta)
81
83
 
82
84
  const newLocalPoint = Mat.applyToPoint(
83
85
  // use the current parent transform in case it has moved/resized since the start
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '3.13.0-canary.ca982decc2f2'
4
+ export const version = '3.13.0-canary.ce8e6cffa809'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-04-22T12:56:35.074Z',
8
- patch: '2025-04-22T12:56:35.074Z',
7
+ minor: '2025-04-29T15:35:44.232Z',
8
+ patch: '2025-04-29T15:35:44.232Z',
9
9
  }