@sjvair/monitor-map 3.6.2 → 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 +6 -4
  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,73 @@
1
+ <script lang="ts">
2
+ import { ContrastIcon, MapIcon, NetworkIcon, RotateCcwIcon, SettingsIcon } from "@lucide/svelte";
3
+ import OptionsMenu from "../OptionsMenu.svelte";
4
+ import CheckboxRow from "../rows/CheckboxRow.svelte";
5
+ import RadioRow from "../rows/RadioRow.svelte";
6
+ import { resetMapOptions } from "../reset-map-options";
7
+ import { MAP_STYLE_OPTIONS } from "../../map/utils";
8
+ import { mapStyleState } from "../../map/map-style-state.svelte.js";
9
+ import { monitorsMapIntegration } from "../../monitors/monitors-map-integration.svelte";
10
+ import { evStationsMapIntegration } from "../../ev-stations/ev-stations-map-integration.svelte";
11
+ </script>
12
+
13
+ <OptionsMenu id="settings" label="Settings" iconOnly alignRight>
14
+ {#snippet icon()}
15
+ <SettingsIcon size={20} color="#696969" />
16
+ {/snippet}
17
+ <CheckboxRow
18
+ label="Marker Clusters"
19
+ bind:checked={
20
+ () => monitorsMapIntegration.clustered && evStationsMapIntegration.clustered,
21
+ (value) => {
22
+ monitorsMapIntegration.clustered = value;
23
+ evStationsMapIntegration.clustered = value;
24
+ }
25
+ }
26
+ >
27
+ {#snippet icon()}
28
+ <NetworkIcon size={18} color="#1e90ff" />
29
+ {/snippet}
30
+ </CheckboxRow>
31
+ <hr class="my-1 border-gray-200" />
32
+ <div class="px-3 py-1.5 text-sm">
33
+ <label for="monitor-map-basemap" class="mb-1 flex items-center gap-2 font-semibold">
34
+ <MapIcon size={18} color="#708090" />
35
+ Basemap
36
+ </label>
37
+ <select
38
+ id="monitor-map-basemap"
39
+ class="w-full rounded-md border border-gray-300 bg-white p-1.5"
40
+ bind:value={
41
+ () => mapStyleState.referenceStyle, (style) => mapStyleState.selectReferenceStyle(style)
42
+ }
43
+ >
44
+ {#each MAP_STYLE_OPTIONS as style (style.getId())}
45
+ <option value={style}>{style.getName()}</option>
46
+ {/each}
47
+ </select>
48
+ </div>
49
+ <hr class="my-1 border-gray-200" />
50
+ <p class="flex items-center gap-2 px-3 pt-1.5 text-sm font-semibold">
51
+ <ContrastIcon size={18} color="#708090" />
52
+ Theme
53
+ </p>
54
+ {#each mapStyleState.referenceStyle.getVariants() as variant (variant.getId())}
55
+ <RadioRow
56
+ name="monitor-map-theme"
57
+ label={variant.getName()}
58
+ checked={variant === mapStyleState.variant}
59
+ onselect={() => mapStyleState.selectVariant(variant)}
60
+ />
61
+ {/each}
62
+ <hr class="my-1 border-gray-200" />
63
+ <div class="px-3 py-1.5">
64
+ <button
65
+ type="button"
66
+ class="flex w-full items-center justify-center gap-2 rounded-md border border-gray-300 px-3 py-1.5 text-sm hover:bg-gray-100"
67
+ onclick={resetMapOptions}
68
+ >
69
+ <RotateCcwIcon size={16} />
70
+ Reset Map Options
71
+ </button>
72
+ </div>
73
+ </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 HMSFireLegend: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
14
+ declare const SettingsMenu: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
15
  [evt: string]: CustomEvent<any>;
16
16
  }, {}, {}, string>;
17
- type HMSFireLegend = InstanceType<typeof HMSFireLegend>;
18
- export default HMSFireLegend;
17
+ type SettingsMenu = InstanceType<typeof SettingsMenu>;
18
+ export default SettingsMenu;
@@ -0,0 +1,12 @@
1
+ /** Which menus are opened by click: at most one on the toolbar, any number in the panel. */
2
+ export declare class OptionsMenusState {
3
+ private open;
4
+ isOpen(id: string): boolean;
5
+ toggle(id: string): void;
6
+ /** Hovering another toolbar trigger closes any dropdown left open by a click. */
7
+ closeOthers(id: string): void;
8
+ close(id: string): void;
9
+ closeAll(): void;
10
+ }
11
+ export declare function provideOptionsMenus(): OptionsMenusState;
12
+ export declare function useOptionsMenus(): OptionsMenusState;
@@ -0,0 +1,42 @@
1
+ import { getContext, setContext } from "svelte";
2
+ import { SvelteSet } from "svelte/reactivity";
3
+ import { isWideLayout } from "./layout";
4
+ const OPTIONS_MENUS_KEY = Symbol("monitor-map-options-menus");
5
+ /** Which menus are opened by click: at most one on the toolbar, any number in the panel. */
6
+ export class OptionsMenusState {
7
+ open = new SvelteSet();
8
+ isOpen(id) {
9
+ return this.open.has(id);
10
+ }
11
+ toggle(id) {
12
+ if (this.open.has(id)) {
13
+ this.open.delete(id);
14
+ return;
15
+ }
16
+ if (isWideLayout())
17
+ this.open.clear();
18
+ this.open.add(id);
19
+ }
20
+ /** Hovering another toolbar trigger closes any dropdown left open by a click. */
21
+ closeOthers(id) {
22
+ for (const other of [...this.open]) {
23
+ if (other !== id)
24
+ this.open.delete(other);
25
+ }
26
+ }
27
+ close(id) {
28
+ this.open.delete(id);
29
+ }
30
+ closeAll() {
31
+ this.open.clear();
32
+ }
33
+ }
34
+ export function provideOptionsMenus() {
35
+ return setContext(OPTIONS_MENUS_KEY, new OptionsMenusState());
36
+ }
37
+ export function useOptionsMenus() {
38
+ const menus = getContext(OPTIONS_MENUS_KEY);
39
+ if (!menus)
40
+ throw new Error("OptionsMenu must be rendered inside OptionsBar");
41
+ return menus;
42
+ }
@@ -0,0 +1,17 @@
1
+ import type { MonitorsPollutant } from "../monitors/types";
2
+ /**
3
+ * What the Pollutant menu shows. "none" is not a monitorsManager.pollutant value — it means
4
+ * the monitors integration is disabled while the manager keeps its last real pollutant.
5
+ */
6
+ export type PollutantSelection = MonitorsPollutant | "none";
7
+ export interface PollutantTargets {
8
+ manager: {
9
+ pollutant: MonitorsPollutant | null;
10
+ };
11
+ integration: {
12
+ enabled: boolean;
13
+ };
14
+ }
15
+ export declare function parsePollutantParam(value: unknown): PollutantSelection | null;
16
+ export declare function currentSelection(pollutant: MonitorsPollutant | null, monitorsEnabled: boolean): PollutantSelection | null;
17
+ export declare function applyPollutantSelection(selection: PollutantSelection, targets: PollutantTargets): void;
@@ -0,0 +1,14 @@
1
+ export function parsePollutantParam(value) {
2
+ return value === "pm25" || value === "o3" || value === "none" ? value : null;
3
+ }
4
+ export function currentSelection(pollutant, monitorsEnabled) {
5
+ return monitorsEnabled ? pollutant : "none";
6
+ }
7
+ export function applyPollutantSelection(selection, targets) {
8
+ if (selection === "none") {
9
+ targets.integration.enabled = false;
10
+ return;
11
+ }
12
+ targets.manager.pollutant = selection;
13
+ targets.integration.enabled = true;
14
+ }
@@ -0,0 +1,2 @@
1
+ /** "Reset Map Options": restores every map option to its default. */
2
+ export declare function resetMapOptions(): void;
@@ -0,0 +1,24 @@
1
+ import { monitorsManager } from "../monitors/monitors.svelte";
2
+ import { monitorsMapIntegration } from "../monitors/monitors-map-integration.svelte";
3
+ import { collocationSitesMapIntegration } from "../collocation-sites/collocations-map-integration.svelte";
4
+ import { hmsFireMapIntegration } from "../hms/hms-fire-map-integration.svelte";
5
+ import { hmsSmokeMapIntegration } from "../hms/hms-smoke-map-integration.svelte";
6
+ import { evStationsMapIntegration } from "../ev-stations/ev-stations-map-integration.svelte";
7
+ import { windMapIntegration } from "../wind/wind.svelte";
8
+ import { mapStyleState } from "../map/map-style-state.svelte.js";
9
+ import { applyMapOptionDefaults } from "./map-option-defaults";
10
+ /** "Reset Map Options": restores every map option to its default. */
11
+ export function resetMapOptions() {
12
+ applyMapOptionDefaults({
13
+ manager: monitorsManager,
14
+ integration: monitorsMapIntegration,
15
+ collocationSites: collocationSitesMapIntegration,
16
+ hmsFire: hmsFireMapIntegration,
17
+ hmsSmoke: hmsSmokeMapIntegration,
18
+ evStations: evStationsMapIntegration,
19
+ wind: windMapIntegration,
20
+ // The basemap reset may start loading a new style; integrations skip the style while
21
+ // mapManager.styleLoading is set and the refresh on "style.load" re-applies them.
22
+ mapStyle: mapStyleState
23
+ }, monitorsManager.meta?.default_pollutant ?? null);
24
+ }
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { Attachment } from "svelte/attachments";
4
+ import { ChevronDownIcon } from "@lucide/svelte";
5
+
6
+ interface Props {
7
+ label: string;
8
+ checked: boolean;
9
+ icon?: Snippet;
10
+ indeterminate?: boolean;
11
+ disabled?: boolean;
12
+ /** When set, shows a chevron reflecting whether this row's children are expanded */
13
+ expanded?: boolean;
14
+ }
15
+
16
+ let {
17
+ label,
18
+ checked = $bindable(),
19
+ icon,
20
+ indeterminate = false,
21
+ disabled = false,
22
+ expanded
23
+ }: Props = $props();
24
+
25
+ // indeterminate is a DOM property with no HTML attribute; re-runs when the prop changes.
26
+ const syncIndeterminate: Attachment<HTMLInputElement> = (input) => {
27
+ input.indeterminate = indeterminate;
28
+ };
29
+ </script>
30
+
31
+ <label
32
+ class="flex cursor-pointer items-center gap-2 px-3 py-1.5 text-sm whitespace-nowrap select-none hover:bg-gray-100 has-disabled:cursor-not-allowed has-disabled:opacity-50"
33
+ >
34
+ <input
35
+ type="checkbox"
36
+ class="accent-brand size-4 shrink-0"
37
+ bind:checked
38
+ {disabled}
39
+ {@attach syncIndeterminate}
40
+ />
41
+ {#if icon}
42
+ <span class="flex size-5 shrink-0 items-center justify-center">{@render icon()}</span>
43
+ {/if}
44
+ <span class="flex-1">{label}</span>
45
+ {#if expanded !== undefined}
46
+ <ChevronDownIcon
47
+ size={16}
48
+ class="ml-3 shrink-0 text-gray-500 transition-transform {expanded ? '' : '-rotate-90'}"
49
+ />
50
+ {/if}
51
+ </label>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ label: string;
4
+ checked: boolean;
5
+ icon?: Snippet;
6
+ indeterminate?: boolean;
7
+ disabled?: boolean;
8
+ /** When set, shows a chevron reflecting whether this row's children are expanded */
9
+ expanded?: boolean;
10
+ }
11
+ declare const CheckboxRow: import("svelte").Component<Props, {}, "checked">;
12
+ type CheckboxRow = ReturnType<typeof CheckboxRow>;
13
+ export default CheckboxRow;
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import CheckboxRow from "./CheckboxRow.svelte";
4
+ import SubmenuRow from "./SubmenuRow.svelte";
5
+ import { groupState, setGroup } from "../group-state";
6
+
7
+ interface GroupOption {
8
+ key: string;
9
+ label: string;
10
+ option: { value: boolean };
11
+ }
12
+
13
+ interface Props {
14
+ label: string;
15
+ /** Only the rows currently visible; hidden rows are never touched by select-all */
16
+ options: Array<GroupOption>;
17
+ icon?: Snippet;
18
+ }
19
+
20
+ let { label, options, icon }: Props = $props();
21
+
22
+ const status = $derived(groupState(options.map((child) => child.option.value)));
23
+ </script>
24
+
25
+ {#if status !== "empty"}
26
+ <SubmenuRow
27
+ {label}
28
+ {icon}
29
+ indeterminate={status === "indeterminate"}
30
+ bind:checked={
31
+ () => status === "checked",
32
+ (value) =>
33
+ setGroup(
34
+ options.map((child) => child.option),
35
+ value
36
+ )
37
+ }
38
+ >
39
+ {#each options as child (child.key)}
40
+ <CheckboxRow label={child.label} bind:checked={child.option.value} />
41
+ {/each}
42
+ </SubmenuRow>
43
+ {/if}
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from "svelte";
2
+ interface GroupOption {
3
+ key: string;
4
+ label: string;
5
+ option: {
6
+ value: boolean;
7
+ };
8
+ }
9
+ interface Props {
10
+ label: string;
11
+ /** Only the rows currently visible; hidden rows are never touched by select-all */
12
+ options: Array<GroupOption>;
13
+ icon?: Snippet;
14
+ }
15
+ declare const GroupRow: import("svelte").Component<Props, {}, "">;
16
+ type GroupRow = ReturnType<typeof GroupRow>;
17
+ export default GroupRow;
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ import type { MapImageIcon } from "../../map/integrations/types";
3
+
4
+ let { icon, class: className = "w-4" }: { icon: MapImageIcon | undefined; class?: string } =
5
+ $props();
6
+ </script>
7
+
8
+ {#if icon}
9
+ <img src={icon.image.src} alt="" class={className} />
10
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { MapImageIcon } from "../../map/integrations/types";
2
+ type $$ComponentProps = {
3
+ icon: MapImageIcon | undefined;
4
+ class?: string;
5
+ };
6
+ declare const MarkerIcon: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type MarkerIcon = ReturnType<typeof MarkerIcon>;
8
+ export default MarkerIcon;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ name: string;
6
+ label: string;
7
+ checked: boolean;
8
+ onselect: () => void;
9
+ unit?: string;
10
+ help?: string;
11
+ icon?: Snippet;
12
+ }
13
+
14
+ let { name, label, checked, onselect, unit, help, icon }: Props = $props();
15
+ </script>
16
+
17
+ <label
18
+ class="flex cursor-pointer flex-col gap-0.5 px-3 py-1.5 text-sm select-none hover:bg-gray-100"
19
+ >
20
+ <span class="flex items-center gap-2 whitespace-nowrap">
21
+ <input type="radio" class="accent-brand size-4 shrink-0" {name} {checked} onchange={onselect} />
22
+ {#if icon}
23
+ <span class="flex size-5 shrink-0 items-center justify-center">{@render icon()}</span>
24
+ {/if}
25
+ <span>
26
+ {label}
27
+ {#if unit}<span class="text-xs text-gray-500">({unit})</span>{/if}
28
+ </span>
29
+ </span>
30
+ {#if help}
31
+ <span class="pl-6 text-xs whitespace-normal text-gray-500">{help}</span>
32
+ {/if}
33
+ </label>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ name: string;
4
+ label: string;
5
+ checked: boolean;
6
+ onselect: () => void;
7
+ unit?: string;
8
+ help?: string;
9
+ icon?: Snippet;
10
+ }
11
+ declare const RadioRow: import("svelte").Component<Props, {}, "">;
12
+ type RadioRow = ReturnType<typeof RadioRow>;
13
+ export default RadioRow;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import CheckboxRow from "./CheckboxRow.svelte";
4
+
5
+ interface Props {
6
+ label: string;
7
+ checked: boolean;
8
+ indeterminate?: boolean;
9
+ icon?: Snippet;
10
+ children: Snippet;
11
+ }
12
+
13
+ let { label, checked = $bindable(), indeterminate = false, icon, children }: Props = $props();
14
+
15
+ // Children show only while the parent is (partly) on, as in the demo.
16
+ const expanded = $derived(checked || indeterminate);
17
+ </script>
18
+
19
+ <CheckboxRow {label} {icon} {indeterminate} {expanded} bind:checked />
20
+ {#if expanded}
21
+ <div class="ml-5 border-l border-gray-200">{@render children()}</div>
22
+ {/if}
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ label: string;
4
+ checked: boolean;
5
+ indeterminate?: boolean;
6
+ icon?: Snippet;
7
+ children: Snippet;
8
+ }
9
+ declare const SubmenuRow: import("svelte").Component<Props, {}, "checked">;
10
+ type SubmenuRow = ReturnType<typeof SubmenuRow>;
11
+ export default SubmenuRow;
@@ -111,19 +111,19 @@
111
111
 
112
112
  <div
113
113
  class={[
114
- "flex flex-col overflow-hidden bg-white shadow-md transition-all duration-300 select-none",
115
- collapsed ? "h-12 w-12 rounded-full" : "w-90 rounded-3xl",
116
- !collapsed && results.length ? "max-h-90" : "max-h-12"
114
+ "flex flex-col overflow-hidden bg-white shadow transition-all duration-300 select-none",
115
+ collapsed ? "h-10 w-10 rounded-md" : "w-[min(22.5rem,calc(100vw-5.5rem))] rounded-lg",
116
+ !collapsed && results.length ? "max-h-90" : "max-h-10"
117
117
  ]}
118
118
  use:clickOutside
119
119
  >
120
- <div class="flex h-12 w-full shrink-0 items-center">
120
+ <div class="flex h-10 w-full shrink-0 items-center">
121
121
  <button
122
- class="bg-brand flex h-12 w-12 shrink-0 items-center justify-center rounded-full border-4 border-white"
122
+ class="flex h-10 w-10 shrink-0 items-center justify-center rounded-md text-gray-700"
123
123
  onclick={openSearch}
124
124
  aria-label="Open search"
125
125
  >
126
- <SearchIcon size="24" color="#FFFFFF" />
126
+ <SearchIcon size="20" />
127
127
  </button>
128
128
  <div class="flex h-full w-full items-center overflow-hidden">
129
129
  <input
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sjvair/monitor-map",
3
3
  "description": "SJVAir's interactive monitor map",
4
- "version": "3.6.2",
4
+ "version": "4.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SJVAir/monitor-map"
@@ -15,7 +15,8 @@
15
15
  "check": "svelte-check --tsconfig ./tsconfig.json",
16
16
  "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
17
17
  "format": "prettier --write .",
18
- "lint": "prettier --check . && eslint ."
18
+ "lint": "prettier --check . && eslint .",
19
+ "test": "vitest run"
19
20
  },
20
21
  "type": "module",
21
22
  "svelte": "./dist/lib/index.js",
@@ -65,13 +66,14 @@
65
66
  "tw-animate-css": "^1.4.0",
66
67
  "typescript": "^6.0.3",
67
68
  "typescript-eslint": "^8.70.0",
68
- "vite": "^8.3.0"
69
+ "vite": "^8.3.0",
70
+ "vitest": "^5.0.3"
69
71
  },
70
72
  "dependencies": {
71
73
  "@lucide/svelte": "^1.46.0",
72
74
  "@maptiler/sdk": "^4.1.0",
73
75
  "@maptiler/weather": "^4.0.1",
74
- "@sjvair/sdk": "npm:@jsr/sjvair__sdk@^4.4.0",
76
+ "@sjvair/sdk": "npm:@jsr/sjvair__sdk@^4.5.0",
75
77
  "@tstk/builtin-extensions": "npm:@jsr/tstk__builtin-extensions@^1.0.8",
76
78
  "@tstk/utils": "npm:@jsr/tstk__utils@^1.1.2",
77
79
  "@turf/area": "^7.4.0",