@tldraw/editor 3.14.0-canary.e0ab6f4c80f9 → 3.14.0-canary.f6a0206007b3

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 (118) hide show
  1. package/dist-cjs/index.d.ts +64 -49
  2. package/dist-cjs/index.js +3 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/editor/Editor.js +2 -2
  5. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  6. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -20
  7. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
  8. package/dist-cjs/lib/editor/managers/FocusManager.js +2 -0
  9. package/dist-cjs/lib/editor/managers/FocusManager.js.map +2 -2
  10. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +8 -0
  11. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +6 -0
  13. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  14. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  15. package/dist-cjs/lib/primitives/Box.js +6 -0
  16. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  17. package/dist-cjs/lib/primitives/Vec.js +18 -13
  18. package/dist-cjs/lib/primitives/Vec.js.map +3 -3
  19. package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
  20. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  21. package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
  22. package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
  23. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
  24. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  25. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
  26. package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
  27. package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -21
  28. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  29. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
  30. package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
  31. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +5 -0
  32. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  33. package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
  34. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  35. package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
  36. package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
  37. package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
  38. package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
  39. package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
  40. package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
  41. package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
  42. package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
  43. package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
  44. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
  45. package/dist-cjs/version.js +3 -3
  46. package/dist-cjs/version.js.map +1 -1
  47. package/dist-esm/index.d.mts +64 -49
  48. package/dist-esm/index.mjs +6 -2
  49. package/dist-esm/index.mjs.map +2 -2
  50. package/dist-esm/lib/editor/Editor.mjs +2 -2
  51. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  52. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -20
  53. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
  54. package/dist-esm/lib/editor/managers/FocusManager.mjs +2 -0
  55. package/dist-esm/lib/editor/managers/FocusManager.mjs.map +2 -2
  56. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +8 -0
  57. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  58. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +6 -0
  59. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  60. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  61. package/dist-esm/lib/primitives/Box.mjs +6 -0
  62. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  63. package/dist-esm/lib/primitives/Vec.mjs +19 -14
  64. package/dist-esm/lib/primitives/Vec.mjs.map +3 -3
  65. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
  66. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  67. package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
  68. package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
  69. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
  70. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  71. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
  72. package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
  73. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -21
  74. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  75. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
  76. package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
  77. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +7 -1
  78. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  79. package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
  80. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  81. package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
  82. package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
  83. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
  84. package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
  85. package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
  86. package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
  87. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
  88. package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
  89. package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
  90. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
  91. package/dist-esm/version.mjs +3 -3
  92. package/dist-esm/version.mjs.map +1 -1
  93. package/package.json +7 -7
  94. package/src/index.ts +5 -1
  95. package/src/lib/editor/Editor.ts +3 -2
  96. package/src/lib/editor/derivations/notVisibleShapes.ts +24 -25
  97. package/src/lib/editor/managers/FocusManager.ts +2 -0
  98. package/src/lib/editor/shapes/ShapeUtil.ts +9 -0
  99. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +8 -0
  100. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +5 -2
  101. package/src/lib/primitives/Box.ts +8 -0
  102. package/src/lib/primitives/Vec.test.ts +2 -2
  103. package/src/lib/primitives/Vec.ts +15 -10
  104. package/src/lib/primitives/geometry/Arc2d.ts +42 -23
  105. package/src/lib/primitives/geometry/Circle2d.ts +12 -12
  106. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
  107. package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
  108. package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
  109. package/src/lib/primitives/geometry/Edge2d.ts +14 -25
  110. package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
  111. package/src/lib/primitives/geometry/Geometry2d.ts +6 -0
  112. package/src/lib/primitives/geometry/Point2d.ts +6 -6
  113. package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
  114. package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
  115. package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
  116. package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
  117. package/src/lib/utils/areShapesContentEqual.ts +2 -1
  118. package/src/version.ts +3 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.14.0-canary.e0ab6f4c80f9'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-05-23T10:08:18.976Z',\n\tpatch: '2025-05-23T10:08:18.976Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.14.0-canary.f6a0206007b3'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-06-03T09:18:36.909Z',\n\tpatch: '2025-06-03T09:18:36.909Z',\n}\n"],
5
5
  "mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
package/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.e0ab6f4c80f9",
4
+ "version": "3.14.0-canary.f6a0206007b3",
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.e0ab6f4c80f9",
52
- "@tldraw/state-react": "3.14.0-canary.e0ab6f4c80f9",
53
- "@tldraw/store": "3.14.0-canary.e0ab6f4c80f9",
54
- "@tldraw/tlschema": "3.14.0-canary.e0ab6f4c80f9",
55
- "@tldraw/utils": "3.14.0-canary.e0ab6f4c80f9",
56
- "@tldraw/validate": "3.14.0-canary.e0ab6f4c80f9",
51
+ "@tldraw/state": "3.14.0-canary.f6a0206007b3",
52
+ "@tldraw/state-react": "3.14.0-canary.f6a0206007b3",
53
+ "@tldraw/store": "3.14.0-canary.f6a0206007b3",
54
+ "@tldraw/tlschema": "3.14.0-canary.f6a0206007b3",
55
+ "@tldraw/utils": "3.14.0-canary.f6a0206007b3",
56
+ "@tldraw/validate": "3.14.0-canary.f6a0206007b3",
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,6 +4,7 @@ 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'
7
8
 
8
9
  // eslint-disable-next-line local/no-export-star
9
10
  export * from '@tldraw/state'
@@ -185,7 +186,10 @@ export {
185
186
  type TLShapeUtilConstructor,
186
187
  } from './lib/editor/shapes/ShapeUtil'
187
188
  export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
188
- export { getPerfectDashProps } from './lib/editor/shapes/shared/getPerfectDashProps'
189
+ export {
190
+ getPerfectDashProps,
191
+ type PerfectDashTerminal,
192
+ } from './lib/editor/shapes/shared/getPerfectDashProps'
189
193
  export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox'
190
194
  export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
191
195
  export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
@@ -506,6 +506,8 @@ 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
+
509
511
  invalidBindingTypes.add(binding.type)
510
512
  if (binding.fromId === shapeAfter.id) {
511
513
  this.getBindingUtil(binding).onAfterChangeFromShape?.({
@@ -5796,8 +5798,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5796
5798
  parent: TLParentId | TLPage | TLShape,
5797
5799
  visitor: (id: TLShapeId) => void | false
5798
5800
  ): this {
5799
- const parentId = typeof parent === 'string' ? parent : parent.id
5800
- const children = this.getSortedChildIdsForParent(parentId)
5801
+ const children = this.getSortedChildIdsForParent(parent)
5801
5802
  for (const id of children) {
5802
5803
  if (visitor(id) === false) continue
5803
5804
  this.visitDescendants(id, visitor)
@@ -1,49 +1,48 @@
1
1
  import { computed, isUninitialized } from '@tldraw/state'
2
2
  import { TLShapeId } from '@tldraw/tlschema'
3
- import { Box } from '../../primitives/Box'
4
3
  import { Editor } from '../Editor'
5
4
 
6
- function isShapeNotVisible(editor: Editor, id: TLShapeId, viewportPageBounds: Box): boolean {
7
- const maskedPageBounds = editor.getShapeMaskedPageBounds(id)
8
- // if the shape is fully outside of its parent's clipping bounds...
9
- if (maskedPageBounds === undefined) return true
10
-
11
- // if the shape is fully outside of the viewport page bounds...
12
- return !viewportPageBounds.includes(maskedPageBounds)
5
+ function fromScratch(editor: Editor): Set<TLShapeId> {
6
+ const shapesIds = editor.getCurrentPageShapeIds()
7
+ const viewportPageBounds = editor.getViewportPageBounds()
8
+ const notVisibleShapes = new Set<TLShapeId>()
9
+ shapesIds.forEach((id) => {
10
+ // If the shape is fully outside of the viewport page bounds, add it to the set.
11
+ // We'll ignore masks here, since they're more expensive to compute and the overhead is not worth it.
12
+ const pageBounds = editor.getShapePageBounds(id)
13
+ if (pageBounds === undefined || !viewportPageBounds.includes(pageBounds)) {
14
+ notVisibleShapes.add(id)
15
+ }
16
+ })
17
+ return notVisibleShapes
13
18
  }
14
19
 
15
20
  /**
16
21
  * Incremental derivation of not visible shapes.
17
- * Non visible shapes are shapes outside of the viewport page bounds and shapes outside of parent's clipping bounds.
22
+ * Non visible shapes are shapes outside of the viewport page bounds.
18
23
  *
19
24
  * @param editor - Instance of the tldraw Editor.
20
25
  * @returns Incremental derivation of non visible shapes.
21
26
  */
22
- export const notVisibleShapes = (editor: Editor) => {
23
- function fromScratch(editor: Editor): Set<TLShapeId> {
24
- const shapes = editor.getCurrentPageShapeIds()
25
- const viewportPageBounds = editor.getViewportPageBounds()
26
- const notVisibleShapes = new Set<TLShapeId>()
27
- shapes.forEach((id) => {
28
- if (isShapeNotVisible(editor, id, viewportPageBounds)) {
29
- notVisibleShapes.add(id)
30
- }
31
- })
32
- return notVisibleShapes
33
- }
34
- return computed<Set<TLShapeId>>('notVisibleShapes', (prevValue) => {
27
+ export function notVisibleShapes(editor: Editor) {
28
+ return computed<Set<TLShapeId>>('notVisibleShapes', function updateNotVisibleShapes(prevValue) {
29
+ const nextValue = fromScratch(editor)
30
+
35
31
  if (isUninitialized(prevValue)) {
36
- return fromScratch(editor)
32
+ return nextValue
37
33
  }
38
34
 
39
- const nextValue = fromScratch(editor)
40
-
35
+ // If there are more or less shapes, we know there's a change
41
36
  if (prevValue.size !== nextValue.size) return nextValue
37
+
38
+ // If any of the old shapes are not in the new set, we know there's a change
42
39
  for (const prev of prevValue) {
43
40
  if (!nextValue.has(prev)) {
44
41
  return nextValue
45
42
  }
46
43
  }
44
+
45
+ // If we've made it here, we know that the set is the same
47
46
  return prevValue
48
47
  })
49
48
  }
@@ -58,6 +58,8 @@ export class FocusManager {
58
58
 
59
59
  private handleKeyDown(keyEvent: KeyboardEvent) {
60
60
  const container = this.editor.getContainer()
61
+ if (this.editor.isIn('select.editing_shape')) return
62
+ if (document.activeElement === container && this.editor.getSelectedShapeIds().length > 0) return
61
63
  if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
62
64
  container.classList.remove('tl-container__no-focus-ring')
63
65
  }
@@ -240,6 +240,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
240
240
  return true
241
241
  }
242
242
 
243
+ /**
244
+ * When the shape is resized, whether the shape's children should also be resized.
245
+ *
246
+ * @public
247
+ */
248
+ canResizeChildren(_shape: Shape): boolean {
249
+ return true
250
+ }
251
+
243
252
  /**
244
253
  * Whether the shape can be edited in read-only mode.
245
254
  *
@@ -20,6 +20,14 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
20
20
  return false
21
21
  }
22
22
 
23
+ canResize() {
24
+ return false
25
+ }
26
+
27
+ canResizeChildren() {
28
+ return true
29
+ }
30
+
23
31
  getDefaultProps(): TLGroupShape['props'] {
24
32
  return {}
25
33
  }
@@ -1,5 +1,8 @@
1
1
  import { TLDefaultDashStyle } from '@tldraw/tlschema'
2
2
 
3
+ /** @public */
4
+ export type PerfectDashTerminal = 'skip' | 'outset' | 'none'
5
+
3
6
  /** @public */
4
7
  export function getPerfectDashProps(
5
8
  totalLength: number,
@@ -7,8 +10,8 @@ export function getPerfectDashProps(
7
10
  opts: {
8
11
  style?: TLDefaultDashStyle
9
12
  snap?: number
10
- end?: 'skip' | 'outset' | 'none'
11
- start?: 'skip' | 'outset' | 'none'
13
+ end?: PerfectDashTerminal
14
+ start?: PerfectDashTerminal
12
15
  lengthRatio?: number
13
16
  closed?: boolean
14
17
  forceSolid?: boolean
@@ -591,6 +591,14 @@ 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
+
594
602
  zeroFix() {
595
603
  this.w = Math.max(1, this.w)
596
604
  this.h = Math.max(1, this.h)
@@ -144,8 +144,8 @@ describe('Vec.Uni', () => {
144
144
  expect(Vec.Uni(new Vec(10, 10))).toMatchObject(new Vec(0.7071067811865475, 0.7071067811865475))
145
145
  })
146
146
 
147
- it('Divide-by-zero spits out NaN (at the moment)', () => {
148
- expect(Vec.Uni(new Vec(0, 0))).toMatchObject(new Vec(NaN, NaN))
147
+ it('Divide-by-zero spits out 0', () => {
148
+ expect(Vec.Uni(new Vec(0, 0))).toMatchObject(new Vec(0, 0))
149
149
  })
150
150
  })
151
151
 
@@ -1,6 +1,6 @@
1
1
  import { VecModel } from '@tldraw/tlschema'
2
2
  import { EASINGS } from './easings'
3
- import { toFixed } from './utils'
3
+ import { clamp, toFixed } from './utils'
4
4
 
5
5
  /** @public */
6
6
  export type VecLike = Vec | VecModel
@@ -189,11 +189,15 @@ export class Vec {
189
189
  }
190
190
 
191
191
  uni() {
192
- return Vec.Uni(this)
192
+ const l = this.len()
193
+ if (l === 0) return this
194
+ this.x /= l
195
+ this.y /= l
196
+ return this
193
197
  }
194
198
 
195
199
  tan(V: VecLike): Vec {
196
- return Vec.Tan(this, V)
200
+ return this.sub(V).uni()
197
201
  }
198
202
 
199
203
  dist(V: VecLike): number {
@@ -236,15 +240,15 @@ export class Vec {
236
240
  return Vec.EqualsXY(this, x, y)
237
241
  }
238
242
 
243
+ /** @deprecated use `uni` instead */
239
244
  norm() {
240
- const l = this.len()
241
- this.x = l === 0 ? 0 : this.x / l
242
- this.y = l === 0 ? 0 : this.y / l
243
- return this
245
+ return this.uni()
244
246
  }
245
247
 
246
248
  toFixed() {
247
- return Vec.ToFixed(this)
249
+ this.x = toFixed(this.x)
250
+ this.y = toFixed(this.y)
251
+ return this
248
252
  }
249
253
 
250
254
  toString() {
@@ -375,7 +379,8 @@ export class Vec {
375
379
  * Get the unit vector of A.
376
380
  */
377
381
  static Uni(A: VecLike) {
378
- return Vec.Div(A, Vec.Len(A))
382
+ const l = Vec.Len(A)
383
+ return new Vec(l === 0 ? 0 : A.x / l, l === 0 ? 0 : A.y / l)
379
384
  }
380
385
 
381
386
  static Tan(A: VecLike, B: VecLike): Vec {
@@ -487,7 +492,7 @@ export class Vec {
487
492
  (Math.pow(A.x, 2) + Math.pow(A.y, 2)) * (Math.pow(B.x, 2) + Math.pow(B.y, 2))
488
493
  )
489
494
  const sign = A.x * B.y - A.y * B.x < 0 ? -1 : 1
490
- const angle = sign * Math.acos(p / n)
495
+ const angle = sign * Math.acos(clamp(p / n, -1, 1))
491
496
 
492
497
  return angle
493
498
  }
@@ -6,16 +6,15 @@ import { getVerticesCountForLength } from './geometry-constants'
6
6
 
7
7
  /** @public */
8
8
  export class Arc2d extends Geometry2d {
9
- _center: Vec
10
- radius: number
11
- start: Vec
12
- end: Vec
13
- largeArcFlag: number
14
- sweepFlag: number
15
-
16
- measure: number
17
- angleStart: number
18
- angleEnd: number
9
+ private _center: Vec
10
+ private _radius: number
11
+ private _start: Vec
12
+ private _end: Vec
13
+ private _largeArcFlag: number
14
+ private _sweepFlag: number
15
+ private _measure: number
16
+ private _angleStart: number
17
+ private _angleEnd: number
19
18
 
20
19
  constructor(
21
20
  config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
@@ -31,21 +30,29 @@ export class Arc2d extends Geometry2d {
31
30
  if (start.equals(end)) throw Error(`Arc must have different start and end points.`)
32
31
 
33
32
  // ensure that the start and end are clockwise
34
- this.angleStart = Vec.Angle(center, start)
35
- this.angleEnd = Vec.Angle(center, end)
36
- this.radius = Vec.Dist(center, start)
37
- this.measure = getArcMeasure(this.angleStart, this.angleEnd, sweepFlag, largeArcFlag)
33
+ this._angleStart = Vec.Angle(center, start)
34
+ this._angleEnd = Vec.Angle(center, end)
35
+ this._radius = Vec.Dist(center, start)
36
+ this._measure = getArcMeasure(this._angleStart, this._angleEnd, sweepFlag, largeArcFlag)
38
37
 
39
- this.start = start
40
- this.end = end
38
+ this._start = start
39
+ this._end = end
41
40
 
42
- this.sweepFlag = sweepFlag
43
- this.largeArcFlag = largeArcFlag
41
+ this._sweepFlag = sweepFlag
42
+ this._largeArcFlag = largeArcFlag
44
43
  this._center = center
45
44
  }
46
45
 
47
46
  nearestPoint(point: VecLike): Vec {
48
- const { _center, measure, radius, angleEnd, angleStart, start: A, end: B } = this
47
+ const {
48
+ _center,
49
+ _measure: measure,
50
+ _radius: radius,
51
+ _angleEnd: angleEnd,
52
+ _angleStart: angleStart,
53
+ _start: A,
54
+ _end: B,
55
+ } = this
49
56
  const t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))
50
57
  if (t <= 0) return A
51
58
  if (t >= 1) return B
@@ -68,7 +75,13 @@ export class Arc2d extends Geometry2d {
68
75
  }
69
76
 
70
77
  hitTestLineSegment(A: VecLike, B: VecLike): boolean {
71
- const { _center, radius, measure, angleStart, angleEnd } = this
78
+ const {
79
+ _center,
80
+ _radius: radius,
81
+ _measure: measure,
82
+ _angleStart: angleStart,
83
+ _angleEnd: angleEnd,
84
+ } = this
72
85
  const intersection = intersectLineSegmentCircle(A, B, _center, radius)
73
86
  if (intersection === null) return false
74
87
 
@@ -79,7 +92,7 @@ export class Arc2d extends Geometry2d {
79
92
  }
80
93
 
81
94
  getVertices(): Vec[] {
82
- const { _center, measure, length, radius, angleStart } = this
95
+ const { _center, _measure: measure, length, _radius: radius, _angleStart: angleStart } = this
83
96
  const vertices: Vec[] = []
84
97
  for (let i = 0, n = getVerticesCountForLength(Math.abs(length)); i < n + 1; i++) {
85
98
  const t = (i / n) * measure
@@ -90,11 +103,17 @@ export class Arc2d extends Geometry2d {
90
103
  }
91
104
 
92
105
  getSvgPathData(first = true) {
93
- const { start, end, radius, largeArcFlag, sweepFlag } = this
106
+ const {
107
+ _start: start,
108
+ _end: end,
109
+ _radius: radius,
110
+ _largeArcFlag: largeArcFlag,
111
+ _sweepFlag: sweepFlag,
112
+ } = this
94
113
  return `${first ? `M${start.toFixed()}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.toFixed()}`
95
114
  }
96
115
 
97
116
  override getLength() {
98
- return Math.abs(this.measure * this.radius)
117
+ return Math.abs(this._measure * this._radius)
99
118
  }
100
119
  }
@@ -7,10 +7,10 @@ import { getVerticesCountForLength } from './geometry-constants'
7
7
 
8
8
  /** @public */
9
9
  export class Circle2d extends Geometry2d {
10
- _center: Vec
11
- radius: number
12
- x: number
13
- y: number
10
+ private _center: Vec
11
+ private _radius: number
12
+ private _x: number
13
+ private _y: number
14
14
 
15
15
  constructor(
16
16
  public config: Omit<Geometry2dOptions, 'isClosed'> & {
@@ -22,18 +22,18 @@ export class Circle2d extends Geometry2d {
22
22
  ) {
23
23
  super({ isClosed: true, ...config })
24
24
  const { x = 0, y = 0, radius } = config
25
- this.x = x
26
- this.y = y
25
+ this._x = x
26
+ this._y = y
27
27
  this._center = new Vec(radius + x, radius + y)
28
- this.radius = radius
28
+ this._radius = radius
29
29
  }
30
30
 
31
31
  getBounds() {
32
- return new Box(this.x, this.y, this.radius * 2, this.radius * 2)
32
+ return new Box(this._x, this._y, this._radius * 2, this._radius * 2)
33
33
  }
34
34
 
35
35
  getVertices(): Vec[] {
36
- const { _center, radius } = this
36
+ const { _center, _radius: radius } = this
37
37
  const perimeter = PI2 * radius
38
38
  const vertices: Vec[] = []
39
39
  for (let i = 0, n = getVerticesCountForLength(perimeter); i < n; i++) {
@@ -44,18 +44,18 @@ export class Circle2d extends Geometry2d {
44
44
  }
45
45
 
46
46
  nearestPoint(point: VecLike): Vec {
47
- const { _center, radius } = this
47
+ const { _center, _radius: radius } = this
48
48
  if (_center.equals(point)) return Vec.AddXY(_center, radius, 0)
49
49
  return Vec.Sub(point, _center).uni().mul(radius).add(_center)
50
50
  }
51
51
 
52
52
  hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
53
- const { _center, radius } = this
53
+ const { _center, _radius: radius } = this
54
54
  return intersectLineSegmentCircle(A, B, _center, radius + distance) !== null
55
55
  }
56
56
 
57
57
  getSvgPathData(): string {
58
- const { _center, radius } = this
58
+ const { _center, _radius: radius } = this
59
59
  return `M${_center.x + radius},${_center.y} a${radius},${radius} 0 1,0 ${radius * 2},0a${radius},${radius} 0 1,0 -${radius * 2},0`
60
60
  }
61
61
  }
@@ -0,0 +1,5 @@
1
+ describe('CubicBezier2d', () => {
2
+ it('should be tested', () => {
3
+ expect(true).toBe(true)
4
+ })
5
+ })
@@ -4,10 +4,10 @@ import { Polyline2d } from './Polyline2d'
4
4
 
5
5
  /** @public */
6
6
  export class CubicBezier2d extends Polyline2d {
7
- a: Vec
8
- b: Vec
9
- c: Vec
10
- d: Vec
7
+ private _a: Vec
8
+ private _b: Vec
9
+ private _c: Vec
10
+ private _d: Vec
11
11
 
12
12
  constructor(
13
13
  config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
@@ -20,15 +20,15 @@ export class CubicBezier2d extends Polyline2d {
20
20
  const { start: a, cp1: b, cp2: c, end: d } = config
21
21
  super({ ...config, points: [a, d] })
22
22
 
23
- this.a = a
24
- this.b = b
25
- this.c = c
26
- this.d = d
23
+ this._a = a
24
+ this._b = b
25
+ this._c = c
26
+ this._d = d
27
27
  }
28
28
 
29
29
  override getVertices() {
30
30
  const vertices = [] as Vec[]
31
- const { a, b, c, d } = this
31
+ const { _a: a, _b: b, _c: c, _d: d } = this
32
32
  // we'll always use ten vertices for each bezier curve
33
33
  for (let i = 0, n = 10; i <= n; i++) {
34
34
  const t = i / n
@@ -48,10 +48,6 @@ export class CubicBezier2d extends Polyline2d {
48
48
  return vertices
49
49
  }
50
50
 
51
- midPoint() {
52
- return CubicBezier2d.GetAtT(this, 0.5)
53
- }
54
-
55
51
  nearestPoint(A: VecLike): Vec {
56
52
  let nearest: Vec | undefined
57
53
  let dist = Infinity
@@ -71,12 +67,12 @@ export class CubicBezier2d extends Polyline2d {
71
67
  }
72
68
 
73
69
  getSvgPathData(first = true) {
74
- const { a, b, c, d } = this
70
+ const { _a: a, _b: b, _c: c, _d: d } = this
75
71
  return `${first ? `M ${a.toFixed()} ` : ``} C${b.toFixed()} ${c.toFixed()} ${d.toFixed()}`
76
72
  }
77
73
 
78
74
  static GetAtT(segment: CubicBezier2d, t: number) {
79
- const { a, b, c, d } = segment
75
+ const { _a: a, _b: b, _c: c, _d: d } = segment
80
76
  return new Vec(
81
77
  (1 - t) * (1 - t) * (1 - t) * a.x +
82
78
  3 * ((1 - t) * (1 - t)) * t * b.x +
@@ -89,9 +85,9 @@ export class CubicBezier2d extends Polyline2d {
89
85
  )
90
86
  }
91
87
 
92
- override getLength(filters?: Geometry2dFilters, precision = 32) {
88
+ override getLength(_filters?: Geometry2dFilters, precision = 32) {
93
89
  let n1: Vec,
94
- p1 = this.a,
90
+ p1 = this._a,
95
91
  length = 0
96
92
  for (let i = 1; i <= precision; i++) {
97
93
  n1 = CubicBezier2d.GetAtT(this, i / precision)
@@ -4,22 +4,22 @@ import { Geometry2d, Geometry2dOptions } from './Geometry2d'
4
4
 
5
5
  /** @public */
6
6
  export class CubicSpline2d extends Geometry2d {
7
- points: Vec[]
7
+ private _points: Vec[]
8
8
 
9
9
  constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { points: Vec[] }) {
10
10
  super({ ...config, isClosed: false, isFilled: false })
11
11
  const { points } = config
12
12
 
13
- this.points = points
13
+ this._points = points
14
14
  }
15
15
 
16
- _segments?: CubicBezier2d[]
16
+ private _segments?: CubicBezier2d[]
17
17
 
18
18
  // eslint-disable-next-line no-restricted-syntax
19
19
  get segments() {
20
20
  if (!this._segments) {
21
21
  this._segments = []
22
- const { points } = this
22
+ const { _points: points } = this
23
23
 
24
24
  const len = points.length
25
25
  const last = len - 2
@@ -54,7 +54,7 @@ export class CubicSpline2d extends Geometry2d {
54
54
  const vertices = this.segments.reduce((acc, segment) => {
55
55
  return acc.concat(segment.vertices)
56
56
  }, [] as Vec[])
57
- vertices.push(this.points[this.points.length - 1])
57
+ vertices.push(this._points[this._points.length - 1])
58
58
  return vertices
59
59
  }
60
60
 
@@ -1,41 +1,36 @@
1
- import { linesIntersect } from '../intersect'
2
1
  import { Vec, VecLike } from '../Vec'
3
2
  import { Geometry2d } from './Geometry2d'
4
3
 
5
4
  /** @public */
6
5
  export class Edge2d extends Geometry2d {
7
- start: Vec
8
- end: Vec
9
- d: Vec
10
- u: Vec
11
- ul: number
6
+ private _start: Vec
7
+ private _end: Vec
8
+ private _d: Vec
9
+ private _u: Vec
10
+ private _ul: number
12
11
 
13
12
  constructor(config: { start: Vec; end: Vec }) {
14
13
  super({ ...config, isClosed: false, isFilled: false })
15
14
  const { start, end } = config
16
15
 
17
- this.start = start
18
- this.end = end
16
+ this._start = start
17
+ this._end = end
19
18
 
20
- this.d = start.clone().sub(end) // the delta from start to end
21
- this.u = this.d.clone().uni() // the unit vector of the edge
22
- this.ul = this.u.len() // the length of the unit vector
19
+ this._d = start.clone().sub(end) // the delta from start to end
20
+ this._u = this._d.clone().uni() // the unit vector of the edge
21
+ this._ul = this._u.len() // the length of the unit vector
23
22
  }
24
23
 
25
24
  override getLength() {
26
- return this.d.len()
27
- }
28
-
29
- midPoint(): Vec {
30
- return this.start.lrp(this.end, 0.5)
25
+ return this._d.len()
31
26
  }
32
27
 
33
28
  override getVertices(): Vec[] {
34
- return [this.start, this.end]
29
+ return [this._start, this._end]
35
30
  }
36
31
 
37
32
  override nearestPoint(point: VecLike): Vec {
38
- const { start, end, d, u, ul: l } = this
33
+ const { _start: start, _end: end, _d: d, _u: u, _ul: l } = this
39
34
  if (d.len() === 0) return start // start and end are the same
40
35
  if (l === 0) return start // no length in the unit vector
41
36
  const k = Vec.Sub(point, start).dpr(u) / l
@@ -48,14 +43,8 @@ export class Edge2d extends Geometry2d {
48
43
  return new Vec(cx, cy)
49
44
  }
50
45
 
51
- override hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
52
- return (
53
- linesIntersect(A, B, this.start, this.end) || this.distanceToLineSegment(A, B) <= distance
54
- )
55
- }
56
-
57
46
  getSvgPathData(first = true) {
58
- const { start, end } = this
47
+ const { _start: start, _end: end } = this
59
48
  return `${first ? `M${start.toFixed()}` : ``} L${end.toFixed()}`
60
49
  }
61
50
  }