@tldraw/editor 3.14.0-canary.c76a772c4700 → 3.14.0-canary.cad451c26011

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 (74) hide show
  1. package/dist-cjs/index.d.ts +122 -16
  2. package/dist-cjs/index.js +4 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +81 -25
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +3 -1
  7. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  8. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +3 -2
  9. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  10. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +0 -10
  11. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +13 -6
  13. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
  14. package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
  15. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  16. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  17. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  18. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
  19. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  20. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
  21. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  22. package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -6
  23. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  24. package/dist-cjs/lib/utils/dom.js +1 -1
  25. package/dist-cjs/lib/utils/dom.js.map +2 -2
  26. package/dist-cjs/lib/utils/reparenting.js +232 -0
  27. package/dist-cjs/lib/utils/reparenting.js.map +7 -0
  28. package/dist-cjs/version.js +4 -4
  29. package/dist-cjs/version.js.map +1 -1
  30. package/dist-esm/index.d.mts +122 -16
  31. package/dist-esm/index.mjs +4 -1
  32. package/dist-esm/index.mjs.map +2 -2
  33. package/dist-esm/lib/editor/Editor.mjs +81 -25
  34. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  35. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +3 -1
  36. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  37. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +3 -2
  38. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  39. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
  40. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  41. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +13 -6
  42. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  43. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  44. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  45. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  46. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  47. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
  48. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  49. package/dist-esm/lib/primitives/geometry/Group2d.mjs +11 -6
  50. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  51. package/dist-esm/lib/utils/dom.mjs +1 -1
  52. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  53. package/dist-esm/lib/utils/reparenting.mjs +216 -0
  54. package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
  55. package/dist-esm/version.mjs +4 -4
  56. package/dist-esm/version.mjs.map +1 -1
  57. package/editor.css +34 -12
  58. package/package.json +7 -7
  59. package/src/index.ts +6 -0
  60. package/src/lib/editor/Editor.ts +101 -36
  61. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +3 -1
  62. package/src/lib/editor/managers/TextManager/TextManager.ts +4 -2
  63. package/src/lib/editor/shapes/ShapeUtil.ts +47 -15
  64. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +25 -17
  65. package/src/lib/editor/tools/StateNode.ts +3 -3
  66. package/src/lib/editor/types/emit-types.ts +4 -0
  67. package/src/lib/editor/types/external-content.ts +11 -2
  68. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  69. package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
  70. package/src/lib/primitives/geometry/Group2d.ts +11 -5
  71. package/src/lib/utils/dom.ts +1 -1
  72. package/src/lib/utils/reparenting.ts +383 -0
  73. package/src/version.ts +4 -4
  74. package/CHANGELOG.md +0 -4327
package/editor.css CHANGED
@@ -176,8 +176,7 @@
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-error: hsl(0, 65%, 51%);
180
- --color-warn: hsl(0, 90%, 43%);
179
+ --color-danger: hsl(0, 90%, 43%);
181
180
  --color-laser: hsl(0, 100%, 50%);
182
181
  /* Shadows */
183
182
  --shadow-1: 0px 1px 2px hsl(0, 0%, 0%, 25%), 0px 1px 3px hsl(0, 0%, 0%, 9%);
@@ -232,8 +231,7 @@
232
231
  --color-success: hsl(123, 38%, 57%);
233
232
  --color-info: hsl(199, 92%, 56%);
234
233
  --color-warning: hsl(36, 100%, 57%);
235
- --color-error: hsl(4, 90%, 58%);
236
- --color-warn: hsl(0, 81%, 66%);
234
+ --color-danger: hsl(0, 82%, 66%);
237
235
  --color-laser: hsl(0, 100%, 50%);
238
236
  /* Shadows */
239
237
  --shadow-1:
@@ -247,6 +245,13 @@
247
245
  inset 0px 0px 0px 1px var(--color-panel-contrast);
248
246
  }
249
247
 
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
+
250
255
  .tl-container,
251
256
  .tl-container * {
252
257
  -webkit-touch-callout: none;
@@ -575,6 +580,23 @@ input,
575
580
 
576
581
  /* ---------------------- Text ---------------------- */
577
582
 
583
+ .tl-text-shape-label {
584
+ position: relative;
585
+ font-weight: normal;
586
+ min-width: 1px;
587
+ padding: 0px;
588
+ margin: 0px;
589
+ border: none;
590
+ width: fit-content;
591
+ height: fit-content;
592
+ font-variant: normal;
593
+ font-style: normal;
594
+ pointer-events: all;
595
+ white-space: pre-wrap;
596
+ overflow-wrap: break-word;
597
+ text-shadow: var(--tl-text-outline);
598
+ }
599
+
578
600
  .tl-text-wrapper[data-font='draw'] {
579
601
  font-family: var(--tl-font-draw);
580
602
  }
@@ -1040,8 +1062,8 @@ input,
1040
1062
  }
1041
1063
 
1042
1064
  .tl-hyperlink__icon {
1043
- width: 16px;
1044
- height: 16px;
1065
+ width: 15px;
1066
+ height: 15px;
1045
1067
  background-color: currentColor;
1046
1068
  pointer-events: none;
1047
1069
  }
@@ -1140,7 +1162,7 @@ input,
1140
1162
  stroke-linejoin: round;
1141
1163
  /* content-visibility: auto; */
1142
1164
  transform-origin: top left;
1143
- color: inherit;
1165
+ color: var(--color-text-1);
1144
1166
  }
1145
1167
 
1146
1168
  /* -------------------- Group shape ------------------ */
@@ -1255,6 +1277,7 @@ input,
1255
1277
  display: flex;
1256
1278
  justify-content: flex-end;
1257
1279
  align-items: flex-start;
1280
+ box-shadow: inset 0px 0px 0px 1px var(--color-divider);
1258
1281
  }
1259
1282
 
1260
1283
  .tl-bookmark__image_container > .tl-hyperlink-button::after {
@@ -1277,7 +1300,7 @@ input,
1277
1300
  }
1278
1301
 
1279
1302
  .tl-bookmark__copy_container {
1280
- background-color: var(--color-muted);
1303
+ background-color: var(--color-muted-0);
1281
1304
  padding: var(--space-4);
1282
1305
  pointer-events: all;
1283
1306
  display: flex;
@@ -1296,11 +1319,11 @@ input,
1296
1319
 
1297
1320
  .tl-bookmark__heading {
1298
1321
  font-size: 16px;
1299
- line-height: 1.5;
1322
+ line-height: 1.6;
1300
1323
  font-weight: bold;
1301
1324
  padding-bottom: var(--space-2);
1302
1325
  overflow: hidden;
1303
- max-height: calc((16px * 1.5) * 2);
1326
+ max-height: calc((16px * 1.6) * 2);
1304
1327
  -webkit-box-orient: vertical;
1305
1328
  -webkit-line-clamp: 2;
1306
1329
  line-clamp: 2;
@@ -1381,7 +1404,6 @@ input,
1381
1404
 
1382
1405
  .tl-image-container {
1383
1406
  position: relative;
1384
- overflow: hidden;
1385
1407
  }
1386
1408
 
1387
1409
  .tl-image {
@@ -1690,7 +1712,7 @@ it from receiving any pointer events or affecting the cursor. */
1690
1712
  gap: var(--space-4);
1691
1713
  }
1692
1714
  .tl-error-boundary__content .tl-error-boundary__reset {
1693
- color: var(--color-warn);
1715
+ color: var(--color-danger);
1694
1716
  }
1695
1717
  .tl-error-boundary__content .tl-error-boundary__refresh {
1696
1718
  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.c76a772c4700",
4
+ "version": "3.14.0-canary.cad451c26011",
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.c76a772c4700",
52
- "@tldraw/state-react": "3.14.0-canary.c76a772c4700",
53
- "@tldraw/store": "3.14.0-canary.c76a772c4700",
54
- "@tldraw/tlschema": "3.14.0-canary.c76a772c4700",
55
- "@tldraw/utils": "3.14.0-canary.c76a772c4700",
56
- "@tldraw/validate": "3.14.0-canary.c76a772c4700",
51
+ "@tldraw/state": "3.14.0-canary.cad451c26011",
52
+ "@tldraw/state-react": "3.14.0-canary.cad451c26011",
53
+ "@tldraw/store": "3.14.0-canary.cad451c26011",
54
+ "@tldraw/tlschema": "3.14.0-canary.cad451c26011",
55
+ "@tldraw/utils": "3.14.0-canary.cad451c26011",
56
+ "@tldraw/validate": "3.14.0-canary.cad451c26011",
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,6 +182,10 @@ 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,
185
189
  type TLGeometryOpts,
186
190
  type TLHandleDragInfo,
187
191
  type TLResizeInfo,
@@ -253,6 +257,7 @@ export {
253
257
  type TLExternalContent,
254
258
  type TLExternalContentSource,
255
259
  type TLFileExternalAsset,
260
+ type TLFileReplaceExternalContent,
256
261
  type TLFilesExternalContent,
257
262
  type TLSvgTextExternalContent,
258
263
  type TLTextExternalContent,
@@ -445,6 +450,7 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
445
450
  export { isAccelKey } from './lib/utils/keyboard'
446
451
  export { normalizeWheel } from './lib/utils/normalizeWheel'
447
452
  export { refreshPage } from './lib/utils/refreshPage'
453
+ export { getDroppedShapesToNewParents, kickoutOccludedShapes } from './lib/utils/reparenting'
448
454
  export {
449
455
  getFontsFromRichText,
450
456
  type RichTextFontVisitor,
@@ -2122,6 +2122,20 @@ export class Editor extends EventEmitter<TLEventMap> {
2122
2122
  return this.getShapesPageBounds(this.getSelectedShapeIds())
2123
2123
  }
2124
2124
 
2125
+ /**
2126
+ * The bounds of the selection bounding box in the current page space.
2127
+ *
2128
+ * @readonly
2129
+ * @public
2130
+ */
2131
+ getSelectionScreenBounds(): Box | undefined {
2132
+ const bounds = this.getSelectionPageBounds()
2133
+ if (!bounds) return undefined
2134
+ const { x, y } = this.pageToScreen(bounds.point)
2135
+ const zoom = this.getZoomLevel()
2136
+ return new Box(x, y, bounds.width * zoom, bounds.height * zoom)
2137
+ }
2138
+
2125
2139
  /**
2126
2140
  * @internal
2127
2141
  */
@@ -3648,7 +3662,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3648
3662
  * @public
3649
3663
  */
3650
3664
  updateViewportScreenBounds(screenBounds: Box | HTMLElement, center = false): this {
3651
- if (screenBounds instanceof HTMLElement) {
3665
+ if (!(screenBounds instanceof Box)) {
3652
3666
  const rect = screenBounds.getBoundingClientRect()
3653
3667
  screenBounds = new Box(
3654
3668
  rect.left || rect.x,
@@ -5514,7 +5528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5514
5528
  if (!id) return undefined
5515
5529
  const freshShape = this.getShape(id)
5516
5530
  if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
5517
- return this.store.get(freshShape.parentId)
5531
+ return this.getShape(freshShape.parentId)
5518
5532
  }
5519
5533
 
5520
5534
  /**
@@ -5697,6 +5711,10 @@ export class Editor extends EventEmitter<TLEventMap> {
5697
5711
  const newPoint = invertedParentTransform.applyToPoint(pagePoint)
5698
5712
  const newRotation = pageTransform.rotation() - parentPageRotation
5699
5713
 
5714
+ if (shape.id === parentId) {
5715
+ throw Error('Attempted to reparent a shape to itself!')
5716
+ }
5717
+
5700
5718
  changes.push({
5701
5719
  id: shape.id,
5702
5720
  type: shape.type,
@@ -5800,6 +5818,11 @@ export class Editor extends EventEmitter<TLEventMap> {
5800
5818
  return shapeIds
5801
5819
  }
5802
5820
 
5821
+ /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5822
+ getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5823
+ return this.getDraggingOverShape(point, droppingShapes)
5824
+ }
5825
+
5803
5826
  /**
5804
5827
  * Get the shape that some shapes should be dropped on at a given point.
5805
5828
  *
@@ -5810,35 +5833,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5810
5833
  *
5811
5834
  * @public
5812
5835
  */
5813
- getDroppingOverShape(point: VecLike, droppingShapes: TLShape[] = []) {
5814
- // starting from the top...
5815
- const currentPageShapesSorted = this.getCurrentPageShapesSorted()
5816
- for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
5817
- const shape = currentPageShapesSorted[i]
5818
-
5819
- if (
5820
- // ignore hidden shapes
5821
- this.isShapeHidden(shape) ||
5822
- // don't allow dropping on selected shapes
5823
- this.getSelectedShapeIds().includes(shape.id) ||
5824
- // only allow shapes that can receive children
5825
- !this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
5826
- // don't allow dropping a shape on itself or one of it's children
5827
- droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
5828
- ) {
5829
- continue
5830
- }
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
+ )
5831
5841
 
5832
- // Only allow dropping into the masked page bounds of the shape, e.g. when a frame is
5833
- // partially clipped by its own parent frame
5834
- const maskedPageBounds = this.getShapeMaskedPageBounds(shape.id)
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
+ )
5835
5852
 
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
5836
5856
  if (
5837
- maskedPageBounds &&
5838
- maskedPageBounds.containsPoint(point) &&
5839
- this.getShapeGeometry(shape).hitTestPoint(this.getPointInShapeSpace(shape, point), 0, true)
5857
+ shapeUtil.onDragShapesOver ||
5858
+ shapeUtil.onDragShapesIn ||
5859
+ shapeUtil.onDragShapesOut ||
5860
+ shapeUtil.onDropShapesOver
5840
5861
  ) {
5841
- return shape
5862
+ return maybeDraggingOverShape
5842
5863
  }
5843
5864
  }
5844
5865
  }
@@ -6197,11 +6218,12 @@ export class Editor extends EventEmitter<TLEventMap> {
6197
6218
  */
6198
6219
  duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
6199
6220
  this.run(() => {
6200
- const ids =
6221
+ const _ids =
6201
6222
  typeof shapes[0] === 'string'
6202
6223
  ? (shapes as TLShapeId[])
6203
6224
  : (shapes as TLShape[]).map((s) => s.id)
6204
6225
 
6226
+ const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
6205
6227
  if (ids.length <= 0) return this
6206
6228
 
6207
6229
  const initialIds = new Set(ids)
@@ -6281,10 +6303,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6281
6303
  })
6282
6304
  const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
6283
6305
 
6284
- const maxShapesReached =
6285
- shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
6286
-
6287
- if (maxShapesReached) {
6306
+ if (!this.canCreateShapes(shapesToCreate)) {
6288
6307
  alertMaxShapes(this)
6289
6308
  return
6290
6309
  }
@@ -7713,6 +7732,32 @@ export class Editor extends EventEmitter<TLEventMap> {
7713
7732
  return {}
7714
7733
  }
7715
7734
 
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
+
7716
7761
  /**
7717
7762
  * Create a single shape.
7718
7763
  *
@@ -7759,6 +7804,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7759
7804
  if (maxShapesReached) {
7760
7805
  // can't create more shapes than fit on the page
7761
7806
  alertMaxShapes(this)
7807
+ // todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
7762
7808
  return this
7763
7809
  }
7764
7810
 
@@ -7791,9 +7837,10 @@ export class Editor extends EventEmitter<TLEventMap> {
7791
7837
 
7792
7838
  for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7793
7839
  const parent = currentPageShapesSorted[i]
7840
+ const util = this.getShapeUtil(parent)
7794
7841
  if (
7842
+ util.canReceiveNewChildrenOfType(parent, partial.type) &&
7795
7843
  !this.isShapeHidden(parent) &&
7796
- this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
7797
7844
  this.isPointInShape(
7798
7845
  parent,
7799
7846
  // If no parent is provided, then we can treat the
@@ -7812,7 +7859,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7812
7859
 
7813
7860
  const prevParentId = partial.parentId
7814
7861
 
7815
- // a shape cannot be it's own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7862
+ // a shape cannot be its own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7816
7863
  if (parentId === partial.id) {
7817
7864
  parentId = focusedGroupId
7818
7865
  }
@@ -7922,6 +7969,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7922
7969
  }
7923
7970
  })
7924
7971
 
7972
+ this.emit('created-shapes', shapeRecordsToCreate)
7973
+ this.emit('edit')
7925
7974
  this.store.put(shapeRecordsToCreate)
7926
7975
  })
7927
7976
 
@@ -8316,6 +8365,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8316
8365
  updates.push(updated)
8317
8366
  }
8318
8367
 
8368
+ this.emit('edited-shapes', updates)
8369
+ this.emit('edit')
8319
8370
  this.store.put(updates)
8320
8371
  })
8321
8372
  }
@@ -8365,6 +8416,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8365
8416
  })
8366
8417
  }
8367
8418
 
8419
+ this.emit('deleted-shapes', [...allShapeIdsToDelete])
8420
+ this.emit('edit')
8368
8421
  return this.run(() => this.store.remove([...allShapeIdsToDelete]))
8369
8422
  }
8370
8423
 
@@ -8813,6 +8866,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8813
8866
  } = {
8814
8867
  text: null,
8815
8868
  files: null,
8869
+ 'file-replace': null,
8816
8870
  embed: null,
8817
8871
  'svg-text': null,
8818
8872
  url: null,
@@ -8862,6 +8916,15 @@ export class Editor extends EventEmitter<TLEventMap> {
8862
8916
  return this.externalContentHandlers[info.type]?.(info as any)
8863
8917
  }
8864
8918
 
8919
+ /**
8920
+ * Handle replacing external content.
8921
+ *
8922
+ * @param info - Info about the external content.
8923
+ */
8924
+ async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8925
+ return this.externalContentHandlers[info.type]?.(info as any)
8926
+ }
8927
+
8865
8928
  /**
8866
8929
  * Get content that can be exported for the given shape ids.
8867
8930
  *
@@ -9481,6 +9544,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9481
9544
  previousPagePoint,
9482
9545
  currentScreenPoint,
9483
9546
  currentPagePoint,
9547
+ originScreenPoint,
9548
+ originPagePoint,
9484
9549
  } = this.inputs
9485
9550
 
9486
9551
  const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -9509,8 +9574,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9509
9574
  // Reset velocity on pointer down, or when a pinch starts or ends
9510
9575
  if (info.name === 'pointer_down' || this.inputs.isPinching) {
9511
9576
  pointerVelocity.set(0, 0)
9512
- this.inputs.originScreenPoint.setTo(currentScreenPoint)
9513
- this.inputs.originPagePoint.setTo(currentPagePoint)
9577
+ originScreenPoint.setTo(currentScreenPoint)
9578
+ originPagePoint.setTo(currentPagePoint)
9514
9579
  }
9515
9580
 
9516
9581
  // todo: We only have to do this if there are multiple users in the document
@@ -241,7 +241,9 @@ export class HistoryManager<R extends UnknownRecord> {
241
241
  }
242
242
 
243
243
  bailToMark(id: string) {
244
- this._undo({ pushToRedoStack: false, toMark: id })
244
+ if (id) {
245
+ this._undo({ pushToRedoStack: false, toMark: id })
246
+ }
245
247
 
246
248
  return this
247
249
  }
@@ -26,6 +26,7 @@ export interface TLMeasureTextOpts {
26
26
  fontWeight: string
27
27
  fontFamily: string
28
28
  fontSize: number
29
+ /** This must be a number, e.g. 1.35, not a pixel value. */
29
30
  lineHeight: number
30
31
  /**
31
32
  * When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
@@ -75,6 +76,7 @@ export class TextManager {
75
76
  // we need to save the default styles so that we can restore them when we're done
76
77
  // these must be the css names, not the js names for the styles
77
78
  this.defaultStyles = {
79
+ 'overflow-wrap': 'break-word',
78
80
  'word-break': 'auto',
79
81
  width: null,
80
82
  height: null,
@@ -123,7 +125,7 @@ export class TextManager {
123
125
  elm.style.setProperty('font-style', opts.fontStyle)
124
126
  elm.style.setProperty('font-weight', opts.fontWeight)
125
127
  elm.style.setProperty('font-size', opts.fontSize + 'px')
126
- elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
128
+ elm.style.setProperty('line-height', opts.lineHeight.toString())
127
129
  elm.style.setProperty('padding', opts.padding)
128
130
 
129
131
  if (opts.maxWidth) {
@@ -287,7 +289,7 @@ export class TextManager {
287
289
  elm.style.setProperty('font-style', opts.fontStyle)
288
290
  elm.style.setProperty('font-weight', opts.fontWeight)
289
291
  elm.style.setProperty('font-size', opts.fontSize + 'px')
290
- elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
292
+ elm.style.setProperty('line-height', opts.lineHeight.toString())
291
293
 
292
294
  const elementWidth = Math.ceil(opts.width - opts.padding * 2)
293
295
  elm.style.setProperty('width', `${elementWidth}px`)
@@ -4,12 +4,15 @@ import { LegacyMigrations, MigrationSequence } from '@tldraw/store'
4
4
  import {
5
5
  RecordProps,
6
6
  TLHandle,
7
+ TLParentId,
7
8
  TLPropsMigrations,
8
9
  TLShape,
9
10
  TLShapeCrop,
11
+ TLShapeId,
10
12
  TLShapePartial,
11
13
  TLUnknownShape,
12
14
  } from '@tldraw/tlschema'
15
+ import { IndexKey } from '@tldraw/utils'
13
16
  import { ReactElement } from 'react'
14
17
  import { Box, SelectionHandle } from '../../primitives/Box'
15
18
  import { Vec } from '../../primitives/Vec'
@@ -387,17 +390,6 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
387
390
  return false
388
391
  }
389
392
 
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
-
401
393
  /**
402
394
  * Get the shape as an SVG object.
403
395
  *
@@ -517,7 +509,16 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
517
509
  ): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void
518
510
 
519
511
  /**
520
- * A callback called when some other shapes are dragged over this one.
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.
521
522
  *
522
523
  * @example
523
524
  *
@@ -531,7 +532,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
531
532
  * @param shapes - The shapes that are being dragged over this one.
532
533
  * @public
533
534
  */
534
- onDragShapesOver?(shape: Shape, shapes: TLShape[]): void
535
+ onDragShapesOver?(shape: Shape, shapes: TLShape[], info: TLDragShapesOverInfo): void
535
536
 
536
537
  /**
537
538
  * A callback called when some other shapes are dragged out of this one.
@@ -540,7 +541,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
540
541
  * @param shapes - The shapes that are being dragged out.
541
542
  * @public
542
543
  */
543
- onDragShapesOut?(shape: Shape, shapes: TLShape[]): void
544
+ onDragShapesOut?(shape: Shape, shapes: TLShape[], info: TLDragShapesOutInfo): void
544
545
 
545
546
  /**
546
547
  * A callback called when some other shapes are dropped over this one.
@@ -549,7 +550,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
549
550
  * @param shapes - The shapes that are being dropped over this one.
550
551
  * @public
551
552
  */
552
- onDropShapesOver?(shape: Shape, shapes: TLShape[]): void
553
+ onDropShapesOver?(shape: Shape, shapes: TLShape[], info: TLDropShapesOverInfo): void
553
554
 
554
555
  /**
555
556
  * A callback called when a shape starts being resized.
@@ -745,6 +746,37 @@ export interface TLCropInfo<T extends TLShape> {
745
746
  crop: TLShapeCrop
746
747
  uncroppedSize: { w: number; h: number }
747
748
  initialShape: T
749
+ aspectRatioLocked?: boolean
750
+ }
751
+
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>
748
780
  }
749
781
 
750
782
  /**
@@ -11,29 +11,36 @@ export class Pointing extends StateNode {
11
11
  static override id = 'pointing'
12
12
 
13
13
  override onPointerMove(info: TLPointerEventInfo) {
14
- if (this.editor.inputs.isDragging) {
15
- const { originPagePoint } = this.editor.inputs
14
+ const { editor } = this
15
+ if (editor.inputs.isDragging) {
16
+ const { originPagePoint } = editor.inputs
16
17
 
17
18
  const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType
18
19
 
19
20
  const id = createShapeId()
20
21
 
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
- },
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,
34
35
  },
35
- ])
36
- .select(id)
36
+ },
37
+ ])
38
+ const shape = editor.getShape(id)
39
+ if (!shape) {
40
+ this.cancel()
41
+ return
42
+ }
43
+ editor.select(id)
37
44
 
38
45
  const parent = this.parent as BaseBoxShapeTool
39
46
  this.editor.setCurrentTool(
@@ -79,6 +86,7 @@ export class Pointing extends StateNode {
79
86
 
80
87
  this.editor.markHistoryStoppingPoint(`creating_box:${id}`)
81
88
 
89
+ // Allow this to trigger the max shapes reached alert
82
90
  // todo: add scale here when dynamic size is enabled (is this still needed?)
83
91
  this.editor.createShapes<TLBaseBoxShape>([
84
92
  {