@sakuzu/maplibre-gl-draw-ui 1.0.0 → 1.2.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.
- package/CHANGELOG.md +188 -0
- package/README.md +345 -53
- package/dist/actions.d.ts +46 -0
- package/dist/basemaps.d.ts +58 -0
- package/dist/controls.d.ts +23 -7
- package/dist/index.d.ts +30 -17
- package/dist/index.js +4048 -1661
- package/dist/index.js.map +1 -1
- package/dist/inspector/types.d.ts +4 -1
- package/dist/layers/legend.d.ts +25 -6
- package/dist/layers/move.d.ts +7 -1
- package/dist/layers/tree.d.ts +43 -13
- package/dist/maplibre-gl-draw-ui.js +5370 -3658
- package/dist/maplibre-gl-draw-ui.js.map +1 -1
- package/dist/messages.d.ts +30 -3
- package/dist/padding.d.ts +13 -10
- package/dist/style.css +1 -2
- package/dist/theme.d.ts +2 -0
- package/dist/types.d.ts +151 -13
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -23,8 +23,14 @@ npm install @sakuzu/maplibre-gl-draw @sakuzu/maplibre-gl-draw-ui maplibre-gl
|
|
|
23
23
|
|
|
24
24
|
`@sakuzu/maplibre-gl-draw` (2.x) and `maplibre-gl` are peer
|
|
25
25
|
dependencies: the application installs them, and the interface uses the
|
|
26
|
-
same copies as the application.
|
|
27
|
-
|
|
26
|
+
same copies as the application.
|
|
27
|
+
|
|
28
|
+
The package is used from any framework or none: React, Vue, Svelte,
|
|
29
|
+
another framework, or a plain page. It ships compiled JavaScript, and
|
|
30
|
+
the page needs no Svelte of its own. The interface is written in
|
|
31
|
+
Svelte, but Svelte is inside the package: its components are compiled
|
|
32
|
+
before they are published, the Svelte runtime they use comes as a
|
|
33
|
+
dependency of the package, and kata is compiled into it.
|
|
28
34
|
|
|
29
35
|
The package loads in two ways.
|
|
30
36
|
|
|
@@ -33,33 +39,156 @@ The package loads in two ways.
|
|
|
33
39
|
`@sakuzu/maplibre-gl-draw-ui/style.css` is the style sheet.
|
|
34
40
|
- Without a bundler: `dist/maplibre-gl-draw-ui.js` is one module with
|
|
35
41
|
Svelte and kata in it. It loads with `<script type="module">` and an
|
|
36
|
-
import map that names `@sakuzu/maplibre-gl-draw
|
|
37
|
-
together with
|
|
42
|
+
import map that names `@sakuzu/maplibre-gl-draw`,
|
|
43
|
+
`@sakuzu/maplibre-gl-draw/geometry` and `maplibre-gl`, together with
|
|
44
|
+
`dist/style.css`.
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"imports": {
|
|
45
|
-
"maplibre-gl": "/vendor/maplibre-gl.mjs",
|
|
46
|
-
"@sakuzu/maplibre-gl-draw": "/vendor/maplibre-gl-draw.js"
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
</script>
|
|
50
|
-
<script type="module">
|
|
51
|
-
import { Map } from 'maplibre-gl';
|
|
52
|
-
import { createDraw } from '@sakuzu/maplibre-gl-draw';
|
|
53
|
-
import { createDrawUI } from '/vendor/maplibre-gl-draw-ui/maplibre-gl-draw-ui.js';
|
|
46
|
+
In every case the interface is created after the draw instance and
|
|
47
|
+
destroyed before it: `createDrawUI(draw)` after `createDraw(map)`, and
|
|
48
|
+
`ui.destroy()`, then `draw.destroy()`, then `map.remove()`.
|
|
49
|
+
|
|
50
|
+
### From React
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
```tsx
|
|
53
|
+
import { useEffect, useRef } from 'react';
|
|
54
|
+
import * as maplibregl from 'maplibre-gl';
|
|
55
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
56
|
+
import { createDraw } from '@sakuzu/maplibre-gl-draw';
|
|
57
|
+
import { createDrawUI } from '@sakuzu/maplibre-gl-draw-ui';
|
|
58
|
+
import '@sakuzu/maplibre-gl-draw-ui/style.css';
|
|
59
|
+
|
|
60
|
+
const style = 'https://tiles.openfreemap.org/styles/liberty';
|
|
61
|
+
|
|
62
|
+
export function DrawMap() {
|
|
63
|
+
const container = useRef<HTMLDivElement>(null);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!container.current) return;
|
|
67
|
+
const map = new maplibregl.Map({ container: container.current, style });
|
|
68
|
+
const draw = createDraw(map);
|
|
69
|
+
const ui = createDrawUI(draw);
|
|
70
|
+
return () => {
|
|
71
|
+
ui.destroy();
|
|
72
|
+
draw.destroy();
|
|
73
|
+
map.remove();
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
return <div ref={container} style={{ height: 400 }} />;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The interface lies over the map's container, so the component renders
|
|
82
|
+
the container alone. In development, Strict Mode runs the effect twice;
|
|
83
|
+
the cleanup destroys the first set, so this is safe.
|
|
84
|
+
|
|
85
|
+
### From Vue
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<script setup lang="ts">
|
|
89
|
+
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
|
90
|
+
import * as maplibregl from 'maplibre-gl';
|
|
91
|
+
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
92
|
+
import { createDraw, type Draw } from '@sakuzu/maplibre-gl-draw';
|
|
93
|
+
import { createDrawUI, type DrawUI } from '@sakuzu/maplibre-gl-draw-ui';
|
|
94
|
+
import '@sakuzu/maplibre-gl-draw-ui/style.css';
|
|
95
|
+
|
|
96
|
+
const style = 'https://tiles.openfreemap.org/styles/liberty';
|
|
97
|
+
const container = ref<HTMLDivElement>();
|
|
98
|
+
let map: maplibregl.Map | undefined;
|
|
99
|
+
let draw: Draw | undefined;
|
|
100
|
+
let ui: DrawUI | undefined;
|
|
101
|
+
|
|
102
|
+
onMounted(() => {
|
|
103
|
+
map = new maplibregl.Map({ container: container.value!, style });
|
|
104
|
+
draw = createDraw(map);
|
|
105
|
+
ui = createDrawUI(draw);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
onBeforeUnmount(() => {
|
|
109
|
+
ui?.destroy();
|
|
110
|
+
draw?.destroy();
|
|
111
|
+
map?.remove();
|
|
112
|
+
});
|
|
57
113
|
</script>
|
|
114
|
+
|
|
115
|
+
<template>
|
|
116
|
+
<div ref="container" style="height: 400px"></div>
|
|
117
|
+
</template>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`map`, `draw` and `ui` are plain variables, not `ref`s, which would wrap
|
|
121
|
+
them in proxies. The guide to
|
|
122
|
+
[using core with a framework](https://sakuzu.github.io/maplibre-gl-draw/guides/frameworks.html)
|
|
123
|
+
shows the same patterns for Svelte, and how to keep the map out of
|
|
124
|
+
server-side rendering. Under some bundlers, maplibre-gl 6 needs its
|
|
125
|
+
worker URL set once per page, as the
|
|
126
|
+
[README of core](https://github.com/sakuzu/maplibre-gl-draw#usage) shows
|
|
127
|
+
for Vite.
|
|
128
|
+
|
|
129
|
+
### Without a bundler
|
|
130
|
+
|
|
131
|
+
A plain HTML page loads every module from a CDN through an import map.
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<!doctype html>
|
|
135
|
+
<html lang="en">
|
|
136
|
+
<head>
|
|
137
|
+
<meta charset="utf-8">
|
|
138
|
+
<title>Draw</title>
|
|
139
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.11.1/dist/maplibre-gl.css">
|
|
140
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@sakuzu/maplibre-gl-draw-ui@1/dist/style.css">
|
|
141
|
+
<script type="importmap">
|
|
142
|
+
{
|
|
143
|
+
"imports": {
|
|
144
|
+
"maplibre-gl": "https://cdn.jsdelivr.net/npm/maplibre-gl@6.11.1/dist/maplibre-gl.mjs",
|
|
145
|
+
"@sakuzu/maplibre-gl-draw": "https://esm.sh/@sakuzu/maplibre-gl-draw@2?external=maplibre-gl",
|
|
146
|
+
"@sakuzu/maplibre-gl-draw/geometry": "https://esm.sh/@sakuzu/maplibre-gl-draw@2/geometry",
|
|
147
|
+
"@sakuzu/maplibre-gl-draw-ui": "https://cdn.jsdelivr.net/npm/@sakuzu/maplibre-gl-draw-ui@1/dist/maplibre-gl-draw-ui.js"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
</script>
|
|
151
|
+
<style>
|
|
152
|
+
html, body, #map { height: 100%; margin: 0; }
|
|
153
|
+
</style>
|
|
154
|
+
</head>
|
|
155
|
+
<body>
|
|
156
|
+
<div id="map"></div>
|
|
157
|
+
<script type="module">
|
|
158
|
+
import * as maplibregl from 'maplibre-gl';
|
|
159
|
+
import { createDraw } from '@sakuzu/maplibre-gl-draw';
|
|
160
|
+
import { createDrawUI } from '@sakuzu/maplibre-gl-draw-ui';
|
|
161
|
+
|
|
162
|
+
const map = new maplibregl.Map({
|
|
163
|
+
container: 'map',
|
|
164
|
+
style: 'https://tiles.openfreemap.org/styles/liberty',
|
|
165
|
+
center: [139.767, 35.681],
|
|
166
|
+
zoom: 14,
|
|
167
|
+
});
|
|
168
|
+
createDrawUI(createDraw(map));
|
|
169
|
+
</script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>
|
|
58
172
|
```
|
|
59
173
|
|
|
60
|
-
The
|
|
61
|
-
|
|
62
|
-
|
|
174
|
+
The import map names four modules.
|
|
175
|
+
|
|
176
|
+
- `maplibre-gl` is maplibre-gl's own ES module build, at a version that
|
|
177
|
+
the peer of core accepts (`~6.11.1`). It starts its worker from the
|
|
178
|
+
same address by itself.
|
|
179
|
+
- `@sakuzu/maplibre-gl-draw` comes from esm.sh, which puts core's own
|
|
180
|
+
dependencies in it. `?external=maplibre-gl` leaves maplibre-gl to the
|
|
181
|
+
import map, so the page and the library share one copy.
|
|
182
|
+
- `@sakuzu/maplibre-gl-draw/geometry` is imported by the interface for
|
|
183
|
+
its measurements.
|
|
184
|
+
- `@sakuzu/maplibre-gl-draw-ui` is the single-file build of this
|
|
185
|
+
package, with Svelte and kata in it.
|
|
186
|
+
|
|
187
|
+
The same page works with files served by the application: put the
|
|
188
|
+
addresses of its own copies (maplibre-gl's `dist/maplibre-gl.mjs`, this
|
|
189
|
+
package's `dist/`, and a build of core with its dependencies in it that
|
|
190
|
+
imports `maplibre-gl` by that name) in the import map and the two
|
|
191
|
+
`<link>` elements. Pin exact versions in production.
|
|
63
192
|
|
|
64
193
|
## Use
|
|
65
194
|
|
|
@@ -85,8 +214,8 @@ The options of `createDrawUI`, all optional:
|
|
|
85
214
|
| --- | --- | --- |
|
|
86
215
|
| `container` | The positioned element it lies over | the map's container |
|
|
87
216
|
| `toolbar` | `false`, or the `tools`, `delete` and `snapping` | `true` |
|
|
88
|
-
| `inspector` | `false`, or
|
|
89
|
-
| `layers` | `false`, or
|
|
217
|
+
| `inspector` | `false`, or `tabs` (the first opens) and `operations` | `true` |
|
|
218
|
+
| `layers` | `false`, or `features`, `datasets`, `add`, `reorder` | `true` |
|
|
90
219
|
| `legend` | The legend beside the layer panel | `true` |
|
|
91
220
|
| `locale` | `en`, `ja`, or words laid over English | `en` |
|
|
92
221
|
| `theme` | `light`, `dark` or `auto` (follows the system) | `auto` |
|
|
@@ -96,6 +225,12 @@ The options of `createDrawUI`, all optional:
|
|
|
96
225
|
| `side` | The side panels `floating` over the map or `beside` it | `floating` |
|
|
97
226
|
| `themeToggle` | The button that switches the look | `true` |
|
|
98
227
|
| `mapControls` | maplibre-gl's globe, compass, zoom and scale | `true` |
|
|
228
|
+
| `basemaps` | The basemaps the basemap row opens | none |
|
|
229
|
+
| `basemap` | The ID of the basemap current at the start | the map's |
|
|
230
|
+
| `onbasemap` | Called with the basemap after it changed | none |
|
|
231
|
+
| `actions` | The actions of the application, in a card at the left | none |
|
|
232
|
+
| `actionsTitle` | The title of the card of the actions | `Actions` |
|
|
233
|
+
| `actionsOpen` | Whether the card of the actions starts unfolded | `true` |
|
|
99
234
|
|
|
100
235
|
Each part also goes alone into an element of the page, with its own
|
|
101
236
|
options and `target`, `locale` and `theme`: `createToolbar`,
|
|
@@ -103,26 +238,58 @@ options and `target`, `locale` and `theme`: `createToolbar`,
|
|
|
103
238
|
|
|
104
239
|
The interface is laid over the map's container. The toolbar alone goes
|
|
105
240
|
into any positioned element with `createToolbar(draw, { target })`.
|
|
241
|
+
The magnet at the end of the toolbar is pressed while snapping is on,
|
|
242
|
+
and opens the snapping settings above the toolbar: snapping itself and,
|
|
243
|
+
under it, the kinds of target (vertices, edges, intersections and
|
|
244
|
+
guides), then snapping to datasets, tracing edges and moving shared
|
|
245
|
+
vertices together, with the key that pauses snapping while it is held.
|
|
246
|
+
Each switch writes `draw.options.update`, and the settings follow
|
|
247
|
+
`options.changed`, so a change made by code shows too.
|
|
106
248
|
The side panels float over the map, gap-md from its edges and as tall
|
|
107
249
|
as their content; `side: 'beside'` docks them beside the map on a wide
|
|
108
|
-
one instead. The map's padding follows the interface
|
|
109
|
-
panel
|
|
110
|
-
|
|
111
|
-
|
|
250
|
+
one instead. The map's padding follows the interface: at the left, the
|
|
251
|
+
room the left panel takes (beside the map, or floating over it with its
|
|
252
|
+
gap), and at the bottom, the height of the sheets of a narrow map, as
|
|
253
|
+
kata's Shell reports them (`onlayout`'s inset), so that `fitBounds` and
|
|
254
|
+
`easeTo` keep clear of them. The right panel opens and closes with the
|
|
255
|
+
selection and leaves the padding alone, so that the view does not jump;
|
|
256
|
+
the controls of the bottom right move out of its way instead (see
|
|
257
|
+
[Map controls](#map-controls)). `padding: false` leaves the map's
|
|
258
|
+
padding alone.
|
|
112
259
|
|
|
113
260
|
On the left, the layer panel and the legend share a panel, in two tabs.
|
|
114
|
-
The layer panel
|
|
115
|
-
|
|
116
|
-
|
|
261
|
+
The layer panel has two sections, Stack and Basemap. Stack is the tree
|
|
262
|
+
of the layers, their groups and their features, from the front, with
|
|
263
|
+
the eye, the lock, reordering by dragging and an add menu (a new layer,
|
|
117
264
|
a new group from the selected features). A feature is named by its
|
|
118
|
-
`properties.name`, or by its type when it has none
|
|
119
|
-
the
|
|
265
|
+
`properties.name`, or by its type when it has none; names are changed
|
|
266
|
+
in the head of the inspector, not in the tree. Each row costs its
|
|
267
|
+
drawing, so a layer lists up to 1,000 features, those of its groups
|
|
268
|
+
included: a layer that holds more lists none of them and shows their
|
|
269
|
+
number instead, with a hint to select them on the map, and its own row
|
|
270
|
+
works as before (the eye, the lock, the active layer). `features` sets
|
|
271
|
+
the limit as a number, and `false` lists no features, only the groups.
|
|
272
|
+
The datasets (`draw.datasets`) are rows of the stack too, in their place
|
|
273
|
+
among the layers: those of `above-store` in front of every layer, those
|
|
274
|
+
of `layer-order` where `layers.getOrder()` places them, and those of
|
|
275
|
+
`below-store` behind every layer. A dataset row shows its ID (core gives
|
|
276
|
+
a dataset no name), with the eye (`setVisible`) and no lock; a press on
|
|
277
|
+
it leaves the selection as it is. Every dataset is dragged among the
|
|
278
|
+
layers: one of `above-store` or `below-store` dropped there is moved to
|
|
279
|
+
`layer-order` (`draw.datasets.move`) and placed where it was dropped
|
|
280
|
+
(`draw.layers.reorder`), and goes back to its own order when the reorder
|
|
281
|
+
is refused. Basemap, under it, is the back of the stack (see
|
|
282
|
+
[Basemaps](#basemaps)). The legend shows the rows of the style rule
|
|
283
|
+
(`styleRule`) of each layer and of each dataset that has one, in the
|
|
284
|
+
order of the stack.
|
|
120
285
|
Shift+L opens and closes the panel, and while it is closed a button at
|
|
121
286
|
the top left of the map opens it again.
|
|
122
287
|
|
|
123
288
|
```ts
|
|
124
289
|
const ui = createDrawUI(draw, {
|
|
125
|
-
|
|
290
|
+
// or false for none; features: true lists up to 1,000 in a layer, a number
|
|
291
|
+
// sets that limit, and false lists none
|
|
292
|
+
layers: { features: true, datasets: true, add: true, reorder: true },
|
|
126
293
|
legend: true,
|
|
127
294
|
});
|
|
128
295
|
```
|
|
@@ -146,15 +313,18 @@ ui.tools.add({
|
|
|
146
313
|
|
|
147
314
|
The inspector is the panel on the right of `createDrawUI`. It opens
|
|
148
315
|
while something is selected and closes, clearing the selection, with its
|
|
149
|
-
close button.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
attributes.
|
|
316
|
+
close button. On a narrow map it is a sheet from the bottom, as wide as
|
|
317
|
+
the map. For one feature it shows its name (`properties.name`, changed
|
|
318
|
+
where it stands); under it, its measurements, and the tabs, which stay
|
|
319
|
+
while the content scrolls: a Style tab with the fields its type reads
|
|
320
|
+
and an Attributes tab with its other attributes. The content of each tab
|
|
321
|
+
starts with its description (`properties.description`, changed where it
|
|
322
|
+
stands). Several features
|
|
154
323
|
show the fields they share, a field whose values differ being mixed, and
|
|
155
324
|
the operations that apply to them (union, intersection, difference,
|
|
156
325
|
split and buffer). A layer and a group show their name, whether they are
|
|
157
|
-
visible and whether they are locked.
|
|
326
|
+
visible and whether they are locked. Delete is at the start of the foot
|
|
327
|
+
of every inspector, for one feature and for several things selected.
|
|
158
328
|
|
|
159
329
|
```ts
|
|
160
330
|
const ui = createDrawUI(draw, {
|
|
@@ -163,6 +333,9 @@ const ui = createDrawUI(draw, {
|
|
|
163
333
|
});
|
|
164
334
|
```
|
|
165
335
|
|
|
336
|
+
The tabs open on the first of `tabs`: `['attributes', 'style']` opens
|
|
337
|
+
on Attributes. A tab chosen stays open from one feature to the next.
|
|
338
|
+
|
|
166
339
|
A value typed into an attribute is kept as the string typed. The
|
|
167
340
|
inspector alone goes into any element with
|
|
168
341
|
`createInspector(draw, { target })`, and an application adds a section
|
|
@@ -191,15 +364,27 @@ ui.inspector?.sections.add({
|
|
|
191
364
|
## Customize
|
|
192
365
|
|
|
193
366
|
Everything the interface draws is inside its root element, which has the
|
|
194
|
-
class `mgd-ui`, and its style sheet reaches nothing outside it
|
|
195
|
-
|
|
196
|
-
|
|
367
|
+
class `mgd-ui`, and its style sheet reaches nothing outside it but
|
|
368
|
+
maplibre-gl's controls of the map (see [Map controls](#map-controls)).
|
|
369
|
+
kata's tokens (the CSS custom properties `--kata-*`) are set on that
|
|
370
|
+
element; set them on `.mgd-ui` to change the look. The `theme` option of
|
|
197
371
|
`createDrawUI` and of each part put alone is `light`, `dark` or `auto`
|
|
198
372
|
(the default, which follows the system's `prefers-color-scheme` as it
|
|
199
373
|
changes), and `ui.setTheme` changes it. kata's theme is dark;
|
|
200
374
|
`data-color-mode="light"` on the root element, which `light` sets, or on
|
|
201
375
|
any element around it, turns it light.
|
|
202
376
|
|
|
377
|
+
maplibre-gl's controls in the map's container, those of the application
|
|
378
|
+
too, follow the theme of `createDrawUI`: while it is on the map,
|
|
379
|
+
maplibre-gl's control container (`.maplibregl-control-container`) has
|
|
380
|
+
`data-mgd-ui-controls`, which takes kata's tokens, and the
|
|
381
|
+
`data-color-mode` of the root. The groups of buttons, the lines between
|
|
382
|
+
them, the attribution and the scale are painted with the tokens, and
|
|
383
|
+
maplibre-gl's icons, which are dark images, are inverted in the dark
|
|
384
|
+
look (`filter: invert(1)`). Set the tokens on
|
|
385
|
+
`[data-mgd-ui-controls]` to change their look. `destroy()` removes both
|
|
386
|
+
attributes.
|
|
387
|
+
|
|
203
388
|
A button at the top right of the map switches the look: a sun while it
|
|
204
389
|
is dark, a moon while it is light. It sets the theme to the look that is
|
|
205
390
|
not shown, so from `auto` it keeps the one the system does not prefer;
|
|
@@ -209,8 +394,106 @@ open over the map or beside it, the button stands to the left of it.
|
|
|
209
394
|
|
|
210
395
|
Beyond the look, the options above choose the parts and what each shows,
|
|
211
396
|
`ui.tools.add` adds a tool for a mode of the application (see
|
|
212
|
-
[Use](#use)),
|
|
213
|
-
inspector (see [Inspector](#inspector)).
|
|
397
|
+
[Use](#use)), `ui.inspector.sections.add` adds a section to the
|
|
398
|
+
inspector (see [Inspector](#inspector)), and `ui.actions.add` adds an
|
|
399
|
+
action of the application (see [Actions](#actions)).
|
|
400
|
+
|
|
401
|
+
### Actions
|
|
402
|
+
|
|
403
|
+
An action of the application, such as saving the drawing or turning a
|
|
404
|
+
setting on and off, is a row of a card at the bottom left of the map,
|
|
405
|
+
above maplibre-gl's scale and, where its box reaches the card across, the
|
|
406
|
+
attribution: a switch (`kind: 'toggle'`) or a button
|
|
407
|
+
(`kind: 'action'`), with its key at the end. A press on the row and its
|
|
408
|
+
key both call `run`; a switch shows what `checked` returns, read again
|
|
409
|
+
after each run and on `ui.actions.refresh()`, so the state stays with
|
|
410
|
+
the application. `disabled` dims the row and turns its key off, and
|
|
411
|
+
`hint` is a caption under it. The keys are listed with `?` under the
|
|
412
|
+
title of the card, and do nothing while a field has the focus. A key the
|
|
413
|
+
interface uses (the keys of the tools, Delete, Backspace, Escape, `?`
|
|
414
|
+
and Shift+L) or another action uses is refused with an error. The card
|
|
415
|
+
shows while there is an action, its head folds it into one button, and
|
|
416
|
+
on a map narrower than 48rem it starts folded; where it would reach the
|
|
417
|
+
toolbar across, it stands above the toolbar, and the layer panel
|
|
418
|
+
floating at the left ends above it. The card is no taller than the map
|
|
419
|
+
above its place, less gap-md at the top and the button that opens the
|
|
420
|
+
layer panel again while it shows; the whole card scrolls when its rows
|
|
421
|
+
do not fit. Its title is `actionsTitle`, or the
|
|
422
|
+
word for actions of the locale.
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
const ui = createDrawUI(draw, {
|
|
426
|
+
actions: [
|
|
427
|
+
{
|
|
428
|
+
id: 'read-only',
|
|
429
|
+
label: 'Read-only',
|
|
430
|
+
kind: 'toggle',
|
|
431
|
+
shortcut: 'R',
|
|
432
|
+
run: () => draw.setReadOnly(!draw.isReadOnly()),
|
|
433
|
+
checked: () => draw.isReadOnly(),
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const remove = ui.actions.add({
|
|
439
|
+
id: 'save',
|
|
440
|
+
label: 'Save',
|
|
441
|
+
kind: 'action',
|
|
442
|
+
shortcut: 'S',
|
|
443
|
+
run: () => localStorage.setItem('drawing', JSON.stringify(draw.document.toJSON())),
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Basemaps
|
|
448
|
+
|
|
449
|
+
The last section of the layer panel, Basemap, is the back of the stack:
|
|
450
|
+
one row under the tree of the layers, apart from it as the basemap is no
|
|
451
|
+
layer, so it is not dragged, hidden, locked or selected. The row shows
|
|
452
|
+
the name of the basemap the map shows: the label of the current one of
|
|
453
|
+
`basemaps`, else the `name` of the map's style, read again on each
|
|
454
|
+
`style.load`, else the word for a basemap.
|
|
455
|
+
|
|
456
|
+
With two or more `basemaps`, pressing the row opens them on the right,
|
|
457
|
+
in the place of the inspector: a list of their labels, each with its
|
|
458
|
+
`preview` (a value of CSS `background`, such as a gradient in the colors
|
|
459
|
+
of the style; a neutral square without one), and a check on the current
|
|
460
|
+
one. Opening it clears the selection, and selecting a feature on the map
|
|
461
|
+
or in the tree closes it, as do its close button and Escape. Choosing a
|
|
462
|
+
basemap replaces the map's style with its `style` (a URL or a style
|
|
463
|
+
object) and calls `onbasemap`, and the list stays open.
|
|
464
|
+
`ui.setBasemap(id)` does the same, and `ui.getBasemap()` returns the
|
|
465
|
+
current one. The current one at the start is `basemap`, or else the
|
|
466
|
+
first whose `style` is the URL the map's style was loaded from. With
|
|
467
|
+
fewer, the row only shows the name.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
const styles = 'https://tiles.openfreemap.org/styles';
|
|
471
|
+
const ui = createDrawUI(draw, {
|
|
472
|
+
basemaps: [
|
|
473
|
+
{
|
|
474
|
+
id: 'bright',
|
|
475
|
+
label: 'Bright',
|
|
476
|
+
style: `${styles}/bright`,
|
|
477
|
+
preview: 'linear-gradient(135deg, #f4f1ea, #dfe7d5)',
|
|
478
|
+
},
|
|
479
|
+
{ id: 'dark', label: 'Dark', style: `${styles}/dark` },
|
|
480
|
+
],
|
|
481
|
+
onbasemap: (basemap) => console.log(basemap.id),
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
The layer panel put alone takes the same options
|
|
486
|
+
(`LayerPanelOptions`):
|
|
487
|
+
`createLayerPanel(draw, { target, basemaps, basemap, onbasemap })`.
|
|
488
|
+
There, the row opens the list in the place of the panel's sections,
|
|
489
|
+
until its close button or Escape closes it.
|
|
490
|
+
|
|
491
|
+
The drawing stays: choosing a basemap calls
|
|
492
|
+
`map.setStyle(style, { diff: false })`, which replaces the style whole,
|
|
493
|
+
and the draw instance adds its layers again on top of the new style once
|
|
494
|
+
it has loaded. Sources, layers and the terrain that the application
|
|
495
|
+
added to the map itself go with the old style, so it adds them again on
|
|
496
|
+
the map's `style.load` event.
|
|
214
497
|
|
|
215
498
|
## Map controls
|
|
216
499
|
|
|
@@ -228,12 +511,21 @@ const ui = createDrawUI(draw, {
|
|
|
228
511
|
});
|
|
229
512
|
```
|
|
230
513
|
|
|
231
|
-
A page that adds controls of its own passes `mapControls: false`.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
514
|
+
A page that adds controls of its own passes `mapControls: false`. The
|
|
515
|
+
bottom corners of the map are kept clear of the interface:
|
|
516
|
+
|
|
517
|
+
- where the toolbar reaches a bottom corner across, as on a narrow map,
|
|
518
|
+
that corner (its controls and the attribution) is lifted above the
|
|
519
|
+
toolbar
|
|
520
|
+
- while the right panel covers the bottom right of the map, the controls
|
|
521
|
+
there (not the attribution) move to the left of it, gap-md apart
|
|
522
|
+
- where the attribution's box reaches the scale across, as the two-line
|
|
523
|
+
attribution of a narrow map does, the bottom left corner is lifted
|
|
524
|
+
above it. The attribution is only read; its compact form is
|
|
525
|
+
maplibre-gl's own
|
|
526
|
+
|
|
527
|
+
These are the rules of the style sheet outside the root element: they
|
|
528
|
+
apply to the map's container while the interface is on it.
|
|
237
529
|
|
|
238
530
|
## Development
|
|
239
531
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Shortcut } from '@sakuzu/kata/svelte';
|
|
2
|
+
import type { Messages } from './messages.js';
|
|
3
|
+
import { Box } from './store.js';
|
|
4
|
+
import type { ActionSpec, ActionsHandle, ToolEntry } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* A key written one way: the modifiers in a fixed order, then the key, all in lowercase, so that
|
|
7
|
+
* `Shift+R`, `shift+r` and `r+shift` compare equal
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeKey(shortcut: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Checks an action of the application
|
|
12
|
+
*
|
|
13
|
+
* @throws Error when a field is missing or has the wrong type
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkAction(spec: ActionSpec): void;
|
|
16
|
+
/**
|
|
17
|
+
* Checks that the key of an action is free
|
|
18
|
+
*
|
|
19
|
+
* @throws Error when it is a key of the interface, of a tool or of another action
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkActionKey(spec: ActionSpec, tools: readonly ToolEntry[], messages: Messages, actions: readonly ActionSpec[]): void;
|
|
22
|
+
/** The actions of the card, and the count of their changes that the rows follow */
|
|
23
|
+
export interface ActionsState {
|
|
24
|
+
/** The actions in the order of the card */
|
|
25
|
+
readonly list: Box<ActionSpec[]>;
|
|
26
|
+
/** Changed after each run and on `refresh()`, so that the rows read `checked` and `disabled` */
|
|
27
|
+
readonly version: Box<number>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The actions of the options, checked
|
|
31
|
+
*
|
|
32
|
+
* @throws Error when an action is not valid, two share an ID, or a key is taken
|
|
33
|
+
*/
|
|
34
|
+
export declare function actionsState(actions: readonly ActionSpec[] | undefined, tools: readonly ToolEntry[], messages: Messages): ActionsState;
|
|
35
|
+
/** Runs an action, unless it is disabled, and has the rows read their state again */
|
|
36
|
+
export declare function runAction(state: ActionsState, spec: ActionSpec): void;
|
|
37
|
+
/** The actions of the card, to add to and to remove from */
|
|
38
|
+
export declare function actionsHandle(state: ActionsState, tools: Box<ToolEntry[]>, messages: Box<Messages>): ActionsHandle;
|
|
39
|
+
/** The keys of the actions, as shortcuts of kata's Shell under the title of the card */
|
|
40
|
+
export declare function actionShortcuts(state: ActionsState, group: string): Shortcut[];
|
|
41
|
+
/**
|
|
42
|
+
* Checks that the key of a tool is not the key of an action
|
|
43
|
+
*
|
|
44
|
+
* @throws Error when an action has the key
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkToolKey(id: string, shortcut: string | undefined, actions: readonly ActionSpec[]): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Box } from './store.js';
|
|
2
|
+
import type { Basemap } from './types.js';
|
|
3
|
+
/** The members of the map the basemaps use */
|
|
4
|
+
export interface BasemapMap {
|
|
5
|
+
setStyle(style: Basemap['style'], options?: {
|
|
6
|
+
diff?: boolean;
|
|
7
|
+
}): unknown;
|
|
8
|
+
/** The URL the style was loaded from (maplibre-gl 6), to find the current basemap */
|
|
9
|
+
getStyleUrl?(): string | null;
|
|
10
|
+
/** The style as it is now, whose name names a basemap that is none of the list */
|
|
11
|
+
getStyle?(): {
|
|
12
|
+
name?: string;
|
|
13
|
+
} | undefined;
|
|
14
|
+
/** Follows the load of each new style, to read its name again */
|
|
15
|
+
on?(type: 'style.load', listener: () => void): unknown;
|
|
16
|
+
off?(type: 'style.load', listener: () => void): unknown;
|
|
17
|
+
}
|
|
18
|
+
/** The basemaps of the options, checked, and the ID of the current one */
|
|
19
|
+
export interface BasemapSettings {
|
|
20
|
+
list: readonly Basemap[];
|
|
21
|
+
current: string | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Checks the basemaps of the options and finds the current one: `initial` when given, else the
|
|
25
|
+
* first whose style is the URL of the map's style, else none.
|
|
26
|
+
*
|
|
27
|
+
* @throws Error when a basemap has no ID, no label or no style, has a preview that is not a
|
|
28
|
+
* string, two share an ID, or `initial` is the ID of none of them
|
|
29
|
+
*/
|
|
30
|
+
export declare function basemapSettings(basemaps: readonly Basemap[] | undefined, initial: string | undefined, styleUrl: string | null): BasemapSettings;
|
|
31
|
+
/** The basemaps on a map: the list, the current one, its name, and the change */
|
|
32
|
+
export interface BasemapControl {
|
|
33
|
+
/** The basemaps, in the order they are offered */
|
|
34
|
+
readonly list: readonly Basemap[];
|
|
35
|
+
/** The ID of the current basemap, which the row and the basemaps to choose from follow */
|
|
36
|
+
readonly current: Box<string | null>;
|
|
37
|
+
/**
|
|
38
|
+
* The name of the basemap the map shows: the label of the current basemap, else the `name` of
|
|
39
|
+
* the map's style, else null. Read inside a component, it is followed
|
|
40
|
+
*/
|
|
41
|
+
name(): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Replaces the map's style with the style of a basemap and calls `onchange` with it; nothing
|
|
44
|
+
* happens when it is current already
|
|
45
|
+
*
|
|
46
|
+
* @throws Error when no basemap has this ID
|
|
47
|
+
*/
|
|
48
|
+
set(id: string): void;
|
|
49
|
+
/** The current basemap, or null */
|
|
50
|
+
get(): Basemap | null;
|
|
51
|
+
/** Stops following the map's style */
|
|
52
|
+
destroy(): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Keeps the current basemap of a map and changes its style. The name of the map's style is read
|
|
56
|
+
* now and again on each `style.load`
|
|
57
|
+
*/
|
|
58
|
+
export declare function basemapControl(map: BasemapMap, settings: BasemapSettings, onchange?: (basemap: Basemap) => void): BasemapControl;
|
package/dist/controls.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ControlPosition, type IControl } from 'maplibre-gl';
|
|
2
|
+
import type { ShellInset } from './padding.js';
|
|
2
3
|
import type { MapControlsOptions } from './types.js';
|
|
3
4
|
/** The members of the map the controls use */
|
|
4
5
|
export interface ControlsMap {
|
|
@@ -20,17 +21,32 @@ export interface MapControls {
|
|
|
20
21
|
export declare function mapControls(map: ControlsMap, option: boolean | MapControlsOptions | undefined): MapControls;
|
|
21
22
|
/** The attribute on the map's container while the interface lifts its bottom corners */
|
|
22
23
|
export declare const LIFT_ATTRIBUTE = "data-mgd-ui-lift";
|
|
23
|
-
/** The bottom corners of the map,
|
|
24
|
+
/** The bottom corners of the map, kept clear of the interface and of the attribution */
|
|
24
25
|
export interface CornerLift {
|
|
25
|
-
/**
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Measures again after the toolbar came, went or changed, or the regions of the shell moved;
|
|
28
|
+
* with the inset the shell reported, which is kept until the next one
|
|
29
|
+
*/
|
|
30
|
+
update(inset?: ShellInset): void;
|
|
27
31
|
/** Stops following and puts the corners back */
|
|
28
32
|
destroy(): void;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
+
* Keeps the bottom corners of maplibre-gl's controls in `container` (the map's container) clear
|
|
36
|
+
* of the interface in `root` and of the attribution. root.css moves them by the custom properties
|
|
37
|
+
* this sets on the container:
|
|
38
|
+
*
|
|
39
|
+
* - --mgd-ui-lift-left and --mgd-ui-lift-right lift a corner above the toolbar while the toolbar
|
|
40
|
+
* reaches it across, by the distance from the top of the toolbar to the bottom of the map; the
|
|
41
|
+
* controls of the bottom right moved to the left (below) count as reaching it where they are
|
|
42
|
+
* moved to
|
|
43
|
+
* - --mgd-ui-shift-right moves the controls of the bottom right, not the attribution, to the left
|
|
44
|
+
* while the right region covers the stage (the inset's right) and they are under it up and
|
|
45
|
+
* down: by the inset and gap-md
|
|
46
|
+
* - --mgd-ui-lift-left also lifts the bottom left corner above the attribution's box while that
|
|
47
|
+
* box reaches the corner across
|
|
48
|
+
*
|
|
49
|
+
* The boxes are measured where maplibre-gl places them, without what this moved. The attribution
|
|
50
|
+
* and the right region are followed with a ResizeObserver, as the toolbar and the corners are.
|
|
35
51
|
*/
|
|
36
52
|
export declare function cornerLift(container: HTMLElement, root: HTMLElement): CornerLift;
|