diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..5bcfffd5c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +name: Lint code + +on: [push, pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Checkout + uses: actions/checkout@v4 + + - name: Install npm dependencies + run: npm install + + - name: Lint code + run: npm run lint + + - name: Auto-commit fixes + uses: EndBug/add-and-commit@v9 + with: + default_author: github_actions + add: "['src/']" diff --git a/src/content-handlers/iiif/BaseConfig.ts b/src/content-handlers/iiif/BaseConfig.ts index 97fbc0356..38ec90c21 100644 --- a/src/content-handlers/iiif/BaseConfig.ts +++ b/src/content-handlers/iiif/BaseConfig.ts @@ -226,7 +226,7 @@ export type SettingsDialogueContent = DialogueContent & { clickToZoomEnabled: string; pagingEnabled: string; reducedMotion: string; - truncateThumbnailLabels: string; + truncateThumbnailLabels: string; preserveViewport: string; title: string; website: string; diff --git a/src/content-handlers/iiif/extensions/uv-openseadragon-extension/DownloadDialogue.tsx b/src/content-handlers/iiif/extensions/uv-openseadragon-extension/DownloadDialogue.tsx index 470faccf4..93a0efc6c 100644 --- a/src/content-handlers/iiif/extensions/uv-openseadragon-extension/DownloadDialogue.tsx +++ b/src/content-handlers/iiif/extensions/uv-openseadragon-extension/DownloadDialogue.tsx @@ -325,7 +325,7 @@ const DownloadDialogue = ({ if (bodies.length == 0) { return null; } - + return bodies[0]; } @@ -342,7 +342,8 @@ const DownloadDialogue = ({ } // presentation api version 3 - const annotationBody: AnnotationBody | null = getCanvasImageAnnotationBody(canvas); + const annotationBody: AnnotationBody | null = + getCanvasImageAnnotationBody(canvas); if (annotationBody) { const format: MediaType | null = annotationBody.getFormat(); diff --git a/src/content-handlers/iiif/extensions/uv-openseadragon-extension/SettingsDialogue.ts b/src/content-handlers/iiif/extensions/uv-openseadragon-extension/SettingsDialogue.ts index 68298f08f..7a2ccee82 100644 --- a/src/content-handlers/iiif/extensions/uv-openseadragon-extension/SettingsDialogue.ts +++ b/src/content-handlers/iiif/extensions/uv-openseadragon-extension/SettingsDialogue.ts @@ -1,6 +1,6 @@ const $ = require("jquery"); import { SettingsDialogue as BaseSettingsDialogue } from "../../modules/uv-dialogues-module/SettingsDialogue"; -import config from "./config/config.json"; +import config from "./config/config.json"; export class SettingsDialogue extends BaseSettingsDialogue { $clickToZoomEnabled: JQuery; @@ -57,25 +57,35 @@ export class SettingsDialogue extends BaseSettingsDialogue { ); this.$pagingEnabled.append(this.$pagingEnabledLabel); - if (config.options.truncateThumbnailLabels) { - this.$truncateThumbnailLabels = $('
'); + if (config.options.truncateThumbnailLabels) { + this.$truncateThumbnailLabels = $( + '
' + ); this.$scroll.append(this.$truncateThumbnailLabels); this.$truncateThumbnailLabelsCheckbox = $( '' ); - this.$truncateThumbnailLabels.append(this.$truncateThumbnailLabelsCheckbox); + this.$truncateThumbnailLabels.append( + this.$truncateThumbnailLabelsCheckbox + ); this.$truncateThumbnailLabelsLabel = $( - '" + '" ); this.$truncateThumbnailLabels.append(this.$truncateThumbnailLabelsLabel); - this.$truncateThumbnailLabelsCheckbox.prop('checked', config.options.truncateThumbnailLabels); + this.$truncateThumbnailLabelsCheckbox.prop( + "checked", + config.options.truncateThumbnailLabels + ); this.$truncateThumbnailLabelsCheckbox.change(() => { const settings: ISettings = {}; - settings.truncateThumbnailLabels = this.$truncateThumbnailLabelsCheckbox.is(":checked"); + settings.truncateThumbnailLabels = + this.$truncateThumbnailLabelsCheckbox.is(":checked"); this.updateSettings(settings); }); } @@ -191,13 +201,13 @@ export class SettingsDialogue extends BaseSettingsDialogue { } else { this.$preserveViewportCheckbox.removeAttr("checked"); } - + if (this.$truncateThumbnailLabelsCheckbox) { if (settings.truncateThumbnailLabels) { this.$truncateThumbnailLabelsCheckbox.prop("checked", true); } else { this.$truncateThumbnailLabelsCheckbox.prop("checked", false); } - } + } } } diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts index 2e83a5aae..5f9e2f54d 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ContentLeftPanel.ts @@ -514,7 +514,10 @@ export class ContentLeftPanel extends LeftPanel { paged, viewingDirection: viewingDirection || ViewingDirection.LEFT_TO_RIGHT, selected: selectedIndices, - truncateThumbnailLabels: settings.truncateThumbnailLabels !== undefined ? settings.truncateThumbnailLabels : true, + truncateThumbnailLabels: + settings.truncateThumbnailLabels !== undefined + ? settings.truncateThumbnailLabels + : true, onClick: (thumb: Thumb) => { this.extensionHost.publish(IIIFEvents.THUMB_SELECTED, thumb); }, diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts index 3a282d0ae..044b679a4 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/GalleryView.ts @@ -85,7 +85,6 @@ export class GalleryView extends BaseView { } public applyExtendedLabelsStyles(): void { - this.$gallery.addClass('label-extended'); + this.$gallery.addClass("label-extended"); } - } diff --git a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx index 5978915b6..b009c335b 100644 --- a/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx +++ b/src/content-handlers/iiif/modules/uv-contentleftpanel-module/ThumbsView.tsx @@ -1,159 +1,159 @@ -import React, { useEffect, useRef } from "react"; -import { Thumb } from "manifesto.js"; -import { ViewingDirection, ViewingHint } from "@iiif/vocabulary"; -import { useInView } from "react-intersection-observer"; -import cx from "classnames"; - -const ThumbImage = ({ - first, - onClick, - onKeyDown, - paged, - selected, - thumb, - truncateThumbnailLabels, - viewingDirection, -}: { - first: boolean; - onClick: (thumb: Thumb) => void; - onKeyDown: (thumb: Thumb) => void; - paged: boolean; - selected: boolean; - thumb: Thumb; - truncateThumbnailLabels: boolean; - viewingDirection: ViewingDirection; -}) => { - const [ref, inView] = useInView({ - threshold: 0, - rootMargin: "0px 0px 0px 0px", - triggerOnce: true, - }); - - var keydownHandler = (e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - onKeyDown(thumb); - } - }; - return ( -
onClick(thumb)} - onKeyDown={keydownHandler} - className={cx("thumb", { - first: first, - placeholder: !thumb.uri, - twoCol: - paged && - (viewingDirection === ViewingDirection.LEFT_TO_RIGHT || - viewingDirection === ViewingDirection.RIGHT_TO_LEFT), - oneCol: !paged, - selected: selected, - "truncate-labels": truncateThumbnailLabels, - })} - tabIndex={0} - > -
- {inView && {thumb.label}} -
-
- - {thumb.label}  - - {thumb.data.searchResults && ( - {thumb.data.searchResults} - )} -
-
- ); -}; - -const Thumbnails = ({ - onClick, - onKeyDown, - paged, - selected, - thumbs, - viewingDirection, - truncateThumbnailLabels, -}: { - onClick: (thumb: Thumb) => void; - onKeyDown: (thumb: Thumb) => void; - paged: boolean; - selected: number[]; - thumbs: Thumb[]; - viewingDirection: ViewingDirection; - truncateThumbnailLabels: boolean; -}) => { - const ref = useRef(null); - - useEffect(() => { - const thumb: HTMLElement = ref.current?.querySelector( - `#thumb-${selected[0]}` - ) as HTMLElement; - const y: number = thumb?.offsetTop; - ref.current?.parentElement!.scrollTo({ - top: y, - left: 0, - behavior: "smooth", - }); - }, [selected]); - - function showSeparator( - paged: boolean, - viewingHint: ViewingHint | null, - index: number - ) { - if (viewingHint === ViewingHint.NON_PAGED) { - return true; - } - - if (paged) { - // if paged, show separator after every 2 thumbs - return !((index - 1) % 2 === 0); - } - - return true; - } - - const firstNonPagedIndex: number = thumbs.findIndex((t) => { - return t.viewingHint !== ViewingHint.NON_PAGED; - }); - - return ( -
- {thumbs.map((thumb, index) => ( - - - {showSeparator(paged, thumb.viewingHint, index) && ( -
- )} -
- ))} -
- ); -}; - -export default Thumbnails; +import React, { useEffect, useRef } from "react"; +import { Thumb } from "manifesto.js"; +import { ViewingDirection, ViewingHint } from "@iiif/vocabulary"; +import { useInView } from "react-intersection-observer"; +import cx from "classnames"; + +const ThumbImage = ({ + first, + onClick, + onKeyDown, + paged, + selected, + thumb, + truncateThumbnailLabels, + viewingDirection, +}: { + first: boolean; + onClick: (thumb: Thumb) => void; + onKeyDown: (thumb: Thumb) => void; + paged: boolean; + selected: boolean; + thumb: Thumb; + truncateThumbnailLabels: boolean; + viewingDirection: ViewingDirection; +}) => { + const [ref, inView] = useInView({ + threshold: 0, + rootMargin: "0px 0px 0px 0px", + triggerOnce: true, + }); + + var keydownHandler = (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onKeyDown(thumb); + } + }; + return ( +
onClick(thumb)} + onKeyDown={keydownHandler} + className={cx("thumb", { + first: first, + placeholder: !thumb.uri, + twoCol: + paged && + (viewingDirection === ViewingDirection.LEFT_TO_RIGHT || + viewingDirection === ViewingDirection.RIGHT_TO_LEFT), + oneCol: !paged, + selected: selected, + "truncate-labels": truncateThumbnailLabels, + })} + tabIndex={0} + > +
+ {inView && {thumb.label}} +
+
+ + {thumb.label}  + + {thumb.data.searchResults && ( + {thumb.data.searchResults} + )} +
+
+ ); +}; + +const Thumbnails = ({ + onClick, + onKeyDown, + paged, + selected, + thumbs, + viewingDirection, + truncateThumbnailLabels, +}: { + onClick: (thumb: Thumb) => void; + onKeyDown: (thumb: Thumb) => void; + paged: boolean; + selected: number[]; + thumbs: Thumb[]; + viewingDirection: ViewingDirection; + truncateThumbnailLabels: boolean; +}) => { + const ref = useRef(null); + + useEffect(() => { + const thumb: HTMLElement = ref.current?.querySelector( + `#thumb-${selected[0]}` + ) as HTMLElement; + const y: number = thumb?.offsetTop; + ref.current?.parentElement!.scrollTo({ + top: y, + left: 0, + behavior: "smooth", + }); + }, [selected]); + + function showSeparator( + paged: boolean, + viewingHint: ViewingHint | null, + index: number + ) { + if (viewingHint === ViewingHint.NON_PAGED) { + return true; + } + + if (paged) { + // if paged, show separator after every 2 thumbs + return !((index - 1) % 2 === 0); + } + + return true; + } + + const firstNonPagedIndex: number = thumbs.findIndex((t) => { + return t.viewingHint !== ViewingHint.NON_PAGED; + }); + + return ( +
+ {thumbs.map((thumb, index) => ( + + + {showSeparator(paged, thumb.viewingHint, index) && ( +
+ )} +
+ ))} +
+ ); +}; + +export default Thumbnails; diff --git a/src/content-handlers/iiif/modules/uv-dialogues-module/SettingsDialogue.ts b/src/content-handlers/iiif/modules/uv-dialogues-module/SettingsDialogue.ts index 15165f553..e2541b3c2 100644 --- a/src/content-handlers/iiif/modules/uv-dialogues-module/SettingsDialogue.ts +++ b/src/content-handlers/iiif/modules/uv-dialogues-module/SettingsDialogue.ts @@ -4,7 +4,9 @@ import { IIIFEvents } from "../../IIIFEvents"; import { Dialogue } from "../uv-shared-module/Dialogue"; import { ILocale } from "../uv-shared-module/ILocale"; -export class SettingsDialogue extends Dialogue { +export class SettingsDialogue extends Dialogue< + BaseConfig["modules"]["settingsDialogue"] +> { $locale: JQuery; $localeDropDown: JQuery; $localeLabel: JQuery; diff --git a/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/MediaElementCenterPanel.ts b/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/MediaElementCenterPanel.ts index a0b9e4881..78e933591 100644 --- a/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/MediaElementCenterPanel.ts +++ b/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/MediaElementCenterPanel.ts @@ -14,7 +14,7 @@ import { Rendering, } from "manifesto.js"; import "mediaelement/build/mediaelement-and-player"; -import 'mediaelement/build/mediaelementplayer.min.css'; +import "mediaelement/build/mediaelementplayer.min.css"; import "./js/source-chooser-fixed.js"; import "mediaelement-plugins/dist/source-chooser/source-chooser.css"; import { TFragment } from "../uv-shared-module/TFragment"; diff --git a/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/js/source-chooser-fixed.js b/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/js/source-chooser-fixed.js index b90518477..bd234c948 100644 --- a/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/js/source-chooser-fixed.js +++ b/src/content-handlers/iiif/modules/uv-mediaelementcenterpanel-module/js/source-chooser-fixed.js @@ -26,280 +26,461 @@ * Copyright 2010-2017, John Dyer (http://j.hn/) * License: MIT * - */(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i' + (''); - - t.addControlElement(player.sourcechooserButton, 'sourcechooser'); - - for (var _i = 0, _total = sources.length; _i < _total; _i++) { - var src = sources[_i]; - if (src.type !== undefined && typeof media.canPlayType === 'function') { - player.addSourceButton(src.src, src.title, src.type, media.src === src.src); - } - } - - player.sourcechooserButton.addEventListener('mouseover', function () { - clearTimeout(hoverTimeout); - player.showSourcechooserSelector(); - }); - player.sourcechooserButton.addEventListener('mouseout', function () { - hoverTimeout = setTimeout(function () { - player.hideSourcechooserSelector(); - }, 0); - }); - - player.sourcechooserButton.addEventListener('keydown', function (e) { - - function focusNext(dir = 1) { - let radioEls = Array.from(player.sourcechooserButton.querySelectorAll('input[type="radio"]')); - let index = radioEls.indexOf(document.activeElement); - let next = dir === 1 ? 1 : radioEls.length - 1; - let nextIndex = (index + next) % radioEls.length; - radioEls[nextIndex].focus(); - } - - if (t.options.keyActions.length) { - var keyCode = e.which || e.keyCode || 0; - - let stopPropagation = false; - switch (keyCode) { - case 13: // Enter - case 32: // Space Bar - if (!player.isSourcechooserSelectorOpen()) { - player.showSourcechooserSelector(); - player.sourcechooserButton.querySelector('input[type=radio]:checked').focus(); - stopPropagation = true; - } else if ( - document.activeElement.matches('input[type="radio"]') && - player.sourcechooserButton.contains(document.activeElement) - ) { - // Handle keyboard selection of radio inputs - document.activeElement.click(); - stopPropagation = true; - - // a slight delay before close - // to show that the new source was selected - setTimeout(() => { - player.hideSourcechooserSelector(); - player.sourcechooserButton.querySelector('button').focus(); - }, 150); - } - break; - case 27: // Escape - player.hideSourcechooserSelector(); - player.sourcechooserButton.querySelector('button').focus(); - stopPropagation = true; - break; - case 9: // Tab - if (player.isSourcechooserSelectorOpen()) { - // Get focused element - let checked = document.activeElement; - // Focus next radio element - if ( - checked.matches('input[type="radio"]') && - player.sourcechooserButton.contains(checked) - ) { - focusNext(e.shiftKey ? -1 : 1); - stopPropagation = true; - } - } - break; - - // Handle keyboard selection of radio inputs - case 37: // Left Arrow - case 38: // Up Arrow - case 39: // Right Arrow - case 40: // Down Arrow - if (!player.isSourcechooserSelectorOpen()) { - break; - } - - if ( - document.activeElement.matches('input[type="radio"]') && - player.sourcechooserButton.contains(document.activeElement) - ) { - let dir = keyCode === 37 || keyCode === 38 ? -1 : 1; - focusNext(dir); - stopPropagation = true; - } - break; - default: - return true; - } - - if (stopPropagation) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - - return true; - } - }); - - player.sourcechooserButton.addEventListener('focusout', mejs.Utils.debounce(function () { - setTimeout(function () { - var parent = document.activeElement.closest('.' + t.options.classPrefix + 'sourcechooser-selector'); - if (!parent) { - player.hideSourcechooserSelector(); - } - }, 0); - }, 100)); - - var radios = player.sourcechooserButton.querySelectorAll('input[type=radio]'); - - for (var _i2 = 0, _total2 = radios.length; _i2 < _total2; _i2++) { - radios[_i2].addEventListener('click', function () { - this.setAttribute('aria-selected', true); - this.checked = true; - - var otherRadios = this.closest('.' + t.options.classPrefix + 'sourcechooser-selector').querySelectorAll('input[type=radio]'); - - for (var j = 0, radioTotal = otherRadios.length; j < radioTotal; j++) { - if (otherRadios[j] !== this) { - otherRadios[j].setAttribute('aria-selected', 'false'); - otherRadios[j].removeAttribute('checked'); - } - } - - var src = this.value; - - if (media.getSrc() !== src) { - var currentTime = media.currentTime; - - var paused = media.paused, - canPlayAfterSourceSwitchHandler = function canPlayAfterSourceSwitchHandler() { - if (!paused) { - media.setCurrentTime(currentTime); - media.play(); - } - media.removeEventListener('canplay', canPlayAfterSourceSwitchHandler); - }; - - media.pause(); - media.setSrc(src); - media.load(); - media.addEventListener('canplay', canPlayAfterSourceSwitchHandler); - } - }); - } - - player.sourcechooserButton.querySelector('button').addEventListener('click', function () { - - var t = this; - - if (t.sourcechooserButton === undefined || !t.sourcechooserButton.querySelector('input[type=radio]')) { - return false; - } - - if (!player.isSourcechooserSelectorOpen()) { - player.showSourcechooserSelector(); - player.sourcechooserButton.querySelector('input[type=radio]:checked').focus(); - } else { - player.hideSourcechooserSelector(); - } - }); - }, - addSourceButton: function addSourceButton(src, label, type, isCurrent) { - var t = this; - if (label === '' || label === undefined) { - label = src; - } - type = type.split('/')[1]; - - t.sourcechooserButton.querySelector('ul').innerHTML += '
  • ' + ('') + ('') + '
  • '; - - t.adjustSourcechooserBox(); - }, - adjustSourcechooserBox: function adjustSourcechooserBox() { - var t = this; - - t.sourcechooserButton.querySelector('.' + t.options.classPrefix + 'sourcechooser-selector').style.height = parseFloat(t.sourcechooserButton.querySelector('.' + t.options.classPrefix + 'sourcechooser-selector ul').offsetHeight) + 'px'; - }, - /** - * @returns {boolean} - */ - isSourcechooserSelectorOpen: function isSourcechooserSelectorOpen() { - - var t = this; - - if (t.sourcechooserButton === undefined || !t.sourcechooserButton.querySelector('input[type=radio]')) { - return false; - } - - var selectorEl = t.sourcechooserButton.querySelector('.' + t.options.classPrefix + 'sourcechooser-selector'); - return mejs.Utils.hasClass(selectorEl, t.options.classPrefix + 'offscreen') === false; - }, - hideSourcechooserSelector: function hideSourcechooserSelector() { - - var t = this; - - if (t.sourcechooserButton === undefined || !t.sourcechooserButton.querySelector('input[type=radio]')) { - return; - } - - var selector = t.sourcechooserButton.querySelector('.' + t.options.classPrefix + 'sourcechooser-selector'), - radios = selector.querySelectorAll('input[type=radio]'); - selector.setAttribute('aria-expanded', 'false'); - selector.setAttribute('aria-hidden', 'true'); - mejs.Utils.addClass(selector, t.options.classPrefix + 'offscreen'); - - for (var i = 0, total = radios.length; i < total; i++) { - radios[i].setAttribute('tabindex', '-1'); - } - }, - showSourcechooserSelector: function showSourcechooserSelector() { - - var t = this; - - if (t.sourcechooserButton === undefined || !t.sourcechooserButton.querySelector('input[type=radio]')) { - return; - } - - var selector = t.sourcechooserButton.querySelector('.' + t.options.classPrefix + 'sourcechooser-selector'), - radios = selector.querySelectorAll('input[type=radio]'); - selector.setAttribute('aria-expanded', 'true'); - selector.setAttribute('aria-hidden', 'false'); - mejs.Utils.removeClass(selector, t.options.classPrefix + 'offscreen'); - - for (var i = 0, total = radios.length; i < total; i++) { - radios[i].setAttribute('tabindex', '0'); - } - } - }); - - },{}]},{},[1]); + */ (function () { + function r(e, n, t) { + function o(i, f) { + if (!n[i]) { + if (!e[i]) { + var c = "function" == typeof require && require; + if (!f && c) return c(i, !0); + if (u) return u(i, !0); + var a = new Error("Cannot find module '" + i + "'"); + throw ((a.code = "MODULE_NOT_FOUND"), a); + } + var p = (n[i] = { exports: {} }); + e[i][0].call( + p.exports, + function (r) { + var n = e[i][1][r]; + return o(n || r); + }, + p, + p.exports, + r, + e, + n, + t + ); + } + return n[i].exports; + } + for ( + var u = "function" == typeof require && require, i = 0; + i < t.length; + i++ + ) + o(t[i]); + return o; + } + return r; +})()( + { + 1: [ + function (_dereq_, module, exports) { + "use strict"; + + mejs.i18n.en["mejs.source-chooser"] = "Source Chooser"; + + Object.assign(mejs.MepDefaults, { + sourcechooserText: null, + }); + + Object.assign(MediaElementPlayer.prototype, { + buildsourcechooser: function buildsourcechooser( + player, + controls, + layers, + media + ) { + var t = this, + sourceTitle = mejs.Utils.isString(t.options.sourcechooserText) + ? t.options.sourcechooserText + : mejs.i18n.t("mejs.source-chooser"), + sources = [], + children = t.mediaFiles ? t.mediaFiles : t.node.children; + + var hoverTimeout = void 0; + + for (var i = 0, total = children.length; i < total; i++) { + var s = children[i]; + + if (t.mediaFiles) { + sources.push(s); + } else if (s.nodeName === "SOURCE") { + sources.push(s); + } + } + + if (sources.length <= 1) { + return; + } + + player.sourcechooserButton = document.createElement("div"); + player.sourcechooserButton.className = + t.options.classPrefix + + "button " + + t.options.classPrefix + + "sourcechooser-button"; + player.sourcechooserButton.innerHTML = + '' + + (''); + + t.addControlElement(player.sourcechooserButton, "sourcechooser"); + + for (var _i = 0, _total = sources.length; _i < _total; _i++) { + var src = sources[_i]; + if ( + src.type !== undefined && + typeof media.canPlayType === "function" + ) { + player.addSourceButton( + src.src, + src.title, + src.type, + media.src === src.src + ); + } + } + + player.sourcechooserButton.addEventListener( + "mouseover", + function () { + clearTimeout(hoverTimeout); + player.showSourcechooserSelector(); + } + ); + player.sourcechooserButton.addEventListener( + "mouseout", + function () { + hoverTimeout = setTimeout(function () { + player.hideSourcechooserSelector(); + }, 0); + } + ); + + player.sourcechooserButton.addEventListener( + "keydown", + function (e) { + function focusNext(dir = 1) { + let radioEls = Array.from( + player.sourcechooserButton.querySelectorAll( + 'input[type="radio"]' + ) + ); + let index = radioEls.indexOf(document.activeElement); + let next = dir === 1 ? 1 : radioEls.length - 1; + let nextIndex = (index + next) % radioEls.length; + radioEls[nextIndex].focus(); + } + + if (t.options.keyActions.length) { + var keyCode = e.which || e.keyCode || 0; + + let stopPropagation = false; + switch (keyCode) { + case 13: // Enter + case 32: // Space Bar + if (!player.isSourcechooserSelectorOpen()) { + player.showSourcechooserSelector(); + player.sourcechooserButton + .querySelector("input[type=radio]:checked") + .focus(); + stopPropagation = true; + } else if ( + document.activeElement.matches('input[type="radio"]') && + player.sourcechooserButton.contains( + document.activeElement + ) + ) { + // Handle keyboard selection of radio inputs + document.activeElement.click(); + stopPropagation = true; + + // a slight delay before close + // to show that the new source was selected + setTimeout(() => { + player.hideSourcechooserSelector(); + player.sourcechooserButton + .querySelector("button") + .focus(); + }, 150); + } + break; + case 27: // Escape + player.hideSourcechooserSelector(); + player.sourcechooserButton + .querySelector("button") + .focus(); + stopPropagation = true; + break; + case 9: // Tab + if (player.isSourcechooserSelectorOpen()) { + // Get focused element + let checked = document.activeElement; + // Focus next radio element + if ( + checked.matches('input[type="radio"]') && + player.sourcechooserButton.contains(checked) + ) { + focusNext(e.shiftKey ? -1 : 1); + stopPropagation = true; + } + } + break; + + // Handle keyboard selection of radio inputs + case 37: // Left Arrow + case 38: // Up Arrow + case 39: // Right Arrow + case 40: // Down Arrow + if (!player.isSourcechooserSelectorOpen()) { + break; + } + + if ( + document.activeElement.matches('input[type="radio"]') && + player.sourcechooserButton.contains( + document.activeElement + ) + ) { + let dir = keyCode === 37 || keyCode === 38 ? -1 : 1; + focusNext(dir); + stopPropagation = true; + } + break; + default: + return true; + } + + if (stopPropagation) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + + return true; + } + } + ); + + player.sourcechooserButton.addEventListener( + "focusout", + mejs.Utils.debounce(function () { + setTimeout(function () { + var parent = document.activeElement.closest( + "." + t.options.classPrefix + "sourcechooser-selector" + ); + if (!parent) { + player.hideSourcechooserSelector(); + } + }, 0); + }, 100) + ); + + var radios = + player.sourcechooserButton.querySelectorAll("input[type=radio]"); + + for (var _i2 = 0, _total2 = radios.length; _i2 < _total2; _i2++) { + radios[_i2].addEventListener("click", function () { + this.setAttribute("aria-selected", true); + this.checked = true; + + var otherRadios = this.closest( + "." + t.options.classPrefix + "sourcechooser-selector" + ).querySelectorAll("input[type=radio]"); + + for ( + var j = 0, radioTotal = otherRadios.length; + j < radioTotal; + j++ + ) { + if (otherRadios[j] !== this) { + otherRadios[j].setAttribute("aria-selected", "false"); + otherRadios[j].removeAttribute("checked"); + } + } + + var src = this.value; + + if (media.getSrc() !== src) { + var currentTime = media.currentTime; + + var paused = media.paused, + canPlayAfterSourceSwitchHandler = + function canPlayAfterSourceSwitchHandler() { + if (!paused) { + media.setCurrentTime(currentTime); + media.play(); + } + media.removeEventListener( + "canplay", + canPlayAfterSourceSwitchHandler + ); + }; + + media.pause(); + media.setSrc(src); + media.load(); + media.addEventListener( + "canplay", + canPlayAfterSourceSwitchHandler + ); + } + }); + } + + player.sourcechooserButton + .querySelector("button") + .addEventListener("click", function () { + var t = this; + + if ( + t.sourcechooserButton === undefined || + !t.sourcechooserButton.querySelector("input[type=radio]") + ) { + return false; + } + + if (!player.isSourcechooserSelectorOpen()) { + player.showSourcechooserSelector(); + player.sourcechooserButton + .querySelector("input[type=radio]:checked") + .focus(); + } else { + player.hideSourcechooserSelector(); + } + }); + }, + addSourceButton: function addSourceButton( + src, + label, + type, + isCurrent + ) { + var t = this; + if (label === "" || label === undefined) { + label = src; + } + type = type.split("/")[1]; + + t.sourcechooserButton.querySelector("ul").innerHTML += + "
  • " + + ('') + + ('") + + "
  • "; + + t.adjustSourcechooserBox(); + }, + adjustSourcechooserBox: function adjustSourcechooserBox() { + var t = this; + + t.sourcechooserButton.querySelector( + "." + t.options.classPrefix + "sourcechooser-selector" + ).style.height = + parseFloat( + t.sourcechooserButton.querySelector( + "." + t.options.classPrefix + "sourcechooser-selector ul" + ).offsetHeight + ) + "px"; + }, + /** + * @returns {boolean} + */ + isSourcechooserSelectorOpen: function isSourcechooserSelectorOpen() { + var t = this; + + if ( + t.sourcechooserButton === undefined || + !t.sourcechooserButton.querySelector("input[type=radio]") + ) { + return false; + } + + var selectorEl = t.sourcechooserButton.querySelector( + "." + t.options.classPrefix + "sourcechooser-selector" + ); + return ( + mejs.Utils.hasClass( + selectorEl, + t.options.classPrefix + "offscreen" + ) === false + ); + }, + hideSourcechooserSelector: function hideSourcechooserSelector() { + var t = this; + + if ( + t.sourcechooserButton === undefined || + !t.sourcechooserButton.querySelector("input[type=radio]") + ) { + return; + } + + var selector = t.sourcechooserButton.querySelector( + "." + t.options.classPrefix + "sourcechooser-selector" + ), + radios = selector.querySelectorAll("input[type=radio]"); + selector.setAttribute("aria-expanded", "false"); + selector.setAttribute("aria-hidden", "true"); + mejs.Utils.addClass(selector, t.options.classPrefix + "offscreen"); + + for (var i = 0, total = radios.length; i < total; i++) { + radios[i].setAttribute("tabindex", "-1"); + } + }, + showSourcechooserSelector: function showSourcechooserSelector() { + var t = this; + + if ( + t.sourcechooserButton === undefined || + !t.sourcechooserButton.querySelector("input[type=radio]") + ) { + return; + } + + var selector = t.sourcechooserButton.querySelector( + "." + t.options.classPrefix + "sourcechooser-selector" + ), + radios = selector.querySelectorAll("input[type=radio]"); + selector.setAttribute("aria-expanded", "true"); + selector.setAttribute("aria-hidden", "false"); + mejs.Utils.removeClass( + selector, + t.options.classPrefix + "offscreen" + ); + + for (var i = 0, total = radios.length; i < total; i++) { + radios[i].setAttribute("tabindex", "0"); + } + }, + }); + }, + {}, + ], + }, + {}, + [1] +); diff --git a/src/content-handlers/iiif/modules/uv-openseadragoncenterpanel-module/OpenSeadragonCenterPanel.ts b/src/content-handlers/iiif/modules/uv-openseadragoncenterpanel-module/OpenSeadragonCenterPanel.ts index e25a9270b..6cb9139fa 100644 --- a/src/content-handlers/iiif/modules/uv-openseadragoncenterpanel-module/OpenSeadragonCenterPanel.ts +++ b/src/content-handlers/iiif/modules/uv-openseadragoncenterpanel-module/OpenSeadragonCenterPanel.ts @@ -587,13 +587,13 @@ export class OpenSeadragonCenterPanel extends CenterPanel< if (this.extension.helper.isRightToLeft()) { this.$prevButton - .prop("title", this.content.nextImage) - .attr("aria-label", this.content.nextImage); - } else { + .prop("title", this.content.nextImage) + .attr("aria-label", this.content.nextImage); + } else { this.$prevButton - .prop("title", this.content.previousImage) - .attr("aria-label", this.content.previousImage); - } + .prop("title", this.content.previousImage) + .attr("aria-label", this.content.previousImage); + } this.$nextButton = $( `