ol-layer-control 0.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 +29 -0
- package/LICENSE +21 -0
- package/package.json +60 -0
- package/readme.md +239 -0
- package/src/ol-layer-control.css +408 -0
- package/src/ol-layer-control.js +1132 -0
- package/types/ol-layer-control.d.ts +451 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While the version is below 1.0.0, a minor version bump may contain breaking changes.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-09-28
|
|
10
|
+
|
|
11
|
+
First public release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `LayerControl`, an OpenLayers control: a map button that opens a panel docked to the left or right of the map. The map shrinks while the panel is open, so the panel never covers it.
|
|
16
|
+
- Five map button positions (`buttonPosition`) that stay clear of the default Zoom, Rotate and Attribution controls. The default follows the panel's side.
|
|
17
|
+
- Resizable panel width, with configurable minimum and maximum.
|
|
18
|
+
- Layer tree with nested, foldable groups. Basemaps (`type: 'base'`) and groups with `exclusive: true` show radio buttons; other groups show checkboxes.
|
|
19
|
+
- Right-click on a group title to switch it between exclusive and non-exclusive (`exclusiveToggle`).
|
|
20
|
+
- Greyed-out entries, with a tooltip, for layers outside their resolution or zoom range and for layers inside a switched-off group.
|
|
21
|
+
- Optional opacity slider per layer (`opacitySlider`).
|
|
22
|
+
- Optional search box that filters the layer tree (`search`), toggled by clicking the panel title.
|
|
23
|
+
- `displayInLayerControl: false` hides a layer or group from the panel.
|
|
24
|
+
- English UI strings, all replaceable through the `i18n` option or `setI18n()`.
|
|
25
|
+
- Theming through CSS custom properties.
|
|
26
|
+
- TypeScript declarations generated from the JSDoc.
|
|
27
|
+
|
|
28
|
+
[Unreleased]: https://github.com/TWIAV/ol-layer-control/compare/v0.1.0...HEAD
|
|
29
|
+
[0.1.0]: https://github.com/TWIAV/ol-layer-control/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TWIAV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ol-layer-control",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A layer control for OpenLayers: a map button that opens a docked, resizable side panel to switch layers and layer groups on and off.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openlayers",
|
|
7
|
+
"ol",
|
|
8
|
+
"layer-control",
|
|
9
|
+
"layer-switcher",
|
|
10
|
+
"layerswitcher",
|
|
11
|
+
"map",
|
|
12
|
+
"gis",
|
|
13
|
+
"control"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/TWIAV/ol-layer-control#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/TWIAV/ol-layer-control/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/TWIAV/ol-layer-control.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "TWIAV",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./src/ol-layer-control.js",
|
|
27
|
+
"types": "./types/ol-layer-control.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./types/ol-layer-control.d.ts",
|
|
31
|
+
"default": "./src/ol-layer-control.js"
|
|
32
|
+
},
|
|
33
|
+
"./ol-layer-control.css": "./src/ol-layer-control.css",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"src",
|
|
38
|
+
"types",
|
|
39
|
+
"CHANGELOG.md"
|
|
40
|
+
],
|
|
41
|
+
"sideEffects": [
|
|
42
|
+
"*.css"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"start": "vite",
|
|
46
|
+
"build": "vite build",
|
|
47
|
+
"serve": "vite preview",
|
|
48
|
+
"build:types": "tsc -p tsconfig.types.json && node scripts/clean-types.js",
|
|
49
|
+
"prepack": "npm run build:types"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"ol": "^10.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"ol": "^10.10.0",
|
|
56
|
+
"proj4": "^2.22.0",
|
|
57
|
+
"typescript": "^6.0.3",
|
|
58
|
+
"vite": "^8.3.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# OpenLayers Layer Control
|
|
2
|
+
|
|
3
|
+
A layer control for [OpenLayers](https://openlayers.org/): a map button that opens a docked, resizable side panel in which the user switches layers and layer groups on and off.
|
|
4
|
+
|
|
5
|
+
The panel is rendered **next to** the map, never on top of it. The map shrinks while the panel is open.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Map button with an inline SVG icon that opens and closes the panel, in one of five positions that stay clear of the OpenLayers default controls.
|
|
10
|
+
- Panel docked to the right (or left) of the map, resizable by dragging its edge. Double-click the edge to reset the width.
|
|
11
|
+
- Nested layer groups, with fold/unfold per group.
|
|
12
|
+
- Basemaps as radio buttons (only one visible at a time).
|
|
13
|
+
- Any other group can be **exclusive** (radio buttons) or **non-exclusive** (checkboxes). Right-click a group title to switch between the two.
|
|
14
|
+
- Layers that are not drawn at the current zoom level (because of `minResolution`/`maxResolution` or `minZoom`/`maxZoom`) are greyed out with a tooltip. Layers inside a switched-off group are greyed out too, with a tooltip naming the group.
|
|
15
|
+
- Optional opacity slider per layer.
|
|
16
|
+
- Optional search box that filters the layer list. Click the panel title to show or hide it.
|
|
17
|
+
- Follows the map: layers added, removed or changed in code are reflected in the panel.
|
|
18
|
+
- English by default. Every string can be translated.
|
|
19
|
+
- Keyboard accessible: real checkboxes, radios and buttons, Escape closes the panel.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install ol-layer-control
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The package requires OpenLayers 10 (`ol` is a peer dependency, so your app provides it). TypeScript declarations are included.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import Map from 'ol/Map.js';
|
|
33
|
+
import LayerControl from 'ol-layer-control';
|
|
34
|
+
|
|
35
|
+
const map = new Map({ /* ... */ });
|
|
36
|
+
map.addControl(new LayerControl({ open: true }));
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The package is published as ES modules and needs a bundler, such as Vite, webpack, Parcel, or Rollup with a CSS plugin. The control imports its own stylesheet, so the single import above is all you need.
|
|
40
|
+
|
|
41
|
+
If your setup handles CSS separately, the stylesheet is also available on its own:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import 'ol-layer-control/ol-layer-control.css';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Layer properties
|
|
48
|
+
|
|
49
|
+
The control reads these properties from layers and groups. Set them as constructor options or with `layer.set(...)`.
|
|
50
|
+
|
|
51
|
+
| Property | On | Meaning |
|
|
52
|
+
| ------------------------ | ------------- | ---------------------------------------------------------------------------------------- |
|
|
53
|
+
| `title` | layer, group | Text shown in the panel. Layers without a title are not listed. |
|
|
54
|
+
| `displayInLayerControl` | layer, group | `false` hides the layer or group from the panel. |
|
|
55
|
+
| `type: 'base'` | layer | Marks a basemap. A group containing basemaps is exclusive and has no checkbox of its own. |
|
|
56
|
+
| `exclusive` | group | `true` renders the children as radio buttons. Right-clicking the group title toggles it. |
|
|
57
|
+
| `combine` | group | `true` shows the group as a single layer instead of expanding its children. |
|
|
58
|
+
| `folded` | group | `true` starts the group collapsed. Updated when the user folds or unfolds. |
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const baseMaps = new LayerGroup({
|
|
62
|
+
title: 'Basemaps',
|
|
63
|
+
layers: [
|
|
64
|
+
new TileLayer({ title: 'OpenStreetMap', type: 'base', source: new OSM() }),
|
|
65
|
+
new TileLayer({ title: 'None', type: 'base', visible: false }),
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const overlays = new LayerGroup({
|
|
70
|
+
title: 'Administrative areas',
|
|
71
|
+
exclusive: true,
|
|
72
|
+
layers: [municipalities, provinces],
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Description |
|
|
79
|
+
| ----------------- | ------------------------- | --------- | ---------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `open` | `boolean` | `false` | Start with the panel open. |
|
|
81
|
+
| `side` | `'right' \| 'left'` | `'right'` | Side of the map the panel docks to. |
|
|
82
|
+
| `buttonPosition` | `ButtonPosition` | per side | Where the map button sits. See [Button position](#button-position). Defaults to `'top-left'` when `side` is `'left'`, otherwise `'right'`. |
|
|
83
|
+
| `panelWidth` | `number` | `320` | Initial panel width in pixels. |
|
|
84
|
+
| `minPanelWidth` | `number` | `200` | Smallest width the user can resize to. |
|
|
85
|
+
| `maxPanelWidth` | `number` | `800` | Largest width the user can resize to. The control also always leaves some map visible. |
|
|
86
|
+
| `resizable` | `boolean` | `true` | Show the drag handle. |
|
|
87
|
+
| `reverse` | `boolean` | `true` | List layers top-most first (the reverse of the map's rendering order). |
|
|
88
|
+
| `exclusiveToggle` | `boolean` | `true` | Let the user right-click a group title to switch between exclusive and non-exclusive. |
|
|
89
|
+
| `opacitySlider` | `boolean` | `false` | Give every layer a button that reveals an opacity slider. |
|
|
90
|
+
| `search` | `boolean` | `false` | Show the search box from the start. The user can always show or hide it by clicking the panel title. |
|
|
91
|
+
| `panelTarget` | `HTMLElement \| string` | | Render the panel into this element (or element id) instead of next to the map. See [Layout](#layout). |
|
|
92
|
+
| `target` | `HTMLElement \| string` | | Standard OpenLayers control option: where to render the map button. |
|
|
93
|
+
| `i18n` | `Partial<LayerControlI18n>` | | Translations, see [Translating](#translating). |
|
|
94
|
+
|
|
95
|
+
### API
|
|
96
|
+
|
|
97
|
+
| Method | Description |
|
|
98
|
+
| ------------------------------- | ------------------------------------------------------------------------------------ |
|
|
99
|
+
| `open()`, `close()`, `toggle()` | Show or hide the panel. |
|
|
100
|
+
| `isOpen()` | Whether the panel is shown. |
|
|
101
|
+
| `setPanelWidth(px)` | Set the panel width (clamped to min/max). |
|
|
102
|
+
| `setButtonPosition(position)`, `getButtonPosition()` | Move the map button, or read where it is. |
|
|
103
|
+
| `getPanelWidth()` | Current panel width in pixels. |
|
|
104
|
+
| `getPanelElement()` | The panel element. |
|
|
105
|
+
| `setExclusive(group, boolean)` | Make a group exclusive or not. When several children are visible, only the top-most stays visible. |
|
|
106
|
+
| `showSearch(boolean)`, `toggleSearch()`, `isSearchVisible()` | Show or hide the search box. Hiding it clears the filter. |
|
|
107
|
+
| `setFilter(text)`, `getFilter()` | Filter the list by (part of) a title. |
|
|
108
|
+
| `setI18n(partial)`, `getI18n()` | Replace (part of) the UI strings at runtime. |
|
|
109
|
+
| `refresh()` | Rebuild the list. Normally not needed; the control follows the map by itself. |
|
|
110
|
+
|
|
111
|
+
The control is an OpenLayers `Control`, so `control.on(...)` works. These properties fire `change:` events:
|
|
112
|
+
|
|
113
|
+
| Property | Type | Event |
|
|
114
|
+
| --------------- | --------- | ----------------------- |
|
|
115
|
+
| `open` | `boolean` | `change:open` |
|
|
116
|
+
| `panelWidth` | `number` | `change:panelWidth` |
|
|
117
|
+
| `searchVisible` | `boolean` | `change:searchVisible` |
|
|
118
|
+
| `buttonPosition` | `ButtonPosition` | `change:buttonPosition` |
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
control.on('change:open', () => console.log('panel open:', control.isOpen()));
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Button position
|
|
125
|
+
|
|
126
|
+
The map button can sit in five places. Each one leaves room for the OpenLayers default control that normally uses that part of the map.
|
|
127
|
+
|
|
128
|
+
| `buttonPosition` | Where |
|
|
129
|
+
| ---------------- | ----------------------------------------------------------------------------------------------- |
|
|
130
|
+
| `'top-left'` | Top left, below the Zoom buttons. |
|
|
131
|
+
| `'bottom-left'` | Bottom left corner. |
|
|
132
|
+
| `'top-right'` | Top right corner. This is the Rotate button's spot, see the note below. |
|
|
133
|
+
| `'right'` | Top right, below the Rotate button. |
|
|
134
|
+
| `'bottom-right'` | Bottom right, above the Attribution button. |
|
|
135
|
+
|
|
136
|
+
The Rotate button only appears when the map is rotated, and it appears in the top-right corner. With `'top-right'` the layer button covers it. That is why the default for a right-side panel is `'right'`, just below it.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
new LayerControl({ side: 'right', buttonPosition: 'bottom-right' });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The list of valid values is exported as `BUTTON_POSITIONS`. An unknown value logs a warning and falls back to the default for the panel's side.
|
|
143
|
+
|
|
144
|
+
The offsets assume the default OpenLayers controls. When you add others, such as a ScaleLine at the bottom left, adjust the spacing with `--ol-layer-control-edge` and `--ol-layer-control-gap` (see [Theming](#theming)), or override the position classes `ol-layer-control--top-left`, `ol-layer-control--bottom-left`, `ol-layer-control--top-right`, `ol-layer-control--right` and `ol-layer-control--bottom-right`.
|
|
145
|
+
|
|
146
|
+
### Translating
|
|
147
|
+
|
|
148
|
+
Pass any subset of the strings in the `i18n` option, or call `setI18n()` later. The full set, with the English defaults, is exported as `DEFAULT_I18N`.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
import LayerControl, { DEFAULT_I18N } from 'ol-layer-control';
|
|
152
|
+
|
|
153
|
+
new LayerControl({
|
|
154
|
+
i18n: {
|
|
155
|
+
buttonTitle: 'Lagen',
|
|
156
|
+
panelTitle: 'Lagen',
|
|
157
|
+
closeTitle: 'Lagenpaneel sluiten',
|
|
158
|
+
resizeTitle: 'Sleep om de breedte te wijzigen, dubbelklik om te herstellen',
|
|
159
|
+
collapseTitle: 'Groep inklappen',
|
|
160
|
+
expandTitle: 'Groep uitklappen',
|
|
161
|
+
exclusiveHint: 'Rechtsklik om te wisselen tussen één laag tegelijk en meerdere lagen',
|
|
162
|
+
outOfRangeHint: 'Niet zichtbaar op dit zoomniveau',
|
|
163
|
+
groupOffHint: 'Niet zichtbaar: groep "{group}" staat uit',
|
|
164
|
+
opacityTitle: 'Transparantie',
|
|
165
|
+
searchToggleTitle: 'Zoekveld tonen of verbergen',
|
|
166
|
+
searchPlaceholder: 'Lagen zoeken',
|
|
167
|
+
searchClearTitle: 'Zoekopdracht wissen',
|
|
168
|
+
searchNoResults: 'Geen lagen gevonden',
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`{group}` in `groupOffHint` is replaced by the title of the switched-off group. Layer and group titles come from the layers themselves and are not translated by the control.
|
|
174
|
+
|
|
175
|
+
### Theming
|
|
176
|
+
|
|
177
|
+
All colours and sizes are CSS custom properties. Override them on any ancestor of the panel, for example `:root`:
|
|
178
|
+
|
|
179
|
+
```css
|
|
180
|
+
:root {
|
|
181
|
+
--ol-layer-control-bg: #1e1e1e;
|
|
182
|
+
--ol-layer-control-fg: #eee;
|
|
183
|
+
--ol-layer-control-border: #444;
|
|
184
|
+
--ol-layer-control-hover: rgba(255, 255, 255, 0.08);
|
|
185
|
+
--ol-layer-control-accent: #6ea8ff;
|
|
186
|
+
--ol-layer-control-font: 14px/1.4 system-ui, sans-serif;
|
|
187
|
+
--ol-layer-control-indent: 1.4em;
|
|
188
|
+
--ol-layer-control-shadow: none;
|
|
189
|
+
--ol-layer-control-edge: 0.5em; /* map button distance from the map edge */
|
|
190
|
+
--ol-layer-control-gap: 0.5em; /* space between the map button and the OpenLayers control it avoids */
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Every element carries a class starting with `ol-layer-control-`, so anything else can be restyled with plain CSS.
|
|
195
|
+
|
|
196
|
+
### Layout
|
|
197
|
+
|
|
198
|
+
By default the control inserts the panel as a sibling of the map's target element and, while the panel is open, sets an inline `width: calc(100% - <panel width>)` on the map element (plus a `margin-left` when the panel is on the left). It calls `map.updateSize()` after every change and restores the original inline style when the panel closes.
|
|
199
|
+
|
|
200
|
+
The panel is absolutely positioned with `top: 0; bottom: 0` against the map's nearest positioned ancestor. This works out of the box when the map fills the page. When the map sits inside a page with other content, give the map's parent element `position: relative` so the panel lines up with the map.
|
|
201
|
+
|
|
202
|
+
If your app has its own layout (flexbox, a sidebar component, a framework), pass `panelTarget`. The control then renders the panel into that element and leaves the map element alone.
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
The control lives in `src/`: `ol-layer-control.js` and `ol-layer-control.css`. It is published as-is, without a build step.
|
|
207
|
+
|
|
208
|
+
The `demo/` folder holds an app (Dutch PDOK services in EPSG:28992) used to develop and test the control. It requires Node 20.19 or newer.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm install
|
|
212
|
+
npm start
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The demo runs at http://localhost:5173. To create a production build of the demo in `dist/`:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npm run build
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The TypeScript declarations in `types/` are generated from the JSDoc comments in `src/`. `npm pack` and `npm publish` generate them automatically. To generate them by hand:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm run build:types
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
To see exactly which files a release would contain:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npm pack --dry-run
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Changelog
|
|
234
|
+
|
|
235
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
236
|
+
|
|
237
|
+
## Roadmap
|
|
238
|
+
|
|
239
|
+
- Legends, using the `legend` property already present on the demo layers.
|