@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,336 @@
1
+ import {
2
+ BindingUtil,
3
+ type BindingRecord,
4
+ type BindingRecordBase,
5
+ type BindingUtilEffect,
6
+ type BindingSelectionCompanionFrameContext,
7
+ type Editor,
8
+ type SelectionOverlayFrameGeometryState,
9
+ type ShapeRecord,
10
+ type ShapeUpdate,
11
+ Vec,
12
+ } from "@land/canvas-core";
13
+ import {
14
+ ARROW_LABEL_BINDING_TYPE,
15
+ textLabelShowsCompanionFrameWhenOwnerSelected,
16
+ } from "./textLabelHelpers";
17
+ import { isTextShapeRecord } from "./textShapeHelpers";
18
+ import type { TextShapeRecord } from "./textTypes";
19
+
20
+ export const ARROW_LABEL_WIDTH = 120;
21
+ export const ARROW_LABEL_MIN_WIDTH = 24;
22
+ export const ARROW_LABEL_INDEX_SUFFIX = ".label";
23
+
24
+ export interface ArrowLabelBindingProps {
25
+ labelPosition: number;
26
+ }
27
+
28
+ export interface ArrowLabelBindingRecord
29
+ extends BindingRecordBase<
30
+ typeof ARROW_LABEL_BINDING_TYPE,
31
+ ArrowLabelBindingProps
32
+ > {
33
+ type: typeof ARROW_LABEL_BINDING_TYPE;
34
+ props: ArrowLabelBindingProps;
35
+ }
36
+
37
+ function isArrowShapeRecord(
38
+ record: ShapeRecord | null | undefined,
39
+ ): record is ShapeRecord {
40
+ return record?.typeName === "shape" && record.type === "arrow";
41
+ }
42
+
43
+ function isArrowLabelBindingRecord(
44
+ record: BindingRecord | null | undefined,
45
+ ): record is ArrowLabelBindingRecord {
46
+ return (
47
+ record?.typeName === "binding" && record.type === ARROW_LABEL_BINDING_TYPE
48
+ );
49
+ }
50
+
51
+ function clampNormalized(value: unknown, fallback: number): number {
52
+ return typeof value === "number" && Number.isFinite(value)
53
+ ? Math.min(1, Math.max(0, value))
54
+ : fallback;
55
+ }
56
+
57
+ export class ArrowLabelBindingUtil extends BindingUtil<ArrowLabelBindingRecord> {
58
+ readonly type = ARROW_LABEL_BINDING_TYPE;
59
+
60
+ getDefaultProps(): ArrowLabelBindingProps {
61
+ return {
62
+ labelPosition: 0.5,
63
+ };
64
+ }
65
+
66
+ validateProps(props: unknown): ArrowLabelBindingProps {
67
+ const fallback = this.getDefaultProps();
68
+ const candidate =
69
+ props && typeof props === "object"
70
+ ? (props as Partial<ArrowLabelBindingProps>)
71
+ : {};
72
+
73
+ return {
74
+ labelPosition: clampNormalized(
75
+ candidate.labelPosition,
76
+ fallback.labelPosition,
77
+ ),
78
+ };
79
+ }
80
+
81
+ canBind(
82
+ _editor: Editor,
83
+ _binding: ArrowLabelBindingRecord,
84
+ context: { fromShape: ShapeRecord; toShape: ShapeRecord },
85
+ ): boolean {
86
+ return (
87
+ isTextShapeRecord(context.fromShape) && isArrowShapeRecord(context.toShape)
88
+ );
89
+ }
90
+
91
+ getExclusiveBindingKey(binding: ArrowLabelBindingRecord): string {
92
+ return binding.toId;
93
+ }
94
+
95
+ onBindingCreate(
96
+ editor: Editor,
97
+ binding: ArrowLabelBindingRecord,
98
+ ): BindingUtilEffect | void {
99
+ return this.syncLabelPosition(editor, binding);
100
+ }
101
+
102
+ onBindingUpdate(
103
+ editor: Editor,
104
+ _before: ArrowLabelBindingRecord,
105
+ after: ArrowLabelBindingRecord,
106
+ ): BindingUtilEffect | void {
107
+ return this.syncLabelPosition(editor, after);
108
+ }
109
+
110
+ onBindingLoad(
111
+ editor: Editor,
112
+ binding: ArrowLabelBindingRecord,
113
+ ): BindingUtilEffect | void {
114
+ return this.syncLabelPosition(editor, binding);
115
+ }
116
+
117
+ getGeometryDependentShapeIds(
118
+ _editor: Editor,
119
+ binding: ArrowLabelBindingRecord,
120
+ context: { role: "from" | "to" },
121
+ ): ShapeRecord["id"][] {
122
+ return context.role === "to" ? [binding.fromId] : [];
123
+ }
124
+
125
+ onAfterShapeChange(
126
+ editor: Editor,
127
+ binding: ArrowLabelBindingRecord,
128
+ _context: { role: "from" | "to" },
129
+ ): BindingUtilEffect | void {
130
+ return this.syncLabelPosition(editor, binding);
131
+ }
132
+
133
+ onBeforeDeleteShape(
134
+ _editor: Editor,
135
+ binding: ArrowLabelBindingRecord,
136
+ context: { role: "from" | "to" },
137
+ ): BindingUtilEffect | void {
138
+ if (context.role === "from") {
139
+ return { deleteBindingIds: [binding.id] };
140
+ }
141
+
142
+ return {
143
+ deleteShapeIds: [binding.fromId],
144
+ deleteBindingIds: [binding.id],
145
+ };
146
+ }
147
+
148
+ onBeforeDeleteBinding(
149
+ _editor: Editor,
150
+ binding: ArrowLabelBindingRecord,
151
+ ): BindingUtilEffect | void {
152
+ return {
153
+ deleteBindingIds: [binding.id],
154
+ };
155
+ }
156
+
157
+ getExportShapeIds(
158
+ _editor: Editor,
159
+ binding: ArrowLabelBindingRecord,
160
+ ): TextShapeRecord["id"][] {
161
+ return [binding.fromId];
162
+ }
163
+
164
+ getSelectionCompanionFrameGeometries(
165
+ editor: Editor,
166
+ binding: ArrowLabelBindingRecord,
167
+ context: BindingSelectionCompanionFrameContext,
168
+ ): SelectionOverlayFrameGeometryState[] {
169
+ if (binding.toId !== context.selectedShape.id) {
170
+ return [];
171
+ }
172
+ if (
173
+ textLabelShowsCompanionFrameWhenOwnerSelected(editor, binding.fromId) !==
174
+ true
175
+ ) {
176
+ return [];
177
+ }
178
+ const label = editor.getShape(binding.fromId);
179
+ const owner = editor.getShape(binding.toId);
180
+ if (!isTextShapeRecord(label) || !isArrowShapeRecord(owner)) {
181
+ return [];
182
+ }
183
+
184
+ const geometry = editor.computeShapeGeometry(label);
185
+ return [
186
+ {
187
+ bounds: geometry.bounds,
188
+ rotation: label.rotation,
189
+ rotationCenter: geometry.center,
190
+ },
191
+ ];
192
+ }
193
+
194
+ private syncLabelPosition(
195
+ editor: Editor,
196
+ binding: ArrowLabelBindingRecord,
197
+ ): BindingUtilEffect | void {
198
+ if (!isArrowLabelBindingRecord(binding)) {
199
+ return;
200
+ }
201
+
202
+ const label = editor.getShape(binding.fromId);
203
+ const owner = editor.getShape(binding.toId);
204
+ if (!isTextShapeRecord(label) || !isArrowShapeRecord(owner)) {
205
+ return { deleteBindingIds: [binding.id] };
206
+ }
207
+
208
+ const labelUpdate = getArrowLabelUpdate(editor, label, owner);
209
+ return labelUpdate
210
+ ? {
211
+ shapes: [
212
+ {
213
+ ...label,
214
+ ...labelUpdate,
215
+ props: {
216
+ ...label.props,
217
+ ...labelUpdate.props,
218
+ },
219
+ },
220
+ ],
221
+ }
222
+ : undefined;
223
+ }
224
+ }
225
+
226
+ function getArrowLabelUpdate(
227
+ editor: Editor,
228
+ label: TextShapeRecord,
229
+ owner: ShapeRecord,
230
+ ): ShapeUpdate | null {
231
+ const nextLayout = getArrowLabelLayout(editor, label, owner);
232
+ const nextParentId = owner.parentId;
233
+ const nextIndex = `${owner.index}${ARROW_LABEL_INDEX_SUFFIX}`;
234
+ const nextAutoSize = false;
235
+ const nextTextAlign = "middle" as const;
236
+
237
+ if (
238
+ label.x === nextLayout.x &&
239
+ label.y === nextLayout.y &&
240
+ label.rotation === nextLayout.rotation &&
241
+ label.parentId === nextParentId &&
242
+ label.index === nextIndex &&
243
+ label.props.w === nextLayout.props.w &&
244
+ label.props.autoSize === nextAutoSize &&
245
+ label.props.textAlign === nextTextAlign
246
+ ) {
247
+ return null;
248
+ }
249
+
250
+ return {
251
+ id: label.id,
252
+ x: nextLayout.x,
253
+ y: nextLayout.y,
254
+ rotation: nextLayout.rotation,
255
+ parentId: nextParentId,
256
+ index: nextIndex,
257
+ props: {
258
+ w: nextLayout.props.w,
259
+ autoSize: nextAutoSize,
260
+ textAlign: nextTextAlign,
261
+ },
262
+ };
263
+ }
264
+
265
+ export function getArrowLabelLayout(
266
+ editor: Editor,
267
+ label: TextShapeRecord,
268
+ owner: ShapeRecord,
269
+ ): Pick<TextShapeRecord, "x" | "y" | "rotation"> & {
270
+ props: Pick<TextShapeRecord["props"], "w">;
271
+ } {
272
+ const ownerCenter = editor.computeShapePageCenter(owner);
273
+ const labelCenter = getArrowBodyMiddlePagePoint(owner) ?? ownerCenter;
274
+ const nextW = Math.max(ARROW_LABEL_MIN_WIDTH, ARROW_LABEL_WIDTH);
275
+ return {
276
+ x: labelCenter.x - (nextW * label.props.scale) / 2,
277
+ y: labelCenter.y - (label.props.h * label.props.scale) / 2,
278
+ rotation: owner.rotation,
279
+ props: {
280
+ w: nextW,
281
+ },
282
+ };
283
+ }
284
+
285
+ function getArrowBodyMiddlePagePoint(owner: ShapeRecord): Vec | null {
286
+ const props = owner.props as Record<string, unknown>;
287
+ const start = getPointProp(props.start);
288
+ const end = getPointProp(props.end);
289
+ const bend = getNumberProp(props.bend) ?? 0;
290
+ if (!start || !end) {
291
+ return null;
292
+ }
293
+
294
+ const localMiddle = Vec.Med(start, end).add(
295
+ end.sub(start).len()
296
+ ? end.sub(start).per().normalize().mul(-bend)
297
+ : new Vec(0, bend),
298
+ );
299
+ const ownerCenter = new Vec(owner.x + getOwnerW(owner) / 2, owner.y + getOwnerH(owner) / 2);
300
+ const pagePoint = new Vec(owner.x + localMiddle.x, owner.y + localMiddle.y);
301
+ if (owner.rotation === 0 && owner.scaleX === 1 && owner.scaleY === 1) {
302
+ return pagePoint;
303
+ }
304
+
305
+ const offset = pagePoint.sub(ownerCenter);
306
+ return ownerCenter.add(
307
+ new Vec(offset.x * owner.scaleX, offset.y * owner.scaleY).rot(
308
+ owner.rotation,
309
+ ),
310
+ );
311
+ }
312
+
313
+ function getPointProp(value: unknown): Vec | null {
314
+ if (!value || typeof value !== "object") {
315
+ return null;
316
+ }
317
+ const point = value as { x?: unknown; y?: unknown };
318
+ return typeof point.x === "number" &&
319
+ Number.isFinite(point.x) &&
320
+ typeof point.y === "number" &&
321
+ Number.isFinite(point.y)
322
+ ? new Vec(point.x, point.y)
323
+ : null;
324
+ }
325
+
326
+ function getNumberProp(value: unknown): number | null {
327
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
328
+ }
329
+
330
+ function getOwnerW(owner: ShapeRecord): number {
331
+ return getNumberProp((owner.props as Record<string, unknown>).w) ?? 1;
332
+ }
333
+
334
+ function getOwnerH(owner: ShapeRecord): number {
335
+ return getNumberProp((owner.props as Record<string, unknown>).h) ?? 1;
336
+ }
@@ -0,0 +1,391 @@
1
+ import {
2
+ BindingUtil,
3
+ type BindingRecord,
4
+ type BindingRecordBase,
5
+ type BindingUtilEffect,
6
+ type Editor,
7
+ type ShapeUpdate,
8
+ type ShapeRecord,
9
+ } from "@land/canvas-core";
10
+ import { GEO_LABEL_BINDING_TYPE } from "./textLabelHelpers";
11
+ import { isTextShapeRecord } from "./textShapeHelpers";
12
+ import type { TextShapeRecord } from "./textTypes";
13
+
14
+ export const GEO_LABEL_EDGE_PADDING = 16;
15
+ export const GEO_LABEL_MIN_WIDTH = 16;
16
+ export const GEO_LABEL_INDEX_SUFFIX = ".label";
17
+
18
+ export interface GeoLabelBindingProps {
19
+ normalizedAnchor: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ ownerBaseHeight: number;
24
+ }
25
+
26
+ export interface GeoLabelBindingRecord
27
+ extends BindingRecordBase<typeof GEO_LABEL_BINDING_TYPE, GeoLabelBindingProps> {
28
+ type: typeof GEO_LABEL_BINDING_TYPE;
29
+ props: GeoLabelBindingProps;
30
+ }
31
+
32
+ function isGeoShapeRecord(
33
+ record: ShapeRecord | null | undefined,
34
+ ): record is ShapeRecord {
35
+ return record?.typeName === "shape" && record.type === "geo";
36
+ }
37
+
38
+ function isGeoLabelBindingRecord(
39
+ record: BindingRecord | null | undefined,
40
+ ): record is GeoLabelBindingRecord {
41
+ return (
42
+ record?.typeName === "binding" && record.type === GEO_LABEL_BINDING_TYPE
43
+ );
44
+ }
45
+
46
+ function clampNormalized(value: unknown, fallback: number): number {
47
+ return typeof value === "number" && Number.isFinite(value)
48
+ ? Math.min(1, Math.max(0, value))
49
+ : fallback;
50
+ }
51
+
52
+ export class GeoLabelBindingUtil extends BindingUtil<GeoLabelBindingRecord> {
53
+ readonly type = GEO_LABEL_BINDING_TYPE;
54
+
55
+ getDefaultProps(): GeoLabelBindingProps {
56
+ return {
57
+ normalizedAnchor: { x: 0.5, y: 0.5 },
58
+ ownerBaseHeight: 0,
59
+ };
60
+ }
61
+
62
+ validateProps(props: unknown): GeoLabelBindingProps {
63
+ const fallback = this.getDefaultProps();
64
+ const candidate =
65
+ props && typeof props === "object"
66
+ ? (props as Partial<GeoLabelBindingProps>)
67
+ : {};
68
+ const anchor =
69
+ candidate.normalizedAnchor &&
70
+ typeof candidate.normalizedAnchor === "object"
71
+ ? candidate.normalizedAnchor
72
+ : fallback.normalizedAnchor;
73
+
74
+ return {
75
+ normalizedAnchor: {
76
+ x: clampNormalized(anchor.x, fallback.normalizedAnchor.x),
77
+ y: clampNormalized(anchor.y, fallback.normalizedAnchor.y),
78
+ },
79
+ ownerBaseHeight: clampPositiveNumber(
80
+ candidate.ownerBaseHeight,
81
+ fallback.ownerBaseHeight,
82
+ ),
83
+ };
84
+ }
85
+
86
+ canBind(
87
+ _editor: Editor,
88
+ _binding: GeoLabelBindingRecord,
89
+ context: { fromShape: ShapeRecord; toShape: ShapeRecord },
90
+ ): boolean {
91
+ return (
92
+ isTextShapeRecord(context.fromShape) && isGeoShapeRecord(context.toShape)
93
+ );
94
+ }
95
+
96
+ getExclusiveBindingKey(binding: GeoLabelBindingRecord): string {
97
+ return binding.toId;
98
+ }
99
+
100
+ onBindingCreate(
101
+ editor: Editor,
102
+ binding: GeoLabelBindingRecord,
103
+ ): BindingUtilEffect | void {
104
+ return this.syncLabelPosition(editor, binding);
105
+ }
106
+
107
+ onBindingUpdate(
108
+ editor: Editor,
109
+ before: GeoLabelBindingRecord,
110
+ after: GeoLabelBindingRecord,
111
+ ): BindingUtilEffect | void {
112
+ return this.syncLabelPosition(
113
+ editor,
114
+ after,
115
+ before.toId === after.toId ? undefined : { role: "to" },
116
+ );
117
+ }
118
+
119
+ onBindingLoad(
120
+ editor: Editor,
121
+ binding: GeoLabelBindingRecord,
122
+ ): BindingUtilEffect | void {
123
+ return this.syncLabelPosition(editor, binding);
124
+ }
125
+
126
+ getGeometryDependentShapeIds(
127
+ _editor: Editor,
128
+ binding: GeoLabelBindingRecord,
129
+ context: { role: "from" | "to" },
130
+ ): ShapeRecord["id"][] {
131
+ return context.role === "from" ? [binding.toId] : [binding.fromId];
132
+ }
133
+
134
+ onAfterShapeChange(
135
+ editor: Editor,
136
+ binding: GeoLabelBindingRecord,
137
+ context: { role: "from" | "to" },
138
+ ): BindingUtilEffect | void {
139
+ return this.syncLabelPosition(editor, binding, context);
140
+ }
141
+
142
+ onBeforeDeleteShape(
143
+ editor: Editor,
144
+ binding: GeoLabelBindingRecord,
145
+ context: { role: "from" | "to" },
146
+ ): BindingUtilEffect | void {
147
+ if (context.role === "from") {
148
+ const owner = editor.getShape(binding.toId);
149
+ const ownerHeightReset = owner
150
+ ? getGeoLabelOwnerHeightReset(owner, binding)
151
+ : null;
152
+ return {
153
+ shapes: ownerHeightReset ? [ownerHeightReset] : undefined,
154
+ deleteBindingIds: [binding.id],
155
+ };
156
+ }
157
+
158
+ return {
159
+ deleteShapeIds: [binding.fromId],
160
+ deleteBindingIds: [binding.id],
161
+ };
162
+ }
163
+
164
+ onBeforeDeleteBinding(
165
+ editor: Editor,
166
+ binding: GeoLabelBindingRecord,
167
+ ): BindingUtilEffect | void {
168
+ const owner = editor.getShape(binding.toId);
169
+ const ownerHeightReset = owner
170
+ ? getGeoLabelOwnerHeightReset(owner, binding)
171
+ : null;
172
+ return {
173
+ shapes: ownerHeightReset ? [ownerHeightReset] : undefined,
174
+ deleteBindingIds: [binding.id],
175
+ };
176
+ }
177
+
178
+ getExportShapeIds(
179
+ _editor: Editor,
180
+ binding: GeoLabelBindingRecord,
181
+ ): TextShapeRecord["id"][] {
182
+ return [binding.fromId];
183
+ }
184
+
185
+ private syncLabelPosition(
186
+ editor: Editor,
187
+ binding: GeoLabelBindingRecord,
188
+ context?: { role: "from" | "to" },
189
+ ): BindingUtilEffect | void {
190
+ if (!isGeoLabelBindingRecord(binding)) {
191
+ return;
192
+ }
193
+
194
+ const label = editor.getShape(binding.fromId);
195
+ const owner = editor.getShape(binding.toId);
196
+ if (!isTextShapeRecord(label) || !isGeoShapeRecord(owner)) {
197
+ return { deleteBindingIds: [binding.id] };
198
+ }
199
+
200
+ const ownerBaseHeight = getGeoLabelOwnerBaseHeight(
201
+ binding,
202
+ owner,
203
+ context,
204
+ );
205
+ const nextBinding =
206
+ binding.props.ownerBaseHeight === ownerBaseHeight
207
+ ? null
208
+ : {
209
+ ...binding,
210
+ props: {
211
+ ...binding.props,
212
+ ownerBaseHeight,
213
+ },
214
+ };
215
+ const nextOwner = getGeoLabelOwnerUpdate(owner, label, ownerBaseHeight);
216
+ const ownerForLabelLayout = nextOwner ?? owner;
217
+ const labelUpdate = getGeoLabelUpdate(editor, label, ownerForLabelLayout);
218
+ const shapes: ShapeRecord[] = [];
219
+
220
+ if (nextOwner) {
221
+ shapes.push(nextOwner);
222
+ }
223
+ if (labelUpdate) {
224
+ shapes.push({
225
+ ...label,
226
+ ...labelUpdate,
227
+ props: {
228
+ ...label.props,
229
+ ...labelUpdate.props,
230
+ },
231
+ });
232
+ }
233
+
234
+ return shapes.length > 0 || nextBinding
235
+ ? {
236
+ shapes: shapes.length > 0 ? shapes : undefined,
237
+ bindings: nextBinding ? [nextBinding] : undefined,
238
+ }
239
+ : undefined;
240
+ }
241
+ }
242
+
243
+ function clampPositiveNumber(value: unknown, fallback: number): number {
244
+ return typeof value === "number" && Number.isFinite(value) && value > 0
245
+ ? value
246
+ : fallback;
247
+ }
248
+
249
+ function getShapePropNumber(
250
+ shape: ShapeRecord,
251
+ key: string,
252
+ ): number | null {
253
+ const value = (shape.props as Record<string, unknown>)[key];
254
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
255
+ }
256
+
257
+ function getGeoLabelOwnerBaseHeight(
258
+ binding: GeoLabelBindingRecord,
259
+ owner: ShapeRecord,
260
+ context?: { role: "from" | "to" },
261
+ ): number {
262
+ const ownerHeight = getShapePropNumber(owner, "h") ?? 1;
263
+ if (context?.role === "to") {
264
+ return ownerHeight;
265
+ }
266
+ return binding.props.ownerBaseHeight > 0
267
+ ? binding.props.ownerBaseHeight
268
+ : ownerHeight;
269
+ }
270
+
271
+ function getGeoLabelRequiredOwnerHeight(label: TextShapeRecord): number {
272
+ return Math.max(
273
+ 1,
274
+ label.props.h * label.props.scale + GEO_LABEL_EDGE_PADDING * 2,
275
+ );
276
+ }
277
+
278
+ function getGeoLabelOwnerUpdate(
279
+ owner: ShapeRecord,
280
+ label: TextShapeRecord,
281
+ ownerBaseHeight: number,
282
+ ): ShapeRecord | null {
283
+ const ownerHeight = getShapePropNumber(owner, "h");
284
+ if (!ownerHeight) {
285
+ return null;
286
+ }
287
+
288
+ const nextHeight = Math.max(
289
+ ownerBaseHeight,
290
+ getGeoLabelRequiredOwnerHeight(label),
291
+ );
292
+ if (ownerHeight === nextHeight) {
293
+ return null;
294
+ }
295
+
296
+ return {
297
+ ...owner,
298
+ props: {
299
+ ...owner.props,
300
+ h: nextHeight,
301
+ },
302
+ };
303
+ }
304
+
305
+ function getGeoLabelOwnerHeightReset(
306
+ owner: ShapeRecord,
307
+ binding: GeoLabelBindingRecord,
308
+ ): ShapeRecord | null {
309
+ const ownerHeight = getShapePropNumber(owner, "h");
310
+ if (!ownerHeight || binding.props.ownerBaseHeight <= 0) {
311
+ return null;
312
+ }
313
+
314
+ if (ownerHeight === binding.props.ownerBaseHeight) {
315
+ return null;
316
+ }
317
+
318
+ return {
319
+ ...owner,
320
+ props: {
321
+ ...owner.props,
322
+ h: binding.props.ownerBaseHeight,
323
+ },
324
+ };
325
+ }
326
+
327
+ function getGeoLabelUpdate(
328
+ editor: Editor,
329
+ label: TextShapeRecord,
330
+ owner: ShapeRecord,
331
+ ): ShapeUpdate | null {
332
+ const nextLayout = getGeoLabelLayout(editor, label, owner);
333
+ const nextParentId = owner.parentId;
334
+ const nextIndex = `${owner.index}${GEO_LABEL_INDEX_SUFFIX}`;
335
+ const nextAutoSize = false;
336
+ const nextTextAlign = "middle" as const;
337
+
338
+ if (
339
+ label.x === nextLayout.x &&
340
+ label.y === nextLayout.y &&
341
+ label.rotation === nextLayout.rotation &&
342
+ label.parentId === nextParentId &&
343
+ label.index === nextIndex &&
344
+ label.props.w === nextLayout.props.w &&
345
+ label.props.autoSize === nextAutoSize &&
346
+ label.props.textAlign === nextTextAlign
347
+ ) {
348
+ return null;
349
+ }
350
+
351
+ return {
352
+ id: label.id,
353
+ x: nextLayout.x,
354
+ y: nextLayout.y,
355
+ rotation: nextLayout.rotation,
356
+ parentId: nextParentId,
357
+ index: nextIndex,
358
+ props: {
359
+ w: nextLayout.props.w,
360
+ autoSize: nextAutoSize,
361
+ textAlign: nextTextAlign,
362
+ },
363
+ };
364
+ }
365
+
366
+ export function getGeoLabelLayout(
367
+ editor: Editor,
368
+ label: TextShapeRecord,
369
+ owner: ShapeRecord,
370
+ ): Pick<TextShapeRecord, "x" | "y" | "rotation"> & {
371
+ props: Pick<TextShapeRecord["props"], "w">;
372
+ } {
373
+ const ownerBounds = editor.computeShapeLocalBounds(owner);
374
+ const ownerCenter = editor.computeShapePageCenter(owner);
375
+ const nextW = Math.max(
376
+ GEO_LABEL_MIN_WIDTH,
377
+ ownerBounds.w - GEO_LABEL_EDGE_PADDING * 2,
378
+ );
379
+ const nextX = ownerCenter.x - (nextW * label.props.scale) / 2;
380
+ const nextY = ownerCenter.y - (label.props.h * label.props.scale) / 2;
381
+ const nextRotation = owner.rotation;
382
+
383
+ return {
384
+ x: nextX,
385
+ y: nextY,
386
+ rotation: nextRotation,
387
+ props: {
388
+ w: nextW,
389
+ },
390
+ };
391
+ }