@aiaiai-pt/design-system 0.4.4 → 0.5.1
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.
- package/components/Button.svelte +4 -0
- package/components/Calendar.svelte +971 -0
- package/components/Calendar.svelte.d.ts +50 -0
- package/components/DatePicker.svelte +473 -0
- package/components/DatePicker.svelte.d.ts +59 -0
- package/components/DateRangePicker.svelte +558 -0
- package/components/DateRangePicker.svelte.d.ts +55 -0
- package/components/DateTimePicker.svelte +275 -0
- package/components/DateTimePicker.svelte.d.ts +55 -0
- package/components/MapCluster.svelte +220 -0
- package/components/MapCluster.svelte.d.ts +39 -0
- package/components/MapDisplay.svelte +139 -0
- package/components/MapDisplay.svelte.d.ts +35 -0
- package/components/MapHeatmap.svelte +164 -0
- package/components/MapHeatmap.svelte.d.ts +50 -0
- package/components/MapPicker.svelte +243 -0
- package/components/MapPicker.svelte.d.ts +49 -0
- package/components/MapPopup.svelte +101 -0
- package/components/MapPopup.svelte.d.ts +30 -0
- package/components/Select.svelte +3 -4
- package/components/StatCard.svelte +195 -0
- package/components/StatCard.svelte.d.ts +42 -0
- package/components/StatGrid.svelte +39 -0
- package/components/StatGrid.svelte.d.ts +29 -0
- package/components/index.d.ts +12 -0
- package/components/index.js +17 -0
- package/components/map-utils.d.ts +100 -0
- package/components/map-utils.js +338 -0
- package/package.json +8 -1
- package/tokens/components.css +215 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component MapDisplay
|
|
3
|
+
|
|
4
|
+
Read-only map showing a point or polygon. OpenLayers with configurable tiles.
|
|
5
|
+
Consumes --map-* tokens from components.css.
|
|
6
|
+
|
|
7
|
+
@example Point
|
|
8
|
+
<MapDisplay center={[-9.14, 38.74]} zoom={14} marker={[-9.14, 38.74]} />
|
|
9
|
+
|
|
10
|
+
@example Styled tiles
|
|
11
|
+
<MapDisplay center={[-9.14, 38.74]} tileSource={{ type: 'stadia', layer: 'stamen_toner_lite' }} />
|
|
12
|
+
-->
|
|
13
|
+
<script>
|
|
14
|
+
import { fromLonLat } from 'ol/proj.js';
|
|
15
|
+
import { createTileLayer, createMapStyles, watchTheme, renderMapError } from './map-utils.js';
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
/** @type {[number, number]} — [longitude, latitude] WGS84 */
|
|
19
|
+
center = [0, 0],
|
|
20
|
+
/** @type {number} */
|
|
21
|
+
zoom = 12,
|
|
22
|
+
/** @type {[number, number] | undefined} — [lon, lat] for single marker */
|
|
23
|
+
marker = undefined,
|
|
24
|
+
/** @type {number[][] | undefined} — polygon coords [[lon,lat], ...] */
|
|
25
|
+
polygon = undefined,
|
|
26
|
+
/** @type {import('./map-utils.js').TileSourceConfig} */
|
|
27
|
+
tileSource = { type: 'osm' },
|
|
28
|
+
/** @type {string} */
|
|
29
|
+
height = '100%',
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
class: className = '',
|
|
32
|
+
...rest
|
|
33
|
+
} = $props();
|
|
34
|
+
|
|
35
|
+
/** @type {HTMLElement | undefined} */
|
|
36
|
+
let container = $state();
|
|
37
|
+
|
|
38
|
+
$effect(() => {
|
|
39
|
+
if (!container) return;
|
|
40
|
+
|
|
41
|
+
let disposed = false;
|
|
42
|
+
/** @type {import('ol/Map.js').default | undefined} */
|
|
43
|
+
let map;
|
|
44
|
+
/** @type {(() => void) | undefined} */
|
|
45
|
+
let disposeTheme;
|
|
46
|
+
|
|
47
|
+
(async () => { try {
|
|
48
|
+
const [
|
|
49
|
+
{ default: OlMap },
|
|
50
|
+
{ default: View },
|
|
51
|
+
{ default: VectorLayer },
|
|
52
|
+
{ default: VectorSource },
|
|
53
|
+
{ default: Feature },
|
|
54
|
+
{ default: Point },
|
|
55
|
+
{ default: Polygon },
|
|
56
|
+
] = await Promise.all([
|
|
57
|
+
import('ol/Map.js'),
|
|
58
|
+
import('ol/View.js'),
|
|
59
|
+
import('ol/layer/Vector.js'),
|
|
60
|
+
import('ol/source/Vector.js'),
|
|
61
|
+
import('ol/Feature.js'),
|
|
62
|
+
import('ol/geom/Point.js'),
|
|
63
|
+
import('ol/geom/Polygon.js'),
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
if (disposed) return;
|
|
67
|
+
|
|
68
|
+
const [tileLayer, styles] = await Promise.all([
|
|
69
|
+
createTileLayer(tileSource),
|
|
70
|
+
createMapStyles(container),
|
|
71
|
+
]);
|
|
72
|
+
if (disposed) return;
|
|
73
|
+
|
|
74
|
+
/** @type {Feature[]} */
|
|
75
|
+
const features = [];
|
|
76
|
+
|
|
77
|
+
if (marker) {
|
|
78
|
+
features.push(new Feature({ geometry: new Point(fromLonLat(marker)) }));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (polygon) {
|
|
82
|
+
features.push(new Feature({
|
|
83
|
+
geometry: new Polygon([polygon.map(c => fromLonLat(c))]),
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const vectorLayer = new VectorLayer({
|
|
88
|
+
source: new VectorSource({ features }),
|
|
89
|
+
style: (feature) => {
|
|
90
|
+
const type = feature.getGeometry()?.getType();
|
|
91
|
+
return type === 'Point' ? styles.marker : styles.polygon;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
map = new OlMap({
|
|
96
|
+
target: container,
|
|
97
|
+
layers: [tileLayer, vectorLayer],
|
|
98
|
+
view: new View({
|
|
99
|
+
center: fromLonLat(center),
|
|
100
|
+
zoom,
|
|
101
|
+
}),
|
|
102
|
+
controls: [],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
disposeTheme = watchTheme(() => {
|
|
106
|
+
styles.refresh();
|
|
107
|
+
vectorLayer.getSource()?.changed();
|
|
108
|
+
});
|
|
109
|
+
} catch (err) { renderMapError(container, 'MapDisplay', /** @type {Error} */ (err)); } })();
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
disposed = true;
|
|
113
|
+
disposeTheme?.();
|
|
114
|
+
map?.setTarget(undefined);
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<div
|
|
120
|
+
bind:this={container}
|
|
121
|
+
class="map-display {className}"
|
|
122
|
+
style:height
|
|
123
|
+
role="img"
|
|
124
|
+
aria-label="Map"
|
|
125
|
+
{...rest}
|
|
126
|
+
></div>
|
|
127
|
+
|
|
128
|
+
<style>
|
|
129
|
+
.map-display {
|
|
130
|
+
width: 100%;
|
|
131
|
+
border: var(--map-border);
|
|
132
|
+
border-radius: var(--map-radius);
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.map-display :global(.ol-viewport) {
|
|
137
|
+
border-radius: inherit;
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export default MapDisplay;
|
|
2
|
+
type MapDisplay = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* MapDisplay
|
|
8
|
+
*
|
|
9
|
+
* Read-only map showing a point or polygon. OpenLayers with configurable tiles.
|
|
10
|
+
* Consumes --map-* tokens from components.css.
|
|
11
|
+
*
|
|
12
|
+
* @example Point
|
|
13
|
+
* <MapDisplay center={[-9.14, 38.74]} zoom={14} marker={[-9.14, 38.74]} />
|
|
14
|
+
*
|
|
15
|
+
* @example Styled tiles
|
|
16
|
+
* <MapDisplay center={[-9.14, 38.74]} tileSource={{ type: 'stadia', layer: 'stamen_toner_lite' }} />
|
|
17
|
+
*/
|
|
18
|
+
declare const MapDisplay: import("svelte").Component<{
|
|
19
|
+
center?: any[];
|
|
20
|
+
zoom?: number;
|
|
21
|
+
marker?: any;
|
|
22
|
+
polygon?: any;
|
|
23
|
+
tileSource?: Record<string, any>;
|
|
24
|
+
height?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
} & Record<string, any>, {}, "">;
|
|
27
|
+
type $$ComponentProps = {
|
|
28
|
+
center?: any[];
|
|
29
|
+
zoom?: number;
|
|
30
|
+
marker?: any;
|
|
31
|
+
polygon?: any;
|
|
32
|
+
tileSource?: Record<string, any>;
|
|
33
|
+
height?: string;
|
|
34
|
+
class?: string;
|
|
35
|
+
} & Record<string, any>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component MapHeatmap
|
|
3
|
+
|
|
4
|
+
Density visualization using OpenLayers built-in Heatmap layer.
|
|
5
|
+
Gradient derived from DS semantic color tokens by default.
|
|
6
|
+
Consumes --map-* tokens from components.css.
|
|
7
|
+
|
|
8
|
+
OL Heatmap intensity is density-driven: overlapping point quads
|
|
9
|
+
accumulate alpha. blur/radius are screen pixels (not map units).
|
|
10
|
+
Keep radius >= blur for peak intensity (blurSlope = radius/blur).
|
|
11
|
+
|
|
12
|
+
@example
|
|
13
|
+
<MapHeatmap
|
|
14
|
+
points={[{ lon: -9.14, lat: 38.74, weight: 5 }]}
|
|
15
|
+
radius={25}
|
|
16
|
+
blur={15}
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
@example Custom gradient
|
|
20
|
+
<MapHeatmap points={data} gradient={['#fef9ee', '#fef3c7', '#f59e0b', '#dc2626']} />
|
|
21
|
+
-->
|
|
22
|
+
<script>
|
|
23
|
+
import { fromLonLat } from 'ol/proj.js';
|
|
24
|
+
import { createTileLayer, getHeatmapGradient, watchTheme, renderMapError } from './map-utils.js';
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
/** @type {{ lon: number, lat: number, weight?: number }[]} */
|
|
28
|
+
points = [],
|
|
29
|
+
/** @type {[number, number]} — fallback center if no points [lon, lat] */
|
|
30
|
+
center = [0, 0],
|
|
31
|
+
/** @type {number} — fallback zoom if no points */
|
|
32
|
+
zoom = 6,
|
|
33
|
+
/** @type {number} — point radius in screen pixels. Keep >= blur for peak intensity. */
|
|
34
|
+
radius = 25,
|
|
35
|
+
/** @type {number} — blur falloff in screen pixels */
|
|
36
|
+
blur = 15,
|
|
37
|
+
/** @type {string[] | undefined} — custom gradient overrides tokens */
|
|
38
|
+
gradient = undefined,
|
|
39
|
+
/** @type {import('./map-utils.js').TileSourceConfig} */
|
|
40
|
+
tileSource = { type: 'osm' },
|
|
41
|
+
/** @type {number} — max zoom when auto-fitting to points extent */
|
|
42
|
+
maxZoom = 17,
|
|
43
|
+
/** @type {string} */
|
|
44
|
+
height = '100%',
|
|
45
|
+
/** @type {string} */
|
|
46
|
+
class: className = '',
|
|
47
|
+
...rest
|
|
48
|
+
} = $props();
|
|
49
|
+
|
|
50
|
+
/** @type {HTMLElement | undefined} */
|
|
51
|
+
let container = $state();
|
|
52
|
+
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (!container) return;
|
|
55
|
+
|
|
56
|
+
let disposed = false;
|
|
57
|
+
/** @type {import('ol/Map.js').default | undefined} */
|
|
58
|
+
let map;
|
|
59
|
+
/** @type {(() => void) | undefined} */
|
|
60
|
+
let disposeTheme;
|
|
61
|
+
|
|
62
|
+
(async () => { try {
|
|
63
|
+
const [
|
|
64
|
+
{ default: OlMap },
|
|
65
|
+
{ default: View },
|
|
66
|
+
{ default: VectorSource },
|
|
67
|
+
{ default: Heatmap },
|
|
68
|
+
{ default: Feature },
|
|
69
|
+
{ default: Point },
|
|
70
|
+
] = await Promise.all([
|
|
71
|
+
import('ol/Map.js'),
|
|
72
|
+
import('ol/View.js'),
|
|
73
|
+
import('ol/source/Vector.js'),
|
|
74
|
+
import('ol/layer/Heatmap.js'),
|
|
75
|
+
import('ol/Feature.js'),
|
|
76
|
+
import('ol/geom/Point.js'),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
if (disposed) return;
|
|
80
|
+
|
|
81
|
+
const tileLayer = await createTileLayer(tileSource);
|
|
82
|
+
if (disposed) return;
|
|
83
|
+
|
|
84
|
+
// Non-linear weight normalization: sqrt lifts low values so they're
|
|
85
|
+
// visible while preserving relative ordering. OL expects 0-1.
|
|
86
|
+
const maxWeight = Math.max(...points.map(p => p.weight ?? 1), 1);
|
|
87
|
+
|
|
88
|
+
const features = points.map(p => {
|
|
89
|
+
const f = new Feature({ geometry: new Point(fromLonLat([p.lon, p.lat])) });
|
|
90
|
+
f.set('weight', Math.sqrt((p.weight ?? 1) / maxWeight));
|
|
91
|
+
return f;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const vectorSource = new VectorSource({ features });
|
|
95
|
+
|
|
96
|
+
const resolvedGradient = gradient ?? getHeatmapGradient(container);
|
|
97
|
+
|
|
98
|
+
const heatmapLayer = new Heatmap({
|
|
99
|
+
source: vectorSource,
|
|
100
|
+
blur,
|
|
101
|
+
radius,
|
|
102
|
+
gradient: resolvedGradient,
|
|
103
|
+
weight: (/** @type {import('ol/Feature.js').default} */ feature) =>
|
|
104
|
+
feature.get('weight') ?? 0,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
map = new OlMap({
|
|
108
|
+
target: container,
|
|
109
|
+
layers: [tileLayer, heatmapLayer],
|
|
110
|
+
view: new View({
|
|
111
|
+
center: fromLonLat(center),
|
|
112
|
+
zoom,
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Auto-fit view to points extent
|
|
117
|
+
if (points.length > 0) {
|
|
118
|
+
const extent = vectorSource.getExtent();
|
|
119
|
+
if (extent && isFinite(extent[0])) {
|
|
120
|
+
map.getView().fit(extent, {
|
|
121
|
+
padding: [50, 50, 50, 50],
|
|
122
|
+
maxZoom,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Theme reactivity: re-read gradient tokens on theme change
|
|
128
|
+
if (!gradient) {
|
|
129
|
+
disposeTheme = watchTheme(() => {
|
|
130
|
+
heatmapLayer.setGradient(getHeatmapGradient(container));
|
|
131
|
+
map?.render();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
} catch (err) { renderMapError(container, 'MapHeatmap', /** @type {Error} */ (err)); } })();
|
|
135
|
+
|
|
136
|
+
return () => {
|
|
137
|
+
disposed = true;
|
|
138
|
+
disposeTheme?.();
|
|
139
|
+
map?.setTarget(undefined);
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<div
|
|
145
|
+
bind:this={container}
|
|
146
|
+
class="map-heatmap {className}"
|
|
147
|
+
style:height
|
|
148
|
+
role="img"
|
|
149
|
+
aria-label="Heatmap"
|
|
150
|
+
{...rest}
|
|
151
|
+
></div>
|
|
152
|
+
|
|
153
|
+
<style>
|
|
154
|
+
.map-heatmap {
|
|
155
|
+
width: 100%;
|
|
156
|
+
border: var(--map-border);
|
|
157
|
+
border-radius: var(--map-radius);
|
|
158
|
+
overflow: hidden;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.map-heatmap :global(.ol-viewport) {
|
|
162
|
+
border-radius: inherit;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export default MapHeatmap;
|
|
2
|
+
type MapHeatmap = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* MapHeatmap
|
|
8
|
+
*
|
|
9
|
+
* Density visualization using OpenLayers built-in Heatmap layer.
|
|
10
|
+
* Gradient derived from DS semantic color tokens by default.
|
|
11
|
+
* Consumes --map-* tokens from components.css.
|
|
12
|
+
*
|
|
13
|
+
* OL Heatmap intensity is density-driven: overlapping point quads
|
|
14
|
+
* accumulate alpha. blur/radius are screen pixels (not map units).
|
|
15
|
+
* Keep radius >= blur for peak intensity (blurSlope = radius/blur).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <MapHeatmap
|
|
19
|
+
* points={[{ lon: -9.14, lat: 38.74, weight: 5 }]}
|
|
20
|
+
* radius={25}
|
|
21
|
+
* blur={15}
|
|
22
|
+
* />
|
|
23
|
+
*
|
|
24
|
+
* @example Custom gradient
|
|
25
|
+
* <MapHeatmap points={data} gradient={['#fef9ee', '#fef3c7', '#f59e0b', '#dc2626']} />
|
|
26
|
+
*/
|
|
27
|
+
declare const MapHeatmap: import("svelte").Component<{
|
|
28
|
+
points?: any[];
|
|
29
|
+
center?: any[];
|
|
30
|
+
zoom?: number;
|
|
31
|
+
radius?: number;
|
|
32
|
+
blur?: number;
|
|
33
|
+
gradient?: any;
|
|
34
|
+
tileSource?: Record<string, any>;
|
|
35
|
+
maxZoom?: number;
|
|
36
|
+
height?: string;
|
|
37
|
+
class?: string;
|
|
38
|
+
} & Record<string, any>, {}, "">;
|
|
39
|
+
type $$ComponentProps = {
|
|
40
|
+
points?: any[];
|
|
41
|
+
center?: any[];
|
|
42
|
+
zoom?: number;
|
|
43
|
+
radius?: number;
|
|
44
|
+
blur?: number;
|
|
45
|
+
gradient?: any;
|
|
46
|
+
tileSource?: Record<string, any>;
|
|
47
|
+
maxZoom?: number;
|
|
48
|
+
height?: string;
|
|
49
|
+
class?: string;
|
|
50
|
+
} & Record<string, any>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component MapPicker
|
|
3
|
+
|
|
4
|
+
Interactive map for selecting a point or drawing a polygon.
|
|
5
|
+
OpenLayers with configurable tiles and Draw interaction.
|
|
6
|
+
Draw sketch styled with DS tokens (not OL blue default).
|
|
7
|
+
Consumes --map-* tokens from components.css.
|
|
8
|
+
|
|
9
|
+
@example Point selection
|
|
10
|
+
<MapPicker mode="point" onchange={(coords) => console.log(coords)} />
|
|
11
|
+
|
|
12
|
+
@example Polygon drawing
|
|
13
|
+
<MapPicker mode="polygon" onchange={(coords) => console.log(coords)} />
|
|
14
|
+
-->
|
|
15
|
+
<script module>
|
|
16
|
+
let _mappickerUid = 0;
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import { fromLonLat, toLonLat } from 'ol/proj.js';
|
|
21
|
+
import { createTileLayer, createMapStyles, watchTheme, renderMapError } from './map-utils.js';
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
/** @type {'point' | 'polygon'} */
|
|
25
|
+
mode = 'point',
|
|
26
|
+
/** @type {[number, number]} — initial center [lon, lat] */
|
|
27
|
+
center = [0, 0],
|
|
28
|
+
/** @type {number} */
|
|
29
|
+
zoom = 12,
|
|
30
|
+
/** @type {[number, number] | undefined} — current point value [lon, lat] */
|
|
31
|
+
value = $bindable(undefined),
|
|
32
|
+
/** @type {string | undefined} */
|
|
33
|
+
label = undefined,
|
|
34
|
+
/** @type {string | undefined} */
|
|
35
|
+
help = undefined,
|
|
36
|
+
/** @type {string | undefined} */
|
|
37
|
+
error = undefined,
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
disabled = false,
|
|
40
|
+
/** @type {import('./map-utils.js').TileSourceConfig} */
|
|
41
|
+
tileSource = { type: 'osm' },
|
|
42
|
+
/** @type {((coords: [number, number] | number[][]) => void) | undefined} */
|
|
43
|
+
onchange = undefined,
|
|
44
|
+
/** @type {string} */
|
|
45
|
+
height = '100%',
|
|
46
|
+
/** @type {string | undefined} */
|
|
47
|
+
id = undefined,
|
|
48
|
+
/** @type {string} */
|
|
49
|
+
class: className = '',
|
|
50
|
+
...rest
|
|
51
|
+
} = $props();
|
|
52
|
+
|
|
53
|
+
const fallbackId = `mappicker-${_mappickerUid++}`;
|
|
54
|
+
const pickerId = $derived(id ?? fallbackId);
|
|
55
|
+
const hintId = $derived(`${pickerId}-hint`);
|
|
56
|
+
const hasHint = $derived(!!error || !!help);
|
|
57
|
+
|
|
58
|
+
/** @type {HTMLElement | undefined} */
|
|
59
|
+
let container = $state();
|
|
60
|
+
|
|
61
|
+
$effect(() => {
|
|
62
|
+
if (!container || disabled) return;
|
|
63
|
+
|
|
64
|
+
let disposed = false;
|
|
65
|
+
/** @type {import('ol/Map.js').default | undefined} */
|
|
66
|
+
let map;
|
|
67
|
+
/** @type {(() => void) | undefined} */
|
|
68
|
+
let disposeTheme;
|
|
69
|
+
|
|
70
|
+
(async () => { try {
|
|
71
|
+
const [
|
|
72
|
+
{ default: OlMap },
|
|
73
|
+
{ default: View },
|
|
74
|
+
{ default: VectorLayer },
|
|
75
|
+
{ default: VectorSource },
|
|
76
|
+
{ default: Feature },
|
|
77
|
+
{ default: Point },
|
|
78
|
+
{ default: Draw },
|
|
79
|
+
] = await Promise.all([
|
|
80
|
+
import('ol/Map.js'),
|
|
81
|
+
import('ol/View.js'),
|
|
82
|
+
import('ol/layer/Vector.js'),
|
|
83
|
+
import('ol/source/Vector.js'),
|
|
84
|
+
import('ol/Feature.js'),
|
|
85
|
+
import('ol/geom/Point.js'),
|
|
86
|
+
import('ol/interaction/Draw.js'),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
if (disposed) return;
|
|
90
|
+
|
|
91
|
+
const [tileLayer, styles] = await Promise.all([
|
|
92
|
+
createTileLayer(tileSource),
|
|
93
|
+
createMapStyles(container),
|
|
94
|
+
]);
|
|
95
|
+
if (disposed) return;
|
|
96
|
+
|
|
97
|
+
const vectorSource = new VectorSource();
|
|
98
|
+
|
|
99
|
+
if (value && mode === 'point') {
|
|
100
|
+
vectorSource.addFeature(new Feature({ geometry: new Point(fromLonLat(value)) }));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const vectorLayer = new VectorLayer({
|
|
104
|
+
source: vectorSource,
|
|
105
|
+
style: (feature) => {
|
|
106
|
+
const type = feature.getGeometry()?.getType();
|
|
107
|
+
return type === 'Point' ? styles.marker : styles.polygon;
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Composite draw style: marker for vertices + polygon for fill/stroke
|
|
112
|
+
const drawStyle = [
|
|
113
|
+
styles.polygon,
|
|
114
|
+
styles.marker,
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const drawType = mode === 'point' ? 'Point' : 'Polygon';
|
|
118
|
+
const drawInteraction = new Draw({
|
|
119
|
+
source: vectorSource,
|
|
120
|
+
type: drawType,
|
|
121
|
+
style: drawStyle,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
drawInteraction.on('drawstart', () => {
|
|
125
|
+
vectorSource.clear();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
drawInteraction.on('drawend', (evt) => {
|
|
129
|
+
const geom = evt.feature.getGeometry();
|
|
130
|
+
if (!geom) return;
|
|
131
|
+
|
|
132
|
+
if (mode === 'point') {
|
|
133
|
+
const coords = /** @type {import('ol/geom/Point.js').default} */ (geom).getCoordinates();
|
|
134
|
+
const wgs84 = /** @type {[number, number]} */ (toLonLat(coords));
|
|
135
|
+
value = wgs84;
|
|
136
|
+
onchange?.(wgs84);
|
|
137
|
+
} else {
|
|
138
|
+
const coords = /** @type {import('ol/geom/Polygon.js').default} */ (geom).getCoordinates()[0];
|
|
139
|
+
const wgs84 = coords.map(c => /** @type {[number, number]} */ (toLonLat(c)));
|
|
140
|
+
onchange?.(wgs84);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const initialCenter = value && mode === 'point' ? fromLonLat(value) : fromLonLat(center);
|
|
145
|
+
|
|
146
|
+
map = new OlMap({
|
|
147
|
+
target: container,
|
|
148
|
+
layers: [tileLayer, vectorLayer],
|
|
149
|
+
view: new View({
|
|
150
|
+
center: initialCenter,
|
|
151
|
+
zoom,
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
map.addInteraction(drawInteraction);
|
|
156
|
+
|
|
157
|
+
disposeTheme = watchTheme(() => {
|
|
158
|
+
styles.refresh();
|
|
159
|
+
vectorSource.changed();
|
|
160
|
+
});
|
|
161
|
+
} catch (err) { renderMapError(container, 'MapPicker', /** @type {Error} */ (err)); } })();
|
|
162
|
+
|
|
163
|
+
return () => {
|
|
164
|
+
disposed = true;
|
|
165
|
+
disposeTheme?.();
|
|
166
|
+
map?.setTarget(undefined);
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
</script>
|
|
170
|
+
|
|
171
|
+
<div class="map-picker {className}" {...rest}>
|
|
172
|
+
{#if label}
|
|
173
|
+
<label class="map-picker-label" for={pickerId}>{label}</label>
|
|
174
|
+
{/if}
|
|
175
|
+
|
|
176
|
+
<div
|
|
177
|
+
bind:this={container}
|
|
178
|
+
id={pickerId}
|
|
179
|
+
class="map-picker-canvas"
|
|
180
|
+
class:map-picker-error={!!error}
|
|
181
|
+
class:map-picker-disabled={disabled}
|
|
182
|
+
style:height
|
|
183
|
+
role="application"
|
|
184
|
+
aria-label={label ?? 'Map picker'}
|
|
185
|
+
aria-describedby={hasHint ? hintId : undefined}
|
|
186
|
+
></div>
|
|
187
|
+
|
|
188
|
+
{#if error}
|
|
189
|
+
<span id={hintId} class="map-picker-error-text" role="alert">{error}</span>
|
|
190
|
+
{:else if help}
|
|
191
|
+
<span id={hintId} class="map-picker-help">{help}</span>
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<style>
|
|
196
|
+
.map-picker {
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
gap: var(--input-label-gap);
|
|
200
|
+
width: 100%;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.map-picker-label {
|
|
204
|
+
font-family: var(--input-label-font);
|
|
205
|
+
font-size: var(--input-label-size);
|
|
206
|
+
letter-spacing: var(--input-label-tracking);
|
|
207
|
+
color: var(--input-label-color);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.map-picker-canvas {
|
|
211
|
+
width: 100%;
|
|
212
|
+
border: var(--map-border);
|
|
213
|
+
border-radius: var(--map-radius);
|
|
214
|
+
overflow: hidden;
|
|
215
|
+
cursor: crosshair;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.map-picker-canvas :global(.ol-viewport) {
|
|
219
|
+
border-radius: inherit;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.map-picker-canvas.map-picker-error {
|
|
223
|
+
border-color: var(--input-error-border-color);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.map-picker-canvas.map-picker-disabled {
|
|
227
|
+
opacity: 0.5;
|
|
228
|
+
pointer-events: none;
|
|
229
|
+
cursor: not-allowed;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.map-picker-help {
|
|
233
|
+
font-family: var(--input-help-font);
|
|
234
|
+
font-size: var(--input-help-size);
|
|
235
|
+
color: var(--input-help-color);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.map-picker-error-text {
|
|
239
|
+
font-family: var(--input-help-font);
|
|
240
|
+
font-size: var(--input-help-size);
|
|
241
|
+
color: var(--input-error-text);
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export default MapPicker;
|
|
2
|
+
type MapPicker = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* MapPicker
|
|
8
|
+
*
|
|
9
|
+
* Interactive map for selecting a point or drawing a polygon.
|
|
10
|
+
* OpenLayers with configurable tiles and Draw interaction.
|
|
11
|
+
* Draw sketch styled with DS tokens (not OL blue default).
|
|
12
|
+
* Consumes --map-* tokens from components.css.
|
|
13
|
+
*
|
|
14
|
+
* @example Point selection
|
|
15
|
+
* <MapPicker mode="point" onchange={(coords) => console.log(coords)} />
|
|
16
|
+
*
|
|
17
|
+
* @example Polygon drawing
|
|
18
|
+
* <MapPicker mode="polygon" onchange={(coords) => console.log(coords)} />
|
|
19
|
+
*/
|
|
20
|
+
declare const MapPicker: import("svelte").Component<{
|
|
21
|
+
mode?: string;
|
|
22
|
+
center?: any[];
|
|
23
|
+
zoom?: number;
|
|
24
|
+
value?: any;
|
|
25
|
+
label?: any;
|
|
26
|
+
help?: any;
|
|
27
|
+
error?: any;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
tileSource?: Record<string, any>;
|
|
30
|
+
onchange?: any;
|
|
31
|
+
height?: string;
|
|
32
|
+
id?: any;
|
|
33
|
+
class?: string;
|
|
34
|
+
} & Record<string, any>, {}, "value">;
|
|
35
|
+
type $$ComponentProps = {
|
|
36
|
+
mode?: string;
|
|
37
|
+
center?: any[];
|
|
38
|
+
zoom?: number;
|
|
39
|
+
value?: any;
|
|
40
|
+
label?: any;
|
|
41
|
+
help?: any;
|
|
42
|
+
error?: any;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
tileSource?: Record<string, any>;
|
|
45
|
+
onchange?: any;
|
|
46
|
+
height?: string;
|
|
47
|
+
id?: any;
|
|
48
|
+
class?: string;
|
|
49
|
+
} & Record<string, any>;
|