diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index e8a46c721..11dfa01c8 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts @@ -15,6 +15,7 @@ import './shared/control-bar/p2p-info-button' import './shared/control-bar/peertube-link-button' import './shared/control-bar/theater-button' import './shared/control-bar/peertube-live-display' +import './shared/settings/menu-focus-fixed' import './shared/settings/resolution-menu-button' import './shared/settings/resolution-menu-item' import './shared/settings/settings-dialog' diff --git a/client/src/assets/player/shared/settings/menu-focus-fixed.ts b/client/src/assets/player/shared/settings/menu-focus-fixed.ts new file mode 100644 index 000000000..0c835c7fa --- /dev/null +++ b/client/src/assets/player/shared/settings/menu-focus-fixed.ts @@ -0,0 +1,79 @@ +import videojs from 'video.js' + +const Menu = videojs.getComponent('Menu') +const Component = videojs.getComponent('Component') + +// Default menu doesn't check if the child is disabled/hidden + +class MenuFocusFixed extends Menu { + private focusedChild_: number + + handleKeyDown (event: KeyboardEvent) { + if (event.key === 'Escape') { + event.preventDefault() + event.stopPropagation() + this.trigger('escaped-key') + return + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + return super.handleKeyDown(event) + } + + stepForward () { + let stepChild = 0 + + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ + 1 + } + this.focus(stepChild) + } + + stepBack () { + let stepChild = 0 + + if (this.focusedChild_ !== undefined) { + stepChild = this.focusedChild_ - 1 + } + this.focus(stepChild) + } + + focus (item = 0): void { + const children = this.children().slice() + const haveTitle = children.length && children[0].hasClass('vjs-menu-title') + + if (haveTitle) { + children.shift() + } + + if (children.length > 0) { + if (item < 0) { + item = 0 + } else if (item >= children.length) { + item = children.length - 1 + } + + const el = children[item].el() as HTMLElement + + if (el.classList.contains('vjs-hidden')) { + if (this.focusedChild_ < item) { + if (item === children.length - 1) return + + return this.focus(item + 1) + } else { + if (item === 0) return + + return this.focus(item - 1) + } + } + + this.focusedChild_ = item + + el.focus() + } + } +} + +Component.registerComponent('MenuFocusFixed', MenuFocusFixed) +export { MenuFocusFixed } diff --git a/client/src/assets/player/shared/settings/settings-menu-button.ts b/client/src/assets/player/shared/settings/settings-menu-button.ts index 2ce175655..bbc5cc751 100644 --- a/client/src/assets/player/shared/settings/settings-menu-button.ts +++ b/client/src/assets/player/shared/settings/settings-menu-button.ts @@ -4,9 +4,9 @@ import { SettingsDialog } from './settings-dialog' import { SettingsMenuItem } from './settings-menu-item' import { SettingsPanel } from './settings-panel' import { SettingsPanelChild } from './settings-panel-child' +import { MenuFocusFixed } from './menu-focus-fixed' const Button = videojs.getComponent('Button') -const Menu = videojs.getComponent('Menu') const Component = videojs.getComponent('Component') export interface SettingsButtonOptions extends videojs.ComponentOptions { @@ -19,7 +19,7 @@ export interface SettingsButtonOptions extends videojs.ComponentOptions { class SettingsButton extends Button { dialog: SettingsDialog dialogEl: HTMLElement - menu: videojs.Menu + menu: MenuFocusFixed panel: SettingsPanel panelChild: SettingsPanelChild @@ -122,6 +122,7 @@ class SettingsButton extends Button { bindEvents () { document.addEventListener('click', this.documentClickHandler) + if (this.isInIframe()) { window.addEventListener('blur', this.documentClickHandler) } @@ -153,8 +154,7 @@ class SettingsButton extends Button { this.setDialogSize(this.getComponentSize(this.menu)) - const firstChild = this.menu.children()[0] - if (firstChild) firstChild.focus() + this.menu.focus() } hideDialog () { @@ -209,7 +209,12 @@ class SettingsButton extends Button { } buildMenu () { - this.menu = new Menu(this.player()) + this.menu = new MenuFocusFixed(this.player()) + this.menu.on('escaped-key', () => { + this.hideDialog() + this.focus() + }) + this.menu.addClass('vjs-main-menu') const entries = this.settingsButtonOptions.entries diff --git a/client/src/assets/player/shared/settings/settings-menu-item.ts b/client/src/assets/player/shared/settings/settings-menu-item.ts index 96a7f7268..d860c760c 100644 --- a/client/src/assets/player/shared/settings/settings-menu-item.ts +++ b/client/src/assets/player/shared/settings/settings-menu-item.ts @@ -144,7 +144,7 @@ class SettingsMenuItem extends MenuItem { createEl () { const el = videojs.dom.createEl('li', { className: 'vjs-menu-item', - tabIndex: -1 + tabIndex: 0 }) this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', { @@ -191,8 +191,7 @@ class SettingsMenuItem extends MenuItem { this.settingsButton.setDialogSize(this.size) - const firstChild = this.subMenu.menu.children()[0] - if (firstChild) firstChild.focus() + this.subMenu.menu.focus() } else { videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') } @@ -249,8 +248,7 @@ class SettingsMenuItem extends MenuItem { this.setMargin() mainMenuEl.style.opacity = '1' - const firstChild = this.mainMenu.children()[0] - if (firstChild) firstChild.focus() + this.mainMenu.focus() }, 0) }