@accelint/map-toolkit 1.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +8 -1
- package/catalog-info.yaml +9 -6
- package/dist/camera/events.js +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/camera/store.d.ts +1 -1
- package/dist/camera/store.js +3 -5
- package/dist/camera/store.js.map +1 -1
- package/dist/camera/types.d.ts +1 -1
- package/dist/camera/types.js +1 -1
- package/dist/cursor-coordinates/constants.js +1 -1
- package/dist/cursor-coordinates/index.d.ts +1 -1
- package/dist/cursor-coordinates/index.js +1 -1
- package/dist/cursor-coordinates/store.d.ts +1 -1
- package/dist/cursor-coordinates/store.js +1 -1
- package/dist/cursor-coordinates/types.d.ts +1 -1
- package/dist/cursor-coordinates/types.js +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.js +4 -9
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/constants.js +1 -1
- package/dist/deckgl/base-map/controls.d.ts +1 -1
- package/dist/deckgl/base-map/controls.js +1 -1
- package/dist/deckgl/base-map/events.js +1 -1
- package/dist/deckgl/base-map/index.d.ts +3 -3
- package/dist/deckgl/base-map/index.js +1 -1
- package/dist/deckgl/base-map/provider.d.ts +1 -1
- package/dist/deckgl/base-map/provider.js +1 -1
- package/dist/deckgl/base-map/types.d.ts +1 -1
- package/dist/deckgl/base-map/types.js +1 -1
- package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.d.ts +144 -0
- package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js +535 -0
- package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js.map +1 -0
- package/dist/deckgl/extensions/coffin-corner/index.d.ts +17 -0
- package/dist/deckgl/extensions/coffin-corner/index.js +19 -0
- package/dist/deckgl/extensions/coffin-corner/store.d.ts +96 -0
- package/dist/deckgl/extensions/coffin-corner/store.js +173 -0
- package/dist/deckgl/extensions/coffin-corner/store.js.map +1 -0
- package/dist/deckgl/extensions/coffin-corner/types.d.ts +76 -0
- package/dist/deckgl/extensions/coffin-corner/types.js +27 -0
- package/dist/deckgl/extensions/coffin-corner/types.js.map +1 -0
- package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.d.ts +81 -0
- package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js +75 -0
- package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js.map +1 -0
- package/dist/deckgl/extensions/index.d.ts +15 -0
- package/dist/deckgl/extensions/index.js +16 -0
- package/dist/deckgl/index.d.ts +9 -4
- package/dist/deckgl/index.js +6 -2
- package/dist/deckgl/saved-viewports/index.d.ts +1 -1
- package/dist/deckgl/saved-viewports/index.js +1 -1
- package/dist/deckgl/saved-viewports/storage.d.ts +1 -1
- package/dist/deckgl/saved-viewports/storage.js +5 -10
- package/dist/deckgl/saved-viewports/storage.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +70 -26
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/index.d.ts +93 -38
- package/dist/deckgl/shapes/display-shape-layer/index.js +433 -187
- package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/store.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/types.d.ts +116 -19
- package/dist/deckgl/shapes/display-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +66 -36
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js +407 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +106 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +28 -39
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js +53 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js.map +1 -0
- package/dist/deckgl/shapes/draw-shape-layer/constants.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +8 -4
- package/dist/deckgl/shapes/draw-shape-layer/index.js +11 -17
- package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +6 -5
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +4 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +6 -20
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +6 -33
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +16 -12
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +2 -32
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/store.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +3 -8
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/constants.js +17 -2
- package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +12 -6
- package/dist/deckgl/shapes/edit-shape-layer/index.js +72 -27
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +4 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +4 -3
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +5 -3
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +83 -14
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +18 -4
- package/dist/deckgl/shapes/edit-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +8 -4
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -1
- package/dist/deckgl/shapes/index.d.ts +5 -4
- package/dist/deckgl/shapes/index.js +3 -2
- package/dist/deckgl/shapes/shared/constants.d.ts +4 -3
- package/dist/deckgl/shapes/shared/constants.js +51 -11
- package/dist/deckgl/shapes/shared/constants.js.map +1 -1
- package/dist/deckgl/shapes/shared/events.d.ts +5 -1
- package/dist/deckgl/shapes/shared/events.js +1 -1
- package/dist/deckgl/shapes/shared/events.js.map +1 -1
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +19 -16
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +182 -54
- package/dist/deckgl/shapes/shared/types.js +155 -2
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/duplicate-shape.d.ts +56 -0
- package/dist/deckgl/shapes/shared/utils/duplicate-shape.js +131 -0
- package/dist/deckgl/shapes/shared/utils/duplicate-shape.js.map +1 -0
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +29 -24
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/layer-config.js +15 -9
- package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/mode-utils.js +50 -20
- package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js +22 -15
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +38 -14
- package/dist/deckgl/shapes/shared/utils/style-utils.js +43 -32
- package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +4 -2
- package/dist/deckgl/symbol-layer/fiber.js +1 -1
- package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
- package/dist/deckgl/symbol-layer/index.d.ts +1 -1
- package/dist/deckgl/symbol-layer/index.js +1 -1
- package/dist/deckgl/text-layer/character-sets.js +1 -1
- package/dist/deckgl/text-layer/default-settings.d.ts +1 -1
- package/dist/deckgl/text-layer/default-settings.js +1 -1
- package/dist/deckgl/text-layer/fiber.d.ts +1 -1
- package/dist/deckgl/text-layer/fiber.js +1 -1
- package/dist/deckgl/text-layer/index.d.ts +1 -1
- package/dist/deckgl/text-layer/index.js +1 -1
- package/dist/deckgl/text-settings.d.ts +3 -3
- package/dist/deckgl/text-settings.js +1 -1
- package/dist/map-cursor/events.js +1 -1
- package/dist/map-cursor/index.d.ts +1 -1
- package/dist/map-cursor/index.js +1 -1
- package/dist/map-cursor/store.d.ts +1 -1
- package/dist/map-cursor/store.js +1 -1
- package/dist/map-cursor/types.d.ts +1 -1
- package/dist/map-cursor/types.js +1 -1
- package/dist/map-cursor/use-map-cursor.d.ts +1 -1
- package/dist/map-cursor/use-map-cursor.js +1 -1
- package/dist/map-mode/events.js +1 -1
- package/dist/map-mode/index.d.ts +1 -1
- package/dist/map-mode/index.js +1 -1
- package/dist/map-mode/store.d.ts +1 -1
- package/dist/map-mode/store.js +3 -8
- package/dist/map-mode/store.js.map +1 -1
- package/dist/map-mode/types.d.ts +1 -1
- package/dist/map-mode/types.js +1 -1
- package/dist/map-mode/use-map-mode.d.ts +1 -1
- package/dist/map-mode/use-map-mode.js +1 -1
- package/dist/maplibre/hooks/use-maplibre.d.ts +1 -1
- package/dist/maplibre/hooks/use-maplibre.js +1 -1
- package/dist/maplibre/index.d.ts +1 -1
- package/dist/maplibre/index.js +1 -1
- package/dist/shared/cleanup.d.ts +1 -1
- package/dist/shared/cleanup.js +1 -1
- package/dist/shared/constants.js +1 -1
- package/dist/shared/create-map-store.d.ts +1 -1
- package/dist/shared/create-map-store.js +1 -1
- package/dist/shared/logger.js +31 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/units.d.ts +15 -56
- package/dist/shared/units.js +2 -53
- package/dist/shared/units.js.map +1 -1
- package/dist/viewport/index.d.ts +3 -4
- package/dist/viewport/index.js +2 -3
- package/dist/viewport/store.d.ts +1 -1
- package/dist/viewport/store.js +1 -1
- package/dist/viewport/types.d.ts +9 -5
- package/dist/viewport/types.js +1 -1
- package/dist/viewport/utils.d.ts +4 -4
- package/dist/viewport/utils.js +17 -9
- package/dist/viewport/utils.js.map +1 -1
- package/dist/viewport/viewport-size.d.ts +7 -6
- package/dist/viewport/viewport-size.js +3 -3
- package/dist/viewport/viewport-size.js.map +1 -1
- package/package.json +29 -20
- package/dist/hotkey-manager/dist/react/use-hotkey.js +0 -39
- package/dist/hotkey-manager/dist/react/use-hotkey.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coffin-corner-extension.js","names":["coffinCornerModule: {\n name: string;\n fs: string;\n uniformTypes: Record<string, string>;\n}","DEFAULT_SELECTED_CORNER_FILL: Rgba255Tuple"],"sources":["../../../../src/deckgl/extensions/coffin-corner/coffin-corner-extension.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 { LayerExtension } from '@deck.gl/core';\nimport { IconLayer, ScatterplotLayer } from '@deck.gl/layers';\nimport { createLoggerDomain } from '@/shared/logger';\nimport type { Rgba255Tuple } from '@accelint/predicates';\nimport type { Layer, UpdateParameters } from '@deck.gl/core';\nimport type { CoffinCornerExtensionProps, EntityId } from './types';\n\nconst logger = createLoggerDomain('[CoffinCornerExtension]');\n\n/** Layer shape with coffin-corner selection and hover state maps. */\ntype CoffinCornerLayer = Layer & {\n state: {\n selectedEntities: Map<EntityId, number>;\n hoveredEntities: Map<EntityId, number>;\n };\n};\n\n/**\n * Shader module defining the `highlightColor` uniform for coffin corner brackets.\n *\n * @property name - Module identifier used by deck.gl's shader assembly.\n * @property fs - Fragment shader (GLSL) source declaring the uniform block.\n * @property uniformTypes - Maps uniform names to WGSL-style type strings for deck.gl's uniform system.\n *\n * @remarks\n * GLSL type reference:\n * - `float` — single decimal number\n * - `vec2` — 2-component vector (x, y)\n * - `vec3` — 3-component vector (x, y, z) or (r, g, b)\n * - `vec4` — 4-component vector (x, y, z, w) or (r, g, b, a)\n * - `uniform` — read-only value passed from JS to the GPU each frame\n * - `in/out` — values passed between vertex and fragment shader stages\n */\nconst coffinCornerModule: {\n name: string;\n fs: string;\n uniformTypes: Record<string, string>;\n} = {\n name: 'coffinCorner',\n fs: /* glsl */ `\\\nuniform coffinCornerUniforms {\n vec4 highlightColor; // RGBA color, each channel 0.0–1.0\n} coffinCorner;\n`,\n uniformTypes: {\n highlightColor: 'vec4<f32>', // WGSL-style type: 4-component vector of 32-bit floats (deck.gl convention)\n },\n};\n\n// -- Shared shader injections (IconLayer and ScatterplotLayer) --\n\n/**\n * Vertex declarations: per-instance attributes for selection/hover state,\n * passed to the fragment shader as varyings.\n */\nconst VS_DECL = /* glsl */ `\\\nin float instanceSelectedEntity;\nin float instanceHoveredEntity;\nout float vInstanceSelectedEntity; // v prefix is conventional for \"varying\"\nout float vInstanceHoveredEntity;\n`;\n\n/** Vertex main-end: forward per-instance attributes to the fragment shader. */\nconst VS_MAIN_END = /* glsl */ `\\\nvInstanceSelectedEntity = instanceSelectedEntity;\nvInstanceHoveredEntity = instanceHoveredEntity;\n`;\n\n// -- ScatterplotLayer-specific vertex shader injections --\n\n/**\n * ScatterplotLayer vertex declarations: adds `vQuadScale` varying to\n * communicate the quad expansion factor to the fragment shader.\n */\nconst SCATTERPLOT_VS_DECL = /* glsl */ `\\\n${VS_DECL}out float vQuadScale;\n`;\n\n/**\n * ScatterplotLayer vertex main-end: expands the quad when hovered/selected\n * to provide room for coffin corner brackets beyond the circle edge.\n *\n * The expansion multiplies `unitPosition` by the scale factor so that\n * fragment shader interpolation covers the larger area. The extra pixel\n * offset is added to `gl_Position` in clip space.\n *\n * References ScatterplotLayer vertex-scope variables:\n * `edgePadding`, `positions`, `outerRadiusPixels` (all still in scope at #main-end).\n */\nconst SCATTERPLOT_VS_MAIN_END = /* glsl */ `\\\n${VS_MAIN_END}\n// Expand the quad when hovered/selected to make room for bracket arms.\n// Scale factor 2.0 gives brackets the same visual footprint as the circle diameter.\n// Skip expansion in globe mode — clip-space XY manipulation causes depth conflicts\n// with the globe surface (known deck.gl limitation, see PR #9975).\nvQuadScale = 1.0;\nif ((instanceSelectedEntity > 0.5 || instanceHoveredEntity > 0.5)\n && project.projectionMode != PROJECTION_MODE_GLOBE) {\n vQuadScale = 2.0;\n // Add extra offset in clip space (works for both billboard and non-billboard)\n vec2 extraPixelOffset = (vQuadScale - 1.0) * edgePadding * positions.xy * outerRadiusPixels;\n gl_Position.xy += project_pixel_size_to_clipspace(extraPixelOffset);\n // Scale unitPosition so fragment interpolation covers the expanded quad\n unitPosition *= vQuadScale;\n}\n`;\n\n/**\n * Fragment declarations: SDF functions for bracket rendering.\n *\n * Signed distance functions (SDF) return negative inside the shape,\n * zero on the edge, and positive outside. Used for anti-aliased rendering.\n */\nconst FS_DECL = /* glsl */ `\\\nin float vInstanceSelectedEntity;\nin float vInstanceHoveredEntity;\n\n/**\n * Signed distance to an axis-aligned box.\n *\n * @param position - point to measure distance from\n * @param halfExtents - half-width and half-height of the box (distance from center to each edge)\n * @returns signed distance: negative inside, zero on edge, positive outside\n */\nfloat coffinCorner_signedDistBox(vec2 position, vec2 halfExtents) {\n vec2 dist = abs(position) - halfExtents;\n return length(max(dist, 0.0)) + min(max(dist.x, dist.y), 0.0);\n}\n\n/**\n * Signed distance to an L-shaped bracket arm (horizontal + vertical box union).\n *\n * @param position - point to measure distance from\n * @param length - length of each arm of the L-shape (fraction of icon size)\n * @param width - stroke width of each arm (fraction of icon size)\n * @returns signed distance to the nearest arm of the L-shape\n */\nfloat coffinCorner_signedDistBracketArm(vec2 position, float length, float width) {\n float horizontalDist = coffinCorner_signedDistBox(position - vec2(length * 0.5, 0.0), vec2(length * 0.5, width * 0.5));\n float verticalDist = coffinCorner_signedDistBox(position - vec2(0.0, length * 0.5), vec2(width * 0.5, length * 0.5));\n return min(horizontalDist, verticalDist);\n}\n\n/**\n * Minimum signed distance from a point to any of the four corner brackets.\n *\n * @param position - point in normalized space (-0.5 to 0.5)\n * @param armLength - length of each bracket arm in UV units\n * @param armWidth - stroke width of each bracket arm in UV units\n * @returns signed distance to the nearest corner bracket\n */\nfloat coffinCorner_allCorners(vec2 position, float armLength, float armWidth) {\n const vec2 halfSize = vec2(0.5);\n\n vec2 topLeft = position + halfSize;\n vec2 topRight = vec2(halfSize.x - position.x, position.y + halfSize.y);\n vec2 bottomLeft = vec2(position.x + halfSize.x, halfSize.y - position.y);\n vec2 bottomRight = halfSize - position;\n\n return min(\n min(coffinCorner_signedDistBracketArm(topLeft, armLength, armWidth),\n coffinCorner_signedDistBracketArm(topRight, armLength, armWidth)),\n min(coffinCorner_signedDistBracketArm(bottomLeft, armLength, armWidth),\n coffinCorner_signedDistBracketArm(bottomRight, armLength, armWidth))\n );\n}\n\n/**\n * Composite bracket visuals over a base color.\n * Applies: hover fill → base color → black stroke → colored bracket fill.\n * Returns the composited color — caller is responsible for writing to fragColor.\n *\n * @param baseColor - the layer's rendered color for this pixel (icon texture or circle fill)\n * @param isHovered - whether the entity is hovered\n * @param isSelected - whether the entity is selected\n * @param strokeAlpha - alpha for the dilated black outline\n * @param fillAlpha - alpha for the bracket fill shape\n * @returns composited RGBA color\n */\nvec4 coffinCorner_composite(\n vec4 baseColor,\n bool isHovered,\n bool isSelected,\n float strokeAlpha,\n float fillAlpha\n) {\n // Start with background fill (only when hovering) — white 30% opacity\n vec4 result = isHovered\n ? vec4(1.0, 1.0, 1.0, 0.3)\n : vec4(0.0); // no fill when just selected\n\n // Composite base color OVER fill\n result.rgb = baseColor.rgb * baseColor.a + result.rgb * result.a * (1.0 - baseColor.a);\n result.a = baseColor.a + result.a * (1.0 - baseColor.a);\n\n // Composite black stroke OVER base (dilated shape)\n if (strokeAlpha > 0.01) {\n vec3 strokeColor = vec3(0.0); // Black\n result.rgb = strokeColor * strokeAlpha + result.rgb * (1.0 - strokeAlpha);\n result.a = strokeAlpha + result.a * (1.0 - strokeAlpha);\n }\n\n // Composite colored fill OVER stroke (normal shape)\n if (fillAlpha > 0.01) {\n vec4 cornerColor = isSelected\n ? coffinCorner.highlightColor\n : vec4(1.0); // White fully opaque for hover-only\n float blendedAlpha = fillAlpha * cornerColor.a;\n result.rgb = cornerColor.rgb * blendedAlpha + result.rgb * (1.0 - blendedAlpha);\n result.a = blendedAlpha + result.a * (1.0 - blendedAlpha);\n }\n\n return result;\n}\n`;\n\n// -- Layer-specific fragment shader main-start injections --\n\n/**\n * IconLayer `fs:#main-start` — samples `iconsTexture` to get the base icon color,\n * then composites brackets over it. References IconLayer-specific uniforms/varyings:\n * `iconsTexture`, `vTextureCoords`, `uv`.\n */\nconst ICON_FS_MAIN_START = /* glsl */ `\\\n geometry.uv = uv; // uv = texture coordinate on the icon quad (ranges -1 to 1, center is 0,0)\n bool isHovered = vInstanceHoveredEntity > 0.5;\n bool isSelected = vInstanceSelectedEntity > 0.5;\n\n if (isHovered || isSelected) {\n vec2 bracketPos = uv * 0.5; // map -1..1 to -0.5..0.5\n\n // Check if inside the icon quad (the rectangular surface the icon texture is drawn on)\n bool insideBox = max(abs(bracketPos.x), abs(bracketPos.y)) < 0.5;\n\n // Distance from this pixel to the nearest bracket edge — drives stroke and fill alpha\n float cornerDist = coffinCorner_allCorners(bracketPos, 0.26, 0.07);\n\n // Outline width (proportional to icon)\n float strokeWidth = 0.026;\n // Anti-alias band: ~1 screen pixel regardless of icon size\n float antiAlias = fwidth(cornerDist);\n\n // strokeAlpha: expanded (dilated) shape for black border\n // fillAlpha: true SDF edge for the bracket fill color\n float strokeAlpha = 1.0 - smoothstep(0.0, antiAlias, cornerDist + strokeWidth);\n float fillAlpha = 1.0 - smoothstep(0.0, antiAlias, cornerDist);\n\n if (insideBox) {\n // Sample icon texture (iconsTexture and vTextureCoords are provided by IconLayer's shader)\n vec4 baseColor = texture(iconsTexture, vTextureCoords);\n\n fragColor = coffinCorner_composite(baseColor, isHovered, isSelected, strokeAlpha, fillAlpha);\n DECKGL_FILTER_COLOR(fragColor, geometry);\n return;\n }\n }\n`;\n\n/**\n * ScatterplotLayer fragment declarations: adds `vQuadScale` varying\n * (received from the vertex shader) to the shared SDF function declarations.\n */\nconst SCATTERPLOT_FS_DECL = /* glsl */ `\\\nin float vQuadScale;\n${FS_DECL}`;\n\n/**\n * ScatterplotLayer `fs:#main-start` — replicates the circle rendering logic to get\n * the base fill/stroke color, then composites brackets over it.\n *\n * Uses two coordinate systems:\n * - **Original** (`originalUnit`): `unitPosition / vQuadScale` — maps back to\n * the pre-expansion circle space (±1) for circle fill/stroke rendering.\n * - **Expanded** (`bracketPos`): `unitPosition / (2 * vQuadScale)` — maps the\n * expanded quad to -0.5..0.5 for the bracket SDF.\n *\n * The vertex shader expands the quad by `vQuadScale` (2×) when hovered/selected,\n * giving the brackets room to render beyond the circle edge while keeping the circle\n * at its original visual size.\n *\n * References ScatterplotLayer-specific varyings/uniforms:\n * `unitPosition`, `outerRadiusPixels`, `innerUnitRadius`, `vFillColor`, `vLineColor`,\n * `scatterplot.antialiasing`, `scatterplot.stroked`, `scatterplot.filled`.\n */\nconst SCATTERPLOT_FS_MAIN_START = /* glsl */ `\\\n geometry.uv = unitPosition;\n bool isHovered = vInstanceHoveredEntity > 0.5;\n bool isSelected = vInstanceSelectedEntity > 0.5;\n\n if (isHovered || isSelected) {\n // Bracket SDF coordinates: map the expanded quad to -0.5..0.5.\n // Since both vertex positions and unitPosition were scaled by vQuadScale,\n // the quad edge is at unitPosition ≈ ±(edgePadding * vQuadScale).\n // Dividing by (2 * vQuadScale) maps ±vQuadScale to ±0.5.\n // The circle edge (unitPosition = 1.0) maps to 1/(2*scale) ≈ 0.25 (for scale=2).\n vec2 bracketPos = unitPosition / (2.0 * vQuadScale);\n\n bool insideBox = max(abs(bracketPos.x), abs(bracketPos.y)) < 0.5;\n\n // Arm proportions scaled for the expanded quad: the circle fills ~50% of the\n // bracket box (circle edge at 0.25, bracket corner at 0.5). Shorter arms (0.15)\n // keep L-shapes distinct instead of merging into a solid square.\n float uvPerPx = fwidth(bracketPos.x);\n float armLength = max(0.15, 6.0 * uvPerPx); // min 6px arm length\n float armWidth = max(0.04, 2.0 * uvPerPx); // min 2px stroke width\n\n float cornerDist = coffinCorner_allCorners(bracketPos, armLength, armWidth);\n float strokeWidth = max(0.016, 1.0 * uvPerPx); // min 1px outline\n float antiAlias = fwidth(cornerDist);\n float strokeAlpha = 1.0 - smoothstep(0.0, antiAlias, cornerDist + strokeWidth);\n float fillAlpha = 1.0 - smoothstep(0.0, antiAlias, cornerDist);\n\n if (insideBox) {\n // Circle rendering uses unitPosition directly (NOT divided by vQuadScale).\n // Because both the vertex positions and unitPosition were scaled equally in\n // the vertex shader, the interpolated unitPosition at the circle-edge screen\n // position is preserved at 1.0 — identical to the original shader's math.\n float distToCenter = length(unitPosition) * outerRadiusPixels;\n float inCircle = scatterplot.antialiasing\n ? smoothedge(distToCenter, outerRadiusPixels)\n : step(distToCenter, outerRadiusPixels);\n\n vec4 baseColor = vec4(0.0);\n if (inCircle > 0.0) {\n if (scatterplot.stroked > 0.5) {\n float isLine = scatterplot.antialiasing\n ? smoothedge(innerUnitRadius * outerRadiusPixels, distToCenter)\n : step(innerUnitRadius * outerRadiusPixels, distToCenter);\n if (scatterplot.filled > 0.5) {\n baseColor = mix(vFillColor, vLineColor, isLine);\n } else {\n baseColor = vec4(vLineColor.rgb, vLineColor.a * isLine);\n }\n } else if (scatterplot.filled > 0.5) {\n baseColor = vFillColor;\n }\n baseColor.a *= inCircle;\n }\n\n fragColor = coffinCorner_composite(baseColor, isHovered, isSelected, strokeAlpha, fillAlpha);\n DECKGL_FILTER_COLOR(fragColor, geometry);\n return;\n }\n }\n`;\n\n// -- Shader configs --\n\n/**\n * Shader injection config for IconLayer.\n *\n * deck.gl inject keys follow the pattern `<stage>:<hook>`:\n * - `vs:#decl` — vertex shader, top-level declarations (before main)\n * - `vs:#main-end` — vertex shader, end of main()\n * - `fs:#decl` — fragment shader, top-level declarations (before main)\n * - `fs:#main-start` — fragment shader, start of main()\n */\nconst ICON_SHADERS = {\n modules: [coffinCornerModule],\n inject: {\n 'vs:#decl': VS_DECL,\n 'vs:#main-end': VS_MAIN_END,\n 'fs:#decl': FS_DECL,\n 'fs:#main-start': ICON_FS_MAIN_START,\n },\n};\n\n/**\n * Shader injection config for ScatterplotLayer.\n * Uses scatterplot-specific vertex injections for quad expansion and\n * scatterplot-specific fragment declarations for the `vQuadScale` varying.\n */\nconst SCATTERPLOT_SHADERS = {\n modules: [coffinCornerModule],\n inject: {\n 'vs:#decl': SCATTERPLOT_VS_DECL,\n 'vs:#main-end': SCATTERPLOT_VS_MAIN_END,\n 'fs:#decl': SCATTERPLOT_FS_DECL,\n 'fs:#main-start': SCATTERPLOT_FS_MAIN_START,\n },\n};\n\n/** Default bracket fill color when selected: #39B7FA fully opaque. */\nconst DEFAULT_SELECTED_CORNER_FILL: Rgba255Tuple = [57, 183, 250, 255];\n\n/** Layer types supported by this extension. */\nconst SUPPORTED_LAYERS = [IconLayer, ScatterplotLayer];\n\n// -- Extension class --\n\n/**\n * deck.gl layer extension that renders bracket-like \"coffin corner\" indicators\n * around hovered and selected map entities.\n *\n * Driven by explicit `hoveredEntityId` and `selectedEntityId` props rather than\n * deck.gl's built-in autoHighlight. Data objects are identified via the\n * `getEntityId` accessor (defaults to `item => item.id`).\n *\n * **Supported layer types:**\n * - **IconLayer** (and subclasses like SymbolLayer) — samples `iconsTexture`\n * to re-composite the icon beneath the brackets.\n * - **ScatterplotLayer** — replicates the circle fill/stroke logic to composite\n * the circle beneath the brackets.\n *\n * Using this extension on unsupported layer types will produce shader compilation\n * errors due to missing layer-specific uniforms/varyings.\n *\n * The host layer must set `pickable` to enable picking events.\n *\n * @see CoffinCornerExtensionProps for the full list of extension props.\n *\n * @example Fiber renderer JSX (IconLayer / SymbolLayer)\n * ```tsx\n * <symbolLayer\n * {...props}\n * pickable\n * extensions={[new CoffinCornerExtension()]}\n * selectedEntityId={selectedId}\n * hoveredEntityId={hoveredId}\n * />\n * ```\n *\n * @example ScatterplotLayer\n * ```typescript\n * new ScatterplotLayer({\n * extensions: [new CoffinCornerExtension()],\n * getEntityId: (d) => d.id,\n * selectedEntityId: selectedId,\n * hoveredEntityId: hoveredId,\n * })\n * ```\n *\n * @example Custom entity ID accessor (e.g. GeoJSON features)\n * ```typescript\n * new IconLayer({\n * extensions: [new CoffinCornerExtension()],\n * getEntityId: (d) => d.properties?.shapeId,\n * selectedEntityId: selectedShapeId,\n * hoveredEntityId: hoveredShapeId,\n * selectedCoffinCornerColor: [255, 0, 0, 255],\n * })\n * ```\n */\nexport class CoffinCornerExtension extends LayerExtension {\n static override componentName = 'CoffinCornerExtension';\n\n static override defaultProps = {\n selectedEntityId: { type: 'value', value: undefined },\n hoveredEntityId: { type: 'value', value: undefined },\n selectedCoffinCornerColor: {\n type: 'color',\n value: DEFAULT_SELECTED_CORNER_FILL,\n },\n getEntityId: {\n type: 'accessor',\n value: (item: { id: EntityId }) => item.id,\n },\n };\n\n /** Returns true if the host layer is a supported type. */\n private static isSupportedLayer(layer: Layer): boolean {\n return SUPPORTED_LAYERS.some((LayerType) => layer instanceof LayerType);\n }\n\n /**\n * Initializes selection and hover entity state maps and registers\n * `instanceSelectedEntity` / `instanceHoveredEntity` GPU attributes.\n * No-op on unsupported layer types (e.g. PathLayer, SolidPolygonLayer).\n */\n override initializeState(this: CoffinCornerLayer) {\n if (!CoffinCornerExtension.isSupportedLayer(this)) {\n logger.warn(\n `CoffinCornerExtension supports IconLayer and ScatterplotLayer (and subclasses). Received: ${(this.constructor as typeof Layer).layerName}`,\n );\n return;\n }\n\n this.state.selectedEntities = new Map<EntityId, number>();\n this.state.hoveredEntities = new Map<EntityId, number>();\n\n const attributeManager = this.getAttributeManager();\n if (!attributeManager) {\n return;\n }\n\n const makeUpdateCallback =\n (stateKey: 'selectedEntities' | 'hoveredEntities') =>\n (\n attribute: { value: unknown },\n { data }: { data: unknown[] | undefined },\n ) => {\n const entities = this.state[stateKey];\n const getId =\n (this.props as unknown as CoffinCornerExtensionProps).getEntityId ??\n // biome-ignore lint/suspicious/noExplicitAny: Default accessor assumes item.id exists.\n ((item: any) => item.id as EntityId);\n const items = data ?? [];\n const value = attribute.value as Float32Array;\n\n for (let i = 0; i < items.length; i++) {\n value[i] = entities.get(getId(items[i])) ?? 0;\n }\n };\n\n attributeManager.addInstanced({\n instanceSelectedEntity: {\n size: 1,\n update: makeUpdateCallback('selectedEntities'),\n },\n instanceHoveredEntity: {\n size: 1,\n update: makeUpdateCallback('hoveredEntities'),\n },\n });\n }\n\n /**\n * Syncs `selectedEntityId` and `hoveredEntityId` prop changes into the\n * entity state maps and invalidates the corresponding GPU attributes.\n * No-op on unsupported layer types.\n *\n * @param params - The deck.gl update parameters containing current and previous props.\n */\n override updateState(\n this: CoffinCornerLayer,\n params: UpdateParameters<Layer<CoffinCornerExtensionProps>>,\n ) {\n if (!CoffinCornerExtension.isSupportedLayer(this)) {\n return;\n }\n\n const attributeManager = this.getAttributeManager();\n\n // Selection state\n const newSelectedId = params.props.selectedEntityId;\n const oldSelectedId = params.oldProps.selectedEntityId;\n if (newSelectedId !== oldSelectedId) {\n const { selectedEntities } = this.state;\n if (oldSelectedId != null) {\n selectedEntities.delete(oldSelectedId);\n }\n if (newSelectedId != null) {\n selectedEntities.set(newSelectedId, 1);\n }\n attributeManager?.invalidate('instanceSelectedEntity');\n }\n\n // Hover state\n const newHoveredId = params.props.hoveredEntityId;\n const oldHoveredId = params.oldProps.hoveredEntityId;\n if (newHoveredId !== oldHoveredId) {\n const { hoveredEntities } = this.state;\n if (oldHoveredId != null) {\n hoveredEntities.delete(oldHoveredId);\n }\n if (newHoveredId != null) {\n hoveredEntities.set(newHoveredId, 1);\n }\n attributeManager?.invalidate('instanceHoveredEntity');\n }\n }\n\n /**\n * Pushes the normalized `selectedCoffinCornerColor` to the shader's `highlightColor` uniform\n * each frame. No-op on unsupported layer types.\n */\n override draw(this: CoffinCornerLayer) {\n if (!CoffinCornerExtension.isSupportedLayer(this)) {\n return;\n }\n\n const color =\n (this.props as unknown as CoffinCornerExtensionProps)\n .selectedCoffinCornerColor ?? DEFAULT_SELECTED_CORNER_FILL;\n\n this.setShaderModuleProps({\n coffinCorner: {\n highlightColor: [\n color[0] / 255,\n color[1] / 255,\n color[2] / 255,\n color[3] / 255,\n ],\n },\n });\n }\n\n /**\n * Returns the appropriate shader injection config based on the host layer type.\n * IconLayer gets texture-sampling shaders; ScatterplotLayer gets circle-replicating shaders.\n * Returns null for unsupported layer types to skip shader injection.\n *\n * @returns The vertex/fragment shader injection config and uniform module, or null.\n */\n override getShaders(this: CoffinCornerLayer, _extensions: this) {\n if (this instanceof ScatterplotLayer) {\n return SCATTERPLOT_SHADERS;\n }\n if (this instanceof IconLayer) {\n return ICON_SHADERS;\n }\n return null;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { CoffinCornerDeselectedEvent, CoffinCornerEvent, CoffinCornerEventType, CoffinCornerEvents, CoffinCornerExtensionProps, CoffinCornerHoveredEvent, CoffinCornerSelectedEvent, EntityId } from "./types.js";
|
|
14
|
+
import { CoffinCornerExtension } from "./coffin-corner-extension.js";
|
|
15
|
+
import { clearSelection, coffinCornerStore, getHoveredEntityId, getSelectedEntityId } from "./store.js";
|
|
16
|
+
import { UseCoffinCornerOptions, UseCoffinCornerReturn, useCoffinCorner } from "./use-coffin-corner.js";
|
|
17
|
+
export { type CoffinCornerDeselectedEvent, type CoffinCornerEvent, type CoffinCornerEventType, CoffinCornerEvents, CoffinCornerExtension, type CoffinCornerExtensionProps, type CoffinCornerHoveredEvent, type CoffinCornerSelectedEvent, type EntityId, type UseCoffinCornerOptions, type UseCoffinCornerReturn, clearSelection, coffinCornerStore, getHoveredEntityId, getSelectedEntityId, useCoffinCorner };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import { CoffinCornerExtension } from "./coffin-corner-extension.js";
|
|
15
|
+
import { CoffinCornerEvents } from "./types.js";
|
|
16
|
+
import { clearSelection, coffinCornerStore, getHoveredEntityId, getSelectedEntityId } from "./store.js";
|
|
17
|
+
import { useCoffinCorner } from "./use-coffin-corner.js";
|
|
18
|
+
|
|
19
|
+
export { CoffinCornerEvents, CoffinCornerExtension, clearSelection, coffinCornerStore, getHoveredEntityId, getSelectedEntityId, useCoffinCorner };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { MapStore } from "../../../shared/create-map-store.js";
|
|
14
|
+
import { EntityId } from "./types.js";
|
|
15
|
+
import { UniqueId } from "@accelint/core";
|
|
16
|
+
|
|
17
|
+
//#region src/deckgl/extensions/coffin-corner/store.d.ts
|
|
18
|
+
/** Extracts an entity ID from a picked data item. */
|
|
19
|
+
type GetEntityId = (item: any) => EntityId;
|
|
20
|
+
/**
|
|
21
|
+
* State for coffin corner selection and hover.
|
|
22
|
+
*/
|
|
23
|
+
type CoffinCornerState = {
|
|
24
|
+
/** Currently selected entity ID, or undefined if nothing is selected. */
|
|
25
|
+
selectedId: EntityId | undefined;
|
|
26
|
+
/** Currently hovered entity ID, or undefined if nothing is hovered. */
|
|
27
|
+
hoveredId: EntityId | undefined;
|
|
28
|
+
/** deck.gl layer ID used to filter map bus events to only this layer's interactions. */
|
|
29
|
+
layerId: string | undefined;
|
|
30
|
+
/** Accessor to extract an entity ID from a picked data item. */
|
|
31
|
+
getEntityId: GetEntityId;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Actions for coffin corner interactions.
|
|
35
|
+
*/
|
|
36
|
+
type CoffinCornerActions = {
|
|
37
|
+
/** Select an entity by ID (emits domain event). Pass undefined to deselect. */
|
|
38
|
+
setSelectedId: (id: EntityId | undefined) => void;
|
|
39
|
+
/** Clear the current selection (emits deselect domain event). */
|
|
40
|
+
deselect: () => void;
|
|
41
|
+
/** Set the deck.gl layer ID used to filter map bus events. */
|
|
42
|
+
setLayerId: (layerId: string) => void;
|
|
43
|
+
/** Override the entity ID accessor for picked data items. */
|
|
44
|
+
setGetEntityId: (fn: GetEntityId) => void;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Coffin corner store - manages selection and hover state per map instance.
|
|
48
|
+
*
|
|
49
|
+
* Subscribes to map bus events for click/hover interactions and translates
|
|
50
|
+
* them into coffin corner domain events. The hook only needs to pass the
|
|
51
|
+
* layer ID — all subscription management lives here.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const { state, setSelectedId, deselect } = coffinCornerStore.use(mapId);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare const coffinCornerStore: MapStore<CoffinCornerState, CoffinCornerActions>;
|
|
59
|
+
/**
|
|
60
|
+
* Get selected entity ID imperatively (non-reactive).
|
|
61
|
+
*
|
|
62
|
+
* @param mapId - The map instance to query.
|
|
63
|
+
* @returns The selected entity ID, or undefined if nothing is selected.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const selectedId = getSelectedEntityId(mapId);
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function getSelectedEntityId(mapId: UniqueId): EntityId | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Get hovered entity ID imperatively (non-reactive).
|
|
73
|
+
*
|
|
74
|
+
* @param mapId - The map instance to query.
|
|
75
|
+
* @returns The hovered entity ID, or undefined if nothing is hovered.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const hoveredId = getHoveredEntityId(mapId);
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
declare function getHoveredEntityId(mapId: UniqueId): EntityId | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Clear selection state (for tests/cleanup).
|
|
85
|
+
*
|
|
86
|
+
* @param mapId - The map instance to clear selection for.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* clearSelection(mapId);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function clearSelection(mapId: UniqueId): void;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { clearSelection, coffinCornerStore, getHoveredEntityId, getSelectedEntityId };
|
|
96
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
'use client';
|
|
15
|
+
|
|
16
|
+
import { createMapStore } from "../../../shared/create-map-store.js";
|
|
17
|
+
import { MapEvents } from "../../base-map/events.js";
|
|
18
|
+
import { CoffinCornerEvents } from "./types.js";
|
|
19
|
+
import { Broadcast } from "@accelint/bus";
|
|
20
|
+
|
|
21
|
+
//#region src/deckgl/extensions/coffin-corner/store.ts
|
|
22
|
+
/** Default accessor — assumes the data item has an `id` property. */
|
|
23
|
+
const defaultGetEntityId = (item) => item.id;
|
|
24
|
+
const coffinCornerEventBus = Broadcast.getInstance();
|
|
25
|
+
const mapEventBus = Broadcast.getInstance();
|
|
26
|
+
/**
|
|
27
|
+
* Coffin corner store - manages selection and hover state per map instance.
|
|
28
|
+
*
|
|
29
|
+
* Subscribes to map bus events for click/hover interactions and translates
|
|
30
|
+
* them into coffin corner domain events. The hook only needs to pass the
|
|
31
|
+
* layer ID — all subscription management lives here.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* const { state, setSelectedId, deselect } = coffinCornerStore.use(mapId);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
const coffinCornerStore = createMapStore({
|
|
39
|
+
defaultState: {
|
|
40
|
+
selectedId: void 0,
|
|
41
|
+
hoveredId: void 0,
|
|
42
|
+
layerId: void 0,
|
|
43
|
+
getEntityId: defaultGetEntityId
|
|
44
|
+
},
|
|
45
|
+
actions: (mapId, { get, set }) => ({
|
|
46
|
+
setSelectedId: (id) => {
|
|
47
|
+
const currentId = get().selectedId;
|
|
48
|
+
if (id == null && currentId != null) {
|
|
49
|
+
coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {
|
|
50
|
+
mapId,
|
|
51
|
+
selectedId: void 0
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (id != null && currentId !== id) {
|
|
56
|
+
coffinCornerEventBus.emit(CoffinCornerEvents.SELECTED, {
|
|
57
|
+
selectedId: id,
|
|
58
|
+
mapId
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
deselect: () => {
|
|
64
|
+
coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {
|
|
65
|
+
mapId,
|
|
66
|
+
selectedId: void 0
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
setLayerId: (layerId) => {
|
|
70
|
+
if (get().layerId !== layerId) set({ layerId });
|
|
71
|
+
},
|
|
72
|
+
setGetEntityId: (fn) => {
|
|
73
|
+
if (get().getEntityId !== fn) set({ getEntityId: fn });
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
bus: (mapId, { get, set }) => {
|
|
77
|
+
const onSelected = coffinCornerEventBus.on(CoffinCornerEvents.SELECTED, (event) => {
|
|
78
|
+
if (event.payload.mapId !== mapId) return;
|
|
79
|
+
if (get().selectedId !== event.payload.selectedId) set({ selectedId: event.payload.selectedId });
|
|
80
|
+
});
|
|
81
|
+
const onDeselected = coffinCornerEventBus.on(CoffinCornerEvents.DESELECTED, (event) => {
|
|
82
|
+
if (event.payload.mapId !== mapId) return;
|
|
83
|
+
if (get().selectedId !== void 0) set({ selectedId: void 0 });
|
|
84
|
+
});
|
|
85
|
+
const onHovered = coffinCornerEventBus.on(CoffinCornerEvents.HOVERED, (event) => {
|
|
86
|
+
if (event.payload.mapId !== mapId) return;
|
|
87
|
+
if (get().hoveredId !== event.payload.hoveredId) set({ hoveredId: event.payload.hoveredId });
|
|
88
|
+
});
|
|
89
|
+
const onClick = mapEventBus.on(MapEvents.click, (event) => {
|
|
90
|
+
if (event.payload.id !== mapId) return;
|
|
91
|
+
const { info } = event.payload;
|
|
92
|
+
const { layerId, selectedId, getEntityId: getId } = get();
|
|
93
|
+
if (info.layerId === layerId && info.object) {
|
|
94
|
+
const entityId = getId(info.object);
|
|
95
|
+
if (selectedId === entityId) coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {
|
|
96
|
+
mapId,
|
|
97
|
+
selectedId: void 0
|
|
98
|
+
});
|
|
99
|
+
else coffinCornerEventBus.emit(CoffinCornerEvents.SELECTED, {
|
|
100
|
+
selectedId: entityId,
|
|
101
|
+
mapId
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (info.index === -1 && selectedId !== void 0) coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {
|
|
106
|
+
mapId,
|
|
107
|
+
selectedId: void 0
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
const onHover = mapEventBus.on(MapEvents.hover, (event) => {
|
|
111
|
+
if (event.payload.id !== mapId) return;
|
|
112
|
+
const { info } = event.payload;
|
|
113
|
+
const { layerId, getEntityId: getId, hoveredId: currentHoveredId } = get();
|
|
114
|
+
const hoveredId = info.layerId === layerId && info.object ? getId(info.object) : void 0;
|
|
115
|
+
if (currentHoveredId !== hoveredId) coffinCornerEventBus.emit(CoffinCornerEvents.HOVERED, {
|
|
116
|
+
hoveredId,
|
|
117
|
+
mapId
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
return () => {
|
|
121
|
+
onSelected();
|
|
122
|
+
onDeselected();
|
|
123
|
+
onHovered();
|
|
124
|
+
onClick();
|
|
125
|
+
onHover();
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* Get selected entity ID imperatively (non-reactive).
|
|
131
|
+
*
|
|
132
|
+
* @param mapId - The map instance to query.
|
|
133
|
+
* @returns The selected entity ID, or undefined if nothing is selected.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const selectedId = getSelectedEntityId(mapId);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function getSelectedEntityId(mapId) {
|
|
141
|
+
return coffinCornerStore.get(mapId).selectedId;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get hovered entity ID imperatively (non-reactive).
|
|
145
|
+
*
|
|
146
|
+
* @param mapId - The map instance to query.
|
|
147
|
+
* @returns The hovered entity ID, or undefined if nothing is hovered.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const hoveredId = getHoveredEntityId(mapId);
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
function getHoveredEntityId(mapId) {
|
|
155
|
+
return coffinCornerStore.get(mapId).hoveredId;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Clear selection state (for tests/cleanup).
|
|
159
|
+
*
|
|
160
|
+
* @param mapId - The map instance to clear selection for.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* clearSelection(mapId);
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
function clearSelection(mapId) {
|
|
168
|
+
coffinCornerStore.clear(mapId);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { clearSelection, coffinCornerStore, getHoveredEntityId, getSelectedEntityId };
|
|
173
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","names":["defaultGetEntityId: GetEntityId"],"sources":["../../../../src/deckgl/extensions/coffin-corner/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'use client';\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { MapEvents } from '../../base-map/events';\nimport { CoffinCornerEvents } from './types';\nimport type { UniqueId } from '@accelint/core';\nimport type {\n MapClickEvent,\n MapEventType,\n MapHoverEvent,\n} from '../../base-map/types';\nimport type { CoffinCornerEvent, EntityId } from './types';\n\n/** Extracts an entity ID from a picked data item. */\n// biome-ignore lint/suspicious/noExplicitAny: Data type is unknown at store level.\ntype GetEntityId = (item: any) => EntityId;\n\n/** Default accessor — assumes the data item has an `id` property. */\n// biome-ignore lint/suspicious/noExplicitAny: Data type is unknown at store level.\nconst defaultGetEntityId: GetEntityId = (item: any) => item.id as EntityId;\n\n/**\n * State for coffin corner selection and hover.\n */\ntype CoffinCornerState = {\n /** Currently selected entity ID, or undefined if nothing is selected. */\n selectedId: EntityId | undefined;\n /** Currently hovered entity ID, or undefined if nothing is hovered. */\n hoveredId: EntityId | undefined;\n /** deck.gl layer ID used to filter map bus events to only this layer's interactions. */\n layerId: string | undefined;\n /** Accessor to extract an entity ID from a picked data item. */\n getEntityId: GetEntityId;\n};\n\n/**\n * Actions for coffin corner interactions.\n */\ntype CoffinCornerActions = {\n /** Select an entity by ID (emits domain event). Pass undefined to deselect. */\n setSelectedId: (id: EntityId | undefined) => void;\n /** Clear the current selection (emits deselect domain event). */\n deselect: () => void;\n /** Set the deck.gl layer ID used to filter map bus events. */\n setLayerId: (layerId: string) => void;\n /** Override the entity ID accessor for picked data items. */\n setGetEntityId: (fn: GetEntityId) => void;\n};\n\nconst coffinCornerEventBus = Broadcast.getInstance<CoffinCornerEvent>();\nconst mapEventBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Coffin corner store - manages selection and hover state per map instance.\n *\n * Subscribes to map bus events for click/hover interactions and translates\n * them into coffin corner domain events. The hook only needs to pass the\n * layer ID — all subscription management lives here.\n *\n * @example\n * ```tsx\n * const { state, setSelectedId, deselect } = coffinCornerStore.use(mapId);\n * ```\n */\nexport const coffinCornerStore = createMapStore<\n CoffinCornerState,\n CoffinCornerActions\n>({\n defaultState: {\n selectedId: undefined,\n hoveredId: undefined,\n layerId: undefined,\n getEntityId: defaultGetEntityId,\n },\n\n // Actions emit domain events rather than calling set() directly.\n // The bus subscriptions below translate events → state, ensuring\n // all state changes are observable by external subscribers.\n actions: (mapId, { get, set }) => ({\n setSelectedId: (id: EntityId | undefined) => {\n const currentId = get().selectedId;\n\n if (id == null && currentId != null) {\n coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {\n mapId,\n selectedId: undefined,\n });\n\n return;\n }\n\n if (id != null && currentId !== id) {\n coffinCornerEventBus.emit(CoffinCornerEvents.SELECTED, {\n selectedId: id,\n mapId,\n });\n\n return;\n }\n },\n\n deselect: () => {\n coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {\n mapId,\n selectedId: undefined,\n });\n },\n\n setLayerId: (layerId: string) => {\n if (get().layerId !== layerId) {\n set({ layerId });\n }\n },\n\n setGetEntityId: (fn: GetEntityId) => {\n if (get().getEntityId !== fn) {\n set({ getEntityId: fn });\n }\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Domain events → state\n const onSelected = coffinCornerEventBus.on(\n CoffinCornerEvents.SELECTED,\n (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n\n if (get().selectedId !== event.payload.selectedId) {\n set({ selectedId: event.payload.selectedId });\n }\n },\n );\n\n const onDeselected = coffinCornerEventBus.on(\n CoffinCornerEvents.DESELECTED,\n (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n\n if (get().selectedId !== undefined) {\n set({ selectedId: undefined });\n }\n },\n );\n\n const onHovered = coffinCornerEventBus.on(\n CoffinCornerEvents.HOVERED,\n (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().hoveredId !== event.payload.hoveredId) {\n set({ hoveredId: event.payload.hoveredId });\n }\n },\n );\n\n // Map clicks → domain events\n const onClick = mapEventBus.on(MapEvents.click, (event: MapClickEvent) => {\n if (event.payload.id !== mapId) {\n return;\n }\n\n const { info } = event.payload;\n const { layerId, selectedId, getEntityId: getId } = get();\n\n if (info.layerId === layerId && info.object) {\n const entityId = getId(info.object);\n\n if (selectedId === entityId) {\n coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {\n mapId,\n selectedId: undefined,\n });\n } else {\n coffinCornerEventBus.emit(CoffinCornerEvents.SELECTED, {\n selectedId: entityId,\n mapId,\n });\n }\n\n return;\n }\n\n if (info.index === -1 && selectedId !== undefined) {\n coffinCornerEventBus.emit(CoffinCornerEvents.DESELECTED, {\n mapId,\n selectedId: undefined,\n });\n }\n });\n\n // Map hovers → domain events\n const onHover = mapEventBus.on(MapEvents.hover, (event: MapHoverEvent) => {\n if (event.payload.id !== mapId) {\n return;\n }\n\n const { info } = event.payload;\n const {\n layerId,\n getEntityId: getId,\n hoveredId: currentHoveredId,\n } = get();\n\n const hoveredId =\n info.layerId === layerId && info.object\n ? getId(info.object)\n : undefined;\n\n if (currentHoveredId !== hoveredId) {\n coffinCornerEventBus.emit(CoffinCornerEvents.HOVERED, {\n hoveredId,\n mapId,\n });\n }\n });\n\n return () => {\n onSelected();\n onDeselected();\n onHovered();\n onClick();\n onHover();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get selected entity ID imperatively (non-reactive).\n *\n * @param mapId - The map instance to query.\n * @returns The selected entity ID, or undefined if nothing is selected.\n *\n * @example\n * ```typescript\n * const selectedId = getSelectedEntityId(mapId);\n * ```\n */\nexport function getSelectedEntityId(mapId: UniqueId): EntityId | undefined {\n return coffinCornerStore.get(mapId).selectedId;\n}\n\n/**\n * Get hovered entity ID imperatively (non-reactive).\n *\n * @param mapId - The map instance to query.\n * @returns The hovered entity ID, or undefined if nothing is hovered.\n *\n * @example\n * ```typescript\n * const hoveredId = getHoveredEntityId(mapId);\n * ```\n */\nexport function getHoveredEntityId(mapId: UniqueId): EntityId | undefined {\n return coffinCornerStore.get(mapId).hoveredId;\n}\n\n/**\n * Clear selection state (for tests/cleanup).\n *\n * @param mapId - The map instance to clear selection for.\n *\n * @example\n * ```typescript\n * clearSelection(mapId);\n * ```\n */\nexport function clearSelection(mapId: UniqueId): void {\n coffinCornerStore.clear(mapId);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Payload } from "@accelint/bus";
|
|
14
|
+
import { UniqueId } from "@accelint/core";
|
|
15
|
+
import { Rgba255Tuple } from "@accelint/predicates";
|
|
16
|
+
|
|
17
|
+
//#region src/deckgl/extensions/coffin-corner/types.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Props added by the CoffinCornerExtension.
|
|
20
|
+
*
|
|
21
|
+
* @template TLayerProps - The host layer's props type to intersect with.
|
|
22
|
+
*/
|
|
23
|
+
type CoffinCornerExtensionProps<TLayerProps = unknown> = {
|
|
24
|
+
/** The currently selected entity ID. */
|
|
25
|
+
selectedEntityId?: EntityId;
|
|
26
|
+
/** The currently hovered entity ID. */
|
|
27
|
+
hoveredEntityId?: EntityId;
|
|
28
|
+
/**
|
|
29
|
+
* RGBA color (0-255) for the selected-state bracket fill.
|
|
30
|
+
* Alpha modulates the bracket opacity.
|
|
31
|
+
* @default [57, 183, 250, 255] (#39B7FA, fully opaque)
|
|
32
|
+
*/
|
|
33
|
+
selectedCoffinCornerColor?: Rgba255Tuple;
|
|
34
|
+
/**
|
|
35
|
+
* Accessor to extract an entity ID from a data item. Matched against
|
|
36
|
+
* `selectedEntityId` and `hoveredEntityId` to drive the shader state.
|
|
37
|
+
* @default (item) => item.id
|
|
38
|
+
*/
|
|
39
|
+
getEntityId?: (item: any) => EntityId;
|
|
40
|
+
} & TLayerProps;
|
|
41
|
+
/**
|
|
42
|
+
* Event type constants for coffin corner interactions.
|
|
43
|
+
* Used as keys with the Broadcast event bus.
|
|
44
|
+
*/
|
|
45
|
+
declare const CoffinCornerEvents: {
|
|
46
|
+
/** Emitted when an entity is selected (click on a new entity). */
|
|
47
|
+
readonly SELECTED: "coffin-corner:selected";
|
|
48
|
+
/** Emitted when the current selection is cleared. */
|
|
49
|
+
readonly DESELECTED: "coffin-corner:deselected";
|
|
50
|
+
/** Emitted when the hovered entity changes. */
|
|
51
|
+
readonly HOVERED: "coffin-corner:hovered";
|
|
52
|
+
};
|
|
53
|
+
/** String literal union of all coffin corner event type keys. */
|
|
54
|
+
type CoffinCornerEventType = (typeof CoffinCornerEvents)[keyof typeof CoffinCornerEvents];
|
|
55
|
+
/** Unique identifier for an entity managed by the coffin corner extension. */
|
|
56
|
+
type EntityId = string | number;
|
|
57
|
+
/** Payload emitted when an entity is selected. Contains the selected ID and map instance. */
|
|
58
|
+
type CoffinCornerSelectedEvent = Payload<typeof CoffinCornerEvents.SELECTED, {
|
|
59
|
+
selectedId: EntityId;
|
|
60
|
+
mapId: UniqueId;
|
|
61
|
+
}>;
|
|
62
|
+
/** Payload emitted when selection is cleared. */
|
|
63
|
+
type CoffinCornerDeselectedEvent = Payload<typeof CoffinCornerEvents.DESELECTED, {
|
|
64
|
+
mapId: UniqueId;
|
|
65
|
+
selectedId: undefined;
|
|
66
|
+
}>;
|
|
67
|
+
/** Payload emitted when the hovered entity changes. `hoveredId` is undefined when hover ends. */
|
|
68
|
+
type CoffinCornerHoveredEvent = Payload<typeof CoffinCornerEvents.HOVERED, {
|
|
69
|
+
hoveredId?: EntityId;
|
|
70
|
+
mapId: UniqueId;
|
|
71
|
+
}>;
|
|
72
|
+
/** Union of all coffin corner event payloads for type-safe bus subscription. */
|
|
73
|
+
type CoffinCornerEvent = CoffinCornerSelectedEvent | CoffinCornerDeselectedEvent | CoffinCornerHoveredEvent;
|
|
74
|
+
//#endregion
|
|
75
|
+
export { CoffinCornerDeselectedEvent, CoffinCornerEvent, CoffinCornerEventType, CoffinCornerEvents, CoffinCornerExtensionProps, CoffinCornerHoveredEvent, CoffinCornerSelectedEvent, EntityId };
|
|
76
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
//#region src/deckgl/extensions/coffin-corner/types.ts
|
|
15
|
+
/**
|
|
16
|
+
* Event type constants for coffin corner interactions.
|
|
17
|
+
* Used as keys with the Broadcast event bus.
|
|
18
|
+
*/
|
|
19
|
+
const CoffinCornerEvents = {
|
|
20
|
+
SELECTED: "coffin-corner:selected",
|
|
21
|
+
DESELECTED: "coffin-corner:deselected",
|
|
22
|
+
HOVERED: "coffin-corner:hovered"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CoffinCornerEvents };
|
|
27
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/deckgl/extensions/coffin-corner/types.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 type { Payload } from '@accelint/bus';\nimport type { UniqueId } from '@accelint/core';\nimport type { Rgba255Tuple } from '@accelint/predicates';\n\n/**\n * Props added by the CoffinCornerExtension.\n *\n * @template TLayerProps - The host layer's props type to intersect with.\n */\nexport type CoffinCornerExtensionProps<TLayerProps = unknown> = {\n /** The currently selected entity ID. */\n selectedEntityId?: EntityId;\n /** The currently hovered entity ID. */\n hoveredEntityId?: EntityId;\n /**\n * RGBA color (0-255) for the selected-state bracket fill.\n * Alpha modulates the bracket opacity.\n * @default [57, 183, 250, 255] (#39B7FA, fully opaque)\n */\n selectedCoffinCornerColor?: Rgba255Tuple;\n /**\n * Accessor to extract an entity ID from a data item. Matched against\n * `selectedEntityId` and `hoveredEntityId` to drive the shader state.\n * @default (item) => item.id\n */\n // biome-ignore lint/suspicious/noExplicitAny: Data type is unknown at extension level.\n getEntityId?: (item: any) => EntityId;\n} & TLayerProps;\n\n/**\n * Event type constants for coffin corner interactions.\n * Used as keys with the Broadcast event bus.\n */\nexport const CoffinCornerEvents = {\n /** Emitted when an entity is selected (click on a new entity). */\n SELECTED: 'coffin-corner:selected',\n /** Emitted when the current selection is cleared. */\n DESELECTED: 'coffin-corner:deselected',\n /** Emitted when the hovered entity changes. */\n HOVERED: 'coffin-corner:hovered',\n} as const;\n\n/** String literal union of all coffin corner event type keys. */\nexport type CoffinCornerEventType =\n (typeof CoffinCornerEvents)[keyof typeof CoffinCornerEvents];\n\n/** Unique identifier for an entity managed by the coffin corner extension. */\nexport type EntityId = string | number;\n\n/** Payload emitted when an entity is selected. Contains the selected ID and map instance. */\nexport type CoffinCornerSelectedEvent = Payload<\n typeof CoffinCornerEvents.SELECTED,\n { selectedId: EntityId; mapId: UniqueId }\n>;\n\n/** Payload emitted when selection is cleared. */\nexport type CoffinCornerDeselectedEvent = Payload<\n typeof CoffinCornerEvents.DESELECTED,\n { mapId: UniqueId; selectedId: undefined }\n>;\n\n/** Payload emitted when the hovered entity changes. `hoveredId` is undefined when hover ends. */\nexport type CoffinCornerHoveredEvent = Payload<\n typeof CoffinCornerEvents.HOVERED,\n { hoveredId?: EntityId; mapId: UniqueId }\n>;\n\n/** Union of all coffin corner event payloads for type-safe bus subscription. */\nexport type CoffinCornerEvent =\n | CoffinCornerSelectedEvent\n | CoffinCornerDeselectedEvent\n | CoffinCornerHoveredEvent;\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6CA,MAAa,qBAAqB;CAEhC,UAAU;CAEV,YAAY;CAEZ,SAAS;CACV"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EntityId } from "./types.js";
|
|
14
|
+
import { UniqueId } from "@accelint/core";
|
|
15
|
+
|
|
16
|
+
//#region src/deckgl/extensions/coffin-corner/use-coffin-corner.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* Return type for useCoffinCorner hook
|
|
19
|
+
*/
|
|
20
|
+
type UseCoffinCornerReturn = {
|
|
21
|
+
/** Currently selected entity ID, or undefined if nothing selected */
|
|
22
|
+
selectedId: EntityId | undefined;
|
|
23
|
+
/** Currently hovered entity ID, or undefined if nothing hovered */
|
|
24
|
+
hoveredId: EntityId | undefined;
|
|
25
|
+
/** Set the selected entity ID */
|
|
26
|
+
setSelectedId: (id: EntityId | undefined) => void;
|
|
27
|
+
/** Clear the current selection */
|
|
28
|
+
deselect: () => void;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Options for the useCoffinCorner hook.
|
|
32
|
+
*/
|
|
33
|
+
type UseCoffinCornerOptions = {
|
|
34
|
+
/**
|
|
35
|
+
* Accessor to extract an entity ID from a picked data item.
|
|
36
|
+
* Must match the `getEntityId` prop passed to the CoffinCornerExtension.
|
|
37
|
+
* Should be a stable reference (module-level or `useCallback`) to avoid unnecessary store updates on every render.
|
|
38
|
+
* @default (item) => item.id
|
|
39
|
+
*/
|
|
40
|
+
getEntityId?: (item: any) => EntityId;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Hook to manage coffin corner entity selection state.
|
|
44
|
+
*
|
|
45
|
+
* Automatically subscribes to map bus events for the given map and layer,
|
|
46
|
+
* so you don't need onClick/onHover callbacks on the layer.
|
|
47
|
+
*
|
|
48
|
+
* @param mapId - The BaseMap instance ID
|
|
49
|
+
* @param layerId - The deck.gl layer ID to listen for interactions on
|
|
50
|
+
* @param options - Optional configuration for entity ID extraction
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* import { useCoffinCorner } from '@accelint/map-toolkit/deckgl/extensions/coffin-corner';
|
|
55
|
+
*
|
|
56
|
+
* function MapWithIcons({ mapId }) {
|
|
57
|
+
* const { selectedId, hoveredId } = useCoffinCorner(mapId, 'base-icons');
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <iconLayer
|
|
61
|
+
* id="base-icons"
|
|
62
|
+
* pickable
|
|
63
|
+
* extensions={[coffinCornerExtension]}
|
|
64
|
+
* selectedEntityId={selectedId}
|
|
65
|
+
* hoveredEntityId={hoveredId}
|
|
66
|
+
* />
|
|
67
|
+
* );
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example Custom entity ID accessor
|
|
72
|
+
* ```tsx
|
|
73
|
+
* const { selectedId, hoveredId } = useCoffinCorner(mapId, 'geojson-icons', {
|
|
74
|
+
* getEntityId: (d) => d.properties?.shapeId,
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function useCoffinCorner(mapId: UniqueId, layerId: string, options?: UseCoffinCornerOptions): UseCoffinCornerReturn;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { UseCoffinCornerOptions, UseCoffinCornerReturn, useCoffinCorner };
|
|
81
|
+
//# sourceMappingURL=use-coffin-corner.d.ts.map
|