@defra/interactive-map 0.0.16-alpha → 0.0.18-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 (224) hide show
  1. package/assets/images/slot-map.svg +264 -0
  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/slots.md +16 -15
  11. package/docs/api/symbol-config.md +160 -0
  12. package/docs/api/symbol-registry.md +115 -0
  13. package/docs/api.md +25 -22
  14. package/docs/getting-started.md +4 -1
  15. package/docs/plugins/datasets.md +657 -0
  16. package/docs/plugins/interact.md +68 -43
  17. package/docs/plugins/search.md +15 -3
  18. package/docs/plugins.md +1 -1
  19. package/package.json +2 -2
  20. package/plugins/beta/datasets/dist/css/index.css +103 -15
  21. package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
  22. package/plugins/beta/datasets/dist/esm/index.js +1 -1
  23. package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
  24. package/plugins/beta/datasets/dist/umd/index.js +1 -1
  25. package/plugins/beta/datasets/src/DatasetsInit.jsx +29 -9
  26. package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
  27. package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +159 -0
  28. package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +75 -0
  29. package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +440 -0
  30. package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +27 -0
  31. package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +31 -0
  32. package/plugins/beta/datasets/src/api/addDataset.js +2 -8
  33. package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
  34. package/plugins/beta/datasets/src/api/getStyle.js +13 -0
  35. package/plugins/beta/datasets/src/api/removeDataset.js +2 -44
  36. package/plugins/beta/datasets/src/api/setData.js +10 -0
  37. package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
  38. package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
  39. package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
  40. package/plugins/beta/datasets/src/api/setStyle.js +22 -0
  41. package/plugins/beta/datasets/src/components/EmptyKey.jsx +7 -0
  42. package/plugins/beta/datasets/src/components/EmptyKey.test.jsx +21 -0
  43. package/plugins/beta/datasets/src/components/KeySvg.jsx +24 -0
  44. package/plugins/beta/datasets/src/components/KeySvgLine.jsx +19 -0
  45. package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +15 -0
  46. package/plugins/beta/datasets/src/components/KeySvgRect.jsx +22 -0
  47. package/plugins/beta/datasets/src/components/KeySvgSymbol.jsx +16 -0
  48. package/plugins/beta/datasets/src/components/svgProperties.js +20 -0
  49. package/plugins/beta/datasets/src/datasets.js +39 -56
  50. package/plugins/beta/datasets/src/defaults.js +44 -8
  51. package/plugins/beta/datasets/src/fetch/createDynamicSource.js +34 -25
  52. package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
  53. package/plugins/beta/datasets/src/index.js +2 -1
  54. package/plugins/beta/datasets/src/manifest.js +25 -17
  55. package/plugins/beta/datasets/src/panels/Key.jsx +51 -51
  56. package/plugins/beta/datasets/src/panels/Key.module.scss +59 -9
  57. package/plugins/beta/datasets/src/panels/Layers.jsx +132 -29
  58. package/plugins/beta/datasets/src/panels/Layers.module.scss +56 -8
  59. package/plugins/beta/datasets/src/reducer.js +134 -9
  60. package/plugins/beta/datasets/src/reducers/keyReducer.js +34 -0
  61. package/plugins/beta/datasets/src/utils/bbox.js +7 -5
  62. package/plugins/beta/datasets/src/utils/filters.js +5 -2
  63. package/plugins/beta/datasets/src/utils/mergeSublayer.js +86 -0
  64. package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
  65. package/plugins/beta/draw-es/src/DrawInit.jsx +3 -2
  66. package/plugins/beta/draw-ml/dist/css/index.css +21 -1
  67. package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
  68. package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
  69. package/plugins/beta/draw-ml/dist/umd/index.js +1 -1
  70. package/plugins/beta/draw-ml/src/DrawInit.jsx +4 -3
  71. package/plugins/beta/draw-ml/src/draw.scss +0 -7
  72. package/plugins/beta/draw-ml/src/manifest.js +16 -16
  73. package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
  74. package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
  75. package/plugins/beta/frame/src/Frame.jsx +5 -5
  76. package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
  77. package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
  78. package/plugins/beta/map-styles/dist/umd/index.js +1 -1
  79. package/plugins/beta/map-styles/src/MapStyles.jsx +5 -4
  80. package/plugins/beta/map-styles/src/MapStylesInit.jsx +5 -4
  81. package/plugins/beta/map-styles/src/manifest.js +1 -1
  82. package/plugins/beta/scale-bar/dist/css/index.css +1 -1
  83. package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
  84. package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
  85. package/plugins/beta/scale-bar/src/index.test.js +3 -3
  86. package/plugins/beta/scale-bar/src/manifest.js +3 -3
  87. package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
  88. package/plugins/interact/dist/css/index.css +1 -1
  89. package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
  90. package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
  91. package/plugins/interact/dist/umd/index.js +1 -1
  92. package/plugins/interact/src/InteractInit.jsx +14 -5
  93. package/plugins/interact/src/InteractInit.test.js +26 -6
  94. package/plugins/interact/src/api/enable.test.js +7 -7
  95. package/plugins/interact/src/defaults.js +4 -6
  96. package/plugins/interact/src/events.js +9 -6
  97. package/plugins/interact/src/events.test.js +28 -4
  98. package/plugins/interact/src/hooks/useHighlightSync.js +3 -3
  99. package/plugins/interact/src/hooks/useHighlightSync.test.js +6 -6
  100. package/plugins/interact/src/hooks/useHoverCursor.js +10 -0
  101. package/plugins/interact/src/hooks/useHoverCursor.test.js +44 -0
  102. package/plugins/interact/src/hooks/useInteractionHandlers.js +111 -69
  103. package/plugins/interact/src/hooks/useInteractionHandlers.test.js +147 -32
  104. package/plugins/interact/src/interact.scss +0 -7
  105. package/plugins/interact/src/manifest.js +14 -18
  106. package/plugins/interact/src/manifest.test.js +3 -1
  107. package/plugins/interact/src/reducer.js +23 -4
  108. package/plugins/interact/src/reducer.test.js +60 -11
  109. package/plugins/interact/src/utils/buildStylesMap.js +17 -4
  110. package/plugins/interact/src/utils/buildStylesMap.test.js +16 -2
  111. package/plugins/interact/src/utils/featureQueries.js +11 -6
  112. package/plugins/interact/src/utils/featureQueries.test.js +8 -1
  113. package/plugins/search/dist/css/index.css +1 -1
  114. package/plugins/search/dist/esm/im-search-plugin.js +1 -1
  115. package/plugins/search/dist/umd/im-search-plugin.js +1 -1
  116. package/plugins/search/src/Search.jsx +3 -1
  117. package/plugins/search/src/components/Form/Form.module.scss +2 -1
  118. package/plugins/search/src/events/fetchSuggestions.js +6 -4
  119. package/plugins/search/src/events/fetchSuggestions.test.js +26 -4
  120. package/plugins/search/src/events/formHandlers.js +3 -3
  121. package/plugins/search/src/events/formHandlers.test.js +1 -1
  122. package/plugins/search/src/events/suggestionHandlers.js +2 -2
  123. package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
  124. package/plugins/search/src/utils/updateMap.js +3 -3
  125. package/plugins/search/src/utils/updateMap.test.js +3 -3
  126. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  127. package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
  128. package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
  129. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  130. package/providers/maplibre/dist/umd/index.js +1 -1
  131. package/providers/maplibre/src/appEvents.js +7 -0
  132. package/providers/maplibre/src/appEvents.test.js +18 -4
  133. package/providers/maplibre/src/maplibreProvider.js +52 -0
  134. package/providers/maplibre/src/maplibreProvider.test.js +105 -1
  135. package/providers/maplibre/src/utils/highlightFeatures.js +37 -7
  136. package/providers/maplibre/src/utils/highlightFeatures.test.js +153 -95
  137. package/providers/maplibre/src/utils/hoverCursor.js +61 -0
  138. package/providers/maplibre/src/utils/hoverCursor.test.js +130 -0
  139. package/providers/maplibre/src/utils/patternImages.js +70 -0
  140. package/providers/maplibre/src/utils/patternImages.test.js +180 -0
  141. package/providers/maplibre/src/utils/queryFeatures.js +38 -16
  142. package/providers/maplibre/src/utils/queryFeatures.test.js +20 -3
  143. package/providers/maplibre/src/utils/rasteriseToImageData.js +30 -0
  144. package/providers/maplibre/src/utils/rasteriseToImageData.test.js +69 -0
  145. package/providers/maplibre/src/utils/symbolImages.js +147 -0
  146. package/providers/maplibre/src/utils/symbolImages.test.js +248 -0
  147. package/src/App/components/Actions/Actions.jsx +2 -2
  148. package/src/App/components/Actions/Actions.module.scss +0 -7
  149. package/src/App/components/Actions/Actions.test.jsx +1 -1
  150. package/src/App/components/Icon/Icon.jsx +3 -2
  151. package/src/App/components/Icon/Icon.module.scss +4 -0
  152. package/src/App/components/Icon/Icon.test.jsx +43 -4
  153. package/src/App/components/MapButton/MapButton.jsx +42 -17
  154. package/src/App/components/MapButton/MapButton.module.scss +4 -13
  155. package/src/App/components/MapButton/MapButton.test.jsx +27 -3
  156. package/src/App/components/Markers/Markers.jsx +122 -27
  157. package/src/App/components/Markers/Markers.module.scss +0 -10
  158. package/src/App/components/Markers/Markers.test.jsx +246 -0
  159. package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
  160. package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
  161. package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
  162. package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
  163. package/src/App/hooks/useButtonStateEvaluator.js +12 -2
  164. package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
  165. package/src/App/hooks/useInterfaceAPI.js +6 -0
  166. package/src/App/hooks/useInterfaceAPI.test.js +156 -0
  167. package/src/App/hooks/useLayoutMeasurements.js +84 -18
  168. package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
  169. package/src/App/hooks/useMarkersAPI.js +2 -5
  170. package/src/App/hooks/useMarkersAPI.test.js +4 -4
  171. package/src/App/layout/Layout.jsx +14 -9
  172. package/src/App/layout/Layout.test.jsx +6 -4
  173. package/src/App/layout/layout.module.scss +67 -29
  174. package/src/App/registry/pluginRegistry.js +1 -1
  175. package/src/App/renderer/HtmlElementHost.jsx +2 -1
  176. package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
  177. package/src/App/renderer/mapButtons.js +1 -1
  178. package/src/App/renderer/mapPanels.test.js +2 -2
  179. package/src/App/renderer/slotHelpers.js +2 -2
  180. package/src/App/renderer/slotHelpers.test.js +5 -5
  181. package/src/App/renderer/slots.js +9 -5
  182. package/src/App/store/AppProvider.jsx +3 -1
  183. package/src/App/store/AppProvider.test.jsx +1 -1
  184. package/src/App/store/ServiceProvider.jsx +8 -4
  185. package/src/App/store/appActionsMap.js +16 -0
  186. package/src/App/store/appActionsMap.test.js +27 -0
  187. package/src/App/store/appDispatchMiddleware.js +1 -1
  188. package/src/App/store/appDispatchMiddleware.test.js +2 -2
  189. package/src/App/store/appReducer.js +2 -0
  190. package/src/App/store/mapActionsMap.js +4 -6
  191. package/src/App/store/mapActionsMap.test.js +3 -2
  192. package/src/App/store/mapReducer.js +2 -1
  193. package/src/InteractiveMap/InteractiveMap.js +4 -0
  194. package/src/config/appConfig.js +5 -8
  195. package/src/config/appConfig.test.js +1 -2
  196. package/src/config/defaults.js +0 -2
  197. package/src/config/events.js +28 -0
  198. package/src/config/mapTheme.js +56 -0
  199. package/src/config/patternConfig.js +16 -0
  200. package/src/config/symbolConfig.js +80 -0
  201. package/src/scss/main.scss +1 -0
  202. package/src/scss/settings/_colors.scss +0 -9
  203. package/src/scss/settings/_dimensions.scss +0 -1
  204. package/src/services/patternRegistry.js +40 -0
  205. package/src/services/patternRegistry.test.js +48 -0
  206. package/src/services/symbolRegistry.js +113 -0
  207. package/src/services/symbolRegistry.test.js +262 -0
  208. package/src/types.js +93 -11
  209. package/src/utils/getSafeZoneInset.js +9 -7
  210. package/src/utils/getSafeZoneInset.test.js +10 -10
  211. package/src/utils/patternUtils.js +94 -0
  212. package/src/utils/patternUtils.test.js +160 -0
  213. package/src/utils/symbolUtils.js +85 -0
  214. package/src/utils/symbolUtils.test.js +156 -0
  215. package/webpack.dev.mjs +1 -1
  216. package/docs/api/slot-map.svg +0 -1
  217. package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
  218. package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
  219. package/plugins/beta/datasets/src/api/showDataset.js +0 -14
  220. package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
  221. package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
  222. package/plugins/beta/datasets/src/mapLayers.js +0 -164
  223. /package/src/{utils → services}/logger.js +0 -0
  224. /package/src/{utils → services}/logger.test.js +0 -0
@@ -61,6 +61,34 @@ export const EVENTS = {
61
61
  */
62
62
  APP_READY: 'app:ready',
63
63
 
64
+ /**
65
+ * Emitted when the map application becomes visible after being hidden.
66
+ *
67
+ * This can occur in 'hybrid behaviour' responsive scenarios where the map is already initialized
68
+ * (e.g. initialized inline on desktop) but was hidden and then shown again
69
+ * (e.g. resizing to mobile and opening the map).
70
+ *
71
+ * @remarks
72
+ * - Only emitted when transitioning from hidden → visible.
73
+ * - Not fired on initial open.
74
+ * - The existing map state may be preserved depending on configuration.
75
+ */
76
+ APP_VISIBLE: 'app:visible',
77
+
78
+ /**
79
+ * Emitted when the map application becomes hidden.
80
+ *
81
+ * This can occur in 'hybrid behaviour' responsive scenarios where the map was initialized inline
82
+ * (e.g. visible on desktop) but then becomes hidden
83
+ * (e.g. resizing to mobile or closing the map view).
84
+ *
85
+ * @remarks
86
+ * - Only emitted when transitioning from visible → hidden.
87
+ * - Not fired on initial load if the map starts hidden.
88
+ * - The map state may be preserved depending on configuration.
89
+ */
90
+ APP_HIDDEN: 'app:hidden',
91
+
64
92
  /**
65
93
  * Emitted when a panel is opened.
66
94
  * Payload: { panelId: string }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Canonical colour values for light and dark map colour schemes.
3
+ * These are the single source of truth for map overlay colours —
4
+ * used both by the symbol SVG renderer (JS canvas) and injected as
5
+ * CSS custom properties onto the app container for CSS-rendered elements.
6
+ *
7
+ * Per-style overrides take precedence: set `haloColor`, `selectedColor`, or
8
+ * `foregroundColor` directly on a `MapStyleConfig` to override these defaults.
9
+ */
10
+ export const SCHEME_COLORS = {
11
+ light: {
12
+ haloColor: '#ffffff',
13
+ selectedColor: '#0b0c0c',
14
+ foregroundColor: '#0b0c0c'
15
+ },
16
+ dark: {
17
+ haloColor: '#0b0c0c',
18
+ selectedColor: '#ffffff',
19
+ foregroundColor: '#ffffff'
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Resolves the three map overlay colours for the given map style,
25
+ * falling back to the appropriate scheme defaults when not explicitly set.
26
+ * Stored in `mapState.mapTheme` so plugins can read resolved values without
27
+ * importing from core.
28
+ *
29
+ * @param {import('../types.js').MapStyleConfig|null} mapStyle
30
+ * @returns {{ haloColor: string, selectedColor: string, foregroundColor: string }}
31
+ */
32
+ export const resolveMapTheme = (mapStyle) => {
33
+ const scheme = SCHEME_COLORS[mapStyle?.mapColorScheme] ?? SCHEME_COLORS.light
34
+ return {
35
+ haloColor: mapStyle?.haloColor ?? scheme.haloColor,
36
+ selectedColor: mapStyle?.selectedColor ?? scheme.selectedColor,
37
+ foregroundColor: mapStyle?.foregroundColor ?? scheme.foregroundColor
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Converts a mapStyle's overlay colours into CSS custom properties,
43
+ * falling back to the appropriate scheme defaults when not explicitly set.
44
+ * Suitable for spreading directly into a React `style` prop.
45
+ *
46
+ * @param {import('../types.js').MapStyleConfig|null} mapStyle
47
+ * @returns {Object} CSS custom property object
48
+ */
49
+ export const getMapThemeVars = (mapStyle) => {
50
+ const { haloColor, selectedColor, foregroundColor } = resolveMapTheme(mapStyle)
51
+ return {
52
+ '--map-overlay-halo-color': haloColor,
53
+ '--map-overlay-selected-color': selectedColor,
54
+ '--map-overlay-foreground-color': foregroundColor
55
+ }
56
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Built-in fill pattern SVG content.
3
+ * Each value is the inner SVG paths only (no wrapper element).
4
+ * Paths are authored in a 16×16 coordinate space and tile seamlessly.
5
+ * Use {{foregroundColor}} and {{backgroundColor}} tokens for colour injection.
6
+ */
7
+ export const BUILT_IN_PATTERNS = {
8
+ 'cross-hatch': '<path d="M0 4.486V3.485h3.5V.001h1v3.484h7.002V.001h1v3.484h3.5v1.001h-3.5v7h3.5v.999h-3.5v3.516h-1v-3.516H4.499v3.516h-1v-3.516H0v-.999h3.5v-7H0zm11.501 0H4.499v7h7.002v-7z" fill="{{foregroundColor}}"/>',
9
+ 'diagonal-cross-hatch': '<path d="M0 8.707V7.293L7.293 0h1.414L16 7.293v1.414L8.707 16H7.293L0 8.707zM.707 8L8 15.293 15.293 8 8 .707.707 8z" fill="{{foregroundColor}}"/>',
10
+ 'forward-diagonal-hatch': '<path d="M16 8.707V7.293L7.293 16h1.414L16 8.707zm-16 0L8.707 0H7.293L0 7.293v1.414z" fill="{{foregroundColor}}"/>',
11
+ 'backward-diagonal-hatch': '<path d="M0 8.707V7.293L8.707 16H7.293L0 8.707zm16 0L7.293 0h1.414L16 7.293v1.414z" fill="{{foregroundColor}}"/>',
12
+ 'horizontal-hatch': '<path d="M0 4.5V3.499h15.999V4.5H0zm0 7h15.999V12.5H0v-1.001z" fill="{{foregroundColor}}"/>',
13
+ 'vertical-hatch': '<path d="M3.501 16.001V0h1v16.001h-1zm7.998 0V0h1v16.001h-1z" fill="{{foregroundColor}}"/>',
14
+ dot: '<path d="M3.999 2A2 2 0 0 1 6 3.999C6 5.103 5.103 6 3.999 6a2 2 0 0 1-1.999-2.001A2 2 0 0 1 3.999 2zm0 7.999C5.103 10 6 10.897 6 12.001A2 2 0 0 1 3.999 14a2 2 0 0 1-1.999-1.999A2 2 0 0 1 3.999 10zM11.999 2A2 2 0 0 1 14 3.999C14 5.103 13.103 6 11.999 6S10 5.103 10 3.999A2 2 0 0 1 11.999 2zm0 7.999c1.104 0 2.001.897 2.001 2.001A2 2 0 0 1 11.999 14 2 2 0 0 1 10 12.001c0-1.104.897-2.001 1.999-2.001z" fill="{{foregroundColor}}"/>',
15
+ diamond: '<path d="M4 .465L7.535 4 4 7.535.465 4 4 .465zm0 7.999l3.535 3.535L4 15.535.465 11.999 4 8.464zm8-8l3.535 3.535-3.536 3.536L8.464 4 12 .464zm0 8.001L15.536 12 12 15.536 8.465 12 12 8.465z" fill="{{foregroundColor}}"/>'
16
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Default token values applied to all symbols unless overridden at the constructor,
3
+ * symbol registration, or marker creation level.
4
+ * Colour values may be a plain string or a map-style-keyed object,
5
+ * e.g. { outdoor: '#ffffff', dark: '#0b0c0c' }
6
+ */
7
+ export const symbolDefaults = {
8
+ symbol: 'pin',
9
+ backgroundColor: '#ca3535',
10
+ foregroundColor: '#ffffff',
11
+ haloWidth: '1',
12
+ selectedWidth: '6'
13
+ }
14
+
15
+ /**
16
+ * Built-in graphic path data strings for use with the `graphic` token.
17
+ *
18
+ * Each value is an SVG `d` attribute string in a 16×16 coordinate space,
19
+ * centred at (8, 8). The built-in symbols (`pin`, `circle`, `square`) apply a
20
+ * `translate` transform to position this 16×16 area correctly within their
21
+ * 38×38 viewBox — so graphic path data does not need to account for symbol
22
+ * positioning.
23
+ *
24
+ * @example
25
+ * markers.add('id', coords, { symbol: 'pin', graphic: graphics.dot })
26
+ *
27
+ * @example
28
+ * // Inline path data (16×16 space, centred at 8,8)
29
+ * markers.add('id', coords, { symbol: 'pin', graphic: 'M3 8 L8 3 L13 8 L8 13 Z' })
30
+ */
31
+ export const graphics = {
32
+ /** Small filled circle — the default graphic for built-in symbols */
33
+ dot: 'M8 3c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.241 5-5-2.24-5-5-5z',
34
+
35
+ /** Filled plus / cross shape */
36
+ cross: 'M6 3H10V6H13V10H10V13H6V10H3V6H6Z',
37
+
38
+ /** Filled diamond / rotated square */
39
+ diamond: 'M8 2L14 8L8 14L2 8Z',
40
+
41
+ /** Filled upward-pointing triangle */
42
+ triangle: 'M8 2L14 14H2Z',
43
+
44
+ /** Filled square */
45
+ square: 'M3 3H13V13H3Z'
46
+ }
47
+
48
+ // ─── Built-in symbol definitions ─────────────────────────────────────────────
49
+ // Each symbol uses a 38×38 viewBox. SVG templates use {{token}} placeholders
50
+ // resolved at render time by the symbolRegistry.
51
+
52
+ export const pin = {
53
+ id: 'pin',
54
+ viewBox: '0 0 38 38',
55
+ anchor: [0.5, 0.9], // NOSONAR
56
+ graphic: graphics.dot,
57
+ svg: `<path d="M19 33.499c-5.318-5-12-9.509-12-16.998 0-6.583 5.417-12 12-12s12 5.417 12 12c0 7.489-6.682 11.998-12 16.998z" fill="none" stroke="{{selectedColor}}" stroke-width="{{selectedWidth}}"/>
58
+ <path d="M19 33.499c-5.318-5-12-9.509-12-16.998 0-6.583 5.417-12 12-12s12 5.417 12 12c0 7.489-6.682 11.998-12 16.998z" fill="{{backgroundColor}}" stroke="{{haloColor}}" stroke-width="{{haloWidth}}"/>
59
+ <g transform="translate(19, 16) scale(0.8) translate(-8, -8)"><path d="{{graphic}}" fill="{{foregroundColor}}"/></g>`
60
+ }
61
+
62
+ export const circle = {
63
+ id: 'circle',
64
+ viewBox: '0 0 38 38',
65
+ anchor: [0.5, 0.5],
66
+ graphic: graphics.dot,
67
+ svg: `<path d="M19 7C12.376 7 7 12.376 7 19s5.376 12 12 12a12.01 12.01 0 0 0 12-12A12.01 12.01 0 0 0 19 7z" fill="none" stroke="{{selectedColor}}" stroke-width="{{selectedWidth}}"/>
68
+ <path d="M19 7C12.376 7 7 12.376 7 19s5.376 12 12 12a12.01 12.01 0 0 0 12-12A12.01 12.01 0 0 0 19 7z" fill="{{backgroundColor}}" stroke="{{haloColor}}" stroke-width="{{haloWidth}}"/>
69
+ <g transform="translate(19, 19) scale(0.8) translate(-8, -8)"><path d="{{graphic}}" fill="{{foregroundColor}}"/></g>`
70
+ }
71
+
72
+ export const square = {
73
+ id: 'square',
74
+ viewBox: '0 0 38 38',
75
+ anchor: [0.5, 0.5],
76
+ graphic: graphics.dot,
77
+ svg: `<path d="M28 7a3 3 0 0 1 3 3v18a3 3 0 0 1-3 3H10a3 3 0 0 1-3-3V10a3 3 0 0 1 3-3h18z" fill="none" stroke="{{selectedColor}}" stroke-width="{{selectedWidth}}"/>
78
+ <path d="M28 7a3 3 0 0 1 3 3v18a3 3 0 0 1-3 3H10a3 3 0 0 1-3-3V10a3 3 0 0 1 3-3h18z" fill="{{backgroundColor}}" stroke="{{haloColor}}" stroke-width="{{haloWidth}}"/>
79
+ <g transform="translate(19, 19) scale(0.8) translate(-8, -8)"><path d="{{graphic}}" fill="{{foregroundColor}}"/></g>`
80
+ }
@@ -19,6 +19,7 @@
19
19
  @use '../App/components/KeyboardHelp/KeyboardHelp.module';
20
20
  @use '../App/components/MapButton/MapButton.module';
21
21
  @use '../App/components/Panel/Panel.module';
22
+ @use '../App/components/Icon/Icon.module';
22
23
  @use '../App/components/Viewport/Viewport.module';
23
24
  @use '../App/components/Tooltip/Tooltip.module';
24
25
  @use '../App/components/PopupMenu/PopupMenu.module';
@@ -35,10 +35,6 @@
35
35
  --attributions-foreground-color: #0b0c0c;
36
36
  --attributions-background-color: #ffffff80;
37
37
 
38
- // Map overlays, such as scale bar, target marker etc
39
- --map-overlay-foreground-color: #0b0c0c;
40
- --map-overlay-halo-color: #ffffff;
41
-
42
38
  // Fixed colours (Dont chnage with dark mode)
43
39
  --fixed-foreground-color: #0b0c0c;
44
40
 
@@ -78,8 +74,3 @@
78
74
  --content-link-color: #ffffff;
79
75
  }
80
76
 
81
- // Map color scheme specific overrides
82
- :root .im-o-app--dark-map {
83
- --map-overlay-foreground-color: #ffffff;
84
- --map-overlay-halo-color: #0b0c0c;
85
- }
@@ -9,7 +9,6 @@
9
9
  --button-padding: 8px;
10
10
  --button-icon-label-offset: 7px;
11
11
  --label-button-padding: 10px;
12
- --touch-button-size: 50px;
13
12
 
14
13
  // Borders and focus
15
14
  --app-border-width: 1px;
@@ -0,0 +1,40 @@
1
+ import { BUILT_IN_PATTERNS } from '../config/patternConfig.js'
2
+
3
+ const patterns = new Map()
4
+
5
+ export const patternRegistry = {
6
+ /**
7
+ * Register a named pattern.
8
+ *
9
+ * @param {string} id - Unique pattern name (e.g. 'my-hatch')
10
+ * @param {string} svgContent - Inner SVG path content in a 16×16 coordinate space.
11
+ * Use {{foregroundColor}} and {{backgroundColor}} tokens for colour injection.
12
+ */
13
+ register (id, svgContent) {
14
+ patterns.set(id, { id, svgContent })
15
+ },
16
+
17
+ /**
18
+ * Retrieve a registered pattern by name.
19
+ *
20
+ * @param {string} id
21
+ * @returns {{ id: string, svgContent: string }|undefined}
22
+ */
23
+ get (id) {
24
+ return patterns.get(id)
25
+ },
26
+
27
+ /**
28
+ * Returns all registered patterns.
29
+ *
30
+ * @returns {{ id: string, svgContent: string }[]}
31
+ */
32
+ list () {
33
+ return [...patterns.values()]
34
+ }
35
+ }
36
+
37
+ // Seed built-in patterns
38
+ Object.entries(BUILT_IN_PATTERNS).forEach(([id, svgContent]) => {
39
+ patternRegistry.register(id, svgContent)
40
+ })
@@ -0,0 +1,48 @@
1
+ import { patternRegistry } from './patternRegistry.js'
2
+
3
+ describe('patternRegistry', () => {
4
+ describe('built-in patterns', () => {
5
+ test.each([
6
+ 'cross-hatch',
7
+ 'diagonal-cross-hatch',
8
+ 'forward-diagonal-hatch',
9
+ 'backward-diagonal-hatch',
10
+ 'horizontal-hatch',
11
+ 'vertical-hatch',
12
+ 'dot',
13
+ 'diamond'
14
+ ])('seeds built-in pattern: %s', (id) => {
15
+ const pattern = patternRegistry.get(id)
16
+ expect(pattern).toBeDefined()
17
+ expect(pattern.id).toBe(id)
18
+ expect(typeof pattern.svgContent).toBe('string')
19
+ expect(pattern.svgContent.length).toBeGreaterThan(0)
20
+ })
21
+ })
22
+
23
+ describe('register / get', () => {
24
+ test('registers a custom pattern and retrieves it by id', () => {
25
+ patternRegistry.register('test-hatch', '<path d="M0 0L8 8" stroke="{{foreground}}"/>')
26
+ const result = patternRegistry.get('test-hatch')
27
+ expect(result).toEqual({ id: 'test-hatch', svgContent: '<path d="M0 0L8 8" stroke="{{foreground}}"/>' })
28
+ })
29
+
30
+ test('returns undefined for an unregistered id', () => {
31
+ expect(patternRegistry.get('nonexistent-pattern')).toBeUndefined()
32
+ })
33
+
34
+ test('overwrite an existing pattern by re-registering the same id', () => {
35
+ patternRegistry.register('overwrite-test', '<path d="M0 0"/>')
36
+ patternRegistry.register('overwrite-test', '<path d="M1 1"/>')
37
+ expect(patternRegistry.get('overwrite-test').svgContent).toBe('<path d="M1 1"/>')
38
+ })
39
+ })
40
+
41
+ describe('list', () => {
42
+ test('returns all registered patterns including built-ins', () => {
43
+ const all = patternRegistry.list()
44
+ expect(all.length).toBeGreaterThanOrEqual(8)
45
+ expect(all.every(p => p.id && p.svgContent)).toBe(true)
46
+ })
47
+ })
48
+ })
@@ -0,0 +1,113 @@
1
+ import { getValueForStyle } from '../utils/getValueForStyle.js'
2
+ import { symbolDefaults, pin, circle, square, graphics } from '../config/symbolConfig.js'
3
+ import { SCHEME_COLORS } from '../config/mapTheme.js'
4
+
5
+ const symbols = new Map()
6
+ let _constructorDefaults = {}
7
+
8
+ // Keys that are structural — not token values for SVG substitution
9
+ const STRUCTURAL = new Set(['id', 'svg', 'viewBox', 'anchor', 'symbol', 'symbolSvgContent'])
10
+
11
+ // selectedWidth is app-wide — not overridable at symbol registration level.
12
+ // selectedColor is a map style concern — always injected from mapStyle, never from cascade.
13
+ const REGISTRY_EXCLUDED = new Set([...STRUCTURAL, 'selectedWidth'])
14
+
15
+ function resolveValues (symbolDef, markerValues, mapStyle) {
16
+ const mapStyleId = mapStyle?.id
17
+ const symbolTokens = Object.fromEntries(
18
+ Object.entries(symbolDef || {}).filter(([k]) => !REGISTRY_EXCLUDED.has(k))
19
+ )
20
+ const constructorTokens = Object.fromEntries(
21
+ Object.entries(_constructorDefaults).filter(([k]) => !STRUCTURAL.has(k))
22
+ )
23
+ const defined = Object.fromEntries(
24
+ Object.entries(markerValues).filter(([, v]) => v != null)
25
+ )
26
+ const merged = { ...symbolDefaults, ...constructorTokens, ...symbolTokens, ...defined }
27
+ // haloColor and selectedColor are map style concerns — always injected from mapStyle, never from the cascade
28
+ const scheme = SCHEME_COLORS[mapStyle?.mapColorScheme] ?? SCHEME_COLORS.light
29
+ merged.haloColor = mapStyle?.haloColor ?? scheme.haloColor
30
+ merged.selectedColor = mapStyle?.selectedColor ?? scheme.selectedColor
31
+ if (typeof merged.graphic === 'string' && graphics[merged.graphic]) {
32
+ merged.graphic = graphics[merged.graphic]
33
+ }
34
+ return Object.fromEntries(
35
+ Object.entries(merged).map(([token, value]) => [token, getValueForStyle(value, mapStyleId) || ''])
36
+ )
37
+ }
38
+
39
+ function resolveLayer (svgString, values) {
40
+ return Object.entries(values).reduce(
41
+ (svg, [token, value]) => svg.replaceAll(`{{${token}}}`, value),
42
+ svgString
43
+ )
44
+ }
45
+
46
+ export const symbolRegistry = {
47
+ /**
48
+ * Set constructor-level defaults. Called once during app initialisation.
49
+ * Merges onto symbolDefaults to form the app-wide token baseline.
50
+ *
51
+ * @param {Object} defaults - Constructor symbolDefaults config
52
+ */
53
+ setDefaults (defaults) {
54
+ _constructorDefaults = defaults || {}
55
+ },
56
+
57
+ /**
58
+ * Returns the merged app-wide defaults (hardcoded + constructor overrides).
59
+ * Includes both structural properties (symbol, viewBox, anchor) and token values.
60
+ *
61
+ * @returns {Object}
62
+ */
63
+ getDefaults () {
64
+ return { ...symbolDefaults, ..._constructorDefaults }
65
+ },
66
+
67
+ register (symbolDef) {
68
+ symbols.set(symbolDef.id, symbolDef)
69
+ },
70
+
71
+ get (id) {
72
+ return symbols.get(id)
73
+ },
74
+
75
+ list () {
76
+ return [...symbols.values()]
77
+ },
78
+
79
+ /**
80
+ * Resolve a symbol's SVG string for normal (unselected) rendering.
81
+ * The selected ring is always hidden regardless of cascade values.
82
+ *
83
+ * @param {Object} symbolDef - Symbol definition
84
+ * @param {Object} styleColors - Token overrides
85
+ * @param {Object} mapStyle - Current map style config (provides selectedColor, haloColor)
86
+ * @returns {string} Resolved SVG string
87
+ */
88
+ resolve (symbolDef, styleColors, mapStyle) {
89
+ const colors = resolveValues(symbolDef, styleColors || {}, mapStyle)
90
+ if (!symbolDef) { return '' }
91
+ colors.selectedColor = ''
92
+ return resolveLayer(symbolDef.svg, colors)
93
+ },
94
+
95
+ /**
96
+ * Resolve a symbol's SVG string for selected rendering.
97
+ * selectedColor comes from mapStyle.selectedColor (or the hardcoded fallback).
98
+ *
99
+ * @param {Object} symbolDef - Symbol definition
100
+ * @param {Object} styleColors - Token overrides
101
+ * @param {Object} mapStyle - Current map style config (provides selectedColor, haloColor)
102
+ * @returns {string} Resolved SVG string
103
+ */
104
+ resolveSelected (symbolDef, styleColors, mapStyle) {
105
+ const colors = resolveValues(symbolDef, styleColors || {}, mapStyle)
106
+ if (!symbolDef) { return '' }
107
+ return resolveLayer(symbolDef.svg, colors)
108
+ }
109
+ }
110
+
111
+ symbolRegistry.register(pin)
112
+ symbolRegistry.register(circle)
113
+ symbolRegistry.register(square)