@tldraw/editor 3.15.0-canary.14c6b9d1aa1e → 3.15.0-canary.1a3b33fa458b

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 (112) hide show
  1. package/dist-cjs/index.d.ts +64 -3
  2. package/dist-cjs/index.js +3 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/SVGContainer.js +1 -1
  5. package/dist-cjs/lib/components/SVGContainer.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultBrush.js +1 -1
  7. package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultGrid.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultGrid.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultHandles.js +1 -1
  17. package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +1 -1
  19. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  20. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +1 -1
  21. package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +2 -2
  22. package/dist-cjs/lib/components/default-components/DefaultSpinner.js +27 -15
  23. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +3 -3
  24. package/dist-cjs/lib/editor/Editor.js +22 -19
  25. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  26. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  27. package/dist-cjs/lib/editor/tools/StateNode.js +20 -1
  28. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  29. package/dist-cjs/lib/hooks/useEditorComponents.js.map +1 -1
  30. package/dist-cjs/lib/license/Watermark.js +2 -2
  31. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  32. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  33. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  34. package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
  35. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  36. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +3 -1
  37. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  38. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -1
  39. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  40. package/dist-cjs/lib/primitives/geometry/geometry-constants.js +2 -2
  41. package/dist-cjs/lib/primitives/geometry/geometry-constants.js.map +2 -2
  42. package/dist-cjs/version.js +3 -3
  43. package/dist-cjs/version.js.map +1 -1
  44. package/dist-esm/index.d.mts +64 -3
  45. package/dist-esm/index.mjs +3 -1
  46. package/dist-esm/index.mjs.map +2 -2
  47. package/dist-esm/lib/components/SVGContainer.mjs +1 -1
  48. package/dist-esm/lib/components/SVGContainer.mjs.map +2 -2
  49. package/dist-esm/lib/components/default-components/DefaultBrush.mjs +1 -1
  50. package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
  51. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
  52. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  53. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
  54. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
  55. package/dist-esm/lib/components/default-components/DefaultCursor.mjs +1 -1
  56. package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +2 -2
  57. package/dist-esm/lib/components/default-components/DefaultGrid.mjs +1 -1
  58. package/dist-esm/lib/components/default-components/DefaultGrid.mjs.map +2 -2
  59. package/dist-esm/lib/components/default-components/DefaultHandles.mjs +1 -1
  60. package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +2 -2
  61. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +1 -1
  62. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  63. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +1 -1
  64. package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +2 -2
  65. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +17 -15
  66. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  67. package/dist-esm/lib/editor/Editor.mjs +22 -19
  68. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  69. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  70. package/dist-esm/lib/editor/tools/StateNode.mjs +20 -1
  71. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  72. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +1 -1
  73. package/dist-esm/lib/license/Watermark.mjs +2 -2
  74. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  75. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
  76. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  77. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +2 -2
  78. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  79. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +3 -1
  80. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  81. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
  82. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  83. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs +2 -2
  84. package/dist-esm/lib/primitives/geometry/geometry-constants.mjs.map +2 -2
  85. package/dist-esm/version.mjs +3 -3
  86. package/dist-esm/version.mjs.map +1 -1
  87. package/editor.css +17 -4
  88. package/package.json +9 -8
  89. package/src/index.ts +1 -0
  90. package/src/lib/components/SVGContainer.tsx +1 -1
  91. package/src/lib/components/default-components/DefaultBrush.tsx +1 -1
  92. package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
  93. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
  94. package/src/lib/components/default-components/DefaultCursor.tsx +1 -1
  95. package/src/lib/components/default-components/DefaultGrid.tsx +1 -1
  96. package/src/lib/components/default-components/DefaultHandles.tsx +5 -1
  97. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +1 -1
  98. package/src/lib/components/default-components/DefaultSnapIndictor.tsx +1 -1
  99. package/src/lib/components/default-components/DefaultSpinner.tsx +12 -12
  100. package/src/lib/editor/Editor.ts +26 -18
  101. package/src/lib/editor/shapes/ShapeUtil.ts +57 -0
  102. package/src/lib/editor/tools/StateNode.test.ts +285 -0
  103. package/src/lib/editor/tools/StateNode.ts +27 -1
  104. package/src/lib/hooks/useEditorComponents.tsx +1 -1
  105. package/src/lib/license/Watermark.tsx +2 -2
  106. package/src/lib/primitives/geometry/Arc2d.ts +2 -2
  107. package/src/lib/primitives/geometry/Circle2d.ts +2 -2
  108. package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -1
  109. package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
  110. package/src/lib/primitives/geometry/geometry-constants.ts +2 -1
  111. package/src/version.ts +3 -3
  112. package/src/lib/test/currentToolIdMask.test.ts +0 -49
package/editor.css CHANGED
@@ -167,7 +167,7 @@
167
167
  --color-text: hsl(0, 0%, 0%);
168
168
  --color-text-0: hsl(0, 0%, 11%);
169
169
  --color-text-1: hsl(0, 0%, 18%);
170
- --color-text-3: hsl(220, 2%, 65%);
170
+ --color-text-3: hsl(204, 4%, 45%);
171
171
  --color-text-shadow: hsl(0, 0%, 100%);
172
172
  --color-text-highlight: hsl(52, 100%, 50%);
173
173
  --color-text-highlight-p3: color(display-p3 0.972 0.8205 0.05);
@@ -222,7 +222,7 @@
222
222
  --color-text: hsl(210, 17%, 98%);
223
223
  --color-text-0: hsl(0, 9%, 94%);
224
224
  --color-text-1: hsl(0, 0%, 85%);
225
- --color-text-3: hsl(210, 6%, 45%);
225
+ --color-text-3: hsl(204, 4%, 75%);
226
226
  --color-text-shadow: hsl(210, 13%, 18%);
227
227
  --color-text-highlight: hsl(52, 100%, 41%);
228
228
  --color-text-highlight-p3: color(display-p3 0.8078 0.6225 0.0312);
@@ -968,14 +968,14 @@ input,
968
968
  font-size: 14px;
969
969
  font-weight: 500;
970
970
  opacity: 0;
971
- animation: fade-in 0.2s ease-in-out forwards;
971
+ animation: tl-fade-in 0.2s ease-in-out forwards;
972
972
  animation-delay: 0.2s;
973
973
  position: absolute;
974
974
  inset: 0px;
975
975
  z-index: var(--layer-canvas-blocker);
976
976
  }
977
977
 
978
- @keyframes fade-in {
978
+ @keyframes tl-fade-in {
979
979
  0% {
980
980
  opacity: 0;
981
981
  }
@@ -984,6 +984,19 @@ input,
984
984
  }
985
985
  }
986
986
 
987
+ .tl-spinner {
988
+ animation: tl-spin 1s linear infinite;
989
+ }
990
+
991
+ @keyframes tl-spin {
992
+ 0% {
993
+ transform: rotate(0deg);
994
+ }
995
+ 100% {
996
+ transform: rotate(360deg);
997
+ }
998
+ }
999
+
987
1000
  /* ---------------------- Brush --------------------- */
988
1001
 
989
1002
  .tl-brush {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
- "description": "A tiny little drawing app (editor).",
4
- "version": "3.15.0-canary.14c6b9d1aa1e",
3
+ "description": "tldraw infinite canvas SDK (editor).",
4
+ "version": "3.15.0-canary.1a3b33fa458b",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "keywords": [
19
19
  "tldraw",
20
+ "sdk",
20
21
  "drawing",
21
22
  "app",
22
23
  "development",
@@ -48,12 +49,12 @@
48
49
  "@tiptap/core": "^2.9.1",
49
50
  "@tiptap/pm": "^2.9.1",
50
51
  "@tiptap/react": "^2.9.1",
51
- "@tldraw/state": "3.15.0-canary.14c6b9d1aa1e",
52
- "@tldraw/state-react": "3.15.0-canary.14c6b9d1aa1e",
53
- "@tldraw/store": "3.15.0-canary.14c6b9d1aa1e",
54
- "@tldraw/tlschema": "3.15.0-canary.14c6b9d1aa1e",
55
- "@tldraw/utils": "3.15.0-canary.14c6b9d1aa1e",
56
- "@tldraw/validate": "3.15.0-canary.14c6b9d1aa1e",
52
+ "@tldraw/state": "3.15.0-canary.1a3b33fa458b",
53
+ "@tldraw/state-react": "3.15.0-canary.1a3b33fa458b",
54
+ "@tldraw/store": "3.15.0-canary.1a3b33fa458b",
55
+ "@tldraw/tlschema": "3.15.0-canary.1a3b33fa458b",
56
+ "@tldraw/utils": "3.15.0-canary.1a3b33fa458b",
57
+ "@tldraw/validate": "3.15.0-canary.1a3b33fa458b",
57
58
  "@types/core-js": "^2.5.8",
58
59
  "@use-gesture/react": "^10.3.1",
59
60
  "classnames": "^2.5.1",
package/src/index.ts CHANGED
@@ -346,6 +346,7 @@ export { CubicBezier2d } from './lib/primitives/geometry/CubicBezier2d'
346
346
  export { CubicSpline2d } from './lib/primitives/geometry/CubicSpline2d'
347
347
  export { Edge2d } from './lib/primitives/geometry/Edge2d'
348
348
  export { Ellipse2d } from './lib/primitives/geometry/Ellipse2d'
349
+ export { getVerticesCountForArcLength } from './lib/primitives/geometry/geometry-constants'
349
350
  export {
350
351
  Geometry2d,
351
352
  Geometry2dFilters,
@@ -7,7 +7,7 @@ export type SVGContainerProps = React.ComponentProps<'svg'>
7
7
  /** @public @react */
8
8
  export function SVGContainer({ children, className = '', ...rest }: SVGContainerProps) {
9
9
  return (
10
- <svg {...rest} className={classNames('tl-svg-container', className)}>
10
+ <svg {...rest} className={classNames('tl-svg-container', className)} aria-hidden="true">
11
11
  {children}
12
12
  </svg>
13
13
  )
@@ -21,7 +21,7 @@ export const DefaultBrush = ({ brush, color, opacity, className }: TLBrushProps)
21
21
  const h = toDomPrecision(Math.max(1, brush.h))
22
22
 
23
23
  return (
24
- <svg className="tl-overlays__item" ref={rSvg}>
24
+ <svg className="tl-overlays__item" ref={rSvg} aria-hidden="true">
25
25
  {color ? (
26
26
  <g className="tl-brush" opacity={opacity}>
27
27
  <rect width={w} height={h} fill={color} opacity={0.75} />
@@ -139,7 +139,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
139
139
  data-testid="canvas"
140
140
  {...events}
141
141
  >
142
- <svg className="tl-svg-context">
142
+ <svg className="tl-svg-context" aria-hidden="true">
143
143
  <defs>
144
144
  {shapeSvgDefs}
145
145
  <CursorDef />
@@ -39,7 +39,7 @@ export function DefaultCollaboratorHint({
39
39
  const cursorHintId = useSharedSafeId('cursor_hint')
40
40
 
41
41
  return (
42
- <svg ref={rSvg} className={classNames('tl-overlays__item', className)}>
42
+ <svg ref={rSvg} className={classNames('tl-overlays__item', className)} aria-hidden="true">
43
43
  <use
44
44
  href={`#${cursorHintId}`}
45
45
  color={color}
@@ -33,7 +33,7 @@ export const DefaultCursor = memo(function DefaultCursor({
33
33
 
34
34
  return (
35
35
  <div ref={rCursor} className={classNames('tl-overlays__item', className)}>
36
- <svg className="tl-cursor">
36
+ <svg className="tl-cursor" aria-hidden="true">
37
37
  <use href={`#${cursorId}`} color={color} />
38
38
  </svg>
39
39
  {chatMessage ? (
@@ -16,7 +16,7 @@ export function DefaultGrid({ x, y, z, size }: TLGridProps) {
16
16
  const editor = useEditor()
17
17
  const { gridSteps } = editor.options
18
18
  return (
19
- <svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg">
19
+ <svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
20
20
  <defs>
21
21
  {gridSteps.map(({ min, mid, step }, i) => {
22
22
  const s = step * size * z
@@ -7,5 +7,9 @@ export interface TLHandlesProps {
7
7
 
8
8
  /** @public @react */
9
9
  export const DefaultHandles = ({ children }: TLHandlesProps) => {
10
- return <svg className="tl-user-handles tl-overlays__item">{children}</svg>
10
+ return (
11
+ <svg className="tl-user-handles tl-overlays__item" aria-hidden="true">
12
+ {children}
13
+ </svg>
14
+ )
11
15
  }
@@ -86,7 +86,7 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
86
86
  }, [hidden])
87
87
 
88
88
  return (
89
- <svg ref={rIndicator} className={classNames('tl-overlays__item', className)}>
89
+ <svg ref={rIndicator} className={classNames('tl-overlays__item', className)} aria-hidden="true">
90
90
  <g className="tl-shape-indicator" stroke={color ?? 'var(--color-selected)'} opacity={opacity}>
91
91
  <InnerIndicator editor={editor} id={shapeId} />
92
92
  </g>
@@ -163,7 +163,7 @@ export interface TLSnapIndicatorProps {
163
163
  /** @public @react */
164
164
  export function DefaultSnapIndicator({ className, line, zoom }: TLSnapIndicatorProps) {
165
165
  return (
166
- <svg className={classNames('tl-overlays__item', className)}>
166
+ <svg className={classNames('tl-overlays__item', className)} aria-hidden="true">
167
167
  {line.type === 'points' ? (
168
168
  <PointsSnapIndicator {...line} zoom={zoom} />
169
169
  ) : line.type === 'gaps' ? (
@@ -1,19 +1,19 @@
1
+ import classNames from 'classnames'
2
+
1
3
  /** @public @react */
2
- export function DefaultSpinner() {
4
+ export function DefaultSpinner(props: React.SVGProps<SVGSVGElement>) {
3
5
  return (
4
- <svg width={16} height={16} viewBox="0 0 16 16" aria-hidden="false">
6
+ <svg
7
+ width={16}
8
+ height={16}
9
+ viewBox="0 0 16 16"
10
+ aria-hidden="false"
11
+ {...props}
12
+ className={classNames('tl-spinner', props.className)}
13
+ >
5
14
  <g strokeWidth={2} fill="none" fillRule="evenodd">
6
15
  <circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
7
- <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor">
8
- <animateTransform
9
- attributeName="transform"
10
- type="rotate"
11
- from="0 8 8"
12
- to="360 8 8"
13
- dur="1s"
14
- repeatCount="indefinite"
15
- />
16
- </path>
16
+ <path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor" />
17
17
  </g>
18
18
  </svg>
19
19
  )
@@ -1863,10 +1863,11 @@ export class Editor extends EventEmitter<TLEventMap> {
1863
1863
  firstParentId &&
1864
1864
  selectedShapeIds.every((shapeId) => this.getShape(shapeId)?.parentId === firstParentId) &&
1865
1865
  !isPageId(firstParentId)
1866
+ const filteredShapes = isSelectedWithinContainer
1867
+ ? this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
1868
+ : this.getCurrentPageShapes().filter((shape) => isPageId(shape.parentId))
1866
1869
  const readingOrderShapes = isSelectedWithinContainer
1867
- ? this._getShapesInReadingOrder(
1868
- this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
1869
- )
1870
+ ? this._getShapesInReadingOrder(filteredShapes)
1870
1871
  : this.getCurrentPageShapesInReadingOrder()
1871
1872
  const currentShapeId: TLShapeId | undefined =
1872
1873
  selectedShapeIds.length === 1
@@ -1883,7 +1884,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1883
1884
  adjacentShapeId = shapeIds[adjacentIndex]
1884
1885
  } else {
1885
1886
  if (!currentShapeId) return
1886
- adjacentShapeId = this.getNearestAdjacentShape(currentShapeId, direction)
1887
+ adjacentShapeId = this.getNearestAdjacentShape(filteredShapes, currentShapeId, direction)
1887
1888
  }
1888
1889
 
1889
1890
  const shape = this.getShape(adjacentShapeId)
@@ -1982,6 +1983,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1982
1983
  * @public
1983
1984
  */
1984
1985
  getNearestAdjacentShape(
1986
+ shapes: TLShape[],
1985
1987
  currentShapeId: TLShapeId,
1986
1988
  direction: 'left' | 'right' | 'up' | 'down'
1987
1989
  ): TLShapeId {
@@ -1989,7 +1991,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1989
1991
  const currentShape = this.getShape(currentShapeId)
1990
1992
  if (!currentShape) return currentShapeId
1991
1993
 
1992
- const shapes = this.getCurrentPageShapes()
1993
1994
  const tabbableShapes = shapes.filter(
1994
1995
  (shape) => this.getShapeUtil(shape).canTabTo(shape) && shape.id !== currentShapeId
1995
1996
  )
@@ -4420,21 +4421,28 @@ export class Editor extends EventEmitter<TLEventMap> {
4420
4421
  */
4421
4422
  deletePage(page: TLPageId | TLPage): this {
4422
4423
  const id = typeof page === 'string' ? page : page.id
4423
- this.run(() => {
4424
- if (this.getIsReadonly()) return
4425
- const pages = this.getPages()
4426
- if (pages.length === 1) return
4424
+ this.run(
4425
+ () => {
4426
+ if (this.getIsReadonly()) return
4427
+ const pages = this.getPages()
4428
+ if (pages.length === 1) return
4427
4429
 
4428
- const deletedPage = this.getPage(id)
4429
- if (!deletedPage) return
4430
+ const deletedPage = this.getPage(id)
4431
+ if (!deletedPage) return
4430
4432
 
4431
- if (id === this.getCurrentPageId()) {
4432
- const index = pages.findIndex((page) => page.id === id)
4433
- const next = pages[index - 1] ?? pages[index + 1]
4434
- this.setCurrentPage(next.id)
4435
- }
4436
- this.store.remove([deletedPage.id])
4437
- })
4433
+ if (id === this.getCurrentPageId()) {
4434
+ const index = pages.findIndex((page) => page.id === id)
4435
+ const next = pages[index - 1] ?? pages[index + 1]
4436
+ this.setCurrentPage(next.id)
4437
+ }
4438
+
4439
+ const shapes = this.getSortedChildIdsForParent(deletedPage.id)
4440
+ this.deleteShapes(shapes)
4441
+
4442
+ this.store.remove([deletedPage.id])
4443
+ },
4444
+ { ignoreShapeLock: true }
4445
+ )
4438
4446
  return this
4439
4447
  }
4440
4448
 
@@ -584,6 +584,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
584
584
  */
585
585
  onResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
586
586
 
587
+ /**
588
+ * A callback called when a shape resize is cancelled.
589
+ *
590
+ * @param initial - The shape at the start of the resize.
591
+ * @param current - The current shape.
592
+ * @public
593
+ */
594
+ onResizeCancel?(initial: Shape, current: Shape): void
595
+
587
596
  /**
588
597
  * A callback called when a shape starts being translated.
589
598
  *
@@ -613,6 +622,25 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
613
622
  */
614
623
  onTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
615
624
 
625
+ /**
626
+ * A callback called when a shape translation is cancelled.
627
+ *
628
+ * @param initial - The shape at the start of the translation.
629
+ * @param current - The current shape.
630
+ * @public
631
+ */
632
+ onTranslateCancel?(initial: Shape, current: Shape): void
633
+
634
+ /**
635
+ * A callback called when a shape's handle starts being dragged.
636
+ *
637
+ * @param shape - The shape.
638
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
639
+ * @returns A change to apply to the shape, or void.
640
+ * @public
641
+ */
642
+ onHandleDragStart?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
643
+
616
644
  /**
617
645
  * A callback called when a shape's handle changes.
618
646
  *
@@ -623,6 +651,25 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
623
651
  */
624
652
  onHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
625
653
 
654
+ /**
655
+ * A callback called when a shape's handle finishes being dragged.
656
+ *
657
+ * @param current - The current shape.
658
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
659
+ * @returns A change to apply to the shape, or void.
660
+ * @public
661
+ */
662
+ onHandleDragEnd?(current: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
663
+
664
+ /**
665
+ * A callback called when a shape's handle drag is cancelled.
666
+ *
667
+ * @param current - The current shape.
668
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
669
+ * @public
670
+ */
671
+ onHandleDragCancel?(current: Shape, info: TLHandleDragInfo<Shape>): void
672
+
626
673
  /**
627
674
  * A callback called when a shape starts being rotated.
628
675
  *
@@ -652,6 +699,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
652
699
  */
653
700
  onRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
654
701
 
702
+ /**
703
+ * A callback called when a shape rotation is cancelled.
704
+ *
705
+ * @param initial - The shape at the start of the rotation.
706
+ * @param current - The current shape.
707
+ * @public
708
+ */
709
+ onRotateCancel?(initial: Shape, current: Shape): void
710
+
655
711
  /**
656
712
  * Not currently used.
657
713
  *
@@ -819,5 +875,6 @@ export interface TLResizeInfo<T extends TLShape> {
819
875
  export interface TLHandleDragInfo<T extends TLShape> {
820
876
  handle: TLHandle
821
877
  isPrecise: boolean
878
+ isCreatingShape: boolean
822
879
  initial?: T | undefined
823
880
  }
@@ -0,0 +1,285 @@
1
+ import { createTLStore } from '../../config/createTLStore'
2
+ import { Editor } from '../Editor'
3
+ import { StateNode } from './StateNode'
4
+
5
+ describe('StateNode.addChild', () => {
6
+ // Test state node classes for addChild tests
7
+ class ParentState extends StateNode {
8
+ static override id = 'parent'
9
+ static override initial = 'child1'
10
+ static override children() {
11
+ return [ChildState1]
12
+ }
13
+ }
14
+
15
+ class ChildState1 extends StateNode {
16
+ static override id = 'child1'
17
+ }
18
+
19
+ class ChildState2 extends StateNode {
20
+ static override id = 'child2'
21
+ }
22
+
23
+ class ChildState3 extends StateNode {
24
+ static override id = 'child3'
25
+ }
26
+
27
+ class LeafState extends StateNode {
28
+ static override id = 'leaf'
29
+ }
30
+
31
+ class RootState extends StateNode {
32
+ static override id = 'root'
33
+ static override initial = 'child1'
34
+ static override children() {
35
+ return [ChildState1]
36
+ }
37
+ }
38
+
39
+ class RootStateWithoutChildren extends StateNode {
40
+ static override id = 'rootWithoutChildren'
41
+ }
42
+
43
+ let editor: Editor
44
+
45
+ beforeEach(() => {
46
+ editor = new Editor({
47
+ initialState: 'parent',
48
+ shapeUtils: [],
49
+ bindingUtils: [],
50
+ tools: [
51
+ ParentState,
52
+ ChildState1,
53
+ ChildState2,
54
+ ChildState3,
55
+ LeafState,
56
+ RootState,
57
+ RootStateWithoutChildren,
58
+ ],
59
+ store: createTLStore({ shapeUtils: [], bindingUtils: [] }),
60
+ getContainer: () => document.body,
61
+ })
62
+ })
63
+
64
+ it('should add a child to a branch state node', () => {
65
+ const parentState = editor.root.children!['parent'] as ParentState
66
+
67
+ // Initially should have one child
68
+ expect(Object.keys(parentState.children!)).toHaveLength(1)
69
+ expect(parentState.children!['child1']).toBeDefined()
70
+
71
+ // Add a new child
72
+ parentState.addChild(ChildState2)
73
+
74
+ // Should now have two children
75
+ expect(Object.keys(parentState.children!)).toHaveLength(2)
76
+ expect(parentState.children!['child1']).toBeDefined()
77
+ expect(parentState.children!['child2']).toBeDefined()
78
+ expect(parentState.children!['child2']).toBeInstanceOf(ChildState2)
79
+ })
80
+
81
+ it('should add a child to a root state node', () => {
82
+ const rootState = editor.root.children!['root'] as RootState
83
+
84
+ // Initially should have one child
85
+ expect(Object.keys(rootState.children!)).toHaveLength(1)
86
+ expect(rootState.children!['child1']).toBeDefined()
87
+
88
+ // Add a new child
89
+ rootState.addChild(ChildState2)
90
+
91
+ // Should now have two children
92
+ expect(Object.keys(rootState.children!)).toHaveLength(2)
93
+ expect(rootState.children!['child1']).toBeDefined()
94
+ expect(rootState.children!['child2']).toBeDefined()
95
+ expect(rootState.children!['child2']).toBeInstanceOf(ChildState2)
96
+ })
97
+
98
+ it('should throw an error when trying to add a child to a leaf state node', () => {
99
+ const leafState = editor.root.children!['leaf'] as LeafState
100
+
101
+ // Leaf state should not have children
102
+ expect(leafState.children).toBeUndefined()
103
+
104
+ // Should throw an error when trying to add a child
105
+ expect(() => {
106
+ leafState.addChild(ChildState2)
107
+ }).toThrow('StateNode.addChild: cannot add child to a leaf node')
108
+ })
109
+
110
+ it('should return the parent state node for chaining', () => {
111
+ const parentState = editor.root.children!['parent'] as ParentState
112
+
113
+ const result = parentState.addChild(ChildState2)
114
+
115
+ expect(result).toBe(parentState)
116
+ })
117
+
118
+ it('should create the child with the correct editor and parent', () => {
119
+ const parentState = editor.root.children!['parent'] as ParentState
120
+
121
+ parentState.addChild(ChildState2)
122
+ const childState = parentState.children!['child2'] as ChildState2
123
+
124
+ expect(childState.editor).toBe(editor)
125
+ expect(childState.parent).toBe(parentState)
126
+ })
127
+
128
+ it('should allow adding multiple children', () => {
129
+ const parentState = editor.root.children!['parent'] as ParentState
130
+
131
+ // Add multiple children
132
+ parentState.addChild(ChildState2).addChild(ChildState3)
133
+
134
+ // Should have three children
135
+ expect(Object.keys(parentState.children!)).toHaveLength(3)
136
+ expect(parentState.children!['child1']).toBeDefined()
137
+ expect(parentState.children!['child2']).toBeDefined()
138
+ expect(parentState.children!['child3']).toBeDefined()
139
+ expect(parentState.children!['child2']).toBeInstanceOf(ChildState2)
140
+ expect(parentState.children!['child3']).toBeInstanceOf(ChildState3)
141
+ })
142
+
143
+ it('should allow transitioning to added children', () => {
144
+ const parentState = editor.root.children!['parent'] as ParentState
145
+
146
+ // Add a new child
147
+ parentState.addChild(ChildState2)
148
+
149
+ // Should be able to transition to the new child
150
+ expect(() => {
151
+ parentState.transition('child2')
152
+ }).not.toThrow()
153
+
154
+ // The current state should be the new child
155
+ expect(parentState.getCurrent()?.id).toBe('child2')
156
+ })
157
+
158
+ it('should maintain existing children when adding new ones', () => {
159
+ const parentState = editor.root.children!['parent'] as ParentState
160
+ const originalChild = parentState.children!['child1']
161
+
162
+ // Add a new child
163
+ parentState.addChild(ChildState2)
164
+
165
+ // Original child should still exist and be the same instance
166
+ expect(parentState.children!['child1']).toBe(originalChild)
167
+ expect(parentState.children!['child1']).toBeInstanceOf(ChildState1)
168
+ })
169
+
170
+ it('should initialize children object for root nodes without static children', () => {
171
+ // Create a StateNode directly as a root node (no parent)
172
+ const mockEditor = {} as Editor
173
+ const rootStateWithoutChildren = new RootStateWithoutChildren(mockEditor, undefined)
174
+
175
+ // Root state without static children should not have children initially
176
+ expect(rootStateWithoutChildren.children).toBeUndefined()
177
+
178
+ // Adding a child should initialize the children object
179
+ rootStateWithoutChildren.addChild(ChildState2)
180
+
181
+ // Should now have children object with the added child
182
+ expect(rootStateWithoutChildren.children).toBeDefined()
183
+ expect(Object.keys(rootStateWithoutChildren.children!)).toHaveLength(1)
184
+ expect(rootStateWithoutChildren.children!['child2']).toBeDefined()
185
+ expect(rootStateWithoutChildren.children!['child2']).toBeInstanceOf(ChildState2)
186
+ })
187
+
188
+ it('should throw an error when trying to add a child with a duplicate ID', () => {
189
+ const parentState = editor.root.children!['parent'] as ParentState
190
+
191
+ // Initially should have one child
192
+ expect(Object.keys(parentState.children!)).toHaveLength(1)
193
+ expect(parentState.children!['child1']).toBeDefined()
194
+
195
+ // Should throw an error when trying to add a child with the same ID
196
+ expect(() => {
197
+ parentState.addChild(ChildState1)
198
+ }).toThrow("StateNode.addChild: a child with id 'child1' already exists")
199
+
200
+ // Should still have only one child
201
+ expect(Object.keys(parentState.children!)).toHaveLength(1)
202
+ expect(parentState.children!['child1']).toBeDefined()
203
+ })
204
+
205
+ it('should throw an error when trying to add a child with a duplicate ID to a root state', () => {
206
+ const rootState = editor.root.children!['root'] as RootState
207
+
208
+ // Initially should have one child
209
+ expect(Object.keys(rootState.children!)).toHaveLength(1)
210
+ expect(rootState.children!['child1']).toBeDefined()
211
+
212
+ // Should throw an error when trying to add a child with the same ID
213
+ expect(() => {
214
+ rootState.addChild(ChildState1)
215
+ }).toThrow("StateNode.addChild: a child with id 'child1' already exists")
216
+
217
+ // Should still have only one child
218
+ expect(Object.keys(rootState.children!)).toHaveLength(1)
219
+ expect(rootState.children!['child1']).toBeDefined()
220
+ })
221
+
222
+ it('should throw an error when trying to add a child with a duplicate ID to a root state without static children', () => {
223
+ // Create a StateNode directly as a root node (no parent)
224
+ const mockEditor = {} as Editor
225
+ const rootStateWithoutChildren = new RootStateWithoutChildren(mockEditor, undefined)
226
+
227
+ // Add a child first
228
+ rootStateWithoutChildren.addChild(ChildState1)
229
+
230
+ // Should throw an error when trying to add a child with the same ID
231
+ expect(() => {
232
+ rootStateWithoutChildren.addChild(ChildState1)
233
+ }).toThrow("StateNode.addChild: a child with id 'child1' already exists")
234
+
235
+ // Should still have only one child
236
+ expect(Object.keys(rootStateWithoutChildren.children!)).toHaveLength(1)
237
+ expect(rootStateWithoutChildren.children!['child1']).toBeDefined()
238
+ })
239
+ })
240
+
241
+ describe('current tool id mask', () => {
242
+ // Tool mask test classes
243
+ class ToolA extends StateNode {
244
+ static override id = 'A'
245
+ }
246
+
247
+ class ToolB extends StateNode {
248
+ static override id = 'B'
249
+ }
250
+
251
+ class ToolC extends StateNode {
252
+ static override id = 'C'
253
+
254
+ override onEnter() {
255
+ this.setCurrentToolIdMask('A')
256
+ }
257
+ }
258
+
259
+ let toolMaskEditor: Editor
260
+
261
+ beforeEach(() => {
262
+ toolMaskEditor = new Editor({
263
+ initialState: 'A',
264
+ shapeUtils: [],
265
+ bindingUtils: [],
266
+ tools: [ToolA, ToolB, ToolC],
267
+ store: createTLStore({ shapeUtils: [], bindingUtils: [] }),
268
+ getContainer: () => document.body,
269
+ })
270
+ })
271
+
272
+ it('starts with the correct tool id', () => {
273
+ expect(toolMaskEditor.getCurrentToolId()).toBe('A')
274
+ })
275
+
276
+ it('updates the current tool id', () => {
277
+ toolMaskEditor.setCurrentTool('B')
278
+ expect(toolMaskEditor.getCurrentToolId()).toBe('B')
279
+ })
280
+
281
+ it('masks the current tool id', () => {
282
+ toolMaskEditor.setCurrentTool('C')
283
+ expect(toolMaskEditor.getCurrentToolId()).toBe('A')
284
+ })
285
+ })