@tldraw/editor 5.4.0 → 5.5.0-canary.05a393565877

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 (67) hide show
  1. package/dist-cjs/index.d.ts +5 -2
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/constants.js +2 -0
  4. package/dist-cjs/lib/constants.js.map +2 -2
  5. package/dist-cjs/lib/editor/Editor.js +9 -6
  6. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  7. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
  8. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  9. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
  10. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
  11. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
  12. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
  13. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  14. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  15. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  16. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  17. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  18. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  19. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -5
  20. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  21. package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
  22. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  23. package/dist-cjs/version.js +3 -3
  24. package/dist-cjs/version.js.map +1 -1
  25. package/dist-esm/index.d.mts +5 -2
  26. package/dist-esm/index.mjs +1 -1
  27. package/dist-esm/lib/constants.mjs +2 -0
  28. package/dist-esm/lib/constants.mjs.map +2 -2
  29. package/dist-esm/lib/editor/Editor.mjs +10 -6
  30. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  31. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
  32. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  33. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
  34. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
  35. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
  36. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
  37. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
  38. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  39. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  40. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  41. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  42. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  43. package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -6
  44. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  45. package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
  46. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  47. package/dist-esm/version.mjs +3 -3
  48. package/dist-esm/version.mjs.map +1 -1
  49. package/package.json +7 -7
  50. package/src/lib/constants.ts +8 -0
  51. package/src/lib/editor/Editor.ts +19 -9
  52. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
  53. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
  54. package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
  55. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
  56. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
  57. package/src/lib/primitives/geometry/Arc2d.test.ts +9 -0
  58. package/src/lib/primitives/geometry/Arc2d.ts +1 -1
  59. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +13 -0
  60. package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
  61. package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
  62. package/src/lib/primitives/geometry/Edge2d.ts +1 -1
  63. package/src/lib/primitives/geometry/Group2d.test.ts +26 -5
  64. package/src/lib/primitives/geometry/Group2d.ts +18 -7
  65. package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
  66. package/src/lib/primitives/geometry/Point2d.ts +1 -1
  67. package/src/version.ts +3 -3
@@ -1,3 +1,4 @@
1
+ import { FRAME_MS_60HZ } from "../../../constants.mjs";
1
2
  import { EASINGS } from "../../../primitives/easings.mjs";
2
3
  import { Vec } from "../../../primitives/Vec.mjs";
3
4
  const EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX = 1e3;
@@ -40,10 +41,13 @@ class EdgeScrollManager {
40
41
  this._edgeScrollDuration / (editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)
41
42
  )
42
43
  ) : 1;
43
- this.moveCameraWhenCloseToEdge({
44
- x: edgeScrollProximityFactor.x * eased,
45
- y: edgeScrollProximityFactor.y * eased
46
- });
44
+ this.moveCameraWhenCloseToEdge(
45
+ {
46
+ x: edgeScrollProximityFactor.x * eased,
47
+ y: edgeScrollProximityFactor.y * eased
48
+ },
49
+ elapsed
50
+ );
47
51
  }
48
52
  }
49
53
  }
@@ -90,14 +94,14 @@ class EdgeScrollManager {
90
94
  * Moves the camera when the mouse is close to the edge of the screen.
91
95
  * @public
92
96
  */
93
- moveCameraWhenCloseToEdge(proximityFactor) {
97
+ moveCameraWhenCloseToEdge(proximityFactor, elapsed) {
94
98
  if (proximityFactor.x === 0 && proximityFactor.y === 0) return;
95
99
  const { editor } = this;
96
100
  const screenBounds = editor.getViewportScreenBounds();
97
101
  const screenSizeFactorX = screenBounds.w < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX ? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR : 1;
98
102
  const screenSizeFactorY = screenBounds.h < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX ? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR : 1;
99
103
  const zoomLevel = editor.getZoomLevel();
100
- const pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed;
104
+ const pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed * (elapsed / FRAME_MS_60HZ);
101
105
  const scrollDeltaX = pxSpeed * proximityFactor.x * screenSizeFactorX / zoomLevel;
102
106
  const scrollDeltaY = pxSpeed * proximityFactor.y * screenSizeFactorY / zoomLevel;
103
107
  const { x, y, z } = editor.getCamera();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts"],
4
- "sourcesContent": ["import { EASINGS } from '../../../primitives/easings'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n// Tuned for touch/small-screen feel; adjust together with coarsePointerWidth.\nconst EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX = 1000\nconst EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR = 0.612\n\n/** @public */\nexport class EdgeScrollManager {\n\tconstructor(public editor: Editor) {}\n\n\tprivate _isEdgeScrolling = false\n\tprivate _edgeScrollDuration = -1\n\n\tgetIsEdgeScrolling() {\n\t\treturn this._isEdgeScrolling\n\t}\n\n\t/**\n\t * Update the camera position when the mouse is close to the edge of the screen.\n\t * Run this on every tick when in a state where edge scrolling is enabled.\n\t *\n\t * @public\n\t */\n\tupdateEdgeScrolling(elapsed: number) {\n\t\tconst { editor } = this\n\n\t\tif (editor.getCameraOptions().isLocked) return\n\n\t\tconst edgeScrollProximityFactor = this.getEdgeScroll()\n\t\tif (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {\n\t\t\tif (this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = false\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t} else {\n\t\t\tif (!this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = true\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t\tthis._edgeScrollDuration += elapsed\n\t\t\tif (this._edgeScrollDuration > editor.options.edgeScrollDelay) {\n\t\t\t\tconst eased =\n\t\t\t\t\teditor.options.edgeScrollEaseDuration > 0\n\t\t\t\t\t\t? EASINGS.easeInCubic(\n\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tthis._edgeScrollDuration /\n\t\t\t\t\t\t\t\t\t\t(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 1\n\t\t\t\tthis.moveCameraWhenCloseToEdge({\n\t\t\t\t\tx: edgeScrollProximityFactor.x * eased,\n\t\t\t\t\ty: edgeScrollProximityFactor.y * eased,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the scroll proximity factor for a given position.\n\t * @param position - The mouse position on the axis.\n\t * @param dimension - The component dimension on the axis.\n\t * @param isCoarse - Whether the pointer is coarse.\n\t * @param insetStart - Whether the pointer is inset at the start of the axis.\n\t * @param insetEnd - Whether the pointer is inset at the end of the axis.\n\t * @internal\n\t */\n\tprivate getEdgeProximityFactors(\n\t\tposition: number,\n\t\tdimension: number,\n\t\tisCoarse: boolean,\n\t\tinsetStart: boolean,\n\t\tinsetEnd: boolean\n\t) {\n\t\tconst { editor } = this\n\t\tconst dist = editor.options.edgeScrollDistance\n\t\tconst pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width\n\t\tconst pMin = position - pw\n\t\tconst pMax = position + pw\n\t\tconst min = insetStart ? 0 : dist\n\t\tconst max = insetEnd ? dimension : dimension - dist\n\t\tif (pMin < min) {\n\t\t\treturn Math.min(1, (min - pMin) / dist)\n\t\t} else if (pMax > max) {\n\t\t\treturn -Math.min(1, (pMax - max) / dist)\n\t\t}\n\t\treturn 0\n\t}\n\n\tprivate getEdgeScroll() {\n\t\tconst { editor } = this\n\t\tconst { x, y } = editor.inputs.getCurrentScreenPoint()\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\tconst {\n\t\t\tisCoarsePointer,\n\t\t\tinsets: [t, r, b, l],\n\t\t} = editor.getInstanceState()\n\t\tconst proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)\n\t\tconst proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)\n\n\t\treturn {\n\t\t\tx: proximityFactorX,\n\t\t\ty: proximityFactorY,\n\t\t}\n\t}\n\n\t/**\n\t * Moves the camera when the mouse is close to the edge of the screen.\n\t * @public\n\t */\n\tprivate moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }) {\n\t\tif (proximityFactor.x === 0 && proximityFactor.y === 0) return\n\t\tconst { editor } = this\n\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\t// Determines how much the speed is affected by the screen size\n\t\tconst screenSizeFactorX =\n\t\t\tscreenBounds.w < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\t\tconst screenSizeFactorY =\n\t\t\tscreenBounds.h < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\n\t\t// Determines the base speed of the scroll\n\t\tconst zoomLevel = editor.getZoomLevel()\n\t\tconst pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed\n\t\tconst scrollDeltaX = (pxSpeed * proximityFactor.x * screenSizeFactorX) / zoomLevel\n\t\tconst scrollDeltaY = (pxSpeed * proximityFactor.y * screenSizeFactorY) / zoomLevel\n\n\t\t// update the camera\n\t\tconst { x, y, z } = editor.getCamera()\n\t\teditor.setCamera(new Vec(x + scrollDeltaX, y + scrollDeltaY, z))\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { FRAME_MS_60HZ } from '../../../constants'\nimport { EASINGS } from '../../../primitives/easings'\nimport { Vec } from '../../../primitives/Vec'\nimport type { Editor } from '../../Editor'\n\n// Tuned for touch/small-screen feel; adjust together with coarsePointerWidth.\nconst EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX = 1000\nconst EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR = 0.612\n\n/** @public */\nexport class EdgeScrollManager {\n\tconstructor(public editor: Editor) {}\n\n\tprivate _isEdgeScrolling = false\n\tprivate _edgeScrollDuration = -1\n\n\tgetIsEdgeScrolling() {\n\t\treturn this._isEdgeScrolling\n\t}\n\n\t/**\n\t * Update the camera position when the mouse is close to the edge of the screen.\n\t * Run this on every tick when in a state where edge scrolling is enabled.\n\t *\n\t * @public\n\t */\n\tupdateEdgeScrolling(elapsed: number) {\n\t\tconst { editor } = this\n\n\t\tif (editor.getCameraOptions().isLocked) return\n\n\t\tconst edgeScrollProximityFactor = this.getEdgeScroll()\n\t\tif (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {\n\t\t\tif (this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = false\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t} else {\n\t\t\tif (!this._isEdgeScrolling) {\n\t\t\t\tthis._isEdgeScrolling = true\n\t\t\t\tthis._edgeScrollDuration = 0\n\t\t\t}\n\t\t\tthis._edgeScrollDuration += elapsed\n\t\t\tif (this._edgeScrollDuration > editor.options.edgeScrollDelay) {\n\t\t\t\tconst eased =\n\t\t\t\t\teditor.options.edgeScrollEaseDuration > 0\n\t\t\t\t\t\t? EASINGS.easeInCubic(\n\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t\t\tthis._edgeScrollDuration /\n\t\t\t\t\t\t\t\t\t\t(editor.options.edgeScrollDelay + editor.options.edgeScrollEaseDuration)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 1\n\t\t\t\tthis.moveCameraWhenCloseToEdge(\n\t\t\t\t\t{\n\t\t\t\t\t\tx: edgeScrollProximityFactor.x * eased,\n\t\t\t\t\t\ty: edgeScrollProximityFactor.y * eased,\n\t\t\t\t\t},\n\t\t\t\t\telapsed\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to get the scroll proximity factor for a given position.\n\t * @param position - The mouse position on the axis.\n\t * @param dimension - The component dimension on the axis.\n\t * @param isCoarse - Whether the pointer is coarse.\n\t * @param insetStart - Whether the pointer is inset at the start of the axis.\n\t * @param insetEnd - Whether the pointer is inset at the end of the axis.\n\t * @internal\n\t */\n\tprivate getEdgeProximityFactors(\n\t\tposition: number,\n\t\tdimension: number,\n\t\tisCoarse: boolean,\n\t\tinsetStart: boolean,\n\t\tinsetEnd: boolean\n\t) {\n\t\tconst { editor } = this\n\t\tconst dist = editor.options.edgeScrollDistance\n\t\tconst pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width\n\t\tconst pMin = position - pw\n\t\tconst pMax = position + pw\n\t\tconst min = insetStart ? 0 : dist\n\t\tconst max = insetEnd ? dimension : dimension - dist\n\t\tif (pMin < min) {\n\t\t\treturn Math.min(1, (min - pMin) / dist)\n\t\t} else if (pMax > max) {\n\t\t\treturn -Math.min(1, (pMax - max) / dist)\n\t\t}\n\t\treturn 0\n\t}\n\n\tprivate getEdgeScroll() {\n\t\tconst { editor } = this\n\t\tconst { x, y } = editor.inputs.getCurrentScreenPoint()\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\tconst {\n\t\t\tisCoarsePointer,\n\t\t\tinsets: [t, r, b, l],\n\t\t} = editor.getInstanceState()\n\t\tconst proximityFactorX = this.getEdgeProximityFactors(x, screenBounds.w, isCoarsePointer, l, r)\n\t\tconst proximityFactorY = this.getEdgeProximityFactors(y, screenBounds.h, isCoarsePointer, t, b)\n\n\t\treturn {\n\t\t\tx: proximityFactorX,\n\t\t\ty: proximityFactorY,\n\t\t}\n\t}\n\n\t/**\n\t * Moves the camera when the mouse is close to the edge of the screen.\n\t * @public\n\t */\n\tprivate moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }, elapsed: number) {\n\t\tif (proximityFactor.x === 0 && proximityFactor.y === 0) return\n\t\tconst { editor } = this\n\n\t\tconst screenBounds = editor.getViewportScreenBounds()\n\n\t\t// Determines how much the speed is affected by the screen size\n\t\tconst screenSizeFactorX =\n\t\t\tscreenBounds.w < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\t\tconst screenSizeFactorY =\n\t\t\tscreenBounds.h < EDGE_SCROLL_SMALL_SCREEN_THRESHOLD_PX\n\t\t\t\t? EDGE_SCROLL_SMALL_SCREEN_SPEED_FACTOR\n\t\t\t\t: 1\n\n\t\t// Determines the base speed of the scroll. edgeScrollSpeed is px per 60 Hz frame, so scale\n\t\t// by elapsed time or a 120 Hz display scrolls twice as fast.\n\t\tconst zoomLevel = editor.getZoomLevel()\n\t\tconst pxSpeed =\n\t\t\teditor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed * (elapsed / FRAME_MS_60HZ)\n\t\tconst scrollDeltaX = (pxSpeed * proximityFactor.x * screenSizeFactorX) / zoomLevel\n\t\tconst scrollDeltaY = (pxSpeed * proximityFactor.y * screenSizeFactorY) / zoomLevel\n\n\t\t// update the camera\n\t\tconst { x, y, z } = editor.getCamera()\n\t\teditor.setCamera(new Vec(x + scrollDeltaX, y + scrollDeltaY, z))\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -41,14 +41,18 @@ class ThemeManager {
41
41
  getCurrentTheme() {
42
42
  return this._themes.get()[this.getCurrentThemeId()];
43
43
  }
44
- /** Set the current theme by id. The theme must have been previously registered. */
44
+ /**
45
+ * Set the current theme by id. The theme must have been previously registered; an
46
+ * unregistered id is ignored and the current theme is kept.
47
+ */
45
48
  setCurrentTheme(id) {
46
- if (process.env.NODE_ENV !== "production") {
47
- if (!(id in this._themes.get())) {
49
+ if (!(id in this._themes.get())) {
50
+ if (process.env.NODE_ENV !== "production") {
48
51
  console.warn(
49
52
  `Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(", ")}`
50
53
  );
51
54
  }
55
+ return;
52
56
  }
53
57
  this._currentThemeId.set(id);
54
58
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/managers/ThemeManager/ThemeManager.ts"],
4
- "sourcesContent": ["import { Atom, atom, computed } from '@tldraw/state'\nimport { TLTheme, TLThemeId, TLThemes } from '@tldraw/tlschema'\nimport { structuredClone } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { DEFAULT_THEME } from './defaultThemes'\n\n/**\n * Resolve a partial set of user-provided themes into a complete `TLThemes`\n * record by merging with `DEFAULT_THEME`. The result is suitable for passing to\n * `registerColorsFromThemes`, `registerFontsFromThemes`, and the\n * `ThemeManager` constructor.\n *\n * @public\n */\nexport function resolveThemes(themes?: Partial<TLThemes>): TLThemes {\n\treturn { default: DEFAULT_THEME, ...themes } as TLThemes\n}\n\n/**\n * Manages the editor's color themes.\n *\n * Stores named theme definitions (each containing light and dark color palettes\n * alongside shared properties like font size). The current theme is resolved by\n * combining the current theme name with the user's color mode preference.\n *\n * **Terminology:**\n * - **Theme** (`TLTheme`): A named set of colors and typographic values for both light and dark modes.\n * - **Color mode** (`'light' | 'dark'`): The resolved appearance mode, derived from the user's\n * `colorScheme` preference (`'light' | 'dark' | 'system'`). Access via `getColorMode()`.\n *\n * @public\n */\nexport class ThemeManager {\n\tprivate readonly _themes: Atom<TLThemes>\n\tprivate readonly _currentThemeId: Atom<TLThemeId>\n\n\tconstructor(\n\t\tprivate readonly editor: Editor,\n\t\toptions: {\n\t\t\tthemes: TLThemes\n\t\t\tinitial: TLThemeId\n\t\t}\n\t) {\n\t\tthis._themes = atom('ThemeManager._definitions', options.themes)\n\t\tthis._currentThemeId = atom('ThemeManager._currentThemeName', options.initial)\n\t}\n\n\t/** Get the current color mode based on the user's dark mode preference. */\n\t@computed getColorMode(): 'light' | 'dark' {\n\t\treturn this.editor.user.getIsDarkMode() ? 'dark' : 'light'\n\t}\n\n\t/** Get all registered theme definitions. */\n\tgetThemes(): TLThemes {\n\t\treturn this._themes.get()\n\t}\n\n\t/** Get a single theme definition by id. */\n\tgetTheme(id: TLThemeId): TLTheme | undefined {\n\t\treturn this._themes.get()[id]\n\t}\n\n\t/** Get the id of the current theme. */\n\tgetCurrentThemeId(): TLThemeId {\n\t\treturn this._currentThemeId.get()\n\t}\n\n\tgetCurrentTheme(): TLTheme {\n\t\treturn this._themes.get()[this.getCurrentThemeId()]!\n\t}\n\n\t/** Set the current theme by id. The theme must have been previously registered. */\n\tsetCurrentTheme(id: TLThemeId): void {\n\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\tif (!(id in this._themes.get())) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tthis._currentThemeId.set(id)\n\t}\n\n\t/** Replace all theme definitions, or update them via a callback that receives a deep copy. */\n\tupdateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)): void {\n\t\tthis._themes.update((prev) => {\n\t\t\tconst next = typeof themes === 'function' ? themes(structuredClone(prev)) : themes\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\tif (!('default' in next)) {\n\t\t\t\t\tconsole.warn(\"The 'default' theme cannot be removed.\")\n\t\t\t\t\treturn prev\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If the current theme was removed, fall back to 'default'\n\t\t\tif (!(this._currentThemeId.get() in next)) {\n\t\t\t\tthis._currentThemeId.set('default')\n\t\t\t}\n\t\t\treturn next\n\t\t})\n\t}\n\n\t/** Register or update a named theme definition. */\n\tupdateTheme(theme: TLTheme): void {\n\t\tthis._themes.update((prev) => ({\n\t\t\t...prev,\n\t\t\t[theme.id]: theme,\n\t\t}))\n\t}\n\n\t/** Clean up any resources held by the manager. */\n\tdispose() {\n\t\t// currently no subscriptions to tear down, but here for consistency\n\t\t// with the manager pattern and for future use\n\t}\n}\n"],
5
- "mappings": ";;;;;;;;;;AAAA,SAAe,MAAM,gBAAgB;AAErC,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAUvB,SAAS,cAAc,QAAsC;AACnE,SAAO,EAAE,SAAS,eAAe,GAAG,OAAO;AAC5C;AAgBO,MAAM,aAAa;AAAA,EAIzB,YACkB,QACjB,SAIC;AALgB;AAMjB,SAAK,UAAU,KAAK,6BAA6B,QAAQ,MAAM;AAC/D,SAAK,kBAAkB,KAAK,kCAAkC,QAAQ,OAAO;AAAA,EAC9E;AAAA,EARkB;AAAA,EAJD;AAAA,EACA;AAAA,EAcP,eAAiC;AAC1C,WAAO,KAAK,OAAO,KAAK,cAAc,IAAI,SAAS;AAAA,EACpD;AAAA;AAAA,EAGA,YAAsB;AACrB,WAAO,KAAK,QAAQ,IAAI;AAAA,EACzB;AAAA;AAAA,EAGA,SAAS,IAAoC;AAC5C,WAAO,KAAK,QAAQ,IAAI,EAAE,EAAE;AAAA,EAC7B;AAAA;AAAA,EAGA,oBAA+B;AAC9B,WAAO,KAAK,gBAAgB,IAAI;AAAA,EACjC;AAAA,EAEA,kBAA2B;AAC1B,WAAO,KAAK,QAAQ,IAAI,EAAE,KAAK,kBAAkB,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,gBAAgB,IAAqB;AACpC,QAAI,QAAQ,IAAI,aAAa,cAAc;AAC1C,UAAI,EAAE,MAAM,KAAK,QAAQ,IAAI,IAAI;AAChC,gBAAQ;AAAA,UACP,UAAU,EAAE,kCAAkC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,QACzF;AAAA,MACD;AAAA,IACD;AAEA,SAAK,gBAAgB,IAAI,EAAE;AAAA,EAC5B;AAAA;AAAA,EAGA,aAAa,QAA2D;AACvE,SAAK,QAAQ,OAAO,CAAC,SAAS;AAC7B,YAAM,OAAO,OAAO,WAAW,aAAa,OAAO,gBAAgB,IAAI,CAAC,IAAI;AAC5E,UAAI,QAAQ,IAAI,aAAa,cAAc;AAC1C,YAAI,EAAE,aAAa,OAAO;AACzB,kBAAQ,KAAK,wCAAwC;AACrD,iBAAO;AAAA,QACR;AAAA,MACD;AAEA,UAAI,EAAE,KAAK,gBAAgB,IAAI,KAAK,OAAO;AAC1C,aAAK,gBAAgB,IAAI,SAAS;AAAA,MACnC;AACA,aAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,YAAY,OAAsB;AACjC,SAAK,QAAQ,OAAO,CAAC,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH,CAAC,MAAM,EAAE,GAAG;AAAA,IACb,EAAE;AAAA,EACH;AAAA;AAAA,EAGA,UAAU;AAAA,EAGV;AACD;AAnEW;AAAA,EAAT;AAAA,GAhBW,aAgBF;",
4
+ "sourcesContent": ["import { Atom, atom, computed } from '@tldraw/state'\nimport { TLTheme, TLThemeId, TLThemes } from '@tldraw/tlschema'\nimport { structuredClone } from '@tldraw/utils'\nimport type { Editor } from '../../Editor'\nimport { DEFAULT_THEME } from './defaultThemes'\n\n/**\n * Resolve a partial set of user-provided themes into a complete `TLThemes`\n * record by merging with `DEFAULT_THEME`. The result is suitable for passing to\n * `registerColorsFromThemes`, `registerFontsFromThemes`, and the\n * `ThemeManager` constructor.\n *\n * @public\n */\nexport function resolveThemes(themes?: Partial<TLThemes>): TLThemes {\n\treturn { default: DEFAULT_THEME, ...themes } as TLThemes\n}\n\n/**\n * Manages the editor's color themes.\n *\n * Stores named theme definitions (each containing light and dark color palettes\n * alongside shared properties like font size). The current theme is resolved by\n * combining the current theme name with the user's color mode preference.\n *\n * **Terminology:**\n * - **Theme** (`TLTheme`): A named set of colors and typographic values for both light and dark modes.\n * - **Color mode** (`'light' | 'dark'`): The resolved appearance mode, derived from the user's\n * `colorScheme` preference (`'light' | 'dark' | 'system'`). Access via `getColorMode()`.\n *\n * @public\n */\nexport class ThemeManager {\n\tprivate readonly _themes: Atom<TLThemes>\n\tprivate readonly _currentThemeId: Atom<TLThemeId>\n\n\tconstructor(\n\t\tprivate readonly editor: Editor,\n\t\toptions: {\n\t\t\tthemes: TLThemes\n\t\t\tinitial: TLThemeId\n\t\t}\n\t) {\n\t\tthis._themes = atom('ThemeManager._definitions', options.themes)\n\t\tthis._currentThemeId = atom('ThemeManager._currentThemeName', options.initial)\n\t}\n\n\t/** Get the current color mode based on the user's dark mode preference. */\n\t@computed getColorMode(): 'light' | 'dark' {\n\t\treturn this.editor.user.getIsDarkMode() ? 'dark' : 'light'\n\t}\n\n\t/** Get all registered theme definitions. */\n\tgetThemes(): TLThemes {\n\t\treturn this._themes.get()\n\t}\n\n\t/** Get a single theme definition by id. */\n\tgetTheme(id: TLThemeId): TLTheme | undefined {\n\t\treturn this._themes.get()[id]\n\t}\n\n\t/** Get the id of the current theme. */\n\tgetCurrentThemeId(): TLThemeId {\n\t\treturn this._currentThemeId.get()\n\t}\n\n\tgetCurrentTheme(): TLTheme {\n\t\treturn this._themes.get()[this.getCurrentThemeId()]!\n\t}\n\n\t/**\n\t * Set the current theme by id. The theme must have been previously registered; an\n\t * unregistered id is ignored and the current theme is kept.\n\t */\n\tsetCurrentTheme(id: TLThemeId): void {\n\t\t// Storing an unregistered id would make getCurrentTheme() return\n\t\t// undefined despite its TLTheme return type.\n\t\tif (!(id in this._themes.get())) {\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tthis._currentThemeId.set(id)\n\t}\n\n\t/** Replace all theme definitions, or update them via a callback that receives a deep copy. */\n\tupdateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)): void {\n\t\tthis._themes.update((prev) => {\n\t\t\tconst next = typeof themes === 'function' ? themes(structuredClone(prev)) : themes\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\tif (!('default' in next)) {\n\t\t\t\t\tconsole.warn(\"The 'default' theme cannot be removed.\")\n\t\t\t\t\treturn prev\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If the current theme was removed, fall back to 'default'\n\t\t\tif (!(this._currentThemeId.get() in next)) {\n\t\t\t\tthis._currentThemeId.set('default')\n\t\t\t}\n\t\t\treturn next\n\t\t})\n\t}\n\n\t/** Register or update a named theme definition. */\n\tupdateTheme(theme: TLTheme): void {\n\t\tthis._themes.update((prev) => ({\n\t\t\t...prev,\n\t\t\t[theme.id]: theme,\n\t\t}))\n\t}\n\n\t/** Clean up any resources held by the manager. */\n\tdispose() {\n\t\t// currently no subscriptions to tear down, but here for consistency\n\t\t// with the manager pattern and for future use\n\t}\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAAA,SAAe,MAAM,gBAAgB;AAErC,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAUvB,SAAS,cAAc,QAAsC;AACnE,SAAO,EAAE,SAAS,eAAe,GAAG,OAAO;AAC5C;AAgBO,MAAM,aAAa;AAAA,EAIzB,YACkB,QACjB,SAIC;AALgB;AAMjB,SAAK,UAAU,KAAK,6BAA6B,QAAQ,MAAM;AAC/D,SAAK,kBAAkB,KAAK,kCAAkC,QAAQ,OAAO;AAAA,EAC9E;AAAA,EARkB;AAAA,EAJD;AAAA,EACA;AAAA,EAcP,eAAiC;AAC1C,WAAO,KAAK,OAAO,KAAK,cAAc,IAAI,SAAS;AAAA,EACpD;AAAA;AAAA,EAGA,YAAsB;AACrB,WAAO,KAAK,QAAQ,IAAI;AAAA,EACzB;AAAA;AAAA,EAGA,SAAS,IAAoC;AAC5C,WAAO,KAAK,QAAQ,IAAI,EAAE,EAAE;AAAA,EAC7B;AAAA;AAAA,EAGA,oBAA+B;AAC9B,WAAO,KAAK,gBAAgB,IAAI;AAAA,EACjC;AAAA,EAEA,kBAA2B;AAC1B,WAAO,KAAK,QAAQ,IAAI,EAAE,KAAK,kBAAkB,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,IAAqB;AAGpC,QAAI,EAAE,MAAM,KAAK,QAAQ,IAAI,IAAI;AAChC,UAAI,QAAQ,IAAI,aAAa,cAAc;AAC1C,gBAAQ;AAAA,UACP,UAAU,EAAE,kCAAkC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,QACzF;AAAA,MACD;AACA;AAAA,IACD;AAEA,SAAK,gBAAgB,IAAI,EAAE;AAAA,EAC5B;AAAA;AAAA,EAGA,aAAa,QAA2D;AACvE,SAAK,QAAQ,OAAO,CAAC,SAAS;AAC7B,YAAM,OAAO,OAAO,WAAW,aAAa,OAAO,gBAAgB,IAAI,CAAC,IAAI;AAC5E,UAAI,QAAQ,IAAI,aAAa,cAAc;AAC1C,YAAI,EAAE,aAAa,OAAO;AACzB,kBAAQ,KAAK,wCAAwC;AACrD,iBAAO;AAAA,QACR;AAAA,MACD;AAEA,UAAI,EAAE,KAAK,gBAAgB,IAAI,KAAK,OAAO;AAC1C,aAAK,gBAAgB,IAAI,SAAS;AAAA,MACnC;AACA,aAAO;AAAA,IACR,CAAC;AAAA,EACF;AAAA;AAAA,EAGA,YAAY,OAAsB;AACjC,SAAK,QAAQ,OAAO,CAAC,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH,CAAC,MAAM,EAAE,GAAG;AAAA,IACb,EAAE;AAAA,EACH;AAAA;AAAA,EAGA,UAAU;AAAA,EAGV;AACD;AAzEW;AAAA,EAAT;AAAA,GAhBW,aAgBF;",
6
6
  "names": []
7
7
  }
@@ -30,12 +30,20 @@ class GroupShapeUtil extends ShapeUtil {
30
30
  if (children.length === 0) {
31
31
  return new Rectangle2d({ width: 1, height: 1, isFilled: false });
32
32
  }
33
- return new Group2d({
34
- children: children.map((childId) => {
35
- const shape2 = this.editor.getShape(childId);
36
- return this.editor.getShapeGeometry(childId).transform(this.editor.getShapeLocalTransform(shape2), { isLabel: false });
37
- })
38
- });
33
+ const geometries = [];
34
+ for (const childId of children) {
35
+ const child = this.editor.getShape(childId);
36
+ if (!child) continue;
37
+ const geometry = this.editor.getShapeGeometry(childId);
38
+ if (!geometry) continue;
39
+ const transform = this.editor.getShapeLocalTransform(child);
40
+ if (!transform) continue;
41
+ geometries.push(geometry.transform(transform, { isLabel: false }));
42
+ }
43
+ if (geometries.length === 0) {
44
+ return new Rectangle2d({ width: 1, height: 1, isFilled: false });
45
+ }
46
+ return new Group2d({ children: geometries });
39
47
  }
40
48
  component(shape) {
41
49
  const isErasing = this.editor.getErasingShapeIds().includes(shape.id);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/lib/editor/shapes/group/GroupShapeUtil.tsx"],
4
- "sourcesContent": ["import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tlschema'\nimport { SVGContainer } from '../../../components/SVGContainer'\nimport { Geometry2d } from '../../../primitives/geometry/Geometry2d'\nimport { Group2d } from '../../../primitives/geometry/Group2d'\nimport { Rectangle2d } from '../../../primitives/geometry/Rectangle2d'\nimport { ShapeUtil } from '../ShapeUtil'\nimport { getPerfectDashProps } from '../shared/getPerfectDashProps'\nimport { DashedOutlineBox } from './DashedOutlineBox'\n\n/** @public */\nexport class GroupShapeUtil extends ShapeUtil<TLGroupShape> {\n\tstatic override type = 'group' as const\n\tstatic override props = groupShapeProps\n\tstatic override migrations = groupShapeMigrations\n\n\toverride hideSelectionBoundsFg(shape: TLGroupShape) {\n\t\treturn true\n\t}\n\n\toverride canBind() {\n\t\treturn false\n\t}\n\n\tcanResize() {\n\t\treturn true\n\t}\n\n\tcanResizeChildren() {\n\t\treturn true\n\t}\n\n\tgetDefaultProps(): TLGroupShape['props'] {\n\t\treturn {}\n\t}\n\n\tgetGeometry(shape: TLGroupShape): Geometry2d {\n\t\tconst children = this.editor.getSortedChildIdsForParent(shape.id)\n\t\tif (children.length === 0) {\n\t\t\treturn new Rectangle2d({ width: 1, height: 1, isFilled: false })\n\t\t}\n\n\t\treturn new Group2d({\n\t\t\tchildren: children.map((childId) => {\n\t\t\t\tconst shape = this.editor.getShape(childId)!\n\t\t\t\treturn this.editor\n\t\t\t\t\t.getShapeGeometry(childId)\n\t\t\t\t\t.transform(this.editor.getShapeLocalTransform(shape)!, { isLabel: false })\n\t\t\t}),\n\t\t})\n\t}\n\n\tcomponent(shape: TLGroupShape) {\n\t\tconst isErasing = this.editor.getErasingShapeIds().includes(shape.id)\n\n\t\tconst { hintingShapeIds } = this.editor.getCurrentPageState()\n\t\tconst isHintingOtherGroup =\n\t\t\thintingShapeIds.length > 0 &&\n\t\t\thintingShapeIds.some(\n\t\t\t\t(id) => id !== shape.id && this.editor.isShapeOfType(this.editor.getShape(id)!, 'group')\n\t\t\t)\n\n\t\tconst isFocused = this.editor.getCurrentPageState().focusedGroupId !== shape.id\n\n\t\tif (\n\t\t\t!isErasing && // always show the outline while we're erasing the group\n\t\t\t// show the outline while the group is focused unless something outside of the group is being hinted\n\t\t\t// this happens dropping shapes from a group onto some outside group\n\t\t\t(isFocused || isHintingOtherGroup)\n\t\t) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\n\t\treturn (\n\t\t\t<SVGContainer>\n\t\t\t\t<DashedOutlineBox className=\"tl-group\" bounds={bounds} />\n\t\t\t</SVGContainer>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLGroupShape): Path2D {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst zoomLevel = this.editor.getEfficientZoomLevel()\n\t\tconst path = new Path2D()\n\n\t\tfor (const side of bounds.sides) {\n\t\t\tconst [start, end] = side\n\t\t\tconst length = start.dist(end)\n\t\t\tif (length <= 0) continue\n\n\t\t\tconst { strokeDasharray, strokeDashoffset } = getPerfectDashProps(length, 1 / zoomLevel, {\n\t\t\t\tstyle: 'dashed',\n\t\t\t\tlengthRatio: 4,\n\t\t\t})\n\n\t\t\tif (strokeDasharray === 'none') {\n\t\t\t\tpath.moveTo(start.x, start.y)\n\t\t\t\tpath.lineTo(end.x, end.y)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst [dashLength, gapLength] = strokeDasharray.split(' ').map(Number)\n\t\t\tconst dashOffset = Number(strokeDashoffset)\n\t\t\tconst period = dashLength + gapLength\n\t\t\tif (!Number.isFinite(period) || period <= 0) continue\n\n\t\t\tconst dx = (end.x - start.x) / length\n\t\t\tconst dy = (end.y - start.y) / length\n\n\t\t\tfor (let dashStart = -dashOffset; dashStart < length; dashStart += period) {\n\t\t\t\tconst dashEnd = Math.min(length, dashStart + dashLength)\n\t\t\t\tconst clippedDashStart = Math.max(0, dashStart)\n\t\t\t\tif (dashEnd <= clippedDashStart) continue\n\n\t\t\t\tpath.moveTo(start.x + dx * clippedDashStart, start.y + dy * clippedDashStart)\n\t\t\t\tpath.lineTo(start.x + dx * dashEnd, start.y + dy * dashEnd)\n\t\t\t}\n\t\t}\n\n\t\treturn path\n\t}\n\n\toverride onChildrenChange(group: TLGroupShape) {\n\t\tconst children = this.editor.getSortedChildIdsForParent(group.id)\n\t\tif (children.length === 0) {\n\t\t\tif (this.editor.getCurrentPageState().focusedGroupId === group.id) {\n\t\t\t\tthis.editor.popFocusedGroupId()\n\t\t\t}\n\t\t\tthis.editor.deleteShapes([group.id])\n\t\t\treturn\n\t\t} else if (children.length === 1) {\n\t\t\tif (this.editor.getCurrentPageState().focusedGroupId === group.id) {\n\t\t\t\tthis.editor.popFocusedGroupId()\n\t\t\t}\n\t\t\tthis.editor.reparentShapes(children, group.parentId, group.index)\n\t\t\tthis.editor.deleteShapes([group.id])\n\t\t\treturn\n\t\t}\n\t}\n}\n"],
5
- "mappings": "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",
6
- "names": ["shape"]
4
+ "sourcesContent": ["import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tlschema'\nimport { SVGContainer } from '../../../components/SVGContainer'\nimport { Geometry2d } from '../../../primitives/geometry/Geometry2d'\nimport { Group2d } from '../../../primitives/geometry/Group2d'\nimport { Rectangle2d } from '../../../primitives/geometry/Rectangle2d'\nimport { ShapeUtil } from '../ShapeUtil'\nimport { getPerfectDashProps } from '../shared/getPerfectDashProps'\nimport { DashedOutlineBox } from './DashedOutlineBox'\n\n/** @public */\nexport class GroupShapeUtil extends ShapeUtil<TLGroupShape> {\n\tstatic override type = 'group' as const\n\tstatic override props = groupShapeProps\n\tstatic override migrations = groupShapeMigrations\n\n\toverride hideSelectionBoundsFg(shape: TLGroupShape) {\n\t\treturn true\n\t}\n\n\toverride canBind() {\n\t\treturn false\n\t}\n\n\tcanResize() {\n\t\treturn true\n\t}\n\n\tcanResizeChildren() {\n\t\treturn true\n\t}\n\n\tgetDefaultProps(): TLGroupShape['props'] {\n\t\treturn {}\n\t}\n\n\tgetGeometry(shape: TLGroupShape): Geometry2d {\n\t\tconst children = this.editor.getSortedChildIdsForParent(shape.id)\n\t\tif (children.length === 0) {\n\t\t\treturn new Rectangle2d({ width: 1, height: 1, isFilled: false })\n\t\t}\n\n\t\t// A child id can outlive its record. The children index and the geometry cache are separate\n\t\t// derivations, so inside a transaction that removes a child the index can still name it while\n\t\t// `getShapeGeometry` already answers nothing \u2014 its `!` hides that it returns `undefined` for a\n\t\t// record the store no longer holds. Asserting instead of checking threw a `TypeError` out of a\n\t\t// reactive recompute, where no caller could catch it.\n\t\tconst geometries: Geometry2d[] = []\n\t\tfor (const childId of children) {\n\t\t\tconst child = this.editor.getShape(childId)\n\t\t\tif (!child) continue\n\t\t\tconst geometry = this.editor.getShapeGeometry(childId)\n\t\t\tif (!geometry) continue\n\t\t\tconst transform = this.editor.getShapeLocalTransform(child)\n\t\t\tif (!transform) continue\n\t\t\tgeometries.push(geometry.transform(transform, { isLabel: false }))\n\t\t}\n\n\t\t// Every child resolved to nothing, so there is no meaningful extent to report. Matches the\n\t\t// no-children case above rather than building an empty `Group2d`, whose bounds are `NaN`.\n\t\tif (geometries.length === 0) {\n\t\t\treturn new Rectangle2d({ width: 1, height: 1, isFilled: false })\n\t\t}\n\n\t\treturn new Group2d({ children: geometries })\n\t}\n\n\tcomponent(shape: TLGroupShape) {\n\t\tconst isErasing = this.editor.getErasingShapeIds().includes(shape.id)\n\n\t\tconst { hintingShapeIds } = this.editor.getCurrentPageState()\n\t\tconst isHintingOtherGroup =\n\t\t\thintingShapeIds.length > 0 &&\n\t\t\thintingShapeIds.some(\n\t\t\t\t(id) => id !== shape.id && this.editor.isShapeOfType(this.editor.getShape(id)!, 'group')\n\t\t\t)\n\n\t\tconst isFocused = this.editor.getCurrentPageState().focusedGroupId !== shape.id\n\n\t\tif (\n\t\t\t!isErasing && // always show the outline while we're erasing the group\n\t\t\t// show the outline while the group is focused unless something outside of the group is being hinted\n\t\t\t// this happens dropping shapes from a group onto some outside group\n\t\t\t(isFocused || isHintingOtherGroup)\n\t\t) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\n\t\treturn (\n\t\t\t<SVGContainer>\n\t\t\t\t<DashedOutlineBox className=\"tl-group\" bounds={bounds} />\n\t\t\t</SVGContainer>\n\t\t)\n\t}\n\n\toverride getIndicatorPath(shape: TLGroupShape): Path2D {\n\t\tconst bounds = this.editor.getShapeGeometry(shape).bounds\n\t\tconst zoomLevel = this.editor.getEfficientZoomLevel()\n\t\tconst path = new Path2D()\n\n\t\tfor (const side of bounds.sides) {\n\t\t\tconst [start, end] = side\n\t\t\tconst length = start.dist(end)\n\t\t\tif (length <= 0) continue\n\n\t\t\tconst { strokeDasharray, strokeDashoffset } = getPerfectDashProps(length, 1 / zoomLevel, {\n\t\t\t\tstyle: 'dashed',\n\t\t\t\tlengthRatio: 4,\n\t\t\t})\n\n\t\t\tif (strokeDasharray === 'none') {\n\t\t\t\tpath.moveTo(start.x, start.y)\n\t\t\t\tpath.lineTo(end.x, end.y)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst [dashLength, gapLength] = strokeDasharray.split(' ').map(Number)\n\t\t\tconst dashOffset = Number(strokeDashoffset)\n\t\t\tconst period = dashLength + gapLength\n\t\t\tif (!Number.isFinite(period) || period <= 0) continue\n\n\t\t\tconst dx = (end.x - start.x) / length\n\t\t\tconst dy = (end.y - start.y) / length\n\n\t\t\tfor (let dashStart = -dashOffset; dashStart < length; dashStart += period) {\n\t\t\t\tconst dashEnd = Math.min(length, dashStart + dashLength)\n\t\t\t\tconst clippedDashStart = Math.max(0, dashStart)\n\t\t\t\tif (dashEnd <= clippedDashStart) continue\n\n\t\t\t\tpath.moveTo(start.x + dx * clippedDashStart, start.y + dy * clippedDashStart)\n\t\t\t\tpath.lineTo(start.x + dx * dashEnd, start.y + dy * dashEnd)\n\t\t\t}\n\t\t}\n\n\t\treturn path\n\t}\n\n\toverride onChildrenChange(group: TLGroupShape) {\n\t\tconst children = this.editor.getSortedChildIdsForParent(group.id)\n\t\tif (children.length === 0) {\n\t\t\tif (this.editor.getCurrentPageState().focusedGroupId === group.id) {\n\t\t\t\tthis.editor.popFocusedGroupId()\n\t\t\t}\n\t\t\tthis.editor.deleteShapes([group.id])\n\t\t\treturn\n\t\t} else if (children.length === 1) {\n\t\t\tif (this.editor.getCurrentPageState().focusedGroupId === group.id) {\n\t\t\t\tthis.editor.popFocusedGroupId()\n\t\t\t}\n\t\t\tthis.editor.reparentShapes(children, group.parentId, group.index)\n\t\t\tthis.editor.deleteShapes([group.id])\n\t\t\treturn\n\t\t}\n\t}\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
@@ -80,7 +80,7 @@ class Arc2d extends Geometry2d {
80
80
  _largeArcFlag: largeArcFlag,
81
81
  _sweepFlag: sweepFlag
82
82
  } = this;
83
- return `${first ? `M${start.toFixed()}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.toFixed()}`;
83
+ return `${first ? `M${start}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end}`;
84
84
  }
85
85
  getLength() {
86
86
  return Math.abs(this._measure * this._radius);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/Arc2d.ts"],
4
- "sourcesContent": ["import { intersectLineSegmentCircle } from '../intersect'\nimport { getArcMeasure, getPointInArcT, getPointOnCircle } from '../utils'\nimport { Vec, VecLike } from '../Vec'\nimport { getVerticesCountForArcLength } from './geometry-constants'\nimport { Geometry2d, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Arc2d extends Geometry2d {\n\tprivate _center: Vec\n\tprivate _radius: number\n\tprivate _start: Vec\n\tprivate _end: Vec\n\tprivate _largeArcFlag: number\n\tprivate _sweepFlag: number\n\tprivate _measure: number\n\tprivate _angleStart: number\n\tprivate _angleEnd: number\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {\n\t\t\tcenter: Vec\n\t\t\tstart: Vec\n\t\t\tend: Vec\n\t\t\tsweepFlag: number\n\t\t\tlargeArcFlag: number\n\t\t}\n\t) {\n\t\tsuper({ ...config, isFilled: false, isClosed: false })\n\t\tconst { center, sweepFlag, largeArcFlag, start, end } = config\n\t\tif (start.equals(end)) throw Error(`Arc must have different start and end points.`)\n\n\t\t// ensure that the start and end are clockwise\n\t\tthis._angleStart = Vec.Angle(center, start)\n\t\tthis._angleEnd = Vec.Angle(center, end)\n\t\tthis._radius = Vec.Dist(center, start)\n\t\tthis._measure = getArcMeasure(this._angleStart, this._angleEnd, sweepFlag, largeArcFlag)\n\n\t\tthis._start = start\n\t\tthis._end = end\n\n\t\tthis._sweepFlag = sweepFlag\n\t\tthis._largeArcFlag = largeArcFlag\n\t\tthis._center = center\n\t}\n\n\tnearestPoint(point: VecLike): Vec {\n\t\tconst {\n\t\t\t_center,\n\t\t\t_measure: measure,\n\t\t\t_radius: radius,\n\t\t\t_angleEnd: angleEnd,\n\t\t\t_angleStart: angleStart,\n\t\t\t_start: A,\n\t\t\t_end: B,\n\t\t} = this\n\t\tconst t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))\n\t\tif (t <= 0) return A\n\t\tif (t >= 1) return B\n\n\t\t// Inlined: Vec.Sub(point, _center).uni().mul(radius).add(_center)\n\t\t// When t is in (0,1), the nearest point is the radial projection of point onto the arc.\n\t\t// Previously this also checked min-distance against A and B, but that's unnecessary when\n\t\t// t is already in range \u2014 the radial projection is always closer.\n\t\tconst dx = point.x - _center.x\n\t\tconst dy = point.y - _center.y\n\t\tconst len = Math.sqrt(dx * dx + dy * dy)\n\t\tif (len === 0) return A\n\t\tconst scale = radius / len\n\t\treturn new Vec(_center.x + dx * scale, _center.y + dy * scale)\n\t}\n\n\thitTestLineSegment(A: VecLike, B: VecLike): boolean {\n\t\tconst {\n\t\t\t_center,\n\t\t\t_radius: radius,\n\t\t\t_measure: measure,\n\t\t\t_angleStart: angleStart,\n\t\t\t_angleEnd: angleEnd,\n\t\t} = this\n\t\tconst intersection = intersectLineSegmentCircle(A, B, _center, radius)\n\t\tif (intersection === null) return false\n\n\t\treturn intersection.some((p) => {\n\t\t\tconst result = getPointInArcT(measure, angleStart, angleEnd, _center.angle(p))\n\t\t\treturn result >= 0 && result <= 1\n\t\t})\n\t}\n\n\tgetVertices(): Vec[] {\n\t\tconst { _center, _measure: measure, length, _radius: radius, _angleStart: angleStart } = this\n\t\tconst vertices: Vec[] = []\n\t\tfor (let i = 0, n = getVerticesCountForArcLength(Math.abs(length)); i < n + 1; i++) {\n\t\t\tconst t = (i / n) * measure\n\t\t\tconst angle = angleStart + t\n\t\t\tvertices.push(getPointOnCircle(_center, radius, angle))\n\t\t}\n\t\treturn vertices\n\t}\n\n\tgetSvgPathData(first = true) {\n\t\tconst {\n\t\t\t_start: start,\n\t\t\t_end: end,\n\t\t\t_radius: radius,\n\t\t\t_largeArcFlag: largeArcFlag,\n\t\t\t_sweepFlag: sweepFlag,\n\t\t} = this\n\t\treturn `${first ? `M${start.toFixed()}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.toFixed()}`\n\t}\n\n\toverride getLength() {\n\t\treturn Math.abs(this._measure * this._radius)\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { intersectLineSegmentCircle } from '../intersect'\nimport { getArcMeasure, getPointInArcT, getPointOnCircle } from '../utils'\nimport { Vec, VecLike } from '../Vec'\nimport { getVerticesCountForArcLength } from './geometry-constants'\nimport { Geometry2d, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Arc2d extends Geometry2d {\n\tprivate _center: Vec\n\tprivate _radius: number\n\tprivate _start: Vec\n\tprivate _end: Vec\n\tprivate _largeArcFlag: number\n\tprivate _sweepFlag: number\n\tprivate _measure: number\n\tprivate _angleStart: number\n\tprivate _angleEnd: number\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {\n\t\t\tcenter: Vec\n\t\t\tstart: Vec\n\t\t\tend: Vec\n\t\t\tsweepFlag: number\n\t\t\tlargeArcFlag: number\n\t\t}\n\t) {\n\t\tsuper({ ...config, isFilled: false, isClosed: false })\n\t\tconst { center, sweepFlag, largeArcFlag, start, end } = config\n\t\tif (start.equals(end)) throw Error(`Arc must have different start and end points.`)\n\n\t\t// ensure that the start and end are clockwise\n\t\tthis._angleStart = Vec.Angle(center, start)\n\t\tthis._angleEnd = Vec.Angle(center, end)\n\t\tthis._radius = Vec.Dist(center, start)\n\t\tthis._measure = getArcMeasure(this._angleStart, this._angleEnd, sweepFlag, largeArcFlag)\n\n\t\tthis._start = start\n\t\tthis._end = end\n\n\t\tthis._sweepFlag = sweepFlag\n\t\tthis._largeArcFlag = largeArcFlag\n\t\tthis._center = center\n\t}\n\n\tnearestPoint(point: VecLike): Vec {\n\t\tconst {\n\t\t\t_center,\n\t\t\t_measure: measure,\n\t\t\t_radius: radius,\n\t\t\t_angleEnd: angleEnd,\n\t\t\t_angleStart: angleStart,\n\t\t\t_start: A,\n\t\t\t_end: B,\n\t\t} = this\n\t\tconst t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))\n\t\tif (t <= 0) return A\n\t\tif (t >= 1) return B\n\n\t\t// Inlined: Vec.Sub(point, _center).uni().mul(radius).add(_center)\n\t\t// When t is in (0,1), the nearest point is the radial projection of point onto the arc.\n\t\t// Previously this also checked min-distance against A and B, but that's unnecessary when\n\t\t// t is already in range \u2014 the radial projection is always closer.\n\t\tconst dx = point.x - _center.x\n\t\tconst dy = point.y - _center.y\n\t\tconst len = Math.sqrt(dx * dx + dy * dy)\n\t\tif (len === 0) return A\n\t\tconst scale = radius / len\n\t\treturn new Vec(_center.x + dx * scale, _center.y + dy * scale)\n\t}\n\n\thitTestLineSegment(A: VecLike, B: VecLike): boolean {\n\t\tconst {\n\t\t\t_center,\n\t\t\t_radius: radius,\n\t\t\t_measure: measure,\n\t\t\t_angleStart: angleStart,\n\t\t\t_angleEnd: angleEnd,\n\t\t} = this\n\t\tconst intersection = intersectLineSegmentCircle(A, B, _center, radius)\n\t\tif (intersection === null) return false\n\n\t\treturn intersection.some((p) => {\n\t\t\tconst result = getPointInArcT(measure, angleStart, angleEnd, _center.angle(p))\n\t\t\treturn result >= 0 && result <= 1\n\t\t})\n\t}\n\n\tgetVertices(): Vec[] {\n\t\tconst { _center, _measure: measure, length, _radius: radius, _angleStart: angleStart } = this\n\t\tconst vertices: Vec[] = []\n\t\tfor (let i = 0, n = getVerticesCountForArcLength(Math.abs(length)); i < n + 1; i++) {\n\t\t\tconst t = (i / n) * measure\n\t\t\tconst angle = angleStart + t\n\t\t\tvertices.push(getPointOnCircle(_center, radius, angle))\n\t\t}\n\t\treturn vertices\n\t}\n\n\tgetSvgPathData(first = true) {\n\t\tconst {\n\t\t\t_start: start,\n\t\t\t_end: end,\n\t\t\t_radius: radius,\n\t\t\t_largeArcFlag: largeArcFlag,\n\t\t\t_sweepFlag: sweepFlag,\n\t\t} = this\n\t\treturn `${first ? `M${start}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end}`\n\t}\n\n\toverride getLength() {\n\t\treturn Math.abs(this._measure * this._radius)\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,kCAAkC;AAC3C,SAAS,eAAe,gBAAgB,wBAAwB;AAChE,SAAS,WAAoB;AAC7B,SAAS,oCAAoC;AAC7C,SAAS,kBAAqC;AAGvC,MAAM,cAAc,WAAW;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACC,QAOC;AACD,UAAM,EAAE,GAAG,QAAQ,UAAU,OAAO,UAAU,MAAM,CAAC;AACrD,UAAM,EAAE,QAAQ,WAAW,cAAc,OAAO,IAAI,IAAI;AACxD,QAAI,MAAM,OAAO,GAAG,EAAG,OAAM,MAAM,+CAA+C;AAGlF,SAAK,cAAc,IAAI,MAAM,QAAQ,KAAK;AAC1C,SAAK,YAAY,IAAI,MAAM,QAAQ,GAAG;AACtC,SAAK,UAAU,IAAI,KAAK,QAAQ,KAAK;AACrC,SAAK,WAAW,cAAc,KAAK,aAAa,KAAK,WAAW,WAAW,YAAY;AAEvF,SAAK,SAAS;AACd,SAAK,OAAO;AAEZ,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,UAAU;AAAA,EAChB;AAAA,EAEA,aAAa,OAAqB;AACjC,UAAM;AAAA,MACL;AAAA,MACA,UAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAW;AAAA,MACX,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,MAAM;AAAA,IACP,IAAI;AACJ,UAAM,IAAI,eAAe,SAAS,YAAY,UAAU,QAAQ,MAAM,KAAK,CAAC;AAC5E,QAAI,KAAK,EAAG,QAAO;AACnB,QAAI,KAAK,EAAG,QAAO;AAMnB,UAAM,KAAK,MAAM,IAAI,QAAQ;AAC7B,UAAM,KAAK,MAAM,IAAI,QAAQ;AAC7B,UAAM,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AACvC,QAAI,QAAQ,EAAG,QAAO;AACtB,UAAM,QAAQ,SAAS;AACvB,WAAO,IAAI,IAAI,QAAQ,IAAI,KAAK,OAAO,QAAQ,IAAI,KAAK,KAAK;AAAA,EAC9D;AAAA,EAEA,mBAAmB,GAAY,GAAqB;AACnD,UAAM;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA,MACb,WAAW;AAAA,IACZ,IAAI;AACJ,UAAM,eAAe,2BAA2B,GAAG,GAAG,SAAS,MAAM;AACrE,QAAI,iBAAiB,KAAM,QAAO;AAElC,WAAO,aAAa,KAAK,CAAC,MAAM;AAC/B,YAAM,SAAS,eAAe,SAAS,YAAY,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC7E,aAAO,UAAU,KAAK,UAAU;AAAA,IACjC,CAAC;AAAA,EACF;AAAA,EAEA,cAAqB;AACpB,UAAM,EAAE,SAAS,UAAU,SAAS,QAAQ,SAAS,QAAQ,aAAa,WAAW,IAAI;AACzF,UAAM,WAAkB,CAAC;AACzB,aAAS,IAAI,GAAG,IAAI,6BAA6B,KAAK,IAAI,MAAM,CAAC,GAAG,IAAI,IAAI,GAAG,KAAK;AACnF,YAAM,IAAK,IAAI,IAAK;AACpB,YAAM,QAAQ,aAAa;AAC3B,eAAS,KAAK,iBAAiB,SAAS,QAAQ,KAAK,CAAC;AAAA,IACvD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,eAAe,QAAQ,MAAM;AAC5B,UAAM;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,eAAe;AAAA,MACf,YAAY;AAAA,IACb,IAAI;AACJ,WAAO,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE,KAAK,MAAM,IAAI,MAAM,MAAM,YAAY,IAAI,SAAS,IAAI,GAAG;AAAA,EAC9F;AAAA,EAES,YAAY;AACpB,WAAO,KAAK,IAAI,KAAK,WAAW,KAAK,OAAO;AAAA,EAC7C;AACD;",
6
6
  "names": []
7
7
  }
@@ -56,7 +56,7 @@ class CubicBezier2d extends Polyline2d {
56
56
  }
57
57
  getSvgPathData(first = true) {
58
58
  const { _a: a, _b: b, _c: c, _d: d } = this;
59
- return `${first ? `M ${a.toFixed()} ` : ``} C${b.toFixed()} ${c.toFixed()} ${d.toFixed()}`;
59
+ return `${first ? `M ${a} ` : ``} C${b} ${c} ${d}`;
60
60
  }
61
61
  static GetAtT(segment, t) {
62
62
  const { _a: a, _b: b, _c: c, _d: d } = segment;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/CubicBezier2d.ts"],
4
- "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2dFilters, Geometry2dOptions } from './Geometry2d'\nimport { Polyline2d } from './Polyline2d'\n\n/** @public */\nexport class CubicBezier2d extends Polyline2d {\n\tprivate _a: Vec\n\tprivate _b: Vec\n\tprivate _c: Vec\n\tprivate _d: Vec\n\tprivate _resolution: number\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {\n\t\t\tstart: Vec\n\t\t\tcp1: Vec\n\t\t\tcp2: Vec\n\t\t\tend: Vec\n\t\t\tresolution?: number\n\t\t}\n\t) {\n\t\tconst { start: a, cp1: b, cp2: c, end: d } = config\n\t\tsuper({ ...config, points: [a, d] })\n\n\t\tthis._a = a\n\t\tthis._b = b\n\t\tthis._c = c\n\t\tthis._d = d\n\t\tthis._resolution = config.resolution ?? 10\n\t}\n\n\toverride getVertices() {\n\t\tconst vertices = [] as Vec[]\n\t\tconst { _a: a, _b: b, _c: c, _d: d } = this\n\t\t// we'll always use ten vertices for each bezier curve\n\t\tfor (let i = 0, n = this._resolution; i <= n; i++) {\n\t\t\tconst t = i / n\n\t\t\tvertices.push(\n\t\t\t\tnew Vec(\n\t\t\t\t\t(1 - t) * (1 - t) * (1 - t) * a.x +\n\t\t\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.x +\n\t\t\t\t\t\t3 * (1 - t) * (t * t) * c.x +\n\t\t\t\t\t\tt * t * t * d.x,\n\t\t\t\t\t(1 - t) * (1 - t) * (1 - t) * a.y +\n\t\t\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.y +\n\t\t\t\t\t\t3 * (1 - t) * (t * t) * c.y +\n\t\t\t\t\t\tt * t * t * d.y\n\t\t\t\t)\n\t\t\t)\n\t\t}\n\t\treturn vertices\n\t}\n\n\tnearestPoint(A: VecLike): Vec {\n\t\tlet nearest: Vec | undefined\n\t\tlet dist = Infinity\n\t\tlet d: number\n\t\tlet p: Vec\n\t\tfor (const edge of this.segments) {\n\t\t\tp = edge.nearestPoint(A)\n\t\t\td = Vec.Dist2(p, A)\n\t\t\tif (d < dist) {\n\t\t\t\tnearest = p\n\t\t\t\tdist = d\n\t\t\t}\n\t\t}\n\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, _hitInside = false): number {\n\t\tconst { segments } = this\n\t\tlet minDist = Infinity\n\t\tfor (let i = 0; i < segments.length; i++) {\n\t\t\tconst d = segments[i].distanceToPoint(point)\n\t\t\tif (d < minDist) minDist = d\n\t\t}\n\t\treturn minDist\n\t}\n\n\tgetSvgPathData(first = true) {\n\t\tconst { _a: a, _b: b, _c: c, _d: d } = this\n\t\treturn `${first ? `M ${a.toFixed()} ` : ``} C${b.toFixed()} ${c.toFixed()} ${d.toFixed()}`\n\t}\n\n\tstatic GetAtT(segment: CubicBezier2d, t: number) {\n\t\tconst { _a: a, _b: b, _c: c, _d: d } = segment\n\t\treturn new Vec(\n\t\t\t(1 - t) * (1 - t) * (1 - t) * a.x +\n\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.x +\n\t\t\t\t3 * (1 - t) * (t * t) * c.x +\n\t\t\t\tt * t * t * d.x,\n\t\t\t(1 - t) * (1 - t) * (1 - t) * a.y +\n\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.y +\n\t\t\t\t3 * (1 - t) * (t * t) * c.y +\n\t\t\t\tt * t * t * d.y\n\t\t)\n\t}\n\n\toverride getLength(_filters?: Geometry2dFilters, precision = 32) {\n\t\tlet n1: Vec,\n\t\t\tp1 = this._a,\n\t\t\tlength = 0\n\t\tfor (let i = 1; i <= precision; i++) {\n\t\t\tn1 = CubicBezier2d.GetAtT(this, i / precision)\n\t\t\tlength += Vec.Dist(p1, n1)\n\t\t\tp1 = n1\n\t\t}\n\t\treturn length\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2dFilters, Geometry2dOptions } from './Geometry2d'\nimport { Polyline2d } from './Polyline2d'\n\n/** @public */\nexport class CubicBezier2d extends Polyline2d {\n\tprivate _a: Vec\n\tprivate _b: Vec\n\tprivate _c: Vec\n\tprivate _d: Vec\n\tprivate _resolution: number\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {\n\t\t\tstart: Vec\n\t\t\tcp1: Vec\n\t\t\tcp2: Vec\n\t\t\tend: Vec\n\t\t\tresolution?: number\n\t\t}\n\t) {\n\t\tconst { start: a, cp1: b, cp2: c, end: d } = config\n\t\tsuper({ ...config, points: [a, d] })\n\n\t\tthis._a = a\n\t\tthis._b = b\n\t\tthis._c = c\n\t\tthis._d = d\n\t\tthis._resolution = config.resolution ?? 10\n\t}\n\n\toverride getVertices() {\n\t\tconst vertices = [] as Vec[]\n\t\tconst { _a: a, _b: b, _c: c, _d: d } = this\n\t\t// we'll always use ten vertices for each bezier curve\n\t\tfor (let i = 0, n = this._resolution; i <= n; i++) {\n\t\t\tconst t = i / n\n\t\t\tvertices.push(\n\t\t\t\tnew Vec(\n\t\t\t\t\t(1 - t) * (1 - t) * (1 - t) * a.x +\n\t\t\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.x +\n\t\t\t\t\t\t3 * (1 - t) * (t * t) * c.x +\n\t\t\t\t\t\tt * t * t * d.x,\n\t\t\t\t\t(1 - t) * (1 - t) * (1 - t) * a.y +\n\t\t\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.y +\n\t\t\t\t\t\t3 * (1 - t) * (t * t) * c.y +\n\t\t\t\t\t\tt * t * t * d.y\n\t\t\t\t)\n\t\t\t)\n\t\t}\n\t\treturn vertices\n\t}\n\n\tnearestPoint(A: VecLike): Vec {\n\t\tlet nearest: Vec | undefined\n\t\tlet dist = Infinity\n\t\tlet d: number\n\t\tlet p: Vec\n\t\tfor (const edge of this.segments) {\n\t\t\tp = edge.nearestPoint(A)\n\t\t\td = Vec.Dist2(p, A)\n\t\t\tif (d < dist) {\n\t\t\t\tnearest = p\n\t\t\t\tdist = d\n\t\t\t}\n\t\t}\n\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, _hitInside = false): number {\n\t\tconst { segments } = this\n\t\tlet minDist = Infinity\n\t\tfor (let i = 0; i < segments.length; i++) {\n\t\t\tconst d = segments[i].distanceToPoint(point)\n\t\t\tif (d < minDist) minDist = d\n\t\t}\n\t\treturn minDist\n\t}\n\n\tgetSvgPathData(first = true) {\n\t\tconst { _a: a, _b: b, _c: c, _d: d } = this\n\t\treturn `${first ? `M ${a} ` : ``} C${b} ${c} ${d}`\n\t}\n\n\tstatic GetAtT(segment: CubicBezier2d, t: number) {\n\t\tconst { _a: a, _b: b, _c: c, _d: d } = segment\n\t\treturn new Vec(\n\t\t\t(1 - t) * (1 - t) * (1 - t) * a.x +\n\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.x +\n\t\t\t\t3 * (1 - t) * (t * t) * c.x +\n\t\t\t\tt * t * t * d.x,\n\t\t\t(1 - t) * (1 - t) * (1 - t) * a.y +\n\t\t\t\t3 * ((1 - t) * (1 - t)) * t * b.y +\n\t\t\t\t3 * (1 - t) * (t * t) * c.y +\n\t\t\t\tt * t * t * d.y\n\t\t)\n\t}\n\n\toverride getLength(_filters?: Geometry2dFilters, precision = 32) {\n\t\tlet n1: Vec,\n\t\t\tp1 = this._a,\n\t\t\tlength = 0\n\t\tfor (let i = 1; i <= precision; i++) {\n\t\t\tn1 = CubicBezier2d.GetAtT(this, i / precision)\n\t\t\tlength += Vec.Dist(p1, n1)\n\t\t\tp1 = n1\n\t\t}\n\t\treturn length\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -50,7 +50,7 @@ class Edge2d extends Geometry2d {
50
50
  }
51
51
  getSvgPathData(first = true) {
52
52
  const { _start: start, _end: end } = this;
53
- return `${first ? `M${start.toFixed()}` : ``} L${end.toFixed()}`;
53
+ return `${first ? `M${start}` : ``} L${end}`;
54
54
  }
55
55
  }
56
56
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/Edge2d.ts"],
4
- "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2d } from './Geometry2d'\n\n/** @public */\nexport class Edge2d extends Geometry2d {\n\tprivate _start: Vec\n\tprivate _end: Vec\n\tprivate _dx: number\n\tprivate _dy: number\n\tprivate _len2: number\n\n\tconstructor(config: { start: Vec; end: Vec }) {\n\t\tsuper({ ...config, isClosed: false, isFilled: false })\n\t\tconst { start, end } = config\n\n\t\tthis._start = start\n\t\tthis._end = end\n\n\t\t// Precomputed segment delta and squared length (replaces Vec.Sub(end, start) and Vec.Len2)\n\t\tthis._dx = end.x - start.x\n\t\tthis._dy = end.y - start.y\n\t\tthis._len2 = this._dx * this._dx + this._dy * this._dy\n\t}\n\n\toverride getLength() {\n\t\treturn Math.sqrt(this._len2)\n\t}\n\n\toverride getVertices(): Vec[] {\n\t\treturn [this._start, this._end]\n\t}\n\n\toverride nearestPoint(point: VecLike): Vec {\n\t\t// Inlined: Vec.NearestPointOnLineSegment(start, end, point)\n\t\t// Uses precomputed dx/dy/len2 and parametric t-clamping instead of Vec allocations.\n\t\tconst { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this\n\t\tif (len2 === 0) return start\n\n\t\tconst t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2\n\t\tif (t <= 0) return start\n\t\tif (t >= 1) return end\n\t\treturn new Vec(start.x + dx * t, start.y + dy * t)\n\t}\n\n\toverride distanceToPoint(point: VecLike, _hitInside = false): number {\n\t\t// Inlined: Vec.Dist(point, this.nearestPoint(point))\n\t\t// Finds nearest point via parametric t-projection then returns scalar distance directly,\n\t\t// avoiding the Vec allocation that nearestPoint would create.\n\t\tconst { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this\n\t\tif (len2 === 0) return Vec.Dist(point, start)\n\n\t\tconst t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2\n\t\tlet nx: number, ny: number\n\t\tif (t <= 0) {\n\t\t\tnx = start.x\n\t\t\tny = start.y\n\t\t} else if (t >= 1) {\n\t\t\tnx = end.x\n\t\t\tny = end.y\n\t\t} else {\n\t\t\tnx = start.x + dx * t\n\t\t\tny = start.y + dy * t\n\t\t}\n\t\tconst ex = point.x - nx\n\t\tconst ey = point.y - ny\n\t\treturn Math.sqrt(ex * ex + ey * ey)\n\t}\n\n\tgetSvgPathData(first = true) {\n\t\tconst { _start: start, _end: end } = this\n\t\treturn `${first ? `M${start.toFixed()}` : ``} L${end.toFixed()}`\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,WAAoB;AAC7B,SAAS,kBAAkB;AAGpB,MAAM,eAAe,WAAW;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAkC;AAC7C,UAAM,EAAE,GAAG,QAAQ,UAAU,OAAO,UAAU,MAAM,CAAC;AACrD,UAAM,EAAE,OAAO,IAAI,IAAI;AAEvB,SAAK,SAAS;AACd,SAAK,OAAO;AAGZ,SAAK,MAAM,IAAI,IAAI,MAAM;AACzB,SAAK,MAAM,IAAI,IAAI,MAAM;AACzB,SAAK,QAAQ,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK;AAAA,EACpD;AAAA,EAES,YAAY;AACpB,WAAO,KAAK,KAAK,KAAK,KAAK;AAAA,EAC5B;AAAA,EAES,cAAqB;AAC7B,WAAO,CAAC,KAAK,QAAQ,KAAK,IAAI;AAAA,EAC/B;AAAA,EAES,aAAa,OAAqB;AAG1C,UAAM,EAAE,QAAQ,OAAO,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;AACpE,QAAI,SAAS,EAAG,QAAO;AAEvB,UAAM,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM;AAClE,QAAI,KAAK,EAAG,QAAO;AACnB,QAAI,KAAK,EAAG,QAAO;AACnB,WAAO,IAAI,IAAI,MAAM,IAAI,KAAK,GAAG,MAAM,IAAI,KAAK,CAAC;AAAA,EAClD;AAAA,EAES,gBAAgB,OAAgB,aAAa,OAAe;AAIpE,UAAM,EAAE,QAAQ,OAAO,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;AACpE,QAAI,SAAS,EAAG,QAAO,IAAI,KAAK,OAAO,KAAK;AAE5C,UAAM,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM;AAClE,QAAI,IAAY;AAChB,QAAI,KAAK,GAAG;AACX,WAAK,MAAM;AACX,WAAK,MAAM;AAAA,IACZ,WAAW,KAAK,GAAG;AAClB,WAAK,IAAI;AACT,WAAK,IAAI;AAAA,IACV,OAAO;AACN,WAAK,MAAM,IAAI,KAAK;AACpB,WAAK,MAAM,IAAI,KAAK;AAAA,IACrB;AACA,UAAM,KAAK,MAAM,IAAI;AACrB,UAAM,KAAK,MAAM,IAAI;AACrB,WAAO,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AAAA,EACnC;AAAA,EAEA,eAAe,QAAQ,MAAM;AAC5B,UAAM,EAAE,QAAQ,OAAO,MAAM,IAAI,IAAI;AACrC,WAAO,GAAG,QAAQ,IAAI,MAAM,QAAQ,CAAC,KAAK,EAAE,KAAK,IAAI,QAAQ,CAAC;AAAA,EAC/D;AACD;",
4
+ "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2d } from './Geometry2d'\n\n/** @public */\nexport class Edge2d extends Geometry2d {\n\tprivate _start: Vec\n\tprivate _end: Vec\n\tprivate _dx: number\n\tprivate _dy: number\n\tprivate _len2: number\n\n\tconstructor(config: { start: Vec; end: Vec }) {\n\t\tsuper({ ...config, isClosed: false, isFilled: false })\n\t\tconst { start, end } = config\n\n\t\tthis._start = start\n\t\tthis._end = end\n\n\t\t// Precomputed segment delta and squared length (replaces Vec.Sub(end, start) and Vec.Len2)\n\t\tthis._dx = end.x - start.x\n\t\tthis._dy = end.y - start.y\n\t\tthis._len2 = this._dx * this._dx + this._dy * this._dy\n\t}\n\n\toverride getLength() {\n\t\treturn Math.sqrt(this._len2)\n\t}\n\n\toverride getVertices(): Vec[] {\n\t\treturn [this._start, this._end]\n\t}\n\n\toverride nearestPoint(point: VecLike): Vec {\n\t\t// Inlined: Vec.NearestPointOnLineSegment(start, end, point)\n\t\t// Uses precomputed dx/dy/len2 and parametric t-clamping instead of Vec allocations.\n\t\tconst { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this\n\t\tif (len2 === 0) return start\n\n\t\tconst t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2\n\t\tif (t <= 0) return start\n\t\tif (t >= 1) return end\n\t\treturn new Vec(start.x + dx * t, start.y + dy * t)\n\t}\n\n\toverride distanceToPoint(point: VecLike, _hitInside = false): number {\n\t\t// Inlined: Vec.Dist(point, this.nearestPoint(point))\n\t\t// Finds nearest point via parametric t-projection then returns scalar distance directly,\n\t\t// avoiding the Vec allocation that nearestPoint would create.\n\t\tconst { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this\n\t\tif (len2 === 0) return Vec.Dist(point, start)\n\n\t\tconst t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2\n\t\tlet nx: number, ny: number\n\t\tif (t <= 0) {\n\t\t\tnx = start.x\n\t\t\tny = start.y\n\t\t} else if (t >= 1) {\n\t\t\tnx = end.x\n\t\t\tny = end.y\n\t\t} else {\n\t\t\tnx = start.x + dx * t\n\t\t\tny = start.y + dy * t\n\t\t}\n\t\tconst ex = point.x - nx\n\t\tconst ey = point.y - ny\n\t\treturn Math.sqrt(ex * ex + ey * ey)\n\t}\n\n\tgetSvgPathData(first = true) {\n\t\tconst { _start: start, _end: end } = this\n\t\treturn `${first ? `M${start}` : ``} L${end}`\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,WAAoB;AAC7B,SAAS,kBAAkB;AAGpB,MAAM,eAAe,WAAW;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAkC;AAC7C,UAAM,EAAE,GAAG,QAAQ,UAAU,OAAO,UAAU,MAAM,CAAC;AACrD,UAAM,EAAE,OAAO,IAAI,IAAI;AAEvB,SAAK,SAAS;AACd,SAAK,OAAO;AAGZ,SAAK,MAAM,IAAI,IAAI,MAAM;AACzB,SAAK,MAAM,IAAI,IAAI,MAAM;AACzB,SAAK,QAAQ,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK;AAAA,EACpD;AAAA,EAES,YAAY;AACpB,WAAO,KAAK,KAAK,KAAK,KAAK;AAAA,EAC5B;AAAA,EAES,cAAqB;AAC7B,WAAO,CAAC,KAAK,QAAQ,KAAK,IAAI;AAAA,EAC/B;AAAA,EAES,aAAa,OAAqB;AAG1C,UAAM,EAAE,QAAQ,OAAO,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;AACpE,QAAI,SAAS,EAAG,QAAO;AAEvB,UAAM,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM;AAClE,QAAI,KAAK,EAAG,QAAO;AACnB,QAAI,KAAK,EAAG,QAAO;AACnB,WAAO,IAAI,IAAI,MAAM,IAAI,KAAK,GAAG,MAAM,IAAI,KAAK,CAAC;AAAA,EAClD;AAAA,EAES,gBAAgB,OAAgB,aAAa,OAAe;AAIpE,UAAM,EAAE,QAAQ,OAAO,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;AACpE,QAAI,SAAS,EAAG,QAAO,IAAI,KAAK,OAAO,KAAK;AAE5C,UAAM,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM;AAClE,QAAI,IAAY;AAChB,QAAI,KAAK,GAAG;AACX,WAAK,MAAM;AACX,WAAK,MAAM;AAAA,IACZ,WAAW,KAAK,GAAG;AAClB,WAAK,IAAI;AACT,WAAK,IAAI;AAAA,IACV,OAAO;AACN,WAAK,MAAM,IAAI,KAAK;AACpB,WAAK,MAAM,IAAI,KAAK;AAAA,IACrB;AACA,UAAM,KAAK,MAAM,IAAI;AACrB,UAAM,KAAK,MAAM,IAAI;AACrB,WAAO,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AAAA,EACnC;AAAA,EAEA,eAAe,QAAQ,MAAM;AAC5B,UAAM,EAAE,QAAQ,OAAO,MAAM,IAAI,IAAI;AACrC,WAAO,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE,KAAK,GAAG;AAAA,EAC3C;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,10 @@
1
1
  import { assert, invLerp, lerp } from "@tldraw/utils";
2
2
  import { Box } from "../Box.mjs";
3
3
  import { Vec } from "../Vec.mjs";
4
- import { Geometry2d, Geometry2dFilters } from "./Geometry2d.mjs";
4
+ import {
5
+ Geometry2d,
6
+ Geometry2dFilters
7
+ } from "./Geometry2d.mjs";
5
8
  class Group2d extends Geometry2d {
6
9
  children = [];
7
10
  ignoredChildren = [];
@@ -181,12 +184,15 @@ class Group2d extends Geometry2d {
181
184
  );
182
185
  return childTLength / totalLength;
183
186
  }
184
- transform(transform) {
187
+ transform(transform, opts) {
185
188
  return new Group2d({
186
- children: this.children.map((c) => c.transform(transform)),
187
- isLabel: this.isLabel,
188
- debugColor: this.debugColor,
189
- ignore: this.ignore
189
+ children: [...this.children, ...this.ignoredChildren].map((c) => c.transform(transform)),
190
+ isLabel: opts?.isLabel ?? this.isLabel,
191
+ isEmptyLabel: this.isEmptyLabel,
192
+ isInternal: opts?.isInternal ?? this.isInternal,
193
+ debugColor: opts?.debugColor ?? this.debugColor,
194
+ ignore: opts?.ignore ?? this.ignore,
195
+ excludeFromShapeBounds: this.excludeFromShapeBounds
190
196
  });
191
197
  }
192
198
  getArea() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/Group2d.ts"],
4
- "sourcesContent": ["import { assert, invLerp, lerp } from '@tldraw/utils'\nimport { Box } from '../Box'\nimport { Mat } from '../Mat'\nimport { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dFilters, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Group2d extends Geometry2d {\n\tchildren: Geometry2d[] = []\n\tignoredChildren: Geometry2d[] = []\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {\n\t\t\tchildren: Geometry2d[]\n\t\t}\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: false })\n\n\t\tconst addChildren = (children: Geometry2d[]) => {\n\t\t\tfor (const child of children) {\n\t\t\t\tif (child instanceof Group2d) {\n\t\t\t\t\taddChildren(child.children)\n\t\t\t\t} else if (child.ignore) {\n\t\t\t\t\tthis.ignoredChildren.push(child)\n\t\t\t\t} else {\n\t\t\t\t\tthis.children.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\taddChildren(config.children)\n\n\t\tif (this.children.length === 0) throw Error('Group2d must have at least one child')\n\t}\n\n\toverride getVertices(filters: Geometry2dFilters): Vec[] {\n\t\tif (this.isExcludedByFilter(filters)) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childVertices = child.getVertices(filters)\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {\n\t\tlet dist = Infinity\n\t\tlet nearest: Vec | undefined\n\n\t\tconst { children } = this\n\n\t\tif (children.length === 0) {\n\t\t\tthrow Error('no children')\n\t\t}\n\n\t\tlet p: Vec\n\t\tlet d: number\n\t\tfor (const child of children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tp = child.nearestPoint(point, filters)\n\t\t\td = Vec.Dist2(p, point)\n\t\t\tif (d < dist) {\n\t\t\t\tdist = d\n\t\t\t\tnearest = p\n\t\t\t}\n\t\t}\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {\n\t\tlet smallestDistance = Infinity\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst distance = child.distanceToPoint(point, hitInside, filters)\n\t\t\tif (distance < smallestDistance) {\n\t\t\t\tsmallestDistance = distance\n\t\t\t}\n\t\t}\n\t\treturn smallestDistance\n\t}\n\n\toverride hitTestPoint(\n\t\tpoint: VecLike,\n\t\tmargin: number,\n\t\thitInside: boolean,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestPoint(point, margin, hitInside)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride hitTestLineSegment(\n\t\tA: VecLike,\n\t\tB: VecLike,\n\t\tzoom: number,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestLineSegment(A, B, zoom)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectLineSegment(A, B, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectCircle(center, radius, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride getBoundsVertices(): Vec[] {\n\t\tif (this.excludeFromShapeBounds) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tconst childVertices = child.getBoundsVertices()\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolygon(polygon, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolyline(polyline, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tconst distanceToTravel = t * totalLength\n\t\tlet distanceTraveled = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.length\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\t\t\tif (newDistanceTraveled >= distanceToTravel) {\n\t\t\t\treturn child.interpolateAlongEdge(\n\t\t\t\t\tinvLerp(distanceTraveled, newDistanceTraveled, distanceToTravel),\n\t\t\t\t\tfilters\n\t\t\t\t)\n\t\t\t}\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\treturn this.children[this.children.length - 1].interpolateAlongEdge(1, filters)\n\t}\n\n\toverride uninterpolateAlongEdge(point: VecLike, filters?: Geometry2dFilters): number {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tlet closestChild = null\n\t\tlet closestDistance = Infinity\n\t\tlet distanceTraveled = 0\n\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.getLength(filters)\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\n\t\t\tconst distance = child.distanceToPoint(point, false, filters)\n\t\t\tif (distance < closestDistance) {\n\t\t\t\tclosestDistance = distance\n\t\t\t\tclosestChild = {\n\t\t\t\t\tstartLength: distanceTraveled,\n\t\t\t\t\tendLength: newDistanceTraveled,\n\t\t\t\t\tchild,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\tassert(closestChild)\n\n\t\tconst normalizedDistanceInChild = closestChild.child.uninterpolateAlongEdge(point, filters)\n\t\tconst childTLength = lerp(\n\t\t\tclosestChild.startLength,\n\t\t\tclosestChild.endLength,\n\t\t\tnormalizedDistanceInChild\n\t\t)\n\t\treturn childTLength / totalLength\n\t}\n\n\toverride transform(transform: Mat): Geometry2d {\n\t\treturn new Group2d({\n\t\t\tchildren: this.children.map((c) => c.transform(transform)),\n\t\t\tisLabel: this.isLabel,\n\t\t\tdebugColor: this.debugColor,\n\t\t\tignore: this.ignore,\n\t\t})\n\t}\n\n\tgetArea() {\n\t\t// todo: this is a temporary solution, assuming that the first child defines the group size; we would want to flatten the group and then find the area of the hull polygon\n\t\treturn this.children[0].area\n\t}\n\n\ttoSimpleSvgPath() {\n\t\tlet path = ''\n\t\tfor (const child of this.children) {\n\t\t\tpath += child.toSimpleSvgPath()\n\t\t}\n\n\t\tconst corners = Box.FromPoints(this.boundsVertices).corners\n\t\t// draw just a few pixels around each corner, e.g. an L shape for the bottom left\n\n\t\tfor (let i = 0, n = corners.length; i < n; i++) {\n\t\t\tconst corner = corners[i]\n\t\t\tconst prevCorner = corners[(i - 1 + n) % n]\n\t\t\tconst prevDist = corner.dist(prevCorner)\n\t\t\tconst nextCorner = corners[(i + 1) % n]\n\t\t\tconst nextDist = corner.dist(nextCorner)\n\n\t\t\tconst A = corner.clone().lrp(prevCorner, 4 / prevDist)\n\t\t\tconst B = corner\n\t\t\tconst C = corner.clone().lrp(nextCorner, 4 / nextDist)\n\n\t\t\tpath += `M${A.x},${A.y} L${B.x},${B.y} L${C.x},${C.y} `\n\t\t}\n\t\treturn path\n\t}\n\n\tgetLength(filters?: Geometry2dFilters): number {\n\t\tlet length = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tlength += child.length\n\t\t}\n\t\treturn length\n\t}\n\n\tgetSvgPathData(): string {\n\t\treturn this.children.map((c, i) => (c.isLabel ? '' : c.getSvgPathData(i === 0))).join(' ')\n\t}\n\n\toverlapsPolygon(polygon: VecLike[]): boolean {\n\t\treturn this.children.some((child) => child.overlapsPolygon(polygon))\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { assert, invLerp, lerp } from '@tldraw/utils'\nimport { Box } from '../Box'\nimport { MatModel } from '../Mat'\nimport { Vec, VecLike } from '../Vec'\nimport {\n\tGeometry2d,\n\tGeometry2dFilters,\n\tGeometry2dOptions,\n\tTransformedGeometry2dOptions,\n} from './Geometry2d'\n\n/** @public */\nexport class Group2d extends Geometry2d {\n\tchildren: Geometry2d[] = []\n\tignoredChildren: Geometry2d[] = []\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {\n\t\t\tchildren: Geometry2d[]\n\t\t}\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: false })\n\n\t\tconst addChildren = (children: Geometry2d[]) => {\n\t\t\tfor (const child of children) {\n\t\t\t\tif (child instanceof Group2d) {\n\t\t\t\t\taddChildren(child.children)\n\t\t\t\t} else if (child.ignore) {\n\t\t\t\t\tthis.ignoredChildren.push(child)\n\t\t\t\t} else {\n\t\t\t\t\tthis.children.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\taddChildren(config.children)\n\n\t\tif (this.children.length === 0) throw Error('Group2d must have at least one child')\n\t}\n\n\toverride getVertices(filters: Geometry2dFilters): Vec[] {\n\t\tif (this.isExcludedByFilter(filters)) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childVertices = child.getVertices(filters)\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {\n\t\tlet dist = Infinity\n\t\tlet nearest: Vec | undefined\n\n\t\tconst { children } = this\n\n\t\tif (children.length === 0) {\n\t\t\tthrow Error('no children')\n\t\t}\n\n\t\tlet p: Vec\n\t\tlet d: number\n\t\tfor (const child of children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tp = child.nearestPoint(point, filters)\n\t\t\td = Vec.Dist2(p, point)\n\t\t\tif (d < dist) {\n\t\t\t\tdist = d\n\t\t\t\tnearest = p\n\t\t\t}\n\t\t}\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {\n\t\tlet smallestDistance = Infinity\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst distance = child.distanceToPoint(point, hitInside, filters)\n\t\t\tif (distance < smallestDistance) {\n\t\t\t\tsmallestDistance = distance\n\t\t\t}\n\t\t}\n\t\treturn smallestDistance\n\t}\n\n\toverride hitTestPoint(\n\t\tpoint: VecLike,\n\t\tmargin: number,\n\t\thitInside: boolean,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestPoint(point, margin, hitInside)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride hitTestLineSegment(\n\t\tA: VecLike,\n\t\tB: VecLike,\n\t\tzoom: number,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestLineSegment(A, B, zoom)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectLineSegment(A, B, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectCircle(center, radius, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride getBoundsVertices(): Vec[] {\n\t\tif (this.excludeFromShapeBounds) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tconst childVertices = child.getBoundsVertices()\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolygon(polygon, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolyline(polyline, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tconst distanceToTravel = t * totalLength\n\t\tlet distanceTraveled = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.length\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\t\t\tif (newDistanceTraveled >= distanceToTravel) {\n\t\t\t\treturn child.interpolateAlongEdge(\n\t\t\t\t\tinvLerp(distanceTraveled, newDistanceTraveled, distanceToTravel),\n\t\t\t\t\tfilters\n\t\t\t\t)\n\t\t\t}\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\treturn this.children[this.children.length - 1].interpolateAlongEdge(1, filters)\n\t}\n\n\toverride uninterpolateAlongEdge(point: VecLike, filters?: Geometry2dFilters): number {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tlet closestChild = null\n\t\tlet closestDistance = Infinity\n\t\tlet distanceTraveled = 0\n\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.getLength(filters)\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\n\t\t\tconst distance = child.distanceToPoint(point, false, filters)\n\t\t\tif (distance < closestDistance) {\n\t\t\t\tclosestDistance = distance\n\t\t\t\tclosestChild = {\n\t\t\t\t\tstartLength: distanceTraveled,\n\t\t\t\t\tendLength: newDistanceTraveled,\n\t\t\t\t\tchild,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\tassert(closestChild)\n\n\t\tconst normalizedDistanceInChild = closestChild.child.uninterpolateAlongEdge(point, filters)\n\t\tconst childTLength = lerp(\n\t\t\tclosestChild.startLength,\n\t\t\tclosestChild.endLength,\n\t\t\tnormalizedDistanceInChild\n\t\t)\n\t\treturn childTLength / totalLength\n\t}\n\n\toverride transform(transform: MatModel, opts?: TransformedGeometry2dOptions): Geometry2d {\n\t\t// Each child's own transform preserves its `ignore` flag, so the constructor\n\t\t// re-partitions ignored children back into `ignoredChildren` instead of dropping\n\t\t// them (#10562).\n\t\treturn new Group2d({\n\t\t\tchildren: [...this.children, ...this.ignoredChildren].map((c) => c.transform(transform)),\n\t\t\tisLabel: opts?.isLabel ?? this.isLabel,\n\t\t\tisEmptyLabel: this.isEmptyLabel,\n\t\t\tisInternal: opts?.isInternal ?? this.isInternal,\n\t\t\tdebugColor: opts?.debugColor ?? this.debugColor,\n\t\t\tignore: opts?.ignore ?? this.ignore,\n\t\t\texcludeFromShapeBounds: this.excludeFromShapeBounds,\n\t\t})\n\t}\n\n\tgetArea() {\n\t\t// todo: this is a temporary solution, assuming that the first child defines the group size; we would want to flatten the group and then find the area of the hull polygon\n\t\treturn this.children[0].area\n\t}\n\n\ttoSimpleSvgPath() {\n\t\tlet path = ''\n\t\tfor (const child of this.children) {\n\t\t\tpath += child.toSimpleSvgPath()\n\t\t}\n\n\t\tconst corners = Box.FromPoints(this.boundsVertices).corners\n\t\t// draw just a few pixels around each corner, e.g. an L shape for the bottom left\n\n\t\tfor (let i = 0, n = corners.length; i < n; i++) {\n\t\t\tconst corner = corners[i]\n\t\t\tconst prevCorner = corners[(i - 1 + n) % n]\n\t\t\tconst prevDist = corner.dist(prevCorner)\n\t\t\tconst nextCorner = corners[(i + 1) % n]\n\t\t\tconst nextDist = corner.dist(nextCorner)\n\n\t\t\tconst A = corner.clone().lrp(prevCorner, 4 / prevDist)\n\t\t\tconst B = corner\n\t\t\tconst C = corner.clone().lrp(nextCorner, 4 / nextDist)\n\n\t\t\tpath += `M${A.x},${A.y} L${B.x},${B.y} L${C.x},${C.y} `\n\t\t}\n\t\treturn path\n\t}\n\n\tgetLength(filters?: Geometry2dFilters): number {\n\t\tlet length = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tlength += child.length\n\t\t}\n\t\treturn length\n\t}\n\n\tgetSvgPathData(): string {\n\t\treturn this.children.map((c, i) => (c.isLabel ? '' : c.getSvgPathData(i === 0))).join(' ')\n\t}\n\n\toverlapsPolygon(polygon: VecLike[]): boolean {\n\t\treturn this.children.some((child) => child.overlapsPolygon(polygon))\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,7 @@ class Point2d extends Geometry2d {
18
18
  }
19
19
  getSvgPathData() {
20
20
  const { _point: point } = this;
21
- return `M${point.toFixed()}`;
21
+ return `M${point}`;
22
22
  }
23
23
  }
24
24
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/primitives/geometry/Point2d.ts"],
4
- "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Point2d extends Geometry2d {\n\tprivate _point: Vec\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec }\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: true })\n\t\tconst { point } = config\n\n\t\tthis._point = point\n\t}\n\n\tgetVertices() {\n\t\treturn [this._point]\n\t}\n\n\tnearestPoint(): Vec {\n\t\treturn this._point\n\t}\n\n\thitTestLineSegment(A: VecLike, B: VecLike, margin: number): boolean {\n\t\treturn Vec.DistanceToLineSegment(A, B, this._point) < margin\n\t}\n\n\tgetSvgPathData() {\n\t\tconst { _point: point } = this\n\t\treturn `M${point.toFixed()}`\n\t}\n}\n"],
5
- "mappings": "AAAA,SAAS,WAAoB;AAC7B,SAAS,kBAAqC;AAGvC,MAAM,gBAAgB,WAAW;AAAA,EAC/B;AAAA,EAER,YACC,QACC;AACD,UAAM,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,CAAC;AACnD,UAAM,EAAE,MAAM,IAAI;AAElB,SAAK,SAAS;AAAA,EACf;AAAA,EAEA,cAAc;AACb,WAAO,CAAC,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,eAAoB;AACnB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,mBAAmB,GAAY,GAAY,QAAyB;AACnE,WAAO,IAAI,sBAAsB,GAAG,GAAG,KAAK,MAAM,IAAI;AAAA,EACvD;AAAA,EAEA,iBAAiB;AAChB,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,WAAO,IAAI,MAAM,QAAQ,CAAC;AAAA,EAC3B;AACD;",
4
+ "sourcesContent": ["import { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Point2d extends Geometry2d {\n\tprivate _point: Vec\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec }\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: true })\n\t\tconst { point } = config\n\n\t\tthis._point = point\n\t}\n\n\tgetVertices() {\n\t\treturn [this._point]\n\t}\n\n\tnearestPoint(): Vec {\n\t\treturn this._point\n\t}\n\n\thitTestLineSegment(A: VecLike, B: VecLike, margin: number): boolean {\n\t\treturn Vec.DistanceToLineSegment(A, B, this._point) < margin\n\t}\n\n\tgetSvgPathData() {\n\t\tconst { _point: point } = this\n\t\treturn `M${point}`\n\t}\n}\n"],
5
+ "mappings": "AAAA,SAAS,WAAoB;AAC7B,SAAS,kBAAqC;AAGvC,MAAM,gBAAgB,WAAW;AAAA,EAC/B;AAAA,EAER,YACC,QACC;AACD,UAAM,EAAE,GAAG,QAAQ,UAAU,MAAM,UAAU,KAAK,CAAC;AACnD,UAAM,EAAE,MAAM,IAAI;AAElB,SAAK,SAAS;AAAA,EACf;AAAA,EAEA,cAAc;AACb,WAAO,CAAC,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,eAAoB;AACnB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,mBAAmB,GAAY,GAAY,QAAyB;AACnE,WAAO,IAAI,sBAAsB,GAAG,GAAG,KAAK,MAAM,IAAI;AAAA,EACvD;AAAA,EAEA,iBAAiB;AAChB,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,WAAO,IAAI,KAAK;AAAA,EACjB;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "5.4.0";
1
+ const version = "5.5.0-canary.05a393565877";
2
2
  const publishDates = {
3
3
  major: "2026-05-06T16:28:18.473Z",
4
- minor: "2026-09-02T11:04:02.901Z",
5
- patch: "2026-09-02T11:04:02.901Z"
4
+ minor: "2026-09-04T14:41:37.519Z",
5
+ patch: "2026-09-04T14:41:37.519Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -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 = '5.4.0'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-02T11:04:02.901Z',\n\tpatch: '2026-09-02T11:04:02.901Z',\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 = '5.5.0-canary.05a393565877'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-04T14:41:37.519Z',\n\tpatch: '2026-09-04T14:41:37.519Z',\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": "tldraw infinite canvas SDK (editor).",
4
- "version": "5.4.0",
4
+ "version": "5.5.0-canary.05a393565877",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -55,12 +55,12 @@
55
55
  "@tiptap/core": "^3.12.1",
56
56
  "@tiptap/pm": "^3.12.1",
57
57
  "@tiptap/react": "^3.12.1",
58
- "@tldraw/state": "5.4.0",
59
- "@tldraw/state-react": "5.4.0",
60
- "@tldraw/store": "5.4.0",
61
- "@tldraw/tlschema": "5.4.0",
62
- "@tldraw/utils": "5.4.0",
63
- "@tldraw/validate": "5.4.0",
58
+ "@tldraw/state": "5.5.0-canary.05a393565877",
59
+ "@tldraw/state-react": "5.5.0-canary.05a393565877",
60
+ "@tldraw/store": "5.5.0-canary.05a393565877",
61
+ "@tldraw/tlschema": "5.5.0-canary.05a393565877",
62
+ "@tldraw/utils": "5.5.0-canary.05a393565877",
63
+ "@tldraw/validate": "5.5.0-canary.05a393565877",
64
64
  "classnames": "^2.5.1",
65
65
  "eventemitter3": "^4.0.7",
66
66
  "idb": "^7.1.1",
@@ -24,6 +24,14 @@ export const INTERNAL_POINTER_IDS = {
24
24
  CAMERA_MOVE: -10,
25
25
  } as const
26
26
 
27
+ /**
28
+ * Per-tick tuning constants (`cameraSlideFriction`, `edgeScrollSpeed`) assume a 60 Hz tick. Scaling
29
+ * motion by `elapsed / FRAME_MS_60HZ` keeps that feel on displays with other refresh rates.
30
+ *
31
+ * @internal
32
+ */
33
+ export const FRAME_MS_60HZ = 1000 / 60
34
+
27
35
  /** @public */
28
36
  export const SIDES = ['top', 'right', 'bottom', 'left'] as const
29
37