@sjvair/monitor-map 4.3.0 → 5.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -138
  3. package/dist/lib/MonitorDetailPanel.svelte +23 -20
  4. package/dist/lib/MonitorMapLayout.svelte +147 -77
  5. package/dist/lib/components/DataBox.svelte +4 -1
  6. package/dist/lib/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
  7. package/dist/lib/config.svelte.d.ts +7 -0
  8. package/dist/lib/config.svelte.js +10 -0
  9. package/dist/lib/core.d.ts +2 -2
  10. package/dist/lib/core.js +2 -1
  11. package/dist/lib/data-chart/DataChart.svelte +40 -25
  12. package/dist/lib/data-chart/DateRange.d.ts +13 -4
  13. package/dist/lib/data-chart/DateRange.js +50 -14
  14. package/dist/lib/data-chart/chart-utils.js +5 -7
  15. package/dist/lib/data-chart/level-colors.d.ts +3 -4
  16. package/dist/lib/data-chart/level-colors.js +7 -4
  17. package/dist/lib/data-chart/pacific-time.d.ts +9 -0
  18. package/dist/lib/data-chart/pacific-time.js +25 -0
  19. package/dist/lib/data-chart/tooltip.js +2 -2
  20. package/{src/lib/ev-stations/components/EvStationTooltip.svelte → dist/lib/ev-stations/EvPopup.svelte} +6 -5
  21. package/dist/lib/ev-stations/EvPopup.svelte.d.ts +7 -0
  22. package/dist/lib/ev-stations/ev-plugin.svelte.d.ts +14 -0
  23. package/dist/lib/ev-stations/ev-plugin.svelte.js +71 -0
  24. package/dist/lib/ev-stations/ev-station-icons.d.ts +1 -4
  25. package/dist/lib/ev-stations/ev-station-icons.js +1 -13
  26. package/dist/lib/ev-stations/ev-stations.d.ts +7 -0
  27. package/dist/lib/ev-stations/ev-stations.js +53 -0
  28. package/dist/lib/ev-stations/ev-store.svelte.d.ts +21 -0
  29. package/dist/lib/ev-stations/ev-store.svelte.js +83 -0
  30. package/dist/lib/ev-stations/station-popup.svelte.d.ts +14 -0
  31. package/dist/lib/ev-stations/station-popup.svelte.js +54 -0
  32. package/dist/lib/ev-stations/types.d.ts +0 -29
  33. package/dist/lib/ev-stations/zip-codes.d.ts +11 -0
  34. package/dist/lib/ev-stations/zip-codes.js +28 -16
  35. package/dist/lib/index.d.ts +8 -56
  36. package/dist/lib/index.js +7 -46
  37. package/dist/lib/legend/Legend.svelte +18 -19
  38. package/dist/lib/legend/Legend.svelte.d.ts +2 -2
  39. package/dist/lib/levels.d.ts +7 -0
  40. package/dist/lib/levels.js +19 -0
  41. package/dist/lib/map/MapShell.svelte +17 -24
  42. package/dist/lib/map/MapShell.svelte.d.ts +4 -4
  43. package/dist/lib/map/basemaps.d.ts +25 -0
  44. package/dist/lib/map/basemaps.js +66 -0
  45. package/dist/lib/map/live-map.svelte.d.ts +3 -0
  46. package/dist/lib/map/live-map.svelte.js +22 -0
  47. package/dist/lib/map/sync-plugins.svelte.d.ts +19 -0
  48. package/dist/lib/map/sync-plugins.svelte.js +52 -0
  49. package/dist/lib/monitors/components/MonitorSubscriptionManager.svelte +66 -61
  50. package/dist/lib/monitors/filtered-source.svelte.d.ts +9 -0
  51. package/dist/lib/monitors/filtered-source.svelte.js +28 -0
  52. package/dist/lib/monitors/monitors.svelte.d.ts +25 -35
  53. package/dist/lib/monitors/monitors.svelte.js +35 -218
  54. package/dist/lib/monitors/retry-load.d.ts +8 -0
  55. package/dist/lib/monitors/retry-load.js +11 -0
  56. package/dist/lib/monitors/types.d.ts +1 -41
  57. package/dist/lib/options/map-option-defaults.d.ts +25 -36
  58. package/dist/lib/options/map-option-defaults.js +12 -30
  59. package/dist/lib/options/map-options.svelte.d.ts +13 -0
  60. package/dist/lib/options/map-options.svelte.js +26 -0
  61. package/dist/lib/options/menus/LayersMenu.svelte +8 -20
  62. package/dist/lib/options/menus/LayersMenu.svelte.d.ts +17 -2
  63. package/dist/lib/options/menus/MonitorsMenu.svelte +34 -18
  64. package/dist/lib/options/menus/OverlaysMenu.svelte +2 -2
  65. package/dist/lib/options/menus/PollutantMenu.svelte +4 -9
  66. package/dist/lib/options/menus/SettingsMenu.svelte +32 -25
  67. package/dist/lib/options/menus/SettingsMenu.svelte.d.ts +5 -16
  68. package/dist/lib/options/plugin-order.d.ts +8 -0
  69. package/dist/lib/options/plugin-order.js +36 -0
  70. package/dist/lib/options/pollutant-param.d.ts +7 -5
  71. package/dist/lib/options/pollutant-param.js +9 -5
  72. package/dist/lib/options/row-icons.d.ts +8 -0
  73. package/dist/lib/options/row-icons.js +15 -0
  74. package/dist/lib/options/rows/MarkerIcon.svelte +5 -5
  75. package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +1 -2
  76. package/dist/lib/search/Search.svelte +20 -33
  77. package/dist/lib/search/Search.svelte.d.ts +5 -1
  78. package/dist/lib/search/search-pin.svelte.d.ts +11 -0
  79. package/dist/lib/search/search-pin.svelte.js +28 -0
  80. package/dist/lib/search/service.d.ts +7 -2
  81. package/dist/lib/search/service.js +17 -10
  82. package/dist/lib/stores.svelte.d.ts +8 -0
  83. package/dist/lib/stores.svelte.js +9 -0
  84. package/dist/lib/wind/wind-plugin.d.ts +11 -0
  85. package/dist/lib/wind/wind-plugin.js +25 -0
  86. package/package.json +12 -10
  87. package/src/lib/MonitorDetailPanel.svelte +23 -20
  88. package/src/lib/MonitorMapLayout.svelte +147 -77
  89. package/src/lib/components/DataBox.svelte +4 -1
  90. package/src/lib/config.svelte.ts +16 -0
  91. package/src/lib/core.ts +2 -2
  92. package/src/lib/data-chart/DataChart.svelte +40 -25
  93. package/src/lib/data-chart/DateRange.ts +58 -15
  94. package/src/lib/data-chart/chart-utils.ts +6 -7
  95. package/src/lib/data-chart/level-colors.ts +8 -9
  96. package/src/lib/data-chart/pacific-time.ts +31 -0
  97. package/src/lib/data-chart/tooltip.ts +2 -2
  98. package/{dist/lib/ev-stations/components/EvStationTooltip.svelte → src/lib/ev-stations/EvPopup.svelte} +6 -5
  99. package/src/lib/ev-stations/ev-plugin.svelte.ts +86 -0
  100. package/src/lib/ev-stations/ev-station-icons.ts +1 -17
  101. package/src/lib/ev-stations/ev-stations.ts +72 -0
  102. package/src/lib/ev-stations/ev-store.svelte.ts +117 -0
  103. package/src/lib/ev-stations/station-popup.svelte.ts +64 -0
  104. package/src/lib/ev-stations/types.ts +0 -34
  105. package/src/lib/ev-stations/zip-codes.ts +29 -16
  106. package/src/lib/index.ts +8 -99
  107. package/src/lib/legend/Legend.svelte +18 -19
  108. package/src/lib/levels.ts +24 -0
  109. package/src/lib/map/MapShell.svelte +17 -24
  110. package/src/lib/map/basemaps.ts +76 -0
  111. package/src/lib/map/live-map.svelte.ts +24 -0
  112. package/src/lib/map/sync-plugins.svelte.ts +69 -0
  113. package/src/lib/monitors/components/MonitorSubscriptionManager.svelte +66 -61
  114. package/src/lib/monitors/filtered-source.svelte.ts +38 -0
  115. package/src/lib/monitors/monitors.svelte.ts +58 -261
  116. package/src/lib/monitors/retry-load.ts +14 -0
  117. package/src/lib/monitors/types.ts +1 -47
  118. package/src/lib/options/map-option-defaults.ts +30 -53
  119. package/src/lib/options/map-options.svelte.ts +30 -0
  120. package/src/lib/options/menus/LayersMenu.svelte +8 -20
  121. package/src/lib/options/menus/MonitorsMenu.svelte +34 -18
  122. package/src/lib/options/menus/OverlaysMenu.svelte +2 -2
  123. package/src/lib/options/menus/PollutantMenu.svelte +4 -9
  124. package/src/lib/options/menus/SettingsMenu.svelte +32 -25
  125. package/src/lib/options/plugin-order.ts +38 -0
  126. package/src/lib/options/pollutant-param.ts +14 -9
  127. package/src/lib/options/row-icons.ts +17 -0
  128. package/src/lib/options/rows/MarkerIcon.svelte +5 -5
  129. package/src/lib/search/Search.svelte +20 -33
  130. package/src/lib/search/search-pin.svelte.ts +35 -0
  131. package/src/lib/search/service.ts +34 -15
  132. package/src/lib/stores.svelte.ts +15 -0
  133. package/src/lib/wind/wind-plugin.ts +33 -0
  134. package/dist/lib/collocation-sites/CollocationTooltip.svelte +0 -98
  135. package/dist/lib/collocation-sites/CollocationTooltip.svelte.d.ts +0 -7
  136. package/dist/lib/collocation-sites/collocations-icon-manager.svelte.d.ts +0 -7
  137. package/dist/lib/collocation-sites/collocations-icon-manager.svelte.js +0 -18
  138. package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +0 -21
  139. package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +0 -113
  140. package/dist/lib/collocation-sites/collocations.svelte.d.ts +0 -10
  141. package/dist/lib/collocation-sites/collocations.svelte.js +0 -21
  142. package/dist/lib/collocation-sites/types.d.ts +0 -8
  143. package/dist/lib/collocation-sites/types.js +0 -1
  144. package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
  145. package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte.d.ts +0 -7
  146. package/dist/lib/ev-stations/components/EvStationTooltip.svelte.d.ts +0 -7
  147. package/dist/lib/ev-stations/ev-stations-cluster-renderer.d.ts +0 -19
  148. package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +0 -131
  149. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.d.ts +0 -31
  150. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +0 -226
  151. package/dist/lib/ev-stations/ev-stations.svelte.d.ts +0 -10
  152. package/dist/lib/ev-stations/ev-stations.svelte.js +0 -57
  153. package/dist/lib/hms/hms-fire-icon-manager.d.ts +0 -13
  154. package/dist/lib/hms/hms-fire-icon-manager.js +0 -39
  155. package/dist/lib/hms/hms-fire-map-integration.svelte.d.ts +0 -23
  156. package/dist/lib/hms/hms-fire-map-integration.svelte.js +0 -94
  157. package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +0 -39
  158. package/dist/lib/hms/hms-smoke-map-integration.svelte.js +0 -87
  159. package/dist/lib/hms/hms.svelte.d.ts +0 -20
  160. package/dist/lib/hms/hms.svelte.js +0 -237
  161. package/dist/lib/legend/LegendBar.svelte +0 -45
  162. package/dist/lib/legend/LegendBar.svelte.d.ts +0 -7
  163. package/dist/lib/legend/legend-data.d.ts +0 -40
  164. package/dist/lib/legend/legend-data.js +0 -65
  165. package/dist/lib/map/Map.svelte +0 -13
  166. package/dist/lib/map/Map.svelte.d.ts +0 -8
  167. package/dist/lib/map/basemap.d.ts +0 -14
  168. package/dist/lib/map/basemap.js +0 -10
  169. package/dist/lib/map/icons.d.ts +0 -7
  170. package/dist/lib/map/icons.js +0 -33
  171. package/dist/lib/map/integrations/base-layer-seperator.d.ts +0 -9
  172. package/dist/lib/map/integrations/base-layer-seperator.js +0 -11
  173. package/dist/lib/map/integrations/click-manager.d.ts +0 -14
  174. package/dist/lib/map/integrations/click-manager.js +0 -52
  175. package/dist/lib/map/integrations/integrations-manager.d.ts +0 -8
  176. package/dist/lib/map/integrations/integrations-manager.js +0 -20
  177. package/dist/lib/map/integrations/map-display-option.svelte.d.ts +0 -7
  178. package/dist/lib/map/integrations/map-display-option.svelte.js +0 -10
  179. package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +0 -32
  180. package/dist/lib/map/integrations/map-geojson-integration.svelte.js +0 -60
  181. package/dist/lib/map/integrations/map-icon-manager.d.ts +0 -15
  182. package/dist/lib/map/integrations/map-icon-manager.js +0 -52
  183. package/dist/lib/map/integrations/map-integration.svelte.d.ts +0 -6
  184. package/dist/lib/map/integrations/map-integration.svelte.js +0 -2
  185. package/dist/lib/map/integrations/map-layer-integration.svelte.d.ts +0 -11
  186. package/dist/lib/map/integrations/map-layer-integration.svelte.js +0 -40
  187. package/dist/lib/map/integrations/tooltip.svelte.d.ts +0 -36
  188. package/dist/lib/map/integrations/tooltip.svelte.js +0 -131
  189. package/dist/lib/map/integrations/types.d.ts +0 -14
  190. package/dist/lib/map/integrations/types.js +0 -1
  191. package/dist/lib/map/map-manager.svelte.d.ts +0 -20
  192. package/dist/lib/map/map-manager.svelte.js +0 -63
  193. package/dist/lib/map/map-style-state.svelte.d.ts +0 -23
  194. package/dist/lib/map/map-style-state.svelte.js +0 -86
  195. package/dist/lib/map/utils.d.ts +0 -7
  196. package/dist/lib/map/utils.js +0 -36
  197. package/dist/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
  198. package/dist/lib/monitors/components/MonitorClusterTooltip.svelte.d.ts +0 -7
  199. package/dist/lib/monitors/components/MonitorTooltip.svelte +0 -61
  200. package/dist/lib/monitors/components/MonitorTooltip.svelte.d.ts +0 -7
  201. package/dist/lib/monitors/monitor-utils.d.ts +0 -4
  202. package/dist/lib/monitors/monitor-utils.js +0 -41
  203. package/dist/lib/monitors/monitors-cluster-renderer.d.ts +0 -33
  204. package/dist/lib/monitors/monitors-cluster-renderer.js +0 -221
  205. package/dist/lib/monitors/monitors-icon-manager.svelte.d.ts +0 -15
  206. package/dist/lib/monitors/monitors-icon-manager.svelte.js +0 -78
  207. package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +0 -43
  208. package/dist/lib/monitors/monitors-map-integration.svelte.js +0 -301
  209. package/dist/lib/options/reset-map-options.d.ts +0 -2
  210. package/dist/lib/options/reset-map-options.js +0 -24
  211. package/dist/lib/reactivity.svelte.d.ts +0 -0
  212. package/dist/lib/reactivity.svelte.js +0 -83
  213. package/dist/lib/wind/wind.svelte.d.ts +0 -10
  214. package/dist/lib/wind/wind.svelte.js +0 -13
  215. package/src/lib/collocation-sites/CollocationTooltip.svelte +0 -98
  216. package/src/lib/collocation-sites/collocations-icon-manager.svelte.ts +0 -21
  217. package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +0 -136
  218. package/src/lib/collocation-sites/collocations.svelte.ts +0 -27
  219. package/src/lib/collocation-sites/types.ts +0 -10
  220. package/src/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
  221. package/src/lib/ev-stations/ev-stations-cluster-renderer.ts +0 -162
  222. package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +0 -248
  223. package/src/lib/ev-stations/ev-stations.svelte.ts +0 -84
  224. package/src/lib/hms/hms-fire-icon-manager.ts +0 -48
  225. package/src/lib/hms/hms-fire-map-integration.svelte.ts +0 -109
  226. package/src/lib/hms/hms-smoke-map-integration.svelte.ts +0 -105
  227. package/src/lib/hms/hms.svelte.ts +0 -270
  228. package/src/lib/legend/LegendBar.svelte +0 -45
  229. package/src/lib/legend/legend-data.ts +0 -101
  230. package/src/lib/map/Map.svelte +0 -13
  231. package/src/lib/map/basemap.ts +0 -24
  232. package/src/lib/map/icons.ts +0 -42
  233. package/src/lib/map/integrations/base-layer-seperator.ts +0 -17
  234. package/src/lib/map/integrations/click-manager.ts +0 -64
  235. package/src/lib/map/integrations/integrations-manager.ts +0 -25
  236. package/src/lib/map/integrations/map-display-option.svelte.ts +0 -13
  237. package/src/lib/map/integrations/map-geojson-integration.svelte.ts +0 -77
  238. package/src/lib/map/integrations/map-icon-manager.ts +0 -67
  239. package/src/lib/map/integrations/map-integration.svelte.ts +0 -8
  240. package/src/lib/map/integrations/map-layer-integration.svelte.ts +0 -49
  241. package/src/lib/map/integrations/tooltip.svelte.ts +0 -153
  242. package/src/lib/map/integrations/types.ts +0 -24
  243. package/src/lib/map/map-manager.svelte.ts +0 -83
  244. package/src/lib/map/map-style-state.svelte.ts +0 -94
  245. package/src/lib/map/utils.ts +0 -53
  246. package/src/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
  247. package/src/lib/monitors/components/MonitorTooltip.svelte +0 -61
  248. package/src/lib/monitors/monitor-utils.ts +0 -57
  249. package/src/lib/monitors/monitors-cluster-renderer.ts +0 -285
  250. package/src/lib/monitors/monitors-icon-manager.svelte.ts +0 -98
  251. package/src/lib/monitors/monitors-map-integration.svelte.ts +0 -318
  252. package/src/lib/options/reset-map-options.ts +0 -28
  253. package/src/lib/reactivity.svelte.ts +0 -82
  254. package/src/lib/wind/wind.svelte.ts +0 -20
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
- import type { MapImageIcon } from "../../map/integrations/types";
2
+ /** A marker image from its SVG markup (e.g. map-sdk's `circle`). */
3
+ let { svg, class: className = "w-4" }: { svg: string | undefined; class?: string } = $props();
3
4
 
4
- let { icon, class: className = "w-4" }: { icon: MapImageIcon | undefined; class?: string } =
5
- $props();
5
+ const src = $derived(svg ? `data:image/svg+xml;utf8,${encodeURIComponent(svg)}` : undefined);
6
6
  </script>
7
7
 
8
- {#if icon}
9
- <img src={icon.image.src} alt="" class={className} />
8
+ {#if src}
9
+ <img {src} alt="" class={className} />
10
10
  {/if}
@@ -1,6 +1,5 @@
1
- import type { MapImageIcon } from "../../map/integrations/types";
2
1
  type $$ComponentProps = {
3
- icon: MapImageIcon | undefined;
2
+ svg: string | undefined;
4
3
  class?: string;
5
4
  };
6
5
  declare const MarkerIcon: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1,19 +1,24 @@
1
1
  <script lang="ts">
2
- import { onDestroy } from "svelte";
3
- import { Marker } from "@maptiler/sdk";
4
2
  import { MapPinIcon, SearchIcon } from "@lucide/svelte";
3
+ import type { MapContext } from "@sjvair/map-sdk";
5
4
  import { useMonitorMapRouter } from "../router-context";
6
- import { mapManager } from "../map/map-manager.svelte.js";
7
5
  import { debounce } from "../utils";
6
+ import { searchPin } from "./search-pin.svelte.js";
8
7
  import { monitorSearch, geocode, type SearchResult } from "./service";
9
- import { monitorsMapIntegration } from "../monitors/monitors-map-integration.svelte";
10
8
 
9
+ interface SearchProps {
10
+ map: MapContext;
11
+ }
12
+
13
+ const { map }: SearchProps = $props();
11
14
  const { navigate, basePath } = useMonitorMapRouter();
12
15
 
13
16
  let collapsed = $state(true);
14
17
  let searchText = $state("");
15
18
  let results: SearchResult[] = $state([]);
16
- let marker: Marker | null = $state.raw(null);
19
+ // The layout passes one MapContext for the widget's lifetime.
20
+ // svelte-ignore state_referenced_locally
21
+ const pin = searchPin(map);
17
22
  let inputEl: HTMLInputElement | null = $state(null);
18
23
 
19
24
  function clickOutside(node: HTMLElement) {
@@ -42,16 +47,14 @@
42
47
  function clearSearch() {
43
48
  searchText = "";
44
49
  results = [];
45
- marker?.remove();
46
- marker = null;
50
+ pin.clear();
47
51
  }
48
52
 
49
53
  async function handleInput() {
50
54
  const query = searchText;
51
55
  if (!query) {
52
56
  results = [];
53
- marker?.remove();
54
- marker = null;
57
+ pin.clear();
55
58
  return;
56
59
  }
57
60
  const monitorResults = await monitorSearch(query);
@@ -73,40 +76,24 @@
73
76
  collapsed = true;
74
77
  searchText = "";
75
78
  results = [];
76
- marker?.remove();
77
- marker = null;
78
- if (mapManager.map && result.monitor.position) {
79
+ pin.clear();
80
+ if (result.monitor.position) {
79
81
  const { coordinates } = result.monitor.position;
80
- const center: [number, number] = [coordinates[0], coordinates[1]];
81
- mapManager.map.flyTo({
82
- center,
83
- zoom: 12
84
- });
82
+ map.flyTo({ center: [coordinates[0], coordinates[1]], zoom: 12 });
85
83
  }
86
84
  await navigate(`${basePath}/monitor/:id`, { params: { id: result.monitor.id } });
87
- monitorsMapIntegration.selectedMonitorId = result.monitor.id;
88
85
  }
89
86
 
90
87
  function selectGeocode(result: Extract<SearchResult, { type: "geocode" }>) {
91
88
  const { feature } = result;
92
- marker?.remove();
93
- marker = null;
94
- if (mapManager.map) {
95
- const center: [number, number] = [feature.center[0], feature.center[1]];
96
- marker = new Marker().setLngLat(center).addTo(mapManager.map);
97
- const isAddress = feature.place_type?.[0] === "address";
98
- mapManager.map.flyTo({
99
- center,
100
- ...(isAddress && { zoom: 16 })
101
- });
102
- }
89
+ pin.clear();
90
+ const center: [number, number] = [feature.center[0], feature.center[1]];
91
+ pin.place(center);
92
+ const isAddress = feature.place_type?.[0] === "address";
93
+ map.flyTo({ center, ...(isAddress && { zoom: 16 }) });
103
94
  results = [];
104
95
  collapsed = true;
105
96
  }
106
-
107
- onDestroy(() => {
108
- marker?.remove();
109
- });
110
97
  </script>
111
98
 
112
99
  <div
@@ -1,3 +1,7 @@
1
- declare const Search: import("svelte").Component<Record<string, never>, {}, "">;
1
+ import type { MapContext } from "@sjvair/map-sdk";
2
+ interface SearchProps {
3
+ map: MapContext;
4
+ }
5
+ declare const Search: import("svelte").Component<SearchProps, {}, "">;
2
6
  type Search = ReturnType<typeof Search>;
3
7
  export default Search;
@@ -0,0 +1,11 @@
1
+ import { type MapContext } from "@sjvair/map-sdk";
2
+ export interface SearchPin {
3
+ place(center: [number, number]): void;
4
+ clear(): void;
5
+ }
6
+ /**
7
+ * The pin a geocoded place leaves on the map. It follows the map: when the GL map is replaced
8
+ * (a basemap or style rebuild), the pin leaves the old one and is added to the new one. Call it
9
+ * during component init; the pin is removed when the component is destroyed.
10
+ */
11
+ export declare function searchPin(map: MapContext): SearchPin;
@@ -0,0 +1,28 @@
1
+ import { Marker } from "@maptiler/sdk";
2
+ import { maptilerMap } from "@sjvair/map-sdk";
3
+ /**
4
+ * The pin a geocoded place leaves on the map. It follows the map: when the GL map is replaced
5
+ * (a basemap or style rebuild), the pin leaves the old one and is added to the new one. Call it
6
+ * during component init; the pin is removed when the component is destroyed.
7
+ */
8
+ export function searchPin(map) {
9
+ let center = $state.raw(null);
10
+ $effect(() => {
11
+ const gl = maptilerMap(map);
12
+ const at = center;
13
+ if (!gl || !at)
14
+ return;
15
+ const marker = new Marker().setLngLat(at).addTo(gl);
16
+ return () => {
17
+ marker.remove();
18
+ };
19
+ });
20
+ return {
21
+ place(next) {
22
+ center = next;
23
+ },
24
+ clear() {
25
+ center = null;
26
+ }
27
+ };
28
+ }
@@ -1,8 +1,8 @@
1
1
  import { type GeocodingFeature } from "@maptiler/sdk";
2
- import type { MonitorLatestType } from "@sjvair/sdk";
2
+ import type { MonitorData } from "@sjvair/sdk";
3
3
  export type MonitorResult = {
4
4
  type: "monitor";
5
- monitor: MonitorLatestType<"pm25" | "o3">;
5
+ monitor: MonitorData;
6
6
  logo: {
7
7
  url: string;
8
8
  alt: string;
@@ -13,5 +13,10 @@ export type GeocodeResult = {
13
13
  feature: GeocodingFeature;
14
14
  };
15
15
  export type SearchResult = MonitorResult | GeocodeResult;
16
+ /** Monitors whose name matches `query`: exact, then prefix, then substring; at most 4. */
17
+ export declare function searchMonitors(query: string, monitors: Iterable<MonitorData>, latest: ReadonlyMap<string, {
18
+ value: number | null;
19
+ }>): MonitorData[];
16
20
  export declare function monitorSearch(query: string): Promise<SearchResult[]>;
21
+ export declare function geocodeQuery(query: string, proximity: [number, number], key: string): Promise<SearchResult[]>;
17
22
  export declare function geocode(query: string): Promise<SearchResult[]>;
@@ -1,5 +1,6 @@
1
1
  import { geocoding } from "@maptiler/sdk";
2
- import { monitorsManager } from "../monitors/monitors.svelte";
2
+ import { monitorMapConfig } from "../config.svelte.js";
3
+ import { monitorsStore } from "../stores.svelte.js";
3
4
  import AirGradientLogo from "../assets/logos/airgradient-blue.svg";
4
5
  import AirNowLogo from "../assets/logos/airnow-compact.jpg";
5
6
  import CarbLogo from "../assets/logos/carb-compact.jpg";
@@ -43,14 +44,16 @@ function resolveProximity() {
43
44
  });
44
45
  });
45
46
  }
46
- export async function monitorSearch(query) {
47
- if (!monitorsManager.latest)
48
- return [];
47
+ /** Monitors whose name matches `query`: exact, then prefix, then substring; at most 4. */
48
+ export function searchMonitors(query, monitors, latest) {
49
49
  const normalized = query.toLowerCase();
50
50
  const exact = [];
51
51
  const prefix = [];
52
52
  const substr = [];
53
- for (const monitor of monitorsManager.latest.values()) {
53
+ // Only monitors with a reading for the current pollutant, as the map shows them.
54
+ for (const monitor of monitors) {
55
+ if (latest.get(monitor.id)?.value == null)
56
+ continue;
54
57
  const name = monitor.name.toLowerCase();
55
58
  if (name === normalized)
56
59
  exact.push(monitor);
@@ -59,14 +62,15 @@ export async function monitorSearch(query) {
59
62
  else if (name.includes(normalized))
60
63
  substr.push(monitor);
61
64
  }
62
- return [...exact, ...prefix, ...substr]
63
- .slice(0, 4)
64
- .map((monitor) => ({ type: "monitor", monitor, logo: getMonitorLogo(monitor) }));
65
+ return [...exact, ...prefix, ...substr].slice(0, 4);
65
66
  }
66
- export async function geocode(query) {
67
- const proximity = await getProximity();
67
+ export async function monitorSearch(query) {
68
+ return searchMonitors(query, monitorsStore.monitors.values(), monitorsStore.latest).map((monitor) => ({ type: "monitor", monitor, logo: getMonitorLogo(monitor) }));
69
+ }
70
+ export async function geocodeQuery(query, proximity, key) {
68
71
  try {
69
72
  const result = await geocoding.forward(query, {
73
+ apiKey: key,
70
74
  country: ["us"],
71
75
  fuzzyMatch: true,
72
76
  limit: 4,
@@ -78,3 +82,6 @@ export async function geocode(query) {
78
82
  return [];
79
83
  }
80
84
  }
85
+ export async function geocode(query) {
86
+ return geocodeQuery(query, await getProximity(), monitorMapConfig().maptilerKey);
87
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The app-wide data stores, created once. This module imports only `@sjvair/map-sdk/stores`, so
3
+ * the light `/core` entry can reach it without loading the map engine.
4
+ */
5
+ export declare const monitorsStore: import("@sjvair/map-sdk/stores").MonitorsStore;
6
+ export declare const hmsSmokeStore: import("@sjvair/map-sdk/stores").HmsSmokeStore;
7
+ export declare const hmsFireStore: import("@sjvair/map-sdk/stores").HmsFireStore;
8
+ export declare const collocationSitesStore: import("@sjvair/map-sdk/stores").CollocationSitesStore;
@@ -0,0 +1,9 @@
1
+ import { createCollocationSitesStore, createHmsFireStore, createHmsSmokeStore, createMonitorsStore } from "@sjvair/map-sdk/stores";
2
+ /**
3
+ * The app-wide data stores, created once. This module imports only `@sjvair/map-sdk/stores`, so
4
+ * the light `/core` entry can reach it without loading the map engine.
5
+ */
6
+ export const monitorsStore = createMonitorsStore();
7
+ export const hmsSmokeStore = createHmsSmokeStore();
8
+ export const hmsFireStore = createHmsFireStore();
9
+ export const collocationSitesStore = createCollocationSitesStore({ monitors: monitorsStore });
@@ -0,0 +1,11 @@
1
+ import { type CustomLayerInterface } from "@maptiler/sdk";
2
+ import type { MapPlugin } from "@sjvair/map-sdk";
3
+ export interface WindPluginOptions {
4
+ /** Builds the layer; runs again on each restyle. Defaults to a `@maptiler/weather` WindLayer. */
5
+ createLayer?: (id: string) => CustomLayerInterface;
6
+ }
7
+ /**
8
+ * Wind particles as a map-sdk custom layer. `@maptiler/weather` can't be a map-sdk dependency, so
9
+ * the host adds it here; only the layout chunk reaches this module.
10
+ */
11
+ export declare function windPlugin(options?: WindPluginOptions): MapPlugin;
@@ -0,0 +1,25 @@
1
+ import { config } from "@maptiler/sdk";
2
+ import { WindLayer } from "@maptiler/weather";
3
+ import { monitorMapConfig } from "../config.svelte.js";
4
+ // The weather layer fetches its data with the SDK's global key, which map-sdk (it passes the key
5
+ // to the map only) never sets.
6
+ const defaultCreateLayer = (id) => {
7
+ config.apiKey = monitorMapConfig().maptilerKey;
8
+ return new WindLayer({ id, opacity: 0.5 });
9
+ };
10
+ /**
11
+ * Wind particles as a map-sdk custom layer. `@maptiler/weather` can't be a map-sdk dependency, so
12
+ * the host adds it here; only the layout chunk reaches this module.
13
+ */
14
+ export function windPlugin(options = {}) {
15
+ const createLayer = options.createLayer ?? defaultCreateLayer;
16
+ return {
17
+ id: "wind",
18
+ label: "Wind",
19
+ selectable: false,
20
+ setup(ctx) {
21
+ ctx.addCustomLayer("wind", createLayer);
22
+ return () => { };
23
+ }
24
+ };
25
+ }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@sjvair/monitor-map",
3
3
  "description": "SJVAir's interactive monitor map",
4
- "version": "4.3.0",
4
+ "version": "5.0.0-beta.0",
5
+ "license": "MIT",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "https://github.com/SJVAir/monitor-map"
@@ -17,6 +18,7 @@
17
18
  "format": "prettier --write .",
18
19
  "lint": "prettier --check . && eslint .",
19
20
  "test": "vitest run",
21
+ "map-sdk:pack": "bash scripts/pack-map-sdk.sh",
20
22
  "test:bundle": "npm run build:lib && node tests/bundle/check-bundle.mjs"
21
23
  },
22
24
  "type": "module",
@@ -48,24 +50,28 @@
48
50
  ],
49
51
  "peerDependencies": {
50
52
  "@internationalized/date": "^3.12.4",
53
+ "@sjvair/sdk": "npm:@jsr/sjvair__sdk@^5.0.0-beta.3",
51
54
  "@sveltejs/enhanced-img": "^0.11.0 || ^1.0.0",
52
55
  "bits-ui": "^2.19.2",
53
56
  "sv-router": "^0.19.0",
54
- "svelte": "^5.0.0"
57
+ "svelte": "^5.57.0"
55
58
  },
56
59
  "devDependencies": {
57
60
  "@eslint/compat": "^2.1.1",
58
61
  "@eslint/js": "^10.0.1",
59
62
  "@fontsource-variable/inter": "^5.3.0",
63
+ "@sjvair/sdk": "npm:@jsr/sjvair__sdk@5.0.0-beta.3",
60
64
  "@sveltejs/enhanced-img": "^1.0.0",
61
65
  "@sveltejs/package": "^2.5.8",
62
66
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
63
67
  "@tailwindcss/vite": "^4.3.3",
64
68
  "@types/node": "^26.6.0",
69
+ "@vitest/browser-playwright": "^5.1.0",
65
70
  "eslint": "^10.10.0",
66
71
  "eslint-config-prettier": "^10.1.8",
67
72
  "eslint-plugin-svelte": "^3.23.0",
68
73
  "globals": "^17.12.0",
74
+ "playwright": "^1.65.0",
69
75
  "postcss": "^8.5.28",
70
76
  "prettier": "^3.9.6",
71
77
  "prettier-plugin-svelte": "^4.1.1",
@@ -78,18 +84,14 @@
78
84
  "typescript": "^6.0.3",
79
85
  "typescript-eslint": "^8.70.0",
80
86
  "vite": "^8.3.0",
81
- "vitest": "^5.0.3"
87
+ "vitest": "^5.0.3",
88
+ "vitest-browser-svelte": "^3.1.0"
82
89
  },
83
90
  "dependencies": {
84
91
  "@lucide/svelte": "^1.46.0",
85
- "@maptiler/sdk": "^4.1.0",
92
+ "@maptiler/sdk": "~4.2.0",
86
93
  "@maptiler/weather": "^4.0.1",
87
- "@sjvair/sdk": "npm:@jsr/sjvair__sdk@^4.7.0",
88
- "@tstk/builtin-extensions": "npm:@jsr/tstk__builtin-extensions@^1.0.8",
89
- "@tstk/utils": "npm:@jsr/tstk__utils@^1.1.2",
90
- "@turf/area": "^7.4.0",
91
- "@turf/clusters-dbscan": "^7.4.0",
92
- "@turf/helpers": "^7.4.0",
94
+ "@sjvair/map-sdk": "0.1.0-beta.6",
93
95
  "@types/geojson": "^7946.0.16",
94
96
  "clsx": "^2.1.1",
95
97
  "color2k": "^2.0.4",
@@ -1,18 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { untrack, type ComponentProps } from "svelte";
3
- import type { SJVAirEntryLevel } from "@sjvair/sdk";
4
3
  import DataBox from "$lib/components/DataBox.svelte";
5
4
  import LoadError from "$lib/LoadError.svelte";
6
5
  import MonitorWidgetModal from "$lib/monitors/components/MonitorWidgetModal.svelte";
7
6
  import MonitorSubscriptionManager from "$lib/monitors/components/MonitorSubscriptionManager.svelte";
8
7
  import DataChart from "$lib/data-chart/DataChart.svelte";
9
- import { monitorsManager } from "$lib";
8
+ import { monitorsManager } from "$lib/monitors/monitors.svelte.js";
10
9
  import { MonitorDetailsState } from "$lib/monitors/monitor-details.svelte";
11
- import { choosePanelPollutant } from "$lib/monitors/panel-pollutant";
10
+ import { choosePanelPollutant, PANEL_POLLUTANTS } from "$lib/monitors/panel-pollutant";
12
11
  import { useMonitorMapRouter } from "./router-context";
13
12
  import { fetchTempByCoords } from "$lib/weather";
14
13
  import { Colors, valueToColor } from "$lib/colors";
15
- import { getCurrentLevel } from "$lib/monitors/monitor-utils";
14
+ import { NEUTRAL_LEVEL_COLOR, readingDisplay, readingLevel } from "$lib/levels";
16
15
 
17
16
  type TempDataBox = Pick<ComponentProps<typeof DataBox>, "color" | "value">;
18
17
 
@@ -28,6 +27,10 @@
28
27
  // current reading for the map's pollutant.
29
28
  const details = new MonitorDetailsState();
30
29
  const meta = $derived(monitorsManager.meta);
30
+ // The map's pollutant, when it is one the panel can show.
31
+ const mapPollutant = $derived(
32
+ PANEL_POLLUTANTS.find((p) => p === monitorsManager.pollutant) ?? null
33
+ );
31
34
  // Shown only once the metadata is in too; labels and levels come from it.
32
35
  const monitor = $derived(details.status === "ready" && meta ? details.monitor : null);
33
36
 
@@ -42,14 +45,15 @@
42
45
  // Bumped whenever the map's readings are replaced, i.e. its 2-minute refresh ran. The data
43
46
  // box follows it; the chart decides for itself whether its date range can have new entries.
44
47
  let refreshTick = $state(0);
45
- let seenLatest = untrack(() => monitorsManager.latest);
48
+ let seenLatest = untrack(() => monitorsManager.store.latest);
46
49
 
47
50
  $effect(() => {
48
- const latest = monitorsManager.latest;
49
- // Null while a pollutant change is loading; wait for the readings that follow.
50
- if (!latest || latest === seenLatest) return;
51
+ const latest = monitorsManager.store.latest;
52
+ // Empty before the first load and while a pollutant change is loading; wait for the
53
+ // readings that follow.
54
+ if (latest.size === 0 || latest === seenLatest) return;
51
55
  // The first readings of a cold load arrive alongside this panel's own request.
52
- const first = seenLatest === null;
56
+ const first = seenLatest.size === 0;
53
57
  seenLatest = latest;
54
58
  if (first) return;
55
59
  untrack(() => {
@@ -59,14 +63,13 @@
59
63
  });
60
64
 
61
65
  const panel = $derived(
62
- monitor && meta ? choosePanelPollutant(monitorsManager.pollutant, monitor, meta) : null
66
+ monitor && meta ? choosePanelPollutant(mapPollutant, monitor, meta) : null
63
67
  );
64
68
  const reading = $derived(
65
69
  monitor && panel?.pollutant && panel.hasReading ? monitor.latest[panel.pollutant] : undefined
66
70
  );
67
71
  // Set when the panel shows a different pollutant than the map is coloring by.
68
72
  const pollutantNote = $derived.by(() => {
69
- const mapPollutant = monitorsManager.pollutant;
70
73
  if (!meta || !panel?.pollutant || !panel.hasReading || !mapPollutant) return;
71
74
  if (panel.pollutant === mapPollutant) return;
72
75
  const shown = meta.entryType(panel.pollutant).label;
@@ -81,18 +84,15 @@
81
84
  if (!monitor || !meta || !panel?.pollutant || !reading) return;
82
85
 
83
86
  const entryType = meta.entryType(panel.pollutant);
84
- const level: SJVAirEntryLevel | undefined = getCurrentLevel(
85
- reading.value,
86
- entryType.asIter.levels!
87
- );
87
+ const level = readingLevel(reading.value, entryType.scale);
88
88
 
89
89
  const updateDuration = meta.monitorType(monitor.type).expected_interval;
90
90
 
91
91
  return {
92
- color: level?.color ?? "",
92
+ color: level?.color ?? NEUTRAL_LEVEL_COLOR,
93
93
  header: entryType.label,
94
94
  subheading: `(${updateDuration} avg)`,
95
- value: Math.round(parseInt(reading.value, 10)).toString()
95
+ value: readingDisplay(reading.value)
96
96
  };
97
97
  });
98
98
 
@@ -201,9 +201,12 @@
201
201
  </div>
202
202
  {/if}
203
203
 
204
- <div class="tall:block hidden">
205
- <MonitorWidgetModal monitorId={monitor.id} />
206
- </div>
204
+ <!-- The widget shows current readings, and errors for an inactive monitor. -->
205
+ {#if panel.hasReading}
206
+ <div class="tall:block hidden">
207
+ <MonitorWidgetModal monitorId={monitor.id} />
208
+ </div>
209
+ {/if}
207
210
  <!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
208
211
  {#if (globalThis as any).USER?.is_authenticated}
209
212
  <MonitorSubscriptionManager monitorId={monitor.id} />