@accelint/map-toolkit 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/catalog-info.yaml +4 -4
  3. package/dist/camera/events.js.map +1 -1
  4. package/dist/camera/store.js.map +1 -1
  5. package/dist/cursor-coordinates/index.d.ts +4 -2
  6. package/dist/cursor-coordinates/index.js +3 -2
  7. package/dist/cursor-coordinates/store.d.ts +48 -0
  8. package/dist/cursor-coordinates/store.js +92 -0
  9. package/dist/cursor-coordinates/store.js.map +1 -0
  10. package/dist/cursor-coordinates/types.d.ts +87 -0
  11. package/dist/cursor-coordinates/types.js +12 -0
  12. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +41 -37
  13. package/dist/cursor-coordinates/use-cursor-coordinates.js +131 -202
  14. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  15. package/dist/deckgl/base-map/controls.d.ts +1 -1
  16. package/dist/deckgl/base-map/controls.js.map +1 -1
  17. package/dist/deckgl/base-map/events.js.map +1 -1
  18. package/dist/deckgl/base-map/index.d.ts +2 -2
  19. package/dist/deckgl/base-map/index.js +3 -3
  20. package/dist/deckgl/base-map/provider.d.ts +2 -2
  21. package/dist/deckgl/base-map/provider.js.map +1 -1
  22. package/dist/deckgl/index.js +1 -1
  23. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  24. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  25. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  26. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
  27. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  28. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js.map +1 -1
  29. package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
  30. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js.map +1 -1
  31. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  32. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  33. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -1
  34. package/dist/deckgl/shapes/draw-shape-layer/events.js.map +1 -1
  35. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -1
  36. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
  37. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  38. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
  39. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
  40. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js.map +1 -1
  41. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
  42. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
  43. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -1
  44. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  45. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
  46. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  47. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  48. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
  49. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
  50. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
  51. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
  52. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  53. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -1
  54. package/dist/deckgl/shapes/index.d.ts +1 -1
  55. package/dist/deckgl/shapes/shared/constants.js.map +1 -1
  56. package/dist/deckgl/shapes/shared/events.js.map +1 -1
  57. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
  58. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  59. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  60. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
  61. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
  62. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
  63. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
  64. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  65. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  66. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  67. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  68. package/dist/deckgl/text-layer/fiber.js.map +1 -1
  69. package/dist/deckgl/text-layer/index.js.map +1 -1
  70. package/dist/deckgl/text-settings.js.map +1 -1
  71. package/dist/map-cursor/events.js.map +1 -1
  72. package/dist/map-cursor/store.js.map +1 -1
  73. package/dist/map-cursor/use-map-cursor.js.map +1 -1
  74. package/dist/map-mode/events.js.map +1 -1
  75. package/dist/map-mode/store.js.map +1 -1
  76. package/dist/map-mode/use-map-mode.js.map +1 -1
  77. package/dist/maplibre/hooks/use-maplibre.js.map +1 -1
  78. package/dist/shared/constants.js.map +1 -1
  79. package/dist/shared/create-map-store.js.map +1 -1
  80. package/dist/shared/units.js.map +1 -1
  81. package/dist/viewport/store.js.map +1 -1
  82. package/dist/viewport/utils.js.map +1 -1
  83. package/dist/viewport/viewport-size.js.map +1 -1
  84. package/package.json +12 -10
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","names":[],"sources":["../../../src/deckgl/saved-viewports/storage.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { getLogger } from '@accelint/logger';\nimport type { MapViewState } from '@deck.gl/core';\n\nconst logger = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[SavedViewports]',\n pretty: true,\n});\n\nexport const STORAGE_ID = 'deckgl-saved-viewports';\n\nconst getContainerKey = (uniqueIdentifier?: string) =>\n uniqueIdentifier ? `${STORAGE_ID}-${uniqueIdentifier}` : STORAGE_ID;\n\nconst getContainer = (containerKey: string) => {\n try {\n return JSON.parse(localStorage.getItem(containerKey) ?? '{}');\n } catch {\n logger.warn(\n `Failed to parse storage container for key: ${containerKey}, returning empty container.`,\n );\n return {};\n }\n};\n\nexport const persist = (\n id: string,\n obj: MapViewState,\n uniqueIdentifier?: string,\n) => {\n const containerKey = getContainerKey(uniqueIdentifier);\n const container = getContainer(containerKey);\n container[id] = obj;\n localStorage.setItem(containerKey, JSON.stringify(container));\n};\n\nexport const retrieve = (id: string, uniqueIdentifier?: string) => {\n const containerKey = getContainerKey(uniqueIdentifier);\n const container = getContainer(containerKey);\n const obj = container[id];\n if (!obj) {\n logger.warn(`Object with id: ${id} does not exist`);\n }\n return obj;\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,MAAM,SAAS,UAAU;CACvB,SACE,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,aAAa;CACpE,OAAO;CACP,QAAQ;CACR,QAAQ;CACT,CAAC;AAEF,MAAa,aAAa;AAE1B,MAAM,mBAAmB,qBACvB,mBAAmB,GAAG,WAAW,GAAG,qBAAqB;AAE3D,MAAM,gBAAgB,iBAAyB;AAC7C,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,QAAQ,aAAa,IAAI,KAAK;SACvD;AACN,SAAO,KACL,8CAA8C,aAAa,8BAC5D;AACD,SAAO,EAAE;;;AAIb,MAAa,WACX,IACA,KACA,qBACG;CACH,MAAM,eAAe,gBAAgB,iBAAiB;CACtD,MAAM,YAAY,aAAa,aAAa;AAC5C,WAAU,MAAM;AAChB,cAAa,QAAQ,cAAc,KAAK,UAAU,UAAU,CAAC;;AAG/D,MAAa,YAAY,IAAY,qBAA8B;CAGjE,MAAM,MADY,aADG,gBAAgB,iBAAiB,CACV,CACtB;AACtB,KAAI,CAAC,IACH,QAAO,KAAK,mBAAmB,GAAG,iBAAiB;AAErD,QAAO"}
1
+ {"version":3,"file":"storage.js","names":[],"sources":["../../../src/deckgl/saved-viewports/storage.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 { getLogger } from '@accelint/logger';\nimport type { MapViewState } from '@deck.gl/core';\n\nconst logger = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[SavedViewports]',\n pretty: true,\n});\n\nexport const STORAGE_ID = 'deckgl-saved-viewports';\n\nconst getContainerKey = (uniqueIdentifier?: string) =>\n uniqueIdentifier ? `${STORAGE_ID}-${uniqueIdentifier}` : STORAGE_ID;\n\nconst getContainer = (containerKey: string) => {\n try {\n return JSON.parse(localStorage.getItem(containerKey) ?? '{}');\n } catch {\n logger.warn(\n `Failed to parse storage container for key: ${containerKey}, returning empty container.`,\n );\n return {};\n }\n};\n\nexport const persist = (\n id: string,\n obj: MapViewState,\n uniqueIdentifier?: string,\n) => {\n const containerKey = getContainerKey(uniqueIdentifier);\n const container = getContainer(containerKey);\n container[id] = obj;\n localStorage.setItem(containerKey, JSON.stringify(container));\n};\n\nexport const retrieve = (id: string, uniqueIdentifier?: string) => {\n const containerKey = getContainerKey(uniqueIdentifier);\n const container = getContainer(containerKey);\n const obj = container[id];\n if (!obj) {\n logger.warn(`Object with id: ${id} does not exist`);\n }\n return obj;\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,MAAM,SAAS,UAAU;CACvB,SACE,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,aAAa;CACpE,OAAO;CACP,QAAQ;CACR,QAAQ;CACT,CAAC;AAEF,MAAa,aAAa;AAE1B,MAAM,mBAAmB,qBACvB,mBAAmB,GAAG,WAAW,GAAG,qBAAqB;AAE3D,MAAM,gBAAgB,iBAAyB;AAC7C,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,QAAQ,aAAa,IAAI,KAAK;SACvD;AACN,SAAO,KACL,8CAA8C,aAAa,8BAC5D;AACD,SAAO,EAAE;;;AAIb,MAAa,WACX,IACA,KACA,qBACG;CACH,MAAM,eAAe,gBAAgB,iBAAiB;CACtD,MAAM,YAAY,aAAa,aAAa;AAC5C,WAAU,MAAM;AAChB,cAAa,QAAQ,cAAc,KAAK,UAAU,UAAU,CAAC;;AAG/D,MAAa,YAAY,IAAY,qBAA8B;CAGjE,MAAM,MADY,aADG,gBAAgB,iBAAiB,CACV,CACtB;AACtB,KAAI,CAAC,IACH,QAAO,KAAK,mBAAmB,GAAG,iBAAiB;AAErD,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/constants.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { DEFAULT_COLORS } from '../shared/constants';\n\n/**\n * Map interaction constants\n * Values derived from ngc2 for consistency\n */\nexport const MAP_INTERACTION = {\n LINE_WIDTH_MIN_PIXELS: 1, // Minimum line width in pixels\n ICON_SIZE: 38, // Size of shape icons\n ICON_HOVER_SIZE_INCREASE: 5, // Additional pixels added on hover\n} as const;\n\n/**\n * Selection highlight configuration\n */\nexport const SELECTION_HIGHLIGHT = {\n /** Uses DEFAULT_COLORS.highlight from shared constants */\n COLOR: DEFAULT_COLORS.highlight,\n ICON_SIZE_INCREASE: 8, // Additional pixels for highlight icon\n} as const;\n\n/**\n * Coffin corners configuration for Point selection/hover feedback\n * Coffin corners are bracket-like corners that appear around points\n */\nexport const COFFIN_CORNERS = {\n /** Icon name for hover state (white corners with background fill) */\n HOVER_ICON: 'coffin-corners-hover',\n /** Icon name for selected state (blue corners, no fill) */\n SELECTED_ICON: 'coffin-corners-selected',\n /** Icon name for selected+hover state (blue corners with background fill) */\n SELECTED_HOVER_ICON: 'coffin-corners-selected-hover',\n /** Size of the coffin corners icon */\n SIZE: 38,\n} as const;\n\n/**\n * Default props for DisplayShapeLayer\n */\nexport const DEFAULT_DISPLAY_PROPS = {\n pickable: true,\n showLabels: 'always' as const,\n showHighlight: false,\n applyBaseOpacity: true,\n highlightColor: SELECTION_HIGHLIGHT.COLOR,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB;CAC7B,uBAAuB;CACvB,WAAW;CACX,0BAA0B;CAC3B;;;;AAKD,MAAa,sBAAsB;CAEjC,OAAO,eAAe;CACtB,oBAAoB;CACrB;;;;;AAMD,MAAa,iBAAiB;CAE5B,YAAY;CAEZ,eAAe;CAEf,qBAAqB;CAErB,MAAM;CACP;;;;AAKD,MAAa,wBAAwB;CACnC,UAAU;CACV,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,gBAAgB,oBAAoB;CACrC"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/constants.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\n'use client';\n\nimport { DEFAULT_COLORS } from '../shared/constants';\n\n/**\n * Map interaction constants\n * Values derived from ngc2 for consistency\n */\nexport const MAP_INTERACTION = {\n LINE_WIDTH_MIN_PIXELS: 1, // Minimum line width in pixels\n ICON_SIZE: 38, // Size of shape icons\n ICON_HOVER_SIZE_INCREASE: 5, // Additional pixels added on hover\n} as const;\n\n/**\n * Selection highlight configuration\n */\nexport const SELECTION_HIGHLIGHT = {\n /** Uses DEFAULT_COLORS.highlight from shared constants */\n COLOR: DEFAULT_COLORS.highlight,\n ICON_SIZE_INCREASE: 8, // Additional pixels for highlight icon\n} as const;\n\n/**\n * Coffin corners configuration for Point selection/hover feedback\n * Coffin corners are bracket-like corners that appear around points\n */\nexport const COFFIN_CORNERS = {\n /** Icon name for hover state (white corners with background fill) */\n HOVER_ICON: 'coffin-corners-hover',\n /** Icon name for selected state (blue corners, no fill) */\n SELECTED_ICON: 'coffin-corners-selected',\n /** Icon name for selected+hover state (blue corners with background fill) */\n SELECTED_HOVER_ICON: 'coffin-corners-selected-hover',\n /** Size of the coffin corners icon */\n SIZE: 38,\n} as const;\n\n/**\n * Default props for DisplayShapeLayer\n */\nexport const DEFAULT_DISPLAY_PROPS = {\n pickable: true,\n showLabels: 'always' as const,\n showHighlight: false,\n applyBaseOpacity: true,\n highlightColor: SELECTION_HIGHLIGHT.COLOR,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB;CAC7B,uBAAuB;CACvB,WAAW;CACX,0BAA0B;CAC3B;;;;AAKD,MAAa,sBAAsB;CAEjC,OAAO,eAAe;CACtB,oBAAoB;CACrB;;;;;AAMD,MAAa,iBAAiB;CAE5B,YAAY;CAEZ,eAAe;CAEf,qBAAqB;CAErB,MAAM;CACP;;;;AAKD,MAAa,wBAAwB;CACnC,UAAU;CACV,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,gBAAgB,oBAAoB;CACrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"fiber.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/fiber.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { extend } from '@deckgl-fiber-renderer/dom';\nimport { DisplayShapeLayer } from './index';\nimport type { DisplayShapeLayerProps } from './types';\n\nextend({ DisplayShapeLayer });\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n displayShapeLayer: DisplayShapeLayerProps;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,OAAO,EAAE,mBAAmB,CAAC"}
1
+ {"version":3,"file":"fiber.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/fiber.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 { extend } from '@deckgl-fiber-renderer/dom';\nimport { DisplayShapeLayer } from './index';\nimport type { DisplayShapeLayerProps } from './types';\n\nextend({ DisplayShapeLayer });\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n displayShapeLayer: DisplayShapeLayerProps;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,OAAO,EAAE,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["features: Shape['feature'][]"],"sources":["../../../../src/deckgl/shapes/display-shape-layer/index.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { Broadcast } from '@accelint/bus';\nimport { getLogger } from '@accelint/logger';\nimport { CompositeLayer } from '@deck.gl/core';\nimport { PathStyleExtension } from '@deck.gl/extensions';\nimport { GeoJsonLayer, IconLayer } from '@deck.gl/layers';\nimport { DASH_ARRAYS, SHAPE_LAYER_IDS } from '../shared/constants';\nimport { type ShapeEvent, ShapeEvents } from '../shared/events';\nimport {\n getDashArray,\n getFillColor,\n getLineColor,\n} from '../shared/utils/style-utils';\nimport {\n COFFIN_CORNERS,\n DEFAULT_DISPLAY_PROPS,\n MAP_INTERACTION,\n} from './constants';\nimport { createShapeLabelLayer } from './shape-label-layer';\nimport {\n getHighlightColor,\n getHighlightLineWidth,\n getHoverLineWidth,\n} from './utils/display-style';\nimport type { Layer, PickingInfo } from '@deck.gl/core';\nimport type { Shape, ShapeId } from '../shared/types';\nimport type { DisplayShapeLayerProps } from './types';\n\nconst logger = getLogger({\n enabled: process.env.NODE_ENV !== 'production',\n level: 'warn',\n prefix: '[DisplayShapeLayer]',\n pretty: true,\n});\n\n/**\n * Typed event bus instance for shape events.\n * Provides type-safe event emission for shape interactions.\n */\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\n\n/**\n * State type for DisplayShapeLayer\n */\ntype DisplayShapeLayerState = {\n /** Index of currently hovered shape, undefined when not hovering */\n hoverIndex?: number;\n /** ID of the last hovered shape for event deduplication */\n lastHoveredId?: ShapeId | null;\n /** Allow additional properties from base layer state */\n [key: string]: unknown;\n};\n\n/**\n * Cache for transformed features to avoid recreating objects on every render.\n */\ntype FeaturesCache = {\n /** Reference to the original data array for identity comparison */\n data: Shape[];\n /** Transformed features with shapeId added to properties */\n features: Shape['feature'][];\n /** Map of shapeId to feature index for O(1) lookup */\n shapeIdToIndex: Map<ShapeId, number>;\n};\n\n/**\n * DisplayShapeLayer - Read-only shapes visualization layer\n *\n * A composite deck.gl layer for displaying geographic shapes with interactive features.\n * Ideal for rendering shapes from external APIs or displaying read-only geographic data.\n *\n * ## Features\n * - **Multiple geometry types**: Point, LineString, Polygon, and Circle\n * - **Icon support**: Custom icons for Point geometries via icon atlases\n * - **Interactive selection**: Click handling with dotted border and optional highlight\n * - **Hover effects**: Border/outline width increases on hover for better UX\n * - **Customizable labels**: Flexible label positioning with per-shape or global options\n * - **Style properties**: Full control over colors, border/outline patterns, and opacity\n * - **Event bus integration**: Automatically emits shape events via @accelint/bus\n * - **Multi-map support**: Events include map instance ID for isolation\n *\n * ## Selection Visual Feedback\n * When a shape is selected via `selectedShapeId`:\n * - The shape's border/outline pattern changes to dotted\n * - An optional highlight renders underneath (controlled by `showHighlight` prop)\n *\n * ## Layer Structure\n * Renders up to four sublayers (in order, bottom to top):\n * 1. **Highlight layer**: Selection highlight effect for non-icon-Point shapes (if showHighlight=true)\n * 2. **Coffin corners layer**: Selection/hover feedback for Point shapes with icons\n * 3. **Main GeoJsonLayer**: Shape geometries with styling and interaction\n * 4. **Label layer**: Text labels (if showLabels enabled)\n *\n * ## Icon Atlas Constraint\n * When using icons for Point geometries, all shapes in a single layer must share the\n * same icon atlas. The layer uses the first atlas found across all features. If you\n * need icons from different atlases, use separate DisplayShapeLayer instances.\n *\n * ## Event Bus Integration\n * Automatically emits shape events that can be consumed anywhere in your app:\n * - `shapes:selected` - Emitted when a shape is clicked (includes mapId)\n * - `shapes:hovered` - Emitted when the hovered shape changes (deduplicated, includes mapId)\n *\n * For selection with auto-deselection, use the companion `useSelectShape` hook which handles\n * all the event wiring automatically. See the example below.\n *\n * @example Basic usage with useSelectShape hook (recommended)\n * ```tsx\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n * import { useSelectShape } from '@accelint/map-toolkit/deckgl/shapes';\n * import { uuid } from '@accelint/core';\n *\n * const MAP_ID = uuid();\n *\n * function MapWithShapes() {\n * const { selectedId } = useSelectShape(MAP_ID);\n *\n * return (\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"my-shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * showLabels=\"always\"\n * pickable={true}\n * />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example With custom label positioning\n * ```tsx\n * <displayShapeLayer\n * id=\"my-shapes\"\n * data={shapes}\n * showLabels=\"always\"\n * labelOptions={{\n * // Position circle labels at the top\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom',\n * circleLabelOffset: [0, -10],\n * // Position line labels at the middle\n * lineStringLabelCoordinateAnchor: 'middle',\n * }}\n * />\n * ```\n */\nexport class DisplayShapeLayer extends CompositeLayer<DisplayShapeLayerProps> {\n // State is typed via DisplayShapeLayerState but deck.gl doesn't support generic state\n declare state: DisplayShapeLayerState;\n\n /** Cache for transformed features to avoid recreating objects on every render */\n private featuresCache: FeaturesCache | null = null;\n\n static override layerName = 'DisplayShapeLayer';\n\n static override defaultProps = {\n ...DEFAULT_DISPLAY_PROPS,\n };\n\n /**\n * Clean up state and caches when layer is destroyed\n */\n override finalizeState(): void {\n // Clear hover state to prevent stale references\n if (this.state?.hoverIndex !== undefined) {\n this.setState({ hoverIndex: undefined, lastHoveredId: undefined });\n }\n // Clear features cache\n this.featuresCache = null;\n }\n\n /**\n * Override getPickingInfo to handle events from sublayers\n * This is the correct pattern for CompositeLayer event handling\n */\n override getPickingInfo({\n info,\n mode,\n sourceLayer,\n }: {\n info: PickingInfo;\n mode?: string;\n // biome-ignore lint/suspicious/noExplicitAny: sourceLayer type from deck.gl is not well-typed\n sourceLayer?: any;\n }) {\n // Check if this picking event came from our main shapes layer\n if (sourceLayer?.id === `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`) {\n // Handle click events (deck.gl uses 'query' mode for clicks)\n if (mode === 'query') {\n this.handleShapeClick(info);\n }\n\n // Handle hover events (including when mode is undefined, which is hover)\n if (mode === 'hover' || !mode) {\n // Update hover state\n if (info.index !== undefined && info.index !== this.state?.hoverIndex) {\n this.setState({ hoverIndex: info.index });\n } else if (\n info.index === undefined &&\n this.state?.hoverIndex !== undefined\n ) {\n this.setState({ hoverIndex: undefined });\n }\n\n // Call hover callback\n this.handleShapeHover(info);\n }\n }\n\n return info;\n }\n\n /**\n * Convert shapes to GeoJSON features with shapeId in properties.\n * Uses caching to avoid recreating objects on every render cycle.\n */\n private getFeaturesWithId(): Shape['feature'][] {\n const { data } = this.props;\n\n // Return cached features if data hasn't changed (identity check)\n if (this.featuresCache?.data === data) {\n return this.featuresCache.features;\n }\n\n // Transform features and build shapeId->index map in a single pass\n const features: Shape['feature'][] = [];\n const shapeIdToIndex = new Map<ShapeId, number>();\n\n for (const [i, shape] of data.entries()) {\n features.push({\n ...shape.feature,\n properties: {\n ...shape.feature.properties,\n shapeId: shape.id,\n },\n });\n shapeIdToIndex.set(shape.id, i);\n }\n\n this.featuresCache = { data, features, shapeIdToIndex };\n return features;\n }\n\n /**\n * Look up a shape by ID from the data prop.\n * Used by event handlers to get full shape without storing in feature properties.\n */\n private getShapeById(shapeId: ShapeId): Shape | undefined {\n return this.props.data.find((shape) => shape.id === shapeId);\n }\n\n /**\n * Handle shape click\n */\n private handleShapeClick = (info: PickingInfo): void => {\n const { onShapeClick, mapId } = this.props;\n\n if (!info.object) {\n return;\n }\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId = info.object.properties?.shapeId as ShapeId | undefined;\n if (!shapeId) {\n return;\n }\n\n const shape = this.getShapeById(shapeId);\n if (!shape) {\n return;\n }\n\n // Emit shape selected event via bus (include mapId for multi-map isolation)\n shapeBus.emit(ShapeEvents.selected, { shapeId: shape.id, mapId });\n\n // Call callback if provided\n if (onShapeClick) {\n onShapeClick(shape);\n }\n };\n\n /**\n * Handle shape hover\n */\n private handleShapeHover = (info: PickingInfo): void => {\n const { onShapeHover, mapId } = this.props;\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId =\n (info.object?.properties?.shapeId as ShapeId | undefined) ?? null;\n const shape = shapeId ? (this.getShapeById(shapeId) ?? null) : null;\n\n // Dedupe hover events - only emit if hovered shape changed\n if (shapeId !== this.state?.lastHoveredId) {\n this.setState({ lastHoveredId: shapeId });\n\n // Emit shape hovered event via bus (include mapId for multi-map isolation)\n shapeBus.emit(ShapeEvents.hovered, {\n shapeId,\n mapId,\n });\n }\n\n // Always call callback if provided (for local state updates)\n if (onShapeHover) {\n onShapeHover(shape);\n }\n };\n\n /**\n * Render highlight sublayer (underneath main layer)\n * Note: Points with icons use coffin corners instead of highlight layer\n */\n private renderHighlightLayer(\n features: Shape['feature'][],\n ): GeoJsonLayer | null {\n const { selectedShapeId, showHighlight, highlightColor } = this.props;\n\n if (!selectedShapeId || showHighlight === false) {\n return null;\n }\n\n const selectedFeature = features.find(\n (f) => f.properties?.shapeId === selectedShapeId,\n );\n\n if (!selectedFeature) {\n return null;\n }\n\n // Skip highlight layer for Point geometries with icons - they use coffin corners instead\n // Points without icons should still show the highlight layer\n if (selectedFeature.geometry.type === 'Point') {\n const hasIcon = !!selectedFeature.properties?.styleProperties?.icon;\n if (hasIcon) {\n return null;\n }\n }\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_HIGHLIGHT}`,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accepts various feature formats\n data: [selectedFeature] as any,\n\n // Styling\n filled: true,\n stroked: true,\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n getFillColor: () => [0, 0, 0, 0], // Transparent fill\n getLineColor: () => highlightColor || getHighlightColor(),\n getLineWidth: getHighlightLineWidth,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n getLineColor: [highlightColor],\n getLineWidth: [selectedShapeId, features],\n },\n });\n }\n\n /**\n * Render coffin corners layer for Point geometries that have icons on hover/select\n * Coffin corners provide visual feedback for points instead of highlight layer\n */\n private renderCoffinCornersLayer(\n features: Shape['feature'][],\n ): IconLayer | null {\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Use cached shapeId->index map for O(1) lookup\n const shapeIdToIndex = this.featuresCache?.shapeIdToIndex;\n if (!shapeIdToIndex) {\n return null;\n }\n\n // Find point features that need coffin corners (hovered or selected)\n const pointFeatures = features.filter((f) => {\n if (f.geometry.type !== 'Point') {\n return false;\n }\n const hasIcon = !!f.properties?.styleProperties?.icon;\n if (!hasIcon) {\n return false;\n }\n\n const shapeId = f.properties?.shapeId;\n const isSelected = shapeId === selectedShapeId;\n const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : undefined;\n const isHovered = hoverIndex !== undefined && featureIndex === hoverIndex;\n\n return isSelected || isHovered;\n });\n\n if (pointFeatures.length === 0) {\n return null;\n }\n\n // Get icon atlas from first point feature (all should share the same atlas)\n const firstPointIcon = pointFeatures[0]?.properties?.styleProperties?.icon;\n const iconAtlas = firstPointIcon?.atlas;\n const iconMapping = firstPointIcon?.mapping;\n\n if (!(iconAtlas && iconMapping)) {\n logger.warn(\n 'Point shape has icon style but missing iconAtlas or iconMapping - coffin corners will not render',\n );\n return null;\n }\n\n // Add coffin corners icons to the mapping\n const extendedMapping = {\n ...iconMapping,\n [COFFIN_CORNERS.HOVER_ICON]: {\n x: 0,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_ICON]: {\n x: 76,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_HOVER_ICON]: {\n x: 152,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n };\n\n return new IconLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-coffin-corners`,\n data: pointFeatures,\n iconAtlas,\n iconMapping: extendedMapping,\n getIcon: (d: Shape['feature']) => {\n const shapeId = d.properties?.shapeId;\n const isSelected = shapeId === selectedShapeId;\n const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : undefined;\n const isHovered =\n hoverIndex !== undefined && featureIndex === hoverIndex;\n\n if (isSelected && isHovered) {\n return COFFIN_CORNERS.SELECTED_HOVER_ICON;\n }\n if (isSelected) {\n return COFFIN_CORNERS.SELECTED_ICON;\n }\n return COFFIN_CORNERS.HOVER_ICON;\n },\n getSize: COFFIN_CORNERS.SIZE,\n getPosition: (d: Shape['feature']) => {\n const coords =\n d.geometry.type === 'Point' ? d.geometry.coordinates : [0, 0];\n return coords as [number, number];\n },\n getPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE;\n // Center the coffin corners on the point icon\n return [-1, -iconSize / 2];\n },\n billboard: false,\n pickable: false,\n updateTriggers: {\n getIcon: [selectedShapeId, this.state?.hoverIndex],\n data: [features, selectedShapeId, this.state?.hoverIndex],\n },\n });\n }\n\n /**\n * Extract icon configuration from features in a single pass.\n * Returns the first icon's atlas and mapping (all shapes share the same atlas).\n * Uses early return for O(1) best case when first feature has icons.\n */\n private getIconConfig(features: Shape['feature'][]): {\n hasIcons: boolean;\n atlas?: string;\n mapping?: Record<\n string,\n { x: number; y: number; width: number; height: number; mask?: boolean }\n >;\n } {\n for (const f of features) {\n const icon = f.properties?.styleProperties?.icon;\n if (icon) {\n return {\n hasIcons: true,\n atlas: icon.atlas,\n mapping: icon.mapping,\n };\n }\n }\n return { hasIcons: false };\n }\n\n /**\n * Render main shapes layer\n */\n private renderMainLayer(features: Shape['feature'][]): GeoJsonLayer {\n const { pickable, applyBaseOpacity, selectedShapeId } = this.props;\n\n // Single-pass icon config extraction (O(1) best case with early return)\n const {\n hasIcons,\n atlas: iconAtlas,\n mapping: iconMapping,\n } = this.getIconConfig(features);\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accepts various feature formats\n data: features as any,\n\n // Styling\n filled: true,\n stroked: true,\n getFillColor: (d: Shape['feature']) => getFillColor(d, applyBaseOpacity),\n getLineColor,\n getLineWidth: (d, info) => {\n const isHovered = info?.index === this.state?.hoverIndex;\n return getHoverLineWidth(d, isHovered);\n },\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n lineWidthMaxPixels: 20,\n\n // Points - use icons if any feature has icon config, otherwise circles\n pointType: hasIcons ? 'icon' : 'circle',\n getPointRadius: (d) => {\n const iconSize = d.properties?.styleProperties?.icon?.size;\n return iconSize ?? 2;\n },\n pointRadiusUnits: 'pixels',\n\n // Icon configuration (only used if pointType includes 'icon')\n ...(hasIcons && iconAtlas ? { iconAtlas } : {}),\n ...(hasIcons && iconMapping ? { iconMapping } : {}),\n ...(hasIcons\n ? {\n getIcon: (d: Shape['feature']) =>\n d.properties?.styleProperties?.icon?.name ?? 'marker',\n getIconSize: (d: Shape['feature']) => {\n return (\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE\n );\n },\n getIconColor: getLineColor,\n getIconPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE;\n return [-1, -iconSize / 2];\n },\n iconBillboard: false,\n }\n : {}),\n\n // Dash pattern support - selected shapes get dotted border\n extensions: [new PathStyleExtension({ dash: true })],\n getDashArray: (d: Shape['feature']) => {\n const isSelected = d.properties?.shapeId === selectedShapeId;\n if (isSelected) {\n return DASH_ARRAYS.dotted;\n }\n return getDashArray(d);\n },\n\n // Behavior\n pickable,\n autoHighlight: false, // We handle highlighting manually\n // Note: onClick and onHover are handled via getPickingInfo() override\n\n // Update triggers\n updateTriggers: {\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features],\n getLineWidth: [features, this.state?.hoverIndex],\n getDashArray: [features, selectedShapeId],\n getPointRadius: [features],\n ...(hasIcons\n ? {\n getIcon: [features],\n getIconSize: [features],\n getIconColor: [features],\n getIconPixelOffset: [features],\n }\n : {}),\n },\n });\n }\n\n /**\n * Render labels layer\n * Supports three modes:\n * - 'always': Show labels for all shapes\n * - 'hover': Show label only for the currently hovered shape\n * - 'never': No labels\n */\n private renderLabelsLayer(): ReturnType<typeof createShapeLabelLayer> | null {\n const { showLabels, data, labelOptions } = this.props;\n\n // No labels if disabled\n if (showLabels === 'never') {\n return null;\n }\n\n // Determine which shapes to show labels for\n let labelData = data;\n if (showLabels === 'hover') {\n const hoverIndex = this.state?.hoverIndex;\n if (hoverIndex === undefined) {\n return null; // No shape hovered, no label to show\n }\n const hoveredShape = data[hoverIndex];\n labelData = hoveredShape ? [hoveredShape] : [];\n }\n\n if (labelData.length === 0) {\n return null;\n }\n\n return createShapeLabelLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_LABELS}`,\n data: labelData,\n labelOptions,\n });\n }\n\n /**\n * Render all sublayers\n */\n renderLayers(): Layer[] {\n // Compute features once per render cycle for performance\n const features = this.getFeaturesWithId();\n\n return [\n this.renderHighlightLayer(features),\n this.renderCoffinCornersLayer(features),\n this.renderMainLayer(features),\n this.renderLabelsLayer(),\n ].filter(Boolean) as Layer[];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["features: Shape['feature'][]"],"sources":["../../../../src/deckgl/shapes/display-shape-layer/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\n'use client';\n\nimport { Broadcast } from '@accelint/bus';\nimport { getLogger } from '@accelint/logger';\nimport { CompositeLayer } from '@deck.gl/core';\nimport { PathStyleExtension } from '@deck.gl/extensions';\nimport { GeoJsonLayer, IconLayer } from '@deck.gl/layers';\nimport { DASH_ARRAYS, SHAPE_LAYER_IDS } from '../shared/constants';\nimport { type ShapeEvent, ShapeEvents } from '../shared/events';\nimport {\n getDashArray,\n getFillColor,\n getLineColor,\n} from '../shared/utils/style-utils';\nimport {\n COFFIN_CORNERS,\n DEFAULT_DISPLAY_PROPS,\n MAP_INTERACTION,\n} from './constants';\nimport { createShapeLabelLayer } from './shape-label-layer';\nimport {\n getHighlightColor,\n getHighlightLineWidth,\n getHoverLineWidth,\n} from './utils/display-style';\nimport type { Layer, PickingInfo } from '@deck.gl/core';\nimport type { Shape, ShapeId } from '../shared/types';\nimport type { DisplayShapeLayerProps } from './types';\n\nconst logger = getLogger({\n enabled: process.env.NODE_ENV !== 'production',\n level: 'warn',\n prefix: '[DisplayShapeLayer]',\n pretty: true,\n});\n\n/**\n * Typed event bus instance for shape events.\n * Provides type-safe event emission for shape interactions.\n */\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\n\n/**\n * State type for DisplayShapeLayer\n */\ntype DisplayShapeLayerState = {\n /** Index of currently hovered shape, undefined when not hovering */\n hoverIndex?: number;\n /** ID of the last hovered shape for event deduplication */\n lastHoveredId?: ShapeId | null;\n /** Allow additional properties from base layer state */\n [key: string]: unknown;\n};\n\n/**\n * Cache for transformed features to avoid recreating objects on every render.\n */\ntype FeaturesCache = {\n /** Reference to the original data array for identity comparison */\n data: Shape[];\n /** Transformed features with shapeId added to properties */\n features: Shape['feature'][];\n /** Map of shapeId to feature index for O(1) lookup */\n shapeIdToIndex: Map<ShapeId, number>;\n};\n\n/**\n * DisplayShapeLayer - Read-only shapes visualization layer\n *\n * A composite deck.gl layer for displaying geographic shapes with interactive features.\n * Ideal for rendering shapes from external APIs or displaying read-only geographic data.\n *\n * ## Features\n * - **Multiple geometry types**: Point, LineString, Polygon, and Circle\n * - **Icon support**: Custom icons for Point geometries via icon atlases\n * - **Interactive selection**: Click handling with dotted border and optional highlight\n * - **Hover effects**: Border/outline width increases on hover for better UX\n * - **Customizable labels**: Flexible label positioning with per-shape or global options\n * - **Style properties**: Full control over colors, border/outline patterns, and opacity\n * - **Event bus integration**: Automatically emits shape events via @accelint/bus\n * - **Multi-map support**: Events include map instance ID for isolation\n *\n * ## Selection Visual Feedback\n * When a shape is selected via `selectedShapeId`:\n * - The shape's border/outline pattern changes to dotted\n * - An optional highlight renders underneath (controlled by `showHighlight` prop)\n *\n * ## Layer Structure\n * Renders up to four sublayers (in order, bottom to top):\n * 1. **Highlight layer**: Selection highlight effect for non-icon-Point shapes (if showHighlight=true)\n * 2. **Coffin corners layer**: Selection/hover feedback for Point shapes with icons\n * 3. **Main GeoJsonLayer**: Shape geometries with styling and interaction\n * 4. **Label layer**: Text labels (if showLabels enabled)\n *\n * ## Icon Atlas Constraint\n * When using icons for Point geometries, all shapes in a single layer must share the\n * same icon atlas. The layer uses the first atlas found across all features. If you\n * need icons from different atlases, use separate DisplayShapeLayer instances.\n *\n * ## Event Bus Integration\n * Automatically emits shape events that can be consumed anywhere in your app:\n * - `shapes:selected` - Emitted when a shape is clicked (includes mapId)\n * - `shapes:hovered` - Emitted when the hovered shape changes (deduplicated, includes mapId)\n *\n * For selection with auto-deselection, use the companion `useSelectShape` hook which handles\n * all the event wiring automatically. See the example below.\n *\n * @example Basic usage with useSelectShape hook (recommended)\n * ```tsx\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n * import { useSelectShape } from '@accelint/map-toolkit/deckgl/shapes';\n * import { uuid } from '@accelint/core';\n *\n * const MAP_ID = uuid();\n *\n * function MapWithShapes() {\n * const { selectedId } = useSelectShape(MAP_ID);\n *\n * return (\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"my-shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * showLabels=\"always\"\n * pickable={true}\n * />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example With custom label positioning\n * ```tsx\n * <displayShapeLayer\n * id=\"my-shapes\"\n * data={shapes}\n * showLabels=\"always\"\n * labelOptions={{\n * // Position circle labels at the top\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom',\n * circleLabelOffset: [0, -10],\n * // Position line labels at the middle\n * lineStringLabelCoordinateAnchor: 'middle',\n * }}\n * />\n * ```\n */\nexport class DisplayShapeLayer extends CompositeLayer<DisplayShapeLayerProps> {\n // State is typed via DisplayShapeLayerState but deck.gl doesn't support generic state\n declare state: DisplayShapeLayerState;\n\n /** Cache for transformed features to avoid recreating objects on every render */\n private featuresCache: FeaturesCache | null = null;\n\n static override layerName = 'DisplayShapeLayer';\n\n static override defaultProps = {\n ...DEFAULT_DISPLAY_PROPS,\n };\n\n /**\n * Clean up state and caches when layer is destroyed\n */\n override finalizeState(): void {\n // Clear hover state to prevent stale references\n if (this.state?.hoverIndex !== undefined) {\n this.setState({ hoverIndex: undefined, lastHoveredId: undefined });\n }\n // Clear features cache\n this.featuresCache = null;\n }\n\n /**\n * Override getPickingInfo to handle events from sublayers\n * This is the correct pattern for CompositeLayer event handling\n */\n override getPickingInfo({\n info,\n mode,\n sourceLayer,\n }: {\n info: PickingInfo;\n mode?: string;\n // biome-ignore lint/suspicious/noExplicitAny: sourceLayer type from deck.gl is not well-typed\n sourceLayer?: any;\n }) {\n // Check if this picking event came from our main shapes layer\n if (sourceLayer?.id === `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`) {\n // Handle click events (deck.gl uses 'query' mode for clicks)\n if (mode === 'query') {\n this.handleShapeClick(info);\n }\n\n // Handle hover events (including when mode is undefined, which is hover)\n if (mode === 'hover' || !mode) {\n // Update hover state\n if (info.index !== undefined && info.index !== this.state?.hoverIndex) {\n this.setState({ hoverIndex: info.index });\n } else if (\n info.index === undefined &&\n this.state?.hoverIndex !== undefined\n ) {\n this.setState({ hoverIndex: undefined });\n }\n\n // Call hover callback\n this.handleShapeHover(info);\n }\n }\n\n return info;\n }\n\n /**\n * Convert shapes to GeoJSON features with shapeId in properties.\n * Uses caching to avoid recreating objects on every render cycle.\n */\n private getFeaturesWithId(): Shape['feature'][] {\n const { data } = this.props;\n\n // Return cached features if data hasn't changed (identity check)\n if (this.featuresCache?.data === data) {\n return this.featuresCache.features;\n }\n\n // Transform features and build shapeId->index map in a single pass\n const features: Shape['feature'][] = [];\n const shapeIdToIndex = new Map<ShapeId, number>();\n\n for (const [i, shape] of data.entries()) {\n features.push({\n ...shape.feature,\n properties: {\n ...shape.feature.properties,\n shapeId: shape.id,\n },\n });\n shapeIdToIndex.set(shape.id, i);\n }\n\n this.featuresCache = { data, features, shapeIdToIndex };\n return features;\n }\n\n /**\n * Look up a shape by ID from the data prop.\n * Used by event handlers to get full shape without storing in feature properties.\n */\n private getShapeById(shapeId: ShapeId): Shape | undefined {\n return this.props.data.find((shape) => shape.id === shapeId);\n }\n\n /**\n * Handle shape click\n */\n private handleShapeClick = (info: PickingInfo): void => {\n const { onShapeClick, mapId } = this.props;\n\n if (!info.object) {\n return;\n }\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId = info.object.properties?.shapeId as ShapeId | undefined;\n if (!shapeId) {\n return;\n }\n\n const shape = this.getShapeById(shapeId);\n if (!shape) {\n return;\n }\n\n // Emit shape selected event via bus (include mapId for multi-map isolation)\n shapeBus.emit(ShapeEvents.selected, { shapeId: shape.id, mapId });\n\n // Call callback if provided\n if (onShapeClick) {\n onShapeClick(shape);\n }\n };\n\n /**\n * Handle shape hover\n */\n private handleShapeHover = (info: PickingInfo): void => {\n const { onShapeHover, mapId } = this.props;\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId =\n (info.object?.properties?.shapeId as ShapeId | undefined) ?? null;\n const shape = shapeId ? (this.getShapeById(shapeId) ?? null) : null;\n\n // Dedupe hover events - only emit if hovered shape changed\n if (shapeId !== this.state?.lastHoveredId) {\n this.setState({ lastHoveredId: shapeId });\n\n // Emit shape hovered event via bus (include mapId for multi-map isolation)\n shapeBus.emit(ShapeEvents.hovered, {\n shapeId,\n mapId,\n });\n }\n\n // Always call callback if provided (for local state updates)\n if (onShapeHover) {\n onShapeHover(shape);\n }\n };\n\n /**\n * Render highlight sublayer (underneath main layer)\n * Note: Points with icons use coffin corners instead of highlight layer\n */\n private renderHighlightLayer(\n features: Shape['feature'][],\n ): GeoJsonLayer | null {\n const { selectedShapeId, showHighlight, highlightColor } = this.props;\n\n if (!selectedShapeId || showHighlight === false) {\n return null;\n }\n\n const selectedFeature = features.find(\n (f) => f.properties?.shapeId === selectedShapeId,\n );\n\n if (!selectedFeature) {\n return null;\n }\n\n // Skip highlight layer for Point geometries with icons - they use coffin corners instead\n // Points without icons should still show the highlight layer\n if (selectedFeature.geometry.type === 'Point') {\n const hasIcon = !!selectedFeature.properties?.styleProperties?.icon;\n if (hasIcon) {\n return null;\n }\n }\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_HIGHLIGHT}`,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accepts various feature formats\n data: [selectedFeature] as any,\n\n // Styling\n filled: true,\n stroked: true,\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n getFillColor: () => [0, 0, 0, 0], // Transparent fill\n getLineColor: () => highlightColor || getHighlightColor(),\n getLineWidth: getHighlightLineWidth,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n getLineColor: [highlightColor],\n getLineWidth: [selectedShapeId, features],\n },\n });\n }\n\n /**\n * Render coffin corners layer for Point geometries that have icons on hover/select\n * Coffin corners provide visual feedback for points instead of highlight layer\n */\n private renderCoffinCornersLayer(\n features: Shape['feature'][],\n ): IconLayer | null {\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Use cached shapeId->index map for O(1) lookup\n const shapeIdToIndex = this.featuresCache?.shapeIdToIndex;\n if (!shapeIdToIndex) {\n return null;\n }\n\n // Find point features that need coffin corners (hovered or selected)\n const pointFeatures = features.filter((f) => {\n if (f.geometry.type !== 'Point') {\n return false;\n }\n const hasIcon = !!f.properties?.styleProperties?.icon;\n if (!hasIcon) {\n return false;\n }\n\n const shapeId = f.properties?.shapeId;\n const isSelected = shapeId === selectedShapeId;\n const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : undefined;\n const isHovered = hoverIndex !== undefined && featureIndex === hoverIndex;\n\n return isSelected || isHovered;\n });\n\n if (pointFeatures.length === 0) {\n return null;\n }\n\n // Get icon atlas from first point feature (all should share the same atlas)\n const firstPointIcon = pointFeatures[0]?.properties?.styleProperties?.icon;\n const iconAtlas = firstPointIcon?.atlas;\n const iconMapping = firstPointIcon?.mapping;\n\n if (!(iconAtlas && iconMapping)) {\n logger.warn(\n 'Point shape has icon style but missing iconAtlas or iconMapping - coffin corners will not render',\n );\n return null;\n }\n\n // Add coffin corners icons to the mapping\n const extendedMapping = {\n ...iconMapping,\n [COFFIN_CORNERS.HOVER_ICON]: {\n x: 0,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_ICON]: {\n x: 76,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_HOVER_ICON]: {\n x: 152,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n };\n\n return new IconLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-coffin-corners`,\n data: pointFeatures,\n iconAtlas,\n iconMapping: extendedMapping,\n getIcon: (d: Shape['feature']) => {\n const shapeId = d.properties?.shapeId;\n const isSelected = shapeId === selectedShapeId;\n const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : undefined;\n const isHovered =\n hoverIndex !== undefined && featureIndex === hoverIndex;\n\n if (isSelected && isHovered) {\n return COFFIN_CORNERS.SELECTED_HOVER_ICON;\n }\n if (isSelected) {\n return COFFIN_CORNERS.SELECTED_ICON;\n }\n return COFFIN_CORNERS.HOVER_ICON;\n },\n getSize: COFFIN_CORNERS.SIZE,\n getPosition: (d: Shape['feature']) => {\n const coords =\n d.geometry.type === 'Point' ? d.geometry.coordinates : [0, 0];\n return coords as [number, number];\n },\n getPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE;\n // Center the coffin corners on the point icon\n return [-1, -iconSize / 2];\n },\n billboard: false,\n pickable: false,\n updateTriggers: {\n getIcon: [selectedShapeId, this.state?.hoverIndex],\n data: [features, selectedShapeId, this.state?.hoverIndex],\n },\n });\n }\n\n /**\n * Extract icon configuration from features in a single pass.\n * Returns the first icon's atlas and mapping (all shapes share the same atlas).\n * Uses early return for O(1) best case when first feature has icons.\n */\n private getIconConfig(features: Shape['feature'][]): {\n hasIcons: boolean;\n atlas?: string;\n mapping?: Record<\n string,\n { x: number; y: number; width: number; height: number; mask?: boolean }\n >;\n } {\n for (const f of features) {\n const icon = f.properties?.styleProperties?.icon;\n if (icon) {\n return {\n hasIcons: true,\n atlas: icon.atlas,\n mapping: icon.mapping,\n };\n }\n }\n return { hasIcons: false };\n }\n\n /**\n * Render main shapes layer\n */\n private renderMainLayer(features: Shape['feature'][]): GeoJsonLayer {\n const { pickable, applyBaseOpacity, selectedShapeId } = this.props;\n\n // Single-pass icon config extraction (O(1) best case with early return)\n const {\n hasIcons,\n atlas: iconAtlas,\n mapping: iconMapping,\n } = this.getIconConfig(features);\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accepts various feature formats\n data: features as any,\n\n // Styling\n filled: true,\n stroked: true,\n getFillColor: (d: Shape['feature']) => getFillColor(d, applyBaseOpacity),\n getLineColor,\n getLineWidth: (d, info) => {\n const isHovered = info?.index === this.state?.hoverIndex;\n return getHoverLineWidth(d, isHovered);\n },\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n lineWidthMaxPixels: 20,\n\n // Points - use icons if any feature has icon config, otherwise circles\n pointType: hasIcons ? 'icon' : 'circle',\n getPointRadius: (d) => {\n const iconSize = d.properties?.styleProperties?.icon?.size;\n return iconSize ?? 2;\n },\n pointRadiusUnits: 'pixels',\n\n // Icon configuration (only used if pointType includes 'icon')\n ...(hasIcons && iconAtlas ? { iconAtlas } : {}),\n ...(hasIcons && iconMapping ? { iconMapping } : {}),\n ...(hasIcons\n ? {\n getIcon: (d: Shape['feature']) =>\n d.properties?.styleProperties?.icon?.name ?? 'marker',\n getIconSize: (d: Shape['feature']) => {\n return (\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE\n );\n },\n getIconColor: getLineColor,\n getIconPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE;\n return [-1, -iconSize / 2];\n },\n iconBillboard: false,\n }\n : {}),\n\n // Dash pattern support - selected shapes get dotted border\n extensions: [new PathStyleExtension({ dash: true })],\n getDashArray: (d: Shape['feature']) => {\n const isSelected = d.properties?.shapeId === selectedShapeId;\n if (isSelected) {\n return DASH_ARRAYS.dotted;\n }\n return getDashArray(d);\n },\n\n // Behavior\n pickable,\n autoHighlight: false, // We handle highlighting manually\n // Note: onClick and onHover are handled via getPickingInfo() override\n\n // Update triggers\n updateTriggers: {\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features],\n getLineWidth: [features, this.state?.hoverIndex],\n getDashArray: [features, selectedShapeId],\n getPointRadius: [features],\n ...(hasIcons\n ? {\n getIcon: [features],\n getIconSize: [features],\n getIconColor: [features],\n getIconPixelOffset: [features],\n }\n : {}),\n },\n });\n }\n\n /**\n * Render labels layer\n * Supports three modes:\n * - 'always': Show labels for all shapes\n * - 'hover': Show label only for the currently hovered shape\n * - 'never': No labels\n */\n private renderLabelsLayer(): ReturnType<typeof createShapeLabelLayer> | null {\n const { showLabels, data, labelOptions } = this.props;\n\n // No labels if disabled\n if (showLabels === 'never') {\n return null;\n }\n\n // Determine which shapes to show labels for\n let labelData = data;\n if (showLabels === 'hover') {\n const hoverIndex = this.state?.hoverIndex;\n if (hoverIndex === undefined) {\n return null; // No shape hovered, no label to show\n }\n const hoveredShape = data[hoverIndex];\n labelData = hoveredShape ? [hoveredShape] : [];\n }\n\n if (labelData.length === 0) {\n return null;\n }\n\n return createShapeLabelLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_LABELS}`,\n data: labelData,\n labelOptions,\n });\n }\n\n /**\n * Render all sublayers\n */\n renderLayers(): Layer[] {\n // Compute features once per render cycle for performance\n const features = this.getFeaturesWithId();\n\n return [\n this.renderHighlightLayer(features),\n this.renderCoffinCornersLayer(features),\n this.renderMainLayer(features),\n this.renderLabelsLayer(),\n ].filter(Boolean) as Layer[];\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"shape-label-layer.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/shape-label-layer.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { TextLayer } from '@deck.gl/layers';\nimport { DEFAULT_TEXT_STYLE } from '../../text-settings';\nimport { SHAPE_LAYER_IDS } from '../shared/constants';\nimport {\n getLabelPosition2d,\n getLabelText,\n type LabelPosition2d,\n type LabelPositionOptions,\n} from './utils/labels';\nimport type { Shape } from '../shared/types';\n\n/**\n * Creates a cached label position getter to avoid computing position multiple times per shape.\n * Uses WeakMap so positions are garbage collected when shapes are removed.\n */\nfunction createCachedPositionGetter(\n labelOptions: LabelPositionOptions | undefined,\n) {\n const cache = new WeakMap<Shape, LabelPosition2d | null>();\n\n // Returns nullable position for filtering\n const getNullable = (shape: Shape): LabelPosition2d | null => {\n if (cache.has(shape)) {\n return cache.get(shape) ?? null;\n }\n const position = getLabelPosition2d(shape, labelOptions);\n cache.set(shape, position);\n return position;\n };\n\n // Returns position, throwing if null (use only after filtering)\n const getRequired = (shape: Shape): LabelPosition2d => {\n const position = getNullable(shape);\n if (!position) {\n throw new Error(\n 'Shape has no valid position - should have been filtered',\n );\n }\n return position;\n };\n\n return { getRequired, getNullable };\n}\n\n/**\n * Props for creating a shape label layer\n */\nexport interface ShapeLabelLayerProps {\n /** Layer ID (defaults to DISPLAY_LABELS constant) */\n id?: string;\n /** Array of shapes to label */\n data: Shape[];\n /**\n * Global label positioning options\n * Per-shape properties in styleProperties take precedence\n */\n labelOptions?: LabelPositionOptions;\n}\n\n/**\n * Creates a TextLayer for rendering shape labels with intelligent positioning\n *\n * ## Features\n * - **Geometry-aware positioning**: Different defaults for Point, LineString, Polygon, and Circle\n * - **Three-tier priority system**: Per-shape properties > labelOptions > defaults\n * - **Coordinate anchoring**: Position labels at start/middle/end (or edge positions for circles)\n * - **Pixel-based offsets**: Consistent label placement at all zoom levels\n * - **Text-only styling**: White uppercase text with black outline for legibility\n * - **Position caching**: Label positions are computed once per shape using a WeakMap cache\n *\n * ## Label Positioning Priority\n * 1. Per-shape `styleProperties` (highest priority)\n * 2. Global `labelOptions` parameter\n * 3. Geometry-specific defaults (fallback)\n *\n * @param props - Shape label layer configuration\n * @returns Configured TextLayer instance\n *\n * @example\n * ```tsx\n * const labelLayer = createShapeLabelLayer({\n * id: 'my-labels',\n * data: shapes,\n * labelOptions: {\n * circleLabelCoordinateAnchor: 'top',\n * pointLabelOffset: [0, -20],\n * },\n * });\n * ```\n */\nexport function createShapeLabelLayer(\n props: ShapeLabelLayerProps,\n): TextLayer<Shape> {\n const { id = SHAPE_LAYER_IDS.DISPLAY_LABELS, data, labelOptions } = props;\n\n // Create cached position getter to avoid computing position 4x per shape\n const { getRequired, getNullable } = createCachedPositionGetter(labelOptions);\n\n // Filter out shapes with invalid positions (null coordinates)\n const validData = data.filter((shape) => getNullable(shape) !== null);\n\n return new TextLayer<Shape>({\n id,\n data: validData,\n\n // Text content - uppercase\n getText: getLabelText,\n\n // Position - use cached getter for all position-related properties\n // getRequired is safe because we filtered out null positions above\n getPosition: (d: Shape) => getRequired(d).coordinates,\n getPixelOffset: (d: Shape) => getRequired(d).pixelOffset,\n getTextAnchor: (d: Shape) => getRequired(d).textAnchor,\n getAlignmentBaseline: (d: Shape) => getRequired(d).alignmentBaseline,\n\n // Cross-platform text styling (SDF font settings for legibility on Windows)\n ...DEFAULT_TEXT_STYLE,\n getAngle: 0,\n\n // No background or border\n background: false,\n\n // Font overrides\n fontFamily: 'Roboto MonoVariable, monospace',\n\n // Update triggers - tell deck.gl to recalculate when labelOptions change\n updateTriggers: {\n getPosition: [labelOptions],\n getPixelOffset: [labelOptions],\n getTextAnchor: [labelOptions],\n getAlignmentBaseline: [labelOptions],\n },\n\n // Behavior\n pickable: false,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAS,2BACP,cACA;CACA,MAAM,wBAAQ,IAAI,SAAwC;CAG1D,MAAM,eAAe,UAAyC;AAC5D,MAAI,MAAM,IAAI,MAAM,CAClB,QAAO,MAAM,IAAI,MAAM,IAAI;EAE7B,MAAM,WAAW,mBAAmB,OAAO,aAAa;AACxD,QAAM,IAAI,OAAO,SAAS;AAC1B,SAAO;;CAIT,MAAM,eAAe,UAAkC;EACrD,MAAM,WAAW,YAAY,MAAM;AACnC,MAAI,CAAC,SACH,OAAM,IAAI,MACR,0DACD;AAEH,SAAO;;AAGT,QAAO;EAAE;EAAa;EAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDrC,SAAgB,sBACd,OACkB;CAClB,MAAM,EAAE,KAAK,gBAAgB,gBAAgB,MAAM,iBAAiB;CAGpE,MAAM,EAAE,aAAa,gBAAgB,2BAA2B,aAAa;AAK7E,QAAO,IAAI,UAAiB;EAC1B;EACA,MAJgB,KAAK,QAAQ,UAAU,YAAY,MAAM,KAAK,KAAK;EAOnE,SAAS;EAIT,cAAc,MAAa,YAAY,EAAE,CAAC;EAC1C,iBAAiB,MAAa,YAAY,EAAE,CAAC;EAC7C,gBAAgB,MAAa,YAAY,EAAE,CAAC;EAC5C,uBAAuB,MAAa,YAAY,EAAE,CAAC;EAGnD,GAAG;EACH,UAAU;EAGV,YAAY;EAGZ,YAAY;EAGZ,gBAAgB;GACd,aAAa,CAAC,aAAa;GAC3B,gBAAgB,CAAC,aAAa;GAC9B,eAAe,CAAC,aAAa;GAC7B,sBAAsB,CAAC,aAAa;GACrC;EAGD,UAAU;EACX,CAAC"}
1
+ {"version":3,"file":"shape-label-layer.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/shape-label-layer.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\n'use client';\n\nimport { TextLayer } from '@deck.gl/layers';\nimport { DEFAULT_TEXT_STYLE } from '../../text-settings';\nimport { SHAPE_LAYER_IDS } from '../shared/constants';\nimport {\n getLabelPosition2d,\n getLabelText,\n type LabelPosition2d,\n type LabelPositionOptions,\n} from './utils/labels';\nimport type { Shape } from '../shared/types';\n\n/**\n * Creates a cached label position getter to avoid computing position multiple times per shape.\n * Uses WeakMap so positions are garbage collected when shapes are removed.\n */\nfunction createCachedPositionGetter(\n labelOptions: LabelPositionOptions | undefined,\n) {\n const cache = new WeakMap<Shape, LabelPosition2d | null>();\n\n // Returns nullable position for filtering\n const getNullable = (shape: Shape): LabelPosition2d | null => {\n if (cache.has(shape)) {\n return cache.get(shape) ?? null;\n }\n const position = getLabelPosition2d(shape, labelOptions);\n cache.set(shape, position);\n return position;\n };\n\n // Returns position, throwing if null (use only after filtering)\n const getRequired = (shape: Shape): LabelPosition2d => {\n const position = getNullable(shape);\n if (!position) {\n throw new Error(\n 'Shape has no valid position - should have been filtered',\n );\n }\n return position;\n };\n\n return { getRequired, getNullable };\n}\n\n/**\n * Props for creating a shape label layer\n */\nexport interface ShapeLabelLayerProps {\n /** Layer ID (defaults to DISPLAY_LABELS constant) */\n id?: string;\n /** Array of shapes to label */\n data: Shape[];\n /**\n * Global label positioning options\n * Per-shape properties in styleProperties take precedence\n */\n labelOptions?: LabelPositionOptions;\n}\n\n/**\n * Creates a TextLayer for rendering shape labels with intelligent positioning\n *\n * ## Features\n * - **Geometry-aware positioning**: Different defaults for Point, LineString, Polygon, and Circle\n * - **Three-tier priority system**: Per-shape properties > labelOptions > defaults\n * - **Coordinate anchoring**: Position labels at start/middle/end (or edge positions for circles)\n * - **Pixel-based offsets**: Consistent label placement at all zoom levels\n * - **Text-only styling**: White uppercase text with black outline for legibility\n * - **Position caching**: Label positions are computed once per shape using a WeakMap cache\n *\n * ## Label Positioning Priority\n * 1. Per-shape `styleProperties` (highest priority)\n * 2. Global `labelOptions` parameter\n * 3. Geometry-specific defaults (fallback)\n *\n * @param props - Shape label layer configuration\n * @returns Configured TextLayer instance\n *\n * @example\n * ```tsx\n * const labelLayer = createShapeLabelLayer({\n * id: 'my-labels',\n * data: shapes,\n * labelOptions: {\n * circleLabelCoordinateAnchor: 'top',\n * pointLabelOffset: [0, -20],\n * },\n * });\n * ```\n */\nexport function createShapeLabelLayer(\n props: ShapeLabelLayerProps,\n): TextLayer<Shape> {\n const { id = SHAPE_LAYER_IDS.DISPLAY_LABELS, data, labelOptions } = props;\n\n // Create cached position getter to avoid computing position 4x per shape\n const { getRequired, getNullable } = createCachedPositionGetter(labelOptions);\n\n // Filter out shapes with invalid positions (null coordinates)\n const validData = data.filter((shape) => getNullable(shape) !== null);\n\n return new TextLayer<Shape>({\n id,\n data: validData,\n\n // Text content - uppercase\n getText: getLabelText,\n\n // Position - use cached getter for all position-related properties\n // getRequired is safe because we filtered out null positions above\n getPosition: (d: Shape) => getRequired(d).coordinates,\n getPixelOffset: (d: Shape) => getRequired(d).pixelOffset,\n getTextAnchor: (d: Shape) => getRequired(d).textAnchor,\n getAlignmentBaseline: (d: Shape) => getRequired(d).alignmentBaseline,\n\n // Cross-platform text styling (SDF font settings for legibility on Windows)\n ...DEFAULT_TEXT_STYLE,\n getAngle: 0,\n\n // No background or border\n background: false,\n\n // Font overrides\n fontFamily: 'Roboto MonoVariable, monospace',\n\n // Update triggers - tell deck.gl to recalculate when labelOptions change\n updateTriggers: {\n getPosition: [labelOptions],\n getPixelOffset: [labelOptions],\n getTextAnchor: [labelOptions],\n getAlignmentBaseline: [labelOptions],\n },\n\n // Behavior\n pickable: false,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAS,2BACP,cACA;CACA,MAAM,wBAAQ,IAAI,SAAwC;CAG1D,MAAM,eAAe,UAAyC;AAC5D,MAAI,MAAM,IAAI,MAAM,CAClB,QAAO,MAAM,IAAI,MAAM,IAAI;EAE7B,MAAM,WAAW,mBAAmB,OAAO,aAAa;AACxD,QAAM,IAAI,OAAO,SAAS;AAC1B,SAAO;;CAIT,MAAM,eAAe,UAAkC;EACrD,MAAM,WAAW,YAAY,MAAM;AACnC,MAAI,CAAC,SACH,OAAM,IAAI,MACR,0DACD;AAEH,SAAO;;AAGT,QAAO;EAAE;EAAa;EAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDrC,SAAgB,sBACd,OACkB;CAClB,MAAM,EAAE,KAAK,gBAAgB,gBAAgB,MAAM,iBAAiB;CAGpE,MAAM,EAAE,aAAa,gBAAgB,2BAA2B,aAAa;AAK7E,QAAO,IAAI,UAAiB;EAC1B;EACA,MAJgB,KAAK,QAAQ,UAAU,YAAY,MAAM,KAAK,KAAK;EAOnE,SAAS;EAIT,cAAc,MAAa,YAAY,EAAE,CAAC;EAC1C,iBAAiB,MAAa,YAAY,EAAE,CAAC;EAC7C,gBAAgB,MAAa,YAAY,EAAE,CAAC;EAC5C,uBAAuB,MAAa,YAAY,EAAE,CAAC;EAGnD,GAAG;EACH,UAAU;EAGV,YAAY;EAGZ,YAAY;EAGZ,gBAAgB;GACd,aAAa,CAAC,aAAa;GAC3B,gBAAgB,CAAC,aAAa;GAC9B,eAAe,CAAC,aAAa;GAC7B,sBAAsB,CAAC,aAAa;GACrC;EAGD,UAAU;EACX,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/store.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Shape Selection Store\n *\n * Manages shape selection state per map instance.\n *\n * @example\n * ```tsx\n * import { shapeSelectionStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function ShapePanel({ mapId }) {\n * const { state, setSelectedId, clearSelection } = shapeSelectionStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Selected: {state.selectedId ?? 'none'}</p>\n * <button onClick={() => setSelectedId('shape-1')}>Select Shape 1</button>\n * <button onClick={clearSelection}>Clear</button>\n * </div>\n * );\n * }\n *\n * // Or with selector for specific values:\n * function SelectedIndicator({ mapId }) {\n * const selectedId = shapeSelectionStore.useSelector(\n * mapId,\n * (s) => s.selectedId\n * );\n * return selectedId ? <Badge>Selected</Badge> : null;\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { MapEvents } from '../../base-map/events';\nimport { ShapeEvents } from '../shared/events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapClickEvent, MapEventType } from '../../base-map/types';\nimport type { ShapeEvent } from '../shared/events';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * State shape for shape selection\n */\ntype ShapeSelectionState = {\n selectedId: ShapeId | undefined;\n};\n\n/**\n * Actions for shape selection\n */\ntype ShapeSelectionActions = {\n /** Set the selected shape ID (emits appropriate events) */\n setSelectedId: (id: ShapeId | undefined) => void;\n /** Clear the current selection */\n clearSelection: () => void;\n};\n\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\nconst mapBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Shape selection store\n */\nexport const shapeSelectionStore = createMapStore<\n ShapeSelectionState,\n ShapeSelectionActions\n>({\n defaultState: { selectedId: undefined },\n\n actions: (mapId, { get }) => ({\n setSelectedId: (id: ShapeId | undefined) => {\n const currentId = get().selectedId;\n\n if (id === undefined && currentId !== undefined) {\n // Emit deselection event\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n } else if (id !== undefined && currentId !== id) {\n // Emit selection event\n shapeBus.emit(ShapeEvents.selected, { shapeId: id, mapId });\n }\n },\n\n clearSelection: () => {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Listen for shape selection events\n const unsubSelected = shapeBus.on(ShapeEvents.selected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== event.payload.shapeId) {\n set({ selectedId: event.payload.shapeId });\n }\n });\n\n // Listen for shape deselection events\n const unsubDeselected = shapeBus.on(ShapeEvents.deselected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== undefined) {\n set({ selectedId: undefined });\n }\n });\n\n // Listen for map clicks to detect clicks on empty space\n const unsubClick = mapBus.on(MapEvents.click, (event: MapClickEvent) => {\n // Deselect if clicked on empty space (index === -1)\n if (\n get().selectedId !== undefined &&\n event.payload.id === mapId &&\n event.payload.info.index === -1\n ) {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n }\n });\n\n return () => {\n unsubSelected();\n unsubDeselected();\n unsubClick();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports for common patterns\n// =============================================================================\n\n/**\n * Hook for shape selection - primary API\n *\n * @example\n * ```tsx\n * const { state, setSelectedId, clearSelection } = useSelectShape(mapId);\n * ```\n */\nexport const useSelectShape = shapeSelectionStore.use;\n\n/**\n * Hook to get just the selected ID\n *\n * @example\n * ```tsx\n * const selectedId = useSelectedShapeId(mapId);\n * ```\n */\nexport function useSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.useSelector(mapId, (s) => s.selectedId);\n}\n\n/**\n * Get selected shape ID imperatively (non-reactive)\n */\nexport function getSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.get(mapId).selectedId;\n}\n\n/**\n * Clear selection state (for tests/cleanup)\n */\nexport function clearSelectionState(mapId: UniqueId): void {\n shapeSelectionStore.clear(mapId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAM,WAAW,UAAU,aAAyB;AACpD,MAAM,SAAS,UAAU,aAA2B;;;;AAKpD,MAAa,sBAAsB,eAGjC;CACA,cAAc,EAAE,YAAY,QAAW;CAEvC,UAAU,OAAO,EAAE,WAAW;EAC5B,gBAAgB,OAA4B;GAC1C,MAAM,YAAY,KAAK,CAAC;AAExB,OAAI,OAAO,UAAa,cAAc,OAEpC,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;YACvC,OAAO,UAAa,cAAc,GAE3C,UAAS,KAAK,YAAY,UAAU;IAAE,SAAS;IAAI;IAAO,CAAC;;EAI/D,sBAAsB;AACpB,YAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;;EAEnD;CAED,MAAM,OAAO,EAAE,KAAK,UAAU;EAE5B,MAAM,gBAAgB,SAAS,GAAG,YAAY,WAAW,UAAU;AACjE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,MAAM,QAAQ,QACrC,KAAI,EAAE,YAAY,MAAM,QAAQ,SAAS,CAAC;IAE5C;EAGF,MAAM,kBAAkB,SAAS,GAAG,YAAY,aAAa,UAAU;AACrE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,OACvB,KAAI,EAAE,YAAY,QAAW,CAAC;IAEhC;EAGF,MAAM,aAAa,OAAO,GAAG,UAAU,QAAQ,UAAyB;AAEtE,OACE,KAAK,CAAC,eAAe,UACrB,MAAM,QAAQ,OAAO,SACrB,MAAM,QAAQ,KAAK,UAAU,GAE7B,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;IAElD;AAEF,eAAa;AACX,kBAAe;AACf,oBAAiB;AACjB,eAAY;;;CAGjB,CAAC;;;;;;;;;AAcF,MAAa,iBAAiB,oBAAoB"}
1
+ {"version":3,"file":"store.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/store.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\n/**\n * Shape Selection Store\n *\n * Manages shape selection state per map instance.\n *\n * @example\n * ```tsx\n * import { shapeSelectionStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function ShapePanel({ mapId }) {\n * const { state, setSelectedId, clearSelection } = shapeSelectionStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Selected: {state.selectedId ?? 'none'}</p>\n * <button onClick={() => setSelectedId('shape-1')}>Select Shape 1</button>\n * <button onClick={clearSelection}>Clear</button>\n * </div>\n * );\n * }\n *\n * // Or with selector for specific values:\n * function SelectedIndicator({ mapId }) {\n * const selectedId = shapeSelectionStore.useSelector(\n * mapId,\n * (s) => s.selectedId\n * );\n * return selectedId ? <Badge>Selected</Badge> : null;\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { MapEvents } from '../../base-map/events';\nimport { ShapeEvents } from '../shared/events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapClickEvent, MapEventType } from '../../base-map/types';\nimport type { ShapeEvent } from '../shared/events';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * State shape for shape selection\n */\ntype ShapeSelectionState = {\n selectedId: ShapeId | undefined;\n};\n\n/**\n * Actions for shape selection\n */\ntype ShapeSelectionActions = {\n /** Set the selected shape ID (emits appropriate events) */\n setSelectedId: (id: ShapeId | undefined) => void;\n /** Clear the current selection */\n clearSelection: () => void;\n};\n\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\nconst mapBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Shape selection store\n */\nexport const shapeSelectionStore = createMapStore<\n ShapeSelectionState,\n ShapeSelectionActions\n>({\n defaultState: { selectedId: undefined },\n\n actions: (mapId, { get }) => ({\n setSelectedId: (id: ShapeId | undefined) => {\n const currentId = get().selectedId;\n\n if (id === undefined && currentId !== undefined) {\n // Emit deselection event\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n } else if (id !== undefined && currentId !== id) {\n // Emit selection event\n shapeBus.emit(ShapeEvents.selected, { shapeId: id, mapId });\n }\n },\n\n clearSelection: () => {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Listen for shape selection events\n const unsubSelected = shapeBus.on(ShapeEvents.selected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== event.payload.shapeId) {\n set({ selectedId: event.payload.shapeId });\n }\n });\n\n // Listen for shape deselection events\n const unsubDeselected = shapeBus.on(ShapeEvents.deselected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== undefined) {\n set({ selectedId: undefined });\n }\n });\n\n // Listen for map clicks to detect clicks on empty space\n const unsubClick = mapBus.on(MapEvents.click, (event: MapClickEvent) => {\n // Deselect if clicked on empty space (index === -1)\n if (\n get().selectedId !== undefined &&\n event.payload.id === mapId &&\n event.payload.info.index === -1\n ) {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n }\n });\n\n return () => {\n unsubSelected();\n unsubDeselected();\n unsubClick();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports for common patterns\n// =============================================================================\n\n/**\n * Hook for shape selection - primary API\n *\n * @example\n * ```tsx\n * const { state, setSelectedId, clearSelection } = useSelectShape(mapId);\n * ```\n */\nexport const useSelectShape = shapeSelectionStore.use;\n\n/**\n * Hook to get just the selected ID\n *\n * @example\n * ```tsx\n * const selectedId = useSelectedShapeId(mapId);\n * ```\n */\nexport function useSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.useSelector(mapId, (s) => s.selectedId);\n}\n\n/**\n * Get selected shape ID imperatively (non-reactive)\n */\nexport function getSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.get(mapId).selectedId;\n}\n\n/**\n * Clear selection state (for tests/cleanup)\n */\nexport function clearSelectionState(mapId: UniqueId): void {\n shapeSelectionStore.clear(mapId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAM,WAAW,UAAU,aAAyB;AACpD,MAAM,SAAS,UAAU,aAA2B;;;;AAKpD,MAAa,sBAAsB,eAGjC;CACA,cAAc,EAAE,YAAY,QAAW;CAEvC,UAAU,OAAO,EAAE,WAAW;EAC5B,gBAAgB,OAA4B;GAC1C,MAAM,YAAY,KAAK,CAAC;AAExB,OAAI,OAAO,UAAa,cAAc,OAEpC,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;YACvC,OAAO,UAAa,cAAc,GAE3C,UAAS,KAAK,YAAY,UAAU;IAAE,SAAS;IAAI;IAAO,CAAC;;EAI/D,sBAAsB;AACpB,YAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;;EAEnD;CAED,MAAM,OAAO,EAAE,KAAK,UAAU;EAE5B,MAAM,gBAAgB,SAAS,GAAG,YAAY,WAAW,UAAU;AACjE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,MAAM,QAAQ,QACrC,KAAI,EAAE,YAAY,MAAM,QAAQ,SAAS,CAAC;IAE5C;EAGF,MAAM,kBAAkB,SAAS,GAAG,YAAY,aAAa,UAAU;AACrE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,OACvB,KAAI,EAAE,YAAY,QAAW,CAAC;IAEhC;EAGF,MAAM,aAAa,OAAO,GAAG,UAAU,QAAQ,UAAyB;AAEtE,OACE,KAAK,CAAC,eAAe,UACrB,MAAM,QAAQ,OAAO,SACrB,MAAM,QAAQ,KAAK,UAAU,GAE7B,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;IAElD;AAEF,eAAa;AACX,kBAAe;AACf,oBAAiB;AACjB,eAAY;;;CAGjB,CAAC;;;;;;;;;AAcF,MAAa,iBAAiB,oBAAoB"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-select-shape.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/use-select-shape.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { shapeSelectionStore } from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * Return type for useSelectShape hook\n */\nexport interface UseSelectShapeReturn {\n /** Currently selected shape ID, or undefined if nothing selected */\n selectedId: ShapeId | undefined;\n /** Manually set the selected shape ID (useful for programmatic selection) */\n setSelectedId: (id: ShapeId | undefined) => void;\n /** Manually clear the selection */\n clearSelection: () => void;\n}\n\n/**\n * Hook to manage shape selection state with automatic deselection\n *\n * This hook encapsulates the common pattern of:\n * 1. Listening to `shapes:selected` events and updating state\n * 2. Listening to `shapes:deselected` events and clearing state\n * 3. Listening to map clicks on empty space and emitting `shapes:deselected`\n *\n * Uses a store pattern with `useSyncExternalStore` for proper listener cleanup\n * during HMR (Hot Module Replacement) in development. The store ensures only\n * one bus listener exists per map instance, regardless of how many React\n * components subscribe.\n *\n * @param mapId - The map instance ID for event filtering\n * @returns Selection state and control functions\n *\n * @example Basic usage\n * ```tsx\n * import { useSelectShape } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function MapWithShapes() {\n * const { selectedId } = useSelectShape(MAP_ID);\n *\n * return (\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example With programmatic selection control\n * ```tsx\n * function MapWithShapes() {\n * const { selectedId, setSelectedId, clearSelection } = useSelectShape(MAP_ID);\n *\n * return (\n * <>\n * <button onClick={() => setSelectedId(shapes[0].id)}>Select First Shape</button>\n * <button onClick={clearSelection}>Clear Selection</button>\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * />\n * </BaseMap>\n * </>\n * );\n * }\n * ```\n */\nexport function useSelectShape(mapId: UniqueId): UseSelectShapeReturn {\n const { state, setSelectedId, clearSelection } =\n shapeSelectionStore.use(mapId);\n\n return {\n selectedId: state.selectedId,\n setSelectedId,\n clearSelection,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,SAAgB,eAAe,OAAuC;CACpE,MAAM,EAAE,OAAO,eAAe,mBAC5B,oBAAoB,IAAI,MAAM;AAEhC,QAAO;EACL,YAAY,MAAM;EAClB;EACA;EACD"}
1
+ {"version":3,"file":"use-select-shape.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/use-select-shape.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\n'use client';\n\nimport { shapeSelectionStore } from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * Return type for useSelectShape hook\n */\nexport interface UseSelectShapeReturn {\n /** Currently selected shape ID, or undefined if nothing selected */\n selectedId: ShapeId | undefined;\n /** Manually set the selected shape ID (useful for programmatic selection) */\n setSelectedId: (id: ShapeId | undefined) => void;\n /** Manually clear the selection */\n clearSelection: () => void;\n}\n\n/**\n * Hook to manage shape selection state with automatic deselection\n *\n * This hook encapsulates the common pattern of:\n * 1. Listening to `shapes:selected` events and updating state\n * 2. Listening to `shapes:deselected` events and clearing state\n * 3. Listening to map clicks on empty space and emitting `shapes:deselected`\n *\n * Uses a store pattern with `useSyncExternalStore` for proper listener cleanup\n * during HMR (Hot Module Replacement) in development. The store ensures only\n * one bus listener exists per map instance, regardless of how many React\n * components subscribe.\n *\n * @param mapId - The map instance ID for event filtering\n * @returns Selection state and control functions\n *\n * @example Basic usage\n * ```tsx\n * import { useSelectShape } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function MapWithShapes() {\n * const { selectedId } = useSelectShape(MAP_ID);\n *\n * return (\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example With programmatic selection control\n * ```tsx\n * function MapWithShapes() {\n * const { selectedId, setSelectedId, clearSelection } = useSelectShape(MAP_ID);\n *\n * return (\n * <>\n * <button onClick={() => setSelectedId(shapes[0].id)}>Select First Shape</button>\n * <button onClick={clearSelection}>Clear Selection</button>\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * />\n * </BaseMap>\n * </>\n * );\n * }\n * ```\n */\nexport function useSelectShape(mapId: UniqueId): UseSelectShapeReturn {\n const { state, setSelectedId, clearSelection } =\n shapeSelectionStore.use(mapId);\n\n return {\n selectedId: state.selectedId,\n setSelectedId,\n clearSelection,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,SAAgB,eAAe,OAAuC;CACpE,MAAM,EAAE,OAAO,eAAe,mBAC5B,oBAAoB,IAAI,MAAM;AAEhC,QAAO;EACL,YAAY,MAAM;EAClB;EACA;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"display-style.js","names":[],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/display-style.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport {\n DEFAULT_COLORS,\n HIGHLIGHT_WIDTH_INCREASE,\n HOVER_WIDTH_INCREASE,\n} from '../../shared/constants';\nimport { getLineWidth, normalizeColor } from '../../shared/utils/style-utils';\nimport type { StyledFeature } from '../../shared/types';\n\n/**\n * Get hover-enhanced border/outline width\n */\nexport function getHoverLineWidth(\n feature: StyledFeature,\n isHovered: boolean,\n): number {\n const baseWidth = getLineWidth(feature);\n return isHovered ? baseWidth + HOVER_WIDTH_INCREASE : baseWidth;\n}\n\n/**\n * Get selection highlight color\n * Returns the default highlight color or allows custom opacity override\n */\nexport function getHighlightColor(\n opacity?: number,\n): [number, number, number, number] {\n const rgba = normalizeColor(DEFAULT_COLORS.highlight);\n\n if (opacity !== undefined) {\n return [rgba[0], rgba[1], rgba[2], Math.round(opacity * 255)];\n }\n\n return rgba;\n}\n\n/**\n * Get highlight border/outline width (base width + increase)\n */\nexport function getHighlightLineWidth(feature: StyledFeature): number {\n const baseWidth = getLineWidth(feature);\n return baseWidth + HIGHLIGHT_WIDTH_INCREASE;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,kBACd,SACA,WACQ;CACR,MAAM,YAAY,aAAa,QAAQ;AACvC,QAAO,YAAY,YAAY,uBAAuB;;;;;;AAOxD,SAAgB,kBACd,SACkC;CAClC,MAAM,OAAO,eAAe,eAAe,UAAU;AAErD,KAAI,YAAY,OACd,QAAO;EAAC,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI,KAAK,MAAM,UAAU,IAAI;EAAC;AAG/D,QAAO;;;;;AAMT,SAAgB,sBAAsB,SAAgC;AAEpE,QADkB,aAAa,QAAQ,GACpB"}
1
+ {"version":3,"file":"display-style.js","names":[],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/display-style.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\n'use client';\n\nimport {\n DEFAULT_COLORS,\n HIGHLIGHT_WIDTH_INCREASE,\n HOVER_WIDTH_INCREASE,\n} from '../../shared/constants';\nimport { getLineWidth, normalizeColor } from '../../shared/utils/style-utils';\nimport type { StyledFeature } from '../../shared/types';\n\n/**\n * Get hover-enhanced border/outline width\n */\nexport function getHoverLineWidth(\n feature: StyledFeature,\n isHovered: boolean,\n): number {\n const baseWidth = getLineWidth(feature);\n return isHovered ? baseWidth + HOVER_WIDTH_INCREASE : baseWidth;\n}\n\n/**\n * Get selection highlight color\n * Returns the default highlight color or allows custom opacity override\n */\nexport function getHighlightColor(\n opacity?: number,\n): [number, number, number, number] {\n const rgba = normalizeColor(DEFAULT_COLORS.highlight);\n\n if (opacity !== undefined) {\n return [rgba[0], rgba[1], rgba[2], Math.round(opacity * 255)];\n }\n\n return rgba;\n}\n\n/**\n * Get highlight border/outline width (base width + increase)\n */\nexport function getHighlightLineWidth(feature: StyledFeature): number {\n const baseWidth = getLineWidth(feature);\n return baseWidth + HIGHLIGHT_WIDTH_INCREASE;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,kBACd,SACA,WACQ;CACR,MAAM,YAAY,aAAa,QAAQ;AACvC,QAAO,YAAY,YAAY,uBAAuB;;;;;;AAOxD,SAAgB,kBACd,SACkC;CAClC,MAAM,OAAO,eAAe,eAAe,UAAU;AAErD,KAAI,YAAY,OACd,QAAO;EAAC,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI,KAAK,MAAM,UAAU,IAAI;EAAC;AAG/D,QAAO;;;;;AAMT,SAAgB,sBAAsB,SAAgC;AAEpE,QADkB,aAAa,QAAQ,GACpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"labels.js","names":["defaultOffset: [number, number]","defaultVertical: LabelVerticalPosition","defaultHorizontal: LabelHorizontalPosition","defaultCoordinateAnchor: CardinalLabelCoordinateAnchor"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/labels.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Label positioning utilities for shapes\n *\n * This module provides utilities for positioning labels on shapes and calculating\n * points along line segments. Use these to customize label placement in your shape data.\n *\n * ## Label Styling\n *\n * Labels use a text-only style for legibility across different map backgrounds:\n * - **Text**: White uppercase with bold Roboto Mono font at 10px\n * - **Outline**: Black 2px outline around text for contrast\n * - **No background or border**: Clean text-only appearance\n *\n * ### Calculating Offsets\n *\n * When positioning labels, the text height is approximately 10px.\n * For example, to position a label exactly 5px above a point:\n * - Label height ≈ text height (10px)\n * - Offset needed: [0, -(10 + 5)] = [0, -15]\n *\n * @example Position label at the middle of a LineString\n * ```typescript\n * import { getLineStringMidpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * // Calculate midpoint for custom positioning\n * const coordinates = [[-122.4, 37.8], [-122.3, 37.9], [-122.2, 38.0]];\n * const midpoint = getLineStringMidpoint(coordinates);\n *\n * // Use in shape data with custom label properties\n * const shape = {\n * feature: {\n * properties: {\n * styleProperties: {\n * labelVerticalAnchor: 'top',\n * labelHorizontalAnchor: 'center',\n * labelOffset: [0, -10], // Small offset above the line\n * }\n * }\n * }\n * };\n * ```\n */\n\n'use client';\n\nimport type { LineString, Point, Polygon } from 'geojson';\nimport { isCircleShape } from '../../shared/types';\nimport type { Shape } from '../../shared/types';\n\n/**\n * Label positioning information including coordinates and screen-space offsets\n */\nexport interface LabelPosition2d {\n /** Geographic coordinates [longitude, latitude] */\n coordinates: [number, number];\n /** Horizontal text anchor point */\n textAnchor: 'start' | 'middle' | 'end';\n /** Vertical text alignment */\n alignmentBaseline: 'top' | 'center' | 'bottom';\n /** Pixel offset from coordinates [x, y] */\n pixelOffset: [number, number];\n}\n\n/**\n * Calculate a point along a line segment\n * @param start - Start coordinate [lon, lat]\n * @param end - End coordinate [lon, lat]\n * @param ratio - Position along segment (0 = start, 0.5 = middle, 1 = end)\n * @returns Interpolated coordinate [lon, lat]\n *\n * @example\n * // Get a point 25% along a line segment\n * const start: [number, number] = [-122.4, 37.8];\n * const end: [number, number] = [-122.3, 37.9];\n * const point = interpolatePoint(start, end, 0.25);\n */\nexport function interpolatePoint(\n start: [number, number],\n end: [number, number],\n ratio: number,\n): [number, number] {\n const clampedRatio = Math.max(0, Math.min(1, ratio));\n return [\n start[0] + (end[0] - start[0]) * clampedRatio,\n start[1] + (end[1] - start[1]) * clampedRatio,\n ];\n}\n\n/**\n * Calculate segment lengths for a line\n */\nfunction calculateSegmentLengths(coordinates: [number, number][]): {\n lengths: number[];\n total: number;\n} {\n let total = 0;\n const lengths: number[] = [];\n\n for (let i = 0; i < coordinates.length - 1; i++) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const length = Math.sqrt(\n (end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2,\n );\n lengths.push(length);\n total += length;\n }\n }\n\n return { lengths, total };\n}\n\n/**\n * Find point at specific distance along line\n */\nfunction findPointAtDistance(\n coordinates: [number, number][],\n segmentLengths: number[],\n targetDistance: number,\n): [number, number] {\n let accumulatedLength = 0;\n\n for (let i = 0; i < segmentLengths.length; i++) {\n const segmentLength = segmentLengths[i];\n if (!segmentLength) {\n continue;\n }\n\n if (accumulatedLength + segmentLength >= targetDistance) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const ratio = (targetDistance - accumulatedLength) / segmentLength;\n return interpolatePoint(start, end, ratio);\n }\n }\n accumulatedLength += segmentLength;\n }\n\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a LineString\n * @param coordinates - LineString coordinates array\n * @returns Midpoint coordinate [lon, lat]\n */\nexport function getLineStringMidpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n if (coordinates.length === 1) {\n return coordinates[0] ?? [0, 0];\n }\n\n const { lengths, total } = calculateSegmentLengths(coordinates);\n const halfLength = total / 2;\n\n return findPointAtDistance(coordinates, lengths, halfLength);\n}\n\n/**\n * Get the end point of a LineString\n * @param coordinates - LineString coordinates array\n * @returns End coordinate [lon, lat]\n */\nexport function getLineStringEndpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a Polygon's outer ring\n * @param coordinates - Polygon coordinates array (rings)\n * @returns Midpoint of outer ring [lon, lat]\n */\nexport function getPolygonMidpoint(\n coordinates: [number, number][][],\n): [number, number] {\n const outerRing = coordinates[0];\n if (!outerRing || outerRing.length === 0) {\n return [0, 0];\n }\n // Use the outer ring (first ring)\n return getLineStringMidpoint(outerRing);\n}\n\n/**\n * Vertical label position relative to anchor point\n */\nexport type LabelVerticalPosition = 'top' | 'middle' | 'bottom';\n\n/**\n * Horizontal label position relative to anchor point\n */\nexport type LabelHorizontalPosition = 'left' | 'center' | 'right';\n\n/**\n * Cardinal direction anchor for positioning labels on geometry edges\n * Uses edge positions relative to the geometry's bounding box\n * Works for LineString, Polygon, and Circle geometries\n * - 'center': centroid/midpoint of the geometry\n * - 'top'/'right'/'bottom'/'left': edge positions\n */\nexport type CardinalLabelCoordinateAnchor =\n | 'center'\n | 'top'\n | 'right'\n | 'bottom'\n | 'left';\n\n/**\n * Global label positioning options for DisplayShapeLayer\n *\n * ## Priority System\n * Label positioning follows a three-tier priority system:\n * 1. **Per-shape properties** in `styleProperties` (highest priority)\n * 2. **Global options** via this interface\n * 3. **Default values** (geometry-specific fallbacks)\n *\n * ## Label Appearance\n *\n * Labels use a clean text-only style:\n * - **Text**: White uppercase, bold Roboto Mono, 10px\n * - **Outline**: Black 2px outline for contrast\n * - **No background or border**\n *\n * Text height ≈ 10px\n *\n * ## Positioning Concepts\n *\n * ### Coordinate Anchor\n * Determines *where* on the geometry to place the label:\n * - **Point**: Label is always at the point coordinate\n * - **LineString/Polygon**: 'start', 'middle', or 'end' along the geometry\n * - **Circle**: 'top', 'right', 'bottom', or 'left' on the perimeter\n *\n * ### Vertical/Horizontal Anchor\n * Determines how the label aligns *relative to* the anchor point:\n * - **Vertical**: 'top' (label text below point), 'middle' (centered), 'bottom' (label text above point)\n * - **Horizontal**: 'left' (label text right of point), 'center' (centered), 'right' (label text left of point)\n *\n * ### Pixel Offset\n * Fine-tune label position with [x, y] pixel offsets:\n * - Positive x moves right, negative moves left\n * - Positive y moves down, negative moves up\n *\n * @example Position circle labels at the top with 5px clearance\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom', // Label above the top point\n * circleLabelOffset: [0, -15], // -(10px text height + 5px clearance)\n * };\n * ```\n *\n * @example Position line labels at middle with offset to the right\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * lineStringLabelCoordinateAnchor: 'middle',\n * lineStringLabelHorizontalAnchor: 'left',\n * lineStringLabelOffset: [10, 0], // 10px to the right\n * };\n * ```\n */\nexport interface LabelPositionOptions {\n // Point geometry options\n /** Vertical anchor for Point labels @default 'top' */\n pointLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Point labels @default 'center' */\n pointLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Point labels [x, y] @default [0, 10] */\n pointLabelOffset?: [number, number];\n\n // LineString geometry options\n /** Position on LineString edge (top/right/bottom/left) @default 'bottom' */\n lineStringLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for LineString labels @default 'top' */\n lineStringLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for LineString labels @default 'center' */\n lineStringLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for LineString labels [x, y] @default [0, 10] */\n lineStringLabelOffset?: [number, number];\n\n // Polygon geometry options\n /** Position on Polygon edge (top/right/bottom/left) @default 'bottom' */\n polygonLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Polygon labels @default 'top' */\n polygonLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Polygon labels @default 'center' */\n polygonLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Polygon labels [x, y] @default [0, 10] */\n polygonLabelOffset?: [number, number];\n\n // Circle geometry options\n /** Position on Circle perimeter (top/right/bottom/left) @default 'bottom' */\n circleLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Circle labels @default 'top' */\n circleLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Circle labels @default 'center' */\n circleLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Circle labels [x, y] @default [0, 10] */\n circleLabelOffset?: [number, number];\n}\n\n/**\n * Convert vertical/horizontal position to deck.gl textAnchor and alignmentBaseline\n */\nfunction convertPositionToAnchors(\n vertical: LabelVerticalPosition,\n horizontal: LabelHorizontalPosition,\n): {\n textAnchor: 'start' | 'middle' | 'end';\n alignmentBaseline: 'top' | 'center' | 'bottom';\n} {\n // Map horizontal to textAnchor\n const textAnchorMap: Record<\n LabelHorizontalPosition,\n 'start' | 'middle' | 'end'\n > = {\n left: 'start',\n center: 'middle',\n right: 'end',\n };\n\n // Map vertical to alignmentBaseline\n const alignmentBaselineMap: Record<\n LabelVerticalPosition,\n 'top' | 'center' | 'bottom'\n > = {\n top: 'top',\n middle: 'center',\n bottom: 'bottom',\n };\n\n return {\n textAnchor: textAnchorMap[horizontal],\n alignmentBaseline: alignmentBaselineMap[vertical],\n };\n}\n\n/**\n * Helper to resolve label properties with priority: shape > options > default\n */\nfunction resolveLabelProperties(\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n defaultOffset: [number, number],\n defaultVertical: LabelVerticalPosition,\n defaultHorizontal: LabelHorizontalPosition,\n optionsOffset?: [number, number],\n optionsVertical?: LabelVerticalPosition,\n optionsHorizontal?: LabelHorizontalPosition,\n) {\n const vertical = (shapeVertical ??\n optionsVertical ??\n defaultVertical) as LabelVerticalPosition;\n const horizontal = (shapeHorizontal ??\n optionsHorizontal ??\n defaultHorizontal) as LabelHorizontalPosition;\n const pixelOffset = shapeOffset ?? optionsOffset ?? defaultOffset;\n\n const anchors = convertPositionToAnchors(vertical, horizontal);\n\n return {\n pixelOffset,\n ...anchors,\n };\n}\n\n/**\n * Get position for Point geometry\n */\nfunction getPointPosition(\n geometry: Point,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.pointLabelOffset,\n options?.pointLabelVerticalAnchor,\n options?.pointLabelHorizontalAnchor,\n );\n\n return {\n coordinates: [\n geometry.coordinates[0] ?? 0,\n geometry.coordinates[1] ?? 0,\n ] as [number, number],\n ...resolved,\n };\n}\n\n/**\n * Get position for LineString geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getLineStringPosition(\n geometry: LineString,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.lineStringLabelOffset,\n options?.lineStringLabelVerticalAnchor,\n options?.lineStringLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.lineStringLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(\n geometry.coordinates as number[][],\n coordinateAnchor,\n );\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get vertex coordinate from ring\n */\nfunction getVertexCoordinate(\n vertex: number[] | undefined,\n): [number, number] | null {\n if (!vertex || vertex[0] === undefined || vertex[1] === undefined) {\n return null;\n }\n return [vertex[0], vertex[1]];\n}\n\n/**\n * Check if a vertex should replace the current target based on edge position\n */\nfunction shouldUpdateEdgeVertex(\n vertexValue: number,\n targetValue: number,\n position: CardinalLabelCoordinateAnchor,\n): boolean {\n // For top and right, find maximum value\n // For bottom and left, find minimum value\n return position === 'top' || position === 'right'\n ? vertexValue > targetValue\n : vertexValue < targetValue;\n}\n\n/**\n * Get the coordinate index based on edge position (0 for x/longitude, 1 for y/latitude)\n */\nfunction getCoordinateIndexForEdgePosition(\n position: CardinalLabelCoordinateAnchor,\n): number {\n return position === 'top' || position === 'bottom' ? 1 : 0;\n}\n\n/**\n * Calculate the centroid (center point) of a set of coordinates\n * For polygons, this calculates the geometric center\n * For lines, this calculates the midpoint of the bounding box\n * Returns null if no valid coordinates exist\n */\nfunction calculateCentroid(coordinates: number[][]): [number, number] | null {\n if (coordinates.length === 0) {\n return null;\n }\n\n let sumX = 0;\n let sumY = 0;\n let count = 0;\n\n for (const coord of coordinates) {\n if (coord && coord[0] !== undefined && coord[1] !== undefined) {\n sumX += coord[0];\n sumY += coord[1];\n count++;\n }\n }\n\n if (count === 0) {\n return null;\n }\n\n return [sumX / count, sumY / count];\n}\n\n/**\n * Find the point on a geometry's perimeter at the specified edge position\n * @param coordinates - Array of coordinates (ring or line)\n * @param position - Edge position (center/top/right/bottom/left) relative to bounding box\n * @returns Coordinate at the specified edge position, or null if no valid coordinates\n */\nfunction findEdgePoint(\n coordinates: number[][] | undefined,\n position: CardinalLabelCoordinateAnchor,\n): [number, number] | null {\n if (!coordinates || coordinates.length === 0) {\n return null;\n }\n\n // Handle center positioning\n if (position === 'center') {\n return calculateCentroid(coordinates);\n }\n\n // Find the vertex with max/min latitude or longitude\n let targetVertex = coordinates[0];\n const coordinateIndex = getCoordinateIndexForEdgePosition(position);\n\n for (const vertex of coordinates) {\n if (!vertex) {\n continue;\n }\n if (!targetVertex) {\n continue;\n }\n\n const vertexValue = vertex[coordinateIndex];\n const targetValue = targetVertex[coordinateIndex];\n\n if (vertexValue === undefined || targetValue === undefined) {\n continue;\n }\n\n if (shouldUpdateEdgeVertex(vertexValue, targetValue, position)) {\n targetVertex = vertex;\n }\n }\n\n return getVertexCoordinate(targetVertex);\n}\n\n/**\n * Get position for Circle geometry (special case of Polygon)\n */\nfunction getCirclePosition(\n ring: number[][] | undefined,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.circleLabelOffset,\n options?.circleLabelVerticalAnchor,\n options?.circleLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.circleLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on coordinate anchor\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get position for Polygon geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getPolygonPosition(\n geometry: Polygon,\n shape: Shape,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const ring = geometry.coordinates[0];\n\n // Circle shapes use circle-specific options\n if (isCircleShape(shape)) {\n return getCirclePosition(\n ring,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n }\n\n // Regular polygons use cardinal direction positioning\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.polygonLabelOffset,\n options?.polygonLabelVerticalAnchor,\n options?.polygonLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.polygonLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get 2D position for label based on geometry type\n * Uses pixel-based offsets for consistent positioning at all zoom levels\n *\n * Priority for positioning:\n * 1. Per-shape properties in styleProperties (highest)\n * 2. Global labelOptions from layer props\n * 3. Default values (fallback)\n *\n * Returns null if no valid coordinates can be determined\n */\nexport function getLabelPosition2d(\n shape: Shape,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const { geometry } = shape.feature;\n const styleProps = shape.feature.properties?.styleProperties;\n\n // Check if shape has custom label properties\n const shapeOffset = styleProps?.labelOffset as [number, number] | undefined;\n const shapeVertical = styleProps?.labelVerticalAnchor;\n const shapeHorizontal = styleProps?.labelHorizontalAnchor;\n const shapeCoordinateAnchor = styleProps?.labelCoordinateAnchor;\n\n switch (geometry.type) {\n case 'Point':\n return getPointPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n options,\n );\n\n case 'LineString':\n return getLineStringPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n case 'Polygon':\n return getPolygonPosition(\n geometry,\n shape,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n default:\n // Unknown geometry type - return null\n return null;\n }\n}\n\n/**\n * Get label text for a shape\n *\n * Returns the display label for the shape on the map in uppercase.\n * - `label`: Optional short display name shown on the map (e.g., \"NYC\")\n * - `name`: Full shape name used internally (e.g., \"New York City Office\")\n *\n * If `label` is not provided, falls back to using `name`.\n * Text is automatically converted to uppercase for display.\n */\nexport function getLabelText(shape: Shape): string {\n return (shape.label || shape.name).toUpperCase();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"labels.js","names":["defaultOffset: [number, number]","defaultVertical: LabelVerticalPosition","defaultHorizontal: LabelHorizontalPosition","defaultCoordinateAnchor: CardinalLabelCoordinateAnchor"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/labels.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\n/**\n * Label positioning utilities for shapes\n *\n * This module provides utilities for positioning labels on shapes and calculating\n * points along line segments. Use these to customize label placement in your shape data.\n *\n * ## Label Styling\n *\n * Labels use a text-only style for legibility across different map backgrounds:\n * - **Text**: White uppercase with bold Roboto Mono font at 10px\n * - **Outline**: Black 2px outline around text for contrast\n * - **No background or border**: Clean text-only appearance\n *\n * ### Calculating Offsets\n *\n * When positioning labels, the text height is approximately 10px.\n * For example, to position a label exactly 5px above a point:\n * - Label height ≈ text height (10px)\n * - Offset needed: [0, -(10 + 5)] = [0, -15]\n *\n * @example Position label at the middle of a LineString\n * ```typescript\n * import { getLineStringMidpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * // Calculate midpoint for custom positioning\n * const coordinates = [[-122.4, 37.8], [-122.3, 37.9], [-122.2, 38.0]];\n * const midpoint = getLineStringMidpoint(coordinates);\n *\n * // Use in shape data with custom label properties\n * const shape = {\n * feature: {\n * properties: {\n * styleProperties: {\n * labelVerticalAnchor: 'top',\n * labelHorizontalAnchor: 'center',\n * labelOffset: [0, -10], // Small offset above the line\n * }\n * }\n * }\n * };\n * ```\n */\n\n'use client';\n\nimport { isCircleShape } from '../../shared/types';\nimport type { LineString, Point, Polygon } from 'geojson';\nimport type { Shape } from '../../shared/types';\n\n/**\n * Label positioning information including coordinates and screen-space offsets\n */\nexport interface LabelPosition2d {\n /** Geographic coordinates [longitude, latitude] */\n coordinates: [number, number];\n /** Horizontal text anchor point */\n textAnchor: 'start' | 'middle' | 'end';\n /** Vertical text alignment */\n alignmentBaseline: 'top' | 'center' | 'bottom';\n /** Pixel offset from coordinates [x, y] */\n pixelOffset: [number, number];\n}\n\n/**\n * Calculate a point along a line segment\n * @param start - Start coordinate [lon, lat]\n * @param end - End coordinate [lon, lat]\n * @param ratio - Position along segment (0 = start, 0.5 = middle, 1 = end)\n * @returns Interpolated coordinate [lon, lat]\n *\n * @example\n * // Get a point 25% along a line segment\n * const start: [number, number] = [-122.4, 37.8];\n * const end: [number, number] = [-122.3, 37.9];\n * const point = interpolatePoint(start, end, 0.25);\n */\nexport function interpolatePoint(\n start: [number, number],\n end: [number, number],\n ratio: number,\n): [number, number] {\n const clampedRatio = Math.max(0, Math.min(1, ratio));\n return [\n start[0] + (end[0] - start[0]) * clampedRatio,\n start[1] + (end[1] - start[1]) * clampedRatio,\n ];\n}\n\n/**\n * Calculate segment lengths for a line\n */\nfunction calculateSegmentLengths(coordinates: [number, number][]): {\n lengths: number[];\n total: number;\n} {\n let total = 0;\n const lengths: number[] = [];\n\n for (let i = 0; i < coordinates.length - 1; i++) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const length = Math.sqrt(\n (end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2,\n );\n lengths.push(length);\n total += length;\n }\n }\n\n return { lengths, total };\n}\n\n/**\n * Find point at specific distance along line\n */\nfunction findPointAtDistance(\n coordinates: [number, number][],\n segmentLengths: number[],\n targetDistance: number,\n): [number, number] {\n let accumulatedLength = 0;\n\n for (let i = 0; i < segmentLengths.length; i++) {\n const segmentLength = segmentLengths[i];\n if (!segmentLength) {\n continue;\n }\n\n if (accumulatedLength + segmentLength >= targetDistance) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const ratio = (targetDistance - accumulatedLength) / segmentLength;\n return interpolatePoint(start, end, ratio);\n }\n }\n accumulatedLength += segmentLength;\n }\n\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a LineString\n * @param coordinates - LineString coordinates array\n * @returns Midpoint coordinate [lon, lat]\n */\nexport function getLineStringMidpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n if (coordinates.length === 1) {\n return coordinates[0] ?? [0, 0];\n }\n\n const { lengths, total } = calculateSegmentLengths(coordinates);\n const halfLength = total / 2;\n\n return findPointAtDistance(coordinates, lengths, halfLength);\n}\n\n/**\n * Get the end point of a LineString\n * @param coordinates - LineString coordinates array\n * @returns End coordinate [lon, lat]\n */\nexport function getLineStringEndpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a Polygon's outer ring\n * @param coordinates - Polygon coordinates array (rings)\n * @returns Midpoint of outer ring [lon, lat]\n */\nexport function getPolygonMidpoint(\n coordinates: [number, number][][],\n): [number, number] {\n const outerRing = coordinates[0];\n if (!outerRing || outerRing.length === 0) {\n return [0, 0];\n }\n // Use the outer ring (first ring)\n return getLineStringMidpoint(outerRing);\n}\n\n/**\n * Vertical label position relative to anchor point\n */\nexport type LabelVerticalPosition = 'top' | 'middle' | 'bottom';\n\n/**\n * Horizontal label position relative to anchor point\n */\nexport type LabelHorizontalPosition = 'left' | 'center' | 'right';\n\n/**\n * Cardinal direction anchor for positioning labels on geometry edges\n * Uses edge positions relative to the geometry's bounding box\n * Works for LineString, Polygon, and Circle geometries\n * - 'center': centroid/midpoint of the geometry\n * - 'top'/'right'/'bottom'/'left': edge positions\n */\nexport type CardinalLabelCoordinateAnchor =\n | 'center'\n | 'top'\n | 'right'\n | 'bottom'\n | 'left';\n\n/**\n * Global label positioning options for DisplayShapeLayer\n *\n * ## Priority System\n * Label positioning follows a three-tier priority system:\n * 1. **Per-shape properties** in `styleProperties` (highest priority)\n * 2. **Global options** via this interface\n * 3. **Default values** (geometry-specific fallbacks)\n *\n * ## Label Appearance\n *\n * Labels use a clean text-only style:\n * - **Text**: White uppercase, bold Roboto Mono, 10px\n * - **Outline**: Black 2px outline for contrast\n * - **No background or border**\n *\n * Text height ≈ 10px\n *\n * ## Positioning Concepts\n *\n * ### Coordinate Anchor\n * Determines *where* on the geometry to place the label:\n * - **Point**: Label is always at the point coordinate\n * - **LineString/Polygon**: 'start', 'middle', or 'end' along the geometry\n * - **Circle**: 'top', 'right', 'bottom', or 'left' on the perimeter\n *\n * ### Vertical/Horizontal Anchor\n * Determines how the label aligns *relative to* the anchor point:\n * - **Vertical**: 'top' (label text below point), 'middle' (centered), 'bottom' (label text above point)\n * - **Horizontal**: 'left' (label text right of point), 'center' (centered), 'right' (label text left of point)\n *\n * ### Pixel Offset\n * Fine-tune label position with [x, y] pixel offsets:\n * - Positive x moves right, negative moves left\n * - Positive y moves down, negative moves up\n *\n * @example Position circle labels at the top with 5px clearance\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom', // Label above the top point\n * circleLabelOffset: [0, -15], // -(10px text height + 5px clearance)\n * };\n * ```\n *\n * @example Position line labels at middle with offset to the right\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * lineStringLabelCoordinateAnchor: 'middle',\n * lineStringLabelHorizontalAnchor: 'left',\n * lineStringLabelOffset: [10, 0], // 10px to the right\n * };\n * ```\n */\nexport interface LabelPositionOptions {\n // Point geometry options\n /** Vertical anchor for Point labels @default 'top' */\n pointLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Point labels @default 'center' */\n pointLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Point labels [x, y] @default [0, 10] */\n pointLabelOffset?: [number, number];\n\n // LineString geometry options\n /** Position on LineString edge (top/right/bottom/left) @default 'bottom' */\n lineStringLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for LineString labels @default 'top' */\n lineStringLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for LineString labels @default 'center' */\n lineStringLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for LineString labels [x, y] @default [0, 10] */\n lineStringLabelOffset?: [number, number];\n\n // Polygon geometry options\n /** Position on Polygon edge (top/right/bottom/left) @default 'bottom' */\n polygonLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Polygon labels @default 'top' */\n polygonLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Polygon labels @default 'center' */\n polygonLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Polygon labels [x, y] @default [0, 10] */\n polygonLabelOffset?: [number, number];\n\n // Circle geometry options\n /** Position on Circle perimeter (top/right/bottom/left) @default 'bottom' */\n circleLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Circle labels @default 'top' */\n circleLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Circle labels @default 'center' */\n circleLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Circle labels [x, y] @default [0, 10] */\n circleLabelOffset?: [number, number];\n}\n\n/**\n * Convert vertical/horizontal position to deck.gl textAnchor and alignmentBaseline\n */\nfunction convertPositionToAnchors(\n vertical: LabelVerticalPosition,\n horizontal: LabelHorizontalPosition,\n): {\n textAnchor: 'start' | 'middle' | 'end';\n alignmentBaseline: 'top' | 'center' | 'bottom';\n} {\n // Map horizontal to textAnchor\n const textAnchorMap: Record<\n LabelHorizontalPosition,\n 'start' | 'middle' | 'end'\n > = {\n left: 'start',\n center: 'middle',\n right: 'end',\n };\n\n // Map vertical to alignmentBaseline\n const alignmentBaselineMap: Record<\n LabelVerticalPosition,\n 'top' | 'center' | 'bottom'\n > = {\n top: 'top',\n middle: 'center',\n bottom: 'bottom',\n };\n\n return {\n textAnchor: textAnchorMap[horizontal],\n alignmentBaseline: alignmentBaselineMap[vertical],\n };\n}\n\n/**\n * Helper to resolve label properties with priority: shape > options > default\n */\nfunction resolveLabelProperties(\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n defaultOffset: [number, number],\n defaultVertical: LabelVerticalPosition,\n defaultHorizontal: LabelHorizontalPosition,\n optionsOffset?: [number, number],\n optionsVertical?: LabelVerticalPosition,\n optionsHorizontal?: LabelHorizontalPosition,\n) {\n const vertical = (shapeVertical ??\n optionsVertical ??\n defaultVertical) as LabelVerticalPosition;\n const horizontal = (shapeHorizontal ??\n optionsHorizontal ??\n defaultHorizontal) as LabelHorizontalPosition;\n const pixelOffset = shapeOffset ?? optionsOffset ?? defaultOffset;\n\n const anchors = convertPositionToAnchors(vertical, horizontal);\n\n return {\n pixelOffset,\n ...anchors,\n };\n}\n\n/**\n * Get position for Point geometry\n */\nfunction getPointPosition(\n geometry: Point,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.pointLabelOffset,\n options?.pointLabelVerticalAnchor,\n options?.pointLabelHorizontalAnchor,\n );\n\n return {\n coordinates: [\n geometry.coordinates[0] ?? 0,\n geometry.coordinates[1] ?? 0,\n ] as [number, number],\n ...resolved,\n };\n}\n\n/**\n * Get position for LineString geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getLineStringPosition(\n geometry: LineString,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.lineStringLabelOffset,\n options?.lineStringLabelVerticalAnchor,\n options?.lineStringLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.lineStringLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(\n geometry.coordinates as number[][],\n coordinateAnchor,\n );\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get vertex coordinate from ring\n */\nfunction getVertexCoordinate(\n vertex: number[] | undefined,\n): [number, number] | null {\n if (!vertex || vertex[0] === undefined || vertex[1] === undefined) {\n return null;\n }\n return [vertex[0], vertex[1]];\n}\n\n/**\n * Check if a vertex should replace the current target based on edge position\n */\nfunction shouldUpdateEdgeVertex(\n vertexValue: number,\n targetValue: number,\n position: CardinalLabelCoordinateAnchor,\n): boolean {\n // For top and right, find maximum value\n // For bottom and left, find minimum value\n return position === 'top' || position === 'right'\n ? vertexValue > targetValue\n : vertexValue < targetValue;\n}\n\n/**\n * Get the coordinate index based on edge position (0 for x/longitude, 1 for y/latitude)\n */\nfunction getCoordinateIndexForEdgePosition(\n position: CardinalLabelCoordinateAnchor,\n): number {\n return position === 'top' || position === 'bottom' ? 1 : 0;\n}\n\n/**\n * Calculate the centroid (center point) of a set of coordinates\n * For polygons, this calculates the geometric center\n * For lines, this calculates the midpoint of the bounding box\n * Returns null if no valid coordinates exist\n */\nfunction calculateCentroid(coordinates: number[][]): [number, number] | null {\n if (coordinates.length === 0) {\n return null;\n }\n\n let sumX = 0;\n let sumY = 0;\n let count = 0;\n\n for (const coord of coordinates) {\n if (coord && coord[0] !== undefined && coord[1] !== undefined) {\n sumX += coord[0];\n sumY += coord[1];\n count++;\n }\n }\n\n if (count === 0) {\n return null;\n }\n\n return [sumX / count, sumY / count];\n}\n\n/**\n * Find the point on a geometry's perimeter at the specified edge position\n * @param coordinates - Array of coordinates (ring or line)\n * @param position - Edge position (center/top/right/bottom/left) relative to bounding box\n * @returns Coordinate at the specified edge position, or null if no valid coordinates\n */\nfunction findEdgePoint(\n coordinates: number[][] | undefined,\n position: CardinalLabelCoordinateAnchor,\n): [number, number] | null {\n if (!coordinates || coordinates.length === 0) {\n return null;\n }\n\n // Handle center positioning\n if (position === 'center') {\n return calculateCentroid(coordinates);\n }\n\n // Find the vertex with max/min latitude or longitude\n let targetVertex = coordinates[0];\n const coordinateIndex = getCoordinateIndexForEdgePosition(position);\n\n for (const vertex of coordinates) {\n if (!vertex) {\n continue;\n }\n if (!targetVertex) {\n continue;\n }\n\n const vertexValue = vertex[coordinateIndex];\n const targetValue = targetVertex[coordinateIndex];\n\n if (vertexValue === undefined || targetValue === undefined) {\n continue;\n }\n\n if (shouldUpdateEdgeVertex(vertexValue, targetValue, position)) {\n targetVertex = vertex;\n }\n }\n\n return getVertexCoordinate(targetVertex);\n}\n\n/**\n * Get position for Circle geometry (special case of Polygon)\n */\nfunction getCirclePosition(\n ring: number[][] | undefined,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.circleLabelOffset,\n options?.circleLabelVerticalAnchor,\n options?.circleLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.circleLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on coordinate anchor\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get position for Polygon geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getPolygonPosition(\n geometry: Polygon,\n shape: Shape,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const ring = geometry.coordinates[0];\n\n // Circle shapes use circle-specific options\n if (isCircleShape(shape)) {\n return getCirclePosition(\n ring,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n }\n\n // Regular polygons use cardinal direction positioning\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.polygonLabelOffset,\n options?.polygonLabelVerticalAnchor,\n options?.polygonLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.polygonLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get 2D position for label based on geometry type\n * Uses pixel-based offsets for consistent positioning at all zoom levels\n *\n * Priority for positioning:\n * 1. Per-shape properties in styleProperties (highest)\n * 2. Global labelOptions from layer props\n * 3. Default values (fallback)\n *\n * Returns null if no valid coordinates can be determined\n */\nexport function getLabelPosition2d(\n shape: Shape,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const { geometry } = shape.feature;\n const styleProps = shape.feature.properties?.styleProperties;\n\n // Check if shape has custom label properties\n const shapeOffset = styleProps?.labelOffset as [number, number] | undefined;\n const shapeVertical = styleProps?.labelVerticalAnchor;\n const shapeHorizontal = styleProps?.labelHorizontalAnchor;\n const shapeCoordinateAnchor = styleProps?.labelCoordinateAnchor;\n\n switch (geometry.type) {\n case 'Point':\n return getPointPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n options,\n );\n\n case 'LineString':\n return getLineStringPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n case 'Polygon':\n return getPolygonPosition(\n geometry,\n shape,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n default:\n // Unknown geometry type - return null\n return null;\n }\n}\n\n/**\n * Get label text for a shape\n *\n * Returns the display label for the shape on the map in uppercase.\n * - `label`: Optional short display name shown on the map (e.g., \"NYC\")\n * - `name`: Full shape name used internally (e.g., \"New York City Office\")\n *\n * If `label` is not provided, falls back to using `name`.\n * Text is automatically converted to uppercase for display.\n */\nexport function getLabelText(shape: Shape): string {\n return (shape.label || shape.name).toUpperCase();\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":["DRAW_CURSOR: CSSCursorType","DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType>"],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/constants.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { ShapeFeatureType } from '../shared/types';\nimport type { CSSCursorType } from '@/map-cursor/types';\n\n/**\n * Mode name for the map-mode integration\n */\nexport const DRAW_SHAPE_MODE = 'draw-shape';\n\n/**\n * Identifier for the draw shape layer.\n * Used as the owner for map-mode/cursor and as the default layer ID.\n */\nexport const DRAW_SHAPE_LAYER_ID = 'draw-shape-layer';\n\n/**\n * Cursor type to use when drawing shapes\n */\nexport const DRAW_CURSOR: CSSCursorType = 'crosshair';\n\n/**\n * Cursor mapping for each shape type (all use crosshair)\n */\nexport const DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType> = {\n [ShapeFeatureType.Point]: DRAW_CURSOR,\n [ShapeFeatureType.LineString]: DRAW_CURSOR,\n [ShapeFeatureType.Polygon]: DRAW_CURSOR,\n [ShapeFeatureType.Rectangle]: DRAW_CURSOR,\n [ShapeFeatureType.Circle]: DRAW_CURSOR,\n [ShapeFeatureType.Ellipse]: DRAW_CURSOR,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB;;;;;AAM/B,MAAa,sBAAsB;;;;AAKnC,MAAaA,cAA6B;;;;AAK1C,MAAaC,kBAA2D;EACrE,iBAAiB,QAAQ;EACzB,iBAAiB,aAAa;EAC9B,iBAAiB,UAAU;EAC3B,iBAAiB,YAAY;EAC7B,iBAAiB,SAAS;EAC1B,iBAAiB,UAAU;CAC7B"}
1
+ {"version":3,"file":"constants.js","names":["DRAW_CURSOR: CSSCursorType","DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType>"],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/constants.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\n'use client';\n\nimport { ShapeFeatureType } from '../shared/types';\nimport type { CSSCursorType } from '@/map-cursor/types';\n\n/**\n * Mode name for the map-mode integration\n */\nexport const DRAW_SHAPE_MODE = 'draw-shape';\n\n/**\n * Identifier for the draw shape layer.\n * Used as the owner for map-mode/cursor and as the default layer ID.\n */\nexport const DRAW_SHAPE_LAYER_ID = 'draw-shape-layer';\n\n/**\n * Cursor type to use when drawing shapes\n */\nexport const DRAW_CURSOR: CSSCursorType = 'crosshair';\n\n/**\n * Cursor mapping for each shape type (all use crosshair)\n */\nexport const DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType> = {\n [ShapeFeatureType.Point]: DRAW_CURSOR,\n [ShapeFeatureType.LineString]: DRAW_CURSOR,\n [ShapeFeatureType.Polygon]: DRAW_CURSOR,\n [ShapeFeatureType.Rectangle]: DRAW_CURSOR,\n [ShapeFeatureType.Circle]: DRAW_CURSOR,\n [ShapeFeatureType.Ellipse]: DRAW_CURSOR,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB;;;;;AAM/B,MAAa,sBAAsB;;;;AAKnC,MAAaA,cAA6B;;;;AAK1C,MAAaC,kBAA2D;EACrE,iBAAiB,QAAQ;EACzB,iBAAiB,aAAa;EAC9B,iBAAiB,UAAU;EAC3B,iBAAiB,YAAY;EAC7B,iBAAiB,SAAS;EAC1B,iBAAiB,UAAU;CAC7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/events.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Draw Shape Events\n *\n * Note on event payload structure:\n * These events define explicit payload types rather than using the `Payload<T, P>` helper\n * from @accelint/bus. This is because the `Shape` type contains GeoJSON `Feature` objects\n * from the `geojson` package, which don't satisfy TypeScript's `StructuredCloneable` type\n * constraint used by the bus.\n *\n * The issue: `StructuredCloneable` (from type-fest) requires objects to have an index\n * signature `[key: string]: StructuredCloneable`, but GeoJSON interfaces define strict\n * property types without index signatures. At runtime, GeoJSON data IS structurally\n * cloneable (can be passed through postMessage, stored in IndexedDB, etc.), but\n * TypeScript can't verify this statically.\n *\n * Events that only contain primitive values (like ShapeId, mapId) can use the `Payload`\n * helper directly - see shared/events.ts for examples.\n *\n * When emitting these events via the bus, use type assertions:\n * @example\n * ```ts\n * bus.emit('shapes:drawn', {\n * type: 'shapes:drawn',\n * payload: { shape, mapId },\n * source: componentId,\n * } as unknown as Payload);\n * ```\n */\n\n'use client';\n\nimport type { UniqueId } from '@accelint/core';\nimport type { Shape, ShapeFeatureType } from '../shared/types';\n\n/**\n * Drawing lifecycle events\n */\nexport const DrawShapeEvents = {\n /** Drawing has started for a shape type */\n drawing: 'shapes:drawing',\n /** Shape has been successfully drawn/created */\n drawn: 'shapes:drawn',\n /** Drawing was canceled */\n canceled: 'shapes:draw-canceled',\n} as const;\n\nexport type DrawShapeEventType =\n (typeof DrawShapeEvents)[keyof typeof DrawShapeEvents];\n\n/**\n * Payload for shapes:drawing event.\n */\nexport type ShapeDrawingPayload = {\n /** The shape type being drawn */\n shapeType: ShapeFeatureType;\n /** Map instance ID for multi-map event isolation */\n mapId: UniqueId;\n};\n\n/**\n * Event payload for shapes:drawing\n * Emitted when drawing starts\n */\nexport type ShapeDrawingEvent = {\n type: 'shapes:drawing';\n payload: ShapeDrawingPayload;\n source: UniqueId;\n target?: UniqueId;\n};\n\n/**\n * Payload for shapes:drawn event.\n */\nexport type ShapeDrawnPayload = {\n /** The completed shape */\n shape: Shape;\n /** Map instance ID for multi-map event isolation */\n mapId: UniqueId;\n};\n\n/**\n * Event payload for shapes:drawn\n * Emitted when a shape is successfully created\n */\nexport type ShapeDrawnEvent = {\n type: 'shapes:drawn';\n payload: ShapeDrawnPayload;\n source: UniqueId;\n target?: UniqueId;\n};\n\n/**\n * Payload for shapes:draw-canceled event.\n */\nexport type ShapeDrawCanceledPayload = {\n /** The shape type that was being drawn */\n shapeType: ShapeFeatureType;\n /** Map instance ID for multi-map event isolation */\n mapId: UniqueId;\n};\n\n/**\n * Event payload for shapes:draw-canceled\n * Emitted when drawing is canceled\n */\nexport type ShapeDrawCanceledEvent = {\n type: 'shapes:draw-canceled';\n payload: ShapeDrawCanceledPayload;\n source: UniqueId;\n target?: UniqueId;\n};\n\n/**\n * Union of all draw shape event types\n */\nexport type DrawShapeEvent =\n | ShapeDrawingEvent\n | ShapeDrawnEvent\n | ShapeDrawCanceledEvent;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAa,kBAAkB;CAE7B,SAAS;CAET,OAAO;CAEP,UAAU;CACX"}
1
+ {"version":3,"file":"events.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/events.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\n/**\n * Draw Shape Events\n *\n * Note on event payload structure:\n * These events define explicit payload types rather than using the `Payload<T, P>` helper\n * from @accelint/bus. This is because the `Shape` type contains GeoJSON `Feature` objects\n * from the `geojson` package, which don't satisfy TypeScript's `StructuredCloneable` type\n * constraint used by the bus.\n *\n * The issue: `StructuredCloneable` (from type-fest) requires objects to have an index\n * signature `[key: string]: StructuredCloneable`, but GeoJSON interfaces define strict\n * property types without index signatures. At runtime, GeoJSON data IS structurally\n * cloneable (can be passed through postMessage, stored in IndexedDB, etc.), but\n * TypeScript can't verify this statically.\n *\n * Events that only contain primitive values (like ShapeId, mapId) can use the `Payload`\n * helper directly - see shared/events.ts for examples.\n *\n * When emitting these events via the bus, use type assertions:\n * @example\n * ```ts\n * bus.emit('shapes:drawn', {\n * type: 'shapes:drawn',\n * payload: { shape, mapId },\n * source: componentId,\n * } as unknown as Payload);\n * ```\n */\n\n'use client';\n\nimport type { UniqueId } from '@accelint/core';\nimport type { Shape, ShapeFeatureType } from '../shared/types';\n\n/**\n * Drawing lifecycle events\n */\nexport const DrawShapeEvents = {\n /** Drawing has started for a shape type */\n drawing: 'shapes:drawing',\n /** Shape has been successfully drawn/created */\n drawn: 'shapes:drawn',\n /** Drawing was canceled */\n canceled: 'shapes:draw-canceled',\n} as const;\n\nexport type DrawShapeEventType =\n (typeof DrawShapeEvents)[keyof typeof DrawShapeEvents];\n\n/**\n * Payload for shapes:drawing event.\n */\nexport type ShapeDrawingPayload = {\n /** The shape type being drawn */\n shapeType: ShapeFeatureType;\n /** Map instance ID for multi-map event isolation */\n mapId: UniqueId;\n};\n\n/**\n * Event payload for shapes:drawing\n * Emitted when drawing starts\n */\nexport type ShapeDrawingEvent = {\n type: 'shapes:drawing';\n payload: ShapeDrawingPayload;\n source: UniqueId;\n target?: UniqueId;\n};\n\n/**\n * Payload for shapes:drawn event.\n */\nexport type ShapeDrawnPayload = {\n /** The completed shape */\n shape: Shape;\n /** Map instance ID for multi-map event isolation */\n mapId: UniqueId;\n};\n\n/**\n * Event payload for shapes:drawn\n * Emitted when a shape is successfully created\n */\nexport type ShapeDrawnEvent = {\n type: 'shapes:drawn';\n payload: ShapeDrawnPayload;\n source: UniqueId;\n target?: UniqueId;\n};\n\n/**\n * Payload for shapes:draw-canceled event.\n */\nexport type ShapeDrawCanceledPayload = {\n /** The shape type that was being drawn */\n shapeType: ShapeFeatureType;\n /** Map instance ID for multi-map event isolation */\n mapId: UniqueId;\n};\n\n/**\n * Event payload for shapes:draw-canceled\n * Emitted when drawing is canceled\n */\nexport type ShapeDrawCanceledEvent = {\n type: 'shapes:draw-canceled';\n payload: ShapeDrawCanceledPayload;\n source: UniqueId;\n target?: UniqueId;\n};\n\n/**\n * Union of all draw shape event types\n */\nexport type DrawShapeEvent =\n | ShapeDrawingEvent\n | ShapeDrawnEvent\n | ShapeDrawCanceledEvent;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAa,kBAAkB;CAE7B,SAAS;CAET,OAAO;CAEP,UAAU;CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"fiber.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/fiber.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\nimport { extend } from '@deckgl-fiber-renderer/dom';\n\n// Extend the fiber renderer with EditableGeoJsonLayer\nextend({ EditableGeoJsonLayer });\n\n// Note: DrawShapeLayer is a React component, not a deck.gl layer class,\n// so it doesn't need fiber registration. It uses <editableGeoJsonLayer>\n// internally which is registered above.\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n // biome-ignore lint/suspicious/noExplicitAny: EditableGeoJsonLayer props are complex and vary by mode\n editableGeoJsonLayer: any;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,OAAO,EAAE,sBAAsB,CAAC"}
1
+ {"version":3,"file":"fiber.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/fiber.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 { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\nimport { extend } from '@deckgl-fiber-renderer/dom';\n\n// Extend the fiber renderer with EditableGeoJsonLayer\nextend({ EditableGeoJsonLayer });\n\n// Note: DrawShapeLayer is a React component, not a deck.gl layer class,\n// so it doesn't need fiber registration. It uses <editableGeoJsonLayer>\n// internally which is registered above.\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n // biome-ignore lint/suspicious/noExplicitAny: EditableGeoJsonLayer props are complex and vary by mode\n editableGeoJsonLayer: any;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,OAAO,EAAE,sBAAsB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/index.tsx"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { useContext, useEffect } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport {\n DEFAULT_TENTATIVE_COLORS,\n EMPTY_FEATURE_COLLECTION,\n} from '../shared/constants';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { DRAW_SHAPE_LAYER_ID } from './constants';\nimport { getModeInstance, triggerDoubleClickFinish } from './modes';\nimport {\n cancelDrawingFromLayer,\n completeDrawingFromLayer,\n drawStore,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { DrawShapeLayerProps } from './types';\n\n/**\n * DrawShapeLayer - A React component for drawing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively drawing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the drawing store for state management\n * - Protected drawing mode (rejects mode change requests while drawing)\n * - Distance/area tooltips during drawing\n *\n * @example\n * ```tsx\n * // Import the fiber registration for JSX support\n * import '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/fiber';\n *\n * function Map({ mapId }) {\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer data={shapes} mapId={mapId} />\n * <DrawShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n */\nexport function DrawShapeLayer({\n id = DRAW_SHAPE_LAYER_ID,\n mapId,\n unit,\n}: DrawShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'DrawShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to drawing state using the v2 store API\n const { state: drawingState } = drawStore.use(actualMapId);\n\n const activeShapeType = drawingState?.activeShapeType ?? null;\n\n // Disable zoom while Shift is held during rectangle drawing\n // This prevents boxZoom (Shift+drag) from interfering with Shift-to-square constraint\n useShiftZoomDisable(actualMapId, activeShapeType === 'Rectangle');\n\n // Set up dblclick listener as workaround for deck.gl-community/editable-layers ~9.1\n // which doesn't register 'dblclick' in EVENT_TYPES\n // @see https://github.com/visgl/deck.gl-community/pull/225\n // TODO: Remove this workaround when @deck.gl-community/editable-layers 9.2.0 is released\n useEffect(() => {\n if (!activeShapeType) {\n return;\n }\n\n const handleDblClick = () => {\n triggerDoubleClickFinish(activeShapeType);\n };\n\n // Add listener to document to catch dblclick anywhere on the map\n document.addEventListener('dblclick', handleDblClick);\n\n return () => {\n document.removeEventListener('dblclick', handleDblClick);\n };\n }, [activeShapeType]);\n\n // If not drawing, return null (don't render the editable layer)\n if (!activeShapeType) {\n return null;\n }\n\n const styleDefaults = drawingState?.styleDefaults ?? null;\n\n // Get the cached mode instance\n const mode = getModeInstance(activeShapeType);\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n // Only process addFeature (drawing complete) and cancelFeature (ESC pressed)\n if (editType === 'addFeature') {\n const feature = updatedData.features[updatedData.features.length - 1];\n if (feature) {\n // Type assertion: editable-layers Feature type differs slightly from geojson Feature\n // but they are structurally compatible for our conversion purposes\n completeDrawingFromLayer(\n actualMapId,\n feature as Parameters<typeof completeDrawingFromLayer>[1],\n );\n }\n } else if (editType === 'cancelFeature') {\n cancelDrawingFromLayer(actualMapId);\n }\n // Ignore other edit types during drawing (tentative updates, etc.)\n };\n\n // Get colors from style defaults or use tentative defaults\n const fillColor = styleDefaults?.fillColor ?? DEFAULT_TENTATIVE_COLORS.fill;\n const lineColor = styleDefaults?.lineColor ?? DEFAULT_TENTATIVE_COLORS.line;\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={EMPTY_FEATURE_COLLECTION}\n mode={mode}\n selectedFeatureIndexes={[]}\n onEdit={handleEdit}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...getDefaultEditableLayerProps(unit)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,QACsB;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;CAE1D,MAAM,kBAAkB,cAAc,mBAAmB;AAIzD,qBAAoB,aAAa,oBAAoB,YAAY;AAMjE,iBAAgB;AACd,MAAI,CAAC,gBACH;EAGF,MAAM,uBAAuB;AAC3B,4BAAyB,gBAAgB;;AAI3C,WAAS,iBAAiB,YAAY,eAAe;AAErD,eAAa;AACX,YAAS,oBAAoB,YAAY,eAAe;;IAEzD,CAAC,gBAAgB,CAAC;AAGrB,KAAI,CAAC,gBACH,QAAO;CAGT,MAAM,gBAAgB,cAAc,iBAAiB;CAGrD,MAAM,OAAO,gBAAgB,gBAAgB;CAG7C,MAAM,cAAc,EAClB,aACA,eACmC;AAEnC,MAAI,aAAa,cAAc;GAC7B,MAAM,UAAU,YAAY,SAAS,YAAY,SAAS,SAAS;AACnE,OAAI,QAGF,0BACE,aACA,QACD;aAEM,aAAa,gBACtB,wBAAuB,YAAY;;AASvC,QACE,oBAAC;EACK;EACJ,MAAM;EACA;EACN,wBAAwB,EAAE;EAC1B,QAAQ;EACR,uBAVc,eAAe,aAAa,yBAAyB;EAWnE,uBAVc,eAAe,aAAa,yBAAyB;EAWnE,GAAI,6BAA6B,KAAK;GACtC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/index.tsx"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { useContext, useEffect } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport {\n DEFAULT_TENTATIVE_COLORS,\n EMPTY_FEATURE_COLLECTION,\n} from '../shared/constants';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { DRAW_SHAPE_LAYER_ID } from './constants';\nimport { getModeInstance, triggerDoubleClickFinish } from './modes';\nimport {\n cancelDrawingFromLayer,\n completeDrawingFromLayer,\n drawStore,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { DrawShapeLayerProps } from './types';\n\n/**\n * DrawShapeLayer - A React component for drawing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively drawing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the drawing store for state management\n * - Protected drawing mode (rejects mode change requests while drawing)\n * - Distance/area tooltips during drawing\n *\n * @example\n * ```tsx\n * // Import the fiber registration for JSX support\n * import '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/fiber';\n *\n * function Map({ mapId }) {\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer data={shapes} mapId={mapId} />\n * <DrawShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n */\nexport function DrawShapeLayer({\n id = DRAW_SHAPE_LAYER_ID,\n mapId,\n unit,\n}: DrawShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'DrawShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to drawing state using the v2 store API\n const { state: drawingState } = drawStore.use(actualMapId);\n\n const activeShapeType = drawingState?.activeShapeType ?? null;\n\n // Disable zoom while Shift is held during rectangle drawing\n // This prevents boxZoom (Shift+drag) from interfering with Shift-to-square constraint\n useShiftZoomDisable(actualMapId, activeShapeType === 'Rectangle');\n\n // Set up dblclick listener as workaround for deck.gl-community/editable-layers ~9.1\n // which doesn't register 'dblclick' in EVENT_TYPES\n // @see https://github.com/visgl/deck.gl-community/pull/225\n // TODO: Remove this workaround when @deck.gl-community/editable-layers 9.2.0 is released\n useEffect(() => {\n if (!activeShapeType) {\n return;\n }\n\n const handleDblClick = () => {\n triggerDoubleClickFinish(activeShapeType);\n };\n\n // Add listener to document to catch dblclick anywhere on the map\n document.addEventListener('dblclick', handleDblClick);\n\n return () => {\n document.removeEventListener('dblclick', handleDblClick);\n };\n }, [activeShapeType]);\n\n // If not drawing, return null (don't render the editable layer)\n if (!activeShapeType) {\n return null;\n }\n\n const styleDefaults = drawingState?.styleDefaults ?? null;\n\n // Get the cached mode instance\n const mode = getModeInstance(activeShapeType);\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n // Only process addFeature (drawing complete) and cancelFeature (ESC pressed)\n if (editType === 'addFeature') {\n const feature = updatedData.features[updatedData.features.length - 1];\n if (feature) {\n // Type assertion: editable-layers Feature type differs slightly from geojson Feature\n // but they are structurally compatible for our conversion purposes\n completeDrawingFromLayer(\n actualMapId,\n feature as Parameters<typeof completeDrawingFromLayer>[1],\n );\n }\n } else if (editType === 'cancelFeature') {\n cancelDrawingFromLayer(actualMapId);\n }\n // Ignore other edit types during drawing (tentative updates, etc.)\n };\n\n // Get colors from style defaults or use tentative defaults\n const fillColor = styleDefaults?.fillColor ?? DEFAULT_TENTATIVE_COLORS.fill;\n const lineColor = styleDefaults?.lineColor ?? DEFAULT_TENTATIVE_COLORS.line;\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={EMPTY_FEATURE_COLLECTION}\n mode={mode}\n selectedFeatureIndexes={[]}\n onEdit={handleEdit}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...getDefaultEditableLayerProps(unit)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,QACsB;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;CAE1D,MAAM,kBAAkB,cAAc,mBAAmB;AAIzD,qBAAoB,aAAa,oBAAoB,YAAY;AAMjE,iBAAgB;AACd,MAAI,CAAC,gBACH;EAGF,MAAM,uBAAuB;AAC3B,4BAAyB,gBAAgB;;AAI3C,WAAS,iBAAiB,YAAY,eAAe;AAErD,eAAa;AACX,YAAS,oBAAoB,YAAY,eAAe;;IAEzD,CAAC,gBAAgB,CAAC;AAGrB,KAAI,CAAC,gBACH,QAAO;CAGT,MAAM,gBAAgB,cAAc,iBAAiB;CAGrD,MAAM,OAAO,gBAAgB,gBAAgB;CAG7C,MAAM,cAAc,EAClB,aACA,eACmC;AAEnC,MAAI,aAAa,cAAc;GAC7B,MAAM,UAAU,YAAY,SAAS,YAAY,SAAS,SAAS;AACnE,OAAI,QAGF,0BACE,aACA,QACD;aAEM,aAAa,gBACtB,wBAAuB,YAAY;;AASvC,QACE,oBAAC;EACK;EACJ,MAAM;EACA;EACN,wBAAwB,EAAE;EAC1B,QAAQ;EACR,uBAVc,eAAe,aAAa,yBAAyB;EAWnE,uBAVc,eAAe,aAAa,yBAAyB;EAWnE,GAAI,6BAA6B,KAAK;GACtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"draw-ellipse-mode-with-tooltip.js","names":["center: [number, number]"],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawEllipseUsingThreePointsMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatDistanceTooltip,\n formatEllipseTooltip,\n} from '../../shared/constants';\n\n/**\n * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.\n *\n * Drawing process (3 clicks):\n * 1. First click: Sets the first endpoint of the major axis\n * 2. Second click: Sets the second endpoint of the major axis\n * - While moving to second click, shows distance tooltip (like line/polygon)\n * 3. Third click: Sets the minor axis radius\n * - While moving to third click, shows area tooltip (like circle)\n *\n * The ellipse center is the midpoint between the first two clicks.\n * The Y semi-axis is half the distance between clicks 1 and 2.\n * The X semi-axis is the distance from center to click 3.\n */\nexport class DrawEllipseModeWithTooltip extends DrawEllipseUsingThreePointsMode {\n private tooltip: Tooltip | null = null;\n\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n const tooltipPosition = mapCoords;\n\n if (clickSequence.length === 1) {\n // First segment: show distance from first click to cursor (like line/polygon)\n const firstPoint = clickSequence[0] as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(firstPoint, currentPoint, { units: distanceUnits });\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n } else if (clickSequence.length === 2) {\n // Second segment: show dimensions and area (like rectangle)\n // The ellipse will have:\n // - center at midpoint of click1 and click2\n // - ySemiAxis = distance(click1, click2) / 2\n // - xSemiAxis = distance(center, cursor)\n const click1 = clickSequence[0] as [number, number];\n const click2 = clickSequence[1] as [number, number];\n const cursorPos = mapCoords as [number, number];\n\n // Calculate center (midpoint)\n const center: [number, number] = [\n (click1[0] + click2[0]) / 2,\n (click1[1] + click2[1]) / 2,\n ];\n\n // Calculate semi-axes\n const ySemiAxis = distance(click1, click2, { units: distanceUnits }) / 2;\n const xSemiAxis = distance(center, cursorPos, { units: distanceUnits });\n\n // Full axes (diameter equivalent)\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n\n // Ellipse area = π × a × b\n const ellipseArea = Math.PI * xSemiAxis * ySemiAxis;\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n ),\n };\n }\n }\n\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,6BAAb,cAAgD,gCAAgC;CAC9E,AAAQ,UAA0B;CAElC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EACrC,MAAM,aAAa,4BAA4B,cAAc;EAC7D,MAAM,kBAAkB;AAExB,MAAI,cAAc,WAAW,GAAG;GAE9B,MAAM,aAAa,cAAc;AAKjC,QAAK,UAAU;IACb,UAAU;IACV,MAAM,sBAJK,SAAS,YAFD,WAE2B,EAAE,OAAO,eAAe,CAAC,EAIrC,WAAW;IAC9C;aACQ,cAAc,WAAW,GAAG;GAMrC,MAAM,SAAS,cAAc;GAC7B,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY;GAGlB,MAAMA,SAA2B,EAC9B,OAAO,KAAK,OAAO,MAAM,IACzB,OAAO,KAAK,OAAO,MAAM,EAC3B;GAGD,MAAM,YAAY,SAAS,QAAQ,QAAQ,EAAE,OAAO,eAAe,CAAC,GAAG;GACvE,MAAM,YAAY,SAAS,QAAQ,WAAW,EAAE,OAAO,eAAe,CAAC;AASvE,QAAK,UAAU;IACb,UAAU;IACV,MAAM,qBARU,KAAK,IAAI,WAAW,UAAU,GAAG,GACjC,KAAK,IAAI,WAAW,UAAU,GAAG,GAG/B,KAAK,KAAK,YAAY,WAQtC,WACD;IACF;;;CAIL,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-ellipse-mode-with-tooltip.js","names":["center: [number, number]"],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawEllipseUsingThreePointsMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatDistanceTooltip,\n formatEllipseTooltip,\n} from '../../shared/constants';\n\n/**\n * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.\n *\n * Drawing process (3 clicks):\n * 1. First click: Sets the first endpoint of the major axis\n * 2. Second click: Sets the second endpoint of the major axis\n * - While moving to second click, shows distance tooltip (like line/polygon)\n * 3. Third click: Sets the minor axis radius\n * - While moving to third click, shows area tooltip (like circle)\n *\n * The ellipse center is the midpoint between the first two clicks.\n * The Y semi-axis is half the distance between clicks 1 and 2.\n * The X semi-axis is the distance from center to click 3.\n */\nexport class DrawEllipseModeWithTooltip extends DrawEllipseUsingThreePointsMode {\n private tooltip: Tooltip | null = null;\n\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n const tooltipPosition = mapCoords;\n\n if (clickSequence.length === 1) {\n // First segment: show distance from first click to cursor (like line/polygon)\n const firstPoint = clickSequence[0] as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(firstPoint, currentPoint, { units: distanceUnits });\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n } else if (clickSequence.length === 2) {\n // Second segment: show dimensions and area (like rectangle)\n // The ellipse will have:\n // - center at midpoint of click1 and click2\n // - ySemiAxis = distance(click1, click2) / 2\n // - xSemiAxis = distance(center, cursor)\n const click1 = clickSequence[0] as [number, number];\n const click2 = clickSequence[1] as [number, number];\n const cursorPos = mapCoords as [number, number];\n\n // Calculate center (midpoint)\n const center: [number, number] = [\n (click1[0] + click2[0]) / 2,\n (click1[1] + click2[1]) / 2,\n ];\n\n // Calculate semi-axes\n const ySemiAxis = distance(click1, click2, { units: distanceUnits }) / 2;\n const xSemiAxis = distance(center, cursorPos, { units: distanceUnits });\n\n // Full axes (diameter equivalent)\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n\n // Ellipse area = π × a × b\n const ellipseArea = Math.PI * xSemiAxis * ySemiAxis;\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n ),\n };\n }\n }\n\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,6BAAb,cAAgD,gCAAgC;CAC9E,AAAQ,UAA0B;CAElC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EACrC,MAAM,aAAa,4BAA4B,cAAc;EAC7D,MAAM,kBAAkB;AAExB,MAAI,cAAc,WAAW,GAAG;GAE9B,MAAM,aAAa,cAAc;AAKjC,QAAK,UAAU;IACb,UAAU;IACV,MAAM,sBAJK,SAAS,YAFD,WAE2B,EAAE,OAAO,eAAe,CAAC,EAIrC,WAAW;IAC9C;aACQ,cAAc,WAAW,GAAG;GAMrC,MAAM,SAAS,cAAc;GAC7B,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY;GAGlB,MAAMA,SAA2B,EAC9B,OAAO,KAAK,OAAO,MAAM,IACzB,OAAO,KAAK,OAAO,MAAM,EAC3B;GAGD,MAAM,YAAY,SAAS,QAAQ,QAAQ,EAAE,OAAO,eAAe,CAAC,GAAG;GACvE,MAAM,YAAY,SAAS,QAAQ,WAAW,EAAE,OAAO,eAAe,CAAC;AASvE,QAAK,UAAU;IACb,UAAU;IACV,MAAM,qBARU,KAAK,IAAI,WAAW,UAAU,GAAG,GACjC,KAAK,IAAI,WAAW,UAAU,GAAG,GAG/B,KAAK,KAAK,YAAY,WAQtC,WACD;IACF;;;CAIL,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/index.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { DrawPointMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { ShapeFeatureType } from '../../shared/types';\nimport { DrawCircleModeWithTooltip } from './draw-circle-mode-with-tooltip';\nimport { DrawEllipseModeWithTooltip } from './draw-ellipse-mode-with-tooltip';\nimport { DrawLineStringModeWithTooltip } from './draw-line-string-mode-with-tooltip';\nimport { DrawPolygonModeWithTooltip } from './draw-polygon-mode-with-tooltip';\nimport { DrawRectangleModeWithTooltip } from './draw-rectangle-mode-with-tooltip';\n\nexport { DrawCircleModeWithTooltip } from './draw-circle-mode-with-tooltip';\nexport { DrawEllipseModeWithTooltip } from './draw-ellipse-mode-with-tooltip';\nexport { DrawLineStringModeWithTooltip } from './draw-line-string-mode-with-tooltip';\nexport { DrawPolygonModeWithTooltip } from './draw-polygon-mode-with-tooltip';\nexport { DrawRectangleModeWithTooltip } from './draw-rectangle-mode-with-tooltip';\n\n/**\n * Cached 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 */\nconst MODE_INSTANCES = {\n [ShapeFeatureType.Point]: new DrawPointMode(),\n [ShapeFeatureType.LineString]: new DrawLineStringModeWithTooltip(),\n [ShapeFeatureType.Polygon]: new DrawPolygonModeWithTooltip(),\n [ShapeFeatureType.Rectangle]: new DrawRectangleModeWithTooltip(),\n [ShapeFeatureType.Circle]: new DrawCircleModeWithTooltip(),\n [ShapeFeatureType.Ellipse]: new DrawEllipseModeWithTooltip(),\n view: new ViewMode(),\n};\n\n/**\n * Get the cached mode instance for a shape type.\n *\n * @param shapeType - The shape type to get the mode for\n * @returns The cached mode instance for drawing that shape type\n */\nexport function getModeInstance(\n shapeType: ShapeFeatureType,\n): (typeof MODE_INSTANCES)[ShapeFeatureType] {\n return MODE_INSTANCES[shapeType];\n}\n\n/**\n * Get the view mode instance (for when not drawing).\n *\n * @returns The cached ViewMode instance\n */\nexport function getViewModeInstance(): ViewMode {\n return MODE_INSTANCES.view;\n}\n\n/**\n * Trigger double-click finish on the active mode.\n * This is a workaround for @deck.gl-community/editable-layers ~9.1 which doesn't\n * register 'dblclick' in EVENT_TYPES. We listen for dblclick at the DOM level\n * and call this function to finish drawing.\n *\n * @param shapeType - The shape type currently being drawn\n * @see https://github.com/visgl/deck.gl-community/pull/225\n */\nexport function triggerDoubleClickFinish(shapeType: ShapeFeatureType): void {\n const mode = MODE_INSTANCES[shapeType];\n\n // Only Polygon and LineString modes support double-click to finish\n if (\n mode instanceof DrawPolygonModeWithTooltip ||\n mode instanceof DrawLineStringModeWithTooltip\n ) {\n mode.handleDoubleClick();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,iBAAiB;EACpB,iBAAiB,QAAQ,IAAI,eAAe;EAC5C,iBAAiB,aAAa,IAAI,+BAA+B;EACjE,iBAAiB,UAAU,IAAI,4BAA4B;EAC3D,iBAAiB,YAAY,IAAI,8BAA8B;EAC/D,iBAAiB,SAAS,IAAI,2BAA2B;EACzD,iBAAiB,UAAU,IAAI,4BAA4B;CAC5D,MAAM,IAAI,UAAU;CACrB;;;;;;;AAQD,SAAgB,gBACd,WAC2C;AAC3C,QAAO,eAAe;;;;;;;;;;;AAqBxB,SAAgB,yBAAyB,WAAmC;CAC1E,MAAM,OAAO,eAAe;AAG5B,KACE,gBAAgB,8BAChB,gBAAgB,8BAEhB,MAAK,mBAAmB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-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 { DrawPointMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { ShapeFeatureType } from '../../shared/types';\nimport { DrawCircleModeWithTooltip } from './draw-circle-mode-with-tooltip';\nimport { DrawEllipseModeWithTooltip } from './draw-ellipse-mode-with-tooltip';\nimport { DrawLineStringModeWithTooltip } from './draw-line-string-mode-with-tooltip';\nimport { DrawPolygonModeWithTooltip } from './draw-polygon-mode-with-tooltip';\nimport { DrawRectangleModeWithTooltip } from './draw-rectangle-mode-with-tooltip';\n\nexport { DrawCircleModeWithTooltip } from './draw-circle-mode-with-tooltip';\nexport { DrawEllipseModeWithTooltip } from './draw-ellipse-mode-with-tooltip';\nexport { DrawLineStringModeWithTooltip } from './draw-line-string-mode-with-tooltip';\nexport { DrawPolygonModeWithTooltip } from './draw-polygon-mode-with-tooltip';\nexport { DrawRectangleModeWithTooltip } from './draw-rectangle-mode-with-tooltip';\n\n/**\n * Cached 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 */\nconst MODE_INSTANCES = {\n [ShapeFeatureType.Point]: new DrawPointMode(),\n [ShapeFeatureType.LineString]: new DrawLineStringModeWithTooltip(),\n [ShapeFeatureType.Polygon]: new DrawPolygonModeWithTooltip(),\n [ShapeFeatureType.Rectangle]: new DrawRectangleModeWithTooltip(),\n [ShapeFeatureType.Circle]: new DrawCircleModeWithTooltip(),\n [ShapeFeatureType.Ellipse]: new DrawEllipseModeWithTooltip(),\n view: new ViewMode(),\n};\n\n/**\n * Get the cached mode instance for a shape type.\n *\n * @param shapeType - The shape type to get the mode for\n * @returns The cached mode instance for drawing that shape type\n */\nexport function getModeInstance(\n shapeType: ShapeFeatureType,\n): (typeof MODE_INSTANCES)[ShapeFeatureType] {\n return MODE_INSTANCES[shapeType];\n}\n\n/**\n * Get the view mode instance (for when not drawing).\n *\n * @returns The cached ViewMode instance\n */\nexport function getViewModeInstance(): ViewMode {\n return MODE_INSTANCES.view;\n}\n\n/**\n * Trigger double-click finish on the active mode.\n * This is a workaround for @deck.gl-community/editable-layers ~9.1 which doesn't\n * register 'dblclick' in EVENT_TYPES. We listen for dblclick at the DOM level\n * and call this function to finish drawing.\n *\n * @param shapeType - The shape type currently being drawn\n * @see https://github.com/visgl/deck.gl-community/pull/225\n */\nexport function triggerDoubleClickFinish(shapeType: ShapeFeatureType): void {\n const mode = MODE_INSTANCES[shapeType];\n\n // Only Polygon and LineString modes support double-click to finish\n if (\n mode instanceof DrawPolygonModeWithTooltip ||\n mode instanceof DrawLineStringModeWithTooltip\n ) {\n mode.handleDoubleClick();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,iBAAiB;EACpB,iBAAiB,QAAQ,IAAI,eAAe;EAC5C,iBAAiB,aAAa,IAAI,+BAA+B;EACjE,iBAAiB,UAAU,IAAI,4BAA4B;EAC3D,iBAAiB,YAAY,IAAI,8BAA8B;EAC/D,iBAAiB,SAAS,IAAI,2BAA2B;EACzD,iBAAiB,UAAU,IAAI,4BAA4B;CAC5D,MAAM,IAAI,UAAU;CACrB;;;;;;;AAQD,SAAgB,gBACd,WAC2C;AAC3C,QAAO,eAAe;;;;;;;;;;;AAqBxB,SAAgB,yBAAyB,WAAmC;CAC1E,MAAM,OAAO,eAAe;AAG5B,KACE,gBAAgB,8BAChB,gBAAgB,8BAEhB,MAAK,mBAAmB"}