@accelint/map-toolkit 0.6.0 → 1.1.0

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 (207) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/catalog-info.yaml +7 -6
  3. package/dist/camera/events.js.map +1 -1
  4. package/dist/camera/index.d.ts +2 -2
  5. package/dist/camera/index.js +2 -2
  6. package/dist/camera/store.d.ts +120 -0
  7. package/dist/camera/store.js +279 -0
  8. package/dist/camera/store.js.map +1 -0
  9. package/dist/cursor-coordinates/index.d.ts +4 -2
  10. package/dist/cursor-coordinates/index.js +3 -2
  11. package/dist/cursor-coordinates/store.d.ts +48 -0
  12. package/dist/cursor-coordinates/store.js +92 -0
  13. package/dist/cursor-coordinates/store.js.map +1 -0
  14. package/dist/cursor-coordinates/types.d.ts +87 -0
  15. package/dist/cursor-coordinates/types.js +12 -0
  16. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +41 -37
  17. package/dist/cursor-coordinates/use-cursor-coordinates.js +131 -202
  18. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  19. package/dist/deckgl/base-map/constants.d.ts +1 -6
  20. package/dist/deckgl/base-map/constants.js +1 -6
  21. package/dist/deckgl/base-map/constants.js.map +1 -1
  22. package/dist/deckgl/base-map/controls.js +2 -0
  23. package/dist/deckgl/base-map/controls.js.map +1 -1
  24. package/dist/deckgl/base-map/events.js.map +1 -1
  25. package/dist/deckgl/base-map/index.d.ts +2 -2
  26. package/dist/deckgl/base-map/index.js +10 -11
  27. package/dist/deckgl/base-map/index.js.map +1 -1
  28. package/dist/deckgl/base-map/provider.d.ts +2 -2
  29. package/dist/deckgl/base-map/provider.js +1 -1
  30. package/dist/deckgl/base-map/provider.js.map +1 -1
  31. package/dist/deckgl/index.d.ts +4 -4
  32. package/dist/deckgl/index.js +4 -4
  33. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  34. package/dist/deckgl/saved-viewports/storage.js +10 -2
  35. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  36. package/dist/deckgl/shapes/display-shape-layer/constants.js +5 -8
  37. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  38. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
  39. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +18 -14
  40. package/dist/deckgl/shapes/display-shape-layer/index.js +63 -30
  41. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  42. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +2 -16
  43. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js.map +1 -1
  44. package/dist/deckgl/shapes/display-shape-layer/store.js +58 -272
  45. package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
  46. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +22 -11
  47. package/dist/deckgl/shapes/display-shape-layer/{use-shape-selection.d.ts → use-select-shape.d.ts} +9 -9
  48. package/dist/deckgl/shapes/display-shape-layer/{use-shape-selection.js → use-select-shape.js} +12 -12
  49. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js.map +1 -0
  50. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +5 -66
  51. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  52. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +2 -65
  53. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +3 -121
  54. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  55. package/dist/deckgl/shapes/draw-shape-layer/constants.js +46 -0
  56. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -0
  57. package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +92 -0
  58. package/dist/deckgl/shapes/draw-shape-layer/events.js +56 -0
  59. package/dist/deckgl/shapes/draw-shape-layer/events.js.map +1 -0
  60. package/dist/deckgl/shapes/draw-shape-layer/fiber.d.ts +11 -0
  61. package/dist/{maplibre/constants.js → deckgl/shapes/draw-shape-layer/fiber.js} +6 -12
  62. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -0
  63. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +53 -0
  64. package/dist/deckgl/shapes/draw-shape-layer/index.js +95 -0
  65. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -0
  66. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +51 -0
  67. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -0
  68. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +73 -0
  69. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -0
  70. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +87 -0
  71. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -0
  72. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +88 -0
  73. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -0
  74. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +77 -0
  75. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -0
  76. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +64 -0
  77. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -0
  78. package/dist/deckgl/shapes/draw-shape-layer/store.js +175 -0
  79. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -0
  80. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +86 -0
  81. package/dist/{viewport/constants.js → deckgl/shapes/draw-shape-layer/types.js} +1 -12
  82. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +82 -0
  83. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +112 -0
  84. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js.map +1 -0
  85. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +147 -0
  86. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -0
  87. package/dist/deckgl/shapes/edit-shape-layer/constants.js +41 -0
  88. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -0
  89. package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +92 -0
  90. package/dist/deckgl/shapes/edit-shape-layer/events.js +56 -0
  91. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -0
  92. package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +13 -0
  93. package/dist/deckgl/shapes/edit-shape-layer/fiber.js +14 -0
  94. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +63 -0
  95. package/dist/deckgl/shapes/edit-shape-layer/index.js +162 -0
  96. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -0
  97. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +154 -0
  98. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -0
  99. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +147 -0
  100. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -0
  101. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +87 -0
  102. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -0
  103. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +61 -0
  104. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -0
  105. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +109 -0
  106. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -0
  107. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +289 -0
  108. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -0
  109. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +121 -0
  110. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -0
  111. package/dist/deckgl/shapes/edit-shape-layer/store.js +194 -0
  112. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -0
  113. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +93 -0
  114. package/dist/deckgl/shapes/edit-shape-layer/types.js +14 -0
  115. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +82 -0
  116. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +114 -0
  117. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -0
  118. package/dist/deckgl/shapes/index.d.ts +15 -6
  119. package/dist/deckgl/shapes/index.js +12 -5
  120. package/dist/deckgl/shapes/shared/constants.d.ts +27 -32
  121. package/dist/deckgl/shapes/shared/constants.js +189 -25
  122. package/dist/deckgl/shapes/shared/constants.js.map +1 -1
  123. package/dist/deckgl/shapes/shared/events.d.ts +1 -20
  124. package/dist/deckgl/shapes/shared/events.js +1 -31
  125. package/dist/deckgl/shapes/shared/events.js.map +1 -1
  126. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +84 -0
  127. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -0
  128. package/dist/deckgl/shapes/shared/types.d.ts +187 -28
  129. package/dist/deckgl/shapes/shared/types.js +55 -1
  130. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  131. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +128 -0
  132. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -0
  133. package/dist/deckgl/shapes/shared/utils/layer-config.js +50 -0
  134. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -0
  135. package/dist/deckgl/shapes/shared/utils/mode-utils.js +113 -0
  136. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -0
  137. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +57 -0
  138. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -0
  139. package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +64 -0
  140. package/dist/deckgl/shapes/shared/utils/style-utils.js +101 -0
  141. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -0
  142. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  143. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  144. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  145. package/dist/deckgl/text-layer/default-settings.js +4 -24
  146. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  147. package/dist/deckgl/text-layer/fiber.js.map +1 -1
  148. package/dist/deckgl/text-layer/index.js.map +1 -1
  149. package/dist/deckgl/text-settings.d.ts +77 -0
  150. package/dist/deckgl/text-settings.js +83 -0
  151. package/dist/deckgl/text-settings.js.map +1 -0
  152. package/dist/map-cursor/events.js.map +1 -1
  153. package/dist/map-cursor/index.d.ts +2 -2
  154. package/dist/map-cursor/index.js +2 -2
  155. package/dist/map-cursor/store.d.ts +32 -61
  156. package/dist/map-cursor/store.js +165 -294
  157. package/dist/map-cursor/store.js.map +1 -1
  158. package/dist/map-cursor/use-map-cursor.d.ts +5 -2
  159. package/dist/map-cursor/use-map-cursor.js +33 -15
  160. package/dist/map-cursor/use-map-cursor.js.map +1 -1
  161. package/dist/map-mode/events.js.map +1 -1
  162. package/dist/map-mode/index.d.ts +2 -2
  163. package/dist/map-mode/index.js +2 -2
  164. package/dist/map-mode/store.d.ts +36 -37
  165. package/dist/map-mode/store.js +131 -237
  166. package/dist/map-mode/store.js.map +1 -1
  167. package/dist/map-mode/use-map-mode.js +6 -5
  168. package/dist/map-mode/use-map-mode.js.map +1 -1
  169. package/dist/maplibre/hooks/use-maplibre.js.map +1 -1
  170. package/dist/maplibre/index.d.ts +2 -2
  171. package/dist/maplibre/index.js +2 -2
  172. package/dist/shared/constants.d.ts +19 -0
  173. package/dist/shared/constants.js +33 -0
  174. package/dist/shared/constants.js.map +1 -0
  175. package/dist/shared/create-map-store.d.ts +202 -0
  176. package/dist/shared/create-map-store.js +223 -0
  177. package/dist/shared/create-map-store.js.map +1 -0
  178. package/dist/shared/units.d.ts +39 -0
  179. package/dist/shared/units.js +49 -0
  180. package/dist/shared/units.js.map +1 -0
  181. package/dist/viewport/index.d.ts +3 -3
  182. package/dist/viewport/index.js +3 -3
  183. package/dist/viewport/store.d.ts +69 -0
  184. package/dist/viewport/store.js +125 -0
  185. package/dist/viewport/store.js.map +1 -0
  186. package/dist/viewport/types.d.ts +2 -2
  187. package/dist/viewport/utils.js +2 -2
  188. package/dist/viewport/utils.js.map +1 -1
  189. package/dist/viewport/viewport-size.d.ts +2 -2
  190. package/dist/viewport/viewport-size.js +2 -2
  191. package/dist/viewport/viewport-size.js.map +1 -1
  192. package/package.json +39 -19
  193. package/dist/camera/use-camera-state.d.ts +0 -153
  194. package/dist/camera/use-camera-state.js +0 -418
  195. package/dist/camera/use-camera-state.js.map +0 -1
  196. package/dist/deckgl/shapes/display-shape-layer/constants.d.ts +0 -44
  197. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.d.ts +0 -66
  198. package/dist/deckgl/shapes/display-shape-layer/store.d.ts +0 -87
  199. package/dist/deckgl/shapes/display-shape-layer/use-shape-selection.js.map +0 -1
  200. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.d.ts +0 -61
  201. package/dist/maplibre/constants.d.ts +0 -13
  202. package/dist/maplibre/constants.js.map +0 -1
  203. package/dist/viewport/constants.d.ts +0 -11
  204. package/dist/viewport/constants.js.map +0 -1
  205. package/dist/viewport/use-viewport-state.d.ts +0 -100
  206. package/dist/viewport/use-viewport-state.js +0 -222
  207. package/dist/viewport/use-viewport-state.js.map +0 -1
@@ -0,0 +1,289 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { ScaleMode } from "@deck.gl-community/editable-layers";
15
+
16
+ //#region src/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.ts
17
+ /**
18
+ * Extends ScaleMode to support non-uniform (free) scaling.
19
+ *
20
+ * ## Features
21
+ * - Default: Free scaling - can stretch/squish in any direction
22
+ * - With modeConfig.lockScaling = true: Uniform scaling (maintains aspect ratio)
23
+ *
24
+ * ## How Non-Uniform Scaling Works
25
+ *
26
+ * Non-uniform scaling applies separate X and Y scale factors based on cursor
27
+ * movement relative to the opposite corner (the "origin" or anchor point).
28
+ *
29
+ * ```
30
+ * Origin (opposite corner) Drag Handle (start)
31
+ * ●───────────────────────────────────●
32
+ * │ │
33
+ * │ startDelta = start - origin │
34
+ * │ currentDelta = current - origin
35
+ * │ │
36
+ * │ scaleX = currentDeltaX / startDeltaX
37
+ * │ scaleY = currentDeltaY / startDeltaY
38
+ * │ │
39
+ * ●───────────────────────────────────● Cursor (current)
40
+ * ```
41
+ *
42
+ * Each coordinate is transformed: `newCoord = origin + (oldCoord - origin) × scale`
43
+ *
44
+ * ## Why We Override Parent's Uniform Scaling
45
+ *
46
+ * The parent ScaleMode calculates uniform scale factors in screen coordinates,
47
+ * which can distort rotated shapes. We calculate our own uniform factor using
48
+ * vector projection to ensure the corner follows the cursor's projection onto
49
+ * the original drag line, preserving shape orientation.
50
+ *
51
+ * ## Minimum Scale Clamping
52
+ *
53
+ * All scale factors are clamped to a minimum of 0.01 to prevent:
54
+ * - Shape inversion (negative scale flipping the shape inside-out)
55
+ * - Shape collapse (scale of 0 making the shape a point/line)
56
+ */
57
+ var ScaleModeWithFreeTransform = class extends ScaleMode {
58
+ /**
59
+ * Override handleDragging to support non-uniform scaling.
60
+ * When lockScaling is false (default), applies separate X/Y scale factors.
61
+ * When lockScaling is true, applies uniform scaling (same factor for X and Y).
62
+ *
63
+ * Note: We don't use parent's handleDragging for uniform scaling because it
64
+ * calculates scale factors in screen coordinates which distorts rotated shapes.
65
+ * Instead, we calculate our own uniform scale factor based on distance ratios.
66
+ */
67
+ handleDragging(event, props) {
68
+ const self = this;
69
+ if (!self._isScaling) return;
70
+ props.onUpdateCursor(self._cursor);
71
+ const scaleContext = this.getScaleContext(event, self);
72
+ if (!scaleContext) return;
73
+ const lockScaling = props.modeConfig?.lockScaling ?? false;
74
+ const { origin, scaleFactors, geometry } = scaleContext;
75
+ const finalScaleX = lockScaling ? this.calculateUniformScaleFactor(event, origin) : scaleFactors.scaleX;
76
+ const finalScaleY = lockScaling ? this.calculateUniformScaleFactor(event, origin) : scaleFactors.scaleY;
77
+ const scaledFeatures = this.applyNonUniformScale(geometry, finalScaleX, finalScaleY, origin);
78
+ const updatedData = self._getUpdatedData(props, scaledFeatures);
79
+ props.onEdit({
80
+ updatedData,
81
+ editType: "scaling",
82
+ editContext: { featureIndexes: props.selectedIndexes }
83
+ });
84
+ event.cancelPan();
85
+ }
86
+ /**
87
+ * Override handleStopDragging to emit the final scaled geometry.
88
+ * Uses the same uniform/non-uniform scaling logic as handleDragging.
89
+ */
90
+ handleStopDragging(event, props) {
91
+ const self = this;
92
+ if (self._isScaling) {
93
+ this.emitFinalScaledGeometry(event, props, self);
94
+ this.resetScaleState(props, self);
95
+ }
96
+ }
97
+ /**
98
+ * Get the scale context (origin, scale factors, geometry) for the current drag.
99
+ * Returns null if required data is not available.
100
+ */
101
+ getScaleContext(event, self) {
102
+ if (!self._selectedEditHandle) return null;
103
+ const oppositeHandle = self._getOppositeScaleHandle(self._selectedEditHandle);
104
+ if (!oppositeHandle) return null;
105
+ const geometry = self._geometryBeingScaled;
106
+ if (!geometry) return null;
107
+ const origin = oppositeHandle.geometry.coordinates;
108
+ return {
109
+ origin,
110
+ scaleFactors: this.calculateScaleFactors(event, origin),
111
+ geometry
112
+ };
113
+ }
114
+ /**
115
+ * Calculate separate X and Y scale factors based on cursor movement.
116
+ *
117
+ * ## Math Explanation
118
+ *
119
+ * For each axis, we compute: `scale = currentDelta / startDelta`
120
+ *
121
+ * Where:
122
+ * - `startDelta` = distance from origin to where drag started (the handle position)
123
+ * - `currentDelta` = distance from origin to current cursor position
124
+ *
125
+ * Example: If the handle started 100px from origin and cursor is now 150px from origin,
126
+ * scale = 150/100 = 1.5 (shape grows to 150% of original size along that axis).
127
+ *
128
+ * ## Edge Cases
129
+ * - If `startDelta` is near zero (handle very close to origin), we return scale=1
130
+ * to avoid division by zero and prevent erratic behavior
131
+ * - Negative scale values are clamped to 0.01 minimum to prevent shape inversion
132
+ */
133
+ calculateScaleFactors(event, origin) {
134
+ const startDragPoint = event.pointerDownMapCoords;
135
+ const currentPoint = event.mapCoords;
136
+ const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);
137
+ const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);
138
+ const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);
139
+ const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);
140
+ const epsilon = 1e-7;
141
+ const minScale = .01;
142
+ const rawScaleX = Math.abs(startDeltaX) > epsilon ? currentDeltaX / startDeltaX : 1;
143
+ const rawScaleY = Math.abs(startDeltaY) > epsilon ? currentDeltaY / startDeltaY : 1;
144
+ return {
145
+ scaleX: Math.max(rawScaleX, minScale),
146
+ scaleY: Math.max(rawScaleY, minScale)
147
+ };
148
+ }
149
+ /**
150
+ * Calculate a uniform scale factor for aspect-ratio-preserving scaling.
151
+ *
152
+ * ## Why Use Projection Instead of Simple Distance Ratio?
153
+ *
154
+ * A naive approach would be: `scale = distance(origin, cursor) / distance(origin, start)`
155
+ *
156
+ * But this fails for diagonal movements - if you drag a corner handle and move
157
+ * perpendicular to the diagonal, the simple distance changes even though you
158
+ * don't want the shape to scale.
159
+ *
160
+ * ## Vector Projection Math
161
+ *
162
+ * Instead, we project the cursor position onto the line defined by
163
+ * (origin → start drag point). This way, only movement along the original
164
+ * drag direction affects the scale.
165
+ *
166
+ * ```
167
+ * Origin Start (drag handle)
168
+ * ●──────────────────●
169
+ * \
170
+ * \ Cursor moved diagonally
171
+ * ●
172
+ * /
173
+ * / Projected point (what we measure from)
174
+ * ●──────────────────●
175
+ * Origin Projection
176
+ * ```
177
+ *
178
+ * The projection formula uses the dot product:
179
+ * ```
180
+ * projectedDist = (current · start) / |start|
181
+ * scale = projectedDist / |start|
182
+ * ```
183
+ *
184
+ * This equals: `scale = (current · start) / |start|²`
185
+ */
186
+ calculateUniformScaleFactor(event, origin) {
187
+ const startDragPoint = event.pointerDownMapCoords;
188
+ const currentPoint = event.mapCoords;
189
+ const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);
190
+ const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);
191
+ const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);
192
+ const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);
193
+ const startDist = Math.sqrt(startDeltaX ** 2 + startDeltaY ** 2);
194
+ if (startDist < 1e-7) return 1;
195
+ const projectedDist = (currentDeltaX * startDeltaX + currentDeltaY * startDeltaY) / startDist;
196
+ const minScale = .01;
197
+ const rawScale = projectedDist / startDist;
198
+ return Math.max(rawScale, minScale);
199
+ }
200
+ /**
201
+ * Emit the final scaled geometry when dragging stops.
202
+ */
203
+ emitFinalScaledGeometry(event, props, self) {
204
+ const scaleContext = this.getScaleContext(event, self);
205
+ if (!scaleContext) return;
206
+ const lockScaling = props.modeConfig?.lockScaling ?? false;
207
+ const { origin, scaleFactors, geometry } = scaleContext;
208
+ const finalScaleX = lockScaling ? this.calculateUniformScaleFactor(event, origin) : scaleFactors.scaleX;
209
+ const finalScaleY = lockScaling ? this.calculateUniformScaleFactor(event, origin) : scaleFactors.scaleY;
210
+ const scaledFeatures = this.applyNonUniformScale(geometry, finalScaleX, finalScaleY, origin);
211
+ const updatedData = self._getUpdatedData(props, scaledFeatures);
212
+ props.onEdit({
213
+ updatedData,
214
+ editType: "scaled",
215
+ editContext: { featureIndexes: props.selectedIndexes }
216
+ });
217
+ }
218
+ /**
219
+ * Reset the scale state after dragging stops.
220
+ */
221
+ resetScaleState(props, self) {
222
+ props.onUpdateCursor(null);
223
+ self._geometryBeingScaled = null;
224
+ self._selectedEditHandle = null;
225
+ self._cursor = null;
226
+ self._isScaling = false;
227
+ }
228
+ /**
229
+ * Apply non-uniform scaling to geometry.
230
+ * Transforms each coordinate by scaling X and Y independently around the origin.
231
+ */
232
+ applyNonUniformScale(geometry, scaleX, scaleY, origin) {
233
+ return {
234
+ type: "FeatureCollection",
235
+ features: geometry.features.map((feature) => {
236
+ const scaledGeometry = this.scaleGeometry(feature.geometry, scaleX, scaleY, origin);
237
+ return {
238
+ ...feature,
239
+ geometry: scaledGeometry
240
+ };
241
+ })
242
+ };
243
+ }
244
+ /**
245
+ * Scale a geometry's coordinates non-uniformly around an origin point.
246
+ *
247
+ * ## Coordinate Transformation
248
+ *
249
+ * Each coordinate is transformed using: `new = origin + (old - origin) × scale`
250
+ *
251
+ * This is equivalent to:
252
+ * 1. Translate so origin is at (0,0): `temp = old - origin`
253
+ * 2. Scale: `temp = temp × scale`
254
+ * 3. Translate back: `new = temp + origin`
255
+ *
256
+ * The origin (opposite corner from the drag handle) stays fixed while
257
+ * all other points move proportionally.
258
+ */
259
+ scaleGeometry(geometry, scaleX, scaleY, origin) {
260
+ const scaleCoord = (coord) => {
261
+ return [(origin[0] ?? 0) + ((coord[0] ?? 0) - (origin[0] ?? 0)) * scaleX, (origin[1] ?? 0) + ((coord[1] ?? 0) - (origin[1] ?? 0)) * scaleY];
262
+ };
263
+ switch (geometry.type) {
264
+ case "Point": return {
265
+ ...geometry,
266
+ coordinates: scaleCoord(geometry.coordinates)
267
+ };
268
+ case "LineString":
269
+ case "MultiPoint": return {
270
+ ...geometry,
271
+ coordinates: geometry.coordinates.map(scaleCoord)
272
+ };
273
+ case "Polygon":
274
+ case "MultiLineString": return {
275
+ ...geometry,
276
+ coordinates: geometry.coordinates.map((ring) => ring.map(scaleCoord))
277
+ };
278
+ case "MultiPolygon": return {
279
+ ...geometry,
280
+ coordinates: geometry.coordinates.map((polygon) => polygon.map((ring) => ring.map(scaleCoord)))
281
+ };
282
+ default: return geometry;
283
+ }
284
+ }
285
+ };
286
+
287
+ //#endregion
288
+ export { ScaleModeWithFreeTransform };
289
+ //# sourceMappingURL=scale-mode-with-free-transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scale-mode-with-free-transform.js","names":["origin: Position"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type FeatureCollection,\n type ModeProps,\n ScaleMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport type { Position } from 'geojson';\n\ntype ScaleFactors = {\n scaleX: number;\n scaleY: number;\n};\n\ntype ScaleContext = {\n origin: Position;\n scaleFactors: ScaleFactors;\n geometry: FeatureCollection;\n};\n\n/**\n * Extends ScaleMode to support non-uniform (free) scaling.\n *\n * ## Features\n * - Default: Free scaling - can stretch/squish in any direction\n * - With modeConfig.lockScaling = true: Uniform scaling (maintains aspect ratio)\n *\n * ## How Non-Uniform Scaling Works\n *\n * Non-uniform scaling applies separate X and Y scale factors based on cursor\n * movement relative to the opposite corner (the \"origin\" or anchor point).\n *\n * ```\n * Origin (opposite corner) Drag Handle (start)\n * ●───────────────────────────────────●\n * │ │\n * │ startDelta = start - origin │\n * │ currentDelta = current - origin\n * │ │\n * │ scaleX = currentDeltaX / startDeltaX\n * │ scaleY = currentDeltaY / startDeltaY\n * │ │\n * ●───────────────────────────────────● Cursor (current)\n * ```\n *\n * Each coordinate is transformed: `newCoord = origin + (oldCoord - origin) × scale`\n *\n * ## Why We Override Parent's Uniform Scaling\n *\n * The parent ScaleMode calculates uniform scale factors in screen coordinates,\n * which can distort rotated shapes. We calculate our own uniform factor using\n * vector projection to ensure the corner follows the cursor's projection onto\n * the original drag line, preserving shape orientation.\n *\n * ## Minimum Scale Clamping\n *\n * All scale factors are clamped to a minimum of 0.01 to prevent:\n * - Shape inversion (negative scale flipping the shape inside-out)\n * - Shape collapse (scale of 0 making the shape a point/line)\n */\nexport class ScaleModeWithFreeTransform extends ScaleMode {\n /**\n * Override handleDragging to support non-uniform scaling.\n * When lockScaling is false (default), applies separate X/Y scale factors.\n * When lockScaling is true, applies uniform scaling (same factor for X and Y).\n *\n * Note: We don't use parent's handleDragging for uniform scaling because it\n * calculates scale factors in screen coordinates which distorts rotated shapes.\n * Instead, we calculate our own uniform scale factor based on distance ratios.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isScaling) {\n return;\n }\n\n props.onUpdateCursor(self._cursor);\n\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n // Calculate based on distance from origin to preserve aspect ratio\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaling',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final scaled geometry.\n * Uses the same uniform/non-uniform scaling logic as handleDragging.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isScaling) {\n this.emitFinalScaledGeometry(event, props, self);\n this.resetScaleState(props, self);\n }\n }\n\n /**\n * Get the scale context (origin, scale factors, geometry) for the current drag.\n * Returns null if required data is not available.\n */\n private getScaleContext(\n event: DraggingEvent | StopDraggingEvent,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ): ScaleContext | null {\n if (!self._selectedEditHandle) {\n return null;\n }\n\n const oppositeHandle = self._getOppositeScaleHandle(\n self._selectedEditHandle,\n );\n if (!oppositeHandle) {\n return null;\n }\n\n const geometry = self._geometryBeingScaled;\n if (!geometry) {\n return null;\n }\n\n const origin: Position = oppositeHandle.geometry.coordinates;\n const scaleFactors = this.calculateScaleFactors(event, origin);\n\n return { origin, scaleFactors, geometry };\n }\n\n /**\n * Calculate separate X and Y scale factors based on cursor movement.\n *\n * ## Math Explanation\n *\n * For each axis, we compute: `scale = currentDelta / startDelta`\n *\n * Where:\n * - `startDelta` = distance from origin to where drag started (the handle position)\n * - `currentDelta` = distance from origin to current cursor position\n *\n * Example: If the handle started 100px from origin and cursor is now 150px from origin,\n * scale = 150/100 = 1.5 (shape grows to 150% of original size along that axis).\n *\n * ## Edge Cases\n * - If `startDelta` is near zero (handle very close to origin), we return scale=1\n * to avoid division by zero and prevent erratic behavior\n * - Negative scale values are clamped to 0.01 minimum to prevent shape inversion\n */\n private calculateScaleFactors(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): ScaleFactors {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Calculate deltas from the anchor point (origin) to drag positions\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Epsilon for near-zero checks to avoid division by zero\n const epsilon = 0.0000001;\n // Minimum scale to prevent shape from collapsing or inverting\n const minScale = 0.01;\n\n // Scale = ratio of (current distance from origin) / (original distance from origin)\n // If original distance is near-zero, default to scale=1 (no change)\n const rawScaleX =\n Math.abs(startDeltaX) > epsilon ? currentDeltaX / startDeltaX : 1;\n const rawScaleY =\n Math.abs(startDeltaY) > epsilon ? currentDeltaY / startDeltaY : 1;\n\n // Clamp to prevent negative values (which would invert/flip the shape)\n const scaleX = Math.max(rawScaleX, minScale);\n const scaleY = Math.max(rawScaleY, minScale);\n\n return { scaleX, scaleY };\n }\n\n /**\n * Calculate a uniform scale factor for aspect-ratio-preserving scaling.\n *\n * ## Why Use Projection Instead of Simple Distance Ratio?\n *\n * A naive approach would be: `scale = distance(origin, cursor) / distance(origin, start)`\n *\n * But this fails for diagonal movements - if you drag a corner handle and move\n * perpendicular to the diagonal, the simple distance changes even though you\n * don't want the shape to scale.\n *\n * ## Vector Projection Math\n *\n * Instead, we project the cursor position onto the line defined by\n * (origin → start drag point). This way, only movement along the original\n * drag direction affects the scale.\n *\n * ```\n * Origin Start (drag handle)\n * ●──────────────────●\n * \\\n * \\ Cursor moved diagonally\n * ●\n * /\n * / Projected point (what we measure from)\n * ●──────────────────●\n * Origin Projection\n * ```\n *\n * The projection formula uses the dot product:\n * ```\n * projectedDist = (current · start) / |start|\n * scale = projectedDist / |start|\n * ```\n *\n * This equals: `scale = (current · start) / |start|²`\n */\n private calculateUniformScaleFactor(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): number {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Vector from origin to start drag point (defines the scaling direction)\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Vector from origin to current cursor position\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Distance from origin to start point (|start|)\n const startDist = Math.sqrt(startDeltaX ** 2 + startDeltaY ** 2);\n\n if (startDist < 0.0000001) {\n return 1;\n }\n\n // Dot product: current · start = |current| × |start| × cos(θ)\n // This gives us the component of 'current' that lies along 'start'\n const dotProduct =\n currentDeltaX * startDeltaX + currentDeltaY * startDeltaY;\n\n // Project current point onto start vector: projectedDist = (current · start) / |start|\n const projectedDist = dotProduct / startDist;\n\n // Scale factor = projectedDist / |start| = (current · start) / |start|²\n // Clamp to minScale to prevent negative values (shape inversion)\n const minScale = 0.01;\n const rawScale = projectedDist / startDist;\n return Math.max(rawScale, minScale);\n }\n\n /**\n * Emit the final scaled geometry when dragging stops.\n */\n private emitFinalScaledGeometry(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaled',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n }\n\n /**\n * Reset the scale state after dragging stops.\n */\n private resetScaleState(\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n props.onUpdateCursor(null);\n self._geometryBeingScaled = null;\n self._selectedEditHandle = null;\n self._cursor = null;\n self._isScaling = false;\n }\n\n /**\n * Apply non-uniform scaling to geometry.\n * Transforms each coordinate by scaling X and Y independently around the origin.\n */\n private applyNonUniformScale(\n geometry: FeatureCollection,\n scaleX: number,\n scaleY: number,\n origin: Position,\n ): FeatureCollection {\n const scaledFeatures = geometry.features.map((feature) => {\n const scaledGeometry = this.scaleGeometry(\n feature.geometry,\n scaleX,\n scaleY,\n origin,\n );\n return {\n ...feature,\n geometry: scaledGeometry,\n };\n });\n\n return {\n type: 'FeatureCollection',\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON feature type variance\n features: scaledFeatures as any,\n };\n }\n\n /**\n * Scale a geometry's coordinates non-uniformly around an origin point.\n *\n * ## Coordinate Transformation\n *\n * Each coordinate is transformed using: `new = origin + (old - origin) × scale`\n *\n * This is equivalent to:\n * 1. Translate so origin is at (0,0): `temp = old - origin`\n * 2. Scale: `temp = temp × scale`\n * 3. Translate back: `new = temp + origin`\n *\n * The origin (opposite corner from the drag handle) stays fixed while\n * all other points move proportionally.\n */\n private scaleGeometry(\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - includes Point, LineString, Polygon, Multi* variants\n geometry: any,\n scaleX: number,\n scaleY: number,\n origin: Position,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - return type varies by input\n ): any {\n // Transform a single coordinate around the origin point\n const scaleCoord = (coord: Position): Position => {\n return [\n (origin[0] ?? 0) + ((coord[0] ?? 0) - (origin[0] ?? 0)) * scaleX,\n (origin[1] ?? 0) + ((coord[1] ?? 0) - (origin[1] ?? 0)) * scaleY,\n ];\n };\n\n switch (geometry.type) {\n case 'Point':\n return {\n ...geometry,\n coordinates: scaleCoord(geometry.coordinates),\n };\n case 'LineString':\n case 'MultiPoint':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map(scaleCoord),\n };\n case 'Polygon':\n case 'MultiLineString':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((ring: Position[]) =>\n ring.map(scaleCoord),\n ),\n };\n case 'MultiPolygon':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((polygon: Position[][]) =>\n polygon.map((ring: Position[]) => ring.map(scaleCoord)),\n ),\n };\n default:\n return geometry;\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { BaseTransformMode } from "./base-transform-mode.js";
15
+ import { RotateModeWithSnap } from "./rotate-mode-with-snap.js";
16
+ import { ScaleModeWithFreeTransform } from "./scale-mode-with-free-transform.js";
17
+ import { ModifyMode, TranslateMode } from "@deck.gl-community/editable-layers";
18
+ import { featureCollection } from "@turf/helpers";
19
+
20
+ //#region src/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.ts
21
+ /**
22
+ * Transform mode for shapes that support vertex editing (polygons and lines).
23
+ *
24
+ * Use this mode for shapes where individual vertices can be dragged to reshape
25
+ * the geometry. This provides the most flexibility for freeform shape editing.
26
+ *
27
+ * This composite mode provides:
28
+ * - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry
29
+ * - **Translation** (TranslateMode): Drag the shape to move it
30
+ * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize
31
+ * - Default: Non-uniform scaling (can stretch/squish)
32
+ * - With Shift: Uniform scaling (maintains aspect ratio)
33
+ * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate
34
+ * - Default: Free rotation
35
+ * - With Shift: Snap to 45° intervals
36
+ *
37
+ * Priority logic:
38
+ * - If hovering over a scale handle, scaling takes priority
39
+ * - If hovering over the rotate handle, rotation takes priority
40
+ * - If hovering over a vertex (edit handle from ModifyMode), vertex editing takes priority
41
+ * - Otherwise, dragging the shape translates it
42
+ *
43
+ * The guides from all modes are combined, showing both vertex handles and transform handles.
44
+ *
45
+ * Note: For shapes like rectangles where vertex editing is filtered out (to preserve
46
+ * rotation), consider using BoundingTransformMode instead.
47
+ *
48
+ * Note: This mode does not show tooltips during editing because arbitrary polygons
49
+ * don't have meaningful dimensions to display. Use BoundingTransformMode for shapes
50
+ * like rectangles and ellipses where dimension tooltips are useful.
51
+ */
52
+ var VertexTransformMode = class extends BaseTransformMode {
53
+ modifyMode;
54
+ translateMode;
55
+ scaleMode;
56
+ rotateMode;
57
+ constructor() {
58
+ const modifyMode = new ModifyMode();
59
+ const translateMode = new TranslateMode();
60
+ const scaleMode = new ScaleModeWithFreeTransform();
61
+ const rotateMode = new RotateModeWithSnap();
62
+ super([
63
+ modifyMode,
64
+ scaleMode,
65
+ rotateMode,
66
+ translateMode
67
+ ]);
68
+ this.modifyMode = modifyMode;
69
+ this.translateMode = translateMode;
70
+ this.scaleMode = scaleMode;
71
+ this.rotateMode = rotateMode;
72
+ }
73
+ getHandleMatchers() {
74
+ return [
75
+ {
76
+ match: (pick) => Boolean(pick.isGuide && pick.object?.properties?.guideType === "editHandle" && pick.object?.properties?.editHandleType === "existing"),
77
+ mode: this.modifyMode
78
+ },
79
+ {
80
+ match: (pick) => Boolean(pick.isGuide && pick.object?.properties?.editHandleType === "scale"),
81
+ mode: this.scaleMode,
82
+ shiftConfig: { configKey: "lockScaling" }
83
+ },
84
+ {
85
+ match: (pick) => Boolean(pick.isGuide && pick.object?.properties?.editHandleType === "rotate"),
86
+ mode: this.rotateMode,
87
+ shiftConfig: { configKey: "snapRotation" }
88
+ }
89
+ ];
90
+ }
91
+ getDefaultMode() {
92
+ return this.translateMode;
93
+ }
94
+ /**
95
+ * Override getGuides to filter duplicate envelope guides and handle rectangles.
96
+ *
97
+ * Both ScaleMode and RotateMode render the same bounding box envelope.
98
+ * We keep scale's envelope and filter rotate's duplicate.
99
+ * We also hide scale handles while rotating to avoid visual clutter.
100
+ *
101
+ * For rectangles, we hide vertex handles because vertex editing would distort
102
+ * the shape or force axis-alignment. Rectangles should use scale handles only.
103
+ */
104
+ getGuides(props) {
105
+ const allGuides = super.getGuides(props);
106
+ const isRectangle = props.data.features[0]?.properties?.shape === "Rectangle";
107
+ return featureCollection(allGuides.features.filter((guide) => {
108
+ const properties = guide.properties || {};
109
+ const editHandleType = properties.editHandleType;
110
+ const guideType = properties.guideType;
111
+ const guidesToFilterOut = [properties.mode];
112
+ if (this.rotateMode.getIsRotating()) guidesToFilterOut.push(editHandleType);
113
+ if (isRectangle && guideType === "editHandle" && editHandleType === "existing") return false;
114
+ return !guidesToFilterOut.includes("scale");
115
+ }));
116
+ }
117
+ };
118
+
119
+ //#endregion
120
+ export { VertexTransformMode };
121
+ //# sourceMappingURL=vertex-transform-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vertex-transform-mode.js","names":["guidesToFilterOut: string[]"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n ModifyMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\n\n/**\n * Transform mode for shapes that support vertex editing (polygons and lines).\n *\n * Use this mode for shapes where individual vertices can be dragged to reshape\n * the geometry. This provides the most flexibility for freeform shape editing.\n *\n * This composite mode provides:\n * - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry\n * - **Translation** (TranslateMode): Drag the shape to move it\n * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize\n * - Default: Non-uniform scaling (can stretch/squish)\n * - With Shift: Uniform scaling (maintains aspect ratio)\n * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate\n * - Default: Free rotation\n * - With Shift: Snap to 45° intervals\n *\n * Priority logic:\n * - If hovering over a scale handle, scaling takes priority\n * - If hovering over the rotate handle, rotation takes priority\n * - If hovering over a vertex (edit handle from ModifyMode), vertex editing takes priority\n * - Otherwise, dragging the shape translates it\n *\n * The guides from all modes are combined, showing both vertex handles and transform handles.\n *\n * Note: For shapes like rectangles where vertex editing is filtered out (to preserve\n * rotation), consider using BoundingTransformMode instead.\n *\n * Note: This mode does not show tooltips during editing because arbitrary polygons\n * don't have meaningful dimensions to display. Use BoundingTransformMode for shapes\n * like rectangles and ellipses where dimension tooltips are useful.\n */\nexport class VertexTransformMode extends BaseTransformMode {\n private modifyMode: ModifyMode;\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const modifyMode = new ModifyMode();\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order matters: first mode to handle the event wins\n // We put modify first so vertex handles take priority over translate\n super([modifyMode, scaleMode, rotateMode, translateMode]);\n\n this.modifyMode = modifyMode;\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Vertex handle: existing point on polygon/line\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'existing',\n ),\n mode: this.modifyMode,\n // No shift config - vertex editing doesn't have modifiers\n },\n {\n // Scale handle: corner handles on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide && pick.object?.properties?.editHandleType === 'scale',\n ),\n mode: this.scaleMode,\n shiftConfig: { configKey: 'lockScaling' },\n },\n {\n // Rotate handle: top handle on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.editHandleType === 'rotate',\n ),\n mode: this.rotateMode,\n shiftConfig: { configKey: 'snapRotation' },\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Override getGuides to filter duplicate envelope guides and handle rectangles.\n *\n * Both ScaleMode and RotateMode render the same bounding box envelope.\n * We keep scale's envelope and filter rotate's duplicate.\n * We also hide scale handles while rotating to avoid visual clutter.\n *\n * For rectangles, we hide vertex handles because vertex editing would distort\n * the shape or force axis-alignment. Rectangles should use scale handles only.\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n // Get guides from all modes (base class handles pick filtering)\n const allGuides = super.getGuides(props);\n\n // Check if we're editing a rectangle - rectangles have shape: 'Rectangle' property\n const isRectangle =\n props.data.features[0]?.properties?.shape === 'Rectangle';\n\n // biome-ignore lint/suspicious/noExplicitAny: Guide properties vary by mode, safely accessing with optional chaining\n const filteredGuides = allGuides.features.filter((guide: any) => {\n const properties = guide.properties || {};\n const editHandleType = properties.editHandleType;\n const guideType = properties.guideType;\n const mode = properties.mode;\n\n // Both scale and rotate modes have the same enveloping box as a guide - only need one\n const guidesToFilterOut: string[] = [mode as string];\n\n // Do not render scaling edit handles if rotating\n if (this.rotateMode.getIsRotating()) {\n guidesToFilterOut.push(editHandleType as string);\n }\n\n // For rectangles, hide ModifyMode vertex handles (editHandleType: 'existing')\n // Rectangles should only use scale handles for resizing to preserve rotation\n // Vertex editing would either distort the shape or force axis-alignment\n if (\n isRectangle &&\n guideType === 'editHandle' &&\n editHandleType === 'existing'\n ) {\n return false;\n }\n\n return !guidesToFilterOut.includes('scale');\n });\n\n // biome-ignore lint/suspicious/noExplicitAny: turf types mismatch with editable-layers GeoJSON types\n return featureCollection(filteredGuides as any) as any;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,YAAY;EACnC,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAI3C,QAAM;GAAC;GAAY;GAAW;GAAY;GAAc,CAAC;AAEzD,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO;GACL;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,WAC/C;IACH,MAAM,KAAK;IAEZ;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,eAAe;IAC1C;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,gBAAgB;IAC3C;GACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;;;;;;;;CAad,AAAS,UACP,OACwB;EAExB,MAAM,YAAY,MAAM,UAAU,MAAM;EAGxC,MAAM,cACJ,MAAM,KAAK,SAAS,IAAI,YAAY,UAAU;AAgChD,SAAO,kBA7BgB,UAAU,SAAS,QAAQ,UAAe;GAC/D,MAAM,aAAa,MAAM,cAAc,EAAE;GACzC,MAAM,iBAAiB,WAAW;GAClC,MAAM,YAAY,WAAW;GAI7B,MAAMA,oBAA8B,CAHvB,WAAW,KAG4B;AAGpD,OAAI,KAAK,WAAW,eAAe,CACjC,mBAAkB,KAAK,eAAyB;AAMlD,OACE,eACA,cAAc,gBACd,mBAAmB,WAEnB,QAAO;AAGT,UAAO,CAAC,kBAAkB,SAAS,QAAQ;IAC3C,CAG6C"}
@@ -0,0 +1,194 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ 'use client';
15
+
16
+ import { createMapStore } from "../../../shared/create-map-store.js";
17
+ import { MapEvents } from "../../base-map/events.js";
18
+ import { isCircleShape, isEllipseShape, isPointShape, isRectangleShape } from "../shared/types.js";
19
+ import { releaseModeAndCursor, requestCursorChange, requestModeChange } from "../shared/utils/mode-utils.js";
20
+ import { EditShapeEvents } from "./events.js";
21
+ import { EDIT_CURSOR_MAP, EDIT_SHAPE_LAYER_ID, EDIT_SHAPE_MODE } from "./constants.js";
22
+ import { Broadcast } from "@accelint/bus";
23
+ import { getLogger } from "@accelint/logger";
24
+
25
+ //#region src/deckgl/shapes/edit-shape-layer/store.ts
26
+ /**
27
+ * Edit Shape Store
28
+ *
29
+ * Manages editing state for shape modification.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * import { editStore } from '@accelint/map-toolkit/deckgl/shapes';
34
+ *
35
+ * function EditControls({ mapId }) {
36
+ * const { state, edit, save, cancel } = editStore.use(mapId);
37
+ *
38
+ * return (
39
+ * <div>
40
+ * <p>Editing: {state.editingShape?.name ?? 'none'}</p>
41
+ * <button onClick={save}>Save</button>
42
+ * <button onClick={cancel}>Cancel</button>
43
+ * </div>
44
+ * );
45
+ * }
46
+ * ```
47
+ */
48
+ const logger = getLogger({
49
+ enabled: process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test",
50
+ level: "warn",
51
+ prefix: "[EditShapeLayer]",
52
+ pretty: true
53
+ });
54
+ /**
55
+ * Typed event bus instances
56
+ */
57
+ const editShapeBus = Broadcast.getInstance();
58
+ const mapEventBus = Broadcast.getInstance();
59
+ /**
60
+ * Default editing state
61
+ */
62
+ const DEFAULT_EDITING_STATE = {
63
+ editingShape: null,
64
+ editMode: "view",
65
+ featureBeingEdited: null
66
+ };
67
+ /**
68
+ * Determine the appropriate edit mode for a shape type
69
+ */
70
+ function getEditModeForShape(shape) {
71
+ if (isPointShape(shape)) return "translate";
72
+ if (isCircleShape(shape)) return "circle-transform";
73
+ if (isEllipseShape(shape) || isRectangleShape(shape)) return "bounding-transform";
74
+ return "vertex-transform";
75
+ }
76
+ /**
77
+ * Start editing a shape
78
+ */
79
+ function startEditing(mapId, state, shape, options, notify, setState) {
80
+ if (shape.locked) {
81
+ logger.warn(`Cannot edit locked shape: "${shape.name}"`);
82
+ return;
83
+ }
84
+ if (state.editingShape) cancelEditingInternal(mapId, state, notify, setState);
85
+ const editMode = options?.mode ?? getEditModeForShape(shape);
86
+ setState({
87
+ editingShape: shape,
88
+ editMode,
89
+ featureBeingEdited: shape.feature
90
+ });
91
+ requestModeChange(mapId, EDIT_SHAPE_MODE, EDIT_SHAPE_LAYER_ID);
92
+ const cursor = EDIT_CURSOR_MAP[editMode];
93
+ requestCursorChange(mapId, cursor, EDIT_SHAPE_LAYER_ID);
94
+ mapEventBus.emit(MapEvents.disablePan, { id: mapId });
95
+ editShapeBus.emit(EditShapeEvents.editing, {
96
+ shape,
97
+ mapId
98
+ });
99
+ notify();
100
+ }
101
+ /**
102
+ * Save editing and create updated shape
103
+ */
104
+ function saveEditingInternal(mapId, state, notify, setState) {
105
+ if (!(state.editingShape && state.featureBeingEdited)) return null;
106
+ const originalShape = state.editingShape;
107
+ const updatedFeature = state.featureBeingEdited;
108
+ const updatedShape = {
109
+ ...originalShape,
110
+ feature: {
111
+ ...updatedFeature,
112
+ properties: {
113
+ ...originalShape.feature.properties,
114
+ ...updatedFeature.properties
115
+ }
116
+ },
117
+ lastUpdated: Date.now()
118
+ };
119
+ setState({
120
+ editingShape: null,
121
+ editMode: "view",
122
+ featureBeingEdited: null
123
+ });
124
+ releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);
125
+ mapEventBus.emit(MapEvents.enablePan, { id: mapId });
126
+ editShapeBus.emit(EditShapeEvents.updated, {
127
+ shape: updatedShape,
128
+ mapId
129
+ });
130
+ notify();
131
+ return updatedShape;
132
+ }
133
+ /**
134
+ * Cancel the current editing operation
135
+ */
136
+ function cancelEditingInternal(mapId, state, notify, setState) {
137
+ if (!state.editingShape) return;
138
+ const originalShape = state.editingShape;
139
+ setState({
140
+ editingShape: null,
141
+ editMode: "view",
142
+ featureBeingEdited: null
143
+ });
144
+ releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);
145
+ mapEventBus.emit(MapEvents.enablePan, { id: mapId });
146
+ editShapeBus.emit(EditShapeEvents.canceled, {
147
+ shape: originalShape,
148
+ mapId
149
+ });
150
+ notify();
151
+ }
152
+ /**
153
+ * Edit shape store
154
+ */
155
+ const editStore = createMapStore({
156
+ defaultState: { ...DEFAULT_EDITING_STATE },
157
+ actions: (mapId, { get, set, notify }) => ({
158
+ edit: (shape, options) => {
159
+ startEditing(mapId, get(), shape, options, notify, set);
160
+ },
161
+ save: () => {
162
+ saveEditingInternal(mapId, get(), notify, set);
163
+ },
164
+ cancel: () => {
165
+ cancelEditingInternal(mapId, get(), notify, set);
166
+ }
167
+ }),
168
+ onCleanup: (mapId, state) => {
169
+ if (state.editingShape) {
170
+ releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);
171
+ mapEventBus.emit(MapEvents.enablePan, { id: mapId });
172
+ editShapeBus.emit(EditShapeEvents.canceled, {
173
+ shape: state.editingShape,
174
+ mapId
175
+ });
176
+ }
177
+ }
178
+ });
179
+ /**
180
+ * Update feature from the layer component (called during drag operations)
181
+ */
182
+ function updateFeatureFromLayer(mapId, feature) {
183
+ editStore.set(mapId, { featureBeingEdited: feature });
184
+ }
185
+ /**
186
+ * Cancel editing (called by the layer component on ESC)
187
+ */
188
+ function cancelEditingFromLayer(mapId) {
189
+ editStore.actions(mapId).cancel();
190
+ }
191
+
192
+ //#endregion
193
+ export { cancelEditingFromLayer, editStore, updateFeatureFromLayer };
194
+ //# sourceMappingURL=store.js.map