@sjvair/map-sdk 0.0.0-stage → 0.1.0-beta.2

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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +506 -2
  3. package/dist/core/basemap.svelte.d.ts +38 -0
  4. package/dist/core/basemap.svelte.js +163 -0
  5. package/dist/core/context.d.ts +5 -0
  6. package/dist/core/context.js +13 -0
  7. package/dist/core/create-map.svelte.d.ts +207 -0
  8. package/dist/core/create-map.svelte.js +376 -0
  9. package/dist/core/gl.d.ts +92 -0
  10. package/dist/core/gl.js +1 -0
  11. package/dist/core/interactions.svelte.d.ts +43 -0
  12. package/dist/core/interactions.svelte.js +230 -0
  13. package/dist/core/plugin-host.svelte.d.ts +46 -0
  14. package/dist/core/plugin-host.svelte.js +366 -0
  15. package/dist/core/plugin.d.ts +155 -0
  16. package/dist/core/plugin.js +1 -0
  17. package/dist/core/scoped-context.d.ts +7 -0
  18. package/dist/core/scoped-context.js +34 -0
  19. package/dist/data/format.d.ts +10 -0
  20. package/dist/data/format.js +24 -0
  21. package/dist/data/index.d.ts +9 -0
  22. package/dist/data/index.js +9 -0
  23. package/dist/data/messages/en.json +52 -0
  24. package/dist/data/messages.d.ts +10 -0
  25. package/dist/data/messages.js +17 -0
  26. package/dist/data/metadata.d.ts +22 -0
  27. package/dist/data/metadata.js +110 -0
  28. package/dist/data/refs.d.ts +18 -0
  29. package/dist/data/refs.js +61 -0
  30. package/dist/data/scale-types.d.ts +7 -0
  31. package/dist/data/scale-types.js +1 -0
  32. package/dist/data/scale.d.ts +13 -0
  33. package/dist/data/scale.js +27 -0
  34. package/dist/data/selection.d.ts +7 -0
  35. package/dist/data/selection.js +31 -0
  36. package/dist/data/static-source.d.ts +4 -0
  37. package/dist/data/static-source.js +3 -0
  38. package/dist/data/types.d.ts +69 -0
  39. package/dist/data/types.js +1 -0
  40. package/dist/index.d.ts +56 -0
  41. package/dist/index.js +41 -0
  42. package/dist/plugins/icons.d.ts +21 -0
  43. package/dist/plugins/icons.js +73 -0
  44. package/dist/plugins/points-clusters.d.ts +60 -0
  45. package/dist/plugins/points-clusters.js +93 -0
  46. package/dist/plugins/points.svelte.d.ts +100 -0
  47. package/dist/plugins/points.svelte.js +501 -0
  48. package/dist/plugins/scale-resolution.svelte.d.ts +27 -0
  49. package/dist/plugins/scale-resolution.svelte.js +111 -0
  50. package/dist/plugins/shapes.svelte.d.ts +29 -0
  51. package/dist/plugins/shapes.svelte.js +307 -0
  52. package/dist/plugins/shared.d.ts +6 -0
  53. package/dist/plugins/shared.js +18 -0
  54. package/dist/plugins/style.d.ts +57 -0
  55. package/dist/plugins/style.js +193 -0
  56. package/dist/presets/CollocationTooltip.svelte +132 -0
  57. package/dist/presets/CollocationTooltip.svelte.d.ts +16 -0
  58. package/dist/presets/MonitorClusterTooltip.svelte +149 -0
  59. package/dist/presets/MonitorClusterTooltip.svelte.d.ts +16 -0
  60. package/dist/presets/MonitorReadingBox.svelte +58 -0
  61. package/dist/presets/MonitorReadingBox.svelte.d.ts +12 -0
  62. package/dist/presets/MonitorTooltip.svelte +88 -0
  63. package/dist/presets/MonitorTooltip.svelte.d.ts +20 -0
  64. package/dist/presets/calheatscore.d.ts +24 -0
  65. package/dist/presets/calheatscore.js +22 -0
  66. package/dist/presets/collocation-sites.d.ts +18 -0
  67. package/dist/presets/collocation-sites.js +67 -0
  68. package/dist/presets/fire-groups.d.ts +26 -0
  69. package/dist/presets/fire-groups.js +51 -0
  70. package/dist/presets/forecast-zones.d.ts +25 -0
  71. package/dist/presets/forecast-zones.js +23 -0
  72. package/dist/presets/hms-fire.d.ts +26 -0
  73. package/dist/presets/hms-fire.js +87 -0
  74. package/dist/presets/hms-smoke.d.ts +19 -0
  75. package/dist/presets/hms-smoke.js +20 -0
  76. package/dist/presets/monitors.d.ts +48 -0
  77. package/dist/presets/monitors.js +243 -0
  78. package/dist/presets/reactive-props.svelte.d.ts +4 -0
  79. package/dist/presets/reactive-props.svelte.js +7 -0
  80. package/dist/presets/regions.d.ts +24 -0
  81. package/dist/presets/regions.js +22 -0
  82. package/dist/stores/abort.d.ts +16 -0
  83. package/dist/stores/abort.js +52 -0
  84. package/dist/stores/collocation-store.svelte.d.ts +25 -0
  85. package/dist/stores/collocation-store.svelte.js +72 -0
  86. package/dist/stores/hms-stores.svelte.d.ts +35 -0
  87. package/dist/stores/hms-stores.svelte.js +97 -0
  88. package/dist/stores/index.d.ts +4 -0
  89. package/dist/stores/index.js +4 -0
  90. package/dist/stores/load-once.svelte.d.ts +25 -0
  91. package/dist/stores/load-once.svelte.js +93 -0
  92. package/dist/stores/monitors-store.svelte.d.ts +85 -0
  93. package/dist/stores/monitors-store.svelte.js +340 -0
  94. package/dist/theme.css +34 -0
  95. package/dist/ui/FeatureList.svelte +505 -0
  96. package/dist/ui/FeatureList.svelte.d.ts +14 -0
  97. package/dist/ui/Legend.svelte +213 -0
  98. package/dist/ui/Legend.svelte.d.ts +22 -0
  99. package/dist/ui/LegendBar.svelte +161 -0
  100. package/dist/ui/LegendBar.svelte.d.ts +11 -0
  101. package/dist/ui/MapControls.svelte +135 -0
  102. package/dist/ui/MapControls.svelte.d.ts +11 -0
  103. package/dist/ui/MapTooltip.svelte +158 -0
  104. package/dist/ui/MapTooltip.svelte.d.ts +28 -0
  105. package/dist/ui/SelectionBar.svelte +123 -0
  106. package/dist/ui/SelectionBar.svelte.d.ts +10 -0
  107. package/dist/ui/TooltipItem.svelte +72 -0
  108. package/dist/ui/TooltipItem.svelte.d.ts +11 -0
  109. package/dist/ui/ViewSummary.svelte +97 -0
  110. package/dist/ui/ViewSummary.svelte.d.ts +7 -0
  111. package/dist/ui/controls.css +45 -0
  112. package/dist/ui/data-credits.svelte.d.ts +9 -0
  113. package/dist/ui/data-credits.svelte.js +52 -0
  114. package/dist/ui/feature-list.d.ts +38 -0
  115. package/dist/ui/feature-list.js +95 -0
  116. package/dist/ui/legend-data.d.ts +41 -0
  117. package/dist/ui/legend-data.js +40 -0
  118. package/dist/ui/level-chip.d.ts +11 -0
  119. package/dist/ui/level-chip.js +15 -0
  120. package/dist/ui/value-format.d.ts +7 -0
  121. package/dist/ui/value-format.js +13 -0
  122. package/dist/ui/view-summary.d.ts +11 -0
  123. package/dist/ui/view-summary.js +33 -0
  124. package/dist/view/MapView.svelte +373 -0
  125. package/dist/view/MapView.svelte.d.ts +43 -0
  126. package/dist/view/create-gl-map.d.ts +34 -0
  127. package/dist/view/create-gl-map.js +182 -0
  128. package/dist/view/list-placement.d.ts +21 -0
  129. package/dist/view/list-placement.js +13 -0
  130. package/package.json +103 -5
@@ -0,0 +1,213 @@
1
+ <script lang="ts">
2
+ import { onMount, untrack } from "svelte";
3
+ import "./controls.css";
4
+ import type { MapContext } from "../core/create-map.svelte.js";
5
+ import { watchTheme } from "../view/create-gl-map.js";
6
+ import LegendBar from "./LegendBar.svelte";
7
+ import { legendSection, type LegendEntry } from "./legend-data.js";
8
+
9
+ let {
10
+ map,
11
+ order = "bottom-first",
12
+ framed = true
13
+ }: {
14
+ map: MapContext;
15
+ /**
16
+ * `true` (default) draws the legend's own card. `false` draws no frame (no border,
17
+ * background, padding, or fixed width) so a host's card frames it. Translucent swatches
18
+ * then blend over the nearest ancestor with an opaque background (a see-through card is
19
+ * skipped), else the `--background` token. The color is read on mount, when `framed`
20
+ * changes, and on a theme change; a card that changes color through its own class or
21
+ * style isn't re-read.
22
+ */
23
+ framed?: boolean;
24
+ /**
25
+ * `"bottom-first"` (default) lists the bottom plugin's section first; `"top-first"`
26
+ * last.
27
+ */
28
+ order?: "bottom-first" | "top-first";
29
+ } = $props();
30
+
31
+ let root: HTMLElement | undefined = $state();
32
+ // The `--background` token as rgb, for mixing translucent swatches over it. Read as soon as the
33
+ // element exists (the bars wait for it), so no color is assumed before.
34
+ let background = $state<string | null>(null);
35
+
36
+ /**
37
+ * A section per plugin with a scale, and a status line per plugin still loading or failing, in
38
+ * `order`.
39
+ */
40
+ const entries = $derived(
41
+ (order === "top-first" ? [...map.plugins].reverse() : map.plugins).flatMap(
42
+ (plugin): LegendEntry[] => {
43
+ const data = plugin.legend?.();
44
+ if (!data) return [];
45
+ if ("status" in data) {
46
+ if (data.status.state === "ready") return [];
47
+ const layer = plugin.label ?? plugin.id;
48
+ return [{ kind: "status", pluginId: data.pluginId, layer, status: data.status }];
49
+ }
50
+ const section = legendSection(data, map.format);
51
+ return section ? [{ kind: "scale", section }] : [];
52
+ }
53
+ )
54
+ );
55
+
56
+ /**
57
+ * A computed color as rgb and whether it's opaque; computed colors can be oklch() or color(),
58
+ * and a 1x1 canvas turns any of them into rgb.
59
+ */
60
+ function toRgb(css: string): { rgb: string; opaque: boolean } {
61
+ const ctx = document.createElement("canvas").getContext("2d");
62
+ if (!ctx) return { rgb: css, opaque: true };
63
+ ctx.clearRect(0, 0, 1, 1);
64
+ ctx.fillStyle = css;
65
+ ctx.fillRect(0, 0, 1, 1);
66
+ const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
67
+ return { rgb: `rgb(${r}, ${g}, ${b})`, opaque: a === 255 };
68
+ }
69
+
70
+ /**
71
+ * What translucent swatches blend over: the legend's own card, or without a frame the nearest
72
+ * ancestor with an opaque background (the host's card), else the `--background` token.
73
+ */
74
+ function resolveBackground(el: HTMLElement, frameless: boolean): string {
75
+ if (!frameless) return toRgb(getComputedStyle(el).backgroundColor).rgb;
76
+ for (let at = el.parentElement; at; at = at.parentElement) {
77
+ const color = toRgb(getComputedStyle(at).backgroundColor);
78
+ if (color.opaque) return color.rgb;
79
+ }
80
+ // A computed custom property has its var() references already substituted.
81
+ return toRgb(getComputedStyle(el).getPropertyValue("--background").trim()).rgb;
82
+ }
83
+
84
+ function update() {
85
+ if (root) background = resolveBackground(root, !framed);
86
+ }
87
+
88
+ onMount(() => watchTheme(root!, update));
89
+
90
+ // On mount, and again when `framed` changes.
91
+ $effect(() => {
92
+ void framed;
93
+ untrack(update);
94
+ });
95
+ </script>
96
+
97
+ <section
98
+ bind:this={root}
99
+ class={["mapsdk-legend", !framed && "mapsdk-legend-frameless"]}
100
+ aria-label={map.messages("mapsdk_legend_label")}
101
+ hidden={entries.length === 0}
102
+ >
103
+ {#each entries as entry (entry.kind === "scale" ? entry.section.pluginId : entry.pluginId)}
104
+ {#if entry.kind === "scale"}
105
+ {@const section = entry.section}
106
+ <div class="mapsdk-legend-section">
107
+ <h3 class="text-sm">
108
+ {section.title}
109
+ {#if section.units}
110
+ <span class="text-xs">({section.units})</span>
111
+ {/if}
112
+ </h3>
113
+ {#if background !== null}
114
+ <LegendBar bar={section.bar} {background} messages={map.messages} />
115
+ {/if}
116
+ </div>
117
+ {:else}
118
+ {@const status = entry.status}
119
+ <div class="mapsdk-legend-status text-sm" role="status">
120
+ {#if status.state === "loading"}
121
+ <h3 class="text-sm">{entry.layer}</h3>
122
+ <p>{map.messages("mapsdk_loading")}</p>
123
+ {:else}
124
+ <p>{map.messages("mapsdk_unavailable", { layer: entry.layer })}</p>
125
+ {#if status.state === "error"}
126
+ <button
127
+ type="button"
128
+ class="mapsdk-retry mapsdk-focus mapsdk-motion text-sm"
129
+ onclick={() => status.retry()}
130
+ >
131
+ {map.messages("mapsdk_error_retry")}
132
+ </button>
133
+ {/if}
134
+ {/if}
135
+ </div>
136
+ {/if}
137
+ {/each}
138
+ </section>
139
+
140
+ <style>
141
+ .mapsdk-legend {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: 0.75rem;
145
+ width: 21rem;
146
+ max-width: calc(100vw - 2rem);
147
+ box-sizing: border-box;
148
+ padding: 0.75rem 1rem;
149
+ border: 1px solid var(--card-border, var(--border));
150
+ border-radius: var(--radius-control, var(--radius));
151
+ background: var(--background);
152
+ color: var(--foreground);
153
+ box-shadow: var(--shadow-card, none);
154
+ pointer-events: auto;
155
+ }
156
+
157
+ /* framed={false}: the host's card is the frame. */
158
+ .mapsdk-legend-frameless {
159
+ width: auto;
160
+ max-width: none;
161
+ padding: 0;
162
+ border: 0;
163
+ border-radius: 0;
164
+ background: none;
165
+ box-shadow: none;
166
+ }
167
+
168
+ .mapsdk-legend[hidden] {
169
+ display: none;
170
+ }
171
+
172
+ h3 {
173
+ margin: 0 0 0.5rem;
174
+ text-align: center;
175
+ font-weight: 600;
176
+ }
177
+
178
+ .mapsdk-legend-status {
179
+ display: flex;
180
+ flex-direction: column;
181
+ align-items: center;
182
+ gap: 0.5rem;
183
+ text-align: center;
184
+ }
185
+
186
+ .mapsdk-legend-status h3 {
187
+ margin: 0;
188
+ }
189
+
190
+ .mapsdk-legend-status p {
191
+ margin: 0;
192
+ color: var(--muted-foreground);
193
+ }
194
+
195
+ .mapsdk-retry {
196
+ padding: 0.25rem 0.75rem;
197
+ border: 1px solid var(--border);
198
+ border-radius: var(--radius-control, var(--radius));
199
+ background: var(--background);
200
+ color: var(--foreground);
201
+ font: inherit;
202
+ cursor: pointer;
203
+ }
204
+
205
+ .mapsdk-retry:hover {
206
+ background: var(--accent, var(--muted));
207
+ }
208
+
209
+ h3 span {
210
+ font-weight: 400;
211
+ color: var(--muted-foreground);
212
+ }
213
+ </style>
@@ -0,0 +1,22 @@
1
+ import "./controls.css";
2
+ import type { MapContext } from "../core/create-map.svelte.js";
3
+ type $$ComponentProps = {
4
+ map: MapContext;
5
+ /**
6
+ * `true` (default) draws the legend's own card. `false` draws no frame (no border,
7
+ * background, padding, or fixed width) so a host's card frames it. Translucent swatches
8
+ * then blend over the nearest ancestor with an opaque background (a see-through card is
9
+ * skipped), else the `--background` token. The color is read on mount, when `framed`
10
+ * changes, and on a theme change; a card that changes color through its own class or
11
+ * style isn't re-read.
12
+ */
13
+ framed?: boolean;
14
+ /**
15
+ * `"bottom-first"` (default) lists the bottom plugin's section first; `"top-first"`
16
+ * last.
17
+ */
18
+ order?: "bottom-first" | "top-first";
19
+ };
20
+ declare const Legend: import("svelte").Component<$$ComponentProps, {}, "">;
21
+ type Legend = ReturnType<typeof Legend>;
22
+ export default Legend;
@@ -0,0 +1,161 @@
1
+ <script lang="ts">
2
+ import type { MessageProvider } from "../data/messages.js";
3
+ import { swatchOverBackground, type LegendBar } from "./legend-data.js";
4
+
5
+ interface Props {
6
+ bar: LegendBar;
7
+ /** The page background as a color color2k can parse. */
8
+ background: string;
9
+ messages: MessageProvider;
10
+ }
11
+
12
+ let { bar, background, messages }: Props = $props();
13
+
14
+ // An empty color (a level with none) gets a neutral swatch rather than nothing.
15
+ const NEUTRAL = "var(--muted-foreground)";
16
+
17
+ const items = $derived(
18
+ bar.kind === "gradient"
19
+ ? bar.stops.map((stop) => ({
20
+ color: stop.color || NEUTRAL,
21
+ label: stop.label,
22
+ // The ticks are aria-hidden, so their numbers reach screen readers here.
23
+ threshold: stop.tick ? messages("mapsdk_legend_threshold", { value: stop.tick }) : ""
24
+ }))
25
+ : bar.blocks.map((block) => ({
26
+ color: block.color
27
+ ? swatchOverBackground(block.color, block.opacity, background)
28
+ : NEUTRAL,
29
+ label: block.label,
30
+ threshold: block.tick ? messages("mapsdk_legend_threshold", { value: block.tick }) : ""
31
+ }))
32
+ );
33
+
34
+ const gradient = $derived(
35
+ bar.kind === "gradient" && bar.stops.length > 0
36
+ ? bar.stops.length === 1
37
+ ? items[0].color
38
+ : `linear-gradient(to right, ${items.map((item) => item.color).join(", ")})`
39
+ : null
40
+ );
41
+ </script>
42
+
43
+ {#if bar.kind === "gradient" && gradient}
44
+ <div class="mapsdk-legend-gradient" style:background={gradient} aria-hidden="true"></div>
45
+ <div class="mapsdk-legend-ticks text-xs" aria-hidden="true">
46
+ {#each bar.stops as stop, i (i)}
47
+ <span>{stop.tick}</span>
48
+ {/each}
49
+ </div>
50
+ {/if}
51
+ {#if bar.kind === "blocks"}
52
+ <!-- One contiguous bar, each level's label under its segment (a list for screen readers). -->
53
+ <div class="mapsdk-legend-blocks" aria-hidden="true">
54
+ {#each items as item, i (i)}
55
+ <span class="mapsdk-legend-block" style:background={item.color}></span>
56
+ {/each}
57
+ </div>
58
+ <ul class="mapsdk-legend-block-labels text-xs" style:--blocks={items.length}>
59
+ {#each items as item, i (i)}
60
+ <li>
61
+ {item.label}
62
+ {#if item.threshold}<span class="mapsdk-legend-threshold">{item.threshold}</span>{/if}
63
+ </li>
64
+ {/each}
65
+ </ul>
66
+ {:else}
67
+ <ul class="mapsdk-legend-items text-xs">
68
+ {#each items as item, i (i)}
69
+ <li>
70
+ <span class="mapsdk-legend-swatch" style:background={item.color}></span>
71
+ <span>{item.label}</span>
72
+ {#if item.threshold}<span class="mapsdk-legend-threshold">{item.threshold}</span>{/if}
73
+ </li>
74
+ {/each}
75
+ </ul>
76
+ {/if}
77
+
78
+ <style>
79
+ .mapsdk-legend-gradient {
80
+ height: 0.75rem;
81
+ border-radius: 0.25rem;
82
+ outline: var(--swatch-outline-width, 1px) solid
83
+ var(--swatch-outline-color, color-mix(in srgb, var(--foreground) 75%, var(--background)));
84
+ outline-offset: 1px;
85
+ }
86
+
87
+ .mapsdk-legend-blocks {
88
+ display: flex;
89
+ height: 0.75rem;
90
+ border-radius: 0.25rem;
91
+ overflow: hidden;
92
+ outline: var(--swatch-outline-width, 1px) solid
93
+ var(--swatch-outline-color, color-mix(in srgb, var(--foreground) 75%, var(--background)));
94
+ outline-offset: 1px;
95
+ }
96
+
97
+ .mapsdk-legend-block {
98
+ flex: 1 1 0;
99
+ }
100
+
101
+ /* Equal columns under the equal segments; long labels wrap inside their column. */
102
+ .mapsdk-legend-block-labels {
103
+ display: grid;
104
+ grid-template-columns: repeat(var(--blocks), minmax(0, 1fr));
105
+ margin: 0.375rem 0 0;
106
+ padding: 0;
107
+ list-style: none;
108
+ text-align: center;
109
+ }
110
+
111
+ .mapsdk-legend-block-labels li {
112
+ position: relative;
113
+ padding: 0 0.125rem;
114
+ overflow-wrap: anywhere;
115
+ }
116
+
117
+ .mapsdk-legend-ticks {
118
+ display: flex;
119
+ justify-content: space-between;
120
+ margin-top: 0.375rem;
121
+ font-variant-numeric: tabular-nums;
122
+ }
123
+
124
+ .mapsdk-legend-items {
125
+ display: flex;
126
+ flex-wrap: wrap;
127
+ gap: 0.25rem 0.75rem;
128
+ margin: 0.5rem 0 0;
129
+ padding: 0;
130
+ list-style: none;
131
+ }
132
+
133
+ /* Visually hidden, read after the label. */
134
+ .mapsdk-legend-threshold {
135
+ position: absolute;
136
+ width: 1px;
137
+ height: 1px;
138
+ overflow: hidden;
139
+ clip-path: inset(50%);
140
+ white-space: nowrap;
141
+ }
142
+
143
+ .mapsdk-legend-items li {
144
+ position: relative;
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 0.375rem;
148
+ }
149
+
150
+ /* The outline sits 1px off the swatch, over the background, so it needs 3:1 against the
151
+ background only; the gap keeps a swatch near the outline's own color readable. */
152
+ .mapsdk-legend-swatch {
153
+ width: 0.875rem;
154
+ height: 0.875rem;
155
+ flex: none;
156
+ border-radius: 0.125rem;
157
+ outline: var(--swatch-outline-width, 1px) solid
158
+ var(--swatch-outline-color, color-mix(in srgb, var(--foreground) 75%, var(--background)));
159
+ outline-offset: 1px;
160
+ }
161
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { MessageProvider } from "../data/messages.js";
2
+ import { type LegendBar } from "./legend-data.js";
3
+ interface Props {
4
+ bar: LegendBar;
5
+ /** The page background as a color color2k can parse. */
6
+ background: string;
7
+ messages: MessageProvider;
8
+ }
9
+ declare const LegendBar: import("svelte").Component<Props, {}, "">;
10
+ type LegendBar = ReturnType<typeof LegendBar>;
11
+ export default LegendBar;
@@ -0,0 +1,135 @@
1
+ <script lang="ts">
2
+ import "./controls.css";
3
+ import type { MapContext } from "../core/create-map.svelte.js";
4
+ import type { GlMapLike } from "../core/gl.js";
5
+
6
+ interface Props {
7
+ map: MapContext;
8
+ /** The live GL map; the zoom buttons do nothing while it is `null`. */
9
+ gl: GlMapLike | null;
10
+ }
11
+
12
+ let { map, gl }: Props = $props();
13
+
14
+ function zoomBy(delta: number) {
15
+ if (!gl) return;
16
+ const reduced = globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
17
+ gl.easeTo({ zoom: gl.getZoom() + delta, ...(reduced && { duration: 0 }) });
18
+ }
19
+ </script>
20
+
21
+ <div class="mapsdk-zoom" role="group" aria-label={map.messages("mapsdk_controls_label")}>
22
+ <button type="button" class="mapsdk-focus mapsdk-motion" disabled={!gl} onclick={() => zoomBy(1)}>
23
+ <span class="mapsdk-zoom-icon" aria-hidden="true">+</span>
24
+ <span class="mapsdk-sr">{map.messages("mapsdk_zoom_in")}</span>
25
+ <span class="mapsdk-zoom-tip mapsdk-motion text-xs" aria-hidden="true"
26
+ >{map.messages("mapsdk_zoom_in")}</span
27
+ >
28
+ </button>
29
+ <button
30
+ type="button"
31
+ class="mapsdk-focus mapsdk-motion"
32
+ disabled={!gl}
33
+ onclick={() => zoomBy(-1)}
34
+ >
35
+ <span class="mapsdk-zoom-icon" aria-hidden="true">&minus;</span>
36
+ <span class="mapsdk-sr">{map.messages("mapsdk_zoom_out")}</span>
37
+ <span class="mapsdk-zoom-tip mapsdk-motion text-xs" aria-hidden="true"
38
+ >{map.messages("mapsdk_zoom_out")}</span
39
+ >
40
+ </button>
41
+ </div>
42
+
43
+ <style>
44
+ .mapsdk-zoom {
45
+ position: absolute;
46
+ top: 0.75rem;
47
+ right: 0.75rem;
48
+ z-index: 2;
49
+ display: flex;
50
+ flex-direction: column;
51
+ border: 1px solid var(--card-border, var(--border));
52
+ border-radius: var(--radius-control, var(--radius));
53
+ background: var(--background);
54
+ box-shadow: var(--shadow-card, none);
55
+ pointer-events: auto;
56
+ }
57
+
58
+ .mapsdk-zoom button {
59
+ width: 2.25rem;
60
+ height: 2.25rem;
61
+ padding: 0;
62
+ border: 0;
63
+ background: transparent;
64
+ color: var(--foreground);
65
+ font: inherit;
66
+ cursor: pointer;
67
+ }
68
+
69
+ .mapsdk-zoom button + button {
70
+ border-top: 1px solid var(--card-border, var(--border));
71
+ }
72
+
73
+ .mapsdk-zoom button:disabled {
74
+ color: var(--muted-foreground);
75
+ cursor: default;
76
+ }
77
+
78
+ .mapsdk-zoom button {
79
+ position: relative;
80
+ }
81
+
82
+ /* The hover fill follows the group's rounded corners. */
83
+ .mapsdk-zoom button:first-child {
84
+ border-radius: var(--radius-control, var(--radius)) var(--radius-control, var(--radius)) 0 0;
85
+ }
86
+
87
+ .mapsdk-zoom button:last-child {
88
+ border-radius: 0 0 var(--radius-control, var(--radius)) var(--radius-control, var(--radius));
89
+ }
90
+
91
+ .mapsdk-zoom button:hover:not(:disabled) {
92
+ background: var(--accent, var(--muted));
93
+ }
94
+
95
+ /* Above the next button, so the ring isn't cut by it. */
96
+ .mapsdk-zoom button:focus-visible {
97
+ z-index: 1;
98
+ }
99
+
100
+ /* The visible name: on hover and focus, left of the button; the screen-reader text stays. */
101
+ .mapsdk-zoom-tip {
102
+ position: absolute;
103
+ top: 50%;
104
+ right: calc(100% + 0.5rem);
105
+ padding: 0.125rem 0.5rem;
106
+ border: 1px solid var(--card-border, var(--border));
107
+ border-radius: var(--radius-control, var(--radius));
108
+ background: var(--popover, var(--background));
109
+ color: var(--popover-foreground, var(--foreground));
110
+ box-shadow: var(--shadow-lifted, var(--shadow-card, none));
111
+ white-space: nowrap;
112
+ opacity: 0;
113
+ pointer-events: none;
114
+ transform: translateY(-50%);
115
+ }
116
+
117
+ .mapsdk-zoom button:hover .mapsdk-zoom-tip,
118
+ .mapsdk-zoom button:focus-visible .mapsdk-zoom-tip {
119
+ opacity: 1;
120
+ }
121
+
122
+ .mapsdk-zoom-icon {
123
+ font-size: 1.25rem;
124
+ line-height: 1;
125
+ }
126
+
127
+ .mapsdk-sr {
128
+ position: absolute;
129
+ width: 1px;
130
+ height: 1px;
131
+ overflow: hidden;
132
+ clip-path: inset(50%);
133
+ white-space: nowrap;
134
+ }
135
+ </style>
@@ -0,0 +1,11 @@
1
+ import "./controls.css";
2
+ import type { MapContext } from "../core/create-map.svelte.js";
3
+ import type { GlMapLike } from "../core/gl.js";
4
+ interface Props {
5
+ map: MapContext;
6
+ /** The live GL map; the zoom buttons do nothing while it is `null`. */
7
+ gl: GlMapLike | null;
8
+ }
9
+ declare const MapControls: import("svelte").Component<Props, {}, "">;
10
+ type MapControls = ReturnType<typeof MapControls>;
11
+ export default MapControls;