@sjvair/monitor-map 3.6.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/MonitorMapLayout.svelte +74 -36
  3. package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +3 -0
  4. package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +15 -1
  5. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +27 -6
  6. package/dist/lib/hms/hms-fire-icon-manager.d.ts +8 -0
  7. package/dist/lib/hms/hms-fire-icon-manager.js +10 -1
  8. package/dist/lib/hms/hms-fire-map-integration.svelte.js +1 -1
  9. package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +20 -0
  10. package/dist/lib/hms/hms-smoke-map-integration.svelte.js +22 -7
  11. package/dist/lib/index.d.ts +21 -7
  12. package/dist/lib/index.js +22 -7
  13. package/dist/lib/legend/Legend.svelte +40 -0
  14. package/dist/lib/legend/Legend.svelte.d.ts +7 -0
  15. package/dist/lib/legend/LegendBar.svelte +45 -0
  16. package/dist/lib/legend/LegendBar.svelte.d.ts +7 -0
  17. package/dist/lib/legend/legend-data.d.ts +40 -0
  18. package/dist/lib/legend/legend-data.js +65 -0
  19. package/dist/lib/map/MapShell.svelte +7 -6
  20. package/dist/lib/map/MapShell.svelte.d.ts +3 -1
  21. package/dist/lib/map/basemap.d.ts +14 -0
  22. package/dist/lib/map/basemap.js +10 -0
  23. package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +6 -0
  24. package/dist/lib/map/integrations/map-geojson-integration.svelte.js +15 -2
  25. package/dist/lib/map/integrations/map-layer-integration.svelte.js +7 -3
  26. package/dist/lib/map/map-style-state.svelte.d.ts +13 -0
  27. package/dist/lib/map/map-style-state.svelte.js +35 -0
  28. package/dist/lib/map/map.svelte.d.ts +6 -0
  29. package/dist/lib/map/map.svelte.js +6 -0
  30. package/dist/lib/monitors/monitor-groups.d.ts +11 -0
  31. package/dist/lib/monitors/monitor-groups.js +7 -0
  32. package/dist/lib/monitors/monitor-utils.js +1 -3
  33. package/dist/lib/monitors/monitors-icon-manager.svelte.js +2 -5
  34. package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +4 -4
  35. package/dist/lib/monitors/monitors-map-integration.svelte.js +43 -30
  36. package/dist/lib/monitors/monitors.svelte.d.ts +1 -0
  37. package/dist/lib/monitors/monitors.svelte.js +22 -0
  38. package/dist/lib/options/OptionsBar.svelte +96 -0
  39. package/dist/lib/options/OptionsBar.svelte.d.ts +10 -0
  40. package/dist/lib/options/OptionsGroup.svelte +12 -0
  41. package/dist/lib/options/OptionsGroup.svelte.d.ts +7 -0
  42. package/dist/lib/options/OptionsMenu.svelte +71 -0
  43. package/dist/lib/options/OptionsMenu.svelte.d.ts +15 -0
  44. package/dist/lib/options/group-state.d.ts +7 -0
  45. package/dist/lib/options/group-state.js +15 -0
  46. package/dist/lib/options/layout.d.ts +7 -0
  47. package/dist/lib/options/layout.js +9 -0
  48. package/dist/lib/options/map-option-defaults.d.ts +56 -0
  49. package/dist/lib/options/map-option-defaults.js +47 -0
  50. package/dist/lib/options/menus/LayersMenu.svelte +47 -0
  51. package/dist/lib/options/menus/LayersMenu.svelte.d.ts +3 -0
  52. package/dist/lib/options/menus/MonitorsMenu.svelte +68 -0
  53. package/dist/lib/options/menus/MonitorsMenu.svelte.d.ts +3 -0
  54. package/dist/lib/options/menus/OverlaysMenu.svelte +18 -0
  55. package/dist/lib/{MapLegend.svelte.d.ts → options/menus/OverlaysMenu.svelte.d.ts} +3 -3
  56. package/dist/lib/options/menus/PollutantMenu.svelte +79 -0
  57. package/dist/lib/options/menus/PollutantMenu.svelte.d.ts +3 -0
  58. package/dist/lib/options/menus/SettingsMenu.svelte +73 -0
  59. package/dist/lib/{hms/components/HMSFireLegend.svelte.d.ts → options/menus/SettingsMenu.svelte.d.ts} +3 -3
  60. package/dist/lib/options/options-state.svelte.d.ts +12 -0
  61. package/dist/lib/options/options-state.svelte.js +42 -0
  62. package/dist/lib/options/pollutant-param.d.ts +17 -0
  63. package/dist/lib/options/pollutant-param.js +14 -0
  64. package/dist/lib/options/reset-map-options.d.ts +2 -0
  65. package/dist/lib/options/reset-map-options.js +24 -0
  66. package/dist/lib/options/rows/CheckboxRow.svelte +51 -0
  67. package/dist/lib/options/rows/CheckboxRow.svelte.d.ts +13 -0
  68. package/dist/lib/options/rows/GroupRow.svelte +43 -0
  69. package/dist/lib/options/rows/GroupRow.svelte.d.ts +17 -0
  70. package/dist/lib/options/rows/MarkerIcon.svelte +10 -0
  71. package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +8 -0
  72. package/dist/lib/options/rows/RadioRow.svelte +33 -0
  73. package/dist/lib/options/rows/RadioRow.svelte.d.ts +13 -0
  74. package/dist/lib/options/rows/SubmenuRow.svelte +22 -0
  75. package/dist/lib/options/rows/SubmenuRow.svelte.d.ts +11 -0
  76. package/dist/lib/search/Search.svelte +6 -6
  77. package/package.json +5 -3
  78. package/src/lib/MonitorMapLayout.svelte +74 -36
  79. package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +17 -1
  80. package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +26 -6
  81. package/src/lib/hms/hms-fire-icon-manager.ts +16 -2
  82. package/src/lib/hms/hms-fire-map-integration.svelte.ts +1 -1
  83. package/src/lib/hms/hms-smoke-map-integration.svelte.ts +25 -7
  84. package/src/lib/index.ts +31 -7
  85. package/src/lib/legend/Legend.svelte +40 -0
  86. package/src/lib/legend/LegendBar.svelte +45 -0
  87. package/src/lib/legend/legend-data.ts +101 -0
  88. package/src/lib/map/MapShell.svelte +7 -6
  89. package/src/lib/map/basemap.ts +24 -0
  90. package/src/lib/map/integrations/map-geojson-integration.svelte.ts +15 -2
  91. package/src/lib/map/integrations/map-layer-integration.svelte.ts +6 -3
  92. package/src/lib/map/map-style-state.svelte.ts +42 -0
  93. package/src/lib/map/map.svelte.ts +6 -0
  94. package/src/lib/monitors/monitor-groups.ts +20 -0
  95. package/src/lib/monitors/monitor-utils.ts +1 -5
  96. package/src/lib/monitors/monitors-icon-manager.svelte.ts +2 -8
  97. package/src/lib/monitors/monitors-map-integration.svelte.ts +37 -27
  98. package/src/lib/monitors/monitors.svelte.ts +22 -0
  99. package/src/lib/options/OptionsBar.svelte +96 -0
  100. package/src/lib/options/OptionsGroup.svelte +12 -0
  101. package/src/lib/options/OptionsMenu.svelte +71 -0
  102. package/src/lib/options/group-state.ts +16 -0
  103. package/src/lib/options/layout.ts +10 -0
  104. package/src/lib/options/map-option-defaults.ts +71 -0
  105. package/src/lib/options/menus/LayersMenu.svelte +47 -0
  106. package/src/lib/options/menus/MonitorsMenu.svelte +68 -0
  107. package/src/lib/options/menus/OverlaysMenu.svelte +18 -0
  108. package/src/lib/options/menus/PollutantMenu.svelte +79 -0
  109. package/src/lib/options/menus/SettingsMenu.svelte +73 -0
  110. package/src/lib/options/options-state.svelte.ts +48 -0
  111. package/src/lib/options/pollutant-param.ts +35 -0
  112. package/src/lib/options/reset-map-options.ts +28 -0
  113. package/src/lib/options/rows/CheckboxRow.svelte +51 -0
  114. package/src/lib/options/rows/GroupRow.svelte +43 -0
  115. package/src/lib/options/rows/MarkerIcon.svelte +10 -0
  116. package/src/lib/options/rows/RadioRow.svelte +33 -0
  117. package/src/lib/options/rows/SubmenuRow.svelte +22 -0
  118. package/src/lib/search/Search.svelte +6 -6
  119. package/dist/lib/MapLegend.svelte +0 -15
  120. package/dist/lib/collocation-sites/collocation-utils.d.ts +0 -3
  121. package/dist/lib/collocation-sites/collocation-utils.js +0 -30
  122. package/dist/lib/components/DisplayOption.svelte +0 -7
  123. package/dist/lib/components/DisplayOption.svelte.d.ts +0 -5
  124. package/dist/lib/components/MapLayersDisplayOptions.svelte +0 -42
  125. package/dist/lib/components/MapLayersDisplayOptions.svelte.d.ts +0 -18
  126. package/dist/lib/components/SegmentedControl.svelte +0 -52
  127. package/dist/lib/components/SegmentedControl.svelte.d.ts +0 -11
  128. package/dist/lib/components/ToggleSwitch.svelte +0 -93
  129. package/dist/lib/components/ToggleSwitch.svelte.d.ts +0 -9
  130. package/dist/lib/ev-stations/components/EvStationsDisplayOptions.svelte +0 -46
  131. package/dist/lib/ev-stations/components/EvStationsDisplayOptions.svelte.d.ts +0 -18
  132. package/dist/lib/hms/components/HMSFireLegend.svelte +0 -21
  133. package/dist/lib/map/MapStyleDisplayOptions.svelte +0 -61
  134. package/dist/lib/map/MapStyleDisplayOptions.svelte.d.ts +0 -3
  135. package/dist/lib/map/Menu.svelte +0 -65
  136. package/dist/lib/map/Menu.svelte.d.ts +0 -5
  137. package/dist/lib/monitors/components/MonitorMarkersLegend.svelte +0 -41
  138. package/dist/lib/monitors/components/MonitorMarkersLegend.svelte.d.ts +0 -3
  139. package/dist/lib/monitors/components/MonitorsDisplayOptions.svelte +0 -85
  140. package/dist/lib/monitors/components/MonitorsDisplayOptions.svelte.d.ts +0 -3
  141. package/src/lib/MapLegend.svelte +0 -15
  142. package/src/lib/collocation-sites/collocation-utils.ts +0 -41
  143. package/src/lib/components/DisplayOption.svelte +0 -7
  144. package/src/lib/components/MapLayersDisplayOptions.svelte +0 -42
  145. package/src/lib/components/SegmentedControl.svelte +0 -52
  146. package/src/lib/components/ToggleSwitch.svelte +0 -93
  147. package/src/lib/ev-stations/components/EvStationsDisplayOptions.svelte +0 -46
  148. package/src/lib/hms/components/HMSFireLegend.svelte +0 -21
  149. package/src/lib/map/MapStyleDisplayOptions.svelte +0 -61
  150. package/src/lib/map/Menu.svelte +0 -65
  151. package/src/lib/monitors/components/MonitorMarkersLegend.svelte +0 -41
  152. package/src/lib/monitors/components/MonitorsDisplayOptions.svelte +0 -85
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { Attachment } from "svelte/attachments";
4
+ import { MenuIcon, XIcon } from "@lucide/svelte";
5
+ import { provideOptionsMenus } from "./options-state.svelte.js";
6
+ import { isWideLayout, WIDE_LAYOUT_QUERY } from "./layout";
7
+
8
+ interface Props {
9
+ /** OptionsGroup / OptionsMenu content */
10
+ children?: Snippet;
11
+ /** Rendered last in the toolbar (wide) or beside the menu button (narrow) */
12
+ search?: Snippet;
13
+ }
14
+
15
+ let { children, search }: Props = $props();
16
+
17
+ const menus = provideOptionsMenus();
18
+ const panelId = "monitor-map-options-panel";
19
+ let panelOpen = $state(false);
20
+
21
+ // Toolbar only: clicks outside close any click-opened dropdown; clicks inside never do.
22
+ const closeOnOutsideClick: Attachment<HTMLElement> = (node) => {
23
+ function handler(e: MouseEvent) {
24
+ if (isWideLayout() && !node.contains(e.target as Node)) menus.closeAll();
25
+ }
26
+ document.addEventListener("click", handler);
27
+ return () => document.removeEventListener("click", handler);
28
+ };
29
+
30
+ // Open menus mean different things in the toolbar (dropdowns) and the panel (expanded
31
+ // sections), so crossing the breakpoint (e.g. rotating a phone) starts from a closed state.
32
+ $effect(() => {
33
+ const query = window.matchMedia(WIDE_LAYOUT_QUERY);
34
+ function onchange() {
35
+ menus.closeAll();
36
+ panelOpen = false;
37
+ }
38
+ query.addEventListener("change", onchange);
39
+ return () => query.removeEventListener("change", onchange);
40
+ });
41
+
42
+ function onkeydown(e: KeyboardEvent) {
43
+ if (e.key !== "Escape") return;
44
+ panelOpen = false;
45
+ if (isWideLayout()) menus.closeAll();
46
+ }
47
+ </script>
48
+
49
+ <svelte:window {onkeydown} />
50
+
51
+ <!-- Covers the map area so the narrow panel can fill it; passes pointer events through. -->
52
+ <div class="pointer-events-none absolute inset-0 z-10">
53
+ <div
54
+ {@attach closeOnOutsideClick}
55
+ class="md:absolute md:top-4 md:right-16 md:left-4 md:flex md:flex-wrap md:items-start md:gap-2"
56
+ >
57
+ <div
58
+ class="pointer-events-auto flex items-start gap-2 max-md:absolute max-md:top-4 max-md:left-4 md:contents"
59
+ >
60
+ <button
61
+ type="button"
62
+ class="flex size-10 shrink-0 items-center justify-center rounded-md bg-white shadow md:hidden"
63
+ aria-label="Open map options"
64
+ aria-expanded={panelOpen}
65
+ aria-controls={panelId}
66
+ onclick={() => (panelOpen = true)}
67
+ >
68
+ <MenuIcon size={22} />
69
+ </button>
70
+ {#if search}
71
+ <div class="md:order-last">{@render search()}</div>
72
+ {/if}
73
+ </div>
74
+ <div
75
+ id={panelId}
76
+ class={[
77
+ "pointer-events-auto md:contents",
78
+ "max-md:absolute max-md:inset-0 max-md:z-20 max-md:flex max-md:flex-col max-md:gap-2 max-md:overflow-y-auto max-md:bg-white max-md:p-4 max-md:transition-[translate,visibility] max-md:duration-200",
79
+ !panelOpen && "max-md:invisible max-md:-translate-x-full"
80
+ ]}
81
+ >
82
+ <div class="mb-2 flex items-center justify-between md:hidden">
83
+ <p class="text-lg font-semibold">Map Options</p>
84
+ <button
85
+ type="button"
86
+ class="flex size-9 items-center justify-center rounded-md hover:bg-gray-100"
87
+ aria-label="Close map options"
88
+ onclick={() => (panelOpen = false)}
89
+ >
90
+ <XIcon size={20} />
91
+ </button>
92
+ </div>
93
+ {@render children?.()}
94
+ </div>
95
+ </div>
96
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ /** OptionsGroup / OptionsMenu content */
4
+ children?: Snippet;
5
+ /** Rendered last in the toolbar (wide) or beside the menu button (narrow) */
6
+ search?: Snippet;
7
+ }
8
+ declare const OptionsBar: import("svelte").Component<Props, {}, "">;
9
+ type OptionsBar = ReturnType<typeof OptionsBar>;
10
+ export default OptionsBar;
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ let { children }: { children: Snippet } = $props();
5
+ </script>
6
+
7
+ <!-- Joined button cluster on the toolbar; a plain stack of sections in the panel. -->
8
+ <div
9
+ class="flex flex-col gap-2 md:flex-row md:gap-0 md:divide-x md:divide-gray-200 md:rounded-md md:bg-white md:shadow"
10
+ >
11
+ {@render children()}
12
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ children: Snippet;
4
+ };
5
+ declare const OptionsGroup: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type OptionsGroup = ReturnType<typeof OptionsGroup>;
7
+ export default OptionsGroup;
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { ChevronDownIcon } from "@lucide/svelte";
4
+ import { useOptionsMenus } from "./options-state.svelte.js";
5
+ import { isWideLayout } from "./layout";
6
+
7
+ interface Props {
8
+ id: string;
9
+ label: string;
10
+ icon: Snippet;
11
+ children: Snippet;
12
+ disabled?: boolean;
13
+ /** Anchor the toolbar dropdown to the trigger's right edge */
14
+ alignRight?: boolean;
15
+ /** Show only the icon on the toolbar; the panel section still shows the label */
16
+ iconOnly?: boolean;
17
+ }
18
+
19
+ let {
20
+ id,
21
+ label,
22
+ icon,
23
+ children,
24
+ disabled = false,
25
+ alignRight = false,
26
+ iconOnly = false
27
+ }: Props = $props();
28
+
29
+ const menus = useOptionsMenus();
30
+ const contentId = $derived(`monitor-map-options-${id}`);
31
+ const open = $derived(menus.isOpen(id) && !disabled);
32
+ </script>
33
+
34
+ <div
35
+ role="presentation"
36
+ class="group/menu relative md:first:[&>button]:rounded-l-md md:last:[&>button]:rounded-r-md"
37
+ onmouseenter={() => {
38
+ if (isWideLayout()) menus.closeOthers(id);
39
+ }}
40
+ >
41
+ <button
42
+ type="button"
43
+ {disabled}
44
+ aria-expanded={open}
45
+ aria-controls={contentId}
46
+ aria-label={iconOnly ? label : undefined}
47
+ class="flex h-10 w-full items-center gap-2 px-3 text-sm font-medium whitespace-nowrap select-none disabled:cursor-not-allowed max-md:rounded-md max-md:border max-md:border-gray-200 max-md:bg-white md:w-auto md:hover:bg-gray-100 md:disabled:hover:bg-transparent"
48
+ onclick={() => menus.toggle(id)}
49
+ >
50
+ <span class={["flex size-5 items-center justify-center", disabled && "opacity-50"]}>
51
+ {@render icon()}
52
+ </span>
53
+ <span class={[disabled && "opacity-50", iconOnly && "md:hidden"]}>{label}</span>
54
+ <span class={["ml-auto", disabled && "opacity-50", iconOnly && "md:hidden"]}>
55
+ <ChevronDownIcon size={16} />
56
+ </span>
57
+ </button>
58
+ <div
59
+ id={contentId}
60
+ class={[
61
+ "max-md:pt-1 md:absolute md:top-full md:z-20 md:min-w-64 md:pt-1",
62
+ alignRight ? "md:right-0" : "md:left-0",
63
+ open ? "block" : "hidden",
64
+ !disabled && "md:group-hover/menu:block md:group-has-[:focus-visible]/menu:block"
65
+ ]}
66
+ >
67
+ <div class="py-1 md:max-h-[69vh] md:overflow-y-auto md:rounded-md md:bg-white md:shadow-lg">
68
+ {@render children()}
69
+ </div>
70
+ </div>
71
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ id: string;
4
+ label: string;
5
+ icon: Snippet;
6
+ children: Snippet;
7
+ disabled?: boolean;
8
+ /** Anchor the toolbar dropdown to the trigger's right edge */
9
+ alignRight?: boolean;
10
+ /** Show only the icon on the toolbar; the panel section still shows the label */
11
+ iconOnly?: boolean;
12
+ }
13
+ declare const OptionsMenu: import("svelte").Component<Props, {}, "">;
14
+ type OptionsMenu = ReturnType<typeof OptionsMenu>;
15
+ export default OptionsMenu;
@@ -0,0 +1,7 @@
1
+ export type GroupState = "checked" | "unchecked" | "indeterminate" | "empty";
2
+ /** Select-all state of a group, computed only from its currently visible rows. */
3
+ export declare function groupState(values: ReadonlyArray<boolean>): GroupState;
4
+ /** Applies a select-all toggle to the given (visible) rows only. */
5
+ export declare function setGroup(options: ReadonlyArray<{
6
+ value: boolean;
7
+ }>, value: boolean): void;
@@ -0,0 +1,15 @@
1
+ /** Select-all state of a group, computed only from its currently visible rows. */
2
+ export function groupState(values) {
3
+ if (values.length === 0)
4
+ return "empty";
5
+ const checkedCount = values.filter(Boolean).length;
6
+ if (checkedCount === 0)
7
+ return "unchecked";
8
+ return checkedCount === values.length ? "checked" : "indeterminate";
9
+ }
10
+ /** Applies a select-all toggle to the given (visible) rows only. */
11
+ export function setGroup(options, value) {
12
+ for (const option of options) {
13
+ option.value = value;
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tailwind's default `md` breakpoint (48rem). Layout itself is pure CSS (`md:` / `max-md:`);
3
+ * this is only read at event time for the few behaviors that differ between the toolbar and
4
+ * the options panel.
5
+ */
6
+ export declare const WIDE_LAYOUT_QUERY = "(min-width: 48rem)";
7
+ export declare function isWideLayout(): boolean;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tailwind's default `md` breakpoint (48rem). Layout itself is pure CSS (`md:` / `max-md:`);
3
+ * this is only read at event time for the few behaviors that differ between the toolbar and
4
+ * the options panel.
5
+ */
6
+ export const WIDE_LAYOUT_QUERY = "(min-width: 48rem)";
7
+ export function isWideLayout() {
8
+ return window.matchMedia(WIDE_LAYOUT_QUERY).matches;
9
+ }
@@ -0,0 +1,56 @@
1
+ import type { MonitorsPollutant } from "../monitors/types";
2
+ import { type PollutantTargets } from "./pollutant-param";
3
+ /**
4
+ * Defaults restored by "Reset Map Options". Keep in sync with the constructor defaults in each
5
+ * integration (monitors/EV display options, `enabled`, `clustered`) so a reset matches a fresh load.
6
+ */
7
+ export declare const MAP_OPTION_DEFAULTS: {
8
+ readonly monitorTypes: {
9
+ readonly airnow: true;
10
+ readonly aqlite: true;
11
+ readonly aqview: true;
12
+ readonly bam1022: true;
13
+ readonly airgradient: true;
14
+ readonly purpleair: true;
15
+ readonly vozbox: true;
16
+ readonly inactive: false;
17
+ readonly inside: false;
18
+ };
19
+ readonly clustered: true;
20
+ readonly collocationSites: false;
21
+ readonly fireAndSmoke: true;
22
+ readonly evStations: false;
23
+ readonly evLevels: {
24
+ readonly lvl2: true;
25
+ readonly lvl3: true;
26
+ };
27
+ readonly wind: false;
28
+ };
29
+ type Toggle = {
30
+ enabled: boolean;
31
+ };
32
+ type Option = {
33
+ value: boolean;
34
+ };
35
+ export interface MapOptionTargets extends PollutantTargets {
36
+ integration: PollutantTargets["integration"] & {
37
+ clustered: boolean;
38
+ displayOptions: Record<string, Option>;
39
+ };
40
+ collocationSites: Toggle;
41
+ hmsFire: Toggle;
42
+ hmsSmoke: Toggle;
43
+ evStations: Toggle & {
44
+ clustered: boolean;
45
+ displayOptions: {
46
+ lvl2: Option;
47
+ lvl3: Option;
48
+ };
49
+ };
50
+ wind: Toggle;
51
+ mapStyle: {
52
+ reset(): void;
53
+ };
54
+ }
55
+ export declare function applyMapOptionDefaults(targets: MapOptionTargets, defaultPollutant: MonitorsPollutant | null): void;
56
+ export {};
@@ -0,0 +1,47 @@
1
+ import { applyPollutantSelection } from "./pollutant-param";
2
+ /**
3
+ * Defaults restored by "Reset Map Options". Keep in sync with the constructor defaults in each
4
+ * integration (monitors/EV display options, `enabled`, `clustered`) so a reset matches a fresh load.
5
+ */
6
+ export const MAP_OPTION_DEFAULTS = {
7
+ monitorTypes: {
8
+ airnow: true,
9
+ aqlite: true,
10
+ aqview: true,
11
+ bam1022: true,
12
+ airgradient: true,
13
+ purpleair: true,
14
+ vozbox: true,
15
+ inactive: false,
16
+ inside: false
17
+ },
18
+ clustered: true,
19
+ collocationSites: false,
20
+ fireAndSmoke: true,
21
+ evStations: false,
22
+ evLevels: { lvl2: true, lvl3: true },
23
+ wind: false
24
+ };
25
+ export function applyMapOptionDefaults(targets, defaultPollutant) {
26
+ if (defaultPollutant) {
27
+ applyPollutantSelection(defaultPollutant, targets);
28
+ }
29
+ else {
30
+ targets.integration.enabled = true;
31
+ }
32
+ for (const [key, value] of Object.entries(MAP_OPTION_DEFAULTS.monitorTypes)) {
33
+ const option = targets.integration.displayOptions[key];
34
+ if (option)
35
+ option.value = value;
36
+ }
37
+ targets.integration.clustered = MAP_OPTION_DEFAULTS.clustered;
38
+ targets.collocationSites.enabled = MAP_OPTION_DEFAULTS.collocationSites;
39
+ targets.hmsFire.enabled = MAP_OPTION_DEFAULTS.fireAndSmoke;
40
+ targets.hmsSmoke.enabled = MAP_OPTION_DEFAULTS.fireAndSmoke;
41
+ targets.evStations.enabled = MAP_OPTION_DEFAULTS.evStations;
42
+ targets.evStations.clustered = MAP_OPTION_DEFAULTS.clustered;
43
+ targets.evStations.displayOptions.lvl2.value = MAP_OPTION_DEFAULTS.evLevels.lvl2;
44
+ targets.evStations.displayOptions.lvl3.value = MAP_OPTION_DEFAULTS.evLevels.lvl3;
45
+ targets.wind.enabled = MAP_OPTION_DEFAULTS.wind;
46
+ targets.mapStyle.reset();
47
+ }
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import { FlameIcon, LayersIcon, EvChargerIcon } from "@lucide/svelte";
3
+ import OptionsMenu from "../OptionsMenu.svelte";
4
+ import CheckboxRow from "../rows/CheckboxRow.svelte";
5
+ import SubmenuRow from "../rows/SubmenuRow.svelte";
6
+ import MarkerIcon from "../rows/MarkerIcon.svelte";
7
+ import { hmsFireMapIntegration } from "../../hms/hms-fire-map-integration.svelte";
8
+ import { hmsSmokeMapIntegration } from "../../hms/hms-smoke-map-integration.svelte";
9
+ import { evStationsMapIntegration } from "../../ev-stations/ev-stations-map-integration.svelte";
10
+
11
+ const levels = $derived(evStationsMapIntegration.displayOptions);
12
+ </script>
13
+
14
+ <OptionsMenu id="layers" label="Layers">
15
+ {#snippet icon()}
16
+ <LayersIcon size={20} color="#708090" />
17
+ {/snippet}
18
+ <CheckboxRow
19
+ label="Fire & Smoke"
20
+ bind:checked={
21
+ () => hmsFireMapIntegration.enabled && hmsSmokeMapIntegration.enabled,
22
+ (value) => {
23
+ hmsFireMapIntegration.enabled = value;
24
+ hmsSmokeMapIntegration.enabled = value;
25
+ }
26
+ }
27
+ >
28
+ {#snippet icon()}
29
+ <FlameIcon size={18} color="#ff4500" />
30
+ {/snippet}
31
+ </CheckboxRow>
32
+ <SubmenuRow label="EV Chargers" bind:checked={evStationsMapIntegration.enabled}>
33
+ {#snippet icon()}
34
+ <EvChargerIcon size={18} color="#708090" />
35
+ {/snippet}
36
+ <CheckboxRow label="Level 2" bind:checked={evStationsMapIntegration.displayOptions.lvl2.value}>
37
+ {#snippet icon()}
38
+ <MarkerIcon icon={levels.lvl2.icon} class="w-5" />
39
+ {/snippet}
40
+ </CheckboxRow>
41
+ <CheckboxRow label="Level 3" bind:checked={evStationsMapIntegration.displayOptions.lvl3.value}>
42
+ {#snippet icon()}
43
+ <MarkerIcon icon={levels.lvl3.icon} class="w-5" />
44
+ {/snippet}
45
+ </CheckboxRow>
46
+ </SubmenuRow>
47
+ </OptionsMenu>
@@ -0,0 +1,3 @@
1
+ declare const LayersMenu: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type LayersMenu = ReturnType<typeof LayersMenu>;
3
+ export default LayersMenu;
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { CrosshairIcon, RadioTowerIcon } from "@lucide/svelte";
3
+ import OptionsMenu from "../OptionsMenu.svelte";
4
+ import CheckboxRow from "../rows/CheckboxRow.svelte";
5
+ import GroupRow from "../rows/GroupRow.svelte";
6
+ import MarkerIcon from "../rows/MarkerIcon.svelte";
7
+ import { monitorsManager } from "../../monitors/monitors.svelte";
8
+ import { monitorsMapIntegration } from "../../monitors/monitors-map-integration.svelte";
9
+ import { collocationSitesMapIntegration } from "../../collocation-sites/collocations-map-integration.svelte";
10
+ import {
11
+ LOW_COST_TYPES,
12
+ REFERENCE_GRADE_TYPES,
13
+ typeSupportsPollutant,
14
+ type MonitorGroupType
15
+ } from "../../monitors/monitor-groups";
16
+
17
+ // Rows for types that don't report the current pollutant are hidden (meta-driven).
18
+ function visibleRows(types: ReadonlyArray<MonitorGroupType>) {
19
+ const displayOptions = monitorsMapIntegration.displayOptions;
20
+ return types
21
+ .filter((type) =>
22
+ typeSupportsPollutant(monitorsManager.meta, type, monitorsManager.pollutant)
23
+ )
24
+ .map((type) => ({
25
+ key: type,
26
+ label: displayOptions[type].label,
27
+ option: displayOptions[type]
28
+ }));
29
+ }
30
+
31
+ const referenceGrade = $derived(visibleRows(REFERENCE_GRADE_TYPES));
32
+ const lowCost = $derived(visibleRows(LOW_COST_TYPES));
33
+ const icons = monitorsMapIntegration.icons;
34
+ </script>
35
+
36
+ <OptionsMenu id="monitors" label="Monitors" disabled={!monitorsMapIntegration.enabled}>
37
+ {#snippet icon()}
38
+ <RadioTowerIcon size={20} color="#4682b4" />
39
+ {/snippet}
40
+ <GroupRow label="Reference-grade" options={referenceGrade}>
41
+ {#snippet icon()}
42
+ <MarkerIcon icon={icons.get("outside-display-triangle")} />
43
+ {/snippet}
44
+ </GroupRow>
45
+ <GroupRow label="Low-cost sensors" options={lowCost}>
46
+ {#snippet icon()}
47
+ <MarkerIcon icon={icons.get("outside-display-circle")} />
48
+ {/snippet}
49
+ </GroupRow>
50
+ <hr class="my-1 border-gray-200" />
51
+ <CheckboxRow label="Inactive" bind:checked={monitorsMapIntegration.displayOptions.inactive.value}>
52
+ {#snippet icon()}
53
+ <MarkerIcon icon={monitorsMapIntegration.displayOptions.inactive.icon} />
54
+ {/snippet}
55
+ </CheckboxRow>
56
+ <CheckboxRow label="Inside" bind:checked={monitorsMapIntegration.displayOptions.inside.value}>
57
+ {#snippet icon()}
58
+ <MarkerIcon icon={monitorsMapIntegration.displayOptions.inside.icon} />
59
+ {/snippet}
60
+ </CheckboxRow>
61
+ {#if monitorsManager.pollutant === "pm25"}
62
+ <CheckboxRow label="Collocation Sites" bind:checked={collocationSitesMapIntegration.enabled}>
63
+ {#snippet icon()}
64
+ <CrosshairIcon size={16} color="#4A5FC6" />
65
+ {/snippet}
66
+ </CheckboxRow>
67
+ {/if}
68
+ </OptionsMenu>
@@ -0,0 +1,3 @@
1
+ declare const MonitorsMenu: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type MonitorsMenu = ReturnType<typeof MonitorsMenu>;
3
+ export default MonitorsMenu;
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { SquareStackIcon, WindIcon } from "@lucide/svelte";
3
+ import OptionsMenu from "../OptionsMenu.svelte";
4
+ import CheckboxRow from "../rows/CheckboxRow.svelte";
5
+ import { windMapIntegration } from "../../wind/wind.svelte";
6
+ </script>
7
+
8
+ <!-- Independent checkboxes: several overlays may be on at once. -->
9
+ <OptionsMenu id="overlays" label="Overlays" alignRight>
10
+ {#snippet icon()}
11
+ <SquareStackIcon size={20} color="#708090" />
12
+ {/snippet}
13
+ <CheckboxRow label="Wind" bind:checked={windMapIntegration.enabled}>
14
+ {#snippet icon()}
15
+ <WindIcon size={18} color="#1e90ff" />
16
+ {/snippet}
17
+ </CheckboxRow>
18
+ </OptionsMenu>
@@ -11,8 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
11
11
  };
12
12
  z_$$bindings?: Bindings;
13
13
  }
14
- declare const MapLegend: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
14
+ declare const OverlaysMenu: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
15
  [evt: string]: CustomEvent<any>;
16
16
  }, {}, {}, string>;
17
- type MapLegend = InstanceType<typeof MapLegend>;
18
- export default MapLegend;
17
+ type OverlaysMenu = InstanceType<typeof OverlaysMenu>;
18
+ export default OverlaysMenu;
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ import { BanIcon, HazeIcon, SunDimIcon } from "@lucide/svelte";
3
+ import OptionsMenu from "../OptionsMenu.svelte";
4
+ import RadioRow from "../rows/RadioRow.svelte";
5
+ import { useOptionsMenus } from "../options-state.svelte.js";
6
+ import { isWideLayout } from "../layout";
7
+ import {
8
+ applyPollutantSelection,
9
+ currentSelection,
10
+ type PollutantSelection
11
+ } from "../pollutant-param";
12
+ import { monitorsManager } from "../../monitors/monitors.svelte";
13
+ import { monitorsMapIntegration } from "../../monitors/monitors-map-integration.svelte";
14
+
15
+ interface PollutantOption {
16
+ value: PollutantSelection;
17
+ label: string;
18
+ unit?: string;
19
+ help?: string;
20
+ icon: typeof HazeIcon;
21
+ color: string;
22
+ }
23
+
24
+ const options: Array<PollutantOption> = [
25
+ {
26
+ value: "pm25",
27
+ label: "PM2.5",
28
+ unit: "µg/m³",
29
+ help: "Fine particles from smoke, dust, and vehicle exhaust.",
30
+ icon: HazeIcon,
31
+ color: "#bc8f8f"
32
+ },
33
+ {
34
+ value: "o3",
35
+ label: "Ozone",
36
+ unit: "ppb",
37
+ help: "Ground-level gas that forms in sunlight on hot days.",
38
+ icon: SunDimIcon,
39
+ color: "#daa520"
40
+ },
41
+ { value: "none", label: "None", icon: BanIcon, color: "#778899" }
42
+ ];
43
+
44
+ const menus = useOptionsMenus();
45
+ const selection = $derived(
46
+ currentSelection(monitorsManager.pollutant, monitorsMapIntegration.enabled)
47
+ );
48
+ const selected = $derived(options.find((o) => o.value === selection) ?? options[0]);
49
+
50
+ function select(value: PollutantSelection) {
51
+ applyPollutantSelection(value, {
52
+ manager: monitorsManager,
53
+ integration: monitorsMapIntegration
54
+ });
55
+ // Toolbar dropdown closes on pick; the panel section stays open.
56
+ if (isWideLayout()) menus.close("pollutant");
57
+ }
58
+ </script>
59
+
60
+ <OptionsMenu id="pollutant" label={selected.label}>
61
+ {#snippet icon()}
62
+ <selected.icon size={20} color={selected.color} />
63
+ {/snippet}
64
+ {#each options as option, i (option.value)}
65
+ {#if i > 0}<hr class="my-1 border-gray-200" />{/if}
66
+ <RadioRow
67
+ name="monitor-map-pollutant"
68
+ label={option.label}
69
+ unit={option.unit}
70
+ help={option.help}
71
+ checked={selection === option.value}
72
+ onselect={() => select(option.value)}
73
+ >
74
+ {#snippet icon()}
75
+ <option.icon size={18} color={option.color} />
76
+ {/snippet}
77
+ </RadioRow>
78
+ {/each}
79
+ </OptionsMenu>
@@ -0,0 +1,3 @@
1
+ declare const PollutantMenu: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type PollutantMenu = ReturnType<typeof PollutantMenu>;
3
+ export default PollutantMenu;