@tldraw/editor 3.14.0-canary.4c533b76dc35 → 3.14.0-canary.4f9d90070add

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 (53) hide show
  1. package/dist-cjs/index.d.ts +17 -9
  2. package/dist-cjs/index.js +1 -3
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
  5. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
  6. package/dist-cjs/lib/editor/Editor.js +31 -20
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  9. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +1 -2
  10. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  11. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  12. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
  13. package/dist-cjs/lib/primitives/Box.js +0 -6
  14. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  15. package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
  16. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
  17. package/dist-cjs/lib/utils/richText.js +7 -2
  18. package/dist-cjs/lib/utils/richText.js.map +2 -2
  19. package/dist-cjs/version.js +3 -3
  20. package/dist-cjs/version.js.map +1 -1
  21. package/dist-esm/index.d.mts +17 -9
  22. package/dist-esm/index.mjs +1 -3
  23. package/dist-esm/index.mjs.map +2 -2
  24. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  25. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  26. package/dist-esm/lib/editor/Editor.mjs +31 -20
  27. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  28. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  29. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +1 -2
  30. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  31. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  32. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  33. package/dist-esm/lib/primitives/Box.mjs +0 -6
  34. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  35. package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
  36. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
  37. package/dist-esm/lib/utils/richText.mjs +8 -3
  38. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  39. package/dist-esm/version.mjs +3 -3
  40. package/dist-esm/version.mjs.map +1 -1
  41. package/editor.css +455 -523
  42. package/package.json +8 -9
  43. package/src/index.ts +0 -1
  44. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  45. package/src/lib/editor/Editor.test.ts +11 -11
  46. package/src/lib/editor/Editor.ts +26 -17
  47. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  48. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -2
  49. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  50. package/src/lib/primitives/Box.ts +0 -8
  51. package/src/lib/utils/areShapesContentEqual.ts +1 -2
  52. package/src/lib/utils/richText.ts +9 -3
  53. package/src/version.ts +3 -3
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.4c533b76dc35",
4
+ "version": "3.14.0-canary.4f9d90070add",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -48,20 +48,19 @@
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.4c533b76dc35",
52
- "@tldraw/state-react": "3.14.0-canary.4c533b76dc35",
53
- "@tldraw/store": "3.14.0-canary.4c533b76dc35",
54
- "@tldraw/tlschema": "3.14.0-canary.4c533b76dc35",
55
- "@tldraw/utils": "3.14.0-canary.4c533b76dc35",
56
- "@tldraw/validate": "3.14.0-canary.4c533b76dc35",
51
+ "@tldraw/state": "3.14.0-canary.4f9d90070add",
52
+ "@tldraw/state-react": "3.14.0-canary.4f9d90070add",
53
+ "@tldraw/store": "3.14.0-canary.4f9d90070add",
54
+ "@tldraw/tlschema": "3.14.0-canary.4f9d90070add",
55
+ "@tldraw/utils": "3.14.0-canary.4f9d90070add",
56
+ "@tldraw/validate": "3.14.0-canary.4f9d90070add",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
60
60
  "core-js": "^3.40.0",
61
61
  "eventemitter3": "^4.0.7",
62
62
  "idb": "^7.1.1",
63
- "is-plain-object": "^5.0.0",
64
- "lodash.isequal": "^4.5.0"
63
+ "is-plain-object": "^5.0.0"
65
64
  },
66
65
  "peerDependencies": {
67
66
  "react": "^18.2.0 || ^19.0.0",
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@ 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 { areShapesContentEqual } from './lib/utils/areShapesContentEqual'
8
7
 
9
8
  // eslint-disable-next-line local/no-export-star
10
9
  export * from '@tldraw/state'
@@ -14,12 +14,12 @@ import {
14
14
  import {
15
15
  deleteFromSessionStorage,
16
16
  getFromSessionStorage,
17
+ isEqual,
17
18
  setInSessionStorage,
18
19
  structuredClone,
19
20
  uniqueId,
20
21
  } from '@tldraw/utils'
21
22
  import { T } from '@tldraw/validate'
22
- import isEqual from 'lodash.isequal'
23
23
  import { tlenv } from '../globals/environment'
24
24
 
25
25
  const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
@@ -233,7 +233,7 @@ describe('getShapesAtPoint', () => {
233
233
  })
234
234
  })
235
235
 
236
- it('returns shapes at a point in order of their index', () => {
236
+ it('returns shapes at a point in reverse z-index order', () => {
237
237
  // Point at (50, 50) should hit shape3's edge (since it's at 50,50 with size 100x100)
238
238
  // This point is exactly at the top-left corner of shape3
239
239
  const shapes = editor.getShapesAtPoint({ x: 50, y: 50 })
@@ -313,12 +313,12 @@ describe('getShapesAtPoint', () => {
313
313
  const shapes = editor.getShapesAtPoint({ x: 100, y: 0 })
314
314
  const shapeIds = shapes.map((s) => s.id)
315
315
 
316
- // Both shapes should be detected at this overlapping point
317
- expect(shapeIds).toEqual([ids.shape1, ids.shape2])
316
+ // Both shapes should be detected at this overlapping point (reversed order - top-most first)
317
+ expect(shapeIds).toEqual([ids.shape2, ids.shape1])
318
318
  expect(shapes).toHaveLength(2)
319
319
  })
320
320
 
321
- it('maintains shape order from getCurrentPageShapesSorted', () => {
321
+ it('maintains reverse shape order and responds to z-index changes', () => {
322
322
  // Create filled shape that overlaps with shape2
323
323
  editor.createShape({
324
324
  id: ids.shape5,
@@ -333,14 +333,14 @@ describe('getShapesAtPoint', () => {
333
333
  const shapes = editor.getShapesAtPoint({ x: 120, y: 120 }, { hitInside: true })
334
334
  const shapeIds = shapes.map((s) => s.id)
335
335
 
336
- // All shapes that contain this point should be returned in z-index order
337
- expect(shapeIds).toEqual([ids.shape1, ids.shape2, ids.shape3, ids.shape4, ids.shape5])
336
+ // All shapes that contain this point should be returned in reverse z-index order (top-most first)
337
+ expect(shapeIds).toEqual([ids.shape5, ids.shape4, ids.shape3, ids.shape2, ids.shape1])
338
338
 
339
- // After bringing shape2 to front, order should change
339
+ // After bringing shape2 to front, order should change (shape2 becomes top-most)
340
340
  editor.bringToFront([ids.shape2])
341
341
  const shapes2 = editor.getShapesAtPoint({ x: 120, y: 120 }, { hitInside: true })
342
342
  const shapeIds2 = shapes2.map((s) => s.id)
343
- expect(shapeIds2).toEqual([ids.shape1, ids.shape3, ids.shape4, ids.shape5, ids.shape2])
343
+ expect(shapeIds2).toEqual([ids.shape2, ids.shape5, ids.shape4, ids.shape3, ids.shape1])
344
344
  })
345
345
 
346
346
  it('combines hitInside and margin options', () => {
@@ -361,7 +361,7 @@ describe('getShapesAtPoint', () => {
361
361
  isShapeHiddenSpy.mockRestore()
362
362
  })
363
363
 
364
- it('returns multiple shapes at same point in z-index order', () => {
364
+ it('returns multiple shapes at same point in reverse z-index order', () => {
365
365
  // Create two shapes at exactly the same position (away from existing shapes)
366
366
  editor.createShape({
367
367
  id: ids.overlap1,
@@ -383,8 +383,8 @@ describe('getShapesAtPoint', () => {
383
383
  const shapes = editor.getShapesAtPoint({ x: 600, y: 600 })
384
384
  const shapeIds = shapes.map((s) => s.id)
385
385
 
386
- // Should return both shapes in z-index order
387
- expect(shapeIds).toEqual([ids.overlap1, ids.overlap2])
386
+ // Should return both shapes in reverse z-index order (top-most first)
387
+ expect(shapeIds).toEqual([ids.overlap2, ids.overlap1])
388
388
  expect(shapes).toHaveLength(2)
389
389
  })
390
390
 
@@ -506,14 +506,13 @@ export class Editor extends EventEmitter<TLEventMap> {
506
506
  shape: {
507
507
  afterChange: (shapeBefore, shapeAfter) => {
508
508
  for (const binding of this.getBindingsInvolvingShape(shapeAfter)) {
509
- if (areShapesContentEqual(shapeBefore, shapeAfter)) continue
510
-
511
509
  invalidBindingTypes.add(binding.type)
512
510
  if (binding.fromId === shapeAfter.id) {
513
511
  this.getBindingUtil(binding).onAfterChangeFromShape?.({
514
512
  binding,
515
513
  shapeBefore,
516
514
  shapeAfter,
515
+ reason: 'self',
517
516
  })
518
517
  }
519
518
  if (binding.toId === shapeAfter.id) {
@@ -521,6 +520,7 @@ export class Editor extends EventEmitter<TLEventMap> {
521
520
  binding,
522
521
  shapeBefore,
523
522
  shapeAfter,
523
+ reason: 'self',
524
524
  })
525
525
  }
526
526
  }
@@ -539,6 +539,7 @@ export class Editor extends EventEmitter<TLEventMap> {
539
539
  binding,
540
540
  shapeBefore: descendantShape,
541
541
  shapeAfter: descendantShape,
542
+ reason: 'ancestry',
542
543
  })
543
544
  }
544
545
  if (binding.toId === descendantShape.id) {
@@ -546,6 +547,7 @@ export class Editor extends EventEmitter<TLEventMap> {
546
547
  binding,
547
548
  shapeBefore: descendantShape,
548
549
  shapeAfter: descendantShape,
550
+ reason: 'ancestry',
549
551
  })
550
552
  }
551
553
  }
@@ -5035,28 +5037,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5035
5037
  *
5036
5038
  * @public
5037
5039
  */
5038
- isShapeOrAncestorLocked(shape?: TLShape): boolean
5039
- isShapeOrAncestorLocked(id?: TLShapeId): boolean
5040
- isShapeOrAncestorLocked(arg?: TLShape | TLShapeId): boolean {
5041
- const shape = typeof arg === 'string' ? this.getShape(arg) : arg
5042
- if (shape === undefined) return false
5043
- if (shape.isLocked) return true
5044
- return this.isShapeOrAncestorLocked(this.getShapeParent(shape))
5040
+ isShapeOrAncestorLocked(shape?: TLShape | TLShapeId): boolean {
5041
+ const _shape = shape && this.getShape(shape)
5042
+ if (_shape === undefined) return false
5043
+ if (_shape.isLocked) return true
5044
+ return this.isShapeOrAncestorLocked(this.getShapeParent(_shape))
5045
5045
  }
5046
5046
 
5047
+ /**
5048
+ * Get shapes that are outside of the viewport.
5049
+ *
5050
+ * @public
5051
+ */
5047
5052
  @computed
5048
- private _notVisibleShapes() {
5049
- return notVisibleShapes(this)
5053
+ getNotVisibleShapes() {
5054
+ return this._notVisibleShapes.get()
5050
5055
  }
5051
5056
 
5057
+ private _notVisibleShapes = notVisibleShapes(this)
5058
+
5052
5059
  /**
5053
- * Get culled shapes.
5060
+ * Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
5054
5061
  *
5055
5062
  * @public
5056
5063
  */
5057
5064
  @computed
5058
5065
  getCulledShapes() {
5059
- const notVisibleShapes = this._notVisibleShapes().get()
5066
+ const notVisibleShapes = this.getNotVisibleShapes()
5060
5067
  const selectedShapeIds = this.getSelectedShapeIds()
5061
5068
  const editingId = this.getEditingShapeId()
5062
5069
  const culledShapes = new Set<TLShapeId>(notVisibleShapes)
@@ -5305,21 +5312,23 @@ export class Editor extends EventEmitter<TLEventMap> {
5305
5312
  * @example
5306
5313
  * ```ts
5307
5314
  * editor.getShapesAtPoint({ x: 100, y: 100 })
5308
- * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, exact: true })
5315
+ * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, margin: 8 })
5309
5316
  * ```
5310
5317
  *
5311
5318
  * @param point - The page point to test.
5312
5319
  * @param opts - The options for the hit point testing.
5313
5320
  *
5321
+ * @returns An array of shapes at the given point, sorted in reverse order of their absolute z-index (top-most shape first).
5322
+ *
5314
5323
  * @public
5315
5324
  */
5316
5325
  getShapesAtPoint(
5317
5326
  point: VecLike,
5318
5327
  opts = {} as { margin?: number; hitInside?: boolean }
5319
5328
  ): TLShape[] {
5320
- return this.getCurrentPageShapesSorted().filter(
5321
- (shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts)
5322
- )
5329
+ return this.getCurrentPageShapesSorted()
5330
+ .filter((shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts))
5331
+ .reverse()
5323
5332
  }
5324
5333
 
5325
5334
  /**
@@ -62,6 +62,12 @@ export interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {
62
62
  shapeBefore: TLShape
63
63
  /** The shape record after the change is made. */
64
64
  shapeAfter: TLShape
65
+ /**
66
+ * Why did this shape change?
67
+ * - 'self': the shape itself changed
68
+ * - 'ancestry': the ancestry of the shape changed, but the shape itself may not have done
69
+ */
70
+ reason: 'self' | 'ancestry'
65
71
  }
66
72
 
67
73
  /**
@@ -96,8 +96,7 @@ export class FontManager {
96
96
  },
97
97
  {
98
98
  areResultsEqual: areArraysShallowEqual,
99
- // @ts-expect-error
100
- areRecordsEqual: (a, b) => a.props.richText === b.props.richText,
99
+ areRecordsEqual: (a, b) => a.props === b.props && a.meta === b.meta,
101
100
  }
102
101
  )
103
102
 
@@ -21,7 +21,7 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
21
21
  }
22
22
 
23
23
  canResize() {
24
- return false
24
+ return true
25
25
  }
26
26
 
27
27
  canResizeChildren() {
@@ -591,14 +591,6 @@ export class Box {
591
591
  return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
592
592
  }
593
593
 
594
- prettyMuchEquals(other: Box | BoxModel) {
595
- return this.clone().toFixed().equals(Box.From(other).toFixed())
596
- }
597
-
598
- static PrettyMuchEquals(a: Box | BoxModel, b: Box | BoxModel) {
599
- return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
600
- }
601
-
602
594
  zeroFix() {
603
595
  this.w = Math.max(1, this.w)
604
596
  this.h = Math.max(1, this.h)
@@ -1,5 +1,4 @@
1
1
  import { TLShape } from '@tldraw/tlschema'
2
2
 
3
- /** @public */
4
3
  export const areShapesContentEqual = (a: TLShape, b: TLShape) =>
5
- a.parentId === b.parentId && a.props === b.props && a.meta === b.meta
4
+ a.props === b.props && a.meta === b.meta
@@ -1,8 +1,8 @@
1
1
  import { getSchema, JSONContent, Editor as TTEditor } from '@tiptap/core'
2
- import { Node } from '@tiptap/pm/model'
2
+ import { Node, Schema } from '@tiptap/pm/model'
3
3
  import { EditorProviderProps } from '@tiptap/react'
4
4
  import { TLRichText } from '@tldraw/tlschema'
5
- import { assert } from '@tldraw/utils'
5
+ import { assert, WeakCache } from '@tldraw/utils'
6
6
  import { Editor } from '../editor/Editor'
7
7
  import { TLFontFace } from '../editor/managers/FontManager/FontManager'
8
8
 
@@ -39,6 +39,11 @@ export type RichTextFontVisitor = (
39
39
  addFont: (font: TLFontFace) => void
40
40
  ) => RichTextFontVisitorState
41
41
 
42
+ const schemaCache = new WeakCache<EditorProviderProps, Schema>()
43
+ export function getTipTapSchema(tipTapConfig: EditorProviderProps) {
44
+ return schemaCache.get(tipTapConfig, () => getSchema(tipTapConfig.extensions ?? []))
45
+ }
46
+
42
47
  /** @public */
43
48
  export function getFontsFromRichText(
44
49
  editor: Editor,
@@ -49,7 +54,8 @@ export function getFontsFromRichText(
49
54
  assert(tipTapConfig, 'textOptions.tipTapConfig must be set to use rich text')
50
55
  assert(addFontsFromNode, 'textOptions.addFontsFromNode must be set to use rich text')
51
56
 
52
- const schema = getSchema(tipTapConfig.extensions ?? [])
57
+ const schema = getTipTapSchema(tipTapConfig)
58
+
53
59
  const rootNode = Node.fromJSON(schema, richText as JSONContent)
54
60
 
55
61
  const fonts = new Set<TLFontFace>()
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.14.0-canary.4c533b76dc35'
4
+ export const version = '3.14.0-canary.4f9d90070add'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-06-08T15:48:10.989Z',
8
- patch: '2025-06-08T15:48:10.989Z',
7
+ minor: '2025-06-15T10:30:26.984Z',
8
+ patch: '2025-06-15T10:30:26.984Z',
9
9
  }