@projektemacher/storymapjs 0.10.8 → 0.11.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 +81 -0
- package/README.md +129 -12
- package/dist/assets/{demo-DBpeF31K.js → demo-bWnOyfUw.js} +1 -1
- package/dist/assets/harness-C3JUv5Ii.js +1 -0
- package/dist/assets/{harness-multi-CYHgI0a9.js → harness-multi-XZNex3tb.js} +1 -1
- package/dist/assets/{harness-support-Do7sIp2j.js → harness-support-CjfZIVeb.js} +1 -1
- package/dist/assets/main-DQ32hWBM.js +17 -0
- package/dist/context.json +13 -2
- package/dist/css/storymap.css +1 -1
- package/dist/demo.html +3 -2
- package/dist/docs/api/assets/navigation.js +1 -1
- package/dist/docs/api/assets/search.js +1 -1
- package/dist/docs/api/classes/StoryMap.html +777 -269
- package/dist/docs/api/functions/MediaType.html +1 -1
- package/dist/docs/api/functions/isPresentation3Collection.html +1 -1
- package/dist/docs/api/functions/isPresentation3Manifest.html +1 -1
- package/dist/docs/api/functions/isSlideshowCollection.html +51 -0
- package/dist/docs/api/functions/loadCSS.html +1 -1
- package/dist/docs/api/functions/manifestToStorymapData.html +1 -1
- package/dist/docs/api/functions/setLanguage.html +1 -1
- package/dist/docs/api/functions/slideshowToStorymapData.html +51 -0
- package/dist/docs/api/functions/slideshowVersion.html +48 -0
- package/dist/docs/api/functions/storymapToManifest.html +1 -1
- package/dist/docs/api/functions/validateStorymap.html +1 -1
- package/dist/docs/api/functions/validateStorymapAndReport.html +1 -1
- package/dist/docs/api/index.html +115 -12
- package/dist/docs/api/interfaces/AnimationHandle.html +2 -2
- package/dist/docs/api/interfaces/ImagereadyPayload.html +4 -4
- package/dist/docs/api/interfaces/LanguageEntry.html +6 -6
- package/dist/docs/api/interfaces/LatLngLiteral.html +4 -4
- package/dist/docs/api/interfaces/LoadOptions.html +2 -2
- package/dist/docs/api/interfaces/MapEvents.html +116 -0
- package/dist/docs/api/interfaces/MapMarkerEvents.html +66 -0
- package/dist/docs/api/interfaces/MarkerEventPayload.html +57 -0
- package/dist/docs/api/interfaces/MediaEvents.html +78 -0
- package/dist/docs/api/interfaces/MenuBarEvents.html +114 -0
- package/dist/docs/api/interfaces/MessageEvents.html +72 -0
- package/dist/docs/api/interfaces/OlLayer.html +1 -1
- package/dist/docs/api/interfaces/OlMap.html +1 -1
- package/dist/docs/api/interfaces/OlSource.html +1 -1
- package/dist/docs/api/interfaces/OlTileLayer.html +1 -1
- package/dist/docs/api/interfaces/OlVectorLayer.html +1 -1
- package/dist/docs/api/interfaces/OlView.html +1 -1
- package/dist/docs/api/interfaces/SlideBackgroundState.html +66 -0
- package/dist/docs/api/interfaces/SlideEvents.html +78 -0
- package/dist/docs/api/interfaces/SlideNavEvents.html +72 -0
- package/dist/docs/api/interfaces/SlideshowPlayerSettings.html +182 -0
- package/dist/docs/api/interfaces/SlideshowToStorymapResult.html +60 -0
- package/dist/docs/api/interfaces/SlideshowTranslatorOptions.html +65 -0
- package/dist/docs/api/interfaces/SlideshowWarning.html +72 -0
- package/dist/docs/api/interfaces/StoryMapEvents.html +146 -0
- package/dist/docs/api/interfaces/StoryMapMarker.html +763 -0
- package/dist/docs/api/interfaces/StorySliderEvents.html +102 -0
- package/dist/docs/api/interfaces/StorymapBasemap.html +69 -0
- package/dist/docs/api/interfaces/StorymapData.html +12 -6
- package/dist/docs/api/interfaces/StorymapDataWrapper.html +2 -2
- package/dist/docs/api/interfaces/StorymapDocument.html +2 -2
- package/dist/docs/api/interfaces/StorymapError.html +3 -3
- package/dist/docs/api/interfaces/StorymapGeoreference.html +5 -5
- package/dist/docs/api/interfaces/StorymapGeoreferenceBody.html +4 -4
- package/dist/docs/api/interfaces/StorymapGroundControlPoint.html +4 -4
- package/dist/docs/api/interfaces/StorymapLanguageMap.html +2 -2
- package/dist/docs/api/interfaces/StorymapManifest.html +12 -12
- package/dist/docs/api/interfaces/StorymapManifestAnnotation.html +43 -11
- package/dist/docs/api/interfaces/StorymapManifestBackground.html +6 -6
- package/dist/docs/api/interfaces/StorymapManifestCanvas.html +65 -17
- package/dist/docs/api/interfaces/StorymapManifestContentResource.html +8 -8
- package/dist/docs/api/interfaces/StorymapManifestFeatureCollection.html +4 -4
- package/dist/docs/api/interfaces/StorymapManifestGeoreferencing.html +7 -7
- package/dist/docs/api/interfaces/StorymapManifestImageService.html +4 -4
- package/dist/docs/api/interfaces/StorymapManifestRange.html +5 -5
- package/dist/docs/api/interfaces/StorymapManifestService.html +4 -4
- package/dist/docs/api/interfaces/StorymapManifestSpecificResource.html +4 -4
- package/dist/docs/api/interfaces/StorymapManifestStatement.html +3 -3
- package/dist/docs/api/interfaces/StorymapMapOptions.html +5 -5
- package/dist/docs/api/interfaces/StorymapOptions.html +277 -75
- package/dist/docs/api/interfaces/StorymapOverlayLayer.html +42 -11
- package/dist/docs/api/interfaces/StorymapSlide.html +70 -16
- package/dist/docs/api/interfaces/StorymapSlideBackground.html +4 -4
- package/dist/docs/api/interfaces/StorymapSlideLocation.html +58 -14
- package/dist/docs/api/interfaces/StorymapSlideMarker.html +8 -9
- package/dist/docs/api/interfaces/StorymapSlideMedia.html +49 -10
- package/dist/docs/api/interfaces/StorymapSlideNarration.html +61 -9
- package/dist/docs/api/interfaces/StorymapSlideText.html +4 -4
- package/dist/docs/api/interfaces/StorymapTilejson.html +8 -8
- package/dist/docs/api/interfaces/SwipableEvents.html +102 -0
- package/dist/docs/api/interfaces/TextEvents.html +66 -0
- package/dist/docs/api/media/events.md +66 -0
- package/dist/docs/api/media/migration-from-knightlab.md +89 -49
- package/dist/docs/api/media/slideshow.md +109 -0
- package/dist/docs/api/media/storymap-as-iiif-manifest.md +1007 -0
- package/dist/docs/api/modules.html +6 -6
- package/dist/docs/api/types/ContentState.html +3 -3
- package/dist/docs/api/types/FiredEvent.html +43 -0
- package/dist/docs/api/types/ImagereadyKind.html +1 -1
- package/dist/docs/api/types/MediaTypeMatch.html +5 -5
- package/dist/docs/api/types/SwipeDirection.html +36 -0
- package/dist/docs/api/types/TileSourceFactory.html +1 -1
- package/dist/docs/authoring.html +20 -7
- package/dist/docs/events.html +263 -0
- package/dist/docs/iiif.html +136 -22
- package/dist/docs/migration.html +66 -19
- package/dist/docs/readme.html +113 -12
- package/dist/embed/index.html +7 -0
- package/dist/examples/font-css-false.json +33 -0
- package/dist/examples/issue-image-region.json +1 -1
- package/dist/examples/layers-control.json +60 -0
- package/dist/examples/map-template-provider.json +36 -0
- package/dist/examples/multi-media.json +71 -0
- package/dist/examples-iiif/annotated-image.json +16 -0
- package/dist/examples-iiif/font-css-false.json +114 -0
- package/dist/examples-iiif/issue-image-region.json +1 -1
- package/dist/examples-iiif/layers-control.json +226 -0
- package/dist/examples-iiif/map-template-provider.json +133 -0
- package/dist/examples-iiif/multi-media.json +237 -0
- package/dist/examples-slideshow/single.json +79 -0
- package/dist/examples-slideshow/static/pixel.png +0 -0
- package/dist/examples-slideshow/static.json +44 -0
- package/dist/harness-multi.html +3 -3
- package/dist/harness.html +4 -3
- package/dist/index.html +3 -2
- package/dist/js/storymap.d.ts +1326 -289
- package/dist/js/storymap.js +6690 -4784
- package/dist/js/storymap.js.map +1 -1
- package/dist/navplace-properties.json +14 -0
- package/dist/site.css +54 -36
- package/docs/migration-from-knightlab.md +89 -49
- package/docs/storymap-as-iiif-manifest.md +199 -126
- package/package.json +4 -2
- package/schema/storymap.schema.json +334 -34
- package/src/core/Util.ts +3 -1
- package/src/core/mixins.ts +42 -28
- package/src/language/Language.ts +7 -0
- package/src/language/locale/.expected-gaps.json +287 -27
- package/src/language/locale/de.json +11 -1
- package/src/language/locale/en.json +11 -1
- package/src/main.ts +51 -0
- package/src/map/Map.ts +44 -6
- package/src/map/MapMarker.ts +16 -3
- package/src/map/openlayers/Map.OpenLayers.ts +787 -62
- package/src/map/openlayers/MapMarker.OpenLayers.ts +113 -13
- package/src/map/slideView.ts +149 -0
- package/src/map/types.ts +2 -0
- package/src/media/EmbedUtil.ts +27 -2
- package/src/media/Media.ts +27 -4
- package/src/media/embedId.ts +6 -0
- package/src/media/types/Flickr.ts +14 -3
- package/src/media/types/HtmlMedia.ts +66 -0
- package/src/media/types/Image.ts +15 -3
- package/src/media/types/SoundCloud.ts +1 -1
- package/src/media/types/Text.ts +27 -8
- package/src/media/types/Twitter.ts +9 -7
- package/src/media/types/Vimeo.ts +1 -1
- package/src/media/types/YouTube.ts +2 -2
- package/src/scss/Typography.scss +15 -15
- package/src/scss/VCO.StoryMap.scss +173 -8
- package/src/scss/core/Reset.scss +5 -18
- package/src/scss/map/VCO.Map.scss +1 -1
- package/src/scss/map/VCO.MapMarker.scss +20 -9
- package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +7 -4
- package/src/scss/media/VCO.Media.scss +7 -7
- package/src/scss/media/types/VCO.Media.Blockquote.scss +1 -1
- package/src/scss/media/types/VCO.Media.Text.scss +5 -5
- package/src/scss/media/types/VCO.Media.Twitter.scss +4 -4
- package/src/scss/media/types/VCO.Media.Wikipedia.scss +2 -19
- package/src/scss/site/site.scss +55 -36
- package/src/scss/slider/VCO.Slide.scss +49 -11
- package/src/scss/slider/VCO.SlideNav.scss +23 -7
- package/src/scss/slider/VCO.StorySlider.scss +50 -0
- package/src/scss/snapmap/VCO.SnapMap.scss +3 -3
- package/src/scss/ui/VCO.Consent.scss +20 -14
- package/src/scss/ui/VCO.Layers.scss +119 -0
- package/src/scss/ui/VCO.MenuBar.Button.scss +140 -19
- package/src/scss/ui/VCO.MenuBar.scss +9 -2
- package/src/scss/ui/VCO.Message.scss +14 -0
- package/src/slider/Slide.ts +232 -74
- package/src/slider/SlideNav.ts +15 -4
- package/src/slider/StorySlider.ts +203 -22
- package/src/storymap/Consent.ts +22 -0
- package/src/storymap/StoryMap.ts +944 -131
- package/src/storymap/content-state.ts +3 -8
- package/src/storymap/font-css.ts +72 -0
- package/src/storymap/from-slideshow.ts +785 -0
- package/src/storymap/iiif-shared.ts +228 -0
- package/src/storymap/iiif.ts +289 -100
- package/src/storymap/to-iiif.ts +131 -44
- package/src/types.ts +382 -17
- package/src/ui/LayersControl.ts +302 -0
- package/src/ui/MenuBar.ts +232 -21
- package/src/ui/Message.ts +36 -3
- package/src/ui/Swipable.ts +25 -7
- package/dist/assets/harness-Cr0rYpvt.js +0 -1
- package/dist/assets/main-DXalctuR.js +0 -17
- package/src/scss/VCO.StoryMap.Dark.scss +0 -150
- package/src/scss/Variables.Dark.scss +0 -57
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import Dom from "../dom/Dom";
|
|
2
|
+
import { DomEvent } from "../dom/DomEvent";
|
|
3
|
+
import { Evented, type EventedInstance } from "../core/mixins";
|
|
4
|
+
import { Language } from "../language/Language";
|
|
5
|
+
|
|
6
|
+
/* LayersControl
|
|
7
|
+
Basemap/overlay switcher panel for the menubar (show_layers_control).
|
|
8
|
+
Owns the disclosure button and the panel; the host of the layers state
|
|
9
|
+
(StoryMap, via MenuBar) supplies rows through the delegate and executes
|
|
10
|
+
toggles. Fires basemapchange/overlaychange, which MenuBar forwards.
|
|
11
|
+
================================================== */
|
|
12
|
+
|
|
13
|
+
export interface LayersOverlayRow {
|
|
14
|
+
kind: "overlay";
|
|
15
|
+
/** Built-layer index: malformed entries are skipped, like setOverlayVisible. */
|
|
16
|
+
index: number;
|
|
17
|
+
label: string;
|
|
18
|
+
checked: boolean;
|
|
19
|
+
locked: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LayersBasemapRow {
|
|
23
|
+
kind: "basemap";
|
|
24
|
+
map_type: string;
|
|
25
|
+
label: string;
|
|
26
|
+
checked: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type LayersRow = LayersOverlayRow | LayersBasemapRow;
|
|
30
|
+
|
|
31
|
+
export interface LayersControlDelegate {
|
|
32
|
+
/** Current rows, basemaps first. Empty while there is nothing to switch. */
|
|
33
|
+
rows(): LayersRow[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LayersControlEvents {
|
|
37
|
+
basemapchange: { map_type: string };
|
|
38
|
+
overlaychange: { index: number; visible: boolean };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Panel id suffix: unique per control so two viewers never share one. */
|
|
42
|
+
let layersPanelSeq = 0;
|
|
43
|
+
|
|
44
|
+
class LayersControlBase {
|
|
45
|
+
declare "_el": {
|
|
46
|
+
container: HTMLElement;
|
|
47
|
+
button: HTMLButtonElement;
|
|
48
|
+
panel: HTMLElement;
|
|
49
|
+
};
|
|
50
|
+
declare "_menubar": HTMLElement;
|
|
51
|
+
declare "_delegate": LayersControlDelegate | null;
|
|
52
|
+
declare "_rows": LayersRow[];
|
|
53
|
+
declare "_open": boolean;
|
|
54
|
+
declare "_panel_id": string;
|
|
55
|
+
declare "_onKeyDownBound": ((e: KeyboardEvent) => void) | null;
|
|
56
|
+
declare "_onPointerDownBound": ((e: Event) => void) | null;
|
|
57
|
+
declare "fire": EventedInstance<LayersControlEvents>["fire"];
|
|
58
|
+
|
|
59
|
+
constructor(menubar: HTMLElement) {
|
|
60
|
+
this._menubar = menubar;
|
|
61
|
+
const panel_id = `vco-layers-panel-${++layersPanelSeq}`;
|
|
62
|
+
this._panel_id = panel_id;
|
|
63
|
+
this._delegate = null;
|
|
64
|
+
this._rows = [];
|
|
65
|
+
this._open = false;
|
|
66
|
+
this._onKeyDownBound = null;
|
|
67
|
+
this._onPointerDownBound = null;
|
|
68
|
+
|
|
69
|
+
const button = Dom.create(
|
|
70
|
+
"button",
|
|
71
|
+
"vco-menubar-button vco-menubar-layers",
|
|
72
|
+
menubar,
|
|
73
|
+
) as HTMLButtonElement;
|
|
74
|
+
button.setAttribute("type", "button");
|
|
75
|
+
button.setAttribute("aria-expanded", "false");
|
|
76
|
+
button.setAttribute("aria-controls", panel_id);
|
|
77
|
+
button.textContent = Language.buttons.layers;
|
|
78
|
+
DomEvent.addListener(button, "click", this._onButtonClick, this);
|
|
79
|
+
this._el = {
|
|
80
|
+
container: menubar,
|
|
81
|
+
button,
|
|
82
|
+
panel: document.createElement("div"),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const panel = this._el.panel;
|
|
86
|
+
panel.id = panel_id;
|
|
87
|
+
panel.className = "vco-layers";
|
|
88
|
+
panel.hidden = true;
|
|
89
|
+
menubar.appendChild(panel);
|
|
90
|
+
// hidden until the first refresh() hands over rows (consent-denied
|
|
91
|
+
// tiles and mapless stories never get any)
|
|
92
|
+
button.style.display = "none";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Public
|
|
96
|
+
================================================== */
|
|
97
|
+
|
|
98
|
+
/** Hand the control its state source (StoryMap wires this via MenuBar). */
|
|
99
|
+
setDelegate(delegate: LayersControlDelegate): void {
|
|
100
|
+
this._delegate = delegate;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Re-pull rows from the delegate and sync the panel in place. */
|
|
104
|
+
refresh(): void {
|
|
105
|
+
const rows = this._delegate?.rows() ?? [];
|
|
106
|
+
// nothing to switch (consent-denied tiles, mapless story): no
|
|
107
|
+
// control at all rather than a panel of dead rows
|
|
108
|
+
this._el.button.style.display = rows.length === 0 ? "none" : "";
|
|
109
|
+
if (rows.length === 0) {
|
|
110
|
+
this.close();
|
|
111
|
+
this._rows = rows;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (this._sameRows(rows)) {
|
|
115
|
+
this._updateRows(rows);
|
|
116
|
+
} else {
|
|
117
|
+
this._buildRows(rows);
|
|
118
|
+
}
|
|
119
|
+
this._rows = rows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Re-resolve row labels (function labels repaint on language change). */
|
|
123
|
+
refreshLabels(): void {
|
|
124
|
+
this._el.button.textContent = Language.buttons.layers;
|
|
125
|
+
this.refresh();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
open(): void {
|
|
129
|
+
if (this._rows.length === 0) return;
|
|
130
|
+
this._open = true;
|
|
131
|
+
this._el.panel.hidden = false;
|
|
132
|
+
this._el.button.setAttribute("aria-expanded", "true");
|
|
133
|
+
this._el.button.classList.add("vco-active");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
close(): void {
|
|
137
|
+
this._open = false;
|
|
138
|
+
this._el.panel.hidden = true;
|
|
139
|
+
this._el.button.setAttribute("aria-expanded", "false");
|
|
140
|
+
this._el.button.classList.remove("vco-active");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Release the document listeners and drop the nodes. Called by
|
|
145
|
+
* `MenuBar.dispose()`; the control must not be used afterwards.
|
|
146
|
+
*/
|
|
147
|
+
dispose(): void {
|
|
148
|
+
if (this._onKeyDownBound) {
|
|
149
|
+
document.removeEventListener("keydown", this._onKeyDownBound);
|
|
150
|
+
this._onKeyDownBound = null;
|
|
151
|
+
}
|
|
152
|
+
if (this._onPointerDownBound) {
|
|
153
|
+
document.removeEventListener("pointerdown", this._onPointerDownBound);
|
|
154
|
+
this._onPointerDownBound = null;
|
|
155
|
+
}
|
|
156
|
+
this._el.button.remove();
|
|
157
|
+
this._el.panel.remove();
|
|
158
|
+
this._delegate = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Events
|
|
162
|
+
================================================== */
|
|
163
|
+
|
|
164
|
+
_onButtonClick(): void {
|
|
165
|
+
if (this._open) {
|
|
166
|
+
this.close();
|
|
167
|
+
} else {
|
|
168
|
+
this.open();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_onRowChange(e: Event, row: LayersRow): void {
|
|
173
|
+
const input = e.target as HTMLInputElement | null;
|
|
174
|
+
if (!input) return;
|
|
175
|
+
if (row.kind === "overlay") {
|
|
176
|
+
if (row.locked) {
|
|
177
|
+
// disabled inputs never fire from user input; a programmatic
|
|
178
|
+
// dispatch still reaches this handler, so ignore it loudly
|
|
179
|
+
// by restoring the checked state instead of toggling
|
|
180
|
+
input.checked = row.checked;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.fire("overlaychange", { index: row.index, visible: input.checked });
|
|
184
|
+
} else {
|
|
185
|
+
if (!input.checked) return;
|
|
186
|
+
this.fire("basemapchange", { map_type: row.map_type });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_onPanelKeyDown(e: KeyboardEvent): void {
|
|
191
|
+
if (e.key !== "Escape" || !this._open) return;
|
|
192
|
+
// Close from anywhere while open: Safari/WebKit does not move focus
|
|
193
|
+
// on mouse clicks, so a focus-scoped handler would strand mouse
|
|
194
|
+
// visitors. No stopPropagation — a host Escape handler may run too.
|
|
195
|
+
// Focus returns to the button only when it was inside the panel;
|
|
196
|
+
// a mouse visitor's focus (body) is left alone.
|
|
197
|
+
const inside = this._el.panel.contains(document.activeElement);
|
|
198
|
+
this.close();
|
|
199
|
+
if (inside) this._el.button.focus();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_onDocumentPointerDown(e: Event): void {
|
|
203
|
+
if (!this._open) return;
|
|
204
|
+
if (this._el.container.contains(e.target as Node | null)) return;
|
|
205
|
+
this.close();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Private Methods
|
|
209
|
+
================================================== */
|
|
210
|
+
|
|
211
|
+
_sameRows(rows: LayersRow[]): boolean {
|
|
212
|
+
if (rows.length !== this._rows.length) return false;
|
|
213
|
+
return rows.every((row, i) => {
|
|
214
|
+
const prev = this._rows[i];
|
|
215
|
+
if (prev.kind !== row.kind) return false;
|
|
216
|
+
return row.kind === "overlay"
|
|
217
|
+
? (prev as LayersOverlayRow).index === row.index
|
|
218
|
+
: (prev as LayersBasemapRow).map_type === row.map_type;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_updateRows(rows: LayersRow[]): void {
|
|
223
|
+
const inputs = this._el.panel.querySelectorAll("input");
|
|
224
|
+
rows.forEach((row, position) => {
|
|
225
|
+
const input = inputs[position] as HTMLInputElement | undefined;
|
|
226
|
+
const label = input?.closest("label");
|
|
227
|
+
if (!input || !label) return;
|
|
228
|
+
input.checked = row.checked;
|
|
229
|
+
input.disabled = row.kind === "overlay" && row.locked;
|
|
230
|
+
const text = label.querySelector(".vco-layers-label");
|
|
231
|
+
if (text) text.textContent = row.label;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_buildRows(rows: LayersRow[]): void {
|
|
236
|
+
const panel = this._el.panel;
|
|
237
|
+
panel.replaceChildren();
|
|
238
|
+
const basemaps = rows.filter((row): row is LayersBasemapRow => row.kind === "basemap");
|
|
239
|
+
const overlays = rows.filter((row): row is LayersOverlayRow => row.kind === "overlay");
|
|
240
|
+
if (basemaps.length > 0) {
|
|
241
|
+
panel.appendChild(this._buildGroup("basemap", basemaps));
|
|
242
|
+
}
|
|
243
|
+
if (overlays.length > 0) {
|
|
244
|
+
panel.appendChild(this._buildGroup("overlay", overlays));
|
|
245
|
+
}
|
|
246
|
+
if (!this._onKeyDownBound) {
|
|
247
|
+
this._onKeyDownBound = (e: KeyboardEvent) => this._onPanelKeyDown(e);
|
|
248
|
+
document.addEventListener("keydown", this._onKeyDownBound);
|
|
249
|
+
}
|
|
250
|
+
if (!this._onPointerDownBound) {
|
|
251
|
+
this._onPointerDownBound = (e: Event) => this._onDocumentPointerDown(e);
|
|
252
|
+
document.addEventListener("pointerdown", this._onPointerDownBound);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
_buildGroup(kind: "basemap" | "overlay", rows: LayersRow[]): HTMLElement {
|
|
257
|
+
const group = document.createElement("fieldset");
|
|
258
|
+
group.className = "vco-layers-group";
|
|
259
|
+
const legend = document.createElement("legend");
|
|
260
|
+
legend.textContent =
|
|
261
|
+
kind === "basemap"
|
|
262
|
+
? Language.buttons.layers_basemaps
|
|
263
|
+
: Language.buttons.layers_overlays;
|
|
264
|
+
group.appendChild(legend);
|
|
265
|
+
for (const row of rows) {
|
|
266
|
+
group.appendChild(this._buildRow(row));
|
|
267
|
+
}
|
|
268
|
+
return group;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_buildRow(row: LayersRow): HTMLElement {
|
|
272
|
+
const label = document.createElement("label");
|
|
273
|
+
const input = document.createElement("input");
|
|
274
|
+
input.setAttribute("type", row.kind === "basemap" ? "radio" : "checkbox");
|
|
275
|
+
if (row.kind === "basemap") {
|
|
276
|
+
input.setAttribute("name", "basemap");
|
|
277
|
+
}
|
|
278
|
+
input.checked = row.checked;
|
|
279
|
+
if (row.kind === "overlay" && row.locked) {
|
|
280
|
+
input.disabled = true;
|
|
281
|
+
}
|
|
282
|
+
const text = document.createElement("span");
|
|
283
|
+
text.className = "vco-layers-label";
|
|
284
|
+
text.textContent = row.label;
|
|
285
|
+
label.appendChild(input);
|
|
286
|
+
label.appendChild(text);
|
|
287
|
+
DomEvent.addListener(input, "change", (e: Event) => this._onRowChange(e, row), this);
|
|
288
|
+
return label;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const EventedLayersControlBase = Evented<LayersControlEvents, typeof LayersControlBase>(
|
|
293
|
+
LayersControlBase,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
export default class LayersControl extends Evented<LayersControlEvents, typeof LayersControlBase>(
|
|
297
|
+
EventedLayersControlBase,
|
|
298
|
+
) {
|
|
299
|
+
constructor(...args: ConstructorParameters<typeof LayersControlBase>) {
|
|
300
|
+
super(...args);
|
|
301
|
+
}
|
|
302
|
+
}
|
package/src/ui/MenuBar.ts
CHANGED
|
@@ -4,8 +4,10 @@ import Dom from "../dom/Dom";
|
|
|
4
4
|
import { easeInOutQuint } from "../animation/easings";
|
|
5
5
|
|
|
6
6
|
import { DomEvent } from "../dom/DomEvent";
|
|
7
|
+
import { PROGRESSBAR_STYLES } from "../types";
|
|
7
8
|
import { Browser } from "../core/Browser";
|
|
8
9
|
import { Language, currentLocale } from "../language/Language";
|
|
10
|
+
import LayersControl, { type LayersControlDelegate } from "./LayersControl";
|
|
9
11
|
|
|
10
12
|
/* MenuBar
|
|
11
13
|
Buttons, progress and distance display for the storymap
|
|
@@ -20,13 +22,30 @@ interface MenuBarOptions {
|
|
|
20
22
|
[key: string]: unknown;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
export interface MenuBarEvents {
|
|
26
|
+
overview: Event;
|
|
27
|
+
back_to_start: Event;
|
|
28
|
+
fullscreen: Event;
|
|
29
|
+
autoplay_toggle: undefined;
|
|
30
|
+
progress_go: { slide: number };
|
|
31
|
+
collapse: { y: number; collapsed: boolean };
|
|
32
|
+
basemapchange: { map_type: string };
|
|
33
|
+
overlaychange: { index: number; visible: boolean };
|
|
34
|
+
loaded: undefined;
|
|
35
|
+
added: undefined;
|
|
36
|
+
removed: undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
class MenuBarBase {
|
|
24
40
|
declare "_el": Record<string, HTMLElement>;
|
|
25
41
|
declare "collapsed": boolean;
|
|
26
42
|
declare "options": MenuBarOptions;
|
|
27
43
|
declare "animator": Record<string, unknown>;
|
|
28
|
-
declare "fire": EventedInstance["fire"];
|
|
44
|
+
declare "fire": EventedInstance<MenuBarEvents>["fire"];
|
|
29
45
|
_fullscreenActive = false;
|
|
46
|
+
_autoplayStopped = false;
|
|
47
|
+
/** The layer switcher, if `show_layers_control` is on. */
|
|
48
|
+
declare "_layersControl": LayersControl | null;
|
|
30
49
|
|
|
31
50
|
/* Constructor
|
|
32
51
|
================================================== */
|
|
@@ -76,6 +95,8 @@ class MenuBarBase {
|
|
|
76
95
|
// Animation
|
|
77
96
|
this.animator = {};
|
|
78
97
|
|
|
98
|
+
this._layersControl = null;
|
|
99
|
+
|
|
79
100
|
// Merge Data and Options
|
|
80
101
|
mergeData(this.options, options);
|
|
81
102
|
|
|
@@ -116,18 +137,24 @@ class MenuBarBase {
|
|
|
116
137
|
this._el.button_fullscreen.innerHTML = `<span class='${icon}'></span>`;
|
|
117
138
|
} else {
|
|
118
139
|
const label = active ? Language.buttons.exit_fullscreen : Language.buttons.fullscreen;
|
|
119
|
-
this._el.button_fullscreen.innerHTML = `${label}
|
|
140
|
+
this._el.button_fullscreen.innerHTML = `${label}<span class='${icon}'></span>`;
|
|
120
141
|
}
|
|
121
142
|
}
|
|
122
143
|
|
|
123
144
|
/**
|
|
124
145
|
* Repaint the overview button label. An image map has no interactive
|
|
125
|
-
* overview, so it gets the shorter wording.
|
|
146
|
+
* overview, so it gets the shorter wording. Icon-only on mobile.
|
|
126
147
|
*/
|
|
127
148
|
_renderOverviewLabel(): void {
|
|
128
|
-
|
|
149
|
+
const label = this.options.map_as_image
|
|
129
150
|
? Language.buttons.overview
|
|
130
151
|
: Language.buttons.map_overview;
|
|
152
|
+
if (Browser.mobile) {
|
|
153
|
+
this._el.button_overview.innerHTML = "<span class='vco-icon-mappin'></span>";
|
|
154
|
+
this._el.button_overview.setAttribute("aria-label", label);
|
|
155
|
+
} else {
|
|
156
|
+
this._el.button_overview.innerHTML = `${label}<span class='vco-icon-mappin'></span>`;
|
|
157
|
+
}
|
|
131
158
|
}
|
|
132
159
|
|
|
133
160
|
/**
|
|
@@ -136,7 +163,7 @@ class MenuBarBase {
|
|
|
136
163
|
_renderBackToStartLabel(): void {
|
|
137
164
|
this._el.button_backtostart.innerHTML = Browser.mobile
|
|
138
165
|
? "<span class='vco-icon-goback'></span>"
|
|
139
|
-
: Language.buttons.backtostart + "
|
|
166
|
+
: Language.buttons.backtostart + "<span class='vco-icon-goback'></span>";
|
|
140
167
|
}
|
|
141
168
|
|
|
142
169
|
/**
|
|
@@ -150,38 +177,177 @@ class MenuBarBase {
|
|
|
150
177
|
const text = collapsed
|
|
151
178
|
? Language.buttons.uncollapse_toggle
|
|
152
179
|
: Language.buttons.collapse_toggle;
|
|
153
|
-
this._el.button_collapse_toggle.innerHTML = `${text}
|
|
180
|
+
this._el.button_collapse_toggle.innerHTML = `${text}<span class='vco-icon-${arrow}'></span>`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Reflect the autoplay state in the toggle label.
|
|
186
|
+
*/
|
|
187
|
+
setAutoplayState(stopped: boolean): void {
|
|
188
|
+
this._autoplayStopped = stopped;
|
|
189
|
+
if (!this._el.button_autoplay) return;
|
|
190
|
+
const label = stopped ? Language.buttons.autoplay_play : Language.buttons.autoplay_pause;
|
|
191
|
+
if (Browser.mobile) {
|
|
192
|
+
this._el.button_autoplay.setAttribute("aria-label", label);
|
|
193
|
+
} else {
|
|
194
|
+
this._el.button_autoplay.textContent = label;
|
|
154
195
|
}
|
|
196
|
+
this._el.button_autoplay.setAttribute("aria-pressed", String(!stopped));
|
|
155
197
|
}
|
|
156
198
|
|
|
157
199
|
/**
|
|
158
200
|
* Repaint every text label from the active language (see `setLanguage`).
|
|
159
|
-
* Icon-only mobile buttons carry
|
|
201
|
+
* Icon-only mobile buttons carry text via aria-label instead.
|
|
160
202
|
*/
|
|
161
203
|
refreshLabels(): void {
|
|
204
|
+
// panel rows are always text, so the control repaints even where
|
|
205
|
+
// the buttons below go icon-only
|
|
206
|
+
this._layersControl?.refreshLabels();
|
|
162
207
|
if (Browser.mobile) {
|
|
208
|
+
this._renderMobileLabels();
|
|
163
209
|
return;
|
|
164
210
|
}
|
|
165
211
|
this._renderOverviewLabel();
|
|
166
212
|
this._renderBackToStartLabel();
|
|
167
213
|
this.setFullscreenState(this._fullscreenActive);
|
|
214
|
+
this.setAutoplayState(this._autoplayStopped);
|
|
168
215
|
this._renderCollapseLabel(this.collapsed);
|
|
169
216
|
}
|
|
170
217
|
|
|
171
218
|
/**
|
|
172
|
-
*
|
|
219
|
+
* Icon-only mobile buttons expose their labels to assistive tech since
|
|
220
|
+
* the visible text is icon glyphs.
|
|
221
|
+
*/
|
|
222
|
+
_renderMobileLabels(): void {
|
|
223
|
+
const labels: [string, string][] = [
|
|
224
|
+
[
|
|
225
|
+
"button_overview",
|
|
226
|
+
this.options.map_as_image
|
|
227
|
+
? Language.buttons.overview
|
|
228
|
+
: Language.buttons.map_overview,
|
|
229
|
+
],
|
|
230
|
+
["button_backtostart", Language.buttons.backtostart],
|
|
231
|
+
[
|
|
232
|
+
"button_collapse_toggle",
|
|
233
|
+
this.collapsed
|
|
234
|
+
? Language.buttons.uncollapse_toggle
|
|
235
|
+
: Language.buttons.collapse_toggle,
|
|
236
|
+
],
|
|
237
|
+
[
|
|
238
|
+
"button_fullscreen",
|
|
239
|
+
this._fullscreenActive
|
|
240
|
+
? Language.buttons.exit_fullscreen
|
|
241
|
+
: Language.buttons.fullscreen,
|
|
242
|
+
],
|
|
243
|
+
[
|
|
244
|
+
"button_autoplay",
|
|
245
|
+
this._autoplayStopped
|
|
246
|
+
? Language.buttons.autoplay_play
|
|
247
|
+
: Language.buttons.autoplay_pause,
|
|
248
|
+
],
|
|
249
|
+
];
|
|
250
|
+
for (const [key, label] of labels) {
|
|
251
|
+
this._el[key]?.setAttribute("aria-label", label);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Hand the layer switcher its state source (StoryMap wires this). */
|
|
256
|
+
setLayersDelegate(delegate: LayersControlDelegate): void {
|
|
257
|
+
this._layersControl?.setDelegate(delegate);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Re-pull the switcher rows (overlays, basemaps, consent state). */
|
|
261
|
+
refreshLayers(): void {
|
|
262
|
+
this._layersControl?.refresh();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Update the progress indicator; no-op when disabled. The `progressbar`
|
|
267
|
+
* option selects the style (slideshow `progressbar`): the classic fill
|
|
268
|
+
* `bar` (default), height variants `block`/`thinblock`, or per-slide
|
|
269
|
+
* `dots`/`squares` buttons that jump to their slide.
|
|
173
270
|
*/
|
|
174
271
|
setProgress(current: number, total: number): void {
|
|
175
|
-
|
|
272
|
+
const style = this._progressStyle();
|
|
273
|
+
if (style === "off" || !this._el.progress) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (style === "dots" || style === "squares") {
|
|
277
|
+
this._renderProgressSteps(style, current, total);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (!this._el.progress_fill) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// a single slide (or none) is fully read by definition, but a
|
|
284
|
+
// progressbar over zero slides is invalid ARIA — clear it instead
|
|
285
|
+
if (!(total > 1)) {
|
|
286
|
+
this._el.progress_fill.style.width = "100%";
|
|
287
|
+
this._el.progress.removeAttribute("role");
|
|
288
|
+
this._el.progress.removeAttribute("aria-valuenow");
|
|
289
|
+
this._el.progress.removeAttribute("aria-valuemin");
|
|
290
|
+
this._el.progress.removeAttribute("aria-valuemax");
|
|
291
|
+
this._el.progress.setAttribute("aria-label", "1 / 1");
|
|
176
292
|
return;
|
|
177
293
|
}
|
|
178
|
-
const percent =
|
|
294
|
+
const percent = Math.round((Math.min(current, total - 1) / (total - 1)) * 100);
|
|
179
295
|
this._el.progress_fill.style.width = percent + "%";
|
|
180
296
|
this._el.progress.setAttribute("role", "progressbar");
|
|
181
|
-
this._el.progress.setAttribute("aria-valuenow", String(current + 1));
|
|
297
|
+
this._el.progress.setAttribute("aria-valuenow", String(Math.min(current, total - 1) + 1));
|
|
182
298
|
this._el.progress.setAttribute("aria-valuemin", "1");
|
|
183
299
|
this._el.progress.setAttribute("aria-valuemax", String(total));
|
|
184
|
-
this._el.progress.setAttribute(
|
|
300
|
+
this._el.progress.setAttribute(
|
|
301
|
+
"aria-label",
|
|
302
|
+
`${Math.min(current, total - 1) + 1} / ${total}`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** The resolved progress style: `progressbar` wins, `show_progress` decides the default. */
|
|
307
|
+
_progressStyle(): "off" | "bar" | "dots" | "squares" | "block" | "thinblock" {
|
|
308
|
+
const raw = this.options.progressbar as string | boolean | undefined;
|
|
309
|
+
if (raw === false || raw === "off") return "off";
|
|
310
|
+
if (typeof raw === "string" && (PROGRESSBAR_STYLES as readonly string[]).includes(raw)) {
|
|
311
|
+
return raw as "bar" | "dots" | "squares" | "block" | "thinblock";
|
|
312
|
+
}
|
|
313
|
+
return this.options.show_progress ? "bar" : "off";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Per-slide progress buttons for the `dots`/`squares` styles. */
|
|
317
|
+
_renderProgressSteps(style: "dots" | "squares", current: number, total: number): void {
|
|
318
|
+
const container = this._el.progress;
|
|
319
|
+
if (!(total > 0)) {
|
|
320
|
+
container.innerHTML = "";
|
|
321
|
+
container.removeAttribute("role");
|
|
322
|
+
container.setAttribute("aria-label", "No slides");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const built = Number(container.getAttribute("data-steps") ?? "0");
|
|
326
|
+
if (built !== total) {
|
|
327
|
+
container.innerHTML = "";
|
|
328
|
+
container.setAttribute("role", "tablist");
|
|
329
|
+
container.setAttribute("aria-label", `Slides 1 / ${total}`);
|
|
330
|
+
for (let i = 0; i < total; i++) {
|
|
331
|
+
const step = document.createElement("button");
|
|
332
|
+
step.setAttribute("type", "button");
|
|
333
|
+
step.className = `vco-menubar-progress-step vco-menubar-progress-${style}`;
|
|
334
|
+
step.setAttribute("role", "tab");
|
|
335
|
+
step.setAttribute("aria-label", `Slide ${i + 1} / ${total}`);
|
|
336
|
+
step.setAttribute("data-slide", String(i));
|
|
337
|
+
step.addEventListener("click", () => {
|
|
338
|
+
this.fire("progress_go", { slide: i });
|
|
339
|
+
});
|
|
340
|
+
container.appendChild(step);
|
|
341
|
+
}
|
|
342
|
+
container.setAttribute("data-steps", String(total));
|
|
343
|
+
}
|
|
344
|
+
const steps = container.querySelectorAll("[data-slide]");
|
|
345
|
+
steps.forEach((entry, index) => {
|
|
346
|
+
const el = entry as HTMLElement;
|
|
347
|
+
el.setAttribute("aria-selected", String(index === current));
|
|
348
|
+
el.classList.toggle("vco-active", index === current);
|
|
349
|
+
el.classList.toggle("vco-past", index < current);
|
|
350
|
+
});
|
|
185
351
|
}
|
|
186
352
|
|
|
187
353
|
/**
|
|
@@ -226,6 +392,18 @@ class MenuBarBase {
|
|
|
226
392
|
this.fire("fullscreen", e);
|
|
227
393
|
}
|
|
228
394
|
|
|
395
|
+
_onButtonAutoplay() {
|
|
396
|
+
this.fire("autoplay_toggle");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
_onLayersBasemap(e: { map_type: string }) {
|
|
400
|
+
this.fire("basemapchange", { map_type: e.map_type });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
_onLayersOverlay(e: { index: number; visible: boolean }) {
|
|
404
|
+
this.fire("overlaychange", { index: e.index, visible: e.visible });
|
|
405
|
+
}
|
|
406
|
+
|
|
229
407
|
_onButtonCollapseMap(e: Event) {
|
|
230
408
|
if (this.collapsed) {
|
|
231
409
|
this.collapsed = false;
|
|
@@ -283,6 +461,19 @@ class MenuBarBase {
|
|
|
283
461
|
this._el.button_fullscreen.style.display = "none";
|
|
284
462
|
}
|
|
285
463
|
|
|
464
|
+
// Autoplay pause/resume toggle (only when the story auto-advances)
|
|
465
|
+
if (typeof this.options.autoplay === "number" && this.options.autoplay > 0) {
|
|
466
|
+
this._el.button_autoplay = Dom.create(
|
|
467
|
+
"button",
|
|
468
|
+
"vco-menubar-button vco-menubar-autoplay",
|
|
469
|
+
this._el.container,
|
|
470
|
+
);
|
|
471
|
+
this._el.button_autoplay.setAttribute("type", "button");
|
|
472
|
+
this._el.button_autoplay.setAttribute("aria-pressed", "true");
|
|
473
|
+
DomEvent.addListener(this._el.button_autoplay, "click", this._onButtonAutoplay, this);
|
|
474
|
+
this.setAutoplayState(false);
|
|
475
|
+
}
|
|
476
|
+
|
|
286
477
|
this._el.button_collapse_toggle = Dom.create(
|
|
287
478
|
"button",
|
|
288
479
|
"vco-menubar-button",
|
|
@@ -296,15 +487,15 @@ class MenuBarBase {
|
|
|
296
487
|
this,
|
|
297
488
|
);
|
|
298
489
|
|
|
299
|
-
|
|
300
|
-
this._el.button_overview.innerHTML = Language.buttons.overview;
|
|
301
|
-
} else {
|
|
302
|
-
this._el.button_overview.innerHTML = Language.buttons.map_overview;
|
|
303
|
-
}
|
|
490
|
+
this._renderOverviewLabel();
|
|
304
491
|
|
|
305
|
-
// Progress indicator (issue #247)
|
|
306
|
-
if (this.
|
|
492
|
+
// Progress indicator (issue #247; dots/squares/block/thinblock via progressbar)
|
|
493
|
+
if (this._progressStyle() !== "off") {
|
|
307
494
|
this._el.progress = Dom.create("span", "vco-menubar-progress", this._el.container);
|
|
495
|
+
const style = this._progressStyle();
|
|
496
|
+
if (style !== "bar") {
|
|
497
|
+
this._el.progress.classList.add(`vco-menubar-progress-${style}`);
|
|
498
|
+
}
|
|
308
499
|
this._el.progress_fill = Dom.create(
|
|
309
500
|
"span",
|
|
310
501
|
"vco-menubar-progress-fill",
|
|
@@ -326,7 +517,7 @@ class MenuBarBase {
|
|
|
326
517
|
this._renderBackToStartLabel();
|
|
327
518
|
this._renderCollapseLabel(this.collapsed);
|
|
328
519
|
this._el.button_fullscreen.innerHTML =
|
|
329
|
-
Language.buttons.fullscreen + "
|
|
520
|
+
Language.buttons.fullscreen + "<span class='vco-icon-resize-full'></span>";
|
|
330
521
|
}
|
|
331
522
|
|
|
332
523
|
if (this.options.layout === "landscape" || this.options.map_type === "none") {
|
|
@@ -335,6 +526,15 @@ class MenuBarBase {
|
|
|
335
526
|
// on screen changes.
|
|
336
527
|
this._el.button_collapse_toggle.style.display = "none";
|
|
337
528
|
}
|
|
529
|
+
|
|
530
|
+
// Layer switcher (opt-in): the control renders nothing until a
|
|
531
|
+
// delegate hands it rows, so consent-denied and mapless stories
|
|
532
|
+
// show no button at all
|
|
533
|
+
if (this.options.show_layers_control) {
|
|
534
|
+
this._layersControl = new LayersControl(this._el.container);
|
|
535
|
+
this._layersControl.on("basemapchange", this._onLayersBasemap, this);
|
|
536
|
+
this._layersControl.on("overlaychange", this._onLayersOverlay, this);
|
|
537
|
+
}
|
|
338
538
|
}
|
|
339
539
|
|
|
340
540
|
// Update Display
|
|
@@ -356,6 +556,7 @@ class MenuBarBase {
|
|
|
356
556
|
["button_overview", this._onButtonOverview as EventListener],
|
|
357
557
|
["button_backtostart", this._onButtonBackToStart as EventListener],
|
|
358
558
|
["button_fullscreen", this._onButtonFullscreen as EventListener],
|
|
559
|
+
["button_autoplay", this._onButtonAutoplay as EventListener],
|
|
359
560
|
["button_collapse_toggle", this._onButtonCollapseMap as EventListener],
|
|
360
561
|
];
|
|
361
562
|
for (const [key, handler] of buttons) {
|
|
@@ -364,12 +565,22 @@ class MenuBarBase {
|
|
|
364
565
|
DomEvent.removeListener(el, "click", handler, this);
|
|
365
566
|
}
|
|
366
567
|
}
|
|
568
|
+
if (this._layersControl) {
|
|
569
|
+
this._layersControl.off("basemapchange", this._onLayersBasemap, this);
|
|
570
|
+
this._layersControl.off("overlaychange", this._onLayersOverlay, this);
|
|
571
|
+
this._layersControl.dispose();
|
|
572
|
+
this._layersControl = null;
|
|
573
|
+
}
|
|
367
574
|
this._el.container?.getAnimations?.().forEach((a) => a.cancel());
|
|
368
575
|
this._el.container?.remove();
|
|
369
576
|
}
|
|
370
577
|
}
|
|
371
578
|
|
|
372
|
-
|
|
579
|
+
const EventedMenuBarBase = Evented<MenuBarEvents, typeof MenuBarBase>(MenuBarBase);
|
|
580
|
+
|
|
581
|
+
export default class MenuBar extends DomMixed<MenuBarEvents, typeof EventedMenuBarBase>(
|
|
582
|
+
EventedMenuBarBase,
|
|
583
|
+
) {
|
|
373
584
|
constructor(...args: ConstructorParameters<typeof MenuBarBase>) {
|
|
374
585
|
super(...args);
|
|
375
586
|
}
|