@defra/interactive-map 0.0.17-alpha → 0.0.19-alpha

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 (185) hide show
  1. package/assets/css/docusaurus.css +58 -34
  2. package/dist/css/index.css +1 -1
  3. package/dist/esm/im-core.js +1 -1
  4. package/dist/esm/im-shell.js +1 -1
  5. package/dist/umd/im-core.js +1 -1
  6. package/dist/umd/index.js +1 -1
  7. package/docs/api/context.md +53 -7
  8. package/docs/api/map-style-config.md +41 -2
  9. package/docs/api/marker-config.md +53 -11
  10. package/docs/api/panel-definition.md +16 -0
  11. package/docs/api/symbol-config.md +160 -0
  12. package/docs/api/symbol-registry.md +115 -0
  13. package/docs/api.md +50 -23
  14. package/docs/assets/basic-map.jpg +0 -0
  15. package/docs/assets/button-first.jpg +0 -0
  16. package/docs/assets/maker-panel.jpg +0 -0
  17. package/docs/examples/add-marker-with-panel.mdx +59 -0
  18. package/docs/examples/basic-map.mdx +24 -0
  19. package/docs/examples/button-map.mdx +24 -0
  20. package/docs/examples/index.mdx +49 -0
  21. package/docs/index.mdx +1 -1
  22. package/docs/plugins/datasets.md +105 -9
  23. package/docs/plugins/interact.md +100 -44
  24. package/docs/plugins/search.md +15 -3
  25. package/docs/plugins.md +1 -1
  26. package/docusaurus.config.cjs +9 -1
  27. package/package.json +1 -1
  28. package/plugins/beta/datasets/dist/css/index.css +32 -14
  29. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  30. package/plugins/beta/datasets/dist/esm/index.js +1 -1
  31. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  32. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  33. package/plugins/beta/datasets/src/DatasetsInit.jsx +9 -4
  34. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +57 -11
  35. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +14 -8
  36. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +155 -53
  37. package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +27 -0
  38. package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +31 -0
  39. package/plugins/beta/datasets/src/api/addDataset.js +1 -1
  40. package/plugins/beta/datasets/src/api/setData.js +4 -2
  41. package/plugins/beta/datasets/src/api/setStyle.js +2 -2
  42. package/plugins/beta/datasets/src/components/EmptyKey.jsx +7 -0
  43. package/plugins/beta/datasets/src/components/EmptyKey.test.jsx +21 -0
  44. package/plugins/beta/datasets/src/components/KeySvg.jsx +24 -0
  45. package/plugins/beta/datasets/src/components/KeySvgLine.jsx +19 -0
  46. package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +15 -0
  47. package/plugins/beta/datasets/src/components/KeySvgRect.jsx +22 -0
  48. package/plugins/beta/datasets/src/components/KeySvgSymbol.jsx +16 -0
  49. package/plugins/beta/datasets/src/components/svgProperties.js +20 -0
  50. package/plugins/beta/datasets/src/datasets.js +13 -4
  51. package/plugins/beta/datasets/src/defaults.js +4 -2
  52. package/plugins/beta/datasets/src/index.js +2 -1
  53. package/plugins/beta/datasets/src/manifest.js +1 -1
  54. package/plugins/beta/datasets/src/panels/Key.jsx +11 -89
  55. package/plugins/beta/datasets/src/panels/Key.module.scss +24 -13
  56. package/plugins/beta/datasets/src/panels/Layers.module.scss +13 -7
  57. package/plugins/beta/datasets/src/reducer.js +6 -0
  58. package/plugins/beta/datasets/src/reducers/keyReducer.js +34 -0
  59. package/plugins/beta/datasets/src/utils/mergeSublayer.js +8 -0
  60. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  61. package/plugins/beta/draw-es/src/DrawInit.jsx +3 -2
  62. package/plugins/beta/draw-ml/dist/css/index.css +3 -0
  63. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  64. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  65. package/plugins/beta/draw-ml/dist/umd/index.js +1 -1
  66. package/plugins/beta/draw-ml/src/DrawInit.jsx +4 -3
  67. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  68. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  69. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  70. package/plugins/beta/map-styles/src/MapStyles.jsx +5 -4
  71. package/plugins/beta/map-styles/src/MapStylesInit.jsx +5 -4
  72. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  73. package/plugins/beta/scale-bar/src/scaleBar.scss +1 -0
  74. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  75. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  76. package/plugins/interact/dist/umd/index.js +1 -1
  77. package/plugins/interact/src/InteractInit.jsx +19 -8
  78. package/plugins/interact/src/InteractInit.test.js +26 -6
  79. package/plugins/interact/src/api/clear.js +1 -1
  80. package/plugins/interact/src/api/enable.test.js +7 -7
  81. package/plugins/interact/src/api/selectMarker.js +14 -0
  82. package/plugins/interact/src/api/selectMarker.test.js +25 -0
  83. package/plugins/interact/src/api/unselectMarker.js +14 -0
  84. package/plugins/interact/src/api/unselectMarker.test.js +14 -0
  85. package/plugins/interact/src/defaults.js +4 -6
  86. package/plugins/interact/src/events.js +27 -36
  87. package/plugins/interact/src/events.test.js +119 -90
  88. package/plugins/interact/src/hooks/useHighlightSync.js +3 -3
  89. package/plugins/interact/src/hooks/useHighlightSync.test.js +6 -6
  90. package/plugins/interact/src/hooks/useHoverCursor.js +10 -0
  91. package/plugins/interact/src/hooks/useHoverCursor.test.js +44 -0
  92. package/plugins/interact/src/hooks/useInteractionHandlers.js +111 -69
  93. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +147 -32
  94. package/plugins/interact/src/manifest.js +10 -2
  95. package/plugins/interact/src/reducer.js +59 -5
  96. package/plugins/interact/src/reducer.test.js +100 -12
  97. package/plugins/interact/src/utils/buildStylesMap.js +17 -4
  98. package/plugins/interact/src/utils/buildStylesMap.test.js +16 -2
  99. package/plugins/interact/src/utils/featureQueries.js +11 -6
  100. package/plugins/interact/src/utils/featureQueries.test.js +8 -1
  101. package/plugins/interact/src/utils/interactionModes.js +12 -0
  102. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  103. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  104. package/plugins/search/src/Search.jsx +3 -1
  105. package/plugins/search/src/events/fetchSuggestions.js +6 -4
  106. package/plugins/search/src/events/fetchSuggestions.test.js +26 -4
  107. package/plugins/search/src/events/formHandlers.js +3 -3
  108. package/plugins/search/src/events/formHandlers.test.js +1 -1
  109. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  110. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  111. package/plugins/search/src/utils/updateMap.js +3 -3
  112. package/plugins/search/src/utils/updateMap.test.js +3 -3
  113. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  114. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  115. package/providers/maplibre/dist/umd/index.js +1 -1
  116. package/providers/maplibre/src/appEvents.js +7 -0
  117. package/providers/maplibre/src/appEvents.test.js +18 -4
  118. package/providers/maplibre/src/maplibreProvider.js +52 -0
  119. package/providers/maplibre/src/maplibreProvider.test.js +105 -1
  120. package/providers/maplibre/src/utils/highlightFeatures.js +36 -7
  121. package/providers/maplibre/src/utils/highlightFeatures.test.js +153 -96
  122. package/providers/maplibre/src/utils/hoverCursor.js +61 -0
  123. package/providers/maplibre/src/utils/hoverCursor.test.js +130 -0
  124. package/providers/maplibre/src/utils/patternImages.js +70 -0
  125. package/providers/maplibre/src/utils/patternImages.test.js +180 -0
  126. package/providers/maplibre/src/utils/queryFeatures.js +38 -16
  127. package/providers/maplibre/src/utils/queryFeatures.test.js +20 -3
  128. package/providers/maplibre/src/utils/rasteriseToImageData.js +30 -0
  129. package/providers/maplibre/src/utils/rasteriseToImageData.test.js +69 -0
  130. package/providers/maplibre/src/utils/symbolImages.js +147 -0
  131. package/providers/maplibre/src/utils/symbolImages.test.js +248 -0
  132. package/src/App/components/Markers/Markers.jsx +122 -27
  133. package/src/App/components/Markers/Markers.module.scss +0 -10
  134. package/src/App/components/Markers/Markers.test.jsx +246 -0
  135. package/src/App/components/Panel/Panel.jsx +6 -6
  136. package/src/App/components/Panel/Panel.test.jsx +37 -0
  137. package/src/App/components/Viewport/Viewport.jsx +5 -15
  138. package/src/App/components/Viewport/Viewport.module.scss +2 -0
  139. package/src/App/components/Viewport/Viewport.test.jsx +16 -33
  140. package/src/App/hooks/useInterfaceAPI.js +7 -7
  141. package/src/App/hooks/useInterfaceAPI.test.js +162 -0
  142. package/src/App/hooks/useLayoutMeasurements.js +64 -72
  143. package/src/App/hooks/useMarkersAPI.js +2 -5
  144. package/src/App/hooks/useMarkersAPI.test.js +4 -4
  145. package/src/App/layout/Layout.jsx +3 -3
  146. package/src/App/layout/Layout.test.jsx +4 -2
  147. package/src/App/layout/layout.module.scss +1 -8
  148. package/src/App/renderer/HtmlElementHost.jsx +10 -5
  149. package/src/App/renderer/mapPanels.js +2 -1
  150. package/src/App/store/ServiceProvider.jsx +7 -5
  151. package/src/App/store/appActionsMap.js +4 -4
  152. package/src/App/store/appActionsMap.test.js +10 -0
  153. package/src/App/store/mapActionsMap.js +4 -6
  154. package/src/App/store/mapActionsMap.test.js +3 -2
  155. package/src/App/store/mapReducer.js +2 -1
  156. package/src/InteractiveMap/InteractiveMap.js +59 -11
  157. package/src/InteractiveMap/InteractiveMap.test.js +126 -4
  158. package/src/InteractiveMap/domStateManager.js +18 -6
  159. package/src/InteractiveMap/domStateManager.test.js +21 -0
  160. package/src/InteractiveMap/historyManager.js +28 -16
  161. package/src/InteractiveMap/historyManager.test.js +17 -0
  162. package/src/config/appConfig.js +2 -7
  163. package/src/config/appConfig.test.js +4 -15
  164. package/src/config/defaults.js +2 -3
  165. package/src/config/events.js +20 -21
  166. package/src/config/mapTheme.js +56 -0
  167. package/src/config/patternConfig.js +16 -0
  168. package/src/config/symbolConfig.js +80 -0
  169. package/src/scss/settings/_colors.scss +0 -9
  170. package/src/services/closeApp.js +1 -10
  171. package/src/services/closeApp.test.js +3 -43
  172. package/src/services/patternRegistry.js +40 -0
  173. package/src/services/patternRegistry.test.js +48 -0
  174. package/src/services/symbolRegistry.js +113 -0
  175. package/src/services/symbolRegistry.test.js +262 -0
  176. package/src/types.js +99 -12
  177. package/src/utils/mapStateSync.js +48 -10
  178. package/src/utils/mapStateSync.test.js +29 -9
  179. package/src/utils/patternUtils.js +94 -0
  180. package/src/utils/patternUtils.test.js +160 -0
  181. package/src/utils/symbolUtils.js +85 -0
  182. package/src/utils/symbolUtils.test.js +156 -0
  183. package/docs/examples.mdx +0 -70
  184. package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +0 -48
  185. package/plugins/beta/datasets/src/styles/patterns.js +0 -157
@@ -1,7 +1,10 @@
1
1
  import { useEffect, useRef } from 'react'
2
+ import { EVENTS } from '../../../src/config/events.js'
2
3
  import { useInteractionHandlers } from './hooks/useInteractionHandlers.js'
3
4
  import { useHighlightSync } from './hooks/useHighlightSync.js'
5
+ import { useHoverCursor } from './hooks/useHoverCursor.js'
4
6
  import { attachEvents } from './events.js'
7
+ import { isSelectMarkerOnly } from './utils/interactionModes.js'
5
8
 
6
9
  export const InteractInit = ({
7
10
  appState,
@@ -12,11 +15,12 @@ export const InteractInit = ({
12
15
  pluginState
13
16
  }) => {
14
17
  const { interfaceType } = appState
15
- const { dispatch, enabled, selectedFeatures, selectionBounds } = pluginState
16
- const { events, eventBus, closeApp } = services
18
+ const { dispatch, enabled, selectedFeatures, selectionBounds, interactionModes, layers } = pluginState
19
+ const { eventBus, closeApp } = services
17
20
  const { crossHair, mapStyle } = mapState
18
21
 
19
22
  const isTouchOrKeyboard = ['touch', 'keyboard'].includes(interfaceType)
23
+ const selectMarkerOnly = isSelectMarkerOnly(interactionModes)
20
24
 
21
25
  // Core interaction logic (click > select/marker)
22
26
  const { handleInteraction } = useInteractionHandlers({
@@ -56,18 +60,25 @@ export const InteractInit = ({
56
60
  selectedFeatures,
57
61
  selectionBounds,
58
62
  dispatch,
59
- events,
63
+ events: EVENTS,
60
64
  eventBus
61
65
  })
62
66
 
67
+ // Notify other components (e.g. Markers) whether interact is active
68
+ useEffect(() => {
69
+ eventBus.emit('interact:active', { active: enabled, interactionModes })
70
+ }, [enabled, interactionModes])
71
+
72
+ useHoverCursor(mapProvider, enabled, interactionModes, layers)
73
+
63
74
  // Toggle target marker visibility
64
75
  useEffect(() => {
65
- if (enabled && isTouchOrKeyboard) {
76
+ if (enabled && isTouchOrKeyboard && !(interfaceType === 'touch' && selectMarkerOnly)) {
66
77
  crossHair.fixAtCenter()
67
78
  } else {
68
79
  crossHair.hide()
69
80
  }
70
- }, [enabled, interfaceType])
81
+ }, [enabled, interfaceType, interactionModes])
71
82
 
72
83
  useEffect(() => {
73
84
  if (!pluginState.enabled) {
@@ -79,15 +90,15 @@ export const InteractInit = ({
79
90
  mapState,
80
91
  getPluginState: () => pluginStateRef.current,
81
92
  buttonConfig,
82
- events,
93
+ events: EVENTS,
83
94
  eventBus,
84
- handleInteraction: (e) => handleInteractionRef.current(e),
95
+ handleInteraction: (event) => handleInteractionRef.current(event),
85
96
  clickReadyRef,
86
97
  closeApp
87
98
  })
88
99
 
89
100
  return cleanupEvents
90
- }, [pluginState.enabled, buttonConfig, events, eventBus, closeApp])
101
+ }, [pluginState.enabled, buttonConfig, eventBus, closeApp])
91
102
 
92
103
  return null
93
104
  }
@@ -1,11 +1,14 @@
1
1
  import { act, render } from '@testing-library/react'
2
+ import { EVENTS } from '../../../src/config/events.js'
2
3
  import { InteractInit } from './InteractInit.jsx'
3
4
  import { useInteractionHandlers } from './hooks/useInteractionHandlers.js'
4
5
  import { useHighlightSync } from './hooks/useHighlightSync.js'
6
+ import { useHoverCursor } from './hooks/useHoverCursor.js'
5
7
  import { attachEvents } from './events.js'
6
8
 
7
9
  jest.mock('./hooks/useInteractionHandlers.js')
8
10
  jest.mock('./hooks/useHighlightSync.js')
11
+ jest.mock('./hooks/useHoverCursor.js')
9
12
  jest.mock('./events.js')
10
13
 
11
14
  describe('InteractInit', () => {
@@ -19,15 +22,24 @@ describe('InteractInit', () => {
19
22
 
20
23
  useInteractionHandlers.mockReturnValue({ handleInteraction: handleInteractionMock })
21
24
  useHighlightSync.mockReturnValue(undefined)
25
+ useHoverCursor.mockReturnValue(undefined)
22
26
  attachEvents.mockReturnValue(cleanupMock)
23
27
 
24
28
  props = {
25
- appState: { interfaceType: 'mouse' },
29
+ appState: { interfaceType: 'mouse', layoutRefs: { viewportRef: { current: null } } },
26
30
  mapState: { crossHair: { fixAtCenter: jest.fn(), hide: jest.fn() }, mapStyle: {} },
27
- services: { events: {}, eventBus: {}, closeApp: jest.fn() },
31
+ services: { eventBus: { emit: jest.fn() }, closeApp: jest.fn() },
28
32
  buttonConfig: {},
29
- mapProvider: {},
30
- pluginState: { dispatch: jest.fn(), enabled: true, selectedFeatures: [], selectionBounds: {} }
33
+ mapProvider: { setHoverCursor: jest.fn() },
34
+ pluginState: {
35
+ dispatch: jest.fn(),
36
+ enabled: true,
37
+ selectedFeatures: [],
38
+ selectedMarkers: [],
39
+ selectionBounds: {},
40
+ interactionModes: ['selectFeature'],
41
+ layers: []
42
+ }
31
43
  }
32
44
  })
33
45
 
@@ -50,7 +62,7 @@ describe('InteractInit', () => {
50
62
  pluginState: props.pluginState,
51
63
  selectedFeatures: props.pluginState.selectedFeatures,
52
64
  dispatch: props.pluginState.dispatch,
53
- events: props.services.events,
65
+ events: EVENTS,
54
66
  eventBus: props.services.eventBus
55
67
  }))
56
68
  })
@@ -75,7 +87,7 @@ describe('InteractInit', () => {
75
87
  handleInteraction: expect.any(Function),
76
88
  mapState: props.mapState,
77
89
  buttonConfig: props.buttonConfig,
78
- events: props.services.events,
90
+ events: EVENTS,
79
91
  eventBus: props.services.eventBus,
80
92
  closeApp: props.services.closeApp
81
93
  }))
@@ -92,6 +104,14 @@ describe('InteractInit', () => {
92
104
  expect(cleanupMock).toHaveBeenCalled()
93
105
  })
94
106
 
107
+ it('emits interact:active with active state and interactionModes on enable', () => {
108
+ render(<InteractInit {...props} />)
109
+ expect(props.services.eventBus.emit).toHaveBeenCalledWith('interact:active', {
110
+ active: true,
111
+ interactionModes: props.pluginState.interactionModes
112
+ })
113
+ })
114
+
95
115
  it('enables click handling after a macrotask', () => {
96
116
  jest.useFakeTimers()
97
117
  render(<InteractInit {...props} />)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Programatically clear selected features and removes the location marker.
2
+ * Programmatically clear all selected features and markers, and remove the location marker.
3
3
  *
4
4
  * @param {Object} params
5
5
  * @param {{ dispatch: Function }} params.pluginState
@@ -9,8 +9,8 @@ describe('enable', () => {
9
9
  })
10
10
 
11
11
  it('dispatches ENABLE with merged payload correctly', () => {
12
- const pluginConfig = { markerColor: 'blue' }
13
- const options = { interactionMode: 'select', markerColor: 'green', dataLayers: [{ layerId: 'test' }] }
12
+ const pluginConfig = { marker: { symbol: 'pin', backgroundColor: 'blue' } }
13
+ const options = { interactionModes: ['selectFeature'], marker: { symbol: 'circle', backgroundColor: 'green' }, layers: [{ layerId: 'test' }] }
14
14
 
15
15
  enable({ pluginState: { dispatch }, pluginConfig }, options)
16
16
 
@@ -18,16 +18,16 @@ describe('enable', () => {
18
18
  expect(dispatch).toHaveBeenCalledWith({
19
19
  type: 'ENABLE',
20
20
  payload: expect.objectContaining({
21
- interactionMode: 'select', // options override DEFAULTS
22
- multiSelect: DEFAULTS.multiSelect, // default preserved
23
- markerColor: 'green', // options override pluginConfig
24
- dataLayers: [{ layerId: 'test' }] // options passed
21
+ interactionModes: ['selectFeature'],
22
+ multiSelect: DEFAULTS.multiSelect,
23
+ marker: { symbol: 'circle', backgroundColor: 'green' },
24
+ layers: [{ layerId: 'test' }]
25
25
  })
26
26
  })
27
27
  })
28
28
 
29
29
  it('handles empty or undefined options', () => {
30
- const pluginConfig = { markerColor: 'blue' }
30
+ const pluginConfig = { marker: { symbol: 'pin', backgroundColor: 'blue' } }
31
31
 
32
32
  enable({ pluginState: { dispatch }, pluginConfig }, {})
33
33
  expect(dispatch).toHaveBeenCalledTimes(1)
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Programmatically select a marker. Dispatches directly to the reducer so it
3
+ * works immediately without waiting for React to re-render after `enable()`.
4
+ *
5
+ * @param {Object} params
6
+ * @param {{ dispatch: Function, multiSelect: boolean }} params.pluginState
7
+ * @param {string} markerId - The ID of the marker to select
8
+ */
9
+ export const selectMarker = ({ pluginState }, markerId) => {
10
+ pluginState.dispatch({
11
+ type: 'SELECT_MARKER',
12
+ payload: { markerId, multiSelect: pluginState.multiSelect }
13
+ })
14
+ }
@@ -0,0 +1,25 @@
1
+ import { selectMarker } from './selectMarker.js'
2
+
3
+ describe('selectMarker', () => {
4
+ it('dispatches SELECT_MARKER with markerId and multiSelect from pluginState', () => {
5
+ const dispatch = jest.fn()
6
+
7
+ selectMarker({ pluginState: { dispatch, multiSelect: false } }, 'm1')
8
+
9
+ expect(dispatch).toHaveBeenCalledWith({
10
+ type: 'SELECT_MARKER',
11
+ payload: { markerId: 'm1', multiSelect: false }
12
+ })
13
+ })
14
+
15
+ it('passes multiSelect: true when pluginState.multiSelect is true', () => {
16
+ const dispatch = jest.fn()
17
+
18
+ selectMarker({ pluginState: { dispatch, multiSelect: true } }, 'm1')
19
+
20
+ expect(dispatch).toHaveBeenCalledWith({
21
+ type: 'SELECT_MARKER',
22
+ payload: { markerId: 'm1', multiSelect: true }
23
+ })
24
+ })
25
+ })
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Programmatically unselect a marker. Dispatches directly to the reducer so it
3
+ * works immediately without waiting for React to re-render after `enable()`.
4
+ *
5
+ * @param {Object} params
6
+ * @param {{ dispatch: Function }} params.pluginState
7
+ * @param {string} markerId - The ID of the marker to unselect
8
+ */
9
+ export const unselectMarker = ({ pluginState }, markerId) => {
10
+ pluginState.dispatch({
11
+ type: 'UNSELECT_MARKER',
12
+ payload: { markerId }
13
+ })
14
+ }
@@ -0,0 +1,14 @@
1
+ import { unselectMarker } from './unselectMarker.js'
2
+
3
+ describe('unselectMarker', () => {
4
+ it('dispatches UNSELECT_MARKER with markerId', () => {
5
+ const dispatch = jest.fn()
6
+
7
+ unselectMarker({ pluginState: { dispatch } }, 'm1')
8
+
9
+ expect(dispatch).toHaveBeenCalledWith({
10
+ type: 'UNSELECT_MARKER',
11
+ payload: { markerId: 'm1' }
12
+ })
13
+ })
14
+ })
@@ -1,11 +1,9 @@
1
1
  export const DEFAULTS = {
2
- tolerance: 10, // Used for cross hair and click events
3
- interactionMode: 'marker',
2
+ tolerance: 10,
3
+ interactionModes: ['selectMarker'],
4
4
  multiSelect: false,
5
5
  contiguous: false,
6
6
  deselectOnClickOutside: false,
7
- markerColor: 'rgba(212,53,28,1)',
8
- selectedStroke: 'rgba(212,53,28,1)',
9
- selectedFill: 'rgba(255, 0, 0, 0.1)',
10
- selectedStrokeWidth: 2
7
+ marker: {},
8
+ selectedStrokeWidth: 3
11
9
  }
@@ -1,3 +1,10 @@
1
+ const buildDonePayload = (coords, selectedFeatures, selectedMarkers, selectionBounds) => ({
2
+ ...(coords && { coords }),
3
+ ...(!coords && selectedFeatures && { selectedFeatures }),
4
+ ...(!coords && selectedMarkers?.length && { selectedMarkers }),
5
+ ...(!coords && selectionBounds && { selectionBounds })
6
+ })
7
+
1
8
  // Helper for feature toggling logic
2
9
  const createFeatureHandler = (mapState, getPluginState) => (args, addToExisting) => {
3
10
  const pluginState = getPluginState()
@@ -12,6 +19,18 @@ const createFeatureHandler = (mapState, getPluginState) => (args, addToExisting)
12
19
  })
13
20
  }
14
21
 
22
+ const createKeyboardHandlers = (viewportRef, onSelectAtTarget) => {
23
+ let enterOnViewport = false
24
+ const handleKeydown = (event) => { enterOnViewport = event.key === 'Enter' && viewportRef.current === event.target }
25
+ const handleKeyup = (event) => {
26
+ if (event.key === 'Enter' && enterOnViewport) {
27
+ event.preventDefault()
28
+ onSelectAtTarget()
29
+ }
30
+ }
31
+ return { handleKeydown, handleKeyup }
32
+ }
33
+
15
34
  export function attachEvents ({
16
35
  getAppState,
17
36
  mapState,
@@ -24,59 +43,31 @@ export function attachEvents ({
24
43
  closeApp
25
44
  }) {
26
45
  const { selectDone, selectAtTarget, selectCancel } = buttonConfig
27
- const { viewportRef } = getAppState().layoutRefs
28
-
29
- // Keyboard Logic
30
- let enterOnViewport = false
31
- const handleKeydown = (e) => { enterOnViewport = e.key === 'Enter' && viewportRef.current === e.target }
32
- const handleKeyup = (e) => {
33
- if (e.key === 'Enter' && enterOnViewport) {
34
- e.preventDefault()
35
- handleSelectAtTarget()
36
- }
37
- }
38
46
 
39
- // Interaction Handlers
40
- const handleMapClick = (e) => {
41
- if (clickReadyRef.current) {
42
- handleInteraction(e)
43
- }
44
- }
45
47
  const handleSelectAtTarget = () => handleInteraction(mapState.crossHair.getDetail())
48
+ const handleMapClick = (mapEvent) => { if (clickReadyRef.current) { handleInteraction(mapEvent) } }
49
+
50
+ const { handleKeydown, handleKeyup } = createKeyboardHandlers(getAppState().layoutRefs.viewportRef, handleSelectAtTarget)
46
51
 
47
52
  const handleSelectDone = () => {
48
53
  const pluginState = getPluginState()
49
54
  const marker = mapState.markers.getMarker('location')
50
55
  const { coords } = marker || {}
51
- const { selectionBounds, selectedFeatures } = pluginState
52
-
53
- if (getAppState().disabledButtons.has('selectDone')) {
54
- return
55
- }
56
-
57
- eventBus.emit('interact:done', {
58
- ...(coords && { coords }),
59
- ...(!coords && selectedFeatures && { selectedFeatures }),
60
- ...(!coords && selectionBounds && { selectionBounds })
61
- })
62
-
63
- if (pluginState.closeOnAction ?? true) {
64
- closeApp()
65
- }
56
+ const { selectionBounds, selectedFeatures, selectedMarkers } = pluginState
57
+ if (getAppState().disabledButtons.has('selectDone')) { return }
58
+ eventBus.emit('interact:done', buildDonePayload(coords, selectedFeatures, selectedMarkers, selectionBounds))
59
+ if (pluginState.closeOnAction ?? true) { closeApp() }
66
60
  }
67
61
 
68
62
  const handleSelectCancel = () => {
69
63
  eventBus.emit('interact:cancel')
70
- if (getPluginState().closeOnAction ?? true) {
71
- closeApp()
72
- }
64
+ if (getPluginState().closeOnAction ?? true) { closeApp() }
73
65
  }
74
66
 
75
67
  const toggleFeature = createFeatureHandler(mapState, getPluginState)
76
68
  const handleSelect = (args) => toggleFeature(args, true)
77
69
  const handleUnselect = (args) => toggleFeature(args, false)
78
70
 
79
- // Attach Listeners
80
71
  document.addEventListener('keydown', handleKeydown)
81
72
  document.addEventListener('keyup', handleKeyup)
82
73
  eventBus.on(events.MAP_CLICK, handleMapClick)