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
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All user-visible strings. Pass a (partial) object with the same keys as
|
|
3
|
+
* the `i18n` option to translate the UI.
|
|
4
|
+
*
|
|
5
|
+
* @typedef {Object} LayerControlI18n
|
|
6
|
+
* @property {string} buttonTitle Tooltip / accessible name of the map button.
|
|
7
|
+
* @property {string} panelTitle Heading of the panel.
|
|
8
|
+
* @property {string} closeTitle Tooltip of the close button.
|
|
9
|
+
* @property {string} resizeTitle Tooltip of the resize handle.
|
|
10
|
+
* @property {string} collapseTitle Tooltip of the fold button when a group is expanded.
|
|
11
|
+
* @property {string} expandTitle Tooltip of the fold button when a group is collapsed.
|
|
12
|
+
* @property {string} exclusiveHint Tooltip of a group title when it can be right-clicked
|
|
13
|
+
* to switch between exclusive and non-exclusive.
|
|
14
|
+
* @property {string} outOfRangeHint Tooltip of a layer that is not shown at the
|
|
15
|
+
* current zoom level (outside its min/max resolution or zoom).
|
|
16
|
+
* @property {string} groupOffHint Tooltip of a layer inside a switched-off group.
|
|
17
|
+
* `{group}` is replaced by the title of that group.
|
|
18
|
+
* @property {string} opacityTitle Tooltip of the opacity button and label of the slider.
|
|
19
|
+
* @property {string} searchToggleTitle Tooltip of the panel title (click shows/hides the search box).
|
|
20
|
+
* @property {string} searchPlaceholder Placeholder text of the search box.
|
|
21
|
+
* @property {string} searchClearTitle Tooltip of the button that clears the search box.
|
|
22
|
+
* @property {string} searchNoResults Message shown when no layer matches the search.
|
|
23
|
+
*/
|
|
24
|
+
/** @type {LayerControlI18n} */
|
|
25
|
+
export const DEFAULT_I18N: LayerControlI18n;
|
|
26
|
+
/**
|
|
27
|
+
* Where the map button sits on the map. Each position leaves room for the
|
|
28
|
+
* OpenLayers default control that normally occupies that corner:
|
|
29
|
+
*
|
|
30
|
+
* 'top-left' below the Zoom buttons
|
|
31
|
+
* 'bottom-left' in the bottom-left corner
|
|
32
|
+
* 'top-right' in the top-right corner (shared with the Rotate button,
|
|
33
|
+
* which only shows when the map is rotated)
|
|
34
|
+
* 'right' top-right, below the Rotate button
|
|
35
|
+
* 'bottom-right' above the Attribution button
|
|
36
|
+
*
|
|
37
|
+
* @typedef {'top-left'|'bottom-left'|'top-right'|'right'|'bottom-right'} ButtonPosition
|
|
38
|
+
*/
|
|
39
|
+
/** @type {ReadonlyArray<ButtonPosition>} */
|
|
40
|
+
export const BUTTON_POSITIONS: ReadonlyArray<ButtonPosition>;
|
|
41
|
+
/**
|
|
42
|
+
* Layer control: a map button plus a docked, resizable side panel.
|
|
43
|
+
*
|
|
44
|
+
* Observable properties (use `control.on('change:open', ...)` etc.):
|
|
45
|
+
* - `open` boolean, whether the panel is shown
|
|
46
|
+
* - `panelWidth` number, current panel width in pixels
|
|
47
|
+
*
|
|
48
|
+
* @extends {Control}
|
|
49
|
+
*/
|
|
50
|
+
export default class LayerControl extends Control {
|
|
51
|
+
/**
|
|
52
|
+
* @param {LayerControlOptions} [options]
|
|
53
|
+
*/
|
|
54
|
+
constructor(options?: LayerControlOptions);
|
|
55
|
+
/** @private @type {LayerControlI18n} */
|
|
56
|
+
private i18n_;
|
|
57
|
+
/** @private @type {'left'|'right'} */
|
|
58
|
+
private side_;
|
|
59
|
+
/** @private @type {ButtonPosition} */
|
|
60
|
+
private defaultButtonPosition_;
|
|
61
|
+
/** @private */
|
|
62
|
+
private defaultPanelWidth_;
|
|
63
|
+
/** @private */
|
|
64
|
+
private minPanelWidth_;
|
|
65
|
+
/** @private */
|
|
66
|
+
private maxPanelWidth_;
|
|
67
|
+
/** @private */
|
|
68
|
+
private resizable_;
|
|
69
|
+
/** @private */
|
|
70
|
+
private reverse_;
|
|
71
|
+
/** @private */
|
|
72
|
+
private exclusiveToggle_;
|
|
73
|
+
/** @private */
|
|
74
|
+
private opacitySlider_;
|
|
75
|
+
/** @private @type {HTMLElement|null} */
|
|
76
|
+
private panelTarget_;
|
|
77
|
+
/** @private @type {HTMLElement|null} The map's target element we shrink. */
|
|
78
|
+
private hostElement_;
|
|
79
|
+
/** @private @type {{width: string, marginLeft: string}|null} */
|
|
80
|
+
private hostSavedStyle_;
|
|
81
|
+
/** @private @type {Array<import('ol/events.js').EventsKey>} */
|
|
82
|
+
private mapListenerKeys_;
|
|
83
|
+
/** @private @type {Array<import('ol/events.js').EventsKey>} */
|
|
84
|
+
private layerListenerKeys_;
|
|
85
|
+
/** @private */
|
|
86
|
+
private rebuildScheduled_;
|
|
87
|
+
/** @private */
|
|
88
|
+
private layoutScheduled_;
|
|
89
|
+
/** @private @type {Array<HTMLButtonElement>} */
|
|
90
|
+
private foldButtons_;
|
|
91
|
+
/** @private @type {Array<{layer: import('ol/layer/Base.js').default, parent: LayerGroup|null, item: HTMLElement, label: HTMLElement}>} */
|
|
92
|
+
private entries_;
|
|
93
|
+
/** @private @type {Map<import('ol/layer/Base.js').default, LayerGroup|null>} */
|
|
94
|
+
private parents_;
|
|
95
|
+
/** @private @type {Array<import('ol/events.js').EventsKey>} */
|
|
96
|
+
private viewListenerKeys_;
|
|
97
|
+
/** @private @type {HTMLElement|null} */
|
|
98
|
+
private resizer_;
|
|
99
|
+
/** @private @type {HTMLButtonElement} */
|
|
100
|
+
private button_;
|
|
101
|
+
/** @private @type {HTMLElement} */
|
|
102
|
+
private panel_;
|
|
103
|
+
/** @private @type {HTMLButtonElement} */
|
|
104
|
+
private titleElement_;
|
|
105
|
+
/** @private @type {HTMLButtonElement} */
|
|
106
|
+
private closeButton_;
|
|
107
|
+
/** @private @type {HTMLElement} */
|
|
108
|
+
private searchRow_;
|
|
109
|
+
/** @private @type {HTMLInputElement} */
|
|
110
|
+
private searchInput_;
|
|
111
|
+
/** @private @type {HTMLButtonElement} */
|
|
112
|
+
private searchClear_;
|
|
113
|
+
/** @private @type {HTMLElement} */
|
|
114
|
+
private rootList_;
|
|
115
|
+
/** @private @type {HTMLElement} */
|
|
116
|
+
private noResults_;
|
|
117
|
+
/** Show the panel. */
|
|
118
|
+
open(): void;
|
|
119
|
+
/** Hide the panel. */
|
|
120
|
+
close(): void;
|
|
121
|
+
/** Show the panel if hidden, hide it if shown. */
|
|
122
|
+
toggle(): void;
|
|
123
|
+
/** @return {boolean} Whether the panel is shown. */
|
|
124
|
+
isOpen(): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* @param {number} width Panel width in pixels (clamped to min/max).
|
|
127
|
+
*/
|
|
128
|
+
setPanelWidth(width: number): void;
|
|
129
|
+
/** @return {number} Current panel width in pixels. */
|
|
130
|
+
getPanelWidth(): number;
|
|
131
|
+
/** @return {HTMLElement} The panel element. */
|
|
132
|
+
getPanelElement(): HTMLElement;
|
|
133
|
+
/**
|
|
134
|
+
* Move the map button. An unknown value falls back to the default for the
|
|
135
|
+
* panel's side.
|
|
136
|
+
* @param {ButtonPosition} position
|
|
137
|
+
*/
|
|
138
|
+
setButtonPosition(position: ButtonPosition): void;
|
|
139
|
+
/** @return {ButtonPosition} Where the map button sits. */
|
|
140
|
+
getButtonPosition(): ButtonPosition;
|
|
141
|
+
/**
|
|
142
|
+
* Show or hide the search box. Hiding it clears the filter.
|
|
143
|
+
* @param {boolean} [visible=true]
|
|
144
|
+
*/
|
|
145
|
+
showSearch(visible?: boolean): void;
|
|
146
|
+
/** Show the search box if hidden, hide it if shown. */
|
|
147
|
+
toggleSearch(): void;
|
|
148
|
+
/** @return {boolean} Whether the search box is shown. */
|
|
149
|
+
isSearchVisible(): boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Filter the layer list by (part of) a title, case-insensitive. Groups
|
|
152
|
+
* stay listed when their title or any of their layers match.
|
|
153
|
+
* @param {string} query Empty string shows everything.
|
|
154
|
+
*/
|
|
155
|
+
setFilter(query: string): void;
|
|
156
|
+
/** @return {string} The current filter text. */
|
|
157
|
+
getFilter(): string;
|
|
158
|
+
/**
|
|
159
|
+
* Replace (part of) the UI strings.
|
|
160
|
+
* @param {Partial<LayerControlI18n>} i18n
|
|
161
|
+
*/
|
|
162
|
+
setI18n(i18n: Partial<LayerControlI18n>): void;
|
|
163
|
+
/** @return {LayerControlI18n} The strings in use. */
|
|
164
|
+
getI18n(): LayerControlI18n;
|
|
165
|
+
/**
|
|
166
|
+
* Rebuild the layer list from the map. Normally not needed: the control
|
|
167
|
+
* follows layer additions, removals and visibility changes by itself.
|
|
168
|
+
*/
|
|
169
|
+
refresh(): void;
|
|
170
|
+
/**
|
|
171
|
+
* Make a layer group exclusive (children as radio buttons, at most one
|
|
172
|
+
* visible) or non-exclusive (checkboxes). When switching to exclusive and
|
|
173
|
+
* several children are visible, only the top-most one stays visible.
|
|
174
|
+
*
|
|
175
|
+
* Basemap groups (children with `type: 'base'`) are always exclusive.
|
|
176
|
+
*
|
|
177
|
+
* @param {LayerGroup} group
|
|
178
|
+
* @param {boolean} exclusive
|
|
179
|
+
*/
|
|
180
|
+
setExclusive(group: LayerGroup, exclusive: boolean): void;
|
|
181
|
+
/** @private Follow the (possibly new) view's resolution. */
|
|
182
|
+
private observeView_;
|
|
183
|
+
/**
|
|
184
|
+
* Dim entries that are outside their resolution/zoom range and explain,
|
|
185
|
+
* in a tooltip, why an entry is not shown on the map (out of range and/or
|
|
186
|
+
* inside a switched-off group).
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
private updateStates_;
|
|
190
|
+
/** @private */
|
|
191
|
+
private attachPanel_;
|
|
192
|
+
/** @private */
|
|
193
|
+
private restoreHost_;
|
|
194
|
+
/** @private */
|
|
195
|
+
private detach_;
|
|
196
|
+
/** @private */
|
|
197
|
+
private scheduleLayout_;
|
|
198
|
+
/** @private */
|
|
199
|
+
private applyLayout_;
|
|
200
|
+
/**
|
|
201
|
+
* @private
|
|
202
|
+
* @param {number} width
|
|
203
|
+
* @return {number}
|
|
204
|
+
*/
|
|
205
|
+
private clampWidth_;
|
|
206
|
+
/**
|
|
207
|
+
* @private
|
|
208
|
+
* @param {HTMLElement} handle
|
|
209
|
+
*/
|
|
210
|
+
private initResizer_;
|
|
211
|
+
/** @private */
|
|
212
|
+
private applyTexts_;
|
|
213
|
+
/**
|
|
214
|
+
* @private
|
|
215
|
+
* @param {string|undefined} position
|
|
216
|
+
* @return {ButtonPosition}
|
|
217
|
+
*/
|
|
218
|
+
private normalizeButtonPosition_;
|
|
219
|
+
/** @private */
|
|
220
|
+
private applyButtonPosition_;
|
|
221
|
+
/** @private */
|
|
222
|
+
private applySearchVisibility_;
|
|
223
|
+
/** @private Show only entries that match the search text. */
|
|
224
|
+
private applyFilter_;
|
|
225
|
+
/** @private */
|
|
226
|
+
private clearLayerListeners_;
|
|
227
|
+
/** @private */
|
|
228
|
+
private scheduleRebuild_;
|
|
229
|
+
/** @private */
|
|
230
|
+
private rebuild_;
|
|
231
|
+
/**
|
|
232
|
+
* @private
|
|
233
|
+
* @param {import('ol/layer/Base.js').default} layer
|
|
234
|
+
* @param {LayerGroup|null} parent
|
|
235
|
+
* @param {HTMLElement} item
|
|
236
|
+
* @param {HTMLElement} label
|
|
237
|
+
*/
|
|
238
|
+
private registerEntry_;
|
|
239
|
+
/**
|
|
240
|
+
* @private
|
|
241
|
+
* @param {import('ol/Collection.js').default<import('ol/layer/Base.js').default>} collection
|
|
242
|
+
* @param {HTMLElement} list The <ul> to render into.
|
|
243
|
+
* @param {string|null} radioName Non-null when the parent group is exclusive.
|
|
244
|
+
* @param {LayerGroup|null} parent The group that owns the collection.
|
|
245
|
+
*/
|
|
246
|
+
private renderCollection_;
|
|
247
|
+
/**
|
|
248
|
+
* The layers of a collection that appear in the panel, in panel order.
|
|
249
|
+
* @private
|
|
250
|
+
* @param {import('ol/Collection.js').default<import('ol/layer/Base.js').default>} collection
|
|
251
|
+
* @return {Array<import('ol/layer/Base.js').default>}
|
|
252
|
+
*/
|
|
253
|
+
private listedLayers_;
|
|
254
|
+
/**
|
|
255
|
+
* @private
|
|
256
|
+
* @param {LayerGroup} group
|
|
257
|
+
* @return {Array<import('ol/layer/Base.js').default>}
|
|
258
|
+
*/
|
|
259
|
+
private listedChildren_;
|
|
260
|
+
/**
|
|
261
|
+
* @private
|
|
262
|
+
* @param {import('ol/layer/Base.js').default} layer
|
|
263
|
+
* @param {Array<import('ol/layer/Base.js').default>} siblings Rendered siblings, incl. the layer itself.
|
|
264
|
+
* @param {HTMLElement} list
|
|
265
|
+
* @param {string|null} radioName
|
|
266
|
+
* @param {LayerGroup|null} parent
|
|
267
|
+
*/
|
|
268
|
+
private renderLayer_;
|
|
269
|
+
/**
|
|
270
|
+
* Adds an opacity button to the row and a (collapsed) slider row to the item.
|
|
271
|
+
* @private
|
|
272
|
+
* @param {import('ol/layer/Base.js').default} layer
|
|
273
|
+
* @param {HTMLElement} item The <li>.
|
|
274
|
+
* @param {HTMLElement} row The row holding the input and label.
|
|
275
|
+
*/
|
|
276
|
+
private addOpacitySlider_;
|
|
277
|
+
/**
|
|
278
|
+
* @private
|
|
279
|
+
* @param {LayerGroup} group
|
|
280
|
+
* @param {Array<import('ol/layer/Base.js').default>} siblings
|
|
281
|
+
* @param {HTMLElement} list
|
|
282
|
+
* @param {string|null} radioName
|
|
283
|
+
* @param {LayerGroup|null} parent
|
|
284
|
+
*/
|
|
285
|
+
private renderGroup_;
|
|
286
|
+
/**
|
|
287
|
+
* @private
|
|
288
|
+
* @param {import('ol/layer/Base.js').default} layer
|
|
289
|
+
* @param {Array<import('ol/layer/Base.js').default>} siblings
|
|
290
|
+
* @param {string|null} radioName
|
|
291
|
+
* @return {HTMLInputElement}
|
|
292
|
+
*/
|
|
293
|
+
private createInput_;
|
|
294
|
+
/**
|
|
295
|
+
* @private
|
|
296
|
+
* @param {import('ol/layer/Base.js').default} layer
|
|
297
|
+
* @param {HTMLInputElement|null} input
|
|
298
|
+
* @return {HTMLElement}
|
|
299
|
+
*/
|
|
300
|
+
private createLabel_;
|
|
301
|
+
/**
|
|
302
|
+
* @private
|
|
303
|
+
* @param {HTMLButtonElement} button
|
|
304
|
+
*/
|
|
305
|
+
private updateFoldButton_;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* All user-visible strings. Pass a (partial) object with the same keys as
|
|
309
|
+
* the `i18n` option to translate the UI.
|
|
310
|
+
*/
|
|
311
|
+
export type LayerControlI18n = {
|
|
312
|
+
/**
|
|
313
|
+
* Tooltip / accessible name of the map button.
|
|
314
|
+
*/
|
|
315
|
+
buttonTitle: string;
|
|
316
|
+
/**
|
|
317
|
+
* Heading of the panel.
|
|
318
|
+
*/
|
|
319
|
+
panelTitle: string;
|
|
320
|
+
/**
|
|
321
|
+
* Tooltip of the close button.
|
|
322
|
+
*/
|
|
323
|
+
closeTitle: string;
|
|
324
|
+
/**
|
|
325
|
+
* Tooltip of the resize handle.
|
|
326
|
+
*/
|
|
327
|
+
resizeTitle: string;
|
|
328
|
+
/**
|
|
329
|
+
* Tooltip of the fold button when a group is expanded.
|
|
330
|
+
*/
|
|
331
|
+
collapseTitle: string;
|
|
332
|
+
/**
|
|
333
|
+
* Tooltip of the fold button when a group is collapsed.
|
|
334
|
+
*/
|
|
335
|
+
expandTitle: string;
|
|
336
|
+
/**
|
|
337
|
+
* Tooltip of a group title when it can be right-clicked
|
|
338
|
+
* to switch between exclusive and non-exclusive.
|
|
339
|
+
*/
|
|
340
|
+
exclusiveHint: string;
|
|
341
|
+
/**
|
|
342
|
+
* Tooltip of a layer that is not shown at the
|
|
343
|
+
* current zoom level (outside its min/max resolution or zoom).
|
|
344
|
+
*/
|
|
345
|
+
outOfRangeHint: string;
|
|
346
|
+
/**
|
|
347
|
+
* Tooltip of a layer inside a switched-off group.
|
|
348
|
+
* `{group}` is replaced by the title of that group.
|
|
349
|
+
*/
|
|
350
|
+
groupOffHint: string;
|
|
351
|
+
/**
|
|
352
|
+
* Tooltip of the opacity button and label of the slider.
|
|
353
|
+
*/
|
|
354
|
+
opacityTitle: string;
|
|
355
|
+
/**
|
|
356
|
+
* Tooltip of the panel title (click shows/hides the search box).
|
|
357
|
+
*/
|
|
358
|
+
searchToggleTitle: string;
|
|
359
|
+
/**
|
|
360
|
+
* Placeholder text of the search box.
|
|
361
|
+
*/
|
|
362
|
+
searchPlaceholder: string;
|
|
363
|
+
/**
|
|
364
|
+
* Tooltip of the button that clears the search box.
|
|
365
|
+
*/
|
|
366
|
+
searchClearTitle: string;
|
|
367
|
+
/**
|
|
368
|
+
* Message shown when no layer matches the search.
|
|
369
|
+
*/
|
|
370
|
+
searchNoResults: string;
|
|
371
|
+
};
|
|
372
|
+
export type LayerControlOptions = {
|
|
373
|
+
/**
|
|
374
|
+
* Where to render the map button
|
|
375
|
+
* (standard OpenLayers control option). Default: the map's control container.
|
|
376
|
+
*/
|
|
377
|
+
target?: HTMLElement | string;
|
|
378
|
+
/**
|
|
379
|
+
* Element (or element id) to
|
|
380
|
+
* render the panel into. When set, the control leaves the map element
|
|
381
|
+
* alone and the host page is responsible for the layout.
|
|
382
|
+
*/
|
|
383
|
+
panelTarget?: HTMLElement | string;
|
|
384
|
+
/**
|
|
385
|
+
* Side of the map the panel docks to.
|
|
386
|
+
*/
|
|
387
|
+
side?: "left" | "right";
|
|
388
|
+
/**
|
|
389
|
+
* Where the map button sits. Defaults to
|
|
390
|
+
* `'top-left'` when `side` is `'left'` and `'right'` otherwise.
|
|
391
|
+
*/
|
|
392
|
+
buttonPosition?: ButtonPosition;
|
|
393
|
+
/**
|
|
394
|
+
* Initial panel width in pixels.
|
|
395
|
+
*/
|
|
396
|
+
panelWidth?: number;
|
|
397
|
+
/**
|
|
398
|
+
* Smallest width the user can resize to.
|
|
399
|
+
*/
|
|
400
|
+
minPanelWidth?: number;
|
|
401
|
+
/**
|
|
402
|
+
* Largest width the user can resize to.
|
|
403
|
+
*/
|
|
404
|
+
maxPanelWidth?: number;
|
|
405
|
+
/**
|
|
406
|
+
* Show a drag handle to resize the panel.
|
|
407
|
+
*/
|
|
408
|
+
resizable?: boolean;
|
|
409
|
+
/**
|
|
410
|
+
* Start with the panel open.
|
|
411
|
+
*/
|
|
412
|
+
open?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* List layers top-most first (rendering order reversed).
|
|
415
|
+
*/
|
|
416
|
+
reverse?: boolean;
|
|
417
|
+
/**
|
|
418
|
+
* Let the user switch a group between
|
|
419
|
+
* exclusive (radio buttons) and non-exclusive (checkboxes) by right-clicking
|
|
420
|
+
* its title. Basemap groups are always exclusive.
|
|
421
|
+
*/
|
|
422
|
+
exclusiveToggle?: boolean;
|
|
423
|
+
/**
|
|
424
|
+
* Give every layer a button that reveals
|
|
425
|
+
* an opacity slider.
|
|
426
|
+
*/
|
|
427
|
+
opacitySlider?: boolean;
|
|
428
|
+
/**
|
|
429
|
+
* Show the search box from the start. The user
|
|
430
|
+
* can always show or hide it by clicking the panel title.
|
|
431
|
+
*/
|
|
432
|
+
search?: boolean;
|
|
433
|
+
/**
|
|
434
|
+
* Translations, see {@link DEFAULT_I18N}.
|
|
435
|
+
*/
|
|
436
|
+
i18n?: Partial<LayerControlI18n>;
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* Where the map button sits on the map. Each position leaves room for the
|
|
440
|
+
* OpenLayers default control that normally occupies that corner:
|
|
441
|
+
*
|
|
442
|
+
* 'top-left' below the Zoom buttons
|
|
443
|
+
* 'bottom-left' in the bottom-left corner
|
|
444
|
+
* 'top-right' in the top-right corner (shared with the Rotate button,
|
|
445
|
+
* which only shows when the map is rotated)
|
|
446
|
+
* 'right' top-right, below the Rotate button
|
|
447
|
+
* 'bottom-right' above the Attribution button
|
|
448
|
+
*/
|
|
449
|
+
export type ButtonPosition = "top-left" | "bottom-left" | "top-right" | "right" | "bottom-right";
|
|
450
|
+
import Control from 'ol/control/Control.js';
|
|
451
|
+
import LayerGroup from 'ol/layer/Group.js';
|