@sakuzu/maplibre-gl-draw-ui 1.0.0 → 1.1.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 +132 -0
- package/README.md +291 -34
- package/dist/actions.d.ts +46 -0
- package/dist/basemaps.d.ts +58 -0
- package/dist/index.d.ts +28 -16
- package/dist/index.js +2467 -960
- 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/tree.d.ts +42 -12
- package/dist/maplibre-gl-draw-ui.js +5230 -4087
- package/dist/maplibre-gl-draw-ui.js.map +1 -1
- package/dist/messages.d.ts +30 -3
- package/dist/style.css +1 -1
- package/dist/types.d.ts +146 -9
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,138 @@ follows semantic versioning.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.1.0] - 2026-10-01
|
|
11
|
+
|
|
12
|
+
The basemaps as the back of the stack in the layer panel, the datasets
|
|
13
|
+
as rows of the stack, a limit on the features the panel lists, the
|
|
14
|
+
snapping settings on the magnet, the actions of the application in a
|
|
15
|
+
card, the inspector on the first tab of its options, and the layer panel
|
|
16
|
+
without a pencil, in-place renaming and row counts. The word
|
|
17
|
+
`activeLayer` leaves the `Messages` type (a locale that still sets it is
|
|
18
|
+
ignored at runtime).
|
|
19
|
+
|
|
20
|
+
### Added in 1.1.0
|
|
21
|
+
|
|
22
|
+
- The datasets are rows of the layer panel, in their place in
|
|
23
|
+
the stack: those of `above-store` in front of every layer, those of
|
|
24
|
+
`layer-order` where `layers.getOrder()` places them among the layers,
|
|
25
|
+
and those of `below-store` behind every layer. A row shows the ID of
|
|
26
|
+
the dataset with a database mark, and the eye shows and hides it with
|
|
27
|
+
`setVisible`. It has no lock, and a press
|
|
28
|
+
on it leaves the selection as it is. A
|
|
29
|
+
`layer-order` dataset is dragged among the layers (`layers.reorder`
|
|
30
|
+
with its ID); the others stay. The rows follow `dataset.added`,
|
|
31
|
+
`dataset.removed`, `dataset.reordered` and the `changed` event of each
|
|
32
|
+
dataset. The option `datasets: false` (`LayerPanelOptions`) leaves
|
|
33
|
+
them out. New word: `datasets`.
|
|
34
|
+
- The basemaps of the layer panel, `basemaps` (`{ id, label,
|
|
35
|
+
style, preview }`), `basemap` and `onbasemap`, in the options of
|
|
36
|
+
`createDrawUI` and of `createLayerPanel` (`LayerPanelOptions`). With
|
|
37
|
+
two or more, the basemap row opens them on the right, in the place of
|
|
38
|
+
the inspector: a list of their labels, each with its `preview` (a
|
|
39
|
+
value of CSS `background`; a neutral square without one) and a check
|
|
40
|
+
on the current one. Opening it clears the selection; a selection made
|
|
41
|
+
on the map or in the tree, its close button and Escape close it.
|
|
42
|
+
Choosing one replaces the map's style, calls `onbasemap` and leaves
|
|
43
|
+
the list open; `basemap` names the current one at the start, and
|
|
44
|
+
`ui.setBasemap(id)` and `ui.getBasemap()` change and read it. The
|
|
45
|
+
drawing is drawn again on top of the new style. The layer panel put
|
|
46
|
+
alone opens the list in the place of its sections.
|
|
47
|
+
- The legend lists the style rules of the datasets as well as
|
|
48
|
+
those of the layers, in the order of the stack: a block for each
|
|
49
|
+
dataset with a rule (`Dataset.getStyleRule()` of core), titled with
|
|
50
|
+
its ID, with the rows `deriveLegend` gives and swatches shaped after
|
|
51
|
+
the types of its first rows. It follows `dataset.added`,
|
|
52
|
+
`dataset.removed`, `dataset.reordered` and the `changed` event of each
|
|
53
|
+
dataset (the reasons `style` and `rows`). The word `noLegend` now says
|
|
54
|
+
that no layer or dataset has a style rule. It needs core with
|
|
55
|
+
`Dataset.getStyleRule()`.
|
|
56
|
+
- The actions of the application, rows of a card at the bottom
|
|
57
|
+
left of the map, above maplibre-gl's scale: `ui.actions` (`add`,
|
|
58
|
+
`remove`, `list`, `refresh`) and the options `actions` and
|
|
59
|
+
`actionsTitle` of `createDrawUI`. An action (`ActionSpec`) is a switch
|
|
60
|
+
(`kind: 'toggle'`), which shows what `checked` returns, or a button
|
|
61
|
+
(`kind: 'action'`), and its `shortcut` is a key of the interface,
|
|
62
|
+
listed with `?` under the title of the card. `run` is called by a
|
|
63
|
+
press and by the key; `checked` and `disabled` are read again after
|
|
64
|
+
each run and on `refresh()`, and `hint` is a caption under the row. A
|
|
65
|
+
key the interface or another action uses is refused with an error, and
|
|
66
|
+
`tools.add` refuses the key of an action. The card folds into one
|
|
67
|
+
button from its head, starts folded on a map narrower than 48rem, and
|
|
68
|
+
stands above the toolbar where it would reach it across; the layer
|
|
69
|
+
panel floating at the left ends above it. New word: `actions`.
|
|
70
|
+
`actionsOpen: false` starts the card folded.
|
|
71
|
+
|
|
72
|
+
### Changed in 1.1.0
|
|
73
|
+
|
|
74
|
+
- The layer panel lists up to 1,000 features in a layer, those
|
|
75
|
+
of its groups included. A layer that holds more lists none of them and
|
|
76
|
+
none of its groups: its one child is a row with their number and a
|
|
77
|
+
hint, "12,345 features. Select them on the map.", which is not
|
|
78
|
+
pressed, hidden, locked or dragged. The row of the layer works as
|
|
79
|
+
before. Each row of the panel costs about a third of a millisecond to
|
|
80
|
+
draw, so a layer of 20,000 features took seconds. `features`
|
|
81
|
+
(`LayerPanelOptions`) is now `boolean | number`: `true` (the default)
|
|
82
|
+
for the limit of 1,000, a number for another limit, `false` for no
|
|
83
|
+
features; a number less than 0 throws. New word: `manyFeatures` (with
|
|
84
|
+
`{count}`).
|
|
85
|
+
- The layer panel is a stack of two sections, as in the
|
|
86
|
+
reference layout. The first, Stack (the new word `stack`), is the
|
|
87
|
+
tree with the add menu in its head; the second, Basemap, holds one
|
|
88
|
+
row with a globe mark and the name of the basemap the map shows: the
|
|
89
|
+
label of the current one of `basemaps`, else the `name` of the map's
|
|
90
|
+
style, else the word `basemap`. The panel lists the stack from the
|
|
91
|
+
front, and the basemap is its back. The row is not a node of the tree:
|
|
92
|
+
it is not dragged, hidden, locked or selected. Before the release, the
|
|
93
|
+
menu of the basemaps was a button at the top right of the map, beside
|
|
94
|
+
the theme button, and then a menu under the tree; both are gone.
|
|
95
|
+
- The inspector of a feature opens on the first tab of
|
|
96
|
+
`inspector.tabs`, so `['attributes', 'style']` opens on Attributes.
|
|
97
|
+
The default stays `['style', 'attributes']`, which opens on Style. Once
|
|
98
|
+
a tab is chosen, it is kept from one feature to the next as before.
|
|
99
|
+
- The magnet of the toolbar opens the snapping settings above
|
|
100
|
+
the toolbar instead of switching snapping: snapping, the kinds of
|
|
101
|
+
target under it (vertices, edges, intersections and guides, off while
|
|
102
|
+
snapping is off), snapping to datasets, tracing edges and moving shared
|
|
103
|
+
vertices together, and the key that pauses snapping. Each switch writes
|
|
104
|
+
`draw.options.update` and follows `options.changed`. The magnet stays
|
|
105
|
+
pressed while snapping is on. New words: `snapVertex`, `snapEdge`,
|
|
106
|
+
`snapIntersection`, `snapGuide`, `snapDatasets`, `traceEdges`,
|
|
107
|
+
`sharedVertexDrag` and `snapPauseKey` (with `{key}`).
|
|
108
|
+
|
|
109
|
+
### Removed in 1.1.0
|
|
110
|
+
|
|
111
|
+
- Renaming in place in the layer panel (F2 or a double click on
|
|
112
|
+
a row), as in the reference layout. The names of layers, groups and
|
|
113
|
+
features are changed in the head of the inspector, which also ends a
|
|
114
|
+
defect: a locked row could still be renamed in the tree.
|
|
115
|
+
- The pencil that marked the active layer (the layer drawn
|
|
116
|
+
features go into) in the layer panel, as in the reference layout: the
|
|
117
|
+
row of a layer looks the same whether it is active or not. Pressing a
|
|
118
|
+
layer still makes it active (`layers.setActive`). The word
|
|
119
|
+
`activeLayer` is gone with it; at run time a locale that still gives
|
|
120
|
+
it is accepted and the word is ignored.
|
|
121
|
+
|
|
122
|
+
### Fixed in 1.1.0
|
|
123
|
+
|
|
124
|
+
- In the inspector of a feature, what a tab shows first is spaced
|
|
125
|
+
from the line of the tabs as the content of a panel is from its head
|
|
126
|
+
(pad-md to the first field, pad-lg to a section title). The panel of
|
|
127
|
+
the tab is now a Stack of its own under the tabs, so kata's spacing of
|
|
128
|
+
a first group applies; before, the title of the first section touched
|
|
129
|
+
the line.
|
|
130
|
+
- The fields of the style have no "Style" title any more: they
|
|
131
|
+
sit straight under the Style tab, and the sections of the application
|
|
132
|
+
and Operations keep their titles. The reset of the style, which was an
|
|
133
|
+
icon button in that title, is a "Reset the style" text action after
|
|
134
|
+
the fields. In the same way, the shared fields of a selection, the
|
|
135
|
+
fields of a group and the fields of a layer have no title repeating
|
|
136
|
+
what the head says; the style rule of a layer keeps its title.
|
|
137
|
+
- The page without a bundler in the README names
|
|
138
|
+
`@sakuzu/maplibre-gl-draw/geometry` in its import map, which the
|
|
139
|
+
single-file build imports. The README also shows the package from
|
|
140
|
+
React and from Vue, and a page that loads it from a CDN.
|
|
141
|
+
|
|
10
142
|
## [1.0.0] - 2026-10-01
|
|
11
143
|
|
|
12
144
|
The first release of the standard user interface of
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
51
|
+
|
|
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
|
|
54
86
|
|
|
55
|
-
|
|
56
|
-
|
|
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>
|
|
58
118
|
```
|
|
59
119
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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>
|
|
172
|
+
```
|
|
173
|
+
|
|
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,6 +238,13 @@ 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
250
|
one instead. The map's padding follows the interface, the width of a
|
|
@@ -111,18 +253,36 @@ so that `fitBounds` and `easeTo` keep clear of them; `padding: false`
|
|
|
111
253
|
leaves the map's padding alone.
|
|
112
254
|
|
|
113
255
|
On the left, the layer panel and the legend share a panel, in two tabs.
|
|
114
|
-
The layer panel
|
|
115
|
-
|
|
116
|
-
|
|
256
|
+
The layer panel has two sections, Stack and Basemap. Stack is the tree
|
|
257
|
+
of the layers, their groups and their features, from the front, with
|
|
258
|
+
the eye, the lock, reordering by dragging and an add menu (a new layer,
|
|
117
259
|
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
|
|
260
|
+
`properties.name`, or by its type when it has none; names are changed
|
|
261
|
+
in the head of the inspector, not in the tree. Each row costs its
|
|
262
|
+
drawing, so a layer lists up to 1,000 features, those of its groups
|
|
263
|
+
included: a layer that holds more lists none of them and shows their
|
|
264
|
+
number instead, with a hint to select them on the map, and its own row
|
|
265
|
+
works as before (the eye, the lock, the active layer). `features` sets
|
|
266
|
+
the limit as a number, and `false` lists no features, only the groups.
|
|
267
|
+
The datasets (`draw.datasets`) are rows of the stack too, in their place
|
|
268
|
+
among the layers: those of `above-store` in front of every layer, those
|
|
269
|
+
of `layer-order` where `layers.getOrder()` places them, and those of
|
|
270
|
+
`below-store` behind every layer. A dataset row shows its ID (core gives
|
|
271
|
+
a dataset no name), with the eye (`setVisible`) and no lock; a press on
|
|
272
|
+
it leaves the selection as it is, and it is dragged among the layers
|
|
273
|
+
only when its order is
|
|
274
|
+
`layer-order`. Basemap, under it, is the back of the stack (see
|
|
275
|
+
[Basemaps](#basemaps)). The legend shows the rows of the style rule
|
|
276
|
+
(`styleRule`) of each layer and of each dataset that has one, in the
|
|
277
|
+
order of the stack.
|
|
120
278
|
Shift+L opens and closes the panel, and while it is closed a button at
|
|
121
279
|
the top left of the map opens it again.
|
|
122
280
|
|
|
123
281
|
```ts
|
|
124
282
|
const ui = createDrawUI(draw, {
|
|
125
|
-
|
|
283
|
+
// or false for none; features: true lists up to 1,000 in a layer, a number
|
|
284
|
+
// sets that limit, and false lists none
|
|
285
|
+
layers: { features: true, datasets: true, add: true, reorder: true },
|
|
126
286
|
legend: true,
|
|
127
287
|
});
|
|
128
288
|
```
|
|
@@ -163,6 +323,9 @@ const ui = createDrawUI(draw, {
|
|
|
163
323
|
});
|
|
164
324
|
```
|
|
165
325
|
|
|
326
|
+
The tabs open on the first of `tabs`: `['attributes', 'style']` opens
|
|
327
|
+
on Attributes. A tab chosen stays open from one feature to the next.
|
|
328
|
+
|
|
166
329
|
A value typed into an attribute is kept as the string typed. The
|
|
167
330
|
inspector alone goes into any element with
|
|
168
331
|
`createInspector(draw, { target })`, and an application adds a section
|
|
@@ -209,8 +372,102 @@ open over the map or beside it, the button stands to the left of it.
|
|
|
209
372
|
|
|
210
373
|
Beyond the look, the options above choose the parts and what each shows,
|
|
211
374
|
`ui.tools.add` adds a tool for a mode of the application (see
|
|
212
|
-
[Use](#use)),
|
|
213
|
-
inspector (see [Inspector](#inspector)).
|
|
375
|
+
[Use](#use)), `ui.inspector.sections.add` adds a section to the
|
|
376
|
+
inspector (see [Inspector](#inspector)), and `ui.actions.add` adds an
|
|
377
|
+
action of the application (see [Actions](#actions)).
|
|
378
|
+
|
|
379
|
+
### Actions
|
|
380
|
+
|
|
381
|
+
An action of the application, such as saving the drawing or turning a
|
|
382
|
+
setting on and off, is a row of a card at the bottom left of the map,
|
|
383
|
+
above maplibre-gl's scale: a switch (`kind: 'toggle'`) or a button
|
|
384
|
+
(`kind: 'action'`), with its key at the end. A press on the row and its
|
|
385
|
+
key both call `run`; a switch shows what `checked` returns, read again
|
|
386
|
+
after each run and on `ui.actions.refresh()`, so the state stays with
|
|
387
|
+
the application. `disabled` dims the row and turns its key off, and
|
|
388
|
+
`hint` is a caption under it. The keys are listed with `?` under the
|
|
389
|
+
title of the card, and do nothing while a field has the focus. A key the
|
|
390
|
+
interface uses (the keys of the tools, Delete, Backspace, Escape, `?`
|
|
391
|
+
and Shift+L) or another action uses is refused with an error. The card
|
|
392
|
+
shows while there is an action, its head folds it into one button, and
|
|
393
|
+
on a map narrower than 48rem it starts folded; where it would reach the
|
|
394
|
+
toolbar across, it stands above the toolbar, and the layer panel
|
|
395
|
+
floating at the left ends above it. Its title is `actionsTitle`, or the
|
|
396
|
+
word for actions of the locale.
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
const ui = createDrawUI(draw, {
|
|
400
|
+
actions: [
|
|
401
|
+
{
|
|
402
|
+
id: 'read-only',
|
|
403
|
+
label: 'Read-only',
|
|
404
|
+
kind: 'toggle',
|
|
405
|
+
shortcut: 'R',
|
|
406
|
+
run: () => draw.setReadOnly(!draw.isReadOnly()),
|
|
407
|
+
checked: () => draw.isReadOnly(),
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const remove = ui.actions.add({
|
|
413
|
+
id: 'save',
|
|
414
|
+
label: 'Save',
|
|
415
|
+
kind: 'action',
|
|
416
|
+
shortcut: 'S',
|
|
417
|
+
run: () => localStorage.setItem('drawing', JSON.stringify(draw.document.toJSON())),
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Basemaps
|
|
422
|
+
|
|
423
|
+
The last section of the layer panel, Basemap, is the back of the stack:
|
|
424
|
+
one row under the tree of the layers, apart from it as the basemap is no
|
|
425
|
+
layer, so it is not dragged, hidden, locked or selected. The row shows
|
|
426
|
+
the name of the basemap the map shows: the label of the current one of
|
|
427
|
+
`basemaps`, else the `name` of the map's style, read again on each
|
|
428
|
+
`style.load`, else the word for a basemap.
|
|
429
|
+
|
|
430
|
+
With two or more `basemaps`, pressing the row opens them on the right,
|
|
431
|
+
in the place of the inspector: a list of their labels, each with its
|
|
432
|
+
`preview` (a value of CSS `background`, such as a gradient in the colors
|
|
433
|
+
of the style; a neutral square without one), and a check on the current
|
|
434
|
+
one. Opening it clears the selection, and selecting a feature on the map
|
|
435
|
+
or in the tree closes it, as do its close button and Escape. Choosing a
|
|
436
|
+
basemap replaces the map's style with its `style` (a URL or a style
|
|
437
|
+
object) and calls `onbasemap`, and the list stays open.
|
|
438
|
+
`ui.setBasemap(id)` does the same, and `ui.getBasemap()` returns the
|
|
439
|
+
current one. The current one at the start is `basemap`, or else the
|
|
440
|
+
first whose `style` is the URL the map's style was loaded from. With
|
|
441
|
+
fewer, the row only shows the name.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
const styles = 'https://tiles.openfreemap.org/styles';
|
|
445
|
+
const ui = createDrawUI(draw, {
|
|
446
|
+
basemaps: [
|
|
447
|
+
{
|
|
448
|
+
id: 'bright',
|
|
449
|
+
label: 'Bright',
|
|
450
|
+
style: `${styles}/bright`,
|
|
451
|
+
preview: 'linear-gradient(135deg, #f4f1ea, #dfe7d5)',
|
|
452
|
+
},
|
|
453
|
+
{ id: 'dark', label: 'Dark', style: `${styles}/dark` },
|
|
454
|
+
],
|
|
455
|
+
onbasemap: (basemap) => console.log(basemap.id),
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The layer panel put alone takes the same options
|
|
460
|
+
(`LayerPanelOptions`):
|
|
461
|
+
`createLayerPanel(draw, { target, basemaps, basemap, onbasemap })`.
|
|
462
|
+
There, the row opens the list in the place of the panel's sections,
|
|
463
|
+
until its close button or Escape closes it.
|
|
464
|
+
|
|
465
|
+
The drawing stays: choosing a basemap calls
|
|
466
|
+
`map.setStyle(style, { diff: false })`, which replaces the style whole,
|
|
467
|
+
and the draw instance adds its layers again on top of the new style once
|
|
468
|
+
it has loaded. Sources, layers and the terrain that the application
|
|
469
|
+
added to the map itself go with the old style, so it adds them again on
|
|
470
|
+
the map's `style.load` event.
|
|
214
471
|
|
|
215
472
|
## Map controls
|
|
216
473
|
|
|
@@ -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;
|