@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.
Files changed (195) hide show
  1. package/CHANGELOG +81 -0
  2. package/README.md +129 -12
  3. package/dist/assets/{demo-DBpeF31K.js → demo-bWnOyfUw.js} +1 -1
  4. package/dist/assets/harness-C3JUv5Ii.js +1 -0
  5. package/dist/assets/{harness-multi-CYHgI0a9.js → harness-multi-XZNex3tb.js} +1 -1
  6. package/dist/assets/{harness-support-Do7sIp2j.js → harness-support-CjfZIVeb.js} +1 -1
  7. package/dist/assets/main-DQ32hWBM.js +17 -0
  8. package/dist/context.json +13 -2
  9. package/dist/css/storymap.css +1 -1
  10. package/dist/demo.html +3 -2
  11. package/dist/docs/api/assets/navigation.js +1 -1
  12. package/dist/docs/api/assets/search.js +1 -1
  13. package/dist/docs/api/classes/StoryMap.html +777 -269
  14. package/dist/docs/api/functions/MediaType.html +1 -1
  15. package/dist/docs/api/functions/isPresentation3Collection.html +1 -1
  16. package/dist/docs/api/functions/isPresentation3Manifest.html +1 -1
  17. package/dist/docs/api/functions/isSlideshowCollection.html +51 -0
  18. package/dist/docs/api/functions/loadCSS.html +1 -1
  19. package/dist/docs/api/functions/manifestToStorymapData.html +1 -1
  20. package/dist/docs/api/functions/setLanguage.html +1 -1
  21. package/dist/docs/api/functions/slideshowToStorymapData.html +51 -0
  22. package/dist/docs/api/functions/slideshowVersion.html +48 -0
  23. package/dist/docs/api/functions/storymapToManifest.html +1 -1
  24. package/dist/docs/api/functions/validateStorymap.html +1 -1
  25. package/dist/docs/api/functions/validateStorymapAndReport.html +1 -1
  26. package/dist/docs/api/index.html +115 -12
  27. package/dist/docs/api/interfaces/AnimationHandle.html +2 -2
  28. package/dist/docs/api/interfaces/ImagereadyPayload.html +4 -4
  29. package/dist/docs/api/interfaces/LanguageEntry.html +6 -6
  30. package/dist/docs/api/interfaces/LatLngLiteral.html +4 -4
  31. package/dist/docs/api/interfaces/LoadOptions.html +2 -2
  32. package/dist/docs/api/interfaces/MapEvents.html +116 -0
  33. package/dist/docs/api/interfaces/MapMarkerEvents.html +66 -0
  34. package/dist/docs/api/interfaces/MarkerEventPayload.html +57 -0
  35. package/dist/docs/api/interfaces/MediaEvents.html +78 -0
  36. package/dist/docs/api/interfaces/MenuBarEvents.html +114 -0
  37. package/dist/docs/api/interfaces/MessageEvents.html +72 -0
  38. package/dist/docs/api/interfaces/OlLayer.html +1 -1
  39. package/dist/docs/api/interfaces/OlMap.html +1 -1
  40. package/dist/docs/api/interfaces/OlSource.html +1 -1
  41. package/dist/docs/api/interfaces/OlTileLayer.html +1 -1
  42. package/dist/docs/api/interfaces/OlVectorLayer.html +1 -1
  43. package/dist/docs/api/interfaces/OlView.html +1 -1
  44. package/dist/docs/api/interfaces/SlideBackgroundState.html +66 -0
  45. package/dist/docs/api/interfaces/SlideEvents.html +78 -0
  46. package/dist/docs/api/interfaces/SlideNavEvents.html +72 -0
  47. package/dist/docs/api/interfaces/SlideshowPlayerSettings.html +182 -0
  48. package/dist/docs/api/interfaces/SlideshowToStorymapResult.html +60 -0
  49. package/dist/docs/api/interfaces/SlideshowTranslatorOptions.html +65 -0
  50. package/dist/docs/api/interfaces/SlideshowWarning.html +72 -0
  51. package/dist/docs/api/interfaces/StoryMapEvents.html +146 -0
  52. package/dist/docs/api/interfaces/StoryMapMarker.html +763 -0
  53. package/dist/docs/api/interfaces/StorySliderEvents.html +102 -0
  54. package/dist/docs/api/interfaces/StorymapBasemap.html +69 -0
  55. package/dist/docs/api/interfaces/StorymapData.html +12 -6
  56. package/dist/docs/api/interfaces/StorymapDataWrapper.html +2 -2
  57. package/dist/docs/api/interfaces/StorymapDocument.html +2 -2
  58. package/dist/docs/api/interfaces/StorymapError.html +3 -3
  59. package/dist/docs/api/interfaces/StorymapGeoreference.html +5 -5
  60. package/dist/docs/api/interfaces/StorymapGeoreferenceBody.html +4 -4
  61. package/dist/docs/api/interfaces/StorymapGroundControlPoint.html +4 -4
  62. package/dist/docs/api/interfaces/StorymapLanguageMap.html +2 -2
  63. package/dist/docs/api/interfaces/StorymapManifest.html +12 -12
  64. package/dist/docs/api/interfaces/StorymapManifestAnnotation.html +43 -11
  65. package/dist/docs/api/interfaces/StorymapManifestBackground.html +6 -6
  66. package/dist/docs/api/interfaces/StorymapManifestCanvas.html +65 -17
  67. package/dist/docs/api/interfaces/StorymapManifestContentResource.html +8 -8
  68. package/dist/docs/api/interfaces/StorymapManifestFeatureCollection.html +4 -4
  69. package/dist/docs/api/interfaces/StorymapManifestGeoreferencing.html +7 -7
  70. package/dist/docs/api/interfaces/StorymapManifestImageService.html +4 -4
  71. package/dist/docs/api/interfaces/StorymapManifestRange.html +5 -5
  72. package/dist/docs/api/interfaces/StorymapManifestService.html +4 -4
  73. package/dist/docs/api/interfaces/StorymapManifestSpecificResource.html +4 -4
  74. package/dist/docs/api/interfaces/StorymapManifestStatement.html +3 -3
  75. package/dist/docs/api/interfaces/StorymapMapOptions.html +5 -5
  76. package/dist/docs/api/interfaces/StorymapOptions.html +277 -75
  77. package/dist/docs/api/interfaces/StorymapOverlayLayer.html +42 -11
  78. package/dist/docs/api/interfaces/StorymapSlide.html +70 -16
  79. package/dist/docs/api/interfaces/StorymapSlideBackground.html +4 -4
  80. package/dist/docs/api/interfaces/StorymapSlideLocation.html +58 -14
  81. package/dist/docs/api/interfaces/StorymapSlideMarker.html +8 -9
  82. package/dist/docs/api/interfaces/StorymapSlideMedia.html +49 -10
  83. package/dist/docs/api/interfaces/StorymapSlideNarration.html +61 -9
  84. package/dist/docs/api/interfaces/StorymapSlideText.html +4 -4
  85. package/dist/docs/api/interfaces/StorymapTilejson.html +8 -8
  86. package/dist/docs/api/interfaces/SwipableEvents.html +102 -0
  87. package/dist/docs/api/interfaces/TextEvents.html +66 -0
  88. package/dist/docs/api/media/events.md +66 -0
  89. package/dist/docs/api/media/migration-from-knightlab.md +89 -49
  90. package/dist/docs/api/media/slideshow.md +109 -0
  91. package/dist/docs/api/media/storymap-as-iiif-manifest.md +1007 -0
  92. package/dist/docs/api/modules.html +6 -6
  93. package/dist/docs/api/types/ContentState.html +3 -3
  94. package/dist/docs/api/types/FiredEvent.html +43 -0
  95. package/dist/docs/api/types/ImagereadyKind.html +1 -1
  96. package/dist/docs/api/types/MediaTypeMatch.html +5 -5
  97. package/dist/docs/api/types/SwipeDirection.html +36 -0
  98. package/dist/docs/api/types/TileSourceFactory.html +1 -1
  99. package/dist/docs/authoring.html +20 -7
  100. package/dist/docs/events.html +263 -0
  101. package/dist/docs/iiif.html +136 -22
  102. package/dist/docs/migration.html +66 -19
  103. package/dist/docs/readme.html +113 -12
  104. package/dist/embed/index.html +7 -0
  105. package/dist/examples/font-css-false.json +33 -0
  106. package/dist/examples/issue-image-region.json +1 -1
  107. package/dist/examples/layers-control.json +60 -0
  108. package/dist/examples/map-template-provider.json +36 -0
  109. package/dist/examples/multi-media.json +71 -0
  110. package/dist/examples-iiif/annotated-image.json +16 -0
  111. package/dist/examples-iiif/font-css-false.json +114 -0
  112. package/dist/examples-iiif/issue-image-region.json +1 -1
  113. package/dist/examples-iiif/layers-control.json +226 -0
  114. package/dist/examples-iiif/map-template-provider.json +133 -0
  115. package/dist/examples-iiif/multi-media.json +237 -0
  116. package/dist/examples-slideshow/single.json +79 -0
  117. package/dist/examples-slideshow/static/pixel.png +0 -0
  118. package/dist/examples-slideshow/static.json +44 -0
  119. package/dist/harness-multi.html +3 -3
  120. package/dist/harness.html +4 -3
  121. package/dist/index.html +3 -2
  122. package/dist/js/storymap.d.ts +1326 -289
  123. package/dist/js/storymap.js +6690 -4784
  124. package/dist/js/storymap.js.map +1 -1
  125. package/dist/navplace-properties.json +14 -0
  126. package/dist/site.css +54 -36
  127. package/docs/migration-from-knightlab.md +89 -49
  128. package/docs/storymap-as-iiif-manifest.md +199 -126
  129. package/package.json +4 -2
  130. package/schema/storymap.schema.json +334 -34
  131. package/src/core/Util.ts +3 -1
  132. package/src/core/mixins.ts +42 -28
  133. package/src/language/Language.ts +7 -0
  134. package/src/language/locale/.expected-gaps.json +287 -27
  135. package/src/language/locale/de.json +11 -1
  136. package/src/language/locale/en.json +11 -1
  137. package/src/main.ts +51 -0
  138. package/src/map/Map.ts +44 -6
  139. package/src/map/MapMarker.ts +16 -3
  140. package/src/map/openlayers/Map.OpenLayers.ts +787 -62
  141. package/src/map/openlayers/MapMarker.OpenLayers.ts +113 -13
  142. package/src/map/slideView.ts +149 -0
  143. package/src/map/types.ts +2 -0
  144. package/src/media/EmbedUtil.ts +27 -2
  145. package/src/media/Media.ts +27 -4
  146. package/src/media/embedId.ts +6 -0
  147. package/src/media/types/Flickr.ts +14 -3
  148. package/src/media/types/HtmlMedia.ts +66 -0
  149. package/src/media/types/Image.ts +15 -3
  150. package/src/media/types/SoundCloud.ts +1 -1
  151. package/src/media/types/Text.ts +27 -8
  152. package/src/media/types/Twitter.ts +9 -7
  153. package/src/media/types/Vimeo.ts +1 -1
  154. package/src/media/types/YouTube.ts +2 -2
  155. package/src/scss/Typography.scss +15 -15
  156. package/src/scss/VCO.StoryMap.scss +173 -8
  157. package/src/scss/core/Reset.scss +5 -18
  158. package/src/scss/map/VCO.Map.scss +1 -1
  159. package/src/scss/map/VCO.MapMarker.scss +20 -9
  160. package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +7 -4
  161. package/src/scss/media/VCO.Media.scss +7 -7
  162. package/src/scss/media/types/VCO.Media.Blockquote.scss +1 -1
  163. package/src/scss/media/types/VCO.Media.Text.scss +5 -5
  164. package/src/scss/media/types/VCO.Media.Twitter.scss +4 -4
  165. package/src/scss/media/types/VCO.Media.Wikipedia.scss +2 -19
  166. package/src/scss/site/site.scss +55 -36
  167. package/src/scss/slider/VCO.Slide.scss +49 -11
  168. package/src/scss/slider/VCO.SlideNav.scss +23 -7
  169. package/src/scss/slider/VCO.StorySlider.scss +50 -0
  170. package/src/scss/snapmap/VCO.SnapMap.scss +3 -3
  171. package/src/scss/ui/VCO.Consent.scss +20 -14
  172. package/src/scss/ui/VCO.Layers.scss +119 -0
  173. package/src/scss/ui/VCO.MenuBar.Button.scss +140 -19
  174. package/src/scss/ui/VCO.MenuBar.scss +9 -2
  175. package/src/scss/ui/VCO.Message.scss +14 -0
  176. package/src/slider/Slide.ts +232 -74
  177. package/src/slider/SlideNav.ts +15 -4
  178. package/src/slider/StorySlider.ts +203 -22
  179. package/src/storymap/Consent.ts +22 -0
  180. package/src/storymap/StoryMap.ts +944 -131
  181. package/src/storymap/content-state.ts +3 -8
  182. package/src/storymap/font-css.ts +72 -0
  183. package/src/storymap/from-slideshow.ts +785 -0
  184. package/src/storymap/iiif-shared.ts +228 -0
  185. package/src/storymap/iiif.ts +289 -100
  186. package/src/storymap/to-iiif.ts +131 -44
  187. package/src/types.ts +382 -17
  188. package/src/ui/LayersControl.ts +302 -0
  189. package/src/ui/MenuBar.ts +232 -21
  190. package/src/ui/Message.ts +36 -3
  191. package/src/ui/Swipable.ts +25 -7
  192. package/dist/assets/harness-Cr0rYpvt.js +0 -1
  193. package/dist/assets/main-DXalctuR.js +0 -17
  194. package/src/scss/VCO.StoryMap.Dark.scss +0 -150
  195. package/src/scss/Variables.Dark.scss +0 -57
@@ -138,6 +138,18 @@
138
138
  "attribution": {
139
139
  "type": "string"
140
140
  },
141
+ "label": {
142
+ "type": "string",
143
+ "description": "Layer switcher row label (defaults to map_type)"
144
+ },
145
+ "locked": {
146
+ "type": "boolean",
147
+ "description": "Render the switcher row checked and disabled"
148
+ },
149
+ "control": {
150
+ "type": "boolean",
151
+ "description": "False hides the row from the switcher without removing the overlay"
152
+ },
141
153
  "className": {
142
154
  "type": "string",
143
155
  "description": "CSS class of the layer container, isolating it from the shared ol-layer canvas (e.g. for blend modes)"
@@ -262,11 +274,83 @@
262
274
  },
263
275
  "attribution": {
264
276
  "type": "string"
277
+ },
278
+ "width": {
279
+ "type": "number",
280
+ "exclusiveMinimum": 0,
281
+ "description": "Canvas width in pixels, enabling region clamping and the static fallback without another fetch"
282
+ },
283
+ "height": {
284
+ "type": "number",
285
+ "exclusiveMinimum": 0,
286
+ "description": "Canvas height in pixels, enabling region clamping and the static fallback without another fetch"
287
+ },
288
+ "fallbackUrl": {
289
+ "type": "string",
290
+ "format": "uri",
291
+ "description": "Full-size image used when info.json cannot be loaded (slideshow image_static)"
265
292
  }
266
293
  }
267
294
  },
295
+ "tilejson": {
296
+ "type": "object",
297
+ "description": "TileJSON source for the base layer: tiles becomes the map_type template, the rest constrains the source/view where the map can act on it",
298
+ "required": ["tiles"],
299
+ "properties": {
300
+ "tiles": {
301
+ "anyOf": [
302
+ { "type": "string" },
303
+ { "type": "array", "items": { "type": "string" }, "minItems": 1 }
304
+ ]
305
+ },
306
+ "minzoom": { "type": "number" },
307
+ "maxzoom": { "type": "number" },
308
+ "bounds": {
309
+ "type": "array",
310
+ "items": { "type": "number" },
311
+ "minItems": 4,
312
+ "maxItems": 4
313
+ },
314
+ "scheme": { "type": "string", "enum": ["xyz", "tms"] },
315
+ "center": {
316
+ "type": "array",
317
+ "items": { "type": "number" },
318
+ "minItems": 3,
319
+ "maxItems": 3
320
+ }
321
+ }
322
+ },
323
+ "see_also": {
324
+ "type": "array",
325
+ "description": "seeAlso targets recorded from a IIIF manifest (annotation collections/pages); recorded but not fetched on load",
326
+ "items": {
327
+ "type": "object",
328
+ "required": ["id", "type"],
329
+ "properties": {
330
+ "id": { "type": "string" },
331
+ "type": { "type": "string" }
332
+ }
333
+ }
334
+ },
335
+ "title": {
336
+ "type": "string",
337
+ "description": "Story title carried by IIIF manifests (label) and slideshow tours; extra on legacy documents"
338
+ },
339
+ "metadata": {
340
+ "type": "array",
341
+ "description": "Label/value pairs carried from a IIIF manifest or slideshow tour; data-only, handed on as data.metadata[]"
342
+ },
343
+ "logo": {
344
+ "description": "Publisher logo carried from a IIIF manifest; data-only, offered as data.logo"
345
+ },
268
346
  "font_css": {
269
- "type": "string"
347
+ "anyOf": [{ "type": "string" }, { "const": false }],
348
+ "description": "Font theme stylesheet (stock:<name> or URL); false disables the injected stylesheet because the host bundles the theme itself"
349
+ },
350
+ "theme": {
351
+ "type": "string",
352
+ "enum": ["dark", "light"],
353
+ "description": "Colour theme: dark forces the dark palette, light pins the light one; unset follows prefers-color-scheme"
270
354
  },
271
355
  "call_to_action": {
272
356
  "type": "boolean"
@@ -286,6 +370,38 @@
286
370
  "show_progress": {
287
371
  "type": "boolean"
288
372
  },
373
+ "progressbar": {
374
+ "anyOf": [
375
+ { "type": "boolean" },
376
+ {
377
+ "type": "string",
378
+ "enum": ["bar", "dots", "squares", "block", "thinblock", "off"]
379
+ }
380
+ ],
381
+ "description": "Progress indicator style (slideshow progressbar): false/off hides it, true/bar is the classic fill bar, dots/squares render one step per slide, block/thinblock are fill-bar height variants. Absent means show_progress decides with the bar style. Honored only with slideshow_source."
382
+ },
383
+ "show_layers_control": {
384
+ "type": "boolean",
385
+ "description": "Show the basemap/overlay switcher in the menubar"
386
+ },
387
+ "basemaps": {
388
+ "type": "array",
389
+ "description": "Selectable base maps for the layer switcher; entry 0 is the initial basemap unless map_type says otherwise",
390
+ "items": {
391
+ "type": "object",
392
+ "required": ["map_type"],
393
+ "properties": {
394
+ "map_type": {
395
+ "type": "string",
396
+ "description": "Any map_type value"
397
+ },
398
+ "label": {
399
+ "type": "string",
400
+ "description": "Control row label (defaults to map_type)"
401
+ }
402
+ }
403
+ }
404
+ },
289
405
  "show_distance": {
290
406
  "type": "boolean"
291
407
  },
@@ -297,6 +413,62 @@
297
413
  "enum": ["left", "center", "right"],
298
414
  "description": "Default horizontal alignment for slide text"
299
415
  },
416
+ "textmode": {
417
+ "type": "string",
418
+ "enum": ["left", "right", "bottom"],
419
+ "description": "Slide panel dock (slideshow textmode) in landscape: right (default) is the current layout, left mirrors it, bottom docks the panel below the map. Portrait layouts always behave like bottom. Honored only with slideshow_source."
420
+ },
421
+ "textsize": {
422
+ "type": "number",
423
+ "minimum": 10,
424
+ "maximum": 80,
425
+ "description": "Slide panel size as a percentage (slideshow textsize): percent of the width for side docks, of the height for the bottom dock. Absent keeps the built-in 50/50 split. Honored only with slideshow_source."
426
+ },
427
+ "fxmode": {
428
+ "type": "string",
429
+ "enum": ["slide", "fade", "none"],
430
+ "description": "Slide transition effect (slideshow fxmode): slide (default) is the current glide, fade cross-fades slides, none jumps without animation. prefers-reduced-motion still forces none. Honored only with slideshow_source."
431
+ },
432
+ "mode": {
433
+ "type": "string",
434
+ "enum": ["standard", "static"],
435
+ "description": "Reading mode (slideshow mode): standard (default) is the slider; static stacks all slides in a scrollable list and follows the map to the slide in view. Honored only with slideshow_source."
436
+ },
437
+ "hudcolor": {
438
+ "type": "string",
439
+ "description": "HUD foreground color override (slideshow hudcolor). Honored only with slideshow_source."
440
+ },
441
+ "hudbgcolor": {
442
+ "type": "string",
443
+ "description": "HUD background color override (slideshow hudbgcolor). Honored only with slideshow_source."
444
+ },
445
+ "hudopacity": {
446
+ "type": "number",
447
+ "minimum": 0,
448
+ "maximum": 100,
449
+ "description": "HUD opacity percent 0..100 (slideshow hudopacity), applied to the HUD background. Honored only with slideshow_source."
450
+ },
451
+ "shownav": {
452
+ "type": "boolean",
453
+ "description": "Show the previous/next slide navigation (slideshow shownav). Default true. Honored only with slideshow_source."
454
+ },
455
+ "show_headings": {
456
+ "type": "boolean",
457
+ "description": "Render slide headlines (slideshow showheadings). Default true; false hides headline elements. Honored only with slideshow_source."
458
+ },
459
+ "show_scrollbars": {
460
+ "type": "boolean",
461
+ "description": "Show scrollbars in the slide panel (slideshow showscrollbars). Default true; false hides them (content still scrolls). Honored only with slideshow_source."
462
+ },
463
+ "viewerheight": {
464
+ "type": "string",
465
+ "pattern": "^[0-9]+(\\.[0-9]+)?(px|%|vh)$",
466
+ "description": "Fixed viewer height (slideshow viewerheight), e.g. 400px: applied as a container height override when set. Honored only with slideshow_source."
467
+ },
468
+ "show_info": {
469
+ "type": "boolean",
470
+ "description": "Show media captions and credits (slideshow showinfo). Default true; false hides the caption/credit block. Honored only with slideshow_source."
471
+ },
300
472
  "text_color": {
301
473
  "type": "string"
302
474
  },
@@ -355,6 +527,15 @@
355
527
  "$ref": "#/$defs/slide"
356
528
  }
357
529
  },
530
+ "credit": {
531
+ "type": "object",
532
+ "description": "Authorship and rights for the story (slideshow creator/rights); rendered into the map attribution line for slideshow tours.",
533
+ "properties": {
534
+ "creator": { "type": "string" },
535
+ "rights": { "type": "string" },
536
+ "licenseUrl": { "type": "string", "format": "uri" }
537
+ }
538
+ },
358
539
  "layout": {
359
540
  "type": "string",
360
541
  "enum": ["landscape", "portrait"],
@@ -364,6 +545,58 @@
364
545
  }
365
546
  },
366
547
  "$defs": {
548
+ "media": {
549
+ "type": "object",
550
+ "properties": {
551
+ "url": {
552
+ "type": ["string", "null"]
553
+ },
554
+ "caption": {
555
+ "type": ["string", "null"]
556
+ },
557
+ "credit": {
558
+ "type": ["string", "null"]
559
+ },
560
+ "thumb": {
561
+ "type": "string"
562
+ },
563
+ "alt": {
564
+ "type": "string"
565
+ },
566
+ "srcset": {
567
+ "type": "string"
568
+ },
569
+ "sizes": {
570
+ "type": "string"
571
+ },
572
+ "subtitles": {
573
+ "type": ["string", "null"],
574
+ "format": "uri",
575
+ "description": "WebVTT subtitle file for an audio or video slide, rendered as a <track kind=\"subtitles\">."
576
+ },
577
+ "offset": {
578
+ "type": "number",
579
+ "minimum": 0,
580
+ "description": "Start offset in seconds for audio/video media (slideshow audio.offset). Honored only with slideshow_source"
581
+ },
582
+ "loop": {
583
+ "type": "boolean",
584
+ "description": "Loop audio/video media (slideshow audio.loop). Honored only with slideshow_source"
585
+ },
586
+ "play": {
587
+ "type": "string",
588
+ "enum": ["auto", "click"],
589
+ "description": "auto plays on activation; click arms paused for the visitor to start. Honored only with slideshow_source"
590
+ },
591
+ "stopOnExit": {
592
+ "type": "boolean",
593
+ "description": "Stop this media when leaving the slide (default true); false lets an audio bed continue. Honored only with slideshow_source"
594
+ },
595
+ "mediatype": {
596
+ "type": ["object", "null"]
597
+ }
598
+ }
599
+ },
367
600
  "slide": {
368
601
  "type": "object",
369
602
  "properties": {
@@ -399,7 +632,41 @@
399
632
  },
400
633
  "minItems": 4,
401
634
  "maxItems": 4,
402
- "description": "Image region [x, y, w, h] in image pixels (IIIF xywh convention) for image maps; the view fits the region on navigation"
635
+ "description": "Image region [x, y, w, h] in image pixels (IIIF xywh convention) for image maps; the view fits the region on navigation. Negative origins are clamped to the canvas."
636
+ },
637
+ "rotation": {
638
+ "type": "number",
639
+ "description": "View rotation in degrees clockwise (slideshow rotation); absent means north-up. Honored only with slideshow_source"
640
+ },
641
+ "filter": {
642
+ "type": "object",
643
+ "description": "Per-slide image grading (slideshow filters), applied as a CSS filter() on the map viewport. Honored only with slideshow_source",
644
+ "properties": {
645
+ "brightness": { "type": "number", "minimum": 0, "maximum": 200 },
646
+ "contrast": { "type": "number", "minimum": 0, "maximum": 200 },
647
+ "saturate": { "type": "number", "minimum": 0, "maximum": 200 },
648
+ "hueRotate": { "type": "number", "minimum": -180, "maximum": 180 },
649
+ "sepia": { "type": "number", "minimum": 0, "maximum": 100 },
650
+ "blur": { "type": "number", "minimum": 0, "maximum": 20 }
651
+ }
652
+ },
653
+ "mask": {
654
+ "type": "object",
655
+ "description": "Spotlight mask (slideshow passepartout): normalized viewport fractions left visible while the rest is dimmed. Honored only with slideshow_source",
656
+ "required": ["x", "y", "w", "h"],
657
+ "properties": {
658
+ "x": { "type": "number" },
659
+ "y": { "type": "number" },
660
+ "w": { "type": "number", "exclusiveMinimum": 0 },
661
+ "h": { "type": "number", "exclusiveMinimum": 0 },
662
+ "color": { "type": "string" },
663
+ "invert": { "type": "boolean" },
664
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 }
665
+ }
666
+ },
667
+ "basemap": {
668
+ "type": "string",
669
+ "description": "Per-slide basemap override (multi-manifest tours): any map_type value or IIIF info.json URL. Absent keeps the story basemap. Honored only with slideshow_source."
403
670
  },
404
671
  "zoom": {
405
672
  "type": "number"
@@ -432,39 +699,20 @@
432
699
  }
433
700
  },
434
701
  "media": {
435
- "type": "object",
436
- "properties": {
437
- "url": {
438
- "type": ["string", "null"]
439
- },
440
- "caption": {
441
- "type": ["string", "null"]
442
- },
443
- "credit": {
444
- "type": ["string", "null"]
445
- },
446
- "thumb": {
447
- "type": "string"
448
- },
449
- "alt": {
450
- "type": "string"
451
- },
452
- "srcset": {
453
- "type": "string"
454
- },
455
- "sizes": {
456
- "type": "string"
457
- },
458
- "subtitles": {
459
- "type": ["string", "null"],
460
- "format": "uri",
461
- "description": "WebVTT subtitle file for an audio or video slide, rendered as a <track kind=\"subtitles\">."
462
- },
463
- "mediatype": {
464
- "type": ["object", "null"]
465
- }
702
+ "$ref": "#/$defs/media"
703
+ },
704
+ "media_extra": {
705
+ "type": ["array", "null"],
706
+ "description": "Additional visible media for this slide (#358), rendered after media in order. Items without a non-empty url are skipped. In a IIIF manifest these are further painting annotations on the canvas.",
707
+ "items": {
708
+ "$ref": "#/$defs/media"
466
709
  }
467
710
  },
711
+ "media_layout": {
712
+ "type": "string",
713
+ "enum": ["stack", "row"],
714
+ "description": "Layout of a multi-media slide (#358): stack piles the items vertically (the panel scrolls), row puts two items side by side. Defaults to stack; row with anything but two items falls back to stack."
715
+ },
468
716
  "text": {
469
717
  "type": "object",
470
718
  "properties": {
@@ -515,11 +763,54 @@
515
763
  },
516
764
  "narration": {
517
765
  "type": ["object", "null"],
518
- "description": "Spoken narration for this slide, played through a dedicated audio element. Storymap JSON only: in a IIIF manifest the closest spelling is a Sound body, which currently lands in media.url rather than here.",
766
+ "description": "Spoken narration for this slide, played through a dedicated audio element. In a IIIF manifest this is a motivation: supplementing Sound/Video body, read and written alongside the painting annotation.",
519
767
  "properties": {
520
768
  "url": {
521
769
  "type": "string",
522
770
  "format": "uri"
771
+ },
772
+ "loop": {
773
+ "type": "boolean",
774
+ "description": "Loop the narration (slideshow audio.loop). Honored only with slideshow_source"
775
+ },
776
+ "offset": {
777
+ "type": "number",
778
+ "minimum": 0,
779
+ "description": "Start offset in seconds (slideshow audio.offset). Honored only with slideshow_source"
780
+ },
781
+ "play": {
782
+ "type": "string",
783
+ "enum": ["auto", "click"],
784
+ "description": "auto plays on navigation; click arms paused for the visitor to start (slideshow audio.play). Honored only with slideshow_source"
785
+ },
786
+ "stopOnExit": {
787
+ "type": "boolean",
788
+ "description": "Stop when leaving the slide (default true); false keeps an audio bed across slides while the URL is unchanged. Honored only with slideshow_source"
789
+ },
790
+ "stopAllPrevious": {
791
+ "type": "boolean",
792
+ "description": "Stop any other playing track before starting (default true); false allows ambient overlap. Honored only with slideshow_source"
793
+ }
794
+ },
795
+ "required": ["url"]
796
+ },
797
+ "slidetimeout": {
798
+ "type": "number",
799
+ "minimum": 0,
800
+ "description": "Per-slide autoplay dwell in milliseconds (slideshow slidetimeout): overrides the global autoplay interval for this slide; 0 holds. Absent means the global interval. Honored only with slideshow_source."
801
+ },
802
+ "imgoverlay": {
803
+ "anyOf": [{ "type": "object" }, { "type": "null" }],
804
+ "description": "Per-slide image overlay on the map (slideshow imgoverlay), shown while this slide is active. Honored only with slideshow_source",
805
+ "properties": {
806
+ "url": { "type": "string", "format": "uri" },
807
+ "size": { "type": "number", "exclusiveMinimum": 0 },
808
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
809
+ "extent": {
810
+ "type": "array",
811
+ "items": { "type": "number" },
812
+ "minItems": 4,
813
+ "maxItems": 4
523
814
  }
524
815
  },
525
816
  "required": ["url"]
@@ -542,6 +833,15 @@
542
833
  },
543
834
  "uniqueid": {
544
835
  "type": "string"
836
+ },
837
+ "provenance": {
838
+ "type": "object",
839
+ "description": "Provenance identifiers for the slide's source (slideshow manifest/canvas/image ids). Data-only, never rendered.",
840
+ "properties": {
841
+ "manifest": { "type": "string", "format": "uri" },
842
+ "canvas": { "type": "string", "format": "uri" },
843
+ "image": { "type": "string", "format": "uri" }
844
+ }
545
845
  }
546
846
  }
547
847
  }
package/src/core/Util.ts CHANGED
@@ -359,11 +359,13 @@ export function slideTransitionDuration(from: number, to: number): number {
359
359
  * (`prefers-reduced-motion: reduce`) — slide/map glides collapse to
360
360
  * instant changes and autoplay stays off.
361
361
  */
362
+ export const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
363
+
362
364
  export function prefersReducedMotion(): boolean {
363
365
  return (
364
366
  typeof window !== "undefined" &&
365
367
  typeof window.matchMedia === "function" &&
366
- window.matchMedia("(prefers-reduced-motion: reduce)").matches
368
+ window.matchMedia(REDUCED_MOTION_QUERY).matches
367
369
  );
368
370
  }
369
371
 
@@ -8,12 +8,29 @@
8
8
  /** Constructor shape accepted by the mixin functions. */
9
9
  export type Constructor<T = object> = new (...args: any[]) => T;
10
10
 
11
+ /** What a listener receives: the payload, plus the framework's own fields.
12
+ *
13
+ * Kept as an intersection because `fire` merges them into one object — that
14
+ * is what a handler gets. As a side effect it also fixes the `type`
15
+ * clobbering at the type level: intersecting `type: "loaded"` with
16
+ * `type?: string` yields `"loaded"`.
17
+ */
18
+ export type FiredEvent<M, K extends keyof M & string> = M[K] & { type: K; target: unknown };
19
+
11
20
  /** Eventing members provided by the Evented mixin. */
12
- export interface EventedInstance {
13
- on: (type: string, fn: unknown, context?: unknown) => unknown;
14
- off: (type: string, fn: unknown, context?: unknown) => unknown;
15
- fire: (type: string, data?: unknown, target?: unknown) => unknown;
16
- hasEventListeners: (type: string) => boolean;
21
+ export interface EventedInstance<M = Record<string, unknown>> {
22
+ on<K extends keyof M & string>(
23
+ type: K,
24
+ fn: (e: FiredEvent<M, K>) => void,
25
+ context?: unknown,
26
+ ): this;
27
+ off<K extends keyof M & string>(
28
+ type: K,
29
+ fn: (e: FiredEvent<M, K>) => void,
30
+ context?: unknown,
31
+ ): this;
32
+ fire<K extends keyof M & string>(type: K, data?: M[K]): this;
33
+ hasEventListeners<K extends keyof M & string>(type: K): boolean;
17
34
  }
18
35
 
19
36
  /**
@@ -25,7 +42,7 @@ export interface EventedInstance {
25
42
  * slider's live region). Requiring `Record<string, HTMLElement>` here would
26
43
  * force every one of those back into an `{} as HTMLElement` placeholder.
27
44
  */
28
- export interface DomMixedInstance extends EventedInstance {
45
+ export interface DomMixedInstance<M = Record<string, unknown>> extends EventedInstance<M> {
29
46
  _el: Record<string, unknown>;
30
47
  data?: unknown;
31
48
  }
@@ -36,7 +53,7 @@ export interface DomMixedInstance extends EventedInstance {
36
53
  ================================================== */
37
54
 
38
55
  /** Adds eventing (on/off/fire/hasEventListeners) to a class. */
39
- export function Evented<T extends Constructor>(Base: T) {
56
+ export function Evented<M, T extends Constructor>(Base: T) {
40
57
  return class extends Base {
41
58
  declare "_vco_events"?: Record<string, { action: unknown; context: unknown }[]>;
42
59
 
@@ -70,31 +87,25 @@ export function Evented<T extends Constructor>(Base: T) {
70
87
  string,
71
88
  { action: unknown; context: unknown }[]
72
89
  >;
73
- for (let i = 0, len = events[type].length; i < len; i++) {
74
- if (
75
- events[type][i].action === fn &&
76
- (!context || events[type][i].context === context)
77
- ) {
78
- events[type].splice(i, 1);
79
- return this;
80
- }
90
+ events[type] = events[type].filter(
91
+ (listener) =>
92
+ !(listener.action === fn && (!context || listener.context === context)),
93
+ );
94
+ if (events[type].length === 0) {
95
+ delete events[type];
81
96
  }
82
97
  return this;
83
98
  }
84
99
 
85
- fire(
86
- /*String*/ type: string,
87
- /*(optional) Object*/ data?: unknown,
88
- target?: unknown,
89
- ): this {
100
+ fire(/*String*/ type: string, /*(optional) Object*/ data?: unknown): this {
90
101
  if (!this.hasEventListeners(type)) {
91
102
  return this;
92
103
  }
93
104
 
94
105
  const event = {
95
- type: type,
96
- target: target || this,
97
106
  ...(data as Record<string, unknown> | undefined),
107
+ type,
108
+ target: this,
98
109
  };
99
110
 
100
111
  const listeners = (
@@ -111,7 +122,7 @@ export function Evented<T extends Constructor>(Base: T) {
111
122
  constructor(...args: any[]) {
112
123
  super(...args);
113
124
  }
114
- };
125
+ } as Constructor<EventedInstance<M>> & T;
115
126
  }
116
127
 
117
128
  /* DomMixed
@@ -121,7 +132,10 @@ export function Evented<T extends Constructor>(Base: T) {
121
132
 
122
133
  /** Adds DOM container conveniences (addTo/removeFrom/setPosition/show/hide
123
134
  * and the onAdd/onRemove/onLoaded lifecycle events) to a class. */
124
- export function DomMixed<T extends Constructor<DomMixedInstance>>(Base: T) {
135
+ export function DomMixed<
136
+ M extends { loaded: unknown; added: unknown; removed: unknown },
137
+ T extends Constructor<DomMixedInstance<M>>,
138
+ >(Base: T) {
125
139
  return class extends Base {
126
140
  /**
127
141
  * The host element. Every DomMixed consumer keeps its root element
@@ -168,17 +182,17 @@ export function DomMixed<T extends Constructor<DomMixedInstance>>(Base: T) {
168
182
  }
169
183
 
170
184
  /* Lifecycle events
171
- ================================================== */
185
+ ================================================== */
172
186
  onLoaded(): void {
173
- this.fire("loaded", this.data);
187
+ this.fire("loaded", this.data as M["loaded"]);
174
188
  }
175
189
 
176
190
  onAdd(): void {
177
- this.fire("added", this.data);
191
+ this.fire("added", this.data as M["added"]);
178
192
  }
179
193
 
180
194
  onRemove(): void {
181
- this.fire("removed", this.data);
195
+ this.fire("removed", this.data as M["removed"]);
182
196
  }
183
197
 
184
198
  constructor(...args: any[]) {
@@ -244,6 +244,13 @@ function setLanguage(code: string): LanguageEntry {
244
244
  messages: (merged.messages as Record<string, string>) ?? FALLBACK.messages,
245
245
  buttons: (merged.buttons as Record<string, string>) ?? FALLBACK.buttons,
246
246
  };
247
+ // The page declares the language the viewer actually renders, so
248
+ // assistive tech pronounces chrome correctly. One effective locale per
249
+ // page (a second viewer claiming another throws), so this cannot fight
250
+ // a sibling viewer.
251
+ if (typeof document !== "undefined") {
252
+ document.documentElement.setAttribute("lang", code);
253
+ }
247
254
  return Language;
248
255
  }
249
256