@yuzhouu/canvas-kit 0.1.2

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 (263) hide show
  1. package/README.md +145 -0
  2. package/_vendor/canvas-core/bindings/BindingUtil.ts +92 -0
  3. package/_vendor/canvas-core/bindings/BindingUtilRegistry.ts +22 -0
  4. package/_vendor/canvas-core/bindings/arrow/arrowBindingHelpers.ts +52 -0
  5. package/_vendor/canvas-core/contextMenu.ts +85 -0
  6. package/_vendor/canvas-core/editor/Editor.ts +2614 -0
  7. package/_vendor/canvas-core/editor/HistoryManager.ts +83 -0
  8. package/_vendor/canvas-core/editor/StateNode.ts +162 -0
  9. package/_vendor/canvas-core/editor/camera.ts +6 -0
  10. package/_vendor/canvas-core/editor/clipboard.ts +478 -0
  11. package/_vendor/canvas-core/editor/commands.ts +833 -0
  12. package/_vendor/canvas-core/editor/edgeScroll.ts +37 -0
  13. package/_vendor/canvas-core/editor/editorComputedHelpers.ts +257 -0
  14. package/_vendor/canvas-core/editor/editorSelectionTransforms.ts +116 -0
  15. package/_vendor/canvas-core/editor/events.ts +93 -0
  16. package/_vendor/canvas-core/editor/index.ts +12 -0
  17. package/_vendor/canvas-core/editor/interactions.ts +101 -0
  18. package/_vendor/canvas-core/editor/internal.ts +22 -0
  19. package/_vendor/canvas-core/editor/managers/BindingManager.ts +262 -0
  20. package/_vendor/canvas-core/editor/managers/CameraManager.ts +147 -0
  21. package/_vendor/canvas-core/editor/managers/ClipboardManager.ts +32 -0
  22. package/_vendor/canvas-core/editor/managers/DocumentSnapshotManager.ts +256 -0
  23. package/_vendor/canvas-core/editor/managers/InputsManager.ts +319 -0
  24. package/_vendor/canvas-core/editor/managers/InteractionTransientManager.ts +218 -0
  25. package/_vendor/canvas-core/editor/managers/QueryManager.ts +837 -0
  26. package/_vendor/canvas-core/editor/managers/RenderWindowManager.ts +347 -0
  27. package/_vendor/canvas-core/editor/managers/SelectionLayoutManager.ts +196 -0
  28. package/_vendor/canvas-core/editor/managers/SelectionManager.ts +98 -0
  29. package/_vendor/canvas-core/editor/managers/SelectionOverlayManager.ts +308 -0
  30. package/_vendor/canvas-core/editor/managers/ShapeEditingManager.ts +226 -0
  31. package/_vendor/canvas-core/editor/managers/ShapeGeometryManager.ts +162 -0
  32. package/_vendor/canvas-core/editor/managers/ShapeHierarchyManager.ts +797 -0
  33. package/_vendor/canvas-core/editor/managers/SideEffectManager.ts +83 -0
  34. package/_vendor/canvas-core/editor/managers/SnapManager.ts +433 -0
  35. package/_vendor/canvas-core/editor/managers/SnapSnapshotBuilder.ts +430 -0
  36. package/_vendor/canvas-core/editor/managers/SpatialIndexManager.ts +354 -0
  37. package/_vendor/canvas-core/editor/managers/ToolManager.ts +81 -0
  38. package/_vendor/canvas-core/editor/managers/queryState.ts +583 -0
  39. package/_vendor/canvas-core/editor/managers/selectionGeometry.ts +287 -0
  40. package/_vendor/canvas-core/editor/managers/shapeOrder.ts +350 -0
  41. package/_vendor/canvas-core/editor/managers/shapeReparenting.ts +160 -0
  42. package/_vendor/canvas-core/editor/managers/snapCalculations.ts +412 -0
  43. package/_vendor/canvas-core/editor/managers/snapGeometry.ts +202 -0
  44. package/_vendor/canvas-core/editor/managers/snapIndicators.ts +403 -0
  45. package/_vendor/canvas-core/editor/managers/snapTypes.ts +122 -0
  46. package/_vendor/canvas-core/editor/queries/shapeTransforms.ts +171 -0
  47. package/_vendor/canvas-core/editor/renderWindow.ts +439 -0
  48. package/_vendor/canvas-core/editor/resizeGeometry.ts +654 -0
  49. package/_vendor/canvas-core/editor/selection.ts +489 -0
  50. package/_vendor/canvas-core/editor/selectionHandleInteractions.ts +43 -0
  51. package/_vendor/canvas-core/editor/selectionOverlay.ts +567 -0
  52. package/_vendor/canvas-core/exports/integrity.ts +174 -0
  53. package/_vendor/canvas-core/exports/svg.ts +494 -0
  54. package/_vendor/canvas-core/geometry/Box.ts +80 -0
  55. package/_vendor/canvas-core/geometry/Ellipse2d.ts +67 -0
  56. package/_vendor/canvas-core/geometry/Geometry2d.ts +52 -0
  57. package/_vendor/canvas-core/geometry/Mat.ts +92 -0
  58. package/_vendor/canvas-core/geometry/Polyline2d.ts +63 -0
  59. package/_vendor/canvas-core/geometry/Rectangle2d.ts +64 -0
  60. package/_vendor/canvas-core/geometry/Vec.ts +123 -0
  61. package/_vendor/canvas-core/geometry/index.ts +13 -0
  62. package/_vendor/canvas-core/geometry/segments.ts +65 -0
  63. package/_vendor/canvas-core/index.ts +391 -0
  64. package/_vendor/canvas-core/plugins/index.ts +5 -0
  65. package/_vendor/canvas-core/plugins/types.ts +53 -0
  66. package/_vendor/canvas-core/reactivity.ts +16 -0
  67. package/_vendor/canvas-core/schema/records.ts +221 -0
  68. package/_vendor/canvas-core/schema/snapshots.ts +527 -0
  69. package/_vendor/canvas-core/shapes/ShapeUtil.ts +516 -0
  70. package/_vendor/canvas-core/shapes/ShapeUtilRegistry.ts +26 -0
  71. package/_vendor/canvas-core/shapes/capabilities.ts +67 -0
  72. package/_vendor/canvas-core/shapes/types.ts +20 -0
  73. package/_vendor/canvas-core/toolbar.ts +148 -0
  74. package/_vendor/canvas-core/tools/hand/HandTool.ts +75 -0
  75. package/_vendor/canvas-core/tools/index.ts +2 -0
  76. package/_vendor/canvas-core/tools/select/SelectTool.ts +33 -0
  77. package/_vendor/canvas-core/tools/select/selectContainment.ts +104 -0
  78. package/_vendor/canvas-core/tools/select/selectToolHelpers.ts +801 -0
  79. package/_vendor/canvas-core/tools/select/states/BrushingState.ts +145 -0
  80. package/_vendor/canvas-core/tools/select/states/DraggingSelectionHandleInteractionState.ts +116 -0
  81. package/_vendor/canvas-core/tools/select/states/IdleState.ts +159 -0
  82. package/_vendor/canvas-core/tools/select/states/PointingCanvasState.ts +41 -0
  83. package/_vendor/canvas-core/tools/select/states/PointingResizeHandleState.ts +78 -0
  84. package/_vendor/canvas-core/tools/select/states/PointingRotateHandleState.ts +64 -0
  85. package/_vendor/canvas-core/tools/select/states/PointingSelectionHandleInteractionState.ts +34 -0
  86. package/_vendor/canvas-core/tools/select/states/PointingShapeState.ts +140 -0
  87. package/_vendor/canvas-core/tools/select/states/ResizingState.ts +171 -0
  88. package/_vendor/canvas-core/tools/select/states/RotatingState.ts +124 -0
  89. package/_vendor/canvas-core/tools/select/states/SelectStateNode.ts +128 -0
  90. package/_vendor/canvas-core/tools/select/states/TranslatingState.ts +139 -0
  91. package/_vendor/canvas-core/tools/select/states/index.ts +11 -0
  92. package/_vendor/canvas-plugin-api/feature.ts +204 -0
  93. package/_vendor/canvas-plugin-api/index.ts +142 -0
  94. package/_vendor/canvas-product-react/api/apiRegistry.ts +35 -0
  95. package/_vendor/canvas-product-react/api/canvasProductApi.ts +422 -0
  96. package/_vendor/canvas-product-react/api/index.ts +31 -0
  97. package/_vendor/canvas-product-react/capabilities/clipboard/clipboardService.ts +36 -0
  98. package/_vendor/canvas-product-react/capabilities/clipboard/systemClipboard.ts +245 -0
  99. package/_vendor/canvas-product-react/capabilities/context-menu/systemContextMenu.ts +396 -0
  100. package/_vendor/canvas-product-react/capabilities/context-menu/useContextMenuSurface.ts +59 -0
  101. package/_vendor/canvas-product-react/capabilities/export/useSvgExport.ts +156 -0
  102. package/_vendor/canvas-product-react/capabilities/history/useHistoryControls.ts +23 -0
  103. package/_vendor/canvas-product-react/capabilities/pages/usePageControls.ts +41 -0
  104. package/_vendor/canvas-product-react/capabilities/toolbar/systemToolbar.ts +490 -0
  105. package/_vendor/canvas-product-react/capabilities/toolbar/useToolbarSurfaces.ts +92 -0
  106. package/_vendor/canvas-product-react/capabilities/zoom/useZoomControls.ts +27 -0
  107. package/_vendor/canvas-product-react/index.ts +84 -0
  108. package/_vendor/canvas-product-react/persistence/index.ts +18 -0
  109. package/_vendor/canvas-product-react/persistence/localSnapshotPersistence.ts +294 -0
  110. package/_vendor/canvas-product-react/runtime/CanvasProductRoot.tsx +250 -0
  111. package/_vendor/canvas-product-react/runtime/productDefinition.ts +72 -0
  112. package/_vendor/canvas-product-react/runtime/productServices.ts +96 -0
  113. package/_vendor/canvas-react/components/CanvasView.tsx +796 -0
  114. package/_vendor/canvas-react/components/OverlayLayer.tsx +879 -0
  115. package/_vendor/canvas-react/components/PageSpace.tsx +34 -0
  116. package/_vendor/canvas-react/components/ShapeLayer.tsx +428 -0
  117. package/_vendor/canvas-react/components/index.ts +4 -0
  118. package/_vendor/canvas-react/hooks/useEditor.ts +11 -0
  119. package/_vendor/canvas-react/hooks/useValue.ts +14 -0
  120. package/_vendor/canvas-react/host/CanvasRendererRegistryContext.tsx +14 -0
  121. package/_vendor/canvas-react/host/contextMenuRegistry.ts +61 -0
  122. package/_vendor/canvas-react/host/domEventGuards.ts +61 -0
  123. package/_vendor/canvas-react/host/index.ts +38 -0
  124. package/_vendor/canvas-react/host/pluginHost.ts +635 -0
  125. package/_vendor/canvas-react/host/rendererRegistry.tsx +58 -0
  126. package/_vendor/canvas-react/host/toolbarRegistry.ts +100 -0
  127. package/_vendor/canvas-react/index.ts +3 -0
  128. package/_vendor/plugin-arrow/ArrowBindingUtil.ts +239 -0
  129. package/_vendor/plugin-arrow/ArrowShapeUtil.ts +311 -0
  130. package/_vendor/plugin-arrow/ArrowTool.ts +208 -0
  131. package/_vendor/plugin-arrow/arrowGeometry.ts +441 -0
  132. package/_vendor/plugin-arrow/arrowTerminalBindingResolver.ts +253 -0
  133. package/_vendor/plugin-arrow/arrowTerminalCommands.ts +230 -0
  134. package/_vendor/plugin-arrow/canvas.tsx +92 -0
  135. package/_vendor/plugin-arrow/core.ts +409 -0
  136. package/_vendor/plugin-arrow/feature.ts +20 -0
  137. package/_vendor/plugin-arrow/index.ts +28 -0
  138. package/_vendor/plugin-arrow/selectionHandleInteractions.ts +154 -0
  139. package/_vendor/plugin-arrow/types.ts +63 -0
  140. package/_vendor/plugin-draw/DrawShapeRenderer.tsx +60 -0
  141. package/_vendor/plugin-draw/DrawShapeUtil.ts +372 -0
  142. package/_vendor/plugin-draw/DrawTool.ts +444 -0
  143. package/_vendor/plugin-draw/canvas.tsx +24 -0
  144. package/_vendor/plugin-draw/core.ts +515 -0
  145. package/_vendor/plugin-draw/drawGeometry.ts +491 -0
  146. package/_vendor/plugin-draw/drawHitTest.ts +271 -0
  147. package/_vendor/plugin-draw/drawInterpolation.ts +250 -0
  148. package/_vendor/plugin-draw/drawPath.ts +550 -0
  149. package/_vendor/plugin-draw/drawSegments.ts +106 -0
  150. package/_vendor/plugin-draw/drawSelectionTransform.ts +67 -0
  151. package/_vendor/plugin-draw/drawShapeGeometry.ts +307 -0
  152. package/_vendor/plugin-draw/drawStrokeGeometry.ts +454 -0
  153. package/_vendor/plugin-draw/feature.ts +20 -0
  154. package/_vendor/plugin-draw/index.ts +31 -0
  155. package/_vendor/plugin-draw/types.ts +36 -0
  156. package/_vendor/plugin-eraser/EraserTool.ts +297 -0
  157. package/_vendor/plugin-eraser/canvas.tsx +616 -0
  158. package/_vendor/plugin-eraser/core.ts +49 -0
  159. package/_vendor/plugin-eraser/feature.ts +20 -0
  160. package/_vendor/plugin-eraser/index.ts +4 -0
  161. package/_vendor/plugin-frame/FrameShapeUtil.ts +173 -0
  162. package/_vendor/plugin-frame/FrameTool.ts +225 -0
  163. package/_vendor/plugin-frame/canvas.tsx +75 -0
  164. package/_vendor/plugin-frame/core.ts +534 -0
  165. package/_vendor/plugin-frame/feature.ts +20 -0
  166. package/_vendor/plugin-frame/index.ts +6 -0
  167. package/_vendor/plugin-geo/GeoShapeUtil.ts +463 -0
  168. package/_vendor/plugin-geo/GeoTools.ts +247 -0
  169. package/_vendor/plugin-geo/canvas.tsx +90 -0
  170. package/_vendor/plugin-geo/core.ts +495 -0
  171. package/_vendor/plugin-geo/feature.ts +20 -0
  172. package/_vendor/plugin-geo/index.ts +7 -0
  173. package/_vendor/plugin-geo/types.ts +27 -0
  174. package/_vendor/plugin-group/GroupShapeUtil.ts +59 -0
  175. package/_vendor/plugin-group/canvas.tsx +59 -0
  176. package/_vendor/plugin-group/core.ts +40 -0
  177. package/_vendor/plugin-group/feature.ts +20 -0
  178. package/_vendor/plugin-group/index.ts +5 -0
  179. package/_vendor/plugin-image/ImageShapeUtil.ts +95 -0
  180. package/_vendor/plugin-image/canvas.tsx +212 -0
  181. package/_vendor/plugin-image/core.ts +542 -0
  182. package/_vendor/plugin-image/crop.ts +770 -0
  183. package/_vendor/plugin-image/cropInteractions.ts +350 -0
  184. package/_vendor/plugin-image/cropSession.ts +38 -0
  185. package/_vendor/plugin-image/feature.ts +20 -0
  186. package/_vendor/plugin-image/index.ts +29 -0
  187. package/_vendor/plugin-image/types.ts +55 -0
  188. package/_vendor/plugin-image-product-react/ImageImportController.tsx +143 -0
  189. package/_vendor/plugin-image-product-react/imageAssetService.ts +52 -0
  190. package/_vendor/plugin-image-product-react/imageBlobStore.ts +315 -0
  191. package/_vendor/plugin-image-product-react/imageHostActions.ts +438 -0
  192. package/_vendor/plugin-image-product-react/imageProductApi.ts +88 -0
  193. package/_vendor/plugin-image-product-react/imageToolbarHostBridge.ts +48 -0
  194. package/_vendor/plugin-image-product-react/imageToolbarRuntime.ts +21 -0
  195. package/_vendor/plugin-image-product-react/index.ts +60 -0
  196. package/_vendor/plugin-image-product-react/snapshotGuards.ts +33 -0
  197. package/_vendor/plugin-image-product-react/svgImageAssetInliner.ts +54 -0
  198. package/_vendor/plugin-image-product-react/useImageImport.ts +64 -0
  199. package/_vendor/plugin-laser/LaserTool.ts +154 -0
  200. package/_vendor/plugin-laser/canvas.tsx +645 -0
  201. package/_vendor/plugin-laser/core.ts +49 -0
  202. package/_vendor/plugin-laser/feature.ts +20 -0
  203. package/_vendor/plugin-laser/index.ts +4 -0
  204. package/_vendor/plugin-text/ArrowLabelBindingUtil.ts +336 -0
  205. package/_vendor/plugin-text/GeoLabelBindingUtil.ts +391 -0
  206. package/_vendor/plugin-text/TextShapeSvgExporter.ts +101 -0
  207. package/_vendor/plugin-text/TextShapeUtil.ts +284 -0
  208. package/_vendor/plugin-text/TextTool.ts +228 -0
  209. package/_vendor/plugin-text/canvas.tsx +115 -0
  210. package/_vendor/plugin-text/core.ts +422 -0
  211. package/_vendor/plugin-text/feature.ts +20 -0
  212. package/_vendor/plugin-text/host/RichTextEditingBox.tsx +144 -0
  213. package/_vendor/plugin-text/host/RichTextToolbar.tsx +266 -0
  214. package/_vendor/plugin-text/host/TiptapTextEditor.tsx +191 -0
  215. package/_vendor/plugin-text/host/richTextRendering.tsx +49 -0
  216. package/_vendor/plugin-text/host/textDomSelection.ts +93 -0
  217. package/_vendor/plugin-text/host/textEditingBoxStyle.ts +36 -0
  218. package/_vendor/plugin-text/host/textEntryFocus.ts +307 -0
  219. package/_vendor/plugin-text/host/textFontLoader.ts +149 -0
  220. package/_vendor/plugin-text/host/textMeasurement.ts +258 -0
  221. package/_vendor/plugin-text/host/textMeasurementSync.ts +77 -0
  222. package/_vendor/plugin-text/host/textRangeSelection.ts +134 -0
  223. package/_vendor/plugin-text/host/textShapeRendererRuntime.tsx +89 -0
  224. package/_vendor/plugin-text/host/textShapeSync.ts +173 -0
  225. package/_vendor/plugin-text/host/tiptapTextEditorController.ts +148 -0
  226. package/_vendor/plugin-text/index.ts +18 -0
  227. package/_vendor/plugin-text/richTextUtils.ts +259 -0
  228. package/_vendor/plugin-text/textInteractions.ts +366 -0
  229. package/_vendor/plugin-text/textLabelHelpers.ts +235 -0
  230. package/_vendor/plugin-text/textShapeHelpers.ts +123 -0
  231. package/_vendor/plugin-text/textStyle.ts +96 -0
  232. package/_vendor/plugin-text/textTypes.ts +48 -0
  233. package/_vendor/signals/atom.ts +74 -0
  234. package/_vendor/signals/computed.ts +132 -0
  235. package/_vendor/signals/core.ts +130 -0
  236. package/_vendor/signals/effect.ts +117 -0
  237. package/_vendor/signals/index.ts +9 -0
  238. package/_vendor/signals/types.ts +17 -0
  239. package/_vendor/store/Store.ts +171 -0
  240. package/_vendor/store/diff.ts +229 -0
  241. package/_vendor/store/history.ts +177 -0
  242. package/_vendor/store/index.ts +32 -0
  243. package/_vendor/store/types.ts +37 -0
  244. package/api/canvasKitApi.d.ts +29 -0
  245. package/index.css +3 -0
  246. package/index.d.ts +16 -0
  247. package/index.mjs +1 -0
  248. package/internal-modules.d.ts +187 -0
  249. package/package.json +48 -0
  250. package/product/Shell.d.ts +19 -0
  251. package/product/chromeContext.d.ts +70 -0
  252. package/product/createCanvasKitEditor.d.ts +6 -0
  253. package/product/index.d.ts +7 -0
  254. package/product/persistence.d.ts +3 -0
  255. package/product/product.d.ts +22 -0
  256. package/product/useCanvasClipboardShortcuts.d.ts +6 -0
  257. package/product-iyBbEPIs.mjs +4 -0
  258. package/product.mjs +1 -0
  259. package/standard/extensions.d.ts +1 -0
  260. package/standard/index.d.ts +3 -0
  261. package/standard/standardApi.d.ts +91 -0
  262. package/standard-BmrxwqpJ.mjs +44 -0
  263. package/standard.mjs +1 -0
@@ -0,0 +1,879 @@
1
+ import { memo } from "react";
2
+ import {
3
+ type ArrowBindingPreviewState,
4
+ type Editor,
5
+ type GapSnapIndicatorState,
6
+ type PointSnapIndicatorState,
7
+ type SelectionOverlayFrameGeometryState,
8
+ type SelectionOverlayHandleAnchorsState,
9
+ type SelectionResizeHandleId,
10
+ type ShapeRecord,
11
+ type SnapIndicatorState,
12
+ type Vec,
13
+ } from "@land/canvas-core";
14
+ import { useEditor } from "../hooks/useEditor";
15
+ import { useValue } from "../hooks/useValue";
16
+ import { PageSpace } from "./PageSpace";
17
+
18
+ function ShapeOutlineOverlay({
19
+ editor,
20
+ shape,
21
+ stroke,
22
+ fill,
23
+ strokeWidth,
24
+ }: {
25
+ editor: Editor;
26
+ shape: ShapeRecord;
27
+ stroke: string;
28
+ fill: string;
29
+ strokeWidth: number;
30
+ }) {
31
+ const outline = editor.getShapePageOutlineVertices(shape.id);
32
+ if (outline.length === 0) {
33
+ return null;
34
+ }
35
+
36
+ return (
37
+ <polygon
38
+ points={outline.map((point) => `${point.x},${point.y}`).join(" ")}
39
+ fill={fill}
40
+ stroke={stroke}
41
+ strokeWidth={strokeWidth}
42
+ strokeLinejoin="round"
43
+ strokeLinecap="round"
44
+ />
45
+ );
46
+ }
47
+
48
+ export function OverlayLayer() {
49
+ return (
50
+ <div
51
+ data-ui="overlay-layer"
52
+ className="overlay-layer pointer-events-none absolute inset-0"
53
+ style={{
54
+ position: "absolute",
55
+ left: 0,
56
+ top: 0,
57
+ width: "100%",
58
+ height: "100%",
59
+ }}
60
+ >
61
+ <PageSpace
62
+ dataUi="overlay-page-space"
63
+ className="overlay-layer__page-space absolute inset-0"
64
+ >
65
+ <OverlaySvgRoot />
66
+ </PageSpace>
67
+ </div>
68
+ );
69
+ }
70
+
71
+ const OverlaySvgRoot = memo(function OverlaySvgRoot() {
72
+ return (
73
+ <svg
74
+ width="100%"
75
+ height="100%"
76
+ style={{
77
+ display: "block",
78
+ width: "100%",
79
+ height: "100%",
80
+ overflow: "visible",
81
+ }}
82
+ >
83
+ <style>
84
+ {`
85
+ [data-selection-terminal],
86
+ [data-selection-arrow-middle],
87
+ [data-selection-custom-handle]:not([data-selection-custom-handle-type="image.crop"]) {
88
+ transition: fill 120ms ease, stroke 120ms ease;
89
+ }
90
+ [data-selection-terminal]:hover,
91
+ [data-selection-arrow-middle]:hover,
92
+ [data-selection-custom-handle]:not([data-selection-custom-handle-type="image.crop"]):hover {
93
+ fill: rgba(219, 234, 254, 0.98);
94
+ stroke: rgba(29, 78, 216, 0.98);
95
+ }
96
+ [data-selection-terminal]:active,
97
+ [data-selection-arrow-middle]:active,
98
+ [data-selection-custom-handle]:active {
99
+ cursor: grabbing;
100
+ }
101
+ `}
102
+ </style>
103
+ <SnapIndicatorsOverlay />
104
+ <BindingPreviewOverlay />
105
+ <BrushBoxOverlay />
106
+ <SelectionOverlaySvg />
107
+ </svg>
108
+ );
109
+ });
110
+
111
+ const BindingPreviewOverlay = memo(function BindingPreviewOverlay() {
112
+ const editor = useEditor();
113
+ const isBindingPreviewActive = useValue(editor.bindingPreviewActiveSignal);
114
+
115
+ if (!isBindingPreviewActive) {
116
+ return null;
117
+ }
118
+
119
+ return <BindingPreviewContent />;
120
+ });
121
+
122
+ const SNAP_STROKE = "rgba(37, 99, 235, 0.9)";
123
+
124
+ const SnapIndicatorsOverlay = memo(function SnapIndicatorsOverlay() {
125
+ const editor = useEditor();
126
+ const snapIndicators = useValue(editor.snapIndicatorsSignal);
127
+
128
+ if (snapIndicators.length === 0) {
129
+ return null;
130
+ }
131
+
132
+ return <SnapIndicatorsContent indicators={snapIndicators} />;
133
+ });
134
+
135
+ const SnapIndicatorsContent = memo(function SnapIndicatorsContent({
136
+ indicators,
137
+ }: {
138
+ indicators: SnapIndicatorState[];
139
+ }) {
140
+ const editor = useEditor();
141
+ const zoom = useValue(editor.cameraZoomSignal);
142
+
143
+ return (
144
+ <>
145
+ {indicators.map((indicator) =>
146
+ indicator.type === "points" ? (
147
+ <PointSnapIndicatorOverlay
148
+ key={indicator.id}
149
+ indicator={indicator}
150
+ zoom={zoom}
151
+ />
152
+ ) : (
153
+ <GapSnapIndicatorOverlay
154
+ key={indicator.id}
155
+ indicator={indicator}
156
+ zoom={zoom}
157
+ />
158
+ ),
159
+ )}
160
+ </>
161
+ );
162
+ });
163
+
164
+ const PointSnapIndicatorOverlay = memo(function PointSnapIndicatorOverlay({
165
+ indicator,
166
+ zoom,
167
+ }: {
168
+ indicator: PointSnapIndicatorState;
169
+ zoom: number;
170
+ }) {
171
+ const crossSize = 2.5 / zoom;
172
+ const strokeWidth = 1 / zoom;
173
+ const [lineStart, lineEnd] = indicator.line;
174
+ const path = `M ${lineStart.x} ${lineStart.y} L ${lineEnd.x} ${lineEnd.y}`;
175
+ const rotationDegrees = (indicator.rotation * 180) / Math.PI;
176
+ const transform =
177
+ indicator.rotation === 0
178
+ ? undefined
179
+ : `rotate(${rotationDegrees} ${indicator.rotationCenter.x} ${indicator.rotationCenter.y})`;
180
+
181
+ return (
182
+ <g data-ui="snap-indicator" transform={transform}>
183
+ <path
184
+ data-ui="snap-indicator-line"
185
+ d={path}
186
+ fill="none"
187
+ stroke={SNAP_STROKE}
188
+ strokeWidth={strokeWidth}
189
+ strokeLinecap="round"
190
+ />
191
+ {indicator.points.map((point, index) => (
192
+ <path
193
+ key={`${indicator.id}:${index}`}
194
+ data-ui="snap-indicator-cross"
195
+ d={`M ${point.x - crossSize} ${point.y - crossSize} L ${point.x + crossSize} ${point.y + crossSize} M ${point.x - crossSize} ${point.y + crossSize} L ${point.x + crossSize} ${point.y - crossSize}`}
196
+ fill="none"
197
+ stroke={SNAP_STROKE}
198
+ strokeWidth={strokeWidth}
199
+ strokeLinecap="round"
200
+ />
201
+ ))}
202
+ </g>
203
+ );
204
+ });
205
+
206
+ function getRangeIntersection(
207
+ first: [number, number],
208
+ second: [number, number],
209
+ ): [number, number] | null {
210
+ const min = Math.max(first[0], second[0]);
211
+ const max = Math.min(first[1], second[1]);
212
+ return min <= max ? [min, max] : null;
213
+ }
214
+
215
+ const GapSnapIndicatorOverlay = memo(function GapSnapIndicatorOverlay({
216
+ indicator,
217
+ zoom,
218
+ }: {
219
+ indicator: GapSnapIndicatorState;
220
+ zoom: number;
221
+ }) {
222
+ if (indicator.gaps.length === 0) {
223
+ return null;
224
+ }
225
+
226
+ const horizontal = indicator.direction === "horizontal";
227
+ let edgeIntersection: [number, number] = [
228
+ Number.NEGATIVE_INFINITY,
229
+ Number.POSITIVE_INFINITY,
230
+ ];
231
+ for (const gap of indicator.gaps) {
232
+ const startIntersection = getRangeIntersection(edgeIntersection, [
233
+ horizontal ? gap.startEdge[0].y : gap.startEdge[0].x,
234
+ horizontal ? gap.startEdge[1].y : gap.startEdge[1].x,
235
+ ]);
236
+ if (startIntersection) {
237
+ edgeIntersection = startIntersection;
238
+ }
239
+ const endIntersection = getRangeIntersection(edgeIntersection, [
240
+ horizontal ? gap.endEdge[0].y : gap.endEdge[0].x,
241
+ horizontal ? gap.endEdge[1].y : gap.endEdge[1].x,
242
+ ]);
243
+ if (endIntersection) {
244
+ edgeIntersection = endIntersection;
245
+ }
246
+ }
247
+
248
+ if (
249
+ !Number.isFinite(edgeIntersection[0]) ||
250
+ !Number.isFinite(edgeIntersection[1])
251
+ ) {
252
+ return null;
253
+ }
254
+
255
+ const midPoint = (edgeIntersection[0] + edgeIntersection[1]) / 2;
256
+ const halfTick = 3.5 / zoom;
257
+ const tickLength = halfTick * 2;
258
+ const commands: string[] = [];
259
+ for (const { startEdge, endEdge } of indicator.gaps) {
260
+ if (horizontal) {
261
+ commands.push(
262
+ `M ${startEdge[0].x} ${midPoint - tickLength} L ${startEdge[1].x} ${midPoint + tickLength}`,
263
+ `M ${endEdge[0].x} ${midPoint - tickLength} L ${endEdge[1].x} ${midPoint + tickLength}`,
264
+ `M ${startEdge[0].x} ${midPoint} L ${endEdge[0].x} ${midPoint}`,
265
+ );
266
+ const centerX = (startEdge[0].x + endEdge[0].x) / 2;
267
+ commands.push(
268
+ `M ${centerX} ${midPoint - halfTick} L ${centerX} ${midPoint + halfTick}`,
269
+ );
270
+ } else {
271
+ commands.push(
272
+ `M ${midPoint - tickLength} ${startEdge[0].y} L ${midPoint + tickLength} ${startEdge[1].y}`,
273
+ `M ${midPoint - tickLength} ${endEdge[0].y} L ${midPoint + tickLength} ${endEdge[1].y}`,
274
+ `M ${midPoint} ${startEdge[0].y} L ${midPoint} ${endEdge[0].y}`,
275
+ );
276
+ const centerY = (startEdge[0].y + endEdge[0].y) / 2;
277
+ commands.push(
278
+ `M ${midPoint - halfTick} ${centerY} L ${midPoint + halfTick} ${centerY}`,
279
+ );
280
+ }
281
+ }
282
+
283
+ const rotationDegrees = (indicator.rotation * 180) / Math.PI;
284
+ const transform =
285
+ indicator.rotation === 0
286
+ ? undefined
287
+ : `rotate(${rotationDegrees} ${indicator.rotationCenter.x} ${indicator.rotationCenter.y})`;
288
+
289
+ return (
290
+ <path
291
+ data-ui="snap-gap-indicator"
292
+ d={commands.join(" ")}
293
+ transform={transform}
294
+ fill="none"
295
+ stroke={SNAP_STROKE}
296
+ strokeWidth={1 / zoom}
297
+ strokeLinecap="round"
298
+ strokeLinejoin="round"
299
+ />
300
+ );
301
+ });
302
+
303
+ const BindingPreviewContent = memo(function BindingPreviewContent() {
304
+ const editor = useEditor();
305
+ const bindingPreview = useValue(editor.bindingPreviewSignal);
306
+
307
+ if (!bindingPreview) {
308
+ return null;
309
+ }
310
+
311
+ return (
312
+ <>
313
+ <BindingPreviewTargetOutline
314
+ targetShapeId={bindingPreview.targetShapeId}
315
+ />
316
+ <BindingPreviewPoints bindingPreview={bindingPreview} />
317
+ </>
318
+ );
319
+ });
320
+
321
+ const BindingPreviewTargetOutline = memo(function BindingPreviewTargetOutline({
322
+ targetShapeId,
323
+ }: {
324
+ targetShapeId: ArrowBindingPreviewState["targetShapeId"];
325
+ }) {
326
+ const editor = useEditor();
327
+ const zoom = useValue(editor.cameraZoomSignal);
328
+ const targetShape = useValue(editor.getShapeSignal(targetShapeId));
329
+
330
+ if (!targetShape) {
331
+ return null;
332
+ }
333
+
334
+ return (
335
+ <ShapeOutlineOverlay
336
+ editor={editor}
337
+ shape={targetShape}
338
+ stroke="rgba(14, 165, 233, 0.78)"
339
+ fill="rgba(14, 165, 233, 0.08)"
340
+ strokeWidth={2 / zoom}
341
+ />
342
+ );
343
+ });
344
+
345
+ const BindingPreviewPoints = memo(function BindingPreviewPoints({
346
+ bindingPreview,
347
+ }: {
348
+ bindingPreview: ArrowBindingPreviewState;
349
+ }) {
350
+ return (
351
+ <>
352
+ <circle
353
+ data-binding-preview-point="raw"
354
+ cx={bindingPreview.rawPagePoint.x}
355
+ cy={bindingPreview.rawPagePoint.y}
356
+ r={6}
357
+ fill="rgba(255,255,255,0.96)"
358
+ stroke="rgba(14, 165, 233, 0.8)"
359
+ strokeWidth={2}
360
+ />
361
+ <circle
362
+ data-binding-preview-point="resolved"
363
+ cx={bindingPreview.resolvedPagePoint.x}
364
+ cy={bindingPreview.resolvedPagePoint.y}
365
+ r={7}
366
+ fill="rgba(14, 165, 233, 0.18)"
367
+ stroke="rgba(2, 132, 199, 0.9)"
368
+ strokeWidth={2}
369
+ />
370
+ </>
371
+ );
372
+ });
373
+
374
+ const BrushBoxOverlay = memo(function BrushBoxOverlay() {
375
+ const editor = useEditor();
376
+ const isBrushBoxActive = useValue(editor.brushBoxActiveSignal);
377
+
378
+ if (!isBrushBoxActive) {
379
+ return null;
380
+ }
381
+
382
+ return <BrushBoxContent />;
383
+ });
384
+
385
+ const BrushBoxContent = memo(function BrushBoxContent() {
386
+ const editor = useEditor();
387
+ const brushBox = useValue(editor.brushBoxSignal);
388
+
389
+ if (!brushBox) {
390
+ return null;
391
+ }
392
+
393
+ return (
394
+ <rect
395
+ data-ui="brush-box-overlay"
396
+ x={brushBox.x}
397
+ y={brushBox.y}
398
+ width={brushBox.w}
399
+ height={brushBox.h}
400
+ fill="rgba(59, 130, 246, 0.14)"
401
+ stroke="rgba(37, 99, 235, 0.92)"
402
+ strokeDasharray="4 3"
403
+ strokeWidth={1}
404
+ />
405
+ );
406
+ });
407
+
408
+ const SelectionOverlaySvg = memo(function SelectionOverlaySvg() {
409
+ return (
410
+ <>
411
+ <SelectionArrowPathOverlay />
412
+ <SelectionFrameOverlay />
413
+ <SelectionHandlesOverlay />
414
+ </>
415
+ );
416
+ });
417
+
418
+ const SelectionArrowPathOverlay = memo(function SelectionArrowPathOverlay() {
419
+ const editor = useEditor();
420
+ const arrowPath = useValue(editor.selectionOverlayArrowPathSignal);
421
+ const frameStyle = useValue(editor.selectionOverlayFrameStyleSignal);
422
+
423
+ if (!arrowPath) {
424
+ return null;
425
+ }
426
+
427
+ return (
428
+ <>
429
+ <path
430
+ data-selection-overlay="arrow-path-underlay"
431
+ d={arrowPath}
432
+ fill="none"
433
+ stroke="rgba(255,255,255,0.92)"
434
+ strokeLinecap="round"
435
+ strokeLinejoin="round"
436
+ strokeWidth={frameStyle.strokeWidth * 2}
437
+ />
438
+ <path
439
+ data-selection-overlay="arrow-path"
440
+ d={arrowPath}
441
+ fill="none"
442
+ stroke="rgba(37, 99, 235, 0.88)"
443
+ strokeLinecap="round"
444
+ strokeLinejoin="round"
445
+ strokeWidth={frameStyle.strokeWidth}
446
+ />
447
+ </>
448
+ );
449
+ });
450
+
451
+ const SelectionFrameOverlay = memo(function SelectionFrameOverlay() {
452
+ const editor = useEditor();
453
+ const selectionFrameGeometry = useValue(
454
+ editor.selectionOverlayFrameGeometrySignal,
455
+ );
456
+ const companionFrameGeometries = useValue(
457
+ editor.selectionOverlayCompanionFrameGeometriesSignal,
458
+ );
459
+
460
+ if (!selectionFrameGeometry && companionFrameGeometries.length === 0) {
461
+ return null;
462
+ }
463
+
464
+ return (
465
+ <>
466
+ {selectionFrameGeometry ? (
467
+ <SelectionFrameContent frameGeometry={selectionFrameGeometry} />
468
+ ) : null}
469
+ {companionFrameGeometries.map((frameGeometry, index) => (
470
+ <SelectionFrameContent
471
+ key={`companion-${index}`}
472
+ frameGeometry={frameGeometry}
473
+ />
474
+ ))}
475
+ </>
476
+ );
477
+ });
478
+
479
+ const SelectionFrameContent = memo(function SelectionFrameContent({
480
+ frameGeometry,
481
+ }: {
482
+ frameGeometry: SelectionOverlayFrameGeometryState;
483
+ }) {
484
+ const editor = useEditor();
485
+ const frameStyle = useValue(editor.selectionOverlayFrameStyleSignal);
486
+ const { bounds, rotation, rotationCenter } = frameGeometry;
487
+ const { cornerRadius, strokeWidth } = frameStyle;
488
+ const rotationDegrees = (rotation * 180) / Math.PI;
489
+ const rotationTransform =
490
+ rotation === 0
491
+ ? undefined
492
+ : `rotate(${rotationDegrees} ${rotationCenter.x} ${rotationCenter.y})`;
493
+
494
+ return (
495
+ <>
496
+ <g transform={rotationTransform}>
497
+ <rect
498
+ data-selection-overlay="bounds-underlay"
499
+ x={bounds.x}
500
+ y={bounds.y}
501
+ width={bounds.w}
502
+ height={bounds.h}
503
+ rx={cornerRadius}
504
+ ry={cornerRadius}
505
+ fill="none"
506
+ stroke="rgba(255,255,255,0.92)"
507
+ strokeWidth={strokeWidth * 2}
508
+ />
509
+ <rect
510
+ data-selection-overlay="bounds"
511
+ x={bounds.x}
512
+ y={bounds.y}
513
+ width={bounds.w}
514
+ height={bounds.h}
515
+ rx={cornerRadius}
516
+ ry={cornerRadius}
517
+ fill="none"
518
+ stroke="rgba(37, 99, 235, 0.88)"
519
+ strokeWidth={strokeWidth}
520
+ />
521
+ </g>
522
+ </>
523
+ );
524
+ });
525
+
526
+ const SelectionHandlesOverlay = memo(function SelectionHandlesOverlay() {
527
+ const editor = useEditor();
528
+ const selectionHandleAnchors = useValue(
529
+ editor.selectionOverlayHandleAnchorsSignal,
530
+ );
531
+
532
+ if (!selectionHandleAnchors) {
533
+ return null;
534
+ }
535
+
536
+ return <SelectionHandlesContent anchors={selectionHandleAnchors} />;
537
+ });
538
+
539
+ const SelectionHandlesContent = memo(function SelectionHandlesContent({
540
+ anchors: _anchors,
541
+ }: {
542
+ anchors: SelectionOverlayHandleAnchorsState;
543
+ }) {
544
+ const editor = useEditor();
545
+ const selectionHandles = useValue(editor.selectionOverlayHandlesSignal);
546
+ const selectionFrame = useValue(editor.selectionOverlayFrameGeometrySignal);
547
+ const zoom = useValue(editor.cameraZoomSignal);
548
+
549
+ if (!selectionHandles) {
550
+ return null;
551
+ }
552
+
553
+ const {
554
+ resizeHandles,
555
+ rotateHandles,
556
+ strokeWidth,
557
+ terminalHandles,
558
+ middleHandles,
559
+ customHandles,
560
+ } = selectionHandles;
561
+
562
+ return (
563
+ <>
564
+ {resizeHandles.map((handle) => (
565
+ <rect
566
+ key={handle.id}
567
+ data-selection-resize-handle={handle.id}
568
+ x={handle.bounds.x}
569
+ y={handle.bounds.y}
570
+ width={handle.bounds.w}
571
+ height={handle.bounds.h}
572
+ rx={4}
573
+ ry={4}
574
+ fill="rgba(255,255,255,0.98)"
575
+ stroke="rgba(37, 99, 235, 0.92)"
576
+ strokeWidth={strokeWidth}
577
+ style={{
578
+ cursor: getSelectionResizeHandleCursor(
579
+ handle.id,
580
+ selectionFrame?.rotation ?? 0,
581
+ ),
582
+ pointerEvents: "auto",
583
+ }}
584
+ />
585
+ ))}
586
+ {rotateHandles.map((handle) => (
587
+ <g
588
+ key={handle.id}
589
+ data-selection-rotate-handle={handle.id}
590
+ transform={`translate(${handle.bounds.center.x - 6 / zoom} ${handle.bounds.center.y - 6 / zoom}) scale(${0.6 / zoom})`}
591
+ fill="none"
592
+ stroke="rgba(37, 99, 235, 0.96)"
593
+ strokeWidth={2}
594
+ strokeLinecap="round"
595
+ strokeLinejoin="round"
596
+ >
597
+ <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
598
+ <path d="M21 3v5h-5" />
599
+ </g>
600
+ ))}
601
+ {terminalHandles.map((handle) => (
602
+ <circle
603
+ key={handle.terminal}
604
+ data-selection-terminal={handle.terminal}
605
+ cx={handle.bounds.center.x}
606
+ cy={handle.bounds.center.y}
607
+ r={Math.min(handle.bounds.w, handle.bounds.h) / 2}
608
+ fill="rgba(255,255,255,0.98)"
609
+ stroke="rgba(37, 99, 235, 0.92)"
610
+ strokeWidth={strokeWidth}
611
+ style={{
612
+ cursor: "grab",
613
+ pointerEvents: "auto",
614
+ }}
615
+ />
616
+ ))}
617
+ {middleHandles.map((handle) => (
618
+ <rect
619
+ key="middle"
620
+ data-selection-arrow-middle="true"
621
+ x={handle.bounds.x}
622
+ y={handle.bounds.y}
623
+ width={handle.bounds.w}
624
+ height={handle.bounds.h}
625
+ rx={2}
626
+ ry={2}
627
+ fill="rgba(255,255,255,0.98)"
628
+ stroke="rgba(37, 99, 235, 0.92)"
629
+ strokeWidth={strokeWidth}
630
+ transform={`rotate(45 ${handle.bounds.center.x} ${handle.bounds.center.y})`}
631
+ style={{
632
+ cursor: "grab",
633
+ pointerEvents: "auto",
634
+ }}
635
+ />
636
+ ))}
637
+ {customHandles.map((handle) =>
638
+ handle.type === "image.crop" ? (
639
+ <ImageCropHandleOverlay
640
+ key={`${handle.type}:${handle.id}`}
641
+ handle={handle}
642
+ strokeWidth={strokeWidth}
643
+ />
644
+ ) : (
645
+ <rect
646
+ key={`${handle.type}:${handle.id}`}
647
+ data-selection-custom-handle={handle.id}
648
+ data-selection-custom-handle-type={handle.type}
649
+ data-selection-custom-handle-id={handle.id}
650
+ x={handle.bounds.x}
651
+ y={handle.bounds.y}
652
+ width={handle.bounds.w}
653
+ height={handle.bounds.h}
654
+ rx={3}
655
+ ry={3}
656
+ fill="rgba(255,255,255,0.98)"
657
+ stroke="rgba(37, 99, 235, 0.92)"
658
+ strokeWidth={strokeWidth}
659
+ style={{
660
+ cursor: handle.cursor ?? "grab",
661
+ pointerEvents: "auto",
662
+ }}
663
+ />
664
+ ),
665
+ )}
666
+ </>
667
+ );
668
+ });
669
+
670
+ const RESIZE_CURSORS = [
671
+ "ew-resize",
672
+ "nwse-resize",
673
+ "ns-resize",
674
+ "nesw-resize",
675
+ ] as const;
676
+
677
+ function getSelectionResizeHandleCursor(
678
+ handleId: SelectionResizeHandleId,
679
+ rotation: number,
680
+ ): string {
681
+ const baseAngle = getSelectionResizeHandleAngle(handleId);
682
+ const normalizedAngle =
683
+ ((((baseAngle + rotation) % Math.PI) + Math.PI) % Math.PI) / (Math.PI / 4);
684
+ const cursorIndex = Math.round(normalizedAngle) % RESIZE_CURSORS.length;
685
+ return RESIZE_CURSORS[cursorIndex] ?? "default";
686
+ }
687
+
688
+ function getSelectionResizeHandleAngle(
689
+ handleId: SelectionResizeHandleId,
690
+ ): number {
691
+ switch (handleId) {
692
+ case "top_left":
693
+ case "bottom_right":
694
+ return Math.PI / 4;
695
+ case "top":
696
+ case "bottom":
697
+ return Math.PI / 2;
698
+ case "top_right":
699
+ case "bottom_left":
700
+ return (Math.PI * 3) / 4;
701
+ case "right":
702
+ case "left":
703
+ return 0;
704
+ }
705
+ }
706
+
707
+ function ImageCropHandleOverlay({
708
+ handle,
709
+ strokeWidth,
710
+ }: {
711
+ handle: {
712
+ id: string;
713
+ type: string;
714
+ cursor?: string;
715
+ bounds: { x: number; y: number; w: number; h: number; center: Vec };
716
+ };
717
+ strokeWidth: number;
718
+ }) {
719
+ const { x, y, w, h, center } = handle.bounds;
720
+ const handleSize = Math.max(w, h);
721
+ const extension = handleSize * 1.35;
722
+ const visualStrokeWidth = Math.max(strokeWidth * 2.5, 2);
723
+ const outwardOffset = visualStrokeWidth / 2;
724
+ const commonProps = {
725
+ fill: "none",
726
+ stroke: "rgba(37, 99, 235, 0.96)",
727
+ strokeLinecap: "butt" as const,
728
+ strokeLinejoin: "miter" as const,
729
+ strokeWidth: visualStrokeWidth,
730
+ pointerEvents: "none" as const,
731
+ };
732
+ const handlePath = getImageCropHandlePath({
733
+ id: handle.id,
734
+ extension,
735
+ outwardOffset,
736
+ centerX: center.x,
737
+ centerY: center.y,
738
+ });
739
+
740
+ return (
741
+ <g>
742
+ <rect
743
+ data-selection-custom-handle={handle.id}
744
+ data-selection-custom-handle-type={handle.type}
745
+ data-selection-custom-handle-id={handle.id}
746
+ x={x - w * 0.8}
747
+ y={y - h * 0.8}
748
+ width={w * 2.6}
749
+ height={h * 2.6}
750
+ fill="transparent"
751
+ style={{
752
+ cursor: handle.cursor ?? "grab",
753
+ pointerEvents: "auto",
754
+ }}
755
+ />
756
+ {handlePath.kind === "corner" ? (
757
+ <polyline points={handlePath.points} {...commonProps} />
758
+ ) : (
759
+ <line
760
+ x1={handlePath.x1}
761
+ y1={handlePath.y1}
762
+ x2={handlePath.x2}
763
+ y2={handlePath.y2}
764
+ {...commonProps}
765
+ />
766
+ )}
767
+ </g>
768
+ );
769
+ }
770
+
771
+ type ImageCropHandlePath =
772
+ | {
773
+ kind: "corner";
774
+ points: string;
775
+ }
776
+ | {
777
+ kind: "edge";
778
+ x1: number;
779
+ y1: number;
780
+ x2: number;
781
+ y2: number;
782
+ };
783
+
784
+ function formatSvgPoint(x: number, y: number): string {
785
+ return `${x},${y}`;
786
+ }
787
+
788
+ function getImageCropHandlePath({
789
+ id,
790
+ extension,
791
+ outwardOffset,
792
+ centerX,
793
+ centerY,
794
+ }: {
795
+ id: string;
796
+ extension: number;
797
+ outwardOffset: number;
798
+ centerX: number;
799
+ centerY: number;
800
+ }): ImageCropHandlePath {
801
+ switch (id) {
802
+ case "top_left":
803
+ return {
804
+ kind: "corner",
805
+ points: [
806
+ formatSvgPoint(centerX - outwardOffset, centerY + extension),
807
+ formatSvgPoint(centerX - outwardOffset, centerY - outwardOffset),
808
+ formatSvgPoint(centerX + extension, centerY - outwardOffset),
809
+ ].join(" "),
810
+ };
811
+ case "top":
812
+ return {
813
+ kind: "edge",
814
+ x1: centerX - extension,
815
+ y1: centerY - outwardOffset,
816
+ x2: centerX + extension,
817
+ y2: centerY - outwardOffset,
818
+ };
819
+ case "top_right":
820
+ return {
821
+ kind: "corner",
822
+ points: [
823
+ formatSvgPoint(centerX - extension, centerY - outwardOffset),
824
+ formatSvgPoint(centerX + outwardOffset, centerY - outwardOffset),
825
+ formatSvgPoint(centerX + outwardOffset, centerY + extension),
826
+ ].join(" "),
827
+ };
828
+ case "right":
829
+ return {
830
+ kind: "edge",
831
+ x1: centerX + outwardOffset,
832
+ y1: centerY - extension,
833
+ x2: centerX + outwardOffset,
834
+ y2: centerY + extension,
835
+ };
836
+ case "bottom_right":
837
+ return {
838
+ kind: "corner",
839
+ points: [
840
+ formatSvgPoint(centerX + outwardOffset, centerY - extension),
841
+ formatSvgPoint(centerX + outwardOffset, centerY + outwardOffset),
842
+ formatSvgPoint(centerX - extension, centerY + outwardOffset),
843
+ ].join(" "),
844
+ };
845
+ case "bottom":
846
+ return {
847
+ kind: "edge",
848
+ x1: centerX - extension,
849
+ y1: centerY + outwardOffset,
850
+ x2: centerX + extension,
851
+ y2: centerY + outwardOffset,
852
+ };
853
+ case "bottom_left":
854
+ return {
855
+ kind: "corner",
856
+ points: [
857
+ formatSvgPoint(centerX + extension, centerY + outwardOffset),
858
+ formatSvgPoint(centerX - outwardOffset, centerY + outwardOffset),
859
+ formatSvgPoint(centerX - outwardOffset, centerY - extension),
860
+ ].join(" "),
861
+ };
862
+ case "left":
863
+ return {
864
+ kind: "edge",
865
+ x1: centerX - outwardOffset,
866
+ y1: centerY - extension,
867
+ x2: centerX - outwardOffset,
868
+ y2: centerY + extension,
869
+ };
870
+ default:
871
+ return {
872
+ kind: "edge",
873
+ x1: centerX,
874
+ y1: centerY,
875
+ x2: centerX,
876
+ y2: centerY,
877
+ };
878
+ }
879
+ }