@accelint/map-toolkit 2.0.0 → 3.0.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 (88) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -1
  3. package/catalog-info.yaml +9 -6
  4. package/dist/deckgl/base-map/index.d.ts +2 -2
  5. package/dist/deckgl/base-map/provider.d.ts +2 -2
  6. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.d.ts +144 -0
  7. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js +535 -0
  8. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js.map +1 -0
  9. package/dist/deckgl/extensions/coffin-corner/index.d.ts +17 -0
  10. package/dist/deckgl/extensions/coffin-corner/index.js +19 -0
  11. package/dist/deckgl/extensions/coffin-corner/store.d.ts +96 -0
  12. package/dist/deckgl/extensions/coffin-corner/store.js +173 -0
  13. package/dist/deckgl/extensions/coffin-corner/store.js.map +1 -0
  14. package/dist/deckgl/extensions/coffin-corner/types.d.ts +76 -0
  15. package/dist/deckgl/extensions/coffin-corner/types.js +27 -0
  16. package/dist/deckgl/extensions/coffin-corner/types.js.map +1 -0
  17. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.d.ts +81 -0
  18. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js +75 -0
  19. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js.map +1 -0
  20. package/dist/deckgl/extensions/index.d.ts +15 -0
  21. package/dist/deckgl/extensions/index.js +16 -0
  22. package/dist/deckgl/index.d.ts +6 -1
  23. package/dist/deckgl/index.js +5 -1
  24. package/dist/deckgl/shapes/display-shape-layer/constants.js +6 -15
  25. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  26. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +31 -15
  27. package/dist/deckgl/shapes/display-shape-layer/index.js +97 -78
  28. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  29. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +8 -0
  30. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +3 -48
  31. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -1
  32. package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js +53 -0
  33. package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js.map +1 -0
  34. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +7 -3
  35. package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -3
  36. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
  37. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +4 -2
  38. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  39. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +3 -2
  40. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  41. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +5 -2
  42. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  43. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +5 -2
  44. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  45. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +7 -2
  46. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  47. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +2 -2
  48. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
  49. package/dist/deckgl/shapes/edit-shape-layer/index.js +22 -8
  50. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  51. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +3 -2
  52. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  53. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +4 -2
  54. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  55. package/dist/deckgl/shapes/edit-shape-layer/store.js +15 -4
  56. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  57. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +4 -2
  58. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +1 -1
  59. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +7 -3
  60. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -1
  61. package/dist/deckgl/shapes/index.d.ts +3 -2
  62. package/dist/deckgl/shapes/index.js +2 -1
  63. package/dist/deckgl/shapes/shared/constants.js +1 -1
  64. package/dist/deckgl/shapes/shared/types.d.ts +11 -4
  65. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  66. package/dist/deckgl/shapes/shared/utils/duplicate-shape.d.ts +56 -0
  67. package/dist/deckgl/shapes/shared/utils/duplicate-shape.js +131 -0
  68. package/dist/deckgl/shapes/shared/utils/duplicate-shape.js.map +1 -0
  69. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  70. package/dist/deckgl/shapes/shared/utils/layer-config.js +10 -7
  71. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
  72. package/dist/deckgl/symbol-layer/fiber.d.ts +3 -1
  73. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  74. package/dist/shared/units.d.ts +15 -56
  75. package/dist/shared/units.js +1 -52
  76. package/dist/shared/units.js.map +1 -1
  77. package/dist/viewport/index.d.ts +2 -3
  78. package/dist/viewport/index.js +1 -2
  79. package/dist/viewport/types.d.ts +8 -4
  80. package/dist/viewport/utils.d.ts +3 -3
  81. package/dist/viewport/utils.js +16 -8
  82. package/dist/viewport/utils.js.map +1 -1
  83. package/dist/viewport/viewport-size.d.ts +4 -3
  84. package/dist/viewport/viewport-size.js +2 -2
  85. package/dist/viewport/viewport-size.js.map +1 -1
  86. package/package.json +13 -6
  87. package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +0 -50
  88. package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"draw-ellipse-mode-with-tooltip.js","names":["center: [number, number]"],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.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 DrawEllipseUsingThreePointsMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatDistanceTooltip,\n formatEllipseTooltip,\n} from '../../shared/constants';\n\n/**\n * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.\n *\n * Provides different tooltip content based on the drawing stage:\n * - **Stage 1** (after first click): Distance from first point to cursor\n * - **Stage 2** (after second click): Major/minor axes dimensions and area\n *\n * ## Drawing Flow\n * 1. **First click**: Sets the first endpoint of the major axis\n * 2. **Second click**: Sets the second endpoint of the major axis\n * - While moving to second click, shows distance tooltip (like line/polygon)\n * 3. **Third click**: Sets the minor axis radius\n * - While moving to third click, shows dimensions and area tooltip\n *\n * ## Geometry Calculations\n * - **Center**: Midpoint between first and second clicks\n * - **Y semi-axis**: Half the distance between clicks 1 and 2\n * - **X semi-axis**: Distance from center to cursor (becomes distance to click 3)\n * - **Area**: π × X semi-axis × Y semi-axis\n *\n * @example\n * ```typescript\n * import { DrawEllipseModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawEllipseModeWithTooltip();\n * ```\n */\nexport class DrawEllipseModeWithTooltip extends DrawEllipseUsingThreePointsMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Handle pointer move events to update the tooltip based on drawing stage.\n *\n * Stage 1 (1 click): Shows distance from first point to cursor.\n * Stage 2 (2 clicks): Shows major/minor axes dimensions and ellipse area.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n const tooltipPosition = mapCoords;\n\n if (clickSequence.length === 1) {\n // First segment: show distance from first click to cursor (like line/polygon)\n const firstPoint = clickSequence[0] as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(firstPoint, currentPoint, { units: distanceUnits });\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n } else if (clickSequence.length === 2) {\n // Second segment: show dimensions and area (like rectangle)\n // The ellipse will have:\n // - center at midpoint of click1 and click2\n // - ySemiAxis = distance(click1, click2) / 2\n // - xSemiAxis = distance(center, cursor)\n const click1 = clickSequence[0] as [number, number];\n const click2 = clickSequence[1] as [number, number];\n const cursorPos = mapCoords as [number, number];\n\n // Calculate center (midpoint)\n const center: [number, number] = [\n (click1[0] + click2[0]) / 2,\n (click1[1] + click2[1]) / 2,\n ];\n\n // Calculate semi-axes\n const ySemiAxis = distance(click1, click2, { units: distanceUnits }) / 2;\n const xSemiAxis = distance(center, cursorPos, { units: distanceUnits });\n\n // Full axes (diameter equivalent)\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n\n // Ellipse area = π × a × b\n const ellipseArea = Math.PI * xSemiAxis * ySemiAxis;\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n ),\n };\n }\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,6BAAb,cAAgD,gCAAgC;;CAE9E,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EACrC,MAAM,aAAa,4BAA4B,cAAc;EAC7D,MAAM,kBAAkB;AAExB,MAAI,cAAc,WAAW,GAAG;GAE9B,MAAM,aAAa,cAAc;AAKjC,QAAK,UAAU;IACb,UAAU;IACV,MAAM,sBAJK,SAAS,YAFD,WAE2B,EAAE,OAAO,eAAe,CAAC,EAIrC,WAAW;IAC9C;aACQ,cAAc,WAAW,GAAG;GAMrC,MAAM,SAAS,cAAc;GAC7B,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY;GAGlB,MAAMA,SAA2B,EAC9B,OAAO,KAAK,OAAO,MAAM,IACzB,OAAO,KAAK,OAAO,MAAM,EAC3B;GAGD,MAAM,YAAY,SAAS,QAAQ,QAAQ,EAAE,OAAO,eAAe,CAAC,GAAG;GACvE,MAAM,YAAY,SAAS,QAAQ,WAAW,EAAE,OAAO,eAAe,CAAC;AASvE,QAAK,UAAU;IACb,UAAU;IACV,MAAM,qBARU,KAAK,IAAI,WAAW,UAAU,GAAG,GACjC,KAAK,IAAI,WAAW,UAAU,GAAG,GAG/B,KAAK,KAAK,YAAY,WAQtC,WACD;IACF;;;;;;;;CASL,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-ellipse-mode-with-tooltip.js","names":["center: [number, number]"],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.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 DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\nimport {\n DrawEllipseUsingThreePointsMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport { DEFAULT_DISTANCE_UNITS } from '@/shared/units';\nimport {\n formatDistanceTooltip,\n formatEllipseTooltip,\n} from '../../shared/constants';\n\n/**\n * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.\n *\n * Provides different tooltip content based on the drawing stage:\n * - **Stage 1** (after first click): Distance from first point to cursor\n * - **Stage 2** (after second click): Major/minor axes dimensions and area\n *\n * ## Drawing Flow\n * 1. **First click**: Sets the first endpoint of the major axis\n * 2. **Second click**: Sets the second endpoint of the major axis\n * - While moving to second click, shows distance tooltip (like line/polygon)\n * 3. **Third click**: Sets the minor axis radius\n * - While moving to third click, shows dimensions and area tooltip\n *\n * ## Geometry Calculations\n * - **Center**: Midpoint between first and second clicks\n * - **Y semi-axis**: Half the distance between clicks 1 and 2\n * - **X semi-axis**: Distance from center to cursor (becomes distance to click 3)\n * - **Area**: π × X semi-axis × Y semi-axis\n *\n * @example\n * ```typescript\n * import { DrawEllipseModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawEllipseModeWithTooltip();\n * ```\n */\nexport class DrawEllipseModeWithTooltip extends DrawEllipseUsingThreePointsMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Handle pointer move events to update the tooltip based on drawing stage.\n *\n * Stage 1 (1 click): Shows distance from first point to cursor.\n * Stage 2 (2 clicks): Shows major/minor axes dimensions and ellipse area.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n (props.modeConfig?.distanceUnits as DistanceUnit) ??\n DEFAULT_DISTANCE_UNITS;\n const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];\n const tooltipPosition = mapCoords;\n\n if (clickSequence.length === 1) {\n // First segment: show distance from first click to cursor (like line/polygon)\n const firstPoint = clickSequence[0] as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(firstPoint, currentPoint, { units: distanceUnits });\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n } else if (clickSequence.length === 2) {\n // Second segment: show dimensions and area (like rectangle)\n // The ellipse will have:\n // - center at midpoint of click1 and click2\n // - ySemiAxis = distance(click1, click2) / 2\n // - xSemiAxis = distance(center, cursor)\n const click1 = clickSequence[0] as [number, number];\n const click2 = clickSequence[1] as [number, number];\n const cursorPos = mapCoords as [number, number];\n\n // Calculate center (midpoint)\n const center: [number, number] = [\n (click1[0] + click2[0]) / 2,\n (click1[1] + click2[1]) / 2,\n ];\n\n // Calculate semi-axes\n const ySemiAxis = distance(click1, click2, { units: distanceUnits }) / 2;\n const xSemiAxis = distance(center, cursorPos, { units: distanceUnits });\n\n // Full axes (diameter equivalent)\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n\n // Ellipse area = π × a × b\n const ellipseArea = Math.PI * xSemiAxis * ySemiAxis;\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n ),\n };\n }\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,IAAa,6BAAb,cAAgD,gCAAgC;;CAE9E,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACH,MAAM,YAAY,iBACnB;EACF,MAAM,aAAa,sBAAsB;EACzC,MAAM,kBAAkB;AAExB,MAAI,cAAc,WAAW,GAAG;GAE9B,MAAM,aAAa,cAAc;AAKjC,QAAK,UAAU;IACb,UAAU;IACV,MAAM,sBAJK,SAAS,YAFD,WAE2B,EAAE,OAAO,eAAe,CAAC,EAIrC,WAAW;IAC9C;aACQ,cAAc,WAAW,GAAG;GAMrC,MAAM,SAAS,cAAc;GAC7B,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY;GAGlB,MAAMA,SAA2B,EAC9B,OAAO,KAAK,OAAO,MAAM,IACzB,OAAO,KAAK,OAAO,MAAM,EAC3B;GAGD,MAAM,YAAY,SAAS,QAAQ,QAAQ,EAAE,OAAO,eAAe,CAAC,GAAG;GACvE,MAAM,YAAY,SAAS,QAAQ,WAAW,EAAE,OAAO,eAAe,CAAC;AASvE,QAAK,UAAU;IACb,UAAU;IACV,MAAM,qBARU,KAAK,IAAI,WAAW,UAAU,GAAG,GACjC,KAAK,IAAI,WAAW,UAAU,GAAG,GAG/B,KAAK,KAAK,YAAY,WAQtC,WACD;IACF;;;;;;;;CASL,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -11,9 +11,10 @@
11
11
  */
12
12
 
13
13
 
14
- import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
14
+ import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
15
15
  import { formatDistanceTooltip } from "../../shared/constants.js";
16
16
  import { distance } from "@turf/turf";
17
+ import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
17
18
  import { DrawLineStringMode } from "@deck.gl-community/editable-layers";
18
19
 
19
20
  //#region src/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.ts
@@ -92,9 +93,11 @@ var DrawLineStringModeWithTooltip = class extends DrawLineStringMode {
92
93
  }
93
94
  const { mapCoords } = event;
94
95
  const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
96
+ const dist = distance(clickSequence.at(-1), mapCoords, { units: distanceUnits });
97
+ const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
95
98
  this.tooltip = {
96
99
  position: mapCoords,
97
- text: formatDistanceTooltip(distance(clickSequence.at(-1), mapCoords, { units: distanceUnits }), getDistanceUnitAbbreviation(distanceUnits))
100
+ text: formatDistanceTooltip(dist, unitAbbrev)
98
101
  };
99
102
  }
100
103
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"draw-line-string-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.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 DrawLineStringMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type SimpleFeatureCollection,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatDistanceTooltip } from '../../shared/constants';\n\n/**\n * Extends DrawLineStringMode to display distance tooltip between points.\n *\n * Shows the distance from the last clicked point to the current cursor position\n * while drawing a line string. The tooltip updates in real-time as the cursor moves.\n *\n * ## Drawing Flow\n * 1. Click to add first point\n * 2. Move cursor (tooltip shows distance from last point)\n * 3. Click to add more points\n * 4. Double-click to finish the line string\n *\n *\n * @example\n * ```typescript\n * import { DrawLineStringModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawLineStringModeWithTooltip();\n * ```\n */\nexport class DrawLineStringModeWithTooltip extends DrawLineStringMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Finish drawing the line string.\n *\n * Creates a LineString geometry from the click sequence and emits an edit action.\n * Requires at least 2 points to create a valid line string.\n * Extracted to share between double-click workaround and parent class logic.\n *\n * @param props - Mode properties with onEdit callback\n */\n override finishDrawing(props: ModeProps<SimpleFeatureCollection>): void {\n const clickSequence = this.getClickSequence();\n if (clickSequence.length <= 1) {\n return;\n }\n\n const lineStringToAdd = {\n type: 'LineString' as const,\n coordinates: [...clickSequence],\n };\n\n this.resetClickSequence();\n this.tooltip = null;\n\n const editAction = this.getAddFeatureAction(lineStringToAdd, props.data);\n if (editAction) {\n props.onEdit(editAction);\n }\n }\n\n /**\n * Override handleClick to store props for double-click workaround.\n *\n * Caches the mode props so that the external double-click handler can\n * access them when finishing the drawing.\n *\n * @param event - Click event with map coordinates\n * @param props - Mode properties including onEdit callback\n */\n override handleClick(\n event: ClickEvent,\n props: ModeProps<SimpleFeatureCollection>,\n ): void {\n super.handleClick(event, props);\n }\n\n /**\n * Handle pointer move events to update the tooltip with distance.\n *\n * Calculates the distance from the last clicked point to the current\n * cursor position and displays it in the configured distance units.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n const lastPoint = clickSequence.at(-1) as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(lastPoint, currentPoint, { units: distanceUnits });\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n this.tooltip = {\n position: mapCoords,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,gCAAb,cAAmD,mBAAmB;;CAEpE,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,cAAc,OAAiD;EACtE,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,cAAc,UAAU,EAC1B;EAGF,MAAM,kBAAkB;GACtB,MAAM;GACN,aAAa,CAAC,GAAG,cAAc;GAChC;AAED,OAAK,oBAAoB;AACzB,OAAK,UAAU;EAEf,MAAM,aAAa,KAAK,oBAAoB,iBAAiB,MAAM,KAAK;AACxE,MAAI,WACF,OAAM,OAAO,WAAW;;;;;;;;;;;CAa5B,AAAS,YACP,OACA,OACM;AACN,QAAM,YAAY,OAAO,MAAM;;;;;;;;;;;CAYjC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;AAQrC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,sBALK,SAHK,cAAc,GAAG,GAAG,EACjB,WAE0B,EAAE,OAAO,eAAe,CAAC,EACrD,4BAA4B,cAAc,CAId;GAC9C;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-line-string-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.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 DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\nimport {\n type ClickEvent,\n DrawLineStringMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type SimpleFeatureCollection,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport { DEFAULT_DISTANCE_UNITS } from '@/shared/units';\nimport { formatDistanceTooltip } from '../../shared/constants';\n\n/**\n * Extends DrawLineStringMode to display distance tooltip between points.\n *\n * Shows the distance from the last clicked point to the current cursor position\n * while drawing a line string. The tooltip updates in real-time as the cursor moves.\n *\n * ## Drawing Flow\n * 1. Click to add first point\n * 2. Move cursor (tooltip shows distance from last point)\n * 3. Click to add more points\n * 4. Double-click to finish the line string\n *\n *\n * @example\n * ```typescript\n * import { DrawLineStringModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawLineStringModeWithTooltip();\n * ```\n */\nexport class DrawLineStringModeWithTooltip extends DrawLineStringMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Finish drawing the line string.\n *\n * Creates a LineString geometry from the click sequence and emits an edit action.\n * Requires at least 2 points to create a valid line string.\n * Extracted to share between double-click workaround and parent class logic.\n *\n * @param props - Mode properties with onEdit callback\n */\n override finishDrawing(props: ModeProps<SimpleFeatureCollection>): void {\n const clickSequence = this.getClickSequence();\n if (clickSequence.length <= 1) {\n return;\n }\n\n const lineStringToAdd = {\n type: 'LineString' as const,\n coordinates: [...clickSequence],\n };\n\n this.resetClickSequence();\n this.tooltip = null;\n\n const editAction = this.getAddFeatureAction(lineStringToAdd, props.data);\n if (editAction) {\n props.onEdit(editAction);\n }\n }\n\n /**\n * Override handleClick to store props for double-click workaround.\n *\n * Caches the mode props so that the external double-click handler can\n * access them when finishing the drawing.\n *\n * @param event - Click event with map coordinates\n * @param props - Mode properties including onEdit callback\n */\n override handleClick(\n event: ClickEvent,\n props: ModeProps<SimpleFeatureCollection>,\n ): void {\n super.handleClick(event, props);\n }\n\n /**\n * Handle pointer move events to update the tooltip with distance.\n *\n * Calculates the distance from the last clicked point to the current\n * cursor position and displays it in the configured distance units.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n (props.modeConfig?.distanceUnits as DistanceUnit) ??\n DEFAULT_DISTANCE_UNITS;\n\n const lastPoint = clickSequence.at(-1) as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(lastPoint, currentPoint, { units: distanceUnits });\n const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];\n\n this.tooltip = {\n position: mapCoords,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,gCAAb,cAAmD,mBAAmB;;CAEpE,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,cAAc,OAAiD;EACtE,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,cAAc,UAAU,EAC1B;EAGF,MAAM,kBAAkB;GACtB,MAAM;GACN,aAAa,CAAC,GAAG,cAAc;GAChC;AAED,OAAK,oBAAoB;AACzB,OAAK,UAAU;EAEf,MAAM,aAAa,KAAK,oBAAoB,iBAAiB,MAAM,KAAK;AACxE,MAAI,WACF,OAAM,OAAO,WAAW;;;;;;;;;;;CAa5B,AAAS,YACP,OACA,OACM;AACN,QAAM,YAAY,OAAO,MAAM;;;;;;;;;;;CAYjC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACH,MAAM,YAAY,iBACnB;EAKF,MAAM,OAAO,SAHK,cAAc,GAAG,GAAG,EACjB,WAE0B,EAAE,OAAO,eAAe,CAAC;EACxE,MAAM,aAAa,sBAAsB;AAEzC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,sBAAsB,MAAM,WAAW;GAC9C;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -11,9 +11,10 @@
11
11
  */
12
12
 
13
13
 
14
- import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
14
+ import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
15
15
  import { formatDistanceTooltip } from "../../shared/constants.js";
16
16
  import { distance } from "@turf/turf";
17
+ import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
17
18
  import { DrawPolygonMode } from "@deck.gl-community/editable-layers";
18
19
 
19
20
  //#region src/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.ts
@@ -81,9 +82,11 @@ var DrawPolygonModeWithTooltip = class extends DrawPolygonMode {
81
82
  }
82
83
  const { mapCoords } = event;
83
84
  const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
85
+ const dist = distance(clickSequence.at(-1), mapCoords, { units: distanceUnits });
86
+ const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
84
87
  this.tooltip = {
85
88
  position: mapCoords,
86
- text: formatDistanceTooltip(distance(clickSequence.at(-1), mapCoords, { units: distanceUnits }), getDistanceUnitAbbreviation(distanceUnits))
89
+ text: formatDistanceTooltip(dist, unitAbbrev)
87
90
  };
88
91
  }
89
92
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"draw-polygon-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.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 DrawPolygonMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type SimpleFeatureCollection,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatDistanceTooltip } from '../../shared/constants';\n\n/**\n * Extends DrawPolygonMode to display distance tooltip between points.\n *\n * Shows the distance from the last clicked point to the current cursor position\n * while drawing a polygon. The tooltip updates in real-time as the cursor moves.\n *\n * ## Drawing Flow\n * 1. Click to add first vertex\n * 2. Move cursor (tooltip shows distance from last vertex)\n * 3. Click to add more vertices\n * 4. Double-click or click the starting point to close the polygon\n *\n *\n * @example\n * ```typescript\n * import { DrawPolygonModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawPolygonModeWithTooltip();\n * ```\n */\nexport class DrawPolygonModeWithTooltip extends DrawPolygonMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Finish drawing the polygon.\n *\n * Creates a Polygon geometry from the click sequence and emits an edit action.\n * Requires at least 3 points to create a valid polygon. Automatically closes\n * the polygon by adding the first point to the end of the coordinate ring.\n *\n * @param props - Mode properties with onEdit callback\n */\n override finishDrawing(props: ModeProps<SimpleFeatureCollection>): void {\n const clickSequence = this.getClickSequence();\n const firstPoint = clickSequence[0];\n if (clickSequence.length <= 2 || !firstPoint) {\n return;\n }\n\n const polygonToAdd = {\n type: 'Polygon' as const,\n coordinates: [[...clickSequence, firstPoint]],\n };\n\n this.resetClickSequence();\n this.tooltip = null;\n\n const editAction = this.getAddFeatureOrBooleanPolygonAction(\n polygonToAdd,\n props,\n );\n if (editAction) {\n props.onEdit(editAction);\n }\n }\n\n /**\n * Handle pointer move events to update the tooltip with distance.\n *\n * Calculates the distance from the last clicked vertex to the current\n * cursor position and displays it in the configured distance units.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n const lastPoint = clickSequence.at(-1) as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(lastPoint, currentPoint, { units: distanceUnits });\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n this.tooltip = {\n position: mapCoords,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,6BAAb,cAAgD,gBAAgB;;CAE9D,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,cAAc,OAAiD;EACtE,MAAM,gBAAgB,KAAK,kBAAkB;EAC7C,MAAM,aAAa,cAAc;AACjC,MAAI,cAAc,UAAU,KAAK,CAAC,WAChC;EAGF,MAAM,eAAe;GACnB,MAAM;GACN,aAAa,CAAC,CAAC,GAAG,eAAe,WAAW,CAAC;GAC9C;AAED,OAAK,oBAAoB;AACzB,OAAK,UAAU;EAEf,MAAM,aAAa,KAAK,oCACtB,cACA,MACD;AACD,MAAI,WACF,OAAM,OAAO,WAAW;;;;;;;;;;;CAa5B,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;AAQrC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,sBALK,SAHK,cAAc,GAAG,GAAG,EACjB,WAE0B,EAAE,OAAO,eAAe,CAAC,EACrD,4BAA4B,cAAc,CAId;GAC9C;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-polygon-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.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 DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\nimport {\n DrawPolygonMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type SimpleFeatureCollection,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport { DEFAULT_DISTANCE_UNITS } from '@/shared/units';\nimport { formatDistanceTooltip } from '../../shared/constants';\n\n/**\n * Extends DrawPolygonMode to display distance tooltip between points.\n *\n * Shows the distance from the last clicked point to the current cursor position\n * while drawing a polygon. The tooltip updates in real-time as the cursor moves.\n *\n * ## Drawing Flow\n * 1. Click to add first vertex\n * 2. Move cursor (tooltip shows distance from last vertex)\n * 3. Click to add more vertices\n * 4. Double-click or click the starting point to close the polygon\n *\n *\n * @example\n * ```typescript\n * import { DrawPolygonModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawPolygonModeWithTooltip();\n * ```\n */\nexport class DrawPolygonModeWithTooltip extends DrawPolygonMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Finish drawing the polygon.\n *\n * Creates a Polygon geometry from the click sequence and emits an edit action.\n * Requires at least 3 points to create a valid polygon. Automatically closes\n * the polygon by adding the first point to the end of the coordinate ring.\n *\n * @param props - Mode properties with onEdit callback\n */\n override finishDrawing(props: ModeProps<SimpleFeatureCollection>): void {\n const clickSequence = this.getClickSequence();\n const firstPoint = clickSequence[0];\n if (clickSequence.length <= 2 || !firstPoint) {\n return;\n }\n\n const polygonToAdd = {\n type: 'Polygon' as const,\n coordinates: [[...clickSequence, firstPoint]],\n };\n\n this.resetClickSequence();\n this.tooltip = null;\n\n const editAction = this.getAddFeatureOrBooleanPolygonAction(\n polygonToAdd,\n props,\n );\n if (editAction) {\n props.onEdit(editAction);\n }\n }\n\n /**\n * Handle pointer move events to update the tooltip with distance.\n *\n * Calculates the distance from the last clicked vertex to the current\n * cursor position and displays it in the configured distance units.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n (props.modeConfig?.distanceUnits as DistanceUnit) ??\n DEFAULT_DISTANCE_UNITS;\n\n const lastPoint = clickSequence.at(-1) as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(lastPoint, currentPoint, { units: distanceUnits });\n const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];\n\n this.tooltip = {\n position: mapCoords,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,6BAAb,cAAgD,gBAAgB;;CAE9D,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,cAAc,OAAiD;EACtE,MAAM,gBAAgB,KAAK,kBAAkB;EAC7C,MAAM,aAAa,cAAc;AACjC,MAAI,cAAc,UAAU,KAAK,CAAC,WAChC;EAGF,MAAM,eAAe;GACnB,MAAM;GACN,aAAa,CAAC,CAAC,GAAG,eAAe,WAAW,CAAC;GAC9C;AAED,OAAK,oBAAoB;AACzB,OAAK,UAAU;EAEf,MAAM,aAAa,KAAK,oCACtB,cACA,MACD;AACD,MAAI,WACF,OAAM,OAAO,WAAW;;;;;;;;;;;CAa5B,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACH,MAAM,YAAY,iBACnB;EAKF,MAAM,OAAO,SAHK,cAAc,GAAG,GAAG,EACjB,WAE0B,EAAE,OAAO,eAAe,CAAC;EACxE,MAAM,aAAa,sBAAsB;AAEzC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,sBAAsB,MAAM,WAAW;GAC9C;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -11,9 +11,10 @@
11
11
  */
12
12
 
13
13
 
14
- import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
14
+ import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
15
15
  import { formatRectangleTooltip } from "../../shared/constants.js";
16
16
  import { area, bbox, bboxPolygon, convertArea, destination, distance } from "@turf/turf";
17
+ import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
17
18
  import { DrawRectangleMode } from "@deck.gl-community/editable-layers";
18
19
  import { featureCollection, point } from "@turf/helpers";
19
20
 
@@ -99,9 +100,13 @@ var DrawRectangleModeWithTooltip = class extends DrawRectangleMode {
99
100
  const firstClickPoint = point(firstClick);
100
101
  const currentPoint = point(mapCoords);
101
102
  const cornerPoint = point([firstClick[0], mapCoords[1]]);
103
+ const dimension1 = distance(firstClickPoint, cornerPoint, { units: distanceUnits });
104
+ const dimension2 = distance(currentPoint, cornerPoint, { units: distanceUnits });
105
+ const convertedArea = convertArea(area(bboxPolygon(bbox(featureCollection([firstClickPoint, currentPoint])))), "meters", distanceUnits);
106
+ const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
102
107
  this.tooltip = {
103
108
  position: mapCoords,
104
- text: formatRectangleTooltip(distance(firstClickPoint, cornerPoint, { units: distanceUnits }), distance(currentPoint, cornerPoint, { units: distanceUnits }), convertArea(area(bboxPolygon(bbox(featureCollection([firstClickPoint, currentPoint])))), "meters", distanceUnits), getDistanceUnitAbbreviation(distanceUnits))
109
+ text: formatRectangleTooltip(dimension1, dimension2, convertedArea, unitAbbrev)
105
110
  };
106
111
  }
107
112
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"draw-rectangle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.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 DrawRectangleMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection, point } from '@turf/helpers';\nimport {\n area,\n bbox,\n bboxPolygon,\n convertArea,\n destination,\n distance,\n} from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatRectangleTooltip } from '../../shared/constants';\nimport type { Position } from 'geojson';\n\n/**\n * Extends DrawRectangleMode to display dimensions and area tooltip.\n *\n * Shows the width, height, and total area of the rectangle being drawn.\n * The tooltip updates in real-time as the cursor moves, displaying measurements\n * in the configured distance units.\n *\n * ## Drawing Flow\n * 1. Click to set first corner\n * 2. Move cursor (tooltip shows dimensions and area)\n * 3. Click to set opposite corner and finish the rectangle\n *\n * ## Shift-to-Square Constraint\n * Hold Shift while drawing to constrain the rectangle to a square with equal sides.\n * Uses real-world distances (via Turf.js) to account for lat/lon distortion.\n *\n * @example\n * ```typescript\n * import { DrawRectangleModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawRectangleModeWithTooltip();\n * ```\n */\nexport class DrawRectangleModeWithTooltip extends DrawRectangleMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n /** Tracks whether Shift key is currently pressed for square constraint */\n private isShiftPressed = false;\n\n /**\n * Override getTwoClickPolygon to support Shift-constrained squares.\n *\n * When Shift is held, constrains the rectangle to a square by using the larger\n * of the horizontal or vertical distances as the side length. Uses real-world\n * geographic distances (via Turf.js) to account for lat/lon distortion at different\n * latitudes.\n *\n * @param coord1 - First corner coordinates [lon, lat]\n * @param coord2 - Second corner coordinates [lon, lat]\n * @param modeConfig - Mode configuration options\n * @returns Polygon geometry for the rectangle or square\n */\n override getTwoClickPolygon(\n coord1: Position,\n coord2: Position,\n modeConfig: ModeProps<FeatureCollection>['modeConfig'],\n ) {\n let finalCoord2 = coord2;\n\n // If Shift is pressed, constrain to a square using real distances\n if (this.isShiftPressed && coord1 && coord2) {\n const lon1 = coord1[0] ?? 0;\n const lat1 = coord1[1] ?? 0;\n const lon2 = coord2[0] ?? 0;\n const lat2 = coord2[1] ?? 0;\n\n // Calculate real-world distances using turf\n // Horizontal distance (along latitude)\n const horizontalDist = distance(\n point([lon1, lat1]),\n point([lon2, lat1]),\n {\n units: 'kilometers',\n },\n );\n // Vertical distance (along longitude)\n const verticalDist = distance(point([lon1, lat1]), point([lon1, lat2]), {\n units: 'kilometers',\n });\n\n // Use the larger distance for the square side\n const maxDist = Math.max(horizontalDist, verticalDist);\n\n // Determine direction signs\n const lonSign = lon2 >= lon1 ? 1 : -1;\n const latSign = lat2 >= lat1 ? 1 : -1;\n\n // Calculate new corner using destination() for accurate geographic positioning\n // Move horizontally from coord1\n const horizontalPoint = destination(\n point([lon1, lat1]),\n maxDist,\n lonSign > 0 ? 90 : 270,\n {\n units: 'kilometers',\n },\n );\n // Move vertically from that point\n const cornerPoint = destination(\n horizontalPoint,\n maxDist,\n latSign > 0 ? 0 : 180,\n {\n units: 'kilometers',\n },\n );\n\n finalCoord2 = cornerPoint.geometry.coordinates;\n }\n\n // Call parent implementation with potentially adjusted coordinates\n return super.getTwoClickPolygon(coord1, finalCoord2, modeConfig);\n }\n\n /**\n * Handle pointer move events to update the tooltip with rectangle measurements.\n *\n * Tracks the Shift key state for square constraint and calculates the width,\n * height, and area of the rectangle being drawn. Uses Turf.js for accurate\n * geographic distance and area calculations.\n *\n * @param event - Pointer move event with cursor position and source event\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // Track shift key state from the source event\n const sourceEvent = event.sourceEvent as KeyboardEvent | undefined;\n this.isShiftPressed = sourceEvent?.shiftKey ?? false;\n\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n const firstClick = clickSequence.at(-1);\n if (!firstClick) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n const firstClickPoint = point(firstClick);\n const currentPoint = point(mapCoords);\n\n // Calculate dimensions by finding the corner point\n const cornerPoint = point([\n firstClick[0] as number,\n mapCoords[1] as number,\n ]);\n const dimension1 = distance(firstClickPoint, cornerPoint, {\n units: distanceUnits,\n });\n const dimension2 = distance(currentPoint, cornerPoint, {\n units: distanceUnits,\n });\n\n // Calculate area properly accounting for Earth's curvature\n const points = featureCollection([firstClickPoint, currentPoint]);\n const bboxPoly = bboxPolygon(bbox(points));\n const rectArea = area(bboxPoly);\n const convertedArea = convertArea(rectArea, 'meters', distanceUnits);\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n this.tooltip = {\n position: mapCoords,\n text: formatRectangleTooltip(\n dimension1,\n dimension2,\n convertedArea,\n unitAbbrev,\n ),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,+BAAb,cAAkD,kBAAkB;;CAElE,AAAQ,UAA0B;;CAElC,AAAQ,iBAAiB;;;;;;;;;;;;;;CAezB,AAAS,mBACP,QACA,QACA,YACA;EACA,IAAI,cAAc;AAGlB,MAAI,KAAK,kBAAkB,UAAU,QAAQ;GAC3C,MAAM,OAAO,OAAO,MAAM;GAC1B,MAAM,OAAO,OAAO,MAAM;GAC1B,MAAM,OAAO,OAAO,MAAM;GAC1B,MAAM,OAAO,OAAO,MAAM;GAI1B,MAAM,iBAAiB,SACrB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,EACE,OAAO,cACR,CACF;GAED,MAAM,eAAe,SAAS,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,EACtE,OAAO,cACR,CAAC;GAGF,MAAM,UAAU,KAAK,IAAI,gBAAgB,aAAa;GAGtD,MAAM,UAAU,QAAQ,OAAO,IAAI;GACnC,MAAM,UAAU,QAAQ,OAAO,IAAI;AAsBnC,iBAToB,YATI,YACtB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,SACA,UAAU,IAAI,KAAK,KACnB,EACE,OAAO,cACR,CACF,EAIC,SACA,UAAU,IAAI,IAAI,KAClB,EACE,OAAO,cACR,CACF,CAEyB,SAAS;;AAIrC,SAAO,MAAM,mBAAmB,QAAQ,aAAa,WAAW;;;;;;;;;;;;CAalE,AAAS,kBACP,OACA,OACA;AAGA,OAAK,iBADe,MAAM,aACS,YAAY;AAE/C,QAAM,kBAAkB,OAAO,MAAM;EAGrC,MAAM,aADgB,KAAK,kBAAkB,CACZ,GAAG,GAAG;AACvC,MAAI,CAAC,YAAY;AACf,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAErC,MAAM,kBAAkB,MAAM,WAAW;EACzC,MAAM,eAAe,MAAM,UAAU;EAGrC,MAAM,cAAc,MAAM,CACxB,WAAW,IACX,UAAU,GACX,CAAC;AAeF,OAAK,UAAU;GACb,UAAU;GACV,MAAM,uBAhBW,SAAS,iBAAiB,aAAa,EACxD,OAAO,eACR,CAAC,EACiB,SAAS,cAAc,aAAa,EACrD,OAAO,eACR,CAAC,EAMoB,YADL,KADA,YAAY,KADd,kBAAkB,CAAC,iBAAiB,aAAa,CAAC,CACxB,CAAC,CACX,EACa,UAAU,cAAc,EACjD,4BAA4B,cAAc,CAS1D;GACF;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-rectangle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.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 DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\nimport {\n DrawRectangleMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection, point } from '@turf/helpers';\nimport {\n area,\n bbox,\n bboxPolygon,\n convertArea,\n destination,\n distance,\n} from '@turf/turf';\nimport { DEFAULT_DISTANCE_UNITS } from '@/shared/units';\nimport { formatRectangleTooltip } from '../../shared/constants';\nimport type { Position } from 'geojson';\n\n/**\n * Extends DrawRectangleMode to display dimensions and area tooltip.\n *\n * Shows the width, height, and total area of the rectangle being drawn.\n * The tooltip updates in real-time as the cursor moves, displaying measurements\n * in the configured distance units.\n *\n * ## Drawing Flow\n * 1. Click to set first corner\n * 2. Move cursor (tooltip shows dimensions and area)\n * 3. Click to set opposite corner and finish the rectangle\n *\n * ## Shift-to-Square Constraint\n * Hold Shift while drawing to constrain the rectangle to a square with equal sides.\n * Uses real-world distances (via Turf.js) to account for lat/lon distortion.\n *\n * @example\n * ```typescript\n * import { DrawRectangleModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawRectangleModeWithTooltip();\n * ```\n */\nexport class DrawRectangleModeWithTooltip extends DrawRectangleMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n /** Tracks whether Shift key is currently pressed for square constraint */\n private isShiftPressed = false;\n\n /**\n * Override getTwoClickPolygon to support Shift-constrained squares.\n *\n * When Shift is held, constrains the rectangle to a square by using the larger\n * of the horizontal or vertical distances as the side length. Uses real-world\n * geographic distances (via Turf.js) to account for lat/lon distortion at different\n * latitudes.\n *\n * @param coord1 - First corner coordinates [lon, lat]\n * @param coord2 - Second corner coordinates [lon, lat]\n * @param modeConfig - Mode configuration options\n * @returns Polygon geometry for the rectangle or square\n */\n override getTwoClickPolygon(\n coord1: Position,\n coord2: Position,\n modeConfig: ModeProps<FeatureCollection>['modeConfig'],\n ) {\n let finalCoord2 = coord2;\n\n // If Shift is pressed, constrain to a square using real distances\n if (this.isShiftPressed && coord1 && coord2) {\n const lon1 = coord1[0] ?? 0;\n const lat1 = coord1[1] ?? 0;\n const lon2 = coord2[0] ?? 0;\n const lat2 = coord2[1] ?? 0;\n\n // Calculate real-world distances using turf\n // Horizontal distance (along latitude)\n const horizontalDist = distance(\n point([lon1, lat1]),\n point([lon2, lat1]),\n {\n units: 'kilometers',\n },\n );\n // Vertical distance (along longitude)\n const verticalDist = distance(point([lon1, lat1]), point([lon1, lat2]), {\n units: 'kilometers',\n });\n\n // Use the larger distance for the square side\n const maxDist = Math.max(horizontalDist, verticalDist);\n\n // Determine direction signs\n const lonSign = lon2 >= lon1 ? 1 : -1;\n const latSign = lat2 >= lat1 ? 1 : -1;\n\n // Calculate new corner using destination() for accurate geographic positioning\n // Move horizontally from coord1\n const horizontalPoint = destination(\n point([lon1, lat1]),\n maxDist,\n lonSign > 0 ? 90 : 270,\n {\n units: 'kilometers',\n },\n );\n // Move vertically from that point\n const cornerPoint = destination(\n horizontalPoint,\n maxDist,\n latSign > 0 ? 0 : 180,\n {\n units: 'kilometers',\n },\n );\n\n finalCoord2 = cornerPoint.geometry.coordinates;\n }\n\n // Call parent implementation with potentially adjusted coordinates\n return super.getTwoClickPolygon(coord1, finalCoord2, modeConfig);\n }\n\n /**\n * Handle pointer move events to update the tooltip with rectangle measurements.\n *\n * Tracks the Shift key state for square constraint and calculates the width,\n * height, and area of the rectangle being drawn. Uses Turf.js for accurate\n * geographic distance and area calculations.\n *\n * @param event - Pointer move event with cursor position and source event\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // Track shift key state from the source event\n const sourceEvent = event.sourceEvent as KeyboardEvent | undefined;\n this.isShiftPressed = sourceEvent?.shiftKey ?? false;\n\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n const firstClick = clickSequence.at(-1);\n if (!firstClick) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n (props.modeConfig?.distanceUnits as DistanceUnit) ??\n DEFAULT_DISTANCE_UNITS;\n\n const firstClickPoint = point(firstClick);\n const currentPoint = point(mapCoords);\n\n // Calculate dimensions by finding the corner point\n const cornerPoint = point([\n firstClick[0] as number,\n mapCoords[1] as number,\n ]);\n const dimension1 = distance(firstClickPoint, cornerPoint, {\n units: distanceUnits,\n });\n const dimension2 = distance(currentPoint, cornerPoint, {\n units: distanceUnits,\n });\n\n // Calculate area properly accounting for Earth's curvature\n const points = featureCollection([firstClickPoint, currentPoint]);\n const bboxPoly = bboxPolygon(bbox(points));\n const rectArea = area(bboxPoly);\n const convertedArea = convertArea(rectArea, 'meters', distanceUnits);\n const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];\n\n this.tooltip = {\n position: mapCoords,\n text: formatRectangleTooltip(\n dimension1,\n dimension2,\n convertedArea,\n unitAbbrev,\n ),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,IAAa,+BAAb,cAAkD,kBAAkB;;CAElE,AAAQ,UAA0B;;CAElC,AAAQ,iBAAiB;;;;;;;;;;;;;;CAezB,AAAS,mBACP,QACA,QACA,YACA;EACA,IAAI,cAAc;AAGlB,MAAI,KAAK,kBAAkB,UAAU,QAAQ;GAC3C,MAAM,OAAO,OAAO,MAAM;GAC1B,MAAM,OAAO,OAAO,MAAM;GAC1B,MAAM,OAAO,OAAO,MAAM;GAC1B,MAAM,OAAO,OAAO,MAAM;GAI1B,MAAM,iBAAiB,SACrB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,EACE,OAAO,cACR,CACF;GAED,MAAM,eAAe,SAAS,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,EACtE,OAAO,cACR,CAAC;GAGF,MAAM,UAAU,KAAK,IAAI,gBAAgB,aAAa;GAGtD,MAAM,UAAU,QAAQ,OAAO,IAAI;GACnC,MAAM,UAAU,QAAQ,OAAO,IAAI;AAsBnC,iBAToB,YATI,YACtB,MAAM,CAAC,MAAM,KAAK,CAAC,EACnB,SACA,UAAU,IAAI,KAAK,KACnB,EACE,OAAO,cACR,CACF,EAIC,SACA,UAAU,IAAI,IAAI,KAClB,EACE,OAAO,cACR,CACF,CAEyB,SAAS;;AAIrC,SAAO,MAAM,mBAAmB,QAAQ,aAAa,WAAW;;;;;;;;;;;;CAalE,AAAS,kBACP,OACA,OACA;AAGA,OAAK,iBADe,MAAM,aACS,YAAY;AAE/C,QAAM,kBAAkB,OAAO,MAAM;EAGrC,MAAM,aADgB,KAAK,kBAAkB,CACZ,GAAG,GAAG;AACvC,MAAI,CAAC,YAAY;AACf,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACH,MAAM,YAAY,iBACnB;EAEF,MAAM,kBAAkB,MAAM,WAAW;EACzC,MAAM,eAAe,MAAM,UAAU;EAGrC,MAAM,cAAc,MAAM,CACxB,WAAW,IACX,UAAU,GACX,CAAC;EACF,MAAM,aAAa,SAAS,iBAAiB,aAAa,EACxD,OAAO,eACR,CAAC;EACF,MAAM,aAAa,SAAS,cAAc,aAAa,EACrD,OAAO,eACR,CAAC;EAMF,MAAM,gBAAgB,YADL,KADA,YAAY,KADd,kBAAkB,CAAC,iBAAiB,aAAa,CAAC,CACxB,CAAC,CACX,EACa,UAAU,cAAc;EACpE,MAAM,aAAa,sBAAsB;AAEzC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,uBACJ,YACA,YACA,eACA,WACD;GACF;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -10,9 +10,9 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { DistanceUnitAbbreviation } from "../../../shared/units.js";
14
13
  import { CircleProperties, Shape, ShapeFeatureType, StyleProperties } from "../shared/types.js";
15
14
  import { UniqueId } from "@accelint/core";
15
+ import { DistanceUnitSymbol } from "@accelint/constants/units";
16
16
  import { Feature } from "geojson";
17
17
 
18
18
  //#region src/deckgl/shapes/draw-shape-layer/types.d.ts
@@ -75,7 +75,7 @@ type DrawShapeLayerProps = {
75
75
  */
76
76
  mapId?: UniqueId;
77
77
  /** Distance unit for tooltip measurements (defaults to 'km') */
78
- unit?: DistanceUnitAbbreviation;
78
+ unit?: DistanceUnitSymbol;
79
79
  };
80
80
  /**
81
81
  * Function type for the draw action
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { EditShapeLayerProps } from "./types.js";
14
+ import "./fiber.js";
14
15
  import * as react_jsx_runtime3 from "react/jsx-runtime";
15
16
 
16
17
  //#region src/deckgl/shapes/edit-shape-layer/index.d.ts
@@ -31,12 +32,14 @@ import * as react_jsx_runtime3 from "react/jsx-runtime";
31
32
  * - Live dimension/area tooltips during editing
32
33
  * - requestAnimationFrame() batching for smooth drag performance
33
34
  *
35
+ * ## Fiber Registration
36
+ * Unlike `DisplayShapeLayer` (a deck.gl layer class), `EditShapeLayer` is a React
37
+ * component that handles its own fiber registration internally. You do **not** need to
38
+ * import `edit-shape-layer/fiber` — but you **do** still need to import
39
+ * `display-shape-layer/fiber` if you use `<displayShapeLayer>` in the same tree.
40
+ *
34
41
  * @example
35
42
  * ```tsx
36
- * // Import the fiber registration for JSX support
37
- * import '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/fiber';
38
- * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';
39
- *
40
43
  * function Map({ mapId }) {
41
44
  * const { editingShape } = useEditShape(mapId);
42
45
  *
@@ -15,12 +15,14 @@
15
15
 
16
16
  import { COMPLETION_EDIT_TYPES, CONTINUOUS_EDIT_TYPES } from "../shared/constants.js";
17
17
  import { DEFAULT_HOTKEY_CONFIG, EDIT_SHAPE_LAYER_ID, SHAPE_PROPERTY_MAP } from "./constants.js";
18
- import { cancelEditingFromLayer, disableEditPanning, editStore, enableEditPanning, saveEditingFromLayer, updateFeatureFromLayer } from "./store.js";
18
+ import { cancelEditingFromLayer, disableEditPanning, editStore, enableEditPanning, saveEditingFromLayer, updateFeature } from "./store.js";
19
19
  import { MapContext } from "../../base-map/provider.js";
20
20
  import { getFillColor, getLineColor } from "../shared/utils/style-utils.js";
21
+ import { getIconConfig, getIconLayerProps } from "../display-shape-layer/utils/icon-config.js";
21
22
  import { useShiftZoomDisable } from "../shared/hooks/use-shift-zoom-disable.js";
22
23
  import { getDefaultEditableLayerProps } from "../shared/utils/layer-config.js";
23
24
  import { getEditModeInstance } from "./modes/index.js";
25
+ import "./fiber.js";
24
26
  import { useCallback, useContext, useEffect, useRef } from "react";
25
27
  import { Keycode, globalBind, registerHotkey, unregisterHotkey } from "@accelint/hotkey-manager";
26
28
  import { jsx } from "react/jsx-runtime";
@@ -90,12 +92,14 @@ function toFeatureCollection(feature, shape) {
90
92
  * - Live dimension/area tooltips during editing
91
93
  * - requestAnimationFrame() batching for smooth drag performance
92
94
  *
95
+ * ## Fiber Registration
96
+ * Unlike `DisplayShapeLayer` (a deck.gl layer class), `EditShapeLayer` is a React
97
+ * component that handles its own fiber registration internally. You do **not** need to
98
+ * import `edit-shape-layer/fiber` — but you **do** still need to import
99
+ * `display-shape-layer/fiber` if you use `<displayShapeLayer>` in the same tree.
100
+ *
93
101
  * @example
94
102
  * ```tsx
95
- * // Import the fiber registration for JSX support
96
- * import '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/fiber';
97
- * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';
98
- *
99
103
  * function Map({ mapId }) {
100
104
  * const { editingShape } = useEditShape(mapId);
101
105
  *
@@ -188,7 +192,7 @@ function EditShapeLayer({ id = EDIT_SHAPE_LAYER_ID, mapId, unit, hotkeyConfig =
188
192
  pendingUpdateRef.current = {
189
193
  feature,
190
194
  rafId: requestAnimationFrame(() => {
191
- updateFeatureFromLayer(actualMapId, feature);
195
+ updateFeature(actualMapId, feature);
192
196
  pendingUpdateRef.current = null;
193
197
  })
194
198
  };
@@ -196,7 +200,7 @@ function EditShapeLayer({ id = EDIT_SHAPE_LAYER_ID, mapId, unit, hotkeyConfig =
196
200
  }
197
201
  if (isCompletionEditType(editType)) {
198
202
  cancelPendingUpdate();
199
- if (feature) updateFeatureFromLayer(actualMapId, feature);
203
+ if (feature) updateFeature(actualMapId, feature);
200
204
  return;
201
205
  }
202
206
  if (editType === "cancelFeature") {
@@ -206,6 +210,15 @@ function EditShapeLayer({ id = EDIT_SHAPE_LAYER_ID, mapId, unit, hotkeyConfig =
206
210
  };
207
211
  const fillColor = getFillColor(editingShape.feature, true);
208
212
  const lineColor = getLineColor(editingShape.feature);
213
+ const { hasIcons, atlas: iconAtlas, mapping: iconMapping } = getIconConfig([editingShape.feature]);
214
+ const defaultProps = getDefaultEditableLayerProps(unit);
215
+ const subLayerProps = hasIcons ? {
216
+ ...defaultProps._subLayerProps,
217
+ geojson: {
218
+ pointType: "icon",
219
+ ...getIconLayerProps(hasIcons, iconAtlas, iconMapping)
220
+ }
221
+ } : defaultProps._subLayerProps;
209
222
  return /* @__PURE__ */ jsx("editableGeoJsonLayer", {
210
223
  id,
211
224
  data,
@@ -216,7 +229,8 @@ function EditShapeLayer({ id = EDIT_SHAPE_LAYER_ID, mapId, unit, hotkeyConfig =
216
229
  getLineColor: lineColor,
217
230
  getTentativeFillColor: fillColor,
218
231
  getTentativeLineColor: lineColor,
219
- ...getDefaultEditableLayerProps(unit)
232
+ ...defaultProps,
233
+ _subLayerProps: subLayerProps
220
234
  });
221
235
  }
222
236
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/index.tsx"],"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\n'use client';\n\nimport {\n globalBind,\n Keycode,\n registerHotkey,\n unregisterHotkey,\n} from '@accelint/hotkey-manager';\nimport { useCallback, useContext, useEffect, useRef } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { getFillColor, getLineColor } from '../shared/utils/style-utils';\nimport {\n COMPLETION_EDIT_TYPES,\n CONTINUOUS_EDIT_TYPES,\n DEFAULT_HOTKEY_CONFIG,\n EDIT_SHAPE_LAYER_ID,\n SHAPE_PROPERTY_MAP,\n} from './constants';\nimport { getEditModeInstance } from './modes';\nimport {\n cancelEditingFromLayer,\n disableEditPanning,\n editStore,\n enableEditPanning,\n saveEditingFromLayer,\n updateFeatureFromLayer,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { Feature } from 'geojson';\nimport type { ShapeFeatureType } from '../shared/types';\nimport type { EditShapeLayerProps } from './types';\n\n/**\n * Check if an edit type is a continuous event (fires during drag).\n * Continuous events are batched with RAF for smooth performance.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is continuous (e.g., 'translating', 'scaling', 'rotating')\n */\nfunction isContinuousEditType(editType: string): boolean {\n return CONTINUOUS_EDIT_TYPES.has(editType);\n}\n\n/**\n * Check if an edit type is a completion event (fires at drag end).\n * Completion events update state immediately without RAF batching.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is a completion event (e.g., 'translated', 'scaled', 'rotated')\n */\nfunction isCompletionEditType(editType: string): boolean {\n return COMPLETION_EDIT_TYPES.has(editType);\n}\n\n/**\n * Convert a GeoJSON Feature to a FeatureCollection for EditableGeoJsonLayer.\n * The editable-layers library accepts standard GeoJSON FeatureCollection at runtime,\n * but has stricter internal types. We use the geojson types which are compatible.\n *\n * For circles, adds the `shape: 'Circle'` property required by ResizeCircleMode.\n * ResizeCircleMode checks `properties.shape.includes('Circle')` to identify circles.\n *\n * For rectangles, adds the `shape: 'Rectangle'` property required by ModifyMode's\n * lockRectangles feature. ModifyMode checks `properties.shape === 'Rectangle'`.\n *\n * @param feature - The GeoJSON Feature to wrap.\n * @param shape - The shape type, used to determine if mode-specific properties are needed.\n * @returns A GeoJSON FeatureCollection containing the single feature.\n */\nfunction toFeatureCollection(\n feature: Feature,\n shape: ShapeFeatureType,\n): import('geojson').FeatureCollection {\n // Add shape property for modes that require it\n // - ResizeCircleMode requires shape: 'Circle'\n // - ModifyMode lockRectangles requires shape: 'Rectangle'\n const shapeProperty = SHAPE_PROPERTY_MAP[shape];\n\n const featureWithShape = shapeProperty\n ? {\n ...feature,\n properties: {\n ...feature.properties,\n shape: shapeProperty,\n },\n }\n : feature;\n\n return {\n type: 'FeatureCollection',\n features: [featureWithShape],\n };\n}\n\n/**\n * EditShapeLayer - A React component for editing existing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively editing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the editing store for state management\n * - Neutral mode authorization (lets UI decide how to handle mode conflicts)\n * - Circles use ResizeCircleMode with tooltip, other shapes use ModifyMode\n * - Fill colors rendered at 20% opacity, edit handles are white\n * - Live dimension/area tooltips during editing\n * - requestAnimationFrame() batching for smooth drag performance\n *\n * @example\n * ```tsx\n * // Import the fiber registration for JSX support\n * import '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/fiber';\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n *\n * function Map({ mapId }) {\n * const { editingShape } = useEditShape(mapId);\n *\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer\n * data={shapes}\n * mapId={mapId}\n * selectedShapeId={editingShape?.id}\n * />\n * <EditShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @throws {Error} Throws if neither `mapId` prop nor `MapProvider` context is available.\n */\nexport function EditShapeLayer({\n id = EDIT_SHAPE_LAYER_ID,\n mapId,\n unit,\n hotkeyConfig = DEFAULT_HOTKEY_CONFIG,\n}: EditShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'EditShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to editing state using the v2 store API\n const { state: editingState } = editStore.use(actualMapId);\n\n const isEditing = editingState?.editingShape != null;\n\n // RAF batching for movePosition events to reduce React updates during drag\n const pendingUpdateRef = useRef<{\n feature: Feature;\n rafId: number;\n } | null>(null);\n\n // Keep a ref to the latest editing state so the hotkey handler can access it\n const editingStateRef = useRef(editingState);\n editingStateRef.current = editingState;\n\n // Ensure global hotkey listeners are initialized\n // Safe to call multiple times - globalBind() checks if already bound\n useEffect(() => {\n globalBind();\n }, []);\n\n // Helper to cancel any pending RAF update (stable reference with useCallback)\n const cancelPendingUpdate = useCallback(() => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n pendingUpdateRef.current = null;\n }\n }, []);\n\n // Register Enter key hotkey scoped to this component and map instance.\n // Handles the full lifecycle: register → bind → unbind → unregister.\n // Without unregistering on cleanup, remounting throws a duplicate-id error.\n useEffect(() => {\n const manager = registerHotkey({\n id: `saveEditHotkey-${actualMapId}`,\n key: { code: Keycode.Enter },\n onKeyUp: () => {\n if (editingStateRef.current?.editingShape) {\n cancelPendingUpdate();\n saveEditingFromLayer(actualMapId);\n }\n },\n });\n\n const unbind = manager.bind();\n\n return () => {\n unbind();\n unregisterHotkey(manager);\n };\n }, [actualMapId, cancelPendingUpdate]);\n\n // Register Space key for enabling panning while editing shape.\n // NOTE: Low threshold (50ms) enables near-instant panning response on hold.\n // alwaysTriggerKeyUp ensures panning ends even if onKeyHeld was triggered.\n useEffect(() => {\n const manager = registerHotkey({\n id: `editPanningHotkey-${actualMapId}`,\n key: hotkeyConfig.panning,\n onKeyDown: (e: KeyboardEvent) => {\n if (editStore.get(actualMapId)?.editingShape) {\n e.preventDefault();\n }\n },\n onKeyHeld: () => {\n enableEditPanning(actualMapId);\n },\n onKeyUp: () => {\n disableEditPanning(actualMapId);\n },\n heldThresholdMs: 50,\n alwaysTriggerKeyUp: true,\n });\n\n const unbind = manager.bind();\n\n return () => {\n unbind();\n unregisterHotkey(manager);\n };\n }, [actualMapId, hotkeyConfig]);\n\n // Disable zoom while Shift is held during editing\n // This prevents boxZoom (Shift+drag) from interfering with Shift modifier constraints\n // (e.g., Shift for uniform scaling, Shift for rotation snap)\n useShiftZoomDisable(actualMapId, isEditing);\n\n // Cleanup RAF on unmount\n useEffect(() => {\n return () => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n }\n };\n }, []);\n\n // If not editing, return null (don't render the editable layer)\n if (!editingState?.editingShape) {\n return null;\n }\n\n const { editingShape, editMode, featureBeingEdited } = editingState;\n\n // Get the cached mode instance\n const mode = getEditModeInstance(editMode);\n\n // Use the live feature being edited, or fall back to original shape\n const featureToRender = featureBeingEdited ?? editingShape.feature;\n const data = toFeatureCollection(featureToRender, editingShape.shape);\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n const feature = updatedData.features[0];\n\n // Continuous events (during drag): batch with RAF for smooth performance\n if (isContinuousEditType(editType) && feature) {\n cancelPendingUpdate();\n const rafId = requestAnimationFrame(() => {\n updateFeatureFromLayer(actualMapId, feature as Feature);\n pendingUpdateRef.current = null;\n });\n pendingUpdateRef.current = { feature: feature as Feature, rafId };\n return;\n }\n\n // Completion events (drag end): update immediately\n if (isCompletionEditType(editType)) {\n cancelPendingUpdate();\n if (feature) {\n updateFeatureFromLayer(actualMapId, feature as Feature);\n }\n return;\n }\n\n // ESC key cancellation\n if (editType === 'cancelFeature') {\n cancelPendingUpdate();\n cancelEditingFromLayer(actualMapId);\n }\n };\n\n // Get colors from the shape's existing style properties with base opacity applied\n const fillColor = getFillColor(editingShape.feature, true);\n const lineColor = getLineColor(editingShape.feature);\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={data}\n mode={mode}\n selectedFeatureIndexes={[0]}\n onEdit={handleEdit}\n getFillColor={fillColor}\n getLineColor={lineColor}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...getDefaultEditableLayerProps(unit)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;AAU5C,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;;;;;;;;;AAkB5C,SAAS,oBACP,SACA,OACqC;CAIrC,MAAM,gBAAgB,mBAAmB;AAYzC,QAAO;EACL,MAAM;EACN,UAAU,CAZa,gBACrB;GACE,GAAG;GACH,YAAY;IACV,GAAG,QAAQ;IACX,OAAO;IACR;GACF,GACD,QAI0B;EAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CH,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,MACA,eAAe,yBACO;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;CAE1D,MAAM,YAAY,cAAc,gBAAgB;CAGhD,MAAM,mBAAmB,OAGf,KAAK;CAGf,MAAM,kBAAkB,OAAO,aAAa;AAC5C,iBAAgB,UAAU;AAI1B,iBAAgB;AACd,cAAY;IACX,EAAE,CAAC;CAGN,MAAM,sBAAsB,kBAAkB;AAC5C,MAAI,iBAAiB,SAAS;AAC5B,wBAAqB,iBAAiB,QAAQ,MAAM;AACpD,oBAAiB,UAAU;;IAE5B,EAAE,CAAC;AAKN,iBAAgB;EACd,MAAM,UAAU,eAAe;GAC7B,IAAI,kBAAkB;GACtB,KAAK,EAAE,MAAM,QAAQ,OAAO;GAC5B,eAAe;AACb,QAAI,gBAAgB,SAAS,cAAc;AACzC,0BAAqB;AACrB,0BAAqB,YAAY;;;GAGtC,CAAC;EAEF,MAAM,SAAS,QAAQ,MAAM;AAE7B,eAAa;AACX,WAAQ;AACR,oBAAiB,QAAQ;;IAE1B,CAAC,aAAa,oBAAoB,CAAC;AAKtC,iBAAgB;EACd,MAAM,UAAU,eAAe;GAC7B,IAAI,qBAAqB;GACzB,KAAK,aAAa;GAClB,YAAY,MAAqB;AAC/B,QAAI,UAAU,IAAI,YAAY,EAAE,aAC9B,GAAE,gBAAgB;;GAGtB,iBAAiB;AACf,sBAAkB,YAAY;;GAEhC,eAAe;AACb,uBAAmB,YAAY;;GAEjC,iBAAiB;GACjB,oBAAoB;GACrB,CAAC;EAEF,MAAM,SAAS,QAAQ,MAAM;AAE7B,eAAa;AACX,WAAQ;AACR,oBAAiB,QAAQ;;IAE1B,CAAC,aAAa,aAAa,CAAC;AAK/B,qBAAoB,aAAa,UAAU;AAG3C,iBAAgB;AACd,eAAa;AACX,OAAI,iBAAiB,QACnB,sBAAqB,iBAAiB,QAAQ,MAAM;;IAGvD,EAAE,CAAC;AAGN,KAAI,CAAC,cAAc,aACjB,QAAO;CAGT,MAAM,EAAE,cAAc,UAAU,uBAAuB;CAGvD,MAAM,OAAO,oBAAoB,SAAS;CAI1C,MAAM,OAAO,oBADW,sBAAsB,aAAa,SACT,aAAa,MAAM;CAGrE,MAAM,cAAc,EAClB,aACA,eACmC;EACnC,MAAM,UAAU,YAAY,SAAS;AAGrC,MAAI,qBAAqB,SAAS,IAAI,SAAS;AAC7C,wBAAqB;AAKrB,oBAAiB,UAAU;IAAW;IAAoB,OAJ5C,4BAA4B;AACxC,4BAAuB,aAAa,QAAmB;AACvD,sBAAiB,UAAU;MAC3B;IAC+D;AACjE;;AAIF,MAAI,qBAAqB,SAAS,EAAE;AAClC,wBAAqB;AACrB,OAAI,QACF,wBAAuB,aAAa,QAAmB;AAEzD;;AAIF,MAAI,aAAa,iBAAiB;AAChC,wBAAqB;AACrB,0BAAuB,YAAY;;;CAKvC,MAAM,YAAY,aAAa,aAAa,SAAS,KAAK;CAC1D,MAAM,YAAY,aAAa,aAAa,QAAQ;AAEpD,QACE,oBAAC;EACK;EACE;EACA;EACN,wBAAwB,CAAC,EAAE;EAC3B,QAAQ;EACR,cAAc;EACd,cAAc;EACd,uBAAuB;EACvB,uBAAuB;EACvB,GAAI,6BAA6B,KAAK;GACtC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/index.tsx"],"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\n'use client';\n\nimport {\n globalBind,\n Keycode,\n registerHotkey,\n unregisterHotkey,\n} from '@accelint/hotkey-manager';\nimport { useCallback, useContext, useEffect, useRef } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport {\n getIconConfig,\n getIconLayerProps,\n} from '../display-shape-layer/utils/icon-config';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { getFillColor, getLineColor } from '../shared/utils/style-utils';\nimport {\n COMPLETION_EDIT_TYPES,\n CONTINUOUS_EDIT_TYPES,\n DEFAULT_HOTKEY_CONFIG,\n EDIT_SHAPE_LAYER_ID,\n SHAPE_PROPERTY_MAP,\n} from './constants';\nimport { getEditModeInstance } from './modes';\nimport {\n cancelEditingFromLayer,\n disableEditPanning,\n editStore,\n enableEditPanning,\n saveEditingFromLayer,\n updateFeature,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { Feature } from 'geojson';\nimport type { ShapeFeatureType } from '../shared/types';\nimport type { EditShapeLayerProps } from './types';\nimport './fiber';\n\n/**\n * Check if an edit type is a continuous event (fires during drag).\n * Continuous events are batched with RAF for smooth performance.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is continuous (e.g., 'translating', 'scaling', 'rotating')\n */\nfunction isContinuousEditType(editType: string): boolean {\n return CONTINUOUS_EDIT_TYPES.has(editType);\n}\n\n/**\n * Check if an edit type is a completion event (fires at drag end).\n * Completion events update state immediately without RAF batching.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is a completion event (e.g., 'translated', 'scaled', 'rotated')\n */\nfunction isCompletionEditType(editType: string): boolean {\n return COMPLETION_EDIT_TYPES.has(editType);\n}\n\n/**\n * Convert a GeoJSON Feature to a FeatureCollection for EditableGeoJsonLayer.\n * The editable-layers library accepts standard GeoJSON FeatureCollection at runtime,\n * but has stricter internal types. We use the geojson types which are compatible.\n *\n * For circles, adds the `shape: 'Circle'` property required by ResizeCircleMode.\n * ResizeCircleMode checks `properties.shape.includes('Circle')` to identify circles.\n *\n * For rectangles, adds the `shape: 'Rectangle'` property required by ModifyMode's\n * lockRectangles feature. ModifyMode checks `properties.shape === 'Rectangle'`.\n *\n * @param feature - The GeoJSON Feature to wrap.\n * @param shape - The shape type, used to determine if mode-specific properties are needed.\n * @returns A GeoJSON FeatureCollection containing the single feature.\n */\nfunction toFeatureCollection(\n feature: Feature,\n shape: ShapeFeatureType,\n): import('geojson').FeatureCollection {\n // Add shape property for modes that require it\n // - ResizeCircleMode requires shape: 'Circle'\n // - ModifyMode lockRectangles requires shape: 'Rectangle'\n const shapeProperty = SHAPE_PROPERTY_MAP[shape];\n\n const featureWithShape = shapeProperty\n ? {\n ...feature,\n properties: {\n ...feature.properties,\n shape: shapeProperty,\n },\n }\n : feature;\n\n return {\n type: 'FeatureCollection',\n features: [featureWithShape],\n };\n}\n\n/**\n * EditShapeLayer - A React component for editing existing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively editing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the editing store for state management\n * - Neutral mode authorization (lets UI decide how to handle mode conflicts)\n * - Circles use ResizeCircleMode with tooltip, other shapes use ModifyMode\n * - Fill colors rendered at 20% opacity, edit handles are white\n * - Live dimension/area tooltips during editing\n * - requestAnimationFrame() batching for smooth drag performance\n *\n * ## Fiber Registration\n * Unlike `DisplayShapeLayer` (a deck.gl layer class), `EditShapeLayer` is a React\n * component that handles its own fiber registration internally. You do **not** need to\n * import `edit-shape-layer/fiber` — but you **do** still need to import\n * `display-shape-layer/fiber` if you use `<displayShapeLayer>` in the same tree.\n *\n * @example\n * ```tsx\n * function Map({ mapId }) {\n * const { editingShape } = useEditShape(mapId);\n *\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer\n * data={shapes}\n * mapId={mapId}\n * selectedShapeId={editingShape?.id}\n * />\n * <EditShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @throws {Error} Throws if neither `mapId` prop nor `MapProvider` context is available.\n */\nexport function EditShapeLayer({\n id = EDIT_SHAPE_LAYER_ID,\n mapId,\n unit,\n hotkeyConfig = DEFAULT_HOTKEY_CONFIG,\n}: EditShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'EditShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to editing state using the v2 store API\n const { state: editingState } = editStore.use(actualMapId);\n\n const isEditing = editingState?.editingShape != null;\n\n // RAF batching for movePosition events to reduce React updates during drag\n const pendingUpdateRef = useRef<{\n feature: Feature;\n rafId: number;\n } | null>(null);\n\n // Keep a ref to the latest editing state so the hotkey handler can access it\n const editingStateRef = useRef(editingState);\n editingStateRef.current = editingState;\n\n // Ensure global hotkey listeners are initialized\n // Safe to call multiple times - globalBind() checks if already bound\n useEffect(() => {\n globalBind();\n }, []);\n\n // Helper to cancel any pending RAF update (stable reference with useCallback)\n const cancelPendingUpdate = useCallback(() => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n pendingUpdateRef.current = null;\n }\n }, []);\n\n // Register Enter key hotkey scoped to this component and map instance.\n // Handles the full lifecycle: register → bind → unbind → unregister.\n // Without unregistering on cleanup, remounting throws a duplicate-id error.\n useEffect(() => {\n const manager = registerHotkey({\n id: `saveEditHotkey-${actualMapId}`,\n key: { code: Keycode.Enter },\n onKeyUp: () => {\n if (editingStateRef.current?.editingShape) {\n cancelPendingUpdate();\n saveEditingFromLayer(actualMapId);\n }\n },\n });\n\n const unbind = manager.bind();\n\n return () => {\n unbind();\n unregisterHotkey(manager);\n };\n }, [actualMapId, cancelPendingUpdate]);\n\n // Register Space key for enabling panning while editing shape.\n // NOTE: Low threshold (50ms) enables near-instant panning response on hold.\n // alwaysTriggerKeyUp ensures panning ends even if onKeyHeld was triggered.\n useEffect(() => {\n const manager = registerHotkey({\n id: `editPanningHotkey-${actualMapId}`,\n key: hotkeyConfig.panning,\n onKeyDown: (e: KeyboardEvent) => {\n if (editStore.get(actualMapId)?.editingShape) {\n e.preventDefault();\n }\n },\n onKeyHeld: () => {\n enableEditPanning(actualMapId);\n },\n onKeyUp: () => {\n disableEditPanning(actualMapId);\n },\n heldThresholdMs: 50,\n alwaysTriggerKeyUp: true,\n });\n\n const unbind = manager.bind();\n\n return () => {\n unbind();\n unregisterHotkey(manager);\n };\n }, [actualMapId, hotkeyConfig]);\n\n // Disable zoom while Shift is held during editing\n // This prevents boxZoom (Shift+drag) from interfering with Shift modifier constraints\n // (e.g., Shift for uniform scaling, Shift for rotation snap)\n useShiftZoomDisable(actualMapId, isEditing);\n\n // Cleanup RAF on unmount\n useEffect(() => {\n return () => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n }\n };\n }, []);\n\n // If not editing, return null (don't render the editable layer)\n if (!editingState?.editingShape) {\n return null;\n }\n\n const { editingShape, editMode, featureBeingEdited } = editingState;\n\n // Get the cached mode instance\n const mode = getEditModeInstance(editMode);\n\n // Use the live feature being edited, or fall back to original shape\n const featureToRender = featureBeingEdited ?? editingShape.feature;\n const data = toFeatureCollection(featureToRender, editingShape.shape);\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n // Single cast at the library boundary: editable-layers Feature is structurally\n // compatible with geojson Feature but typed differently\n const feature = updatedData.features[0] as Feature | undefined;\n\n // Continuous events (during drag): batch with RAF for smooth performance\n if (isContinuousEditType(editType) && feature) {\n cancelPendingUpdate();\n const rafId = requestAnimationFrame(() => {\n updateFeature(actualMapId, feature);\n pendingUpdateRef.current = null;\n });\n pendingUpdateRef.current = { feature, rafId };\n return;\n }\n\n // Completion events (drag end): update immediately\n if (isCompletionEditType(editType)) {\n cancelPendingUpdate();\n if (feature) {\n updateFeature(actualMapId, feature);\n }\n return;\n }\n\n // ESC key cancellation\n if (editType === 'cancelFeature') {\n cancelPendingUpdate();\n cancelEditingFromLayer(actualMapId);\n }\n };\n\n // Get colors from the shape's existing style properties with base opacity applied\n const fillColor = getFillColor(editingShape.feature, true);\n const lineColor = getLineColor(editingShape.feature);\n\n // Auto-detect icon config from the editing shape's style properties.\n // EditableGeoJsonLayer only proxies a fixed set of props to its inner GeoJsonLayer,\n // so icon props must be injected via _subLayerProps targeting the 'geojson' sub-layer.\n const {\n hasIcons,\n atlas: iconAtlas,\n mapping: iconMapping,\n } = getIconConfig([editingShape.feature]);\n const defaultProps = getDefaultEditableLayerProps(unit);\n const subLayerProps = hasIcons\n ? {\n ...defaultProps._subLayerProps,\n geojson: {\n pointType: 'icon' as const,\n ...getIconLayerProps(hasIcons, iconAtlas, iconMapping),\n },\n }\n : defaultProps._subLayerProps;\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={data}\n mode={mode}\n selectedFeatureIndexes={[0]}\n onEdit={handleEdit}\n getFillColor={fillColor}\n getLineColor={lineColor}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...defaultProps}\n _subLayerProps={subLayerProps}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;AAU5C,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;;;;;;;;;AAkB5C,SAAS,oBACP,SACA,OACqC;CAIrC,MAAM,gBAAgB,mBAAmB;AAYzC,QAAO;EACL,MAAM;EACN,UAAU,CAZa,gBACrB;GACE,GAAG;GACH,YAAY;IACV,GAAG,QAAQ;IACX,OAAO;IACR;GACF,GACD,QAI0B;EAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CH,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,MACA,eAAe,yBACO;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;CAE1D,MAAM,YAAY,cAAc,gBAAgB;CAGhD,MAAM,mBAAmB,OAGf,KAAK;CAGf,MAAM,kBAAkB,OAAO,aAAa;AAC5C,iBAAgB,UAAU;AAI1B,iBAAgB;AACd,cAAY;IACX,EAAE,CAAC;CAGN,MAAM,sBAAsB,kBAAkB;AAC5C,MAAI,iBAAiB,SAAS;AAC5B,wBAAqB,iBAAiB,QAAQ,MAAM;AACpD,oBAAiB,UAAU;;IAE5B,EAAE,CAAC;AAKN,iBAAgB;EACd,MAAM,UAAU,eAAe;GAC7B,IAAI,kBAAkB;GACtB,KAAK,EAAE,MAAM,QAAQ,OAAO;GAC5B,eAAe;AACb,QAAI,gBAAgB,SAAS,cAAc;AACzC,0BAAqB;AACrB,0BAAqB,YAAY;;;GAGtC,CAAC;EAEF,MAAM,SAAS,QAAQ,MAAM;AAE7B,eAAa;AACX,WAAQ;AACR,oBAAiB,QAAQ;;IAE1B,CAAC,aAAa,oBAAoB,CAAC;AAKtC,iBAAgB;EACd,MAAM,UAAU,eAAe;GAC7B,IAAI,qBAAqB;GACzB,KAAK,aAAa;GAClB,YAAY,MAAqB;AAC/B,QAAI,UAAU,IAAI,YAAY,EAAE,aAC9B,GAAE,gBAAgB;;GAGtB,iBAAiB;AACf,sBAAkB,YAAY;;GAEhC,eAAe;AACb,uBAAmB,YAAY;;GAEjC,iBAAiB;GACjB,oBAAoB;GACrB,CAAC;EAEF,MAAM,SAAS,QAAQ,MAAM;AAE7B,eAAa;AACX,WAAQ;AACR,oBAAiB,QAAQ;;IAE1B,CAAC,aAAa,aAAa,CAAC;AAK/B,qBAAoB,aAAa,UAAU;AAG3C,iBAAgB;AACd,eAAa;AACX,OAAI,iBAAiB,QACnB,sBAAqB,iBAAiB,QAAQ,MAAM;;IAGvD,EAAE,CAAC;AAGN,KAAI,CAAC,cAAc,aACjB,QAAO;CAGT,MAAM,EAAE,cAAc,UAAU,uBAAuB;CAGvD,MAAM,OAAO,oBAAoB,SAAS;CAI1C,MAAM,OAAO,oBADW,sBAAsB,aAAa,SACT,aAAa,MAAM;CAGrE,MAAM,cAAc,EAClB,aACA,eACmC;EAGnC,MAAM,UAAU,YAAY,SAAS;AAGrC,MAAI,qBAAqB,SAAS,IAAI,SAAS;AAC7C,wBAAqB;AAKrB,oBAAiB,UAAU;IAAE;IAAS,OAJxB,4BAA4B;AACxC,mBAAc,aAAa,QAAQ;AACnC,sBAAiB,UAAU;MAC3B;IAC2C;AAC7C;;AAIF,MAAI,qBAAqB,SAAS,EAAE;AAClC,wBAAqB;AACrB,OAAI,QACF,eAAc,aAAa,QAAQ;AAErC;;AAIF,MAAI,aAAa,iBAAiB;AAChC,wBAAqB;AACrB,0BAAuB,YAAY;;;CAKvC,MAAM,YAAY,aAAa,aAAa,SAAS,KAAK;CAC1D,MAAM,YAAY,aAAa,aAAa,QAAQ;CAKpD,MAAM,EACJ,UACA,OAAO,WACP,SAAS,gBACP,cAAc,CAAC,aAAa,QAAQ,CAAC;CACzC,MAAM,eAAe,6BAA6B,KAAK;CACvD,MAAM,gBAAgB,WAClB;EACE,GAAG,aAAa;EAChB,SAAS;GACP,WAAW;GACX,GAAG,kBAAkB,UAAU,WAAW,YAAY;GACvD;EACF,GACD,aAAa;AAEjB,QACE,oBAAC;EACK;EACE;EACA;EACN,wBAAwB,CAAC,EAAE;EAC3B,QAAQ;EACR,cAAc;EACd,cAAc;EACd,uBAAuB;EACvB,uBAAuB;EACvB,GAAI;EACJ,gBAAgB;GAChB"}
@@ -11,12 +11,13 @@
11
11
  */
12
12
 
13
13
 
14
- import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
14
+ import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
15
15
  import { formatEllipseTooltip, formatRectangleTooltip } from "../../shared/constants.js";
16
16
  import { computeEllipseMeasurementsFromPolygon, computeRectangleMeasurementsFromCorners } from "../../shared/utils/geometry-measurements.js";
17
17
  import { BaseTransformMode } from "./base-transform-mode.js";
18
18
  import { RotateModeWithSnap } from "./rotate-mode-with-snap.js";
19
19
  import { ScaleModeWithFreeTransform } from "./scale-mode-with-free-transform.js";
20
+ import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
20
21
  import { TranslateMode } from "@deck.gl-community/editable-layers";
21
22
  import { featureCollection } from "@turf/helpers";
22
23
 
@@ -146,7 +147,7 @@ var BoundingTransformMode = class extends BaseTransformMode {
146
147
  }
147
148
  const isRectangle = feature.properties?.shape === "Rectangle";
148
149
  let text;
149
- const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);
150
+ const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
150
151
  if (isRectangle) {
151
152
  const corner0 = coordinates[0];
152
153
  const corner1 = coordinates[1];
@@ -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 * ## 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 // biome-ignore lint/suspicious/noExplicitAny: Library type inconsistency — see HandleMatcher JSDoc in base-transform-mode\n return this.translateMode as any;\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;AAEnD,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 DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\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 { DEFAULT_DISTANCE_UNITS } 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 // biome-ignore lint/suspicious/noExplicitAny: Library type inconsistency — see HandleMatcher JSDoc in base-transform-mode\n return this.translateMode as any;\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 as DistanceUnit) ??\n 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 = DISTANCE_UNIT_SYMBOLS[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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqFA,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;AAEnD,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,gBACH,MAAM,YAAY,iBACnB;EAIF,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,sBAAsB;AAEzC,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"}
@@ -11,11 +11,12 @@
11
11
  */
12
12
 
13
13
 
14
- import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
14
+ import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
15
15
  import { formatCircleTooltip } from "../../shared/constants.js";
16
16
  import { computeCircleMeasurements } from "../../shared/utils/geometry-measurements.js";
17
17
  import { BaseTransformMode } from "./base-transform-mode.js";
18
18
  import { centroid } from "@turf/turf";
19
+ import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
19
20
  import { ResizeCircleMode, TranslateMode } from "@deck.gl-community/editable-layers";
20
21
 
21
22
  //#region src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts
@@ -100,9 +101,10 @@ var CircleTransformMode = class extends BaseTransformMode {
100
101
  const center = centroid(feature).geometry.coordinates;
101
102
  const firstPoint = coordinates[0];
102
103
  const { radius, area: area$1 } = computeCircleMeasurements(center, firstPoint, distanceUnits);
104
+ const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
103
105
  this.tooltip = {
104
106
  position: mapCoords,
105
- text: formatCircleTooltip(radius, area$1, getDistanceUnitAbbreviation(distanceUnits))
107
+ text: formatCircleTooltip(radius, area$1, unitAbbrev)
106
108
  };
107
109
  }
108
110
  };
@@ -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 * ## 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 radius 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 // biome-ignore lint/suspicious/noExplicitAny: Library type inconsistency — see HandleMatcher JSDoc in base-transform-mode\n return this.translateMode as any;\n }\n\n /**\n * Update the tooltip with circle radius 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 { radius, 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(radius, 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;AAEnD,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,QAAQ,iBAAS,0BACvB,QACA,YACA,cACD;AAID,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,QAAQA,QALjB,4BAA4B,cAAc,CAKR;GACpD"}
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 DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\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 { DEFAULT_DISTANCE_UNITS } 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 radius 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 // biome-ignore lint/suspicious/noExplicitAny: Library type inconsistency — see HandleMatcher JSDoc in base-transform-mode\n return this.translateMode as any;\n }\n\n /**\n * Update the tooltip with circle radius 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 as DistanceUnit) ??\n 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 { radius, area } = computeCircleMeasurements(\n center,\n firstPoint,\n distanceUnits,\n );\n const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(radius, area, unitAbbrev),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,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;AAEnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,WAC/B;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACH,MAAM,YAAY,iBACnB;EAIF,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,QAAQ,iBAAS,0BACvB,QACA,YACA,cACD;EACD,MAAM,aAAa,sBAAsB;AAGzC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,QAAQA,QAAM,WAAW;GACpD"}
@@ -185,11 +185,22 @@ function clearEditingState(mapId) {
185
185
  editStore.clear(mapId);
186
186
  }
187
187
  /**
188
- * Update feature from the layer component (called during drag operations)
188
+ * Update the feature currently being edited.
189
+ *
190
+ * Called internally by the layer during drag operations, and also available
191
+ * to consumers via the `useEditShape` hook for form-driven updates.
192
+ *
189
193
  * @param mapId - The map instance ID.
190
- * @param feature - The updated GeoJSON feature from the editable layer.
194
+ * @param feature - The updated GeoJSON feature to store as the live editing state.
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * // From a form input handler
199
+ * const newGeometry = circle(center, radius, { units: 'kilometers' }).geometry;
200
+ * updateFeature(mapId, { ...currentFeature, geometry: newGeometry });
201
+ * ```
191
202
  */
192
- function updateFeatureFromLayer(mapId, feature) {
203
+ function updateFeature(mapId, feature) {
193
204
  editStore.set(mapId, { featureBeingEdited: feature });
194
205
  }
195
206
  /**
@@ -263,5 +274,5 @@ function disableEditPanning(mapId) {
263
274
  }
264
275
 
265
276
  //#endregion
266
- export { cancelEditingFromLayer, clearEditingState, disableEditPanning, editStore, enableEditPanning, saveEditingFromLayer, updateFeatureFromLayer };
277
+ export { cancelEditingFromLayer, clearEditingState, disableEditPanning, editStore, enableEditPanning, saveEditingFromLayer, updateFeature };
267
278
  //# sourceMappingURL=store.js.map