@tldraw/editor 3.13.0-canary.8e04030e54fe → 3.13.0-canary.8f06b22abe77

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 (116) hide show
  1. package/dist-cjs/index.d.ts +97 -99
  2. package/dist-cjs/index.js +7 -22
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +10 -6
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  6. package/dist-cjs/lib/editor/Editor.js +92 -16
  7. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  8. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  9. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +1 -1
  10. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  11. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +0 -3
  12. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  14. package/dist-cjs/lib/hooks/useEditorComponents.js +1 -2
  15. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  16. package/dist-cjs/lib/primitives/Box.js +16 -0
  17. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  18. package/dist-cjs/lib/primitives/Mat.js +1 -1
  19. package/dist-cjs/lib/primitives/Mat.js.map +2 -2
  20. package/dist-cjs/lib/primitives/Vec.js +20 -0
  21. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  22. package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
  23. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  24. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  25. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  26. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  27. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  28. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  29. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  30. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  31. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  32. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
  33. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  34. package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
  35. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  36. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  37. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  38. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  39. package/dist-cjs/lib/utils/debug-flags.js +5 -2
  40. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  41. package/dist-cjs/version.js +3 -3
  42. package/dist-cjs/version.js.map +1 -1
  43. package/dist-esm/index.d.mts +97 -99
  44. package/dist-esm/index.mjs +9 -41
  45. package/dist-esm/index.mjs.map +2 -2
  46. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +10 -6
  47. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  48. package/dist-esm/lib/editor/Editor.mjs +92 -16
  49. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  50. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  51. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +1 -1
  52. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  53. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +0 -3
  54. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  55. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  56. package/dist-esm/lib/hooks/useEditorComponents.mjs +1 -4
  57. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  58. package/dist-esm/lib/primitives/Box.mjs +16 -0
  59. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  60. package/dist-esm/lib/primitives/Mat.mjs +1 -1
  61. package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
  62. package/dist-esm/lib/primitives/Vec.mjs +20 -0
  63. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  64. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  65. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  66. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
  67. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  68. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  69. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  70. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  71. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  72. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  73. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  74. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
  75. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  76. package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
  77. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  78. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  79. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  80. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  81. package/dist-esm/lib/utils/debug-flags.mjs +5 -2
  82. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  83. package/dist-esm/version.mjs +3 -3
  84. package/dist-esm/version.mjs.map +1 -1
  85. package/editor.css +36 -4
  86. package/package.json +7 -7
  87. package/src/index.ts +16 -31
  88. package/src/lib/components/default-components/DefaultCanvas.tsx +11 -6
  89. package/src/lib/editor/Editor.test.ts +1 -1
  90. package/src/lib/editor/Editor.ts +105 -16
  91. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
  92. package/src/lib/editor/shapes/ShapeUtil.ts +10 -2
  93. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +0 -4
  94. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
  95. package/src/lib/hooks/useEditorComponents.tsx +2 -5
  96. package/src/lib/primitives/Box.ts +20 -0
  97. package/src/lib/primitives/Mat.ts +5 -4
  98. package/src/lib/primitives/Vec.ts +23 -0
  99. package/src/lib/primitives/geometry/Arc2d.ts +5 -5
  100. package/src/lib/primitives/geometry/Circle2d.ts +4 -4
  101. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
  102. package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
  103. package/src/lib/primitives/geometry/Edge2d.ts +3 -3
  104. package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
  105. package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
  106. package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
  107. package/src/lib/primitives/geometry/Group2d.ts +70 -7
  108. package/src/lib/primitives/geometry/Point2d.ts +2 -2
  109. package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
  110. package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
  111. package/src/lib/test/currentToolIdMask.test.ts +1 -1
  112. package/src/lib/test/user.test.ts +1 -1
  113. package/src/lib/utils/debug-flags.ts +7 -2
  114. package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
  115. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
  116. package/src/version.ts +3 -3
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.8e04030e54fe",
4
+ "version": "3.13.0-canary.8f06b22abe77",
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.8e04030e54fe",
52
- "@tldraw/state-react": "3.13.0-canary.8e04030e54fe",
53
- "@tldraw/store": "3.13.0-canary.8e04030e54fe",
54
- "@tldraw/tlschema": "3.13.0-canary.8e04030e54fe",
55
- "@tldraw/utils": "3.13.0-canary.8e04030e54fe",
56
- "@tldraw/validate": "3.13.0-canary.8e04030e54fe",
51
+ "@tldraw/state": "3.13.0-canary.8f06b22abe77",
52
+ "@tldraw/state-react": "3.13.0-canary.8f06b22abe77",
53
+ "@tldraw/store": "3.13.0-canary.8f06b22abe77",
54
+ "@tldraw/tlschema": "3.13.0-canary.8f06b22abe77",
55
+ "@tldraw/utils": "3.13.0-canary.8f06b22abe77",
56
+ "@tldraw/validate": "3.13.0-canary.8f06b22abe77",
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'
@@ -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(),
@@ -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 })
@@ -326,7 +326,6 @@ export class Editor extends EventEmitter<TLEventMap> {
326
326
  this.options = { ...defaultTldrawOptions, ...options }
327
327
 
328
328
  this.store = store
329
- this.disposables.add(this.store.dispose.bind(this.store))
330
329
  this.history = new HistoryManager<TLRecord>({
331
330
  store,
332
331
  annotateError: (error) => {
@@ -956,6 +955,7 @@ export class Editor extends EventEmitter<TLEventMap> {
956
955
  dispose() {
957
956
  this.disposables.forEach((dispose) => dispose())
958
957
  this.disposables.clear()
958
+ this.store.dispose()
959
959
  this.isDisposed = true
960
960
  }
961
961
 
@@ -1815,9 +1815,28 @@ export class Editor extends EventEmitter<TLEventMap> {
1815
1815
  return this
1816
1816
  }
1817
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
+ */
1818
1828
  selectAdjacentShape(direction: TLAdjacentDirection) {
1819
- const readingOrderShapes = this.getCurrentPageShapesInReadingOrder()
1820
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()
1821
1840
  const currentShapeId: TLShapeId | undefined =
1822
1841
  selectedShapeIds.length === 1
1823
1842
  ? selectedShapeIds[0]
@@ -1839,13 +1858,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1839
1858
  const shape = this.getShape(adjacentShapeId)
1840
1859
  if (!shape) return
1841
1860
 
1842
- this.setSelectedShapes([shape.id])
1843
- this.zoomToSelectionIfOffscreen(256, {
1844
- animation: {
1845
- duration: this.options.animationMediumMs,
1846
- },
1847
- inset: 0,
1848
- })
1861
+ this._selectShapesAndZoom([shape.id])
1849
1862
  }
1850
1863
 
1851
1864
  /**
@@ -1855,10 +1868,14 @@ export class Editor extends EventEmitter<TLEventMap> {
1855
1868
  * @public
1856
1869
  */
1857
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[] {
1858
1876
  const SHALLOW_ANGLE = 20
1859
1877
  const ROW_THRESHOLD = 100
1860
1878
 
1861
- const shapes = this.getCurrentPageShapes()
1862
1879
  const tabbableShapes = shapes.filter((shape) => this.getShapeUtil(shape).canTabTo(shape))
1863
1880
 
1864
1881
  if (tabbableShapes.length <= 1) return tabbableShapes
@@ -2004,6 +2021,36 @@ export class Editor extends EventEmitter<TLEventMap> {
2004
2021
  return lowestScoringShape!.shape.id
2005
2022
  }
2006
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
+
2007
2054
  /**
2008
2055
  * Clear the selection.
2009
2056
  *
@@ -2276,13 +2323,21 @@ export class Editor extends EventEmitter<TLEventMap> {
2276
2323
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2277
2324
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2278
2325
  this.setRichTextEditor(null)
2279
- if (id !== this.getEditingShapeId()) {
2326
+ const prevEditingShapeId = this.getEditingShapeId()
2327
+ if (id !== prevEditingShapeId) {
2280
2328
  if (id) {
2281
2329
  const shape = this.getShape(id)
2282
2330
  if (shape && this.getShapeUtil(shape).canEdit(shape)) {
2283
2331
  this.run(
2284
2332
  () => {
2285
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)
2286
2341
  },
2287
2342
  { history: 'ignore' }
2288
2343
  )
@@ -2295,6 +2350,12 @@ export class Editor extends EventEmitter<TLEventMap> {
2295
2350
  () => {
2296
2351
  this._updateCurrentPageState({ editingShapeId: null })
2297
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
+ }
2298
2359
  },
2299
2360
  { history: 'ignore' }
2300
2361
  )
@@ -10223,6 +10284,36 @@ export class Editor extends EventEmitter<TLEventMap> {
10223
10284
  const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
10224
10285
  const { x: dx, y: dy, z: dz = 0 } = info.delta
10225
10286
 
10287
+ if (this.inputs.keys.has('KeyV')) {
10288
+ const selectedShapeIds = this.getSelectedShapeIds()
10289
+ if (selectedShapeIds.length > 0) {
10290
+ const instanceState = this.getInstanceState()
10291
+ if (!instanceState.isChangingStyle) {
10292
+ this.updateInstanceState({ isChangingStyle: true })
10293
+ }
10294
+ clearTimeout(this._isChangingStyleTimeout)
10295
+ this._isChangingStyleTimeout = this.timers.setTimeout(() => {
10296
+ this._updateInstanceState({ isChangingStyle: false }, { history: 'ignore' })
10297
+ }, 1000)
10298
+ this.updateShapes(
10299
+ compact(
10300
+ selectedShapeIds.map((id) => {
10301
+ const shape = this.getShape(id)
10302
+ if (!shape) return
10303
+ if (hasOwnProperty(shape.props, 'scale')) {
10304
+ const scale = (shape.props as any)['scale'] as number
10305
+ const change = info.delta.y / 100
10306
+ const next = scale + change
10307
+
10308
+ return { ...shape, props: { scale: Math.max(0.01, next) } }
10309
+ }
10310
+ })
10311
+ )
10312
+ )
10313
+ return
10314
+ }
10315
+ }
10316
+
10226
10317
  let behavior = wheelBehavior
10227
10318
 
10228
10319
  // If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
@@ -10343,12 +10434,10 @@ export class Editor extends EventEmitter<TLEventMap> {
10343
10434
  if (this.inputs.isPanning && this.inputs.isPointing) {
10344
10435
  // Handle spacebar / middle mouse button panning
10345
10436
  const { currentScreenPoint, previousScreenPoint } = this.inputs
10346
- const { panSpeed } = cameraOptions
10347
10437
  const offset = Vec.Sub(currentScreenPoint, previousScreenPoint)
10348
- this.setCamera(
10349
- new Vec(cx + (offset.x * panSpeed) / cz, cy + (offset.y * panSpeed) / cz, cz),
10350
- { immediate: true }
10351
- )
10438
+ this.setCamera(new Vec(cx + offset.x / cz, cy + offset.y / cz, cz), {
10439
+ immediate: true,
10440
+ })
10352
10441
  this.maybeTrackPerformance('Panning')
10353
10442
  return
10354
10443
  }
@@ -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
@@ -245,7 +245,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
245
245
  *
246
246
  * @public
247
247
  */
248
- canEditInReadOnly(_shape: Shape): boolean {
248
+ canEditInReadonly(_shape: Shape): boolean {
249
249
  return false
250
250
  }
251
251
 
@@ -707,7 +707,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
707
707
  onClick?(shape: Shape): TLShapePartial<Shape> | void
708
708
 
709
709
  /**
710
- * A callback called when a shape finishes being editing.
710
+ * A callback called when a shape starts being edited.
711
+ *
712
+ * @param shape - The shape.
713
+ * @public
714
+ */
715
+ onEditStart?(shape: Shape): void
716
+
717
+ /**
718
+ * A callback called when a shape finishes being edited.
711
719
  *
712
720
  * @param shape - The shape.
713
721
  * @public
@@ -12,10 +12,6 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
12
12
  static override props = groupShapeProps
13
13
  static override migrations = groupShapeMigrations
14
14
 
15
- override canTabTo() {
16
- return false
17
- }
18
-
19
15
  override hideSelectionBoundsFg() {
20
16
  return true
21
17
  }
@@ -4,15 +4,15 @@ import { TLDefaultDashStyle } from '@tldraw/tlschema'
4
4
  export function getPerfectDashProps(
5
5
  totalLength: number,
6
6
  strokeWidth: number,
7
- opts = {} as Partial<{
8
- style: TLDefaultDashStyle
9
- snap: number
10
- end: 'skip' | 'outset' | 'none'
11
- start: 'skip' | 'outset' | 'none'
12
- lengthRatio: number
13
- closed: boolean
14
- forceSolid: boolean
15
- }>
7
+ opts: {
8
+ style?: TLDefaultDashStyle
9
+ snap?: number
10
+ end?: 'skip' | 'outset' | 'none'
11
+ start?: 'skip' | 'outset' | 'none'
12
+ lengthRatio?: number
13
+ closed?: boolean
14
+ forceSolid?: boolean
15
+ } = {}
16
16
  ): {
17
17
  strokeDasharray: string
18
18
  strokeDashoffset: string
@@ -19,10 +19,7 @@ import { DefaultHandle, TLHandleProps } from '../components/default-components/D
19
19
  import { DefaultHandles, TLHandlesProps } from '../components/default-components/DefaultHandles'
20
20
  import { DefaultLoadingScreen } from '../components/default-components/DefaultLoadingScreen'
21
21
  import { DefaultScribble, TLScribbleProps } from '../components/default-components/DefaultScribble'
22
- import {
23
- DefaultSelectionBackground,
24
- TLSelectionBackgroundProps,
25
- } from '../components/default-components/DefaultSelectionBackground'
22
+ import { TLSelectionBackgroundProps } from '../components/default-components/DefaultSelectionBackground'
26
23
  import {
27
24
  DefaultSelectionForeground,
28
25
  TLSelectionForegroundProps,
@@ -113,7 +110,7 @@ export function EditorComponentsProvider({
113
110
  OnTheCanvas: null,
114
111
  Overlays: null,
115
112
  Scribble: DefaultScribble,
116
- SelectionBackground: DefaultSelectionBackground,
113
+ SelectionBackground: null,
117
114
  SelectionForeground: DefaultSelectionForeground,
118
115
  ShapeIndicator: DefaultShapeIndicator,
119
116
  ShapeIndicators: DefaultShapeIndicators,
@@ -57,6 +57,11 @@ export class Box {
57
57
  this.x = n
58
58
  }
59
59
 
60
+ // eslint-disable-next-line no-restricted-syntax
61
+ get left() {
62
+ return this.x
63
+ }
64
+
60
65
  // eslint-disable-next-line no-restricted-syntax
61
66
  get midX() {
62
67
  return this.x + this.w / 2
@@ -67,6 +72,11 @@ export class Box {
67
72
  return this.x + this.w
68
73
  }
69
74
 
75
+ // eslint-disable-next-line no-restricted-syntax
76
+ get right() {
77
+ return this.x + this.w
78
+ }
79
+
70
80
  // eslint-disable-next-line no-restricted-syntax
71
81
  get minY() {
72
82
  return this.y
@@ -77,6 +87,11 @@ export class Box {
77
87
  this.y = n
78
88
  }
79
89
 
90
+ // eslint-disable-next-line no-restricted-syntax
91
+ get top() {
92
+ return this.y
93
+ }
94
+
80
95
  // eslint-disable-next-line no-restricted-syntax
81
96
  get midY() {
82
97
  return this.y + this.h / 2
@@ -87,6 +102,11 @@ export class Box {
87
102
  return this.y + this.h
88
103
  }
89
104
 
105
+ // eslint-disable-next-line no-restricted-syntax
106
+ get bottom() {
107
+ return this.y + this.h
108
+ }
109
+
90
110
  // eslint-disable-next-line no-restricted-syntax
91
111
  get width() {
92
112
  return this.w
@@ -157,12 +157,13 @@ export class Mat {
157
157
  return Mat.Compose(Mat.Translate(cx, cy!), rotationMatrix, Mat.Translate(-cx, -cy!))
158
158
  }
159
159
 
160
- static Scale(x: number, y: number): MatModel
161
- static Scale(x: number, y: number, cx: number, cy: number): MatModel
162
- static Scale(x: number, y: number, cx?: number, cy?: number): MatModel {
160
+ static Scale(x: number, y: number): Mat
161
+ static Scale(x: number, y: number, cx: number, cy: number): Mat
162
+ static Scale(x: number, y: number, cx?: number, cy?: number): Mat {
163
163
  const scaleMatrix = new Mat(x, 0, 0, y, 0, 0)
164
164
  if (cx === undefined) return scaleMatrix
165
- return Mat.Compose(Mat.Translate(cx, cy!), scaleMatrix, Mat.Translate(-cx, -cy!))
165
+
166
+ return Mat.Translate(cx, cy!).multiply(scaleMatrix).translate(-cx, -cy!)
166
167
  }
167
168
  static Multiply(m1: MatModel, m2: MatModel): MatModel {
168
169
  return {
@@ -319,6 +319,11 @@ export class Vec {
319
319
  return ((A.y - B.y) ** 2 + (A.x - B.x) ** 2) ** 0.5
320
320
  }
321
321
 
322
+ // Get the Manhattan distance between two points.
323
+ static ManhattanDist(A: VecLike, B: VecLike): number {
324
+ return Math.abs(A.x - B.x) + Math.abs(A.y - B.y)
325
+ }
326
+
322
327
  // Get whether a distance between two points is less than a number. This is faster to calulate than using `Vec.Dist(a, b) < n`.
323
328
  static DistMin(A: VecLike, B: VecLike, n: number): boolean {
324
329
  return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y) < n ** 2
@@ -465,10 +470,28 @@ export class Vec {
465
470
  return isNaN(A.x) || isNaN(A.y)
466
471
  }
467
472
 
473
+ /**
474
+ * Get the angle from position A to position B.
475
+ */
468
476
  static Angle(A: VecLike, B: VecLike): number {
469
477
  return Math.atan2(B.y - A.y, B.x - A.x)
470
478
  }
471
479
 
480
+ /**
481
+ * Get the angle between vector A and vector B. This will return the smallest angle between the
482
+ * two vectors, between -π and π. The sign indicates direction of angle.
483
+ */
484
+ static AngleBetween(A: VecLike, B: VecLike): number {
485
+ const p = A.x * B.x + A.y * B.y
486
+ const n = Math.sqrt(
487
+ (Math.pow(A.x, 2) + Math.pow(A.y, 2)) * (Math.pow(B.x, 2) + Math.pow(B.y, 2))
488
+ )
489
+ const sign = A.x * B.y - A.y * B.x < 0 ? -1 : 1
490
+ const angle = sign * Math.acos(p / n)
491
+
492
+ return angle
493
+ }
494
+
472
495
  /**
473
496
  * Linearly interpolate between two points.
474
497
  * @param A - The first point.