@sjvair/monitor-map 3.0.1 → 3.0.3

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 (151) hide show
  1. package/dist/lib/LoadScreen.js +15 -15
  2. package/dist/lib/{src/MapView.js → MainMapView.js} +2 -2
  3. package/dist/lib/MapLegend.js +13 -0
  4. package/dist/lib/MonitorDetailPanel.js +169 -0
  5. package/dist/lib/MonitorMapLayout.js +91 -0
  6. package/dist/lib/_virtual/_rolldown/runtime.js +11 -0
  7. package/dist/lib/assets/logos/airgradient-blue.js +4 -0
  8. package/dist/lib/assets/logos/airnow-compact.js +4 -0
  9. package/dist/lib/assets/logos/carb-compact.js +4 -0
  10. package/dist/lib/assets/logos/purpleair-compact.js +4 -0
  11. package/dist/lib/assets/logos/sjvair.js +4 -0
  12. package/dist/lib/collocation-sites/CollocationTooltip.js +1 -1
  13. package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +20 -20
  14. package/dist/lib/components/DataBox.js +1 -1
  15. package/dist/lib/components/DisplayOption.js +1 -1
  16. package/dist/lib/components/MapLayersDisplayOptions.js +47 -31
  17. package/dist/lib/components/SegmentedControl.js +4 -4
  18. package/dist/lib/components/ToggleSwitch.js +1 -1
  19. package/dist/lib/components/ui/button/button.js +4 -4
  20. package/dist/lib/components/ui/range-calendar/range-calendar-caption.js +57 -54
  21. package/dist/lib/components/ui/range-calendar/range-calendar-cell.js +19 -18
  22. package/dist/lib/components/ui/range-calendar/range-calendar-day.js +19 -18
  23. package/dist/lib/components/ui/range-calendar/range-calendar-grid-body.js +17 -16
  24. package/dist/lib/components/ui/range-calendar/range-calendar-grid-head.js +17 -16
  25. package/dist/lib/components/ui/range-calendar/range-calendar-grid-row.js +19 -18
  26. package/dist/lib/components/ui/range-calendar/range-calendar-grid.js +19 -18
  27. package/dist/lib/components/ui/range-calendar/range-calendar-head-cell.js +19 -18
  28. package/dist/lib/components/ui/range-calendar/range-calendar-header.js +19 -18
  29. package/dist/lib/components/ui/range-calendar/range-calendar-month-select.js +31 -31
  30. package/dist/lib/components/ui/range-calendar/range-calendar-month.js +19 -19
  31. package/dist/lib/components/ui/range-calendar/range-calendar-months.js +19 -19
  32. package/dist/lib/components/ui/range-calendar/range-calendar-nav.js +19 -19
  33. package/dist/lib/components/ui/range-calendar/range-calendar-next-button.js +27 -26
  34. package/dist/lib/components/ui/range-calendar/range-calendar-prev-button.js +27 -26
  35. package/dist/lib/components/ui/range-calendar/range-calendar-year-select.js +29 -29
  36. package/dist/lib/components/ui/range-calendar/range-calendar.js +154 -154
  37. package/dist/lib/data-chart/DataChart.js +76 -76
  38. package/dist/lib/ev-stations/components/EvStationClusterTooltip.js +16 -0
  39. package/dist/lib/ev-stations/components/EvStationTooltip.js +120 -0
  40. package/dist/lib/ev-stations/components/EvStationsDisplayOptions.js +41 -0
  41. package/dist/lib/ev-stations/data-maps.js +4 -0
  42. package/dist/lib/ev-stations/ev-station-icons.js +32 -0
  43. package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +118 -0
  44. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +181 -0
  45. package/dist/lib/ev-stations/ev-stations.svelte.js +33 -0
  46. package/dist/lib/ev-stations/zip-codes.js +136 -0
  47. package/dist/lib/hms/components/HMSFireLegend.js +35 -0
  48. package/dist/lib/hms/hms.svelte.js +12 -12
  49. package/dist/lib/index.js +19 -17
  50. package/dist/lib/map/Map.js +1 -1
  51. package/dist/lib/map/MapStyleDisplayOptions.js +16 -16
  52. package/dist/lib/map/Menu.js +23 -24
  53. package/dist/lib/map/integrations/map-display-option.svelte.js +3 -2
  54. package/dist/lib/map/integrations/map-icon-manager.js +4 -1
  55. package/dist/lib/map/utils.js +16 -13
  56. package/dist/lib/monitors/components/MonitorClusterTooltip.js +4 -4
  57. package/dist/lib/monitors/components/MonitorMarkersLegend.js +35 -0
  58. package/dist/lib/monitors/components/MonitorSubscriptionManager.js +1 -1
  59. package/dist/lib/monitors/components/MonitorTooltip.js +1 -1
  60. package/dist/lib/monitors/components/MonitorWidgetModal.js +1 -1
  61. package/dist/lib/monitors/components/MonitorsDisplayOptions.js +48 -39
  62. package/dist/lib/monitors/monitors-icon-manager.svelte.js +24 -17
  63. package/dist/lib/monitors/monitors-map-integration.svelte.js +65 -59
  64. package/dist/lib/monitors/monitors.svelte.js +38 -42
  65. package/dist/lib/router-context.js +13 -0
  66. package/dist/lib/routes.js +10 -0
  67. package/dist/lib/search/Search.js +124 -0
  68. package/dist/lib/search/service.js +90 -0
  69. package/dist/lib/sjvair-air.webp +0 -0
  70. package/dist/lib/sjvair-air2.webp +0 -0
  71. package/dist/lib/sjvair-icon.avif +0 -0
  72. package/dist/lib/sjvair-icon.png +0 -0
  73. package/dist/lib/sjvair-icon2.avif +0 -0
  74. package/dist/lib/sjvair-icon2.png +0 -0
  75. package/dist/lib/sjvair-sjv.avif +0 -0
  76. package/dist/lib/sjvair-sjv.webp +0 -0
  77. package/dist/lib/sjvair-sjv2.avif +0 -0
  78. package/dist/lib/sjvair-sjv2.webp +0 -0
  79. package/dist/lib/utils.js +9 -3
  80. package/package.json +37 -36
  81. package/src/lib/LoadScreen.svelte +5 -5
  82. package/src/lib/MainMapView.svelte +1 -0
  83. package/src/lib/MapLegend.svelte +15 -0
  84. package/src/lib/MonitorDetailPanel.svelte +152 -0
  85. package/src/lib/MonitorMapLayout.svelte +193 -0
  86. package/src/lib/components/MapLayersDisplayOptions.svelte +27 -10
  87. package/src/lib/ev-stations/components/EvStationsDisplayOptions.svelte +14 -2
  88. package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +4 -4
  89. package/src/lib/hms/components/HMSFireLegend.svelte +21 -0
  90. package/src/lib/index.ts +34 -0
  91. package/src/lib/map/Map.svelte +1 -0
  92. package/src/lib/map/Menu.svelte +4 -7
  93. package/src/lib/map/integrations/map-display-option.svelte.ts +5 -1
  94. package/src/lib/map/integrations/map-icon-manager.ts +7 -3
  95. package/src/lib/monitors/components/MonitorsDisplayOptions.svelte +4 -1
  96. package/src/lib/monitors/monitors-icon-manager.svelte.ts +3 -1
  97. package/src/lib/monitors/monitors-map-integration.svelte.ts +9 -9
  98. package/src/lib/monitors/monitors.svelte.ts +1 -3
  99. package/src/lib/router-context.ts +48 -0
  100. package/src/lib/routes.ts +8 -0
  101. package/src/lib/search/Search.svelte +189 -0
  102. package/src/lib/search/service.ts +101 -0
  103. package/src/lib/utils.ts +11 -0
  104. package/dist/lib/assets/icons/adjustment-horizontal.js +0 -4
  105. package/dist/lib/node_modules/@internationalized/date/dist/private/CalendarDate.js +0 -122
  106. package/dist/lib/node_modules/@internationalized/date/dist/private/DateFormatter.js +0 -84
  107. package/dist/lib/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js +0 -91
  108. package/dist/lib/node_modules/@internationalized/date/dist/private/conversion.js +0 -104
  109. package/dist/lib/node_modules/@internationalized/date/dist/private/manipulation.js +0 -159
  110. package/dist/lib/node_modules/@internationalized/date/dist/private/queries.js +0 -32
  111. package/dist/lib/node_modules/@internationalized/date/dist/private/string.js +0 -37
  112. package/dist/lib/node_modules/@internationalized/date/dist/private/utils.js +0 -6
  113. package/dist/lib/node_modules/clsx/dist/clsx.js +0 -16
  114. package/dist/lib/node_modules/tailwind-merge/dist/bundle-mjs.js +0 -1723
  115. package/dist/lib/node_modules/tailwind-variants/dist/chunk-LQJYWU4O.js +0 -70
  116. package/dist/lib/node_modules/tailwind-variants/dist/chunk-RZF76H2U.js +0 -166
  117. package/dist/lib/node_modules/tailwind-variants/dist/index.js +0 -19
  118. package/dist/lib/node_modules/uplot/dist/uPlot.esm.js +0 -2147
  119. package/dist/lib/src/MonitorDetailPanel.js +0 -169
  120. package/dist/lib/src/router.js +0 -9
  121. package/dist/monitor-map/MonitorDetailPanel.css +0 -1
  122. package/dist/monitor-map/assets/MonitorDetailPanel-DmN3xPui.js +0 -15
  123. package/dist/monitor-map/assets/validate-routes-CgbRa8fT.js +0 -1
  124. package/dist/monitor-map/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  125. package/dist/monitor-map/inter-cyrillic-wght-normal.woff2 +0 -0
  126. package/dist/monitor-map/inter-greek-ext-wght-normal.woff2 +0 -0
  127. package/dist/monitor-map/inter-greek-wght-normal.woff2 +0 -0
  128. package/dist/monitor-map/inter-latin-ext-wght-normal.woff2 +0 -0
  129. package/dist/monitor-map/inter-latin-wght-normal.woff2 +0 -0
  130. package/dist/monitor-map/inter-vietnamese-wght-normal.woff2 +0 -0
  131. package/dist/monitor-map/lungs.svg +0 -99
  132. package/dist/monitor-map/sjvair-air.avif +0 -0
  133. package/dist/monitor-map/sjvair-air.png +0 -0
  134. package/dist/monitor-map/sjvair-air.webp +0 -0
  135. package/dist/monitor-map/sjvair-air2.avif +0 -0
  136. package/dist/monitor-map/sjvair-air2.png +0 -0
  137. package/dist/monitor-map/sjvair-air2.webp +0 -0
  138. package/dist/monitor-map/sjvair-icon.avif +0 -0
  139. package/dist/monitor-map/sjvair-icon.png +0 -0
  140. package/dist/monitor-map/sjvair-icon.webp +0 -0
  141. package/dist/monitor-map/sjvair-icon2.avif +0 -0
  142. package/dist/monitor-map/sjvair-icon2.png +0 -0
  143. package/dist/monitor-map/sjvair-icon2.webp +0 -0
  144. package/dist/monitor-map/sjvair-sjv.avif +0 -0
  145. package/dist/monitor-map/sjvair-sjv.png +0 -0
  146. package/dist/monitor-map/sjvair-sjv.webp +0 -0
  147. package/dist/monitor-map/sjvair-sjv2.avif +0 -0
  148. package/dist/monitor-map/sjvair-sjv2.png +0 -0
  149. package/dist/monitor-map/sjvair-sjv2.webp +0 -0
  150. package/dist/monitor-map/sjvairMonitorMap.css +0 -1
  151. package/dist/monitor-map/sjvairMonitorMap.js +0 -4042
@@ -0,0 +1,48 @@
1
+ import { getContext, setContext } from "svelte";
2
+ import type {
3
+ ConstructUrlOptions,
4
+ Navigation,
5
+ NavigateOptions,
6
+ RouterApi,
7
+ Routes
8
+ } from "sv-router";
9
+
10
+ export const ROUTER_KEY = Symbol("monitor-map-router");
11
+
12
+ export interface MonitorMapRouterApi {
13
+ route: RouterApi<Routes>["route"];
14
+ /**
15
+ * Loosely typed against `string` rather than sv-router's literal-checked `Path<T>` union:
16
+ * internal calls prefix paths with `basePath` (a runtime string), which can't satisfy a
17
+ * template literal type checked against the host app's full route tree.
18
+ */
19
+ navigate(
20
+ path: string,
21
+ options?: NavigateOptions & { params?: Record<string, string> }
22
+ ): Promise<Navigation>;
23
+ p(path: string, options?: ConstructUrlOptions & { params?: Record<string, string> }): string;
24
+ isActive: {
25
+ (path: string, params?: Record<string, string>): boolean;
26
+ startsWith(path: string, params?: Record<string, string>): boolean;
27
+ };
28
+ /**
29
+ * Prefix under which monitorMapRoutes is mounted in the host app's router,
30
+ * e.g. "" at the app root, or "/my-map" when nested under a host route.
31
+ */
32
+ basePath: string;
33
+ }
34
+
35
+ export function provideMonitorMapRouter(api: MonitorMapRouterApi) {
36
+ setContext(ROUTER_KEY, api);
37
+ }
38
+
39
+ export function useMonitorMapRouter(): MonitorMapRouterApi {
40
+ const api = getContext<MonitorMapRouterApi>(ROUTER_KEY);
41
+ if (!api) {
42
+ throw new Error(
43
+ "monitor-map router context not found. Wrap your app root with " +
44
+ "provideMonitorMapRouter(...) before rendering monitor-map components."
45
+ );
46
+ }
47
+ return api;
48
+ }
@@ -0,0 +1,8 @@
1
+ import MonitorMapLayout from "./MonitorMapLayout.svelte";
2
+ import MainMapView from "./MainMapView.svelte";
3
+
4
+ export const monitorMapRoutes = {
5
+ layout: MonitorMapLayout,
6
+ "/": MainMapView,
7
+ "/monitor/:id": () => import("./MonitorDetailPanel.svelte")
8
+ };
@@ -0,0 +1,189 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from "svelte";
3
+ import { Marker } from "@maptiler/sdk";
4
+ import { SearchIcon } from "@lucide/svelte";
5
+ import { useMonitorMapRouter } from "../router-context";
6
+ import { mapManager } from "$lib/map/map.svelte";
7
+ import { debounce } from "$lib/utils";
8
+ import { monitorSearch, geocode, type SearchResult } from "./service";
9
+ import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
10
+
11
+ const { navigate, basePath } = useMonitorMapRouter();
12
+
13
+ let collapsed = $state(true);
14
+ let searchText = $state("");
15
+ let results: SearchResult[] = $state([]);
16
+ let marker: Marker | null = $state.raw(null);
17
+ let inputEl: HTMLInputElement | null = $state(null);
18
+
19
+ function clickOutside(node: HTMLElement) {
20
+ function handler(e: MouseEvent) {
21
+ if (!collapsed && !node.contains(e.target as Node)) {
22
+ collapsed = true;
23
+ searchText = "";
24
+ results = [];
25
+ }
26
+ }
27
+ document.addEventListener("click", handler, true);
28
+ return {
29
+ destroy() {
30
+ document.removeEventListener("click", handler, true);
31
+ }
32
+ };
33
+ }
34
+
35
+ async function openSearch() {
36
+ if (collapsed) {
37
+ collapsed = false;
38
+ inputEl?.focus();
39
+ }
40
+ }
41
+
42
+ function clearSearch() {
43
+ searchText = "";
44
+ results = [];
45
+ marker?.remove();
46
+ marker = null;
47
+ }
48
+
49
+ async function handleInput() {
50
+ const query = searchText;
51
+ if (!query) {
52
+ results = [];
53
+ marker?.remove();
54
+ marker = null;
55
+ return;
56
+ }
57
+ const monitorResults = await monitorSearch(query);
58
+ if (searchText !== query) return;
59
+ if (monitorResults.length) {
60
+ results = monitorResults;
61
+ } else if (query.length > 4) {
62
+ const geo = await geocode(query);
63
+ if (searchText !== query) return;
64
+ results = geo;
65
+ } else {
66
+ results = [];
67
+ }
68
+ }
69
+
70
+ const debouncedSearch = debounce(handleInput, 500);
71
+
72
+ async function selectMonitor(result: Extract<SearchResult, { type: "monitor" }>) {
73
+ collapsed = true;
74
+ searchText = "";
75
+ results = [];
76
+ marker?.remove();
77
+ marker = null;
78
+ if (mapManager.map && result.monitor.position) {
79
+ const { coordinates } = result.monitor.position;
80
+ const center: [number, number] = [coordinates[0], coordinates[1]];
81
+ mapManager.map.flyTo({
82
+ center,
83
+ zoom: 12
84
+ });
85
+ }
86
+ await navigate(`${basePath}/monitor/:id`, { params: { id: result.monitor.id } });
87
+ monitorsMapIntegration.selectedMonitorId = result.monitor.id;
88
+ }
89
+
90
+ function selectGeocode(result: Extract<SearchResult, { type: "geocode" }>) {
91
+ 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
+ }
103
+ results = [];
104
+ collapsed = true;
105
+ }
106
+
107
+ onDestroy(() => {
108
+ marker?.remove();
109
+ });
110
+ </script>
111
+
112
+ <div
113
+ class={[
114
+ "flex flex-col bg-white shadow-md overflow-hidden transition-all duration-300 select-none",
115
+ collapsed ? "w-12 h-12 rounded-full" : "w-90 rounded-3xl",
116
+ !collapsed && results.length ? "max-h-90" : "max-h-12"
117
+ ]}
118
+ use:clickOutside
119
+ >
120
+ <div class="flex items-center w-full h-12 shrink-0">
121
+ <button
122
+ class="w-12 h-12 shrink-0 flex items-center justify-center rounded-full bg-brand border-4 border-white"
123
+ onclick={openSearch}
124
+ aria-label="Open search"
125
+ >
126
+ <SearchIcon size="24" color="#FFFFFF" />
127
+ </button>
128
+ <div class="overflow-hidden w-full h-full flex items-center">
129
+ <input
130
+ bind:this={inputEl}
131
+ bind:value={searchText}
132
+ oninput={debouncedSearch}
133
+ type="text"
134
+ placeholder="Search monitors or locations..."
135
+ class="h-full px-2 text-sm bg-transparent border-none outline-none w-full"
136
+ tabindex={collapsed ? -1 : 0}
137
+ />
138
+ {#if searchText && !collapsed}
139
+ <button
140
+ onclick={clearSearch}
141
+ class="px-2 text-gray-400 hover:text-gray-600 text-xl leading-none cursor-pointer"
142
+ aria-label="Clear search">×</button
143
+ >
144
+ {/if}
145
+ </div>
146
+ </div>
147
+ <div
148
+ class={[
149
+ "overflow-y-auto transition-all duration-300 divide-y divide-gray-100",
150
+ !collapsed ? "max-h-80" : "max-h-0"
151
+ ]}
152
+ >
153
+ {#each results as result (result.type === "monitor" ? result.monitor.id : result.feature.place_name)}
154
+ {#if result.type === "monitor"}
155
+ <button
156
+ onclick={() => selectMonitor(result)}
157
+ class="flex items-center w-full h-12 px-4 hover:brightness-95 bg-white text-left"
158
+ >
159
+ {#if result.logo.url}
160
+ <img
161
+ src={result.logo.url}
162
+ alt={result.logo.alt}
163
+ class="w-10 h-10 object-contain mr-3 shrink-0"
164
+ />
165
+ {/if}
166
+ <span class="text-sm truncate">{result.monitor.name}</span>
167
+ </button>
168
+ {:else}
169
+ {@const comma = result.feature.place_name.indexOf(",")}
170
+ <button
171
+ onclick={() => selectGeocode(result)}
172
+ class="flex items-center w-full h-12 px-4 hover:brightness-95 bg-white text-left"
173
+ >
174
+ <span class="svg-icon location-on bg-brand w-6 h-6 shrink-0 mr-3"></span>
175
+ <div class="min-w-0">
176
+ <p class="text-sm font-medium truncate">
177
+ {result.feature.place_name.substring(0, comma < 0 ? undefined : comma)}
178
+ </p>
179
+ {#if comma >= 0}
180
+ <p class="text-xs text-muted-foreground truncate">
181
+ {result.feature.place_name.substring(comma + 1).trim()}
182
+ </p>
183
+ {/if}
184
+ </div>
185
+ </button>
186
+ {/if}
187
+ {/each}
188
+ </div>
189
+ </div>
@@ -0,0 +1,101 @@
1
+ import { geocoding, type GeocodingFeature } from "@maptiler/sdk";
2
+ import type { MonitorLatestType } from "@sjvair/sdk";
3
+ import { monitorsManager } from "$lib/monitors/monitors.svelte";
4
+ import AirGradientLogo from "../assets/logos/airgradient-blue.svg";
5
+ import AirNowLogo from "../assets/logos/airnow-compact.jpg";
6
+ import CarbLogo from "../assets/logos/carb-compact.jpg";
7
+ import PurpleairLogo from "../assets/logos/purpleair-compact.png";
8
+ import SJVAirLogo from "../assets/logos/sjvair.svg";
9
+
10
+ export type MonitorResult = {
11
+ type: "monitor";
12
+ monitor: MonitorLatestType<"pm25" | "o3">;
13
+ logo: { url: string; alt: string };
14
+ };
15
+
16
+ export type GeocodeResult = {
17
+ type: "geocode";
18
+ feature: GeocodingFeature;
19
+ };
20
+
21
+ export type SearchResult = MonitorResult | GeocodeResult;
22
+
23
+ const SJV_CENTER: [number, number] = [-119.8, 36.76];
24
+
25
+ function getMonitorLogo(monitor: MonitorLatestType<"pm25" | "o3">): { url: string; alt: string } {
26
+ switch (monitor.data_source.name) {
27
+ case "AQview":
28
+ return { url: CarbLogo, alt: "AQview" };
29
+ case "AirNow.gov":
30
+ return { url: AirNowLogo, alt: "AirNow.gov" };
31
+ case "PurpleAir":
32
+ return { url: PurpleairLogo, alt: "PurpleAir" };
33
+ case "Central California Asthma Collaborative":
34
+ return { url: SJVAirLogo, alt: "Central California Asthma Collaborative" };
35
+ case "AirGradient":
36
+ return { url: AirGradientLogo, alt: "AirGradient" };
37
+ default:
38
+ console.warn(`[search] Unknown data source: ${monitor.data_source.name}`);
39
+ return { url: "", alt: "" };
40
+ }
41
+ }
42
+
43
+ let proximityCache: Promise<[number, number]> | null = null;
44
+
45
+ function getProximity(): Promise<[number, number]> {
46
+ return (proximityCache ??= resolveProximity());
47
+ }
48
+
49
+ function resolveProximity(): Promise<[number, number]> {
50
+ return new Promise((resolve) => {
51
+ if (!navigator.geolocation) {
52
+ resolve(SJV_CENTER);
53
+ return;
54
+ }
55
+ const timeout = setTimeout(() => resolve(SJV_CENTER), 3000);
56
+ navigator.geolocation.getCurrentPosition(
57
+ (pos) => {
58
+ clearTimeout(timeout);
59
+ resolve([pos.coords.longitude, pos.coords.latitude]);
60
+ },
61
+ () => {
62
+ clearTimeout(timeout);
63
+ resolve(SJV_CENTER);
64
+ }
65
+ );
66
+ });
67
+ }
68
+
69
+ export async function monitorSearch(query: string): Promise<SearchResult[]> {
70
+ if (!monitorsManager.latest) return [];
71
+ const normalized = query.toLowerCase();
72
+ const exact: MonitorLatestType<"pm25" | "o3">[] = [];
73
+ const prefix: MonitorLatestType<"pm25" | "o3">[] = [];
74
+ const substr: MonitorLatestType<"pm25" | "o3">[] = [];
75
+
76
+ for (const monitor of monitorsManager.latest.values()) {
77
+ const name = monitor.name.toLowerCase();
78
+ if (name === normalized) exact.push(monitor);
79
+ else if (name.startsWith(normalized)) prefix.push(monitor);
80
+ else if (name.includes(normalized)) substr.push(monitor);
81
+ }
82
+
83
+ return [...exact, ...prefix, ...substr]
84
+ .slice(0, 4)
85
+ .map((monitor) => ({ type: "monitor" as const, monitor, logo: getMonitorLogo(monitor) }));
86
+ }
87
+
88
+ export async function geocode(query: string): Promise<SearchResult[]> {
89
+ const proximity = await getProximity();
90
+ try {
91
+ const result = await geocoding.forward(query, {
92
+ country: ["us"],
93
+ fuzzyMatch: true,
94
+ limit: 4,
95
+ proximity
96
+ });
97
+ return result.features.map((feature) => ({ type: "geocode" as const, feature }));
98
+ } catch {
99
+ return [];
100
+ }
101
+ }
package/src/lib/utils.ts CHANGED
@@ -11,3 +11,14 @@ export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
11
11
  export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
12
12
  export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
13
13
  export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
14
+
15
+ export function debounce<T extends (...args: any[]) => void>(
16
+ fn: T,
17
+ wait: number
18
+ ): (...args: Parameters<T>) => void {
19
+ let timer: ReturnType<typeof setTimeout>;
20
+ return (...args: Parameters<T>) => {
21
+ clearTimeout(timer);
22
+ timer = setTimeout(() => fn(...args), wait);
23
+ };
24
+ }
@@ -1,4 +0,0 @@
1
- //#region src/lib/assets/icons/adjustment-horizontal.svg
2
- var e = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20fill='none'%20viewBox='0%200%2024%2024'%20stroke-width='1.5'%20stroke='currentColor'%20class='size-6'%3e%3cpath%20stroke-linecap='round'%20stroke-linejoin='round'%20d='M10.5%206h9.75M10.5%206a1.5%201.5%200%201%201-3%200m3%200a1.5%201.5%200%201%200-3%200M3.75%206H7.5m3%2012h9.75m-9.75%200a1.5%201.5%200%200%201-3%200m3%200a1.5%201.5%200%200%200-3%200m-3.75%200H7.5m9-6h3.75m-3.75%200a1.5%201.5%200%200%201-3%200m3%200a1.5%201.5%200%200%200-3%200m-9.75%200h9.75'%20/%3e%3c/svg%3e";
3
- //#endregion
4
- export { e as default };
@@ -1,122 +0,0 @@
1
- import { $93635573935797de$export$80ee6245ec4f29ec as e } from "./calendars/GregorianCalendar.js";
2
- import { $ad063034c8620db8$export$68781ddf31c0090f as t, $ad063034c8620db8$export$c19a80a9721b80f6 as n } from "./queries.js";
3
- import { $d07e34cce18680fd$export$83aac07b4c37b25 as r, $d07e34cce18680fd$export$84c95a83c799e074 as i, $d07e34cce18680fd$export$b21e0b124e224484 as a, $d07e34cce18680fd$export$e67a095c620b86fe as o } from "./conversion.js";
4
- import { $435a2ceaa8778ed8$export$31b5430eb18be4f8 as s, $435a2ceaa8778ed8$export$4e2d2ead65e5f7e3 as c, $435a2ceaa8778ed8$export$6814caac34ca03c7 as l, $435a2ceaa8778ed8$export$96b1d28349274637 as u, $435a2ceaa8778ed8$export$9a297d111fc86b79 as d, $435a2ceaa8778ed8$export$adaa4cf7ef1b65be as f, $435a2ceaa8778ed8$export$c4e2ecac49351ef2 as p, $435a2ceaa8778ed8$export$d52ced6badfb9a4c as m, $435a2ceaa8778ed8$export$dd02b3e0007dfe28 as h, $435a2ceaa8778ed8$export$e16d8520af44a096 as g, $435a2ceaa8778ed8$export$e5d5e1c1822b6e56 as _ } from "./manipulation.js";
5
- import { $58246871e4652552$export$4223de14708adc63 as v, $58246871e4652552$export$60dfd74aa96791bd as y, $58246871e4652552$export$bf79f1ebf4b18792 as b } from "./string.js";
6
- //#region node_modules/@internationalized/date/dist/private/CalendarDate.mjs
7
- function x(t) {
8
- let n = typeof t[0] == "object" ? t.shift() : new e(), r;
9
- if (typeof t[0] == "string") r = t.shift();
10
- else {
11
- let e = n.getEras();
12
- r = e[e.length - 1];
13
- }
14
- let i = t.shift(), a = t.shift(), o = t.shift();
15
- return [
16
- n,
17
- r,
18
- i,
19
- a,
20
- o
21
- ];
22
- }
23
- var S = class e {
24
- constructor(...e) {
25
- let [t, n, r, i, a] = x(e);
26
- this.calendar = t, this.era = n, this.year = r, this.month = i, this.day = a, p(this);
27
- }
28
- copy() {
29
- return this.era ? new e(this.calendar, this.era, this.year, this.month, this.day) : new e(this.calendar, this.year, this.month, this.day);
30
- }
31
- add(e) {
32
- return g(this, e);
33
- }
34
- subtract(e) {
35
- return c(this, e);
36
- }
37
- set(e) {
38
- return f(this, e);
39
- }
40
- cycle(e, t, n) {
41
- return m(this, e, t, n);
42
- }
43
- toDate(e) {
44
- return o(this, e);
45
- }
46
- toString() {
47
- return y(this);
48
- }
49
- compare(e) {
50
- return t(this, e);
51
- }
52
- }, C = class e {
53
- constructor(...e) {
54
- let [t, n, r, i, a] = x(e);
55
- this.calendar = t, this.era = n, this.year = r, this.month = i, this.day = a, this.hour = e.shift() || 0, this.minute = e.shift() || 0, this.second = e.shift() || 0, this.millisecond = e.shift() || 0, p(this);
56
- }
57
- copy() {
58
- return this.era ? new e(this.calendar, this.era, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond) : new e(this.calendar, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
59
- }
60
- add(e) {
61
- return g(this, e);
62
- }
63
- subtract(e) {
64
- return c(this, e);
65
- }
66
- set(e) {
67
- return f(_(this, e), e);
68
- }
69
- cycle(e, t, n) {
70
- switch (e) {
71
- case "era":
72
- case "year":
73
- case "month":
74
- case "day": return m(this, e, t, n);
75
- default: return h(this, e, t, n);
76
- }
77
- }
78
- toDate(e, t) {
79
- return o(this, e, t);
80
- }
81
- toString() {
82
- return v(this);
83
- }
84
- compare(e) {
85
- let r = t(this, e);
86
- return r === 0 ? n(this, a(e)) : r;
87
- }
88
- }, w = class e {
89
- constructor(...e) {
90
- let [t, n, r, i, a] = x(e), o = e.shift(), s = e.shift();
91
- this.calendar = t, this.era = n, this.year = r, this.month = i, this.day = a, this.timeZone = o, this.offset = s, this.hour = e.shift() || 0, this.minute = e.shift() || 0, this.second = e.shift() || 0, this.millisecond = e.shift() || 0, p(this);
92
- }
93
- copy() {
94
- return this.era ? new e(this.calendar, this.era, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond) : new e(this.calendar, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond);
95
- }
96
- add(e) {
97
- return u(this, e);
98
- }
99
- subtract(e) {
100
- return l(this, e);
101
- }
102
- set(e, t) {
103
- return s(this, e, t);
104
- }
105
- cycle(e, t, n) {
106
- return d(this, e, t, n);
107
- }
108
- toDate() {
109
- return r(this);
110
- }
111
- toString() {
112
- return b(this);
113
- }
114
- toAbsoluteString() {
115
- return this.toDate().toISOString();
116
- }
117
- compare(e) {
118
- return this.toDate().getTime() - i(e, this.timeZone).toDate().getTime();
119
- }
120
- };
121
- //#endregion
122
- export { S as $2aaf608024c21ca1$export$99faa760c7908e4f, C as $2aaf608024c21ca1$export$ca871e8dbb80966f, w as $2aaf608024c21ca1$export$d3b7288e7994edea };
@@ -1,84 +0,0 @@
1
- //#region node_modules/@internationalized/date/dist/private/DateFormatter.mjs
2
- var e = /* @__PURE__ */ new Map(), t = class {
3
- constructor(e, t = {}) {
4
- this.formatter = r(e, t), this.options = t;
5
- }
6
- format(e) {
7
- return this.formatter.format(e);
8
- }
9
- formatToParts(e) {
10
- return this.formatter.formatToParts(e);
11
- }
12
- formatRange(e, t) {
13
- if (typeof this.formatter.formatRange == "function") return this.formatter.formatRange(e, t);
14
- if (t < e) throw RangeError("End date must be >= start date");
15
- return `${this.formatter.format(e)} \u{2013} ${this.formatter.format(t)}`;
16
- }
17
- formatRangeToParts(e, t) {
18
- if (typeof this.formatter.formatRangeToParts == "function") return this.formatter.formatRangeToParts(e, t);
19
- if (t < e) throw RangeError("End date must be >= start date");
20
- let n = this.formatter.formatToParts(e), r = this.formatter.formatToParts(t);
21
- return [
22
- ...n.map((e) => ({
23
- ...e,
24
- source: "startRange"
25
- })),
26
- {
27
- type: "literal",
28
- value: " – ",
29
- source: "shared"
30
- },
31
- ...r.map((e) => ({
32
- ...e,
33
- source: "endRange"
34
- }))
35
- ];
36
- }
37
- resolvedOptions() {
38
- let e = this.formatter.resolvedOptions();
39
- return s() && (this.resolvedHourCycle ||= c(e.locale, this.options), e.hourCycle = this.resolvedHourCycle, e.hour12 = this.resolvedHourCycle === "h11" || this.resolvedHourCycle === "h12"), e.calendar === "ethiopic-amete-alem" && (e.calendar = "ethioaa"), e;
40
- }
41
- }, n = {
42
- true: { ja: "h11" },
43
- false: {}
44
- };
45
- function r(t, r = {}) {
46
- if (typeof r.hour12 == "boolean" && a()) {
47
- r = { ...r };
48
- let e = n[String(r.hour12)][t.split("-")[0]], i = r.hour12 ? "h12" : "h23";
49
- r.hourCycle = e ?? i, delete r.hour12;
50
- }
51
- let i = t + (r ? Object.entries(r).sort((e, t) => e[0] < t[0] ? -1 : 1).join() : "");
52
- if (e.has(i)) return e.get(i);
53
- let o = new Intl.DateTimeFormat(t, r);
54
- return e.set(i, o), o;
55
- }
56
- var i = null;
57
- function a() {
58
- return i ??= new Intl.DateTimeFormat("en-US", {
59
- hour: "numeric",
60
- hour12: !1
61
- }).format(new Date(2020, 2, 3, 0)) === "24", i;
62
- }
63
- var o = null;
64
- function s() {
65
- return o ??= new Intl.DateTimeFormat("fr", {
66
- hour: "numeric",
67
- hour12: !1
68
- }).resolvedOptions().hourCycle === "h12", o;
69
- }
70
- function c(e, t) {
71
- if (!t.timeStyle && !t.hour) return;
72
- e = e.replace(/(-u-)?-nu-[a-zA-Z0-9]+/, ""), e += (e.includes("-u-") ? "" : "-u") + "-nu-latn";
73
- let n = r(e, {
74
- ...t,
75
- timeZone: void 0
76
- }), i = parseInt(n.formatToParts(new Date(2020, 2, 3, 0)).find((e) => e.type === "hour").value, 10), a = parseInt(n.formatToParts(new Date(2020, 2, 3, 23)).find((e) => e.type === "hour").value, 10);
77
- if (i === 0 && a === 23) return "h23";
78
- if (i === 24 && a === 23) return "h24";
79
- if (i === 0 && a === 11) return "h11";
80
- if (i === 12 && a === 11) return "h12";
81
- throw Error("Unexpected hour cycle result");
82
- }
83
- //#endregion
84
- export { t as $12a3c853105e5a70$export$ad991b66133851cf };
@@ -1,91 +0,0 @@
1
- import { $09ec6a572d60460f$export$842a2cf37af977e1 as e } from "../utils.js";
2
- import { $2aaf608024c21ca1$export$99faa760c7908e4f as t } from "../CalendarDate.js";
3
- //#region node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.mjs
4
- var n = 1721426;
5
- function r(e, t, r, o) {
6
- t = a(e, t);
7
- let s = t - 1, c = -2;
8
- return r <= 2 ? c = 0 : i(t) && (c = -1), n - 1 + 365 * s + Math.floor(s / 4) - Math.floor(s / 100) + Math.floor(s / 400) + Math.floor((367 * r - 362) / 12 + c + o);
9
- }
10
- function i(e) {
11
- return e % 4 == 0 && (e % 100 != 0 || e % 400 == 0);
12
- }
13
- function a(e, t) {
14
- return e === "BC" ? 1 - t : t;
15
- }
16
- function o(e) {
17
- let t = "AD";
18
- return e <= 0 && (t = "BC", e = 1 - e), [t, e];
19
- }
20
- var s = {
21
- standard: [
22
- 31,
23
- 28,
24
- 31,
25
- 30,
26
- 31,
27
- 30,
28
- 31,
29
- 31,
30
- 30,
31
- 31,
32
- 30,
33
- 31
34
- ],
35
- leapyear: [
36
- 31,
37
- 29,
38
- 31,
39
- 30,
40
- 31,
41
- 30,
42
- 31,
43
- 31,
44
- 30,
45
- 31,
46
- 30,
47
- 31
48
- ]
49
- }, c = class {
50
- fromJulianDay(a) {
51
- let s = a, c = s - n, l = Math.floor(c / 146097), u = e(c, 146097), d = Math.floor(u / 36524), f = e(u, 36524), p = Math.floor(f / 1461), m = e(f, 1461), h = Math.floor(m / 365), [g, _] = o(l * 400 + d * 100 + p * 4 + h + +(d !== 4 && h !== 4)), v = s - r(g, _, 1, 1), y = 2;
52
- s < r(g, _, 3, 1) ? y = 0 : i(_) && (y = 1);
53
- let b = Math.floor(((v + y) * 12 + 373) / 367);
54
- return new t(g, _, b, s - r(g, _, b, 1) + 1);
55
- }
56
- toJulianDay(e) {
57
- return r(e.era, e.year, e.month, e.day);
58
- }
59
- getDaysInMonth(e) {
60
- return s[i(e.year) ? "leapyear" : "standard"][e.month - 1];
61
- }
62
- getMonthsInYear(e) {
63
- return 12;
64
- }
65
- getDaysInYear(e) {
66
- return i(e.year) ? 366 : 365;
67
- }
68
- getMaximumMonthsInYear() {
69
- return 12;
70
- }
71
- getMaximumDaysInMonth() {
72
- return 31;
73
- }
74
- getYearsInEra(e) {
75
- return 9999;
76
- }
77
- getEras() {
78
- return ["BC", "AD"];
79
- }
80
- isInverseEra(e) {
81
- return e.era === "BC";
82
- }
83
- balanceDate(e) {
84
- e.year <= 0 && (e.era = e.era === "BC" ? "AD" : "BC", e.year = 1 - e.year);
85
- }
86
- constructor() {
87
- this.identifier = "gregory";
88
- }
89
- };
90
- //#endregion
91
- export { c as $93635573935797de$export$80ee6245ec4f29ec, a as $93635573935797de$export$c36e0ecb2d4fa69d };