@accelint/map-toolkit 1.2.0 → 1.4.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 (139) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/catalog-info.yaml +4 -4
  3. package/dist/camera/events.d.ts +45 -0
  4. package/dist/camera/events.js +45 -0
  5. package/dist/camera/events.js.map +1 -1
  6. package/dist/camera/store.d.ts +47 -0
  7. package/dist/camera/store.js +81 -0
  8. package/dist/camera/store.js.map +1 -1
  9. package/dist/camera/types.d.ts +81 -0
  10. package/dist/cursor-coordinates/constants.d.ts +8 -0
  11. package/dist/cursor-coordinates/constants.js +22 -0
  12. package/dist/cursor-coordinates/constants.js.map +1 -0
  13. package/dist/cursor-coordinates/store.d.ts +1 -0
  14. package/dist/cursor-coordinates/store.js +1 -0
  15. package/dist/cursor-coordinates/store.js.map +1 -1
  16. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +5 -0
  17. package/dist/cursor-coordinates/use-cursor-coordinates.js +23 -8
  18. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  19. package/dist/deckgl/base-map/constants.d.ts +12 -0
  20. package/dist/deckgl/base-map/constants.js +12 -0
  21. package/dist/deckgl/base-map/constants.js.map +1 -1
  22. package/dist/deckgl/base-map/controls.d.ts +11 -1
  23. package/dist/deckgl/base-map/controls.js +5 -0
  24. package/dist/deckgl/base-map/controls.js.map +1 -1
  25. package/dist/deckgl/base-map/events.d.ts +30 -0
  26. package/dist/deckgl/base-map/events.js +30 -0
  27. package/dist/deckgl/base-map/events.js.map +1 -1
  28. package/dist/deckgl/base-map/index.d.ts +2 -2
  29. package/dist/deckgl/base-map/index.js +43 -4
  30. package/dist/deckgl/base-map/index.js.map +1 -1
  31. package/dist/deckgl/base-map/provider.d.ts +2 -2
  32. package/dist/deckgl/index.js +1 -1
  33. package/dist/deckgl/saved-viewports/index.d.ts +75 -0
  34. package/dist/deckgl/saved-viewports/index.js +58 -0
  35. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  36. package/dist/deckgl/saved-viewports/storage.d.ts +51 -0
  37. package/dist/deckgl/saved-viewports/storage.js +64 -0
  38. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  39. package/dist/deckgl/shapes/display-shape-layer/constants.js +18 -6
  40. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  41. package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +7 -0
  42. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
  43. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +61 -4
  44. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  45. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +22 -8
  46. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +75 -4
  47. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  48. package/dist/deckgl/shapes/draw-shape-layer/constants.js +30 -0
  49. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -1
  50. package/dist/deckgl/shapes/draw-shape-layer/fiber.js +36 -0
  51. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -1
  52. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +2 -2
  53. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +32 -1
  54. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  55. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +37 -8
  56. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  57. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +43 -1
  58. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  59. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +44 -1
  60. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  61. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +46 -3
  62. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  63. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +37 -1
  64. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
  65. package/dist/deckgl/shapes/draw-shape-layer/store.js +50 -2
  66. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
  67. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +138 -17
  68. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
  69. package/dist/deckgl/shapes/edit-shape-layer/constants.js +2 -1
  70. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
  71. package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
  72. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -1
  73. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +2 -2
  74. package/dist/deckgl/shapes/edit-shape-layer/index.js +41 -9
  75. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  76. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +56 -8
  77. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
  78. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +26 -4
  79. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  80. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +28 -3
  81. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  82. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +32 -2
  83. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
  84. package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +129 -0
  85. package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -0
  86. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +33 -4
  87. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
  88. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +21 -2
  89. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
  90. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +35 -11
  91. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
  92. package/dist/deckgl/shapes/edit-shape-layer/store.js +12 -3
  93. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  94. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +4 -3
  95. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +12 -0
  96. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
  97. package/dist/deckgl/shapes/shared/types.d.ts +3 -3
  98. package/dist/deckgl/shapes/shared/types.js +2 -2
  99. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  100. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +3 -3
  101. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  102. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +1 -1
  103. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
  104. package/dist/deckgl/symbol-layer/fiber.d.ts +18 -0
  105. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  106. package/dist/deckgl/symbol-layer/index.d.ts +79 -1
  107. package/dist/deckgl/symbol-layer/index.js +72 -1
  108. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  109. package/dist/deckgl/text-layer/character-sets.d.ts +30 -0
  110. package/dist/deckgl/text-layer/character-sets.js +26 -0
  111. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  112. package/dist/deckgl/text-layer/default-settings.d.ts +29 -0
  113. package/dist/deckgl/text-layer/default-settings.js +28 -0
  114. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  115. package/dist/deckgl/text-layer/index.d.ts +65 -0
  116. package/dist/deckgl/text-layer/index.js +56 -0
  117. package/dist/deckgl/text-layer/index.js.map +1 -1
  118. package/dist/hotkey-manager/dist/react/use-hotkey.js +39 -0
  119. package/dist/hotkey-manager/dist/react/use-hotkey.js.map +1 -0
  120. package/dist/map-cursor/events.d.ts +19 -0
  121. package/dist/map-cursor/events.js +19 -0
  122. package/dist/map-cursor/events.js.map +1 -1
  123. package/dist/map-cursor/store.d.ts +34 -2
  124. package/dist/map-cursor/store.js +44 -3
  125. package/dist/map-cursor/store.js.map +1 -1
  126. package/dist/map-mode/store.d.ts +43 -4
  127. package/dist/map-mode/store.js +56 -6
  128. package/dist/map-mode/store.js.map +1 -1
  129. package/dist/shared/create-map-store.d.ts +14 -0
  130. package/dist/shared/create-map-store.js +26 -2
  131. package/dist/shared/create-map-store.js.map +1 -1
  132. package/dist/shared/units.d.ts +24 -0
  133. package/dist/shared/units.js +24 -0
  134. package/dist/shared/units.js.map +1 -1
  135. package/dist/viewport/store.d.ts +1 -0
  136. package/dist/viewport/store.js +4 -0
  137. package/dist/viewport/store.js.map +1 -1
  138. package/dist/viewport/viewport-size.d.ts +2 -2
  139. package/package.json +7 -6
@@ -28,6 +28,7 @@ import { featureCollection } from "@turf/helpers";
28
28
  * editing is not meaningful or desired. Instead, shapes are manipulated via their
29
29
  * bounding box handles.
30
30
  *
31
+ * ## Capabilities
31
32
  * This composite mode provides:
32
33
  * - **Translation** (TranslateMode): Drag the shape body to move it
33
34
  * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize
@@ -38,12 +39,33 @@ import { featureCollection } from "@turf/helpers";
38
39
  * - With Shift: Snap to 45° intervals
39
40
  * - **Live tooltip**: Shows dimensions and area during scaling
40
41
  *
42
+ * ## Differences from VertexTransformMode
41
43
  * Unlike VertexTransformMode, this mode does NOT include vertex editing handles.
44
+ * This prevents accidental distortion of shapes that have specific geometric constraints
45
+ * (e.g., ellipses must maintain their elliptical shape, rectangles must maintain right angles).
42
46
  *
43
- * Priority logic:
44
- * - If hovering over a scale handle, scaling takes priority
45
- * - If hovering over the rotate handle, rotation takes priority
46
- * - Otherwise, dragging the shape body translates it
47
+ * ## Handle Priority Logic
48
+ * When drag starts, modes are evaluated in this priority order:
49
+ * 1. If hovering over a scale handle scaling takes priority
50
+ * 2. If hovering over the rotate handle rotation takes priority
51
+ * 3. Otherwise → dragging the shape body translates it
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import { BoundingTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode';
56
+ * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';
57
+ *
58
+ * // Used internally by EditShapeLayer for rectangles and ellipses
59
+ * const mode = new BoundingTransformMode();
60
+ *
61
+ * const layer = new EditableGeoJsonLayer({
62
+ * mode,
63
+ * data: rectangleFeatureCollection,
64
+ * selectedFeatureIndexes: [0],
65
+ * onEdit: handleEdit,
66
+ * // ... other props
67
+ * });
68
+ * ```
47
69
  */
48
70
  var BoundingTransformMode = class extends BaseTransformMode {
49
71
  translateMode;
@@ -1 +1 @@
1
- {"version":3,"file":"bounding-transform-mode.js","names":["guidesToFilterOut: string[]","text: string"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/bounding-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 DraggingEvent,\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatEllipseTooltip,\n formatRectangleTooltip,\n} from '../../shared/constants';\nimport {\n computeEllipseMeasurementsFromPolygon,\n computeRectangleMeasurementsFromCorners,\n} from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for shapes that use bounding box manipulation (no vertex editing).\n *\n * Use this mode for shapes like ellipses and rectangles where individual vertex\n * editing is not meaningful or desired. Instead, shapes are manipulated via their\n * bounding box handles.\n *\n * This composite mode provides:\n * - **Translation** (TranslateMode): Drag the shape body 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 * - **Live tooltip**: Shows dimensions and area during scaling\n *\n * Unlike VertexTransformMode, this mode does NOT include vertex editing handles.\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 * - Otherwise, dragging the shape body translates it\n */\nexport class BoundingTransformMode extends BaseTransformMode {\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order: scale and rotate first so their handles take priority over translate\n super([scaleMode, rotateMode, translateMode]);\n\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\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 * Update tooltip with shape dimensions during scaling.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when scaling\n if (this.activeDragMode !== this.scaleMode) {\n return;\n }\n\n this.updateShapeTooltip(event, props);\n }\n\n /**\n * Override getGuides to filter duplicate envelope guides.\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 override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n const allGuides = super.getGuides(props);\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 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 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 /**\n * Update the tooltip with shape dimensions and area.\n * Called during scaling to show live measurements.\n * Handles both rectangles (5 points) and ellipses (many points).\n */\n private updateShapeTooltip(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 4) {\n this.tooltip = null;\n return;\n }\n\n // Check if this is a rectangle (has shape: 'Rectangle' property)\n const isRectangle = feature.properties?.shape === 'Rectangle';\n\n let text: string;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n if (isRectangle) {\n // Rectangle: calculate width and height from corners\n const corner0 = coordinates[0] as [number, number];\n const corner1 = coordinates[1] as [number, number];\n const corner2 = coordinates[2] as [number, number];\n\n const { width, height, area } = computeRectangleMeasurementsFromCorners(\n corner0,\n corner1,\n corner2,\n distanceUnits,\n );\n\n text = formatRectangleTooltip(width, height, area, unitAbbrev);\n } else {\n // Ellipse: calculate major/minor axes using consolidated utility\n const {\n majorAxis,\n minorAxis,\n area: ellipseArea,\n } = computeEllipseMeasurementsFromPolygon(\n coordinates as [number, number][],\n distanceUnits,\n );\n\n text = formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n );\n }\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,IAAa,wBAAb,cAA2C,kBAAkB;CAC3D,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAG3C,QAAM;GAAC;GAAW;GAAY;GAAc,CAAC;AAE7C,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;GACH,MAAM,KAAK;GACX,aAAa,EAAE,WAAW,eAAe;GAC1C,EACD;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;GACH,MAAM,KAAK;GACX,aAAa,EAAE,WAAW,gBAAgB;GAC3C,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,UAC/B;AAGF,OAAK,mBAAmB,OAAO,MAAM;;;;;;;;;CAUvC,AAAS,UACP,OACwB;AAqBxB,SAAO,kBApBW,MAAM,UAAU,MAAM,CAGP,SAAS,QAAQ,UAAe;GAC/D,MAAM,aAAa,MAAM,cAAc,EAAE;GACzC,MAAM,iBAAiB,WAAW;GAIlC,MAAMA,oBAA8B,CAHvB,WAAW,KAG4B;AAGpD,OAAI,KAAK,WAAW,eAAe,CACjC,mBAAkB,KAAK,eAAyB;AAGlD,UAAO,CAAC,kBAAkB,SAAS,QAAQ;IAC3C,CAG6C;;;;;;;CAQjD,AAAQ,mBACN,OACA,OACA;EACA,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAGF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,YAAY,UAAU;EAElD,IAAIC;EACJ,MAAM,aAAa,4BAA4B,cAAc;AAE7D,MAAI,aAAa;GAEf,MAAM,UAAU,YAAY;GAC5B,MAAM,UAAU,YAAY;GAC5B,MAAM,UAAU,YAAY;GAE5B,MAAM,EAAE,OAAO,QAAQ,SAAS,wCAC9B,SACA,SACA,SACA,cACD;AAED,UAAO,uBAAuB,OAAO,QAAQ,MAAM,WAAW;SACzD;GAEL,MAAM,EACJ,WACA,WACA,MAAM,gBACJ,sCACF,aACA,cACD;AAED,UAAO,qBACL,WACA,WACA,aACA,WACD;;AAIH,OAAK,UAAU;GACb,UAAU;GACV;GACD"}
1
+ {"version":3,"file":"bounding-transform-mode.js","names":["guidesToFilterOut: string[]","text: string"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/bounding-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 DraggingEvent,\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatEllipseTooltip,\n formatRectangleTooltip,\n} from '../../shared/constants';\nimport {\n computeEllipseMeasurementsFromPolygon,\n computeRectangleMeasurementsFromCorners,\n} from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for shapes that use bounding box manipulation (no vertex editing).\n *\n * Use this mode for shapes like ellipses and rectangles where individual vertex\n * editing is not meaningful or desired. Instead, shapes are manipulated via their\n * bounding box handles.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Translation** (TranslateMode): Drag the shape body 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 * - **Live tooltip**: Shows dimensions and area during scaling\n *\n * ## Differences from VertexTransformMode\n * Unlike VertexTransformMode, this mode does NOT include vertex editing handles.\n * This prevents accidental distortion of shapes that have specific geometric constraints\n * (e.g., ellipses must maintain their elliptical shape, rectangles must maintain right angles).\n *\n * ## Handle Priority Logic\n * When drag starts, modes are evaluated in this priority order:\n * 1. If hovering over a scale handle → scaling takes priority\n * 2. If hovering over the rotate handle → rotation takes priority\n * 3. Otherwise → dragging the shape body translates it\n *\n * @example\n * ```typescript\n * import { BoundingTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for rectangles and ellipses\n * const mode = new BoundingTransformMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: rectangleFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * // ... other props\n * });\n * ```\n */\nexport class BoundingTransformMode extends BaseTransformMode {\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order: scale and rotate first so their handles take priority over translate\n super([scaleMode, rotateMode, translateMode]);\n\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\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 * Update tooltip with shape dimensions during scaling.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when scaling\n if (this.activeDragMode !== this.scaleMode) {\n return;\n }\n\n this.updateShapeTooltip(event, props);\n }\n\n /**\n * Override getGuides to filter duplicate envelope guides.\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 override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n const allGuides = super.getGuides(props);\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 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 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 /**\n * Update the tooltip with shape dimensions and area.\n * Called during scaling to show live measurements.\n * Handles both rectangles (5 points) and ellipses (many points).\n */\n private updateShapeTooltip(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 4) {\n this.tooltip = null;\n return;\n }\n\n // Check if this is a rectangle (has shape: 'Rectangle' property)\n const isRectangle = feature.properties?.shape === 'Rectangle';\n\n let text: string;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n if (isRectangle) {\n // Rectangle: calculate width and height from corners\n const corner0 = coordinates[0] as [number, number];\n const corner1 = coordinates[1] as [number, number];\n const corner2 = coordinates[2] as [number, number];\n\n const { width, height, area } = computeRectangleMeasurementsFromCorners(\n corner0,\n corner1,\n corner2,\n distanceUnits,\n );\n\n text = formatRectangleTooltip(width, height, area, unitAbbrev);\n } else {\n // Ellipse: calculate major/minor axes using consolidated utility\n const {\n majorAxis,\n minorAxis,\n area: ellipseArea,\n } = computeEllipseMeasurementsFromPolygon(\n coordinates as [number, number][],\n distanceUnits,\n );\n\n text = formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n );\n }\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,IAAa,wBAAb,cAA2C,kBAAkB;CAC3D,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAG3C,QAAM;GAAC;GAAW;GAAY;GAAc,CAAC;AAE7C,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;GACH,MAAM,KAAK;GACX,aAAa,EAAE,WAAW,eAAe;GAC1C,EACD;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;GACH,MAAM,KAAK;GACX,aAAa,EAAE,WAAW,gBAAgB;GAC3C,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,UAC/B;AAGF,OAAK,mBAAmB,OAAO,MAAM;;;;;;;;;CAUvC,AAAS,UACP,OACwB;AAqBxB,SAAO,kBApBW,MAAM,UAAU,MAAM,CAGP,SAAS,QAAQ,UAAe;GAC/D,MAAM,aAAa,MAAM,cAAc,EAAE;GACzC,MAAM,iBAAiB,WAAW;GAIlC,MAAMA,oBAA8B,CAHvB,WAAW,KAG4B;AAGpD,OAAI,KAAK,WAAW,eAAe,CACjC,mBAAkB,KAAK,eAAyB;AAGlD,UAAO,CAAC,kBAAkB,SAAS,QAAQ;IAC3C,CAG6C;;;;;;;CAQjD,AAAQ,mBACN,OACA,OACA;EACA,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAGF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,YAAY,UAAU;EAElD,IAAIC;EACJ,MAAM,aAAa,4BAA4B,cAAc;AAE7D,MAAI,aAAa;GAEf,MAAM,UAAU,YAAY;GAC5B,MAAM,UAAU,YAAY;GAC5B,MAAM,UAAU,YAAY;GAE5B,MAAM,EAAE,OAAO,QAAQ,SAAS,wCAC9B,SACA,SACA,SACA,cACD;AAED,UAAO,uBAAuB,OAAO,QAAQ,MAAM,WAAW;SACzD;GAEL,MAAM,EACJ,WACA,WACA,MAAM,gBACJ,sCACF,aACA,cACD;AAED,UAAO,qBACL,WACA,WACA,aACA,WACD;;AAIH,OAAK,UAAU;GACb,UAAU;GACV;GACD"}
@@ -22,14 +22,39 @@ import { centroid } from "@turf/turf";
22
22
  /**
23
23
  * Transform mode for circles combining resize and translate.
24
24
  *
25
+ * ## Capabilities
25
26
  * This composite mode provides:
26
27
  * - **Resize** (ResizeCircleMode): Drag edge to resize from center
27
28
  * - **Translation** (TranslateMode): Drag the circle body to move it
28
29
  * - **Live tooltip**: Shows diameter and area during resize
29
30
  *
30
- * Priority logic:
31
- * - If dragging on the edge/handle, resize takes priority
32
- * - If dragging on the circle body, translate takes priority
31
+ * ## Handle Priority Logic
32
+ * When drag starts, modes are evaluated in this priority order:
33
+ * 1. If dragging on the edge/handle resize takes priority
34
+ * 2. Otherwise → dragging the circle body translates it
35
+ *
36
+ * ## Resize Behavior
37
+ * Unlike scale operations in BoundingTransformMode, circle resize maintains
38
+ * the shape's circular geometry by resizing from the center point. The center
39
+ * remains fixed while the radius changes based on cursor distance.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { CircleTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode';
44
+ * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';
45
+ *
46
+ * // Used internally by EditShapeLayer for circles
47
+ * const mode = new CircleTransformMode();
48
+ *
49
+ * const layer = new EditableGeoJsonLayer({
50
+ * mode,
51
+ * data: circleFeatureCollection,
52
+ * selectedFeatureIndexes: [0],
53
+ * onEdit: handleEdit,
54
+ * modeConfig: { distanceUnits: 'kilometers' },
55
+ * // ... other props
56
+ * });
57
+ * ```
33
58
  */
34
59
  var CircleTransformMode = class extends BaseTransformMode {
35
60
  resizeMode;
@@ -1 +1 @@
1
- {"version":3,"file":"circle-transform-mode.js","names":["area"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/circle-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 DraggingEvent,\n type FeatureCollection,\n type GeoJsonEditMode,\n type ModeProps,\n ResizeCircleMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { centroid } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for circles combining resize and translate.\n *\n * This composite mode provides:\n * - **Resize** (ResizeCircleMode): Drag edge to resize from center\n * - **Translation** (TranslateMode): Drag the circle body to move it\n * - **Live tooltip**: Shows diameter and area during resize\n *\n * Priority logic:\n * - If dragging on the edge/handle, resize takes priority\n * - If dragging on the circle body, translate takes priority\n */\nexport class CircleTransformMode extends BaseTransformMode {\n private resizeMode: ResizeCircleMode;\n private translateMode: TranslateMode;\n\n constructor() {\n const resizeMode = new ResizeCircleMode();\n const translateMode = new TranslateMode();\n\n // Order matters: resize first so edge handles take priority\n super([resizeMode, translateMode]);\n\n this.resizeMode = resizeMode;\n this.translateMode = translateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Resize handle: intermediate point on circle edge\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'intermediate',\n ),\n mode: this.resizeMode,\n // No shift config - resize doesn't have modifiers\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Update the tooltip with circle diameter and area during resize.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when resizing\n if (this.activeDragMode !== this.resizeMode) {\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature to calculate radius from its geometry\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n // Calculate center and radius from the polygon geometry\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 3) {\n this.tooltip = null;\n return;\n }\n\n const centerFeature = centroid(feature);\n const center = centerFeature.geometry.coordinates as [number, number];\n const firstPoint = coordinates[0] as [number, number];\n const { diameter, area } = computeCircleMeasurements(\n center,\n firstPoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(diameter, area, unitAbbrev),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,kBAAkB;EACzC,MAAM,gBAAgB,IAAI,eAAe;AAGzC,QAAM,CAAC,YAAY,cAAc,CAAC;AAElC,OAAK,aAAa;AAClB,OAAK,gBAAgB;;CAGvB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,eAC/C;GACH,MAAM,KAAK;GAEZ,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,WAC/B;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,SADgB,SAAS,QAAQ,CACV,SAAS;EACtC,MAAM,aAAa,YAAY;EAC/B,MAAM,EAAE,UAAU,iBAAS,0BACzB,QACA,YACA,cACD;AAID,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,UAAUA,QALnB,4BAA4B,cAAc,CAKN;GACtD"}
1
+ {"version":3,"file":"circle-transform-mode.js","names":["area"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/circle-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 DraggingEvent,\n type FeatureCollection,\n type GeoJsonEditMode,\n type ModeProps,\n ResizeCircleMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { centroid } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for circles combining resize and translate.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Resize** (ResizeCircleMode): Drag edge to resize from center\n * - **Translation** (TranslateMode): Drag the circle body to move it\n * - **Live tooltip**: Shows diameter and area during resize\n *\n * ## Handle Priority Logic\n * When drag starts, modes are evaluated in this priority order:\n * 1. If dragging on the edge/handle resize takes priority\n * 2. Otherwise dragging the circle body translates it\n *\n * ## Resize Behavior\n * Unlike scale operations in BoundingTransformMode, circle resize maintains\n * the shape's circular geometry by resizing from the center point. The center\n * remains fixed while the radius changes based on cursor distance.\n *\n * @example\n * ```typescript\n * import { CircleTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for circles\n * const mode = new CircleTransformMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: circleFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: { distanceUnits: 'kilometers' },\n * // ... other props\n * });\n * ```\n */\nexport class CircleTransformMode extends BaseTransformMode {\n private resizeMode: ResizeCircleMode;\n private translateMode: TranslateMode;\n\n constructor() {\n const resizeMode = new ResizeCircleMode();\n const translateMode = new TranslateMode();\n\n // Order matters: resize first so edge handles take priority\n super([resizeMode, translateMode]);\n\n this.resizeMode = resizeMode;\n this.translateMode = translateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Resize handle: intermediate point on circle edge\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'intermediate',\n ),\n mode: this.resizeMode,\n // No shift config - resize doesn't have modifiers\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Update the tooltip with circle diameter and area during resize.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when resizing\n if (this.activeDragMode !== this.resizeMode) {\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature to calculate radius from its geometry\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n // Calculate center and radius from the polygon geometry\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 3) {\n this.tooltip = null;\n return;\n }\n\n const centerFeature = centroid(feature);\n const center = centerFeature.geometry.coordinates as [number, number];\n const firstPoint = coordinates[0] as [number, number];\n const { diameter, area } = computeCircleMeasurements(\n center,\n firstPoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(diameter, area, unitAbbrev),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,kBAAkB;EACzC,MAAM,gBAAgB,IAAI,eAAe;AAGzC,QAAM,CAAC,YAAY,cAAc,CAAC;AAElC,OAAK,aAAa;AAClB,OAAK,gBAAgB;;CAGvB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,eAC/C;GACH,MAAM,KAAK;GAEZ,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,WAC/B;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,SADgB,SAAS,QAAQ,CACV,SAAS;EACtC,MAAM,aAAa,YAAY;EAC/B,MAAM,EAAE,UAAU,iBAAS,0BACzB,QACA,YACA,cACD;AAID,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,UAAUA,QALnB,4BAA4B,cAAc,CAKN;GACtD"}
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { BoundingTransformMode } from "./bounding-transform-mode.js";
15
15
  import { CircleTransformMode } from "./circle-transform-mode.js";
16
+ import { PointTranslateMode } from "./point-translate-mode.js";
16
17
  import { VertexTransformMode } from "./vertex-transform-mode.js";
17
18
  import { TranslateMode, ViewMode } from "@deck.gl-community/editable-layers";
18
19
 
@@ -37,20 +38,49 @@ import { TranslateMode, ViewMode } from "@deck.gl-community/editable-layers";
37
38
  * for circles, allowing resize from edge plus drag to translate.
38
39
  * Shows live diameter/area tooltips during resize.
39
40
  *
40
- * TranslateMode allows dragging to move the shape (used for points).
41
+ * PointTranslateMode allows clicking anywhere on the map to reposition
42
+ * a point, or dragging the point for traditional translation behavior.
43
+ *
44
+ * TranslateMode allows dragging to move the shape (generic translation).
41
45
  */
42
46
  const EDIT_MODE_INSTANCES = {
43
47
  view: new ViewMode(),
44
48
  "bounding-transform": new BoundingTransformMode(),
45
49
  "vertex-transform": new VertexTransformMode(),
46
50
  "circle-transform": new CircleTransformMode(),
47
- translate: new TranslateMode()
51
+ translate: new TranslateMode(),
52
+ "point-translate": new PointTranslateMode()
48
53
  };
49
54
  /**
50
55
  * Get the cached mode instance for an edit mode.
51
56
  *
57
+ * Returns the pre-instantiated edit mode for the specified mode type.
58
+ * Modes are cached at module level to prevent deck.gl assertion failures
59
+ * that occur when creating new mode instances on each render.
60
+ *
61
+ * ## Available Edit Modes
62
+ * - `'bounding-transform'`: For shapes without vertex editing (rectangles, ellipses)
63
+ * - `'vertex-transform'`: For shapes with vertex editing (polygons, lines)
64
+ * - `'circle-transform'`: For circles (resize from edge + translate)
65
+ * - `'point-translate'`: For points (click to place + drag to move)
66
+ * - `'translate'`: Generic translation (drag to move)
67
+ *
52
68
  * @param mode - The edit mode to get the instance for
53
69
  * @returns The cached mode instance
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import { getEditModeInstance } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes';
74
+ *
75
+ * // Get the bounding transform mode for editing rectangles/ellipses
76
+ * const boundingMode = getEditModeInstance('bounding-transform');
77
+ *
78
+ * // Use with EditableGeoJsonLayer
79
+ * const layer = new EditableGeoJsonLayer({
80
+ * mode: boundingMode,
81
+ * // ... other props
82
+ * });
83
+ * ```
54
84
  */
55
85
  function getEditModeInstance(mode) {
56
86
  return EDIT_MODE_INSTANCES[mode];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/index.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 { TranslateMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { BoundingTransformMode } from './bounding-transform-mode';\nimport { CircleTransformMode } from './circle-transform-mode';\nimport { VertexTransformMode } from './vertex-transform-mode';\nimport type { EditMode } from '../types';\n\n/**\n * Cached edit mode instances.\n *\n * CRITICAL: Mode instances must be cached at module level to prevent\n * deck.gl assertion failures. Creating new mode instances on each render\n * causes the EditableGeoJsonLayer to fail with assertion errors.\n *\n * BoundingTransformMode combines ScaleModeWithFreeTransform, RotateMode, and\n * TranslateMode for shapes without vertex editing (ellipses, rectangles),\n * allowing non-uniform scaling plus rotate/translate via bounding box handles.\n * Shows live dimension tooltips during scaling.\n *\n * VertexTransformMode combines ModifyMode with ScaleModeWithFreeTransform,\n * RotateMode, and TranslateMode for shapes that support vertex editing\n * (polygons, lines), allowing vertex manipulation plus scale/rotate/translate.\n *\n * CircleTransformMode combines ResizeCircleMode with TranslateMode\n * for circles, allowing resize from edge plus drag to translate.\n * Shows live diameter/area tooltips during resize.\n *\n * TranslateMode allows dragging to move the shape (used for points).\n */\nconst EDIT_MODE_INSTANCES = {\n view: new ViewMode(),\n 'bounding-transform': new BoundingTransformMode(),\n 'vertex-transform': new VertexTransformMode(),\n 'circle-transform': new CircleTransformMode(),\n translate: new TranslateMode(),\n} as const;\n\n/**\n * Get the cached mode instance for an edit mode.\n *\n * @param mode - The edit mode to get the instance for\n * @returns The cached mode instance\n */\nexport function getEditModeInstance(\n mode: EditMode,\n): (typeof EDIT_MODE_INSTANCES)[EditMode] {\n return EDIT_MODE_INSTANCES[mode];\n}\n\n/**\n * Get the ViewMode instance (for when not editing).\n *\n * @returns The cached ViewMode instance\n */\nexport function getViewModeInstance(): ViewMode {\n return EDIT_MODE_INSTANCES.view;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAM,sBAAsB;CAC1B,MAAM,IAAI,UAAU;CACpB,sBAAsB,IAAI,uBAAuB;CACjD,oBAAoB,IAAI,qBAAqB;CAC7C,oBAAoB,IAAI,qBAAqB;CAC7C,WAAW,IAAI,eAAe;CAC/B;;;;;;;AAQD,SAAgB,oBACd,MACwC;AACxC,QAAO,oBAAoB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/index.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 { TranslateMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { BoundingTransformMode } from './bounding-transform-mode';\nimport { CircleTransformMode } from './circle-transform-mode';\nimport { PointTranslateMode } from './point-translate-mode';\nimport { VertexTransformMode } from './vertex-transform-mode';\nimport type { EditMode } from '../types';\n\n/**\n * Cached edit mode instances.\n *\n * CRITICAL: Mode instances must be cached at module level to prevent\n * deck.gl assertion failures. Creating new mode instances on each render\n * causes the EditableGeoJsonLayer to fail with assertion errors.\n *\n * BoundingTransformMode combines ScaleModeWithFreeTransform, RotateMode, and\n * TranslateMode for shapes without vertex editing (ellipses, rectangles),\n * allowing non-uniform scaling plus rotate/translate via bounding box handles.\n * Shows live dimension tooltips during scaling.\n *\n * VertexTransformMode combines ModifyMode with ScaleModeWithFreeTransform,\n * RotateMode, and TranslateMode for shapes that support vertex editing\n * (polygons, lines), allowing vertex manipulation plus scale/rotate/translate.\n *\n * CircleTransformMode combines ResizeCircleMode with TranslateMode\n * for circles, allowing resize from edge plus drag to translate.\n * Shows live diameter/area tooltips during resize.\n *\n * PointTranslateMode allows clicking anywhere on the map to reposition\n * a point, or dragging the point for traditional translation behavior.\n *\n * TranslateMode allows dragging to move the shape (generic translation).\n */\nconst EDIT_MODE_INSTANCES = {\n view: new ViewMode(),\n 'bounding-transform': new BoundingTransformMode(),\n 'vertex-transform': new VertexTransformMode(),\n 'circle-transform': new CircleTransformMode(),\n translate: new TranslateMode(),\n 'point-translate': new PointTranslateMode(),\n} as const;\n\n/**\n * Get the cached mode instance for an edit mode.\n *\n * Returns the pre-instantiated edit mode for the specified mode type.\n * Modes are cached at module level to prevent deck.gl assertion failures\n * that occur when creating new mode instances on each render.\n *\n * ## Available Edit Modes\n * - `'bounding-transform'`: For shapes without vertex editing (rectangles, ellipses)\n * - `'vertex-transform'`: For shapes with vertex editing (polygons, lines)\n * - `'circle-transform'`: For circles (resize from edge + translate)\n * - `'point-translate'`: For points (click to place + drag to move)\n * - `'translate'`: Generic translation (drag to move)\n *\n * @param mode - The edit mode to get the instance for\n * @returns The cached mode instance\n *\n * @example\n * ```typescript\n * import { getEditModeInstance } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes';\n *\n * // Get the bounding transform mode for editing rectangles/ellipses\n * const boundingMode = getEditModeInstance('bounding-transform');\n *\n * // Use with EditableGeoJsonLayer\n * const layer = new EditableGeoJsonLayer({\n * mode: boundingMode,\n * // ... other props\n * });\n * ```\n */\nexport function getEditModeInstance(\n mode: EditMode,\n): (typeof EDIT_MODE_INSTANCES)[EditMode] {\n return EDIT_MODE_INSTANCES[mode];\n}\n\n/**\n * Get the ViewMode instance (for when not editing).\n *\n * Returns the pre-instantiated ViewMode which is the default mode when\n * no editing operation is active. This mode allows viewing and interacting\n * with the map without editing shapes.\n *\n * @returns The cached ViewMode instance\n *\n * @example\n * ```typescript\n * import { getViewModeInstance } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes';\n *\n * // Get the view mode (default when not editing)\n * const viewMode = getViewModeInstance();\n *\n * // Use with EditableGeoJsonLayer\n * const layer = new EditableGeoJsonLayer({\n * mode: viewMode,\n * // ... other props\n * });\n * ```\n */\nexport function getViewModeInstance(): ViewMode {\n return EDIT_MODE_INSTANCES.view;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAM,sBAAsB;CAC1B,MAAM,IAAI,UAAU;CACpB,sBAAsB,IAAI,uBAAuB;CACjD,oBAAoB,IAAI,qBAAqB;CAC7C,oBAAoB,IAAI,qBAAqB;CAC7C,WAAW,IAAI,eAAe;CAC9B,mBAAmB,IAAI,oBAAoB;CAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCD,SAAgB,oBACd,MACwC;AACxC,QAAO,oBAAoB"}
@@ -0,0 +1,129 @@
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 { GeoJsonEditMode, TranslateMode } from "@deck.gl-community/editable-layers";
15
+
16
+ //#region src/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.ts
17
+ /**
18
+ * Edit mode for Point shapes that supports both click-to-place and drag behaviors.
19
+ *
20
+ * ## Capabilities
21
+ * This mode provides two ways to reposition a point:
22
+ * - **Click on empty space**: Instantly moves the point to the clicked location
23
+ * - **Drag the point**: Traditional click-and-drag behavior (via TranslateMode)
24
+ *
25
+ * ## Behavior Details
26
+ * - Clicking anywhere on the map (that isn't the point itself) repositions the point
27
+ * - Clicking directly on the point and dragging works as traditional translation
28
+ * - Both behaviors emit the 'translated' edit type for consistency with existing event handling
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { PointTranslateMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/point-translate-mode';
33
+ * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';
34
+ *
35
+ * // Used internally by EditShapeLayer for points
36
+ * const mode = new PointTranslateMode();
37
+ *
38
+ * const layer = new EditableGeoJsonLayer({
39
+ * mode,
40
+ * data: pointFeatureCollection,
41
+ * selectedFeatureIndexes: [0],
42
+ * onEdit: handleEdit,
43
+ * });
44
+ * ```
45
+ */
46
+ var PointTranslateMode = class extends GeoJsonEditMode {
47
+ translateMode = new TranslateMode();
48
+ /**
49
+ * Handle click events to reposition the point.
50
+ *
51
+ * If the click is on empty map space (not on the point itself),
52
+ * moves the point to the clicked location immediately.
53
+ *
54
+ * @param event - Click event containing map coordinates and pick information
55
+ * @param props - Mode props containing data, selected indexes, and edit callback
56
+ */
57
+ handleClick(event, props) {
58
+ if (event.picks?.some((pick) => pick.isGuide || pick.featureIndex !== void 0)) return;
59
+ const { mapCoords } = event;
60
+ const selectedIndex = props.selectedIndexes?.[0];
61
+ if (selectedIndex === void 0) return;
62
+ const feature = props.data.features[selectedIndex];
63
+ if (!feature) return;
64
+ const updatedFeature = {
65
+ ...feature,
66
+ geometry: {
67
+ type: "Point",
68
+ coordinates: mapCoords
69
+ }
70
+ };
71
+ props.onEdit({
72
+ updatedData: {
73
+ ...props.data,
74
+ features: [updatedFeature]
75
+ },
76
+ editType: "translated",
77
+ editContext: { featureIndexes: [selectedIndex] }
78
+ });
79
+ }
80
+ /**
81
+ * Delegate pointer move events to TranslateMode for cursor updates.
82
+ *
83
+ * @param event - Pointer move event with current cursor position
84
+ * @param props - Mode props containing state and configuration
85
+ */
86
+ handlePointerMove(event, props) {
87
+ this.translateMode.handlePointerMove(event, props);
88
+ }
89
+ /**
90
+ * Delegate start dragging to TranslateMode for traditional drag behavior.
91
+ *
92
+ * @param event - Drag start event with pointer down coordinates
93
+ * @param props - Mode props containing data and edit callback
94
+ */
95
+ handleStartDragging(event, props) {
96
+ this.translateMode.handleStartDragging(event, props);
97
+ }
98
+ /**
99
+ * Delegate dragging to TranslateMode for traditional drag behavior.
100
+ *
101
+ * @param event - Dragging event with current and previous pointer positions
102
+ * @param props - Mode props containing data and edit callback
103
+ */
104
+ handleDragging(event, props) {
105
+ this.translateMode.handleDragging(event, props);
106
+ }
107
+ /**
108
+ * Delegate stop dragging to TranslateMode for traditional drag behavior.
109
+ *
110
+ * @param event - Drag stop event with final pointer position
111
+ * @param props - Mode props containing data and edit callback
112
+ */
113
+ handleStopDragging(event, props) {
114
+ this.translateMode.handleStopDragging(event, props);
115
+ }
116
+ /**
117
+ * Delegate guide rendering to TranslateMode.
118
+ *
119
+ * @param props - Mode props containing data and selected indexes
120
+ * @returns Guide feature collection for rendering edit handles
121
+ */
122
+ getGuides(props) {
123
+ return this.translateMode.getGuides(props);
124
+ }
125
+ };
126
+
127
+ //#endregion
128
+ export { PointTranslateMode };
129
+ //# sourceMappingURL=point-translate-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"point-translate-mode.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/point-translate-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 ClickEvent,\n type DraggingEvent,\n type FeatureCollection,\n GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type StartDraggingEvent,\n type StopDraggingEvent,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\n\n/**\n * Edit mode for Point shapes that supports both click-to-place and drag behaviors.\n *\n * ## Capabilities\n * This mode provides two ways to reposition a point:\n * - **Click on empty space**: Instantly moves the point to the clicked location\n * - **Drag the point**: Traditional click-and-drag behavior (via TranslateMode)\n *\n * ## Behavior Details\n * - Clicking anywhere on the map (that isn't the point itself) repositions the point\n * - Clicking directly on the point and dragging works as traditional translation\n * - Both behaviors emit the 'translated' edit type for consistency with existing event handling\n *\n * @example\n * ```typescript\n * import { PointTranslateMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/point-translate-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for points\n * const mode = new PointTranslateMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: pointFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * });\n * ```\n */\nexport class PointTranslateMode extends GeoJsonEditMode {\n private translateMode = new TranslateMode();\n\n /**\n * Handle click events to reposition the point.\n *\n * If the click is on empty map space (not on the point itself),\n * moves the point to the clicked location immediately.\n *\n * @param event - Click event containing map coordinates and pick information\n * @param props - Mode props containing data, selected indexes, and edit callback\n */\n override handleClick(\n event: ClickEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // If clicked on the point itself or a guide, let drag handle it\n const clickedOnFeature = event.picks?.some(\n (pick) => pick.isGuide || pick.featureIndex !== undefined,\n );\n\n if (clickedOnFeature) {\n return;\n }\n\n const { mapCoords } = event;\n const selectedIndex = props.selectedIndexes?.[0];\n\n if (selectedIndex === undefined) {\n return;\n }\n\n const feature = props.data.features[selectedIndex];\n\n if (!feature) {\n return;\n }\n\n // Create updated feature with new coordinates\n const updatedFeature = {\n ...feature,\n geometry: {\n type: 'Point' as const,\n coordinates: mapCoords,\n },\n };\n\n // Emit edit action with 'translated' type to work with existing completion handlers\n props.onEdit({\n updatedData: {\n ...props.data,\n features: [updatedFeature],\n },\n editType: 'translated',\n editContext: {\n featureIndexes: [selectedIndex],\n },\n });\n }\n\n /**\n * Delegate pointer move events to TranslateMode for cursor updates.\n *\n * @param event - Pointer move event with current cursor position\n * @param props - Mode props containing state and configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handlePointerMove(event, props);\n }\n\n /**\n * Delegate start dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Drag start event with pointer down coordinates\n * @param props - Mode props containing data and edit callback\n */\n override handleStartDragging(\n event: StartDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleStartDragging(event, props);\n }\n\n /**\n * Delegate dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Dragging event with current and previous pointer positions\n * @param props - Mode props containing data and edit callback\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleDragging(event, props);\n }\n\n /**\n * Delegate stop dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Drag stop event with final pointer position\n * @param props - Mode props containing data and edit callback\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleStopDragging(event, props);\n }\n\n /**\n * Delegate guide rendering to TranslateMode.\n *\n * @param props - Mode props containing data and selected indexes\n * @returns Guide feature collection for rendering edit handles\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n return this.translateMode.getGuides(props);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,qBAAb,cAAwC,gBAAgB;CACtD,AAAQ,gBAAgB,IAAI,eAAe;;;;;;;;;;CAW3C,AAAS,YACP,OACA,OACM;AAMN,MAJyB,MAAM,OAAO,MACnC,SAAS,KAAK,WAAW,KAAK,iBAAiB,OACjD,CAGC;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBAAgB,MAAM,kBAAkB;AAE9C,MAAI,kBAAkB,OACpB;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAEpC,MAAI,CAAC,QACH;EAIF,MAAM,iBAAiB;GACrB,GAAG;GACH,UAAU;IACR,MAAM;IACN,aAAa;IACd;GACF;AAGD,QAAM,OAAO;GACX,aAAa;IACX,GAAG,MAAM;IACT,UAAU,CAAC,eAAe;IAC3B;GACD,UAAU;GACV,aAAa,EACX,gBAAgB,CAAC,cAAc,EAChC;GACF,CAAC;;;;;;;;CASJ,AAAS,kBACP,OACA,OACM;AACN,OAAK,cAAc,kBAAkB,OAAO,MAAM;;;;;;;;CASpD,AAAS,oBACP,OACA,OACM;AACN,OAAK,cAAc,oBAAoB,OAAO,MAAM;;;;;;;;CAStD,AAAS,eACP,OACA,OACM;AACN,OAAK,cAAc,eAAe,OAAO,MAAM;;;;;;;;CASjD,AAAS,mBACP,OACA,OACM;AACN,OAAK,cAAc,mBAAmB,OAAO,MAAM;;;;;;;;CASrD,AAAS,UACP,OACwB;AACxB,SAAO,KAAK,cAAc,UAAU,MAAM"}
@@ -38,11 +38,40 @@ function snapAngle(angle, interval) {
38
38
  /**
39
39
  * Extends RotateMode to support snapping rotation to 45° intervals.
40
40
  *
41
- * Features:
42
- * - Default: Free rotation
43
- * - With modeConfig.snapRotation = true: Snap to 45° intervals (0°, 45°, 90°, etc.)
41
+ * ## Features
42
+ * - **Default**: Free rotation (smooth, continuous angles)
43
+ * - **With Shift**: Snap to 45° intervals (0°, 45°, 90°, 135°, 180°, etc.)
44
44
  *
45
- * This allows precise alignment of shapes to common angles.
45
+ * This allows precise alignment of shapes to common angles, making it easy to
46
+ * create axis-aligned or diagonally-aligned shapes.
47
+ *
48
+ * ## Implementation
49
+ * The snap behavior is controlled by the `modeConfig.snapRotation` property,
50
+ * which is set by BaseTransformMode when the Shift key is held. This class
51
+ * calculates the rotation angle and rounds it to the nearest 45° interval
52
+ * when snapping is enabled.
53
+ *
54
+ * ## Snap Interval
55
+ * The snap interval is fixed at 45° (8 positions around the circle), providing
56
+ * these angles: 0°, 45°, 90°, 135°, 180°, 225°, 270°, 315°.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import { RotateModeWithSnap } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap';
61
+ * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';
62
+ *
63
+ * const mode = new RotateModeWithSnap();
64
+ *
65
+ * const layer = new EditableGeoJsonLayer({
66
+ * mode,
67
+ * data: featureCollection,
68
+ * selectedFeatureIndexes: [0],
69
+ * onEdit: handleEdit,
70
+ * modeConfig: {
71
+ * snapRotation: true, // Enable 45° snapping
72
+ * },
73
+ * });
74
+ * ```
46
75
  */
47
76
  var RotateModeWithSnap = class extends RotateMode {
48
77
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"rotate-mode-with-snap.js","names":["rotatedFeatures: FeatureCollection"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.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 ImmutableFeatureCollection,\n type ModeProps,\n RotateMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport { bearing, centroid, transformRotate } from '@turf/turf';\nimport type { Position } from 'geojson';\n\n/** Snap interval in degrees (45° = 8 positions around the circle) */\nconst SNAP_INTERVAL_DEGREES = 45;\n\n/**\n * Calculate the angle between two points relative to a centroid.\n * Uses turfBearing for geographic bearing convention (matches parent RotateMode).\n * Returns angle in degrees.\n *\n * Note: centroid must be a turf Point Feature (not just coordinates) to match\n * the parent RotateMode's behavior exactly.\n */\nfunction getRotationAngle(\n centroidFeature: ReturnType<typeof centroid>,\n startPoint: Position,\n endPoint: Position,\n): number {\n const bearing1 = bearing(centroidFeature, startPoint);\n const bearing2 = bearing(centroidFeature, endPoint);\n return bearing2 - bearing1;\n}\n\n/**\n * Snap an angle to the nearest interval.\n */\nfunction snapAngle(angle: number, interval: number): number {\n return Math.round(angle / interval) * interval;\n}\n\n/**\n * Extends RotateMode to support snapping rotation to 45° intervals.\n *\n * Features:\n * - Default: Free rotation\n * - With modeConfig.snapRotation = true: Snap to 45° intervals (0°, 45°, 90°, etc.)\n *\n * This allows precise alignment of shapes to common angles.\n */\nexport class RotateModeWithSnap extends RotateMode {\n /**\n * Override handleDragging to support snapped rotation.\n * When snapRotation is true, rotates to nearest 45° interval.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isRotating) {\n return;\n }\n\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotating',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final rotated geometry with snap.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleStopDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isRotating) {\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotated',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n // Reset state\n self._geometryBeingRotated = null;\n self._selectedEditHandle = null;\n self._isRotating = false;\n }\n }\n\n /**\n * Get a rotate action, optionally snapping to 45° intervals.\n */\n private getRotateActionWithSnap(\n startDragPoint: Position,\n currentPoint: Position,\n editType: string,\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._geometryBeingRotated) {\n return null;\n }\n\n const geometry = self._geometryBeingRotated as FeatureCollection;\n // @ts-expect-error turf types differ from editable-layers types\n const centerFeature = centroid(geometry);\n\n // Calculate the rotation angle (pass centroid Feature to match parent RotateMode)\n let angle = getRotationAngle(centerFeature, startDragPoint, currentPoint);\n\n // Snap to 45° intervals if enabled\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n if (snapRotation) {\n angle = snapAngle(angle, SNAP_INTERVAL_DEGREES);\n }\n\n // Apply the rotation using turf (use centroid Feature as pivot to match parent)\n // @ts-expect-error turf types differ from editable-layers types\n const rotatedFeatures: FeatureCollection = transformRotate(\n // @ts-expect-error turf types differ from editable-layers types\n geometry,\n angle,\n {\n pivot: centerFeature,\n },\n );\n\n // Build the updated data using ImmutableFeatureCollection (matches parent RotateMode)\n const selectedIndexes = props.selectedIndexes;\n let updatedData = new ImmutableFeatureCollection(props.data);\n\n for (let i = 0; i < selectedIndexes.length; i++) {\n const selectedIndex = selectedIndexes[i];\n const movedFeature = rotatedFeatures.features[i];\n if (selectedIndex !== undefined && movedFeature) {\n updatedData = updatedData.replaceGeometry(\n selectedIndex,\n movedFeature.geometry,\n );\n }\n }\n\n return {\n updatedData: updatedData.getObject(),\n editType,\n editContext: {\n featureIndexes: selectedIndexes,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,wBAAwB;;;;;;;;;AAU9B,SAAS,iBACP,iBACA,YACA,UACQ;CACR,MAAM,WAAW,QAAQ,iBAAiB,WAAW;AAErD,QADiB,QAAQ,iBAAiB,SAAS,GACjC;;;;;AAMpB,SAAS,UAAU,OAAe,UAA0B;AAC1D,QAAO,KAAK,MAAM,QAAQ,SAAS,GAAG;;;;;;;;;;;AAYxC,IAAa,qBAAb,cAAwC,WAAW;;;;;;CAMjD,AAAS,eACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,eAAe,OAAO,MAAM;AAClC;;AAMF,MAAI,CAFS,KAEH,YACR;EAGF,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,YACA,MACD;AAED,MAAI,aACF,OAAM,OAAO,aAAa;AAG5B,QAAM,WAAW;;;;;;CAOnB,AAAS,mBACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,mBAAmB,OAAO,MAAM;AACtC;;EAIF,MAAM,OAAO;AAEb,MAAI,KAAK,aAAa;GACpB,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,WACA,MACD;AAED,OAAI,aACF,OAAM,OAAO,aAAa;AAI5B,QAAK,wBAAwB;AAC7B,QAAK,sBAAsB;AAC3B,QAAK,cAAc;;;;;;CAOvB,AAAQ,wBACN,gBACA,cACA,UACA,OACA;EAEA,MAAM,OAAO;AAEb,MAAI,CAAC,KAAK,sBACR,QAAO;EAGT,MAAM,WAAW,KAAK;EAEtB,MAAM,gBAAgB,SAAS,SAAS;EAGxC,IAAI,QAAQ,iBAAiB,eAAe,gBAAgB,aAAa;AAIzE,MADqB,MAAM,YAAY,gBAAgB,MAErD,SAAQ,UAAU,OAAO,sBAAsB;EAKjD,MAAMA,kBAAqC,gBAEzC,UACA,OACA,EACE,OAAO,eACR,CACF;EAGD,MAAM,kBAAkB,MAAM;EAC9B,IAAI,cAAc,IAAI,2BAA2B,MAAM,KAAK;AAE5D,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,gBAAgB,gBAAgB;GACtC,MAAM,eAAe,gBAAgB,SAAS;AAC9C,OAAI,kBAAkB,UAAa,aACjC,eAAc,YAAY,gBACxB,eACA,aAAa,SACd;;AAIL,SAAO;GACL,aAAa,YAAY,WAAW;GACpC;GACA,aAAa,EACX,gBAAgB,iBACjB;GACF"}
1
+ {"version":3,"file":"rotate-mode-with-snap.js","names":["rotatedFeatures: FeatureCollection"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.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 ImmutableFeatureCollection,\n type ModeProps,\n RotateMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport { bearing, centroid, transformRotate } from '@turf/turf';\nimport type { Position } from 'geojson';\n\n/** Snap interval in degrees (45° = 8 positions around the circle) */\nconst SNAP_INTERVAL_DEGREES = 45;\n\n/**\n * Calculate the angle between two points relative to a centroid.\n * Uses turfBearing for geographic bearing convention (matches parent RotateMode).\n * Returns angle in degrees.\n *\n * Note: centroid must be a turf Point Feature (not just coordinates) to match\n * the parent RotateMode's behavior exactly.\n */\nfunction getRotationAngle(\n centroidFeature: ReturnType<typeof centroid>,\n startPoint: Position,\n endPoint: Position,\n): number {\n const bearing1 = bearing(centroidFeature, startPoint);\n const bearing2 = bearing(centroidFeature, endPoint);\n return bearing2 - bearing1;\n}\n\n/**\n * Snap an angle to the nearest interval.\n */\nfunction snapAngle(angle: number, interval: number): number {\n return Math.round(angle / interval) * interval;\n}\n\n/**\n * Extends RotateMode to support snapping rotation to 45° intervals.\n *\n * ## Features\n * - **Default**: Free rotation (smooth, continuous angles)\n * - **With Shift**: Snap to 45° intervals (0°, 45°, 90°, 135°, 180°, etc.)\n *\n * This allows precise alignment of shapes to common angles, making it easy to\n * create axis-aligned or diagonally-aligned shapes.\n *\n * ## Implementation\n * The snap behavior is controlled by the `modeConfig.snapRotation` property,\n * which is set by BaseTransformMode when the Shift key is held. This class\n * calculates the rotation angle and rounds it to the nearest 45° interval\n * when snapping is enabled.\n *\n * ## Snap Interval\n * The snap interval is fixed at 45° (8 positions around the circle), providing\n * these angles: 0°, 45°, 90°, 135°, 180°, 225°, 270°, 315°.\n *\n * @example\n * ```typescript\n * import { RotateModeWithSnap } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * const mode = new RotateModeWithSnap();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: featureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: {\n * snapRotation: true, // Enable 45° snapping\n * },\n * });\n * ```\n */\nexport class RotateModeWithSnap extends RotateMode {\n /**\n * Override handleDragging to support snapped rotation.\n * When snapRotation is true, rotates to nearest 45° interval.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isRotating) {\n return;\n }\n\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotating',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final rotated geometry with snap.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleStopDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isRotating) {\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotated',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n // Reset state\n self._geometryBeingRotated = null;\n self._selectedEditHandle = null;\n self._isRotating = false;\n }\n }\n\n /**\n * Get a rotate action, optionally snapping to 45° intervals.\n */\n private getRotateActionWithSnap(\n startDragPoint: Position,\n currentPoint: Position,\n editType: string,\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._geometryBeingRotated) {\n return null;\n }\n\n const geometry = self._geometryBeingRotated as FeatureCollection;\n // @ts-expect-error turf types differ from editable-layers types\n const centerFeature = centroid(geometry);\n\n // Calculate the rotation angle (pass centroid Feature to match parent RotateMode)\n let angle = getRotationAngle(centerFeature, startDragPoint, currentPoint);\n\n // Snap to 45° intervals if enabled\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n if (snapRotation) {\n angle = snapAngle(angle, SNAP_INTERVAL_DEGREES);\n }\n\n // Apply the rotation using turf (use centroid Feature as pivot to match parent)\n // @ts-expect-error turf types differ from editable-layers types\n const rotatedFeatures: FeatureCollection = transformRotate(\n // @ts-expect-error turf types differ from editable-layers types\n geometry,\n angle,\n {\n pivot: centerFeature,\n },\n );\n\n // Build the updated data using ImmutableFeatureCollection (matches parent RotateMode)\n const selectedIndexes = props.selectedIndexes;\n let updatedData = new ImmutableFeatureCollection(props.data);\n\n for (let i = 0; i < selectedIndexes.length; i++) {\n const selectedIndex = selectedIndexes[i];\n const movedFeature = rotatedFeatures.features[i];\n if (selectedIndex !== undefined && movedFeature) {\n updatedData = updatedData.replaceGeometry(\n selectedIndex,\n movedFeature.geometry,\n );\n }\n }\n\n return {\n updatedData: updatedData.getObject(),\n editType,\n editContext: {\n featureIndexes: selectedIndexes,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,wBAAwB;;;;;;;;;AAU9B,SAAS,iBACP,iBACA,YACA,UACQ;CACR,MAAM,WAAW,QAAQ,iBAAiB,WAAW;AAErD,QADiB,QAAQ,iBAAiB,SAAS,GACjC;;;;;AAMpB,SAAS,UAAU,OAAe,UAA0B;AAC1D,QAAO,KAAK,MAAM,QAAQ,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCxC,IAAa,qBAAb,cAAwC,WAAW;;;;;;CAMjD,AAAS,eACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,eAAe,OAAO,MAAM;AAClC;;AAMF,MAAI,CAFS,KAEH,YACR;EAGF,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,YACA,MACD;AAED,MAAI,aACF,OAAM,OAAO,aAAa;AAG5B,QAAM,WAAW;;;;;;CAOnB,AAAS,mBACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,mBAAmB,OAAO,MAAM;AACtC;;EAIF,MAAM,OAAO;AAEb,MAAI,KAAK,aAAa;GACpB,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,WACA,MACD;AAED,OAAI,aACF,OAAM,OAAO,aAAa;AAI5B,QAAK,wBAAwB;AAC7B,QAAK,sBAAsB;AAC3B,QAAK,cAAc;;;;;;CAOvB,AAAQ,wBACN,gBACA,cACA,UACA,OACA;EAEA,MAAM,OAAO;AAEb,MAAI,CAAC,KAAK,sBACR,QAAO;EAGT,MAAM,WAAW,KAAK;EAEtB,MAAM,gBAAgB,SAAS,SAAS;EAGxC,IAAI,QAAQ,iBAAiB,eAAe,gBAAgB,aAAa;AAIzE,MADqB,MAAM,YAAY,gBAAgB,MAErD,SAAQ,UAAU,OAAO,sBAAsB;EAKjD,MAAMA,kBAAqC,gBAEzC,UACA,OACA,EACE,OAAO,eACR,CACF;EAGD,MAAM,kBAAkB,MAAM;EAC9B,IAAI,cAAc,IAAI,2BAA2B,MAAM,KAAK;AAE5D,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,gBAAgB,gBAAgB;GACtC,MAAM,eAAe,gBAAgB,SAAS;AAC9C,OAAI,kBAAkB,UAAa,aACjC,eAAc,YAAY,gBACxB,eACA,aAAa,SACd;;AAIL,SAAO;GACL,aAAa,YAAY,WAAW;GACpC;GACA,aAAa,EACX,gBAAgB,iBACjB;GACF"}
@@ -18,8 +18,8 @@ import { ScaleMode } from "@deck.gl-community/editable-layers";
18
18
  * Extends ScaleMode to support non-uniform (free) scaling.
19
19
  *
20
20
  * ## Features
21
- * - Default: Free scaling - can stretch/squish in any direction
22
- * - With modeConfig.lockScaling = true: Uniform scaling (maintains aspect ratio)
21
+ * - **Default**: Free scaling - can stretch/squish in any direction
22
+ * - **With Shift**: Uniform scaling (maintains aspect ratio)
23
23
  *
24
24
  * ## How Non-Uniform Scaling Works
25
25
  *
@@ -53,6 +53,25 @@ import { ScaleMode } from "@deck.gl-community/editable-layers";
53
53
  * All scale factors are clamped to a minimum of 0.01 to prevent:
54
54
  * - Shape inversion (negative scale flipping the shape inside-out)
55
55
  * - Shape collapse (scale of 0 making the shape a point/line)
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * import { ScaleModeWithFreeTransform } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform';
60
+ * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';
61
+ *
62
+ * const mode = new ScaleModeWithFreeTransform();
63
+ *
64
+ * const layer = new EditableGeoJsonLayer({
65
+ * mode,
66
+ * data: featureCollection,
67
+ * selectedFeatureIndexes: [0],
68
+ * onEdit: handleEdit,
69
+ * modeConfig: {
70
+ * lockScaling: false, // Default: free scaling (stretch/squish)
71
+ * // lockScaling: true, // Hold Shift: uniform scaling (maintain aspect ratio)
72
+ * },
73
+ * });
74
+ * ```
56
75
  */
57
76
  var ScaleModeWithFreeTransform = class extends ScaleMode {
58
77
  /**