@accelint/map-toolkit 1.4.0 → 2.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.
- package/CHANGELOG.md +45 -0
- package/catalog-info.yaml +4 -4
- package/dist/camera/events.js +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/camera/store.d.ts +1 -1
- package/dist/camera/store.js +4 -6
- package/dist/camera/store.js.map +1 -1
- package/dist/camera/types.d.ts +1 -1
- package/dist/camera/types.js +1 -1
- package/dist/cursor-coordinates/constants.js +1 -1
- package/dist/cursor-coordinates/index.d.ts +1 -1
- package/dist/cursor-coordinates/index.js +1 -1
- package/dist/cursor-coordinates/store.d.ts +1 -1
- package/dist/cursor-coordinates/store.js +1 -1
- package/dist/cursor-coordinates/types.d.ts +1 -1
- package/dist/cursor-coordinates/types.js +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.js +4 -9
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/constants.js +1 -1
- package/dist/deckgl/base-map/controls.d.ts +1 -1
- package/dist/deckgl/base-map/controls.js +1 -1
- package/dist/deckgl/base-map/events.js +1 -1
- package/dist/deckgl/base-map/index.d.ts +3 -3
- package/dist/deckgl/base-map/index.js +15 -7
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/base-map/provider.d.ts +3 -3
- package/dist/deckgl/base-map/provider.js +3 -5
- package/dist/deckgl/base-map/provider.js.map +1 -1
- package/dist/deckgl/base-map/types.d.ts +1 -1
- package/dist/deckgl/base-map/types.js +1 -1
- package/dist/deckgl/index.d.ts +4 -4
- package/dist/deckgl/index.js +4 -4
- package/dist/deckgl/saved-viewports/index.d.ts +1 -1
- package/dist/deckgl/saved-viewports/index.js +1 -1
- package/dist/deckgl/saved-viewports/storage.d.ts +1 -1
- package/dist/deckgl/saved-viewports/storage.js +5 -10
- package/dist/deckgl/saved-viewports/storage.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +66 -13
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/index.d.ts +74 -35
- package/dist/deckgl/shapes/display-shape-layer/index.js +381 -154
- package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/store.js +8 -2
- package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/types.d.ts +108 -19
- package/dist/deckgl/shapes/display-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +66 -36
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js +407 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +151 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +50 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +28 -39
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/constants.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -17
- package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +8 -9
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +4 -21
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +4 -34
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +11 -12
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +2 -32
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/store.js +38 -4
- package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +4 -9
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/constants.js +17 -2
- package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
- package/dist/deckgl/shapes/edit-shape-layer/index.js +52 -21
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +4 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +7 -7
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +78 -14
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +14 -2
- package/dist/deckgl/shapes/edit-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +2 -2
- package/dist/deckgl/shapes/index.d.ts +4 -4
- package/dist/deckgl/shapes/index.js +5 -5
- package/dist/deckgl/shapes/shared/constants.d.ts +4 -3
- package/dist/deckgl/shapes/shared/constants.js +55 -15
- package/dist/deckgl/shapes/shared/constants.js.map +1 -1
- package/dist/deckgl/shapes/shared/events.d.ts +5 -1
- package/dist/deckgl/shapes/shared/events.js +1 -1
- package/dist/deckgl/shapes/shared/events.js.map +1 -1
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +19 -16
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +174 -53
- package/dist/deckgl/shapes/shared/types.js +155 -2
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +29 -24
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/layer-config.js +9 -6
- package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/mode-utils.js +50 -20
- package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js +22 -15
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +38 -14
- package/dist/deckgl/shapes/shared/utils/style-utils.js +43 -32
- package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +1 -1
- package/dist/deckgl/symbol-layer/fiber.js +1 -1
- package/dist/deckgl/symbol-layer/index.d.ts +1 -1
- package/dist/deckgl/symbol-layer/index.js +1 -1
- package/dist/deckgl/text-layer/character-sets.js +1 -1
- package/dist/deckgl/text-layer/default-settings.d.ts +1 -1
- package/dist/deckgl/text-layer/default-settings.js +1 -1
- package/dist/deckgl/text-layer/fiber.d.ts +1 -1
- package/dist/deckgl/text-layer/fiber.js +1 -1
- package/dist/deckgl/text-layer/index.d.ts +1 -1
- package/dist/deckgl/text-layer/index.js +1 -1
- package/dist/deckgl/text-settings.d.ts +3 -3
- package/dist/deckgl/text-settings.js +1 -1
- package/dist/map-cursor/events.js +1 -1
- package/dist/map-cursor/index.d.ts +1 -1
- package/dist/map-cursor/index.js +1 -1
- package/dist/map-cursor/store.d.ts +1 -1
- package/dist/map-cursor/store.js +1 -1
- package/dist/map-cursor/types.d.ts +1 -1
- package/dist/map-cursor/types.js +1 -1
- package/dist/map-cursor/use-map-cursor.d.ts +1 -1
- package/dist/map-cursor/use-map-cursor.js +1 -1
- package/dist/map-mode/events.js +1 -1
- package/dist/map-mode/index.d.ts +1 -1
- package/dist/map-mode/index.js +1 -1
- package/dist/map-mode/store.d.ts +1 -1
- package/dist/map-mode/store.js +3 -8
- package/dist/map-mode/store.js.map +1 -1
- package/dist/map-mode/types.d.ts +1 -1
- package/dist/map-mode/types.js +1 -1
- package/dist/map-mode/use-map-mode.d.ts +1 -1
- package/dist/map-mode/use-map-mode.js +1 -1
- package/dist/maplibre/hooks/use-maplibre.d.ts +1 -1
- package/dist/maplibre/hooks/use-maplibre.js +1 -1
- package/dist/maplibre/index.d.ts +1 -1
- package/dist/maplibre/index.js +1 -1
- package/dist/shared/cleanup.d.ts +58 -0
- package/dist/shared/cleanup.js +93 -0
- package/dist/shared/cleanup.js.map +1 -0
- package/dist/shared/constants.js +1 -1
- package/dist/shared/create-map-store.d.ts +13 -1
- package/dist/shared/create-map-store.js +9 -4
- package/dist/shared/create-map-store.js.map +1 -1
- package/dist/shared/logger.js +31 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/units.js +1 -1
- package/dist/viewport/index.d.ts +1 -1
- package/dist/viewport/index.js +1 -1
- package/dist/viewport/store.d.ts +1 -1
- package/dist/viewport/store.js +1 -1
- package/dist/viewport/types.d.ts +1 -1
- package/dist/viewport/types.js +1 -1
- package/dist/viewport/utils.d.ts +1 -1
- package/dist/viewport/utils.js +1 -1
- package/dist/viewport/viewport-size.d.ts +3 -3
- package/dist/viewport/viewport-size.js +1 -1
- package/package.json +22 -19
- package/dist/hotkey-manager/dist/react/use-hotkey.js +0 -39
- package/dist/hotkey-manager/dist/react/use-hotkey.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatCircleTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
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
|
-
import { ResizeCircleMode, TranslateMode } from "@deck.gl-community/editable-layers";
|
|
19
18
|
import { centroid } from "@turf/turf";
|
|
19
|
+
import { ResizeCircleMode, TranslateMode } from "@deck.gl-community/editable-layers";
|
|
20
20
|
|
|
21
21
|
//#region src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts
|
|
22
22
|
/**
|
|
@@ -26,7 +26,7 @@ import { centroid } from "@turf/turf";
|
|
|
26
26
|
* This composite mode provides:
|
|
27
27
|
* - **Resize** (ResizeCircleMode): Drag edge to resize from center
|
|
28
28
|
* - **Translation** (TranslateMode): Drag the circle body to move it
|
|
29
|
-
* - **Live tooltip**: Shows
|
|
29
|
+
* - **Live tooltip**: Shows radius and area during resize
|
|
30
30
|
*
|
|
31
31
|
* ## Handle Priority Logic
|
|
32
32
|
* When drag starts, modes are evaluated in this priority order:
|
|
@@ -76,7 +76,7 @@ var CircleTransformMode = class extends BaseTransformMode {
|
|
|
76
76
|
return this.translateMode;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Update the tooltip with circle
|
|
79
|
+
* Update the tooltip with circle radius and area during resize.
|
|
80
80
|
*/
|
|
81
81
|
onDragging(event, props) {
|
|
82
82
|
if (this.activeDragMode !== this.resizeMode) return;
|
|
@@ -99,10 +99,10 @@ var CircleTransformMode = class extends BaseTransformMode {
|
|
|
99
99
|
}
|
|
100
100
|
const center = centroid(feature).geometry.coordinates;
|
|
101
101
|
const firstPoint = coordinates[0];
|
|
102
|
-
const {
|
|
102
|
+
const { radius, area: area$1 } = computeCircleMeasurements(center, firstPoint, distanceUnits);
|
|
103
103
|
this.tooltip = {
|
|
104
104
|
position: mapCoords,
|
|
105
|
-
text: formatCircleTooltip(
|
|
105
|
+
text: formatCircleTooltip(radius, area$1, getDistanceUnitAbbreviation(distanceUnits))
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
};
|
|
@@ -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
|
|
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,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -36,7 +36,7 @@ import { TranslateMode, ViewMode } from "@deck.gl-community/editable-layers";
|
|
|
36
36
|
*
|
|
37
37
|
* CircleTransformMode combines ResizeCircleMode with TranslateMode
|
|
38
38
|
* for circles, allowing resize from edge plus drag to translate.
|
|
39
|
-
* Shows live
|
|
39
|
+
* Shows live radius/area tooltips during resize.
|
|
40
40
|
*
|
|
41
41
|
* PointTranslateMode allows clicking anywhere on the map to reposition
|
|
42
42
|
* a point, or dragging the point for traditional translation behavior.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/index.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { TranslateMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { BoundingTransformMode } from './bounding-transform-mode';\nimport { CircleTransformMode } from './circle-transform-mode';\nimport { PointTranslateMode } from './point-translate-mode';\nimport { VertexTransformMode } from './vertex-transform-mode';\nimport type { EditMode } from '../types';\n\n/**\n * Cached edit mode instances.\n *\n * CRITICAL: Mode instances must be cached at module level to prevent\n * deck.gl assertion failures. Creating new mode instances on each render\n * causes the EditableGeoJsonLayer to fail with assertion errors.\n *\n * BoundingTransformMode combines ScaleModeWithFreeTransform, RotateMode, and\n * TranslateMode for shapes without vertex editing (ellipses, rectangles),\n * allowing non-uniform scaling plus rotate/translate via bounding box handles.\n * Shows live dimension tooltips during scaling.\n *\n * VertexTransformMode combines ModifyMode with ScaleModeWithFreeTransform,\n * RotateMode, and TranslateMode for shapes that support vertex editing\n * (polygons, lines), allowing vertex manipulation plus scale/rotate/translate.\n *\n * CircleTransformMode combines ResizeCircleMode with TranslateMode\n * for circles, allowing resize from edge plus drag to translate.\n * Shows live
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/index.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { TranslateMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { BoundingTransformMode } from './bounding-transform-mode';\nimport { CircleTransformMode } from './circle-transform-mode';\nimport { PointTranslateMode } from './point-translate-mode';\nimport { VertexTransformMode } from './vertex-transform-mode';\nimport type { EditMode } from '../types';\n\n/**\n * Cached edit mode instances.\n *\n * CRITICAL: Mode instances must be cached at module level to prevent\n * deck.gl assertion failures. Creating new mode instances on each render\n * causes the EditableGeoJsonLayer to fail with assertion errors.\n *\n * BoundingTransformMode combines ScaleModeWithFreeTransform, RotateMode, and\n * TranslateMode for shapes without vertex editing (ellipses, rectangles),\n * allowing non-uniform scaling plus rotate/translate via bounding box handles.\n * Shows live dimension tooltips during scaling.\n *\n * VertexTransformMode combines ModifyMode with ScaleModeWithFreeTransform,\n * RotateMode, and TranslateMode for shapes that support vertex editing\n * (polygons, lines), allowing vertex manipulation plus scale/rotate/translate.\n *\n * CircleTransformMode combines ResizeCircleMode with TranslateMode\n * for circles, allowing resize from edge plus drag to translate.\n * Shows live radius/area tooltips during resize.\n *\n * PointTranslateMode allows clicking anywhere on the map to reposition\n * a point, or dragging the point for traditional translation behavior.\n *\n * TranslateMode allows dragging to move the shape (generic translation).\n */\nconst EDIT_MODE_INSTANCES = {\n view: new ViewMode(),\n 'bounding-transform': new BoundingTransformMode(),\n 'vertex-transform': new VertexTransformMode(),\n 'circle-transform': new CircleTransformMode(),\n translate: new TranslateMode(),\n 'point-translate': new PointTranslateMode(),\n} as const;\n\n/**\n * Get the cached mode instance for an edit mode.\n *\n * Returns the pre-instantiated edit mode for the specified mode type.\n * Modes are cached at module level to prevent deck.gl assertion failures\n * that occur when creating new mode instances on each render.\n *\n * ## Available Edit Modes\n * - `'bounding-transform'`: For shapes without vertex editing (rectangles, ellipses)\n * - `'vertex-transform'`: For shapes with vertex editing (polygons, lines)\n * - `'circle-transform'`: For circles (resize from edge + translate)\n * - `'point-translate'`: For points (click to place + drag to move)\n * - `'translate'`: Generic translation (drag to move)\n *\n * @param mode - The edit mode to get the instance for\n * @returns The cached mode instance\n *\n * @example\n * ```typescript\n * import { getEditModeInstance } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes';\n *\n * // Get the bounding transform mode for editing rectangles/ellipses\n * const boundingMode = getEditModeInstance('bounding-transform');\n *\n * // Use with EditableGeoJsonLayer\n * const layer = new EditableGeoJsonLayer({\n * mode: boundingMode,\n * // ... other props\n * });\n * ```\n */\nexport function getEditModeInstance(\n mode: EditMode,\n): (typeof EDIT_MODE_INSTANCES)[EditMode] {\n return EDIT_MODE_INSTANCES[mode];\n}\n\n/**\n * Get the ViewMode instance (for when not editing).\n *\n * Returns the pre-instantiated ViewMode which is the default mode when\n * no editing operation is active. This mode allows viewing and interacting\n * with the map without editing shapes.\n *\n * @returns The cached ViewMode instance\n *\n * @example\n * ```typescript\n * import { getViewModeInstance } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes';\n *\n * // Get the view mode (default when not editing)\n * const viewMode = getViewModeInstance();\n *\n * // Use with EditableGeoJsonLayer\n * const layer = new EditableGeoJsonLayer({\n * mode: viewMode,\n * // ... other props\n * });\n * ```\n */\nexport function getViewModeInstance(): ViewMode {\n return EDIT_MODE_INSTANCES.view;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAM,sBAAsB;CAC1B,MAAM,IAAI,UAAU;CACpB,sBAAsB,IAAI,uBAAuB;CACjD,oBAAoB,IAAI,qBAAqB;CAC7C,oBAAoB,IAAI,qBAAqB;CAC7C,WAAW,IAAI,eAAe;CAC9B,mBAAmB,IAAI,oBAAoB;CAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCD,SAAgB,oBACd,MACwC;AACxC,QAAO,oBAAoB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"point-translate-mode.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type ClickEvent,\n type DraggingEvent,\n type FeatureCollection,\n GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type StartDraggingEvent,\n type StopDraggingEvent,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\n\n/**\n * Edit mode for Point shapes that supports both click-to-place and drag behaviors.\n *\n * ## Capabilities\n * This mode provides two ways to reposition a point:\n * - **Click on empty space**: Instantly moves the point to the clicked location\n * - **Drag the point**: Traditional click-and-drag behavior (via TranslateMode)\n *\n * ## Behavior Details\n * - Clicking anywhere on the map (that isn't the point itself) repositions the point\n * - Clicking directly on the point and dragging works as traditional translation\n * - Both behaviors emit the 'translated' edit type for consistency with existing event handling\n *\n * @example\n * ```typescript\n * import { PointTranslateMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/point-translate-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for points\n * const mode = new PointTranslateMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: pointFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * });\n * ```\n */\nexport class PointTranslateMode extends GeoJsonEditMode {\n private translateMode = new TranslateMode();\n\n /**\n * Handle click events to reposition the point.\n *\n * If the click is on empty map space (not on the point itself),\n * moves the point to the clicked location immediately.\n *\n * @param event - Click event containing map coordinates and pick information\n * @param props - Mode props containing data, selected indexes, and edit callback\n */\n override handleClick(\n event: ClickEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // If clicked on the point itself or a guide, let drag handle it\n const clickedOnFeature = event.picks?.some(\n (pick) => pick.isGuide || pick.featureIndex !== undefined,\n );\n\n if (clickedOnFeature) {\n return;\n }\n\n const { mapCoords } = event;\n const selectedIndex = props.selectedIndexes?.[0];\n\n if (selectedIndex === undefined) {\n return;\n }\n\n const feature = props.data.features[selectedIndex];\n\n if (!feature) {\n return;\n }\n\n // Create updated feature with new coordinates\n const updatedFeature = {\n ...feature,\n geometry: {\n type: 'Point' as const,\n coordinates: mapCoords,\n },\n };\n\n // Emit edit action with 'translated' type to work with existing completion handlers\n props.onEdit({\n updatedData: {\n ...props.data,\n features: [updatedFeature],\n },\n editType: 'translated',\n editContext: {\n featureIndexes: [selectedIndex],\n },\n });\n }\n\n /**\n * Delegate pointer move events to TranslateMode for cursor updates.\n *\n * @param event - Pointer move event with current cursor position\n * @param props - Mode props containing state and configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handlePointerMove(event, props);\n }\n\n /**\n * Delegate start dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Drag start event with pointer down coordinates\n * @param props - Mode props containing data and edit callback\n */\n override handleStartDragging(\n event: StartDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleStartDragging(event, props);\n }\n\n /**\n * Delegate dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Dragging event with current and previous pointer positions\n * @param props - Mode props containing data and edit callback\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleDragging(event, props);\n }\n\n /**\n * Delegate stop dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Drag stop event with final pointer position\n * @param props - Mode props containing data and edit callback\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleStopDragging(event, props);\n }\n\n /**\n * Delegate guide rendering to TranslateMode.\n *\n * @param props - Mode props containing data and selected indexes\n * @returns Guide feature collection for rendering edit handles\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n return this.translateMode.getGuides(props);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,qBAAb,cAAwC,gBAAgB;
|
|
1
|
+
{"version":3,"file":"point-translate-mode.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type ClickEvent,\n type DraggingEvent,\n type FeatureCollection,\n GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type StartDraggingEvent,\n type StopDraggingEvent,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\n\n/**\n * Edit mode for Point shapes that supports both click-to-place and drag behaviors.\n *\n * ## Capabilities\n * This mode provides two ways to reposition a point:\n * - **Click on empty space**: Instantly moves the point to the clicked location\n * - **Drag the point**: Traditional click-and-drag behavior (via TranslateMode)\n *\n * ## Behavior Details\n * - Clicking anywhere on the map (that isn't the point itself) repositions the point\n * - Clicking directly on the point and dragging works as traditional translation\n * - Both behaviors emit the 'translated' edit type for consistency with existing event handling\n *\n * @example\n * ```typescript\n * import { PointTranslateMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/point-translate-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for points\n * const mode = new PointTranslateMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: pointFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * });\n * ```\n */\nexport class PointTranslateMode extends GeoJsonEditMode {\n // biome-ignore lint/suspicious/noExplicitAny: TranslateMode narrows ModeProps to SimpleFeatureCollection but GeoJsonEditMode uses FeatureCollection — library type inconsistency\n private translateMode: GeoJsonEditMode = new TranslateMode() as any;\n\n /**\n * Handle click events to reposition the point.\n *\n * If the click is on empty map space (not on the point itself),\n * moves the point to the clicked location immediately.\n *\n * @param event - Click event containing map coordinates and pick information\n * @param props - Mode props containing data, selected indexes, and edit callback\n */\n override handleClick(\n event: ClickEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // If clicked on the point itself or a guide, let drag handle it\n const clickedOnFeature = event.picks?.some(\n (pick) => pick.isGuide || pick.featureIndex !== undefined,\n );\n\n if (clickedOnFeature) {\n return;\n }\n\n const { mapCoords } = event;\n const selectedIndex = props.selectedIndexes?.[0];\n\n if (selectedIndex === undefined) {\n return;\n }\n\n const feature = props.data.features[selectedIndex];\n\n if (!feature) {\n return;\n }\n\n // Create updated feature with new coordinates\n const updatedFeature = {\n ...feature,\n geometry: {\n type: 'Point' as const,\n coordinates: mapCoords,\n },\n };\n\n // Emit edit action with 'translated' type to work with existing completion handlers\n props.onEdit({\n updatedData: {\n ...props.data,\n features: [updatedFeature],\n },\n editType: 'translated',\n editContext: {\n featureIndexes: [selectedIndex],\n },\n });\n }\n\n /**\n * Delegate pointer move events to TranslateMode for cursor updates.\n *\n * @param event - Pointer move event with current cursor position\n * @param props - Mode props containing state and configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handlePointerMove(event, props);\n }\n\n /**\n * Delegate start dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Drag start event with pointer down coordinates\n * @param props - Mode props containing data and edit callback\n */\n override handleStartDragging(\n event: StartDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleStartDragging(event, props);\n }\n\n /**\n * Delegate dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Dragging event with current and previous pointer positions\n * @param props - Mode props containing data and edit callback\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleDragging(event, props);\n }\n\n /**\n * Delegate stop dragging to TranslateMode for traditional drag behavior.\n *\n * @param event - Drag stop event with final pointer position\n * @param props - Mode props containing data and edit callback\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n this.translateMode.handleStopDragging(event, props);\n }\n\n /**\n * Delegate guide rendering to TranslateMode.\n *\n * @param props - Mode props containing data and selected indexes\n * @returns Guide feature collection for rendering edit handles\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n return this.translateMode.getGuides(props);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,qBAAb,cAAwC,gBAAgB;CAEtD,AAAQ,gBAAiC,IAAI,eAAe;;;;;;;;;;CAW5D,AAAS,YACP,OACA,OACM;AAMN,MAJyB,MAAM,OAAO,MACnC,SAAS,KAAK,WAAW,KAAK,iBAAiB,OACjD,CAGC;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBAAgB,MAAM,kBAAkB;AAE9C,MAAI,kBAAkB,OACpB;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAEpC,MAAI,CAAC,QACH;EAIF,MAAM,iBAAiB;GACrB,GAAG;GACH,UAAU;IACR,MAAM;IACN,aAAa;IACd;GACF;AAGD,QAAM,OAAO;GACX,aAAa;IACX,GAAG,MAAM;IACT,UAAU,CAAC,eAAe;IAC3B;GACD,UAAU;GACV,aAAa,EACX,gBAAgB,CAAC,cAAc,EAChC;GACF,CAAC;;;;;;;;CASJ,AAAS,kBACP,OACA,OACM;AACN,OAAK,cAAc,kBAAkB,OAAO,MAAM;;;;;;;;CASpD,AAAS,oBACP,OACA,OACM;AACN,OAAK,cAAc,oBAAoB,OAAO,MAAM;;;;;;;;CAStD,AAAS,eACP,OACA,OACM;AACN,OAAK,cAAc,eAAe,OAAO,MAAM;;;;;;;;CASjD,AAAS,mBACP,OACA,OACM;AACN,OAAK,cAAc,mBAAmB,OAAO,MAAM;;;;;;;;CASrD,AAAS,UACP,OACwB;AACxB,SAAO,KAAK,cAAc,UAAU,MAAM"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { ImmutableFeatureCollection, RotateMode } from "@deck.gl-community/editable-layers";
|
|
15
14
|
import { bearing, centroid, transformRotate } from "@turf/turf";
|
|
15
|
+
import { ImmutableFeatureCollection, RotateMode } from "@deck.gl-community/editable-layers";
|
|
16
16
|
|
|
17
17
|
//#region src/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.ts
|
|
18
18
|
/** Snap interval in degrees (45° = 8 positions around the circle) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotate-mode-with-snap.js","names":["rotatedFeatures:
|
|
1
|
+
{"version":3,"file":"rotate-mode-with-snap.js","names":["rotatedFeatures: SimpleFeatureCollection"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n ImmutableFeatureCollection,\n type ModeProps,\n RotateMode,\n type SimpleFeatureCollection,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport { bearing, centroid, transformRotate } from '@turf/turf';\nimport type { Position } from 'geojson';\n\n/** Snap interval in degrees (45° = 8 positions around the circle) */\nconst SNAP_INTERVAL_DEGREES = 45;\n\n/**\n * Calculate the angle between two points relative to a centroid.\n * Uses turfBearing for geographic bearing convention (matches parent RotateMode).\n * Returns angle in degrees.\n *\n * Note: centroid must be a turf Point Feature (not just coordinates) to match\n * the parent RotateMode's behavior exactly.\n */\nfunction getRotationAngle(\n centroidFeature: ReturnType<typeof centroid>,\n startPoint: Position,\n endPoint: Position,\n): number {\n const bearing1 = bearing(centroidFeature, startPoint);\n const bearing2 = bearing(centroidFeature, endPoint);\n return bearing2 - bearing1;\n}\n\n/**\n * Snap an angle to the nearest interval.\n */\nfunction snapAngle(angle: number, interval: number): number {\n return Math.round(angle / interval) * interval;\n}\n\n/**\n * Extends RotateMode to support snapping rotation to 45° intervals.\n *\n * ## Features\n * - **Default**: Free rotation (smooth, continuous angles)\n * - **With Shift**: Snap to 45° intervals (0°, 45°, 90°, 135°, 180°, etc.)\n *\n * This allows precise alignment of shapes to common angles, making it easy to\n * create axis-aligned or diagonally-aligned shapes.\n *\n * ## Implementation\n * The snap behavior is controlled by the `modeConfig.snapRotation` property,\n * which is set by BaseTransformMode when the Shift key is held. This class\n * calculates the rotation angle and rounds it to the nearest 45° interval\n * when snapping is enabled.\n *\n * ## Snap Interval\n * The snap interval is fixed at 45° (8 positions around the circle), providing\n * these angles: 0°, 45°, 90°, 135°, 180°, 225°, 270°, 315°.\n *\n * @example\n * ```typescript\n * import { RotateModeWithSnap } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * const mode = new RotateModeWithSnap();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: featureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: {\n * snapRotation: true, // Enable 45° snapping\n * },\n * });\n * ```\n */\nexport class RotateModeWithSnap extends RotateMode {\n /**\n * Override handleDragging to support snapped rotation.\n * When snapRotation is true, rotates to nearest 45° interval.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<SimpleFeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isRotating) {\n return;\n }\n\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotating',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final rotated geometry with snap.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<SimpleFeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleStopDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isRotating) {\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotated',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n // Reset state\n self._geometryBeingRotated = null;\n self._selectedEditHandle = null;\n self._isRotating = false;\n }\n }\n\n /**\n * Get a rotate action, optionally snapping to 45° intervals.\n */\n private getRotateActionWithSnap(\n startDragPoint: Position,\n currentPoint: Position,\n editType: string,\n props: ModeProps<SimpleFeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._geometryBeingRotated) {\n return null;\n }\n\n const geometry = self._geometryBeingRotated as SimpleFeatureCollection;\n const centerFeature = centroid(geometry);\n\n // Calculate the rotation angle (pass centroid Feature to match parent RotateMode)\n let angle = getRotationAngle(centerFeature, startDragPoint, currentPoint);\n\n // Snap to 45° intervals if enabled\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n if (snapRotation) {\n angle = snapAngle(angle, SNAP_INTERVAL_DEGREES);\n }\n\n // Apply the rotation using turf (use centroid Feature as pivot to match parent)\n const rotatedFeatures: SimpleFeatureCollection = transformRotate(\n geometry,\n angle,\n {\n pivot: centerFeature,\n },\n );\n\n // Build the updated data using ImmutableFeatureCollection (matches parent RotateMode)\n const selectedIndexes = props.selectedIndexes;\n let updatedData = new ImmutableFeatureCollection(props.data);\n\n for (let i = 0; i < selectedIndexes.length; i++) {\n const selectedIndex = selectedIndexes[i];\n const movedFeature = rotatedFeatures.features[i];\n if (selectedIndex !== undefined && movedFeature) {\n updatedData = updatedData.replaceGeometry(\n selectedIndex,\n movedFeature.geometry,\n );\n }\n }\n\n return {\n updatedData: updatedData.getObject(),\n editType,\n editContext: {\n featureIndexes: selectedIndexes,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,wBAAwB;;;;;;;;;AAU9B,SAAS,iBACP,iBACA,YACA,UACQ;CACR,MAAM,WAAW,QAAQ,iBAAiB,WAAW;AAErD,QADiB,QAAQ,iBAAiB,SAAS,GACjC;;;;;AAMpB,SAAS,UAAU,OAAe,UAA0B;AAC1D,QAAO,KAAK,MAAM,QAAQ,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCxC,IAAa,qBAAb,cAAwC,WAAW;;;;;;CAMjD,AAAS,eACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,eAAe,OAAO,MAAM;AAClC;;AAMF,MAAI,CAFS,KAEH,YACR;EAGF,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,YACA,MACD;AAED,MAAI,aACF,OAAM,OAAO,aAAa;AAG5B,QAAM,WAAW;;;;;;CAOnB,AAAS,mBACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,mBAAmB,OAAO,MAAM;AACtC;;EAIF,MAAM,OAAO;AAEb,MAAI,KAAK,aAAa;GACpB,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,WACA,MACD;AAED,OAAI,aACF,OAAM,OAAO,aAAa;AAI5B,QAAK,wBAAwB;AAC7B,QAAK,sBAAsB;AAC3B,QAAK,cAAc;;;;;;CAOvB,AAAQ,wBACN,gBACA,cACA,UACA,OACA;EAEA,MAAM,OAAO;AAEb,MAAI,CAAC,KAAK,sBACR,QAAO;EAGT,MAAM,WAAW,KAAK;EACtB,MAAM,gBAAgB,SAAS,SAAS;EAGxC,IAAI,QAAQ,iBAAiB,eAAe,gBAAgB,aAAa;AAIzE,MADqB,MAAM,YAAY,gBAAgB,MAErD,SAAQ,UAAU,OAAO,sBAAsB;EAIjD,MAAMA,kBAA2C,gBAC/C,UACA,OACA,EACE,OAAO,eACR,CACF;EAGD,MAAM,kBAAkB,MAAM;EAC9B,IAAI,cAAc,IAAI,2BAA2B,MAAM,KAAK;AAE5D,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,gBAAgB,gBAAgB;GACtC,MAAM,eAAe,gBAAgB,SAAS;AAC9C,OAAI,kBAAkB,UAAa,aACjC,eAAc,YAAY,gBACxB,eACA,aAAa,SACd;;AAIL,SAAO;GACL,aAAa,YAAY,WAAW;GACpC;GACA,aAAa,EACX,gBAAgB,iBACjB;GACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scale-mode-with-free-transform.js","names":["origin: Position"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type FeatureCollection,\n type ModeProps,\n ScaleMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport type { Position } from 'geojson';\n\ntype ScaleFactors = {\n scaleX: number;\n scaleY: number;\n};\n\ntype ScaleContext = {\n origin: Position;\n scaleFactors: ScaleFactors;\n geometry: FeatureCollection;\n};\n\n/**\n * Extends ScaleMode to support non-uniform (free) scaling.\n *\n * ## Features\n * - **Default**: Free scaling - can stretch/squish in any direction\n * - **With Shift**: Uniform scaling (maintains aspect ratio)\n *\n * ## How Non-Uniform Scaling Works\n *\n * Non-uniform scaling applies separate X and Y scale factors based on cursor\n * movement relative to the opposite corner (the \"origin\" or anchor point).\n *\n * ```\n * Origin (opposite corner) Drag Handle (start)\n * ●───────────────────────────────────●\n * │ │\n * │ startDelta = start - origin │\n * │ currentDelta = current - origin\n * │ │\n * │ scaleX = currentDeltaX / startDeltaX\n * │ scaleY = currentDeltaY / startDeltaY\n * │ │\n * ●───────────────────────────────────● Cursor (current)\n * ```\n *\n * Each coordinate is transformed: `newCoord = origin + (oldCoord - origin) × scale`\n *\n * ## Why We Override Parent's Uniform Scaling\n *\n * The parent ScaleMode calculates uniform scale factors in screen coordinates,\n * which can distort rotated shapes. We calculate our own uniform factor using\n * vector projection to ensure the corner follows the cursor's projection onto\n * the original drag line, preserving shape orientation.\n *\n * ## Minimum Scale Clamping\n *\n * All scale factors are clamped to a minimum of 0.01 to prevent:\n * - Shape inversion (negative scale flipping the shape inside-out)\n * - Shape collapse (scale of 0 making the shape a point/line)\n *\n * @example\n * ```typescript\n * import { ScaleModeWithFreeTransform } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * const mode = new ScaleModeWithFreeTransform();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: featureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: {\n * lockScaling: false, // Default: free scaling (stretch/squish)\n * // lockScaling: true, // Hold Shift: uniform scaling (maintain aspect ratio)\n * },\n * });\n * ```\n */\nexport class ScaleModeWithFreeTransform extends ScaleMode {\n /**\n * Override handleDragging to support non-uniform scaling.\n * When lockScaling is false (default), applies separate X/Y scale factors.\n * When lockScaling is true, applies uniform scaling (same factor for X and Y).\n *\n * Note: We don't use parent's handleDragging for uniform scaling because it\n * calculates scale factors in screen coordinates which distorts rotated shapes.\n * Instead, we calculate our own uniform scale factor based on distance ratios.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isScaling) {\n return;\n }\n\n props.onUpdateCursor(self._cursor);\n\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n // Calculate based on distance from origin to preserve aspect ratio\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaling',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final scaled geometry.\n * Uses the same uniform/non-uniform scaling logic as handleDragging.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isScaling) {\n this.emitFinalScaledGeometry(event, props, self);\n this.resetScaleState(props, self);\n }\n }\n\n /**\n * Get the scale context (origin, scale factors, geometry) for the current drag.\n * Returns null if required data is not available.\n */\n private getScaleContext(\n event: DraggingEvent | StopDraggingEvent,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ): ScaleContext | null {\n if (!self._selectedEditHandle) {\n return null;\n }\n\n const oppositeHandle = self._getOppositeScaleHandle(\n self._selectedEditHandle,\n );\n if (!oppositeHandle) {\n return null;\n }\n\n const geometry = self._geometryBeingScaled;\n if (!geometry) {\n return null;\n }\n\n const origin: Position = oppositeHandle.geometry.coordinates;\n const scaleFactors = this.calculateScaleFactors(event, origin);\n\n return { origin, scaleFactors, geometry };\n }\n\n /**\n * Calculate separate X and Y scale factors based on cursor movement.\n *\n * ## Math Explanation\n *\n * For each axis, we compute: `scale = currentDelta / startDelta`\n *\n * Where:\n * - `startDelta` = distance from origin to where drag started (the handle position)\n * - `currentDelta` = distance from origin to current cursor position\n *\n * Example: If the handle started 100px from origin and cursor is now 150px from origin,\n * scale = 150/100 = 1.5 (shape grows to 150% of original size along that axis).\n *\n * ## Edge Cases\n * - If `startDelta` is near zero (handle very close to origin), we return scale=1\n * to avoid division by zero and prevent erratic behavior\n * - Negative scale values are clamped to 0.01 minimum to prevent shape inversion\n */\n private calculateScaleFactors(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): ScaleFactors {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Calculate deltas from the anchor point (origin) to drag positions\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Epsilon for near-zero checks to avoid division by zero\n const epsilon = 0.0000001;\n // Minimum scale to prevent shape from collapsing or inverting\n const minScale = 0.01;\n\n // Scale = ratio of (current distance from origin) / (original distance from origin)\n // If original distance is near-zero, default to scale=1 (no change)\n const rawScaleX =\n Math.abs(startDeltaX) > epsilon ? currentDeltaX / startDeltaX : 1;\n const rawScaleY =\n Math.abs(startDeltaY) > epsilon ? currentDeltaY / startDeltaY : 1;\n\n // Clamp to prevent negative values (which would invert/flip the shape)\n const scaleX = Math.max(rawScaleX, minScale);\n const scaleY = Math.max(rawScaleY, minScale);\n\n return { scaleX, scaleY };\n }\n\n /**\n * Calculate a uniform scale factor for aspect-ratio-preserving scaling.\n *\n * ## Why Use Projection Instead of Simple Distance Ratio?\n *\n * A naive approach would be: `scale = distance(origin, cursor) / distance(origin, start)`\n *\n * But this fails for diagonal movements - if you drag a corner handle and move\n * perpendicular to the diagonal, the simple distance changes even though you\n * don't want the shape to scale.\n *\n * ## Vector Projection Math\n *\n * Instead, we project the cursor position onto the line defined by\n * (origin → start drag point). This way, only movement along the original\n * drag direction affects the scale.\n *\n * ```\n * Origin Start (drag handle)\n * ●──────────────────●\n * \\\n * \\ Cursor moved diagonally\n * ●\n * /\n * / Projected point (what we measure from)\n * ●──────────────────●\n * Origin Projection\n * ```\n *\n * The projection formula uses the dot product:\n * ```\n * projectedDist = (current · start) / |start|\n * scale = projectedDist / |start|\n * ```\n *\n * This equals: `scale = (current · start) / |start|²`\n */\n private calculateUniformScaleFactor(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): number {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Vector from origin to start drag point (defines the scaling direction)\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Vector from origin to current cursor position\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Distance from origin to start point (|start|)\n const startDist = Math.sqrt(startDeltaX ** 2 + startDeltaY ** 2);\n\n if (startDist < 0.0000001) {\n return 1;\n }\n\n // Dot product: current · start = |current| × |start| × cos(θ)\n // This gives us the component of 'current' that lies along 'start'\n const dotProduct =\n currentDeltaX * startDeltaX + currentDeltaY * startDeltaY;\n\n // Project current point onto start vector: projectedDist = (current · start) / |start|\n const projectedDist = dotProduct / startDist;\n\n // Scale factor = projectedDist / |start| = (current · start) / |start|²\n // Clamp to minScale to prevent negative values (shape inversion)\n const minScale = 0.01;\n const rawScale = projectedDist / startDist;\n return Math.max(rawScale, minScale);\n }\n\n /**\n * Emit the final scaled geometry when dragging stops.\n */\n private emitFinalScaledGeometry(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaled',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n }\n\n /**\n * Reset the scale state after dragging stops.\n */\n private resetScaleState(\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n props.onUpdateCursor(null);\n self._geometryBeingScaled = null;\n self._selectedEditHandle = null;\n self._cursor = null;\n self._isScaling = false;\n }\n\n /**\n * Apply non-uniform scaling to geometry.\n * Transforms each coordinate by scaling X and Y independently around the origin.\n */\n private applyNonUniformScale(\n geometry: FeatureCollection,\n scaleX: number,\n scaleY: number,\n origin: Position,\n ): FeatureCollection {\n const scaledFeatures = geometry.features.map((feature) => {\n const scaledGeometry = this.scaleGeometry(\n feature.geometry,\n scaleX,\n scaleY,\n origin,\n );\n return {\n ...feature,\n geometry: scaledGeometry,\n };\n });\n\n return {\n type: 'FeatureCollection',\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON feature type variance\n features: scaledFeatures as any,\n };\n }\n\n /**\n * Scale a geometry's coordinates non-uniformly around an origin point.\n *\n * ## Coordinate Transformation\n *\n * Each coordinate is transformed using: `new = origin + (old - origin) × scale`\n *\n * This is equivalent to:\n * 1. Translate so origin is at (0,0): `temp = old - origin`\n * 2. Scale: `temp = temp × scale`\n * 3. Translate back: `new = temp + origin`\n *\n * The origin (opposite corner from the drag handle) stays fixed while\n * all other points move proportionally.\n */\n private scaleGeometry(\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - includes Point, LineString, Polygon, Multi* variants\n geometry: any,\n scaleX: number,\n scaleY: number,\n origin: Position,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - return type varies by input\n ): any {\n // Transform a single coordinate around the origin point\n const scaleCoord = (coord: Position): Position => {\n return [\n (origin[0] ?? 0) + ((coord[0] ?? 0) - (origin[0] ?? 0)) * scaleX,\n (origin[1] ?? 0) + ((coord[1] ?? 0) - (origin[1] ?? 0)) * scaleY,\n ];\n };\n\n switch (geometry.type) {\n case 'Point':\n return {\n ...geometry,\n coordinates: scaleCoord(geometry.coordinates),\n };\n case 'LineString':\n case 'MultiPoint':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map(scaleCoord),\n };\n case 'Polygon':\n case 'MultiLineString':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((ring: Position[]) =>\n ring.map(scaleCoord),\n ),\n };\n case 'MultiPolygon':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((polygon: Position[][]) =>\n polygon.map((ring: Position[]) => ring.map(scaleCoord)),\n ),\n };\n default:\n return geometry;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scale-mode-with-free-transform.js","names":["origin: Position"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type ModeProps,\n ScaleMode,\n type SimpleFeatureCollection,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport type { Position } from 'geojson';\n\ntype ScaleFactors = {\n scaleX: number;\n scaleY: number;\n};\n\ntype ScaleContext = {\n origin: Position;\n scaleFactors: ScaleFactors;\n geometry: SimpleFeatureCollection;\n};\n\n/**\n * Extends ScaleMode to support non-uniform (free) scaling.\n *\n * ## Features\n * - **Default**: Free scaling - can stretch/squish in any direction\n * - **With Shift**: Uniform scaling (maintains aspect ratio)\n *\n * ## How Non-Uniform Scaling Works\n *\n * Non-uniform scaling applies separate X and Y scale factors based on cursor\n * movement relative to the opposite corner (the \"origin\" or anchor point).\n *\n * ```\n * Origin (opposite corner) Drag Handle (start)\n * ●───────────────────────────────────●\n * │ │\n * │ startDelta = start - origin │\n * │ currentDelta = current - origin\n * │ │\n * │ scaleX = currentDeltaX / startDeltaX\n * │ scaleY = currentDeltaY / startDeltaY\n * │ │\n * ●───────────────────────────────────● Cursor (current)\n * ```\n *\n * Each coordinate is transformed: `newCoord = origin + (oldCoord - origin) × scale`\n *\n * ## Why We Override Parent's Uniform Scaling\n *\n * The parent ScaleMode calculates uniform scale factors in screen coordinates,\n * which can distort rotated shapes. We calculate our own uniform factor using\n * vector projection to ensure the corner follows the cursor's projection onto\n * the original drag line, preserving shape orientation.\n *\n * ## Minimum Scale Clamping\n *\n * All scale factors are clamped to a minimum of 0.01 to prevent:\n * - Shape inversion (negative scale flipping the shape inside-out)\n * - Shape collapse (scale of 0 making the shape a point/line)\n *\n * @example\n * ```typescript\n * import { ScaleModeWithFreeTransform } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * const mode = new ScaleModeWithFreeTransform();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: featureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: {\n * lockScaling: false, // Default: free scaling (stretch/squish)\n * // lockScaling: true, // Hold Shift: uniform scaling (maintain aspect ratio)\n * },\n * });\n * ```\n */\nexport class ScaleModeWithFreeTransform extends ScaleMode {\n /**\n * Override handleDragging to support non-uniform scaling.\n * When lockScaling is false (default), applies separate X/Y scale factors.\n * When lockScaling is true, applies uniform scaling (same factor for X and Y).\n *\n * Note: We don't use parent's handleDragging for uniform scaling because it\n * calculates scale factors in screen coordinates which distorts rotated shapes.\n * Instead, we calculate our own uniform scale factor based on distance ratios.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<SimpleFeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isScaling) {\n return;\n }\n\n props.onUpdateCursor(self._cursor);\n\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n // Calculate based on distance from origin to preserve aspect ratio\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaling',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final scaled geometry.\n * Uses the same uniform/non-uniform scaling logic as handleDragging.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<SimpleFeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isScaling) {\n this.emitFinalScaledGeometry(event, props, self);\n this.resetScaleState(props, self);\n }\n }\n\n /**\n * Get the scale context (origin, scale factors, geometry) for the current drag.\n * Returns null if required data is not available.\n */\n private getScaleContext(\n event: DraggingEvent | StopDraggingEvent,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ): ScaleContext | null {\n if (!self._selectedEditHandle) {\n return null;\n }\n\n const oppositeHandle = self._getOppositeScaleHandle(\n self._selectedEditHandle,\n );\n if (!oppositeHandle) {\n return null;\n }\n\n const geometry = self._geometryBeingScaled;\n if (!geometry) {\n return null;\n }\n\n const origin: Position = oppositeHandle.geometry.coordinates;\n const scaleFactors = this.calculateScaleFactors(event, origin);\n\n return { origin, scaleFactors, geometry };\n }\n\n /**\n * Calculate separate X and Y scale factors based on cursor movement.\n *\n * ## Math Explanation\n *\n * For each axis, we compute: `scale = currentDelta / startDelta`\n *\n * Where:\n * - `startDelta` = distance from origin to where drag started (the handle position)\n * - `currentDelta` = distance from origin to current cursor position\n *\n * Example: If the handle started 100px from origin and cursor is now 150px from origin,\n * scale = 150/100 = 1.5 (shape grows to 150% of original size along that axis).\n *\n * ## Edge Cases\n * - If `startDelta` is near zero (handle very close to origin), we return scale=1\n * to avoid division by zero and prevent erratic behavior\n * - Negative scale values are clamped to 0.01 minimum to prevent shape inversion\n */\n private calculateScaleFactors(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): ScaleFactors {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Calculate deltas from the anchor point (origin) to drag positions\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Epsilon for near-zero checks to avoid division by zero\n const epsilon = 0.0000001;\n // Minimum scale to prevent shape from collapsing or inverting\n const minScale = 0.01;\n\n // Scale = ratio of (current distance from origin) / (original distance from origin)\n // If original distance is near-zero, default to scale=1 (no change)\n const rawScaleX =\n Math.abs(startDeltaX) > epsilon ? currentDeltaX / startDeltaX : 1;\n const rawScaleY =\n Math.abs(startDeltaY) > epsilon ? currentDeltaY / startDeltaY : 1;\n\n // Clamp to prevent negative values (which would invert/flip the shape)\n const scaleX = Math.max(rawScaleX, minScale);\n const scaleY = Math.max(rawScaleY, minScale);\n\n return { scaleX, scaleY };\n }\n\n /**\n * Calculate a uniform scale factor for aspect-ratio-preserving scaling.\n *\n * ## Why Use Projection Instead of Simple Distance Ratio?\n *\n * A naive approach would be: `scale = distance(origin, cursor) / distance(origin, start)`\n *\n * But this fails for diagonal movements - if you drag a corner handle and move\n * perpendicular to the diagonal, the simple distance changes even though you\n * don't want the shape to scale.\n *\n * ## Vector Projection Math\n *\n * Instead, we project the cursor position onto the line defined by\n * (origin → start drag point). This way, only movement along the original\n * drag direction affects the scale.\n *\n * ```\n * Origin Start (drag handle)\n * ●──────────────────●\n * \\\n * \\ Cursor moved diagonally\n * ●\n * /\n * / Projected point (what we measure from)\n * ●──────────────────●\n * Origin Projection\n * ```\n *\n * The projection formula uses the dot product:\n * ```\n * projectedDist = (current · start) / |start|\n * scale = projectedDist / |start|\n * ```\n *\n * This equals: `scale = (current · start) / |start|²`\n */\n private calculateUniformScaleFactor(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): number {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Vector from origin to start drag point (defines the scaling direction)\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Vector from origin to current cursor position\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Distance from origin to start point (|start|)\n const startDist = Math.sqrt(startDeltaX ** 2 + startDeltaY ** 2);\n\n if (startDist < 0.0000001) {\n return 1;\n }\n\n // Dot product: current · start = |current| × |start| × cos(θ)\n // This gives us the component of 'current' that lies along 'start'\n const dotProduct =\n currentDeltaX * startDeltaX + currentDeltaY * startDeltaY;\n\n // Project current point onto start vector: projectedDist = (current · start) / |start|\n const projectedDist = dotProduct / startDist;\n\n // Scale factor = projectedDist / |start| = (current · start) / |start|²\n // Clamp to minScale to prevent negative values (shape inversion)\n const minScale = 0.01;\n const rawScale = projectedDist / startDist;\n return Math.max(rawScale, minScale);\n }\n\n /**\n * Emit the final scaled geometry when dragging stops.\n */\n private emitFinalScaledGeometry(\n event: StopDraggingEvent,\n props: ModeProps<SimpleFeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaled',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n }\n\n /**\n * Reset the scale state after dragging stops.\n */\n private resetScaleState(\n props: ModeProps<SimpleFeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n props.onUpdateCursor(null);\n self._geometryBeingScaled = null;\n self._selectedEditHandle = null;\n self._cursor = null;\n self._isScaling = false;\n }\n\n /**\n * Apply non-uniform scaling to geometry.\n * Transforms each coordinate by scaling X and Y independently around the origin.\n */\n private applyNonUniformScale(\n geometry: SimpleFeatureCollection,\n scaleX: number,\n scaleY: number,\n origin: Position,\n ): SimpleFeatureCollection {\n const scaledFeatures = geometry.features.map((feature) => {\n const scaledGeometry = this.scaleGeometry(\n feature.geometry,\n scaleX,\n scaleY,\n origin,\n );\n return {\n ...feature,\n geometry: scaledGeometry,\n };\n });\n\n return {\n type: 'FeatureCollection',\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON feature type variance\n features: scaledFeatures as any,\n };\n }\n\n /**\n * Scale a geometry's coordinates non-uniformly around an origin point.\n *\n * ## Coordinate Transformation\n *\n * Each coordinate is transformed using: `new = origin + (old - origin) × scale`\n *\n * This is equivalent to:\n * 1. Translate so origin is at (0,0): `temp = old - origin`\n * 2. Scale: `temp = temp × scale`\n * 3. Translate back: `new = temp + origin`\n *\n * The origin (opposite corner from the drag handle) stays fixed while\n * all other points move proportionally.\n */\n private scaleGeometry(\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - includes Point, LineString, Polygon, Multi* variants\n geometry: any,\n scaleX: number,\n scaleY: number,\n origin: Position,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - return type varies by input\n ): any {\n // Transform a single coordinate around the origin point\n const scaleCoord = (coord: Position): Position => {\n return [\n (origin[0] ?? 0) + ((coord[0] ?? 0) - (origin[0] ?? 0)) * scaleX,\n (origin[1] ?? 0) + ((coord[1] ?? 0) - (origin[1] ?? 0)) * scaleY,\n ];\n };\n\n switch (geometry.type) {\n case 'Point':\n return {\n ...geometry,\n coordinates: scaleCoord(geometry.coordinates),\n };\n case 'LineString':\n case 'MultiPoint':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map(scaleCoord),\n };\n case 'Polygon':\n case 'MultiLineString':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((ring: Position[]) =>\n ring.map(scaleCoord),\n ),\n };\n case 'MultiPolygon':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((polygon: Position[][]) =>\n polygon.map((ring: Position[]) => ring.map(scaleCoord)),\n ),\n };\n default:\n return geometry;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertex-transform-mode.js","names":["guidesToFilterOut: string[]"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n ModifyMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\n\n/**\n * Transform mode for shapes that support vertex editing (polygons and lines).\n *\n * Use this mode for shapes where individual vertices can be dragged to reshape\n * the geometry. This provides the most flexibility for freeform shape editing.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry\n * - **Translation** (TranslateMode): Drag the shape to move it\n * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize\n * - Default: Non-uniform scaling (can stretch/squish)\n * - With Shift: Uniform scaling (maintains aspect ratio)\n * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate\n * - Default: Free rotation\n * - With Shift: Snap to 45° intervals\n *\n * ## Handle Priority Logic\n * When drag starts, modes are evaluated in this priority order:\n * 1. If hovering over a vertex (edit handle) → vertex editing takes priority\n * 2. If hovering over a scale handle → scaling takes priority\n * 3. If hovering over the rotate handle → rotation takes priority\n * 4. Otherwise → dragging the shape translates it\n *\n * ## Visual Behavior\n * The guides from all modes are combined, showing both vertex handles (white circles\n * on existing points) and transform handles (corner/rotation handles on bounding box).\n *\n * ## Tooltips\n * This mode does not show live measurement tooltips during editing because arbitrary\n * polygons don't have well-defined dimensions. Use BoundingTransformMode for shapes\n * like rectangles and ellipses where dimension tooltips are useful.\n *\n * ## Rectangle Special Handling\n * For rectangles, vertex handles are hidden to preserve rotation and right angles.\n * Only scale/rotate/translate handles are shown. Consider using BoundingTransformMode\n * directly for rectangles if vertex editing should never be available.\n *\n * @example\n * ```typescript\n * import { VertexTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for polygons and lines\n * const mode = new VertexTransformMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: polygonFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * // ... other props\n * });\n * ```\n */\nexport class VertexTransformMode extends BaseTransformMode {\n private modifyMode: ModifyMode;\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const modifyMode = new ModifyMode();\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order matters: first mode to handle the event wins\n // We put modify first so vertex handles take priority over translate\n super([modifyMode, scaleMode, rotateMode, translateMode]);\n\n this.modifyMode = modifyMode;\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Vertex handle: existing point on polygon/line\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'existing',\n ),\n mode: this.modifyMode,\n // No shift config - vertex editing doesn't have modifiers\n },\n {\n // Scale handle: corner handles on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide && pick.object?.properties?.editHandleType === 'scale',\n ),\n mode: this.scaleMode,\n shiftConfig: { configKey: 'lockScaling' },\n },\n {\n // Rotate handle: top handle on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.editHandleType === 'rotate',\n ),\n mode: this.rotateMode,\n shiftConfig: { configKey: 'snapRotation' },\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Override getGuides to filter duplicate envelope guides and handle rectangles.\n *\n * Both ScaleMode and RotateMode render the same bounding box envelope.\n * We keep scale's envelope and filter rotate's duplicate.\n * We also hide scale handles while rotating to avoid visual clutter.\n *\n * For rectangles, we hide vertex handles because vertex editing would distort\n * the shape or force axis-alignment. Rectangles should use scale handles only.\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n // Get guides from all modes (base class handles pick filtering)\n const allGuides = super.getGuides(props);\n\n // Check if we're editing a rectangle - rectangles have shape: 'Rectangle' property\n const isRectangle =\n props.data.features[0]?.properties?.shape === 'Rectangle';\n\n // biome-ignore lint/suspicious/noExplicitAny: Guide properties vary by mode, safely accessing with optional chaining\n const filteredGuides = allGuides.features.filter((guide: any) => {\n const properties = guide.properties || {};\n const editHandleType = properties.editHandleType;\n const guideType = properties.guideType;\n const mode = properties.mode;\n\n // Both scale and rotate modes have the same enveloping box as a guide - only need one\n const guidesToFilterOut: string[] = [mode as string];\n\n // Do not render scaling edit handles if rotating\n if (this.rotateMode.getIsRotating()) {\n guidesToFilterOut.push(editHandleType as string);\n }\n\n // For rectangles, hide ModifyMode vertex handles (editHandleType: 'existing')\n // Rectangles should only use scale handles for resizing to preserve rotation\n // Vertex editing would either distort the shape or force axis-alignment\n if (\n isRectangle &&\n guideType === 'editHandle' &&\n editHandleType === 'existing'\n ) {\n return false;\n }\n\n return !guidesToFilterOut.includes('scale');\n });\n\n // biome-ignore lint/suspicious/noExplicitAny: turf types mismatch with editable-layers GeoJSON types\n return featureCollection(filteredGuides as any) as any;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,YAAY;EACnC,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAI3C,QAAM;GAAC;GAAY;GAAW;GAAY;GAAc,CAAC;AAEzD,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO;GACL;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,WAC/C;IACH,MAAM,KAAK;IAEZ;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,eAAe;IAC1C;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,gBAAgB;IAC3C;GACF;;CAGH,AAAmB,iBAAkC;
|
|
1
|
+
{"version":3,"file":"vertex-transform-mode.js","names":["guidesToFilterOut: string[]"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n ModifyMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\n\n/**\n * Transform mode for shapes that support vertex editing (polygons and lines).\n *\n * Use this mode for shapes where individual vertices can be dragged to reshape\n * the geometry. This provides the most flexibility for freeform shape editing.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry\n * - **Translation** (TranslateMode): Drag the shape to move it\n * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize\n * - Default: Non-uniform scaling (can stretch/squish)\n * - With Shift: Uniform scaling (maintains aspect ratio)\n * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate\n * - Default: Free rotation\n * - With Shift: Snap to 45° intervals\n *\n * ## Handle Priority Logic\n * When drag starts, modes are evaluated in this priority order:\n * 1. If hovering over a vertex (edit handle) → vertex editing takes priority\n * 2. If hovering over a scale handle → scaling takes priority\n * 3. If hovering over the rotate handle → rotation takes priority\n * 4. Otherwise → dragging the shape translates it\n *\n * ## Visual Behavior\n * The guides from all modes are combined, showing both vertex handles (white circles\n * on existing points) and transform handles (corner/rotation handles on bounding box).\n *\n * ## Tooltips\n * This mode does not show live measurement tooltips during editing because arbitrary\n * polygons don't have well-defined dimensions. Use BoundingTransformMode for shapes\n * like rectangles and ellipses where dimension tooltips are useful.\n *\n * ## Rectangle Special Handling\n * For rectangles, vertex handles are hidden to preserve rotation and right angles.\n * Only scale/rotate/translate handles are shown. Consider using BoundingTransformMode\n * directly for rectangles if vertex editing should never be available.\n *\n * @example\n * ```typescript\n * import { VertexTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for polygons and lines\n * const mode = new VertexTransformMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: polygonFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * // ... other props\n * });\n * ```\n */\nexport class VertexTransformMode extends BaseTransformMode {\n private modifyMode: ModifyMode;\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const modifyMode = new ModifyMode();\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order matters: first mode to handle the event wins\n // We put modify first so vertex handles take priority over translate\n super([modifyMode, scaleMode, rotateMode, translateMode]);\n\n this.modifyMode = modifyMode;\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Vertex handle: existing point on polygon/line\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'existing',\n ),\n mode: this.modifyMode,\n // No shift config - vertex editing doesn't have modifiers\n },\n {\n // Scale handle: corner handles on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide && pick.object?.properties?.editHandleType === 'scale',\n ),\n mode: this.scaleMode,\n shiftConfig: { configKey: 'lockScaling' },\n },\n {\n // Rotate handle: top handle on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.editHandleType === 'rotate',\n ),\n mode: this.rotateMode,\n shiftConfig: { configKey: 'snapRotation' },\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n // 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 * Override getGuides to filter duplicate envelope guides and handle rectangles.\n *\n * Both ScaleMode and RotateMode render the same bounding box envelope.\n * We keep scale's envelope and filter rotate's duplicate.\n * We also hide scale handles while rotating to avoid visual clutter.\n *\n * For rectangles, we hide vertex handles because vertex editing would distort\n * the shape or force axis-alignment. Rectangles should use scale handles only.\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n // Get guides from all modes (base class handles pick filtering)\n const allGuides = super.getGuides(props);\n\n // Check if we're editing a rectangle - rectangles have shape: 'Rectangle' property\n const isRectangle =\n props.data.features[0]?.properties?.shape === 'Rectangle';\n\n // biome-ignore lint/suspicious/noExplicitAny: Guide properties vary by mode, safely accessing with optional chaining\n const filteredGuides = allGuides.features.filter((guide: any) => {\n const properties = guide.properties || {};\n const editHandleType = properties.editHandleType;\n const guideType = properties.guideType;\n const mode = properties.mode;\n\n // Both scale and rotate modes have the same enveloping box as a guide - only need one\n const guidesToFilterOut: string[] = [mode as string];\n\n // Do not render scaling edit handles if rotating\n if (this.rotateMode.getIsRotating()) {\n guidesToFilterOut.push(editHandleType as string);\n }\n\n // For rectangles, hide ModifyMode vertex handles (editHandleType: 'existing')\n // Rectangles should only use scale handles for resizing to preserve rotation\n // Vertex editing would either distort the shape or force axis-alignment\n if (\n isRectangle &&\n guideType === 'editHandle' &&\n editHandleType === 'existing'\n ) {\n return false;\n }\n\n return !guidesToFilterOut.includes('scale');\n });\n\n // biome-ignore lint/suspicious/noExplicitAny: turf types mismatch with editable-layers GeoJSON types\n return featureCollection(filteredGuides as any) as any;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,YAAY;EACnC,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAI3C,QAAM;GAAC;GAAY;GAAW;GAAY;GAAc,CAAC;AAEzD,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO;GACL;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,WAC/C;IACH,MAAM,KAAK;IAEZ;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,eAAe;IAC1C;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,gBAAgB;IAC3C;GACF;;CAGH,AAAmB,iBAAkC;AAEnD,SAAO,KAAK;;;;;;;;;;;;CAad,AAAS,UACP,OACwB;EAExB,MAAM,YAAY,MAAM,UAAU,MAAM;EAGxC,MAAM,cACJ,MAAM,KAAK,SAAS,IAAI,YAAY,UAAU;AAgChD,SAAO,kBA7BgB,UAAU,SAAS,QAAQ,UAAe;GAC/D,MAAM,aAAa,MAAM,cAAc,EAAE;GACzC,MAAM,iBAAiB,WAAW;GAClC,MAAM,YAAY,WAAW;GAI7B,MAAMA,oBAA8B,CAHvB,WAAW,KAG4B;AAGpD,OAAI,KAAK,WAAW,eAAe,CACjC,mBAAkB,KAAK,eAAyB;AAMlD,OACE,eACA,cAAc,gBACd,mBAAmB,WAEnB,QAAO;AAGT,UAAO,CAAC,kBAAkB,SAAS,QAAQ;IAC3C,CAG6C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
import { createMapStore } from "../../../shared/create-map-store.js";
|
|
17
17
|
import { MapEvents } from "../../base-map/events.js";
|
|
18
|
-
import {
|
|
18
|
+
import { createLoggerDomain } from "../../../shared/logger.js";
|
|
19
19
|
import { releaseModeAndCursor, requestCursorChange, requestModeChange } from "../shared/utils/mode-utils.js";
|
|
20
|
-
import {
|
|
20
|
+
import { isCircleShape, isEllipseShape, isPointShape, isRectangleShape } from "../shared/types.js";
|
|
21
21
|
import { EDIT_CURSOR_MAP, EDIT_SHAPE_LAYER_ID, EDIT_SHAPE_MODE } from "./constants.js";
|
|
22
|
+
import { EditShapeEvents } from "./events.js";
|
|
22
23
|
import { Broadcast } from "@accelint/bus";
|
|
23
|
-
import { getLogger } from "@accelint/logger";
|
|
24
24
|
|
|
25
25
|
//#region src/deckgl/shapes/edit-shape-layer/store.ts
|
|
26
26
|
/**
|
|
@@ -45,12 +45,7 @@ import { getLogger } from "@accelint/logger";
|
|
|
45
45
|
* }
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
|
-
const logger =
|
|
49
|
-
enabled: process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test",
|
|
50
|
-
level: "warn",
|
|
51
|
-
prefix: "[EditShapeLayer]",
|
|
52
|
-
pretty: true
|
|
53
|
-
});
|
|
48
|
+
const logger = createLoggerDomain("[EditShapeLayer]");
|
|
54
49
|
/**
|
|
55
50
|
* Typed event bus instances
|
|
56
51
|
*/
|
|
@@ -62,7 +57,8 @@ const mapEventBus = Broadcast.getInstance();
|
|
|
62
57
|
const DEFAULT_EDITING_STATE = {
|
|
63
58
|
editingShape: null,
|
|
64
59
|
editMode: "view",
|
|
65
|
-
featureBeingEdited: null
|
|
60
|
+
featureBeingEdited: null,
|
|
61
|
+
previousMode: null
|
|
66
62
|
};
|
|
67
63
|
/**
|
|
68
64
|
* Determine the appropriate edit mode for a shape type
|
|
@@ -122,7 +118,8 @@ function saveEditingInternal(mapId, state, notify, setState) {
|
|
|
122
118
|
setState({
|
|
123
119
|
editingShape: null,
|
|
124
120
|
editMode: "view",
|
|
125
|
-
featureBeingEdited: null
|
|
121
|
+
featureBeingEdited: null,
|
|
122
|
+
previousMode: null
|
|
126
123
|
});
|
|
127
124
|
releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);
|
|
128
125
|
mapEventBus.emit(MapEvents.enablePan, { id: mapId });
|
|
@@ -142,7 +139,8 @@ function cancelEditingInternal(mapId, state, notify, setState) {
|
|
|
142
139
|
setState({
|
|
143
140
|
editingShape: null,
|
|
144
141
|
editMode: "view",
|
|
145
|
-
featureBeingEdited: null
|
|
142
|
+
featureBeingEdited: null,
|
|
143
|
+
previousMode: null
|
|
146
144
|
});
|
|
147
145
|
releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);
|
|
148
146
|
mapEventBus.emit(MapEvents.enablePan, { id: mapId });
|
|
@@ -180,24 +178,90 @@ const editStore = createMapStore({
|
|
|
180
178
|
}
|
|
181
179
|
});
|
|
182
180
|
/**
|
|
181
|
+
* Manually clear editing state for a specific mapId.
|
|
182
|
+
* @param mapId - The map instance ID.
|
|
183
|
+
*/
|
|
184
|
+
function clearEditingState(mapId) {
|
|
185
|
+
editStore.clear(mapId);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
183
188
|
* Update feature from the layer component (called during drag operations)
|
|
189
|
+
* @param mapId - The map instance ID.
|
|
190
|
+
* @param feature - The updated GeoJSON feature from the editable layer.
|
|
184
191
|
*/
|
|
185
192
|
function updateFeatureFromLayer(mapId, feature) {
|
|
186
193
|
editStore.set(mapId, { featureBeingEdited: feature });
|
|
187
194
|
}
|
|
188
195
|
/**
|
|
189
196
|
* Cancel editing (called by the layer component on ESC)
|
|
197
|
+
* @param mapId - The map instance ID.
|
|
190
198
|
*/
|
|
191
199
|
function cancelEditingFromLayer(mapId) {
|
|
192
200
|
editStore.actions(mapId).cancel();
|
|
193
201
|
}
|
|
194
202
|
/**
|
|
195
203
|
* Save editing (called by the layer component on Enter)
|
|
204
|
+
* @param mapId - The map instance ID.
|
|
196
205
|
*/
|
|
197
206
|
function saveEditingFromLayer(mapId) {
|
|
198
207
|
editStore.actions(mapId).save();
|
|
199
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Enables map panning while editing by switching to view mode and storing the
|
|
211
|
+
* current edit mode for restoration.
|
|
212
|
+
*
|
|
213
|
+
* No-op if no shape is currently being edited.
|
|
214
|
+
*
|
|
215
|
+
* @param mapId - The map instance ID.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* enableEditPanning(mapId);
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
function enableEditPanning(mapId) {
|
|
223
|
+
const state = editStore.get(mapId);
|
|
224
|
+
if (state?.previousMode !== null || !state?.editingShape) return;
|
|
225
|
+
editStore.set(mapId, {
|
|
226
|
+
previousMode: state.editMode,
|
|
227
|
+
editMode: "view"
|
|
228
|
+
});
|
|
229
|
+
mapEventBus.emit(MapEvents.enablePan, { id: mapId });
|
|
230
|
+
requestCursorChange(mapId, "grab", EDIT_SHAPE_LAYER_ID);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Disables map panning and restores the previous edit mode.
|
|
234
|
+
*
|
|
235
|
+
* If no shape is being edited, clears the stored `previousMode` and returns.
|
|
236
|
+
* Otherwise, restores the edit mode from `previousMode`, re-disables panning,
|
|
237
|
+
* and sets the cursor back to the shape-appropriate edit cursor.
|
|
238
|
+
*
|
|
239
|
+
* @param mapId - The map instance ID.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* disableEditPanning(mapId);
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function disableEditPanning(mapId) {
|
|
247
|
+
const state = editStore.get(mapId);
|
|
248
|
+
if (!state?.editingShape) {
|
|
249
|
+
editStore.set(mapId, { previousMode: null });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const previousMode = state?.previousMode;
|
|
253
|
+
if (!previousMode) {
|
|
254
|
+
editStore.set(mapId, { previousMode: null });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
editStore.set(mapId, {
|
|
258
|
+
editMode: previousMode,
|
|
259
|
+
previousMode: null
|
|
260
|
+
});
|
|
261
|
+
mapEventBus.emit(MapEvents.disablePan, { id: mapId });
|
|
262
|
+
requestCursorChange(mapId, EDIT_CURSOR_MAP[previousMode], EDIT_SHAPE_LAYER_ID);
|
|
263
|
+
}
|
|
200
264
|
|
|
201
265
|
//#endregion
|
|
202
|
-
export { cancelEditingFromLayer, editStore, saveEditingFromLayer, updateFeatureFromLayer };
|
|
266
|
+
export { cancelEditingFromLayer, clearEditingState, disableEditPanning, editStore, enableEditPanning, saveEditingFromLayer, updateFeatureFromLayer };
|
|
203
267
|
//# sourceMappingURL=store.js.map
|