mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-07 16:17:20 +02:00
Make Stream settings dialog controller-friendly
This commit is contained in:
parent
be338f3e34
commit
b66cb448ec
@ -17,11 +17,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bx-settings-wrapper {
|
.bx-settings-wrapper {
|
||||||
width: 450px;
|
min-width: 450px;
|
||||||
|
max-width: 600px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 12px 6px;
|
padding: 12px 6px;
|
||||||
|
|
||||||
@media screen and (max-width: 450px) {
|
@media screen and (max-width: 450px) {
|
||||||
|
min-width: unset;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
font-family: var(--bx-monospaced-font);
|
font-family: var(--bx-monospaced-font);
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -49,6 +49,11 @@
|
|||||||
background: #2f2f2f;
|
background: #2f2f2f;
|
||||||
border-color: #484848;
|
border-color: #484848;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #fff;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,13 +75,14 @@
|
|||||||
box-shadow: 0px 0px 6px #000;
|
box-shadow: 0px 0px 6px #000;
|
||||||
overflow: overlay;
|
overflow: overlay;
|
||||||
|
|
||||||
> div[data-group=mkb] {
|
> div[data-tab-group=mkb] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
*:focus {
|
*:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
@ -106,8 +112,23 @@
|
|||||||
.bx-stream-settings-row {
|
.bx-stream-settings-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-bottom: 1px solid #40404080;
|
border-bottom: 1px solid #40404080;
|
||||||
margin-bottom: 16px;
|
padding: 16px 8px;
|
||||||
padding-bottom: 16px;
|
border-left: 2px solid transparent;
|
||||||
|
|
||||||
|
&:hover, &:focus-within {
|
||||||
|
background-color: #242424;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox],
|
||||||
|
select {
|
||||||
|
&:focus {
|
||||||
|
filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input:focus), &:has(select:focus), &:has(button:focus) {
|
||||||
|
border-left-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
> label {
|
> label {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -120,6 +141,10 @@
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
accent-color: var(--bx-primary-button-color);
|
accent-color: var(--bx-primary-button-color);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
accent-color: var(--bx-danger-button-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select:disabled {
|
select:disabled {
|
||||||
@ -143,7 +168,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bx-stream-settings-tab-contents {
|
.bx-stream-settings-tab-contents {
|
||||||
div[data-group="shortcuts"] {
|
div[data-tab-group="shortcuts"] {
|
||||||
> div {
|
> div {
|
||||||
&[data-has-gamepad=true] {
|
&[data-has-gamepad=true] {
|
||||||
> div:first-of-type {
|
> div:first-of-type {
|
||||||
|
@ -73,10 +73,10 @@
|
|||||||
|
|
||||||
button.bx-button {
|
button.bx-button {
|
||||||
border: none;
|
border: none;
|
||||||
height: 30px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 30px;
|
line-height: 24px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -85,7 +85,8 @@
|
|||||||
|
|
||||||
&.bx-inactive {
|
&.bx-inactive {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.1;
|
opacity: 0.2;
|
||||||
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
@ -14,6 +14,19 @@ import { StreamStats } from "./stream-stats";
|
|||||||
import { BxSelectElement } from "@/web-components/bx-select";
|
import { BxSelectElement } from "@/web-components/bx-select";
|
||||||
import { onChangeVideoPlayerType, updateVideoPlayer } from "./stream-settings-utils";
|
import { onChangeVideoPlayerType, updateVideoPlayer } from "./stream-settings-utils";
|
||||||
|
|
||||||
|
enum FocusDirection {
|
||||||
|
UP,
|
||||||
|
RIGHT,
|
||||||
|
DOWN,
|
||||||
|
LEFT,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FocusContainer {
|
||||||
|
OUTSIDE,
|
||||||
|
TABS,
|
||||||
|
SETTINGS,
|
||||||
|
}
|
||||||
|
|
||||||
export class StreamSettings {
|
export class StreamSettings {
|
||||||
private static instance: StreamSettings;
|
private static instance: StreamSettings;
|
||||||
|
|
||||||
@ -26,6 +39,8 @@ export class StreamSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private $container: HTMLElement | undefined;
|
private $container: HTMLElement | undefined;
|
||||||
|
private $tabs: HTMLElement | undefined;
|
||||||
|
private $settings: HTMLElement | undefined;
|
||||||
private $overlay: HTMLElement | undefined;
|
private $overlay: HTMLElement | undefined;
|
||||||
|
|
||||||
readonly SETTINGS_UI = [{
|
readonly SETTINGS_UI = [{
|
||||||
@ -248,40 +263,207 @@ export class StreamSettings {
|
|||||||
const $container = this.$container!;
|
const $container = this.$container!;
|
||||||
// Select tab
|
// Select tab
|
||||||
if (tabId) {
|
if (tabId) {
|
||||||
const $tab = $container.querySelector(`.bx-stream-settings-tabs svg[data-group=${tabId}]`);
|
const $tab = $container.querySelector(`.bx-stream-settings-tabs svg[data-tab-group=${tabId}]`);
|
||||||
$tab && $tab.dispatchEvent(new Event('click'));
|
$tab && $tab.dispatchEvent(new Event('click'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show overlay
|
||||||
this.$overlay!.classList.remove('bx-gone');
|
this.$overlay!.classList.remove('bx-gone');
|
||||||
this.$overlay!.dataset.isPlaying = STATES.isPlaying.toString();
|
this.$overlay!.dataset.isPlaying = STATES.isPlaying.toString();
|
||||||
|
|
||||||
|
// Show dialog
|
||||||
$container.classList.remove('bx-gone');
|
$container.classList.remove('bx-gone');
|
||||||
|
// Lock scroll bar
|
||||||
document.body.classList.add('bx-no-scroll');
|
document.body.classList.add('bx-no-scroll');
|
||||||
|
|
||||||
|
if (getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
|
||||||
|
// Focus the first visible setting
|
||||||
|
this.#focusDirection(FocusDirection.DOWN);
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
$container.addEventListener('keydown', this);
|
||||||
|
}
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
|
// Hide overlay
|
||||||
this.$overlay!.classList.add('bx-gone');
|
this.$overlay!.classList.add('bx-gone');
|
||||||
|
// Hide dialog
|
||||||
this.$container!.classList.add('bx-gone');
|
this.$container!.classList.add('bx-gone');
|
||||||
|
// Show scroll bar
|
||||||
document.body.classList.remove('bx-no-scroll');
|
document.body.classList.remove('bx-no-scroll');
|
||||||
|
|
||||||
|
// Remove event listeners
|
||||||
|
this.$container!.removeEventListener('keydown', this);
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#handleTabsNavigation($focusing: HTMLElement, direction: FocusDirection) {
|
||||||
|
if (direction === FocusDirection.UP || direction === FocusDirection.DOWN) {
|
||||||
|
let $sibling = $focusing;
|
||||||
|
const siblingProperty = direction === FocusDirection.UP ? 'previousElementSibling' : 'nextElementSibling';
|
||||||
|
|
||||||
|
while ($sibling[siblingProperty]) {
|
||||||
|
$sibling = $sibling[siblingProperty] as HTMLElement;
|
||||||
|
$sibling && $sibling.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (direction === FocusDirection.RIGHT) {
|
||||||
|
this.#focusFirstVisibleSetting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleSettingsNavigation($focusing: HTMLElement, direction: FocusDirection) {
|
||||||
|
// If current element's tabIndex property is not 0
|
||||||
|
if ($focusing.tabIndex !== 0) {
|
||||||
|
// Find first visible setting
|
||||||
|
const $childSetting = $focusing.querySelector('div[data-tab-group]:not(.bx-gone) [tabindex="0"]:not(a)') as HTMLElement;
|
||||||
|
if ($childSetting) {
|
||||||
|
$childSetting.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current element is setting -> Find the next one
|
||||||
|
// Find parent
|
||||||
|
let $parent = $focusing.closest('.bx-stream-settings-row') || $focusing.closest('h2');
|
||||||
|
|
||||||
|
if (!$parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find sibling setting
|
||||||
|
let $sibling = $parent;
|
||||||
|
if (direction === FocusDirection.UP || direction === FocusDirection.DOWN) {
|
||||||
|
const siblingProperty = direction === FocusDirection.UP ? 'previousElementSibling' : 'nextElementSibling';
|
||||||
|
|
||||||
|
while ($sibling[siblingProperty]) {
|
||||||
|
$sibling = $sibling[siblingProperty];
|
||||||
|
const $childSetting = $sibling.querySelector('[tabindex="0"]:last-of-type') as HTMLElement;
|
||||||
|
if ($childSetting) {
|
||||||
|
$childSetting.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (direction === FocusDirection.LEFT || direction === FocusDirection.RIGHT) {
|
||||||
|
// Find all child elements with tabindex
|
||||||
|
const children = Array.from($parent.querySelectorAll('[tabindex="0"]'));
|
||||||
|
const index = children.indexOf($focusing);
|
||||||
|
let nextIndex;
|
||||||
|
if (direction === FocusDirection.LEFT) {
|
||||||
|
nextIndex = index - 1;
|
||||||
|
} else {
|
||||||
|
nextIndex = index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextIndex = Math.max(-1, Math.min(nextIndex, children.length - 1));
|
||||||
|
if (nextIndex === -1) {
|
||||||
|
// Focus setting tabs
|
||||||
|
const $tab = this.$tabs!.querySelector('svg.bx-active') as HTMLElement;
|
||||||
|
$tab && $tab.focus();
|
||||||
|
} else if (nextIndex !== index) {
|
||||||
|
(children[nextIndex] as HTMLElement).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#focusFirstVisibleSetting() {
|
||||||
|
// Focus the first visible tab content
|
||||||
|
const $tab = this.$settings!.querySelector('div[data-tab-group]:not(.bx-gone)') as HTMLElement;
|
||||||
|
|
||||||
|
if ($tab) {
|
||||||
|
// Focus on the first focusable setting
|
||||||
|
const $control = $tab.querySelector('[tabindex="0"]:not(a)') as HTMLElement;
|
||||||
|
if ($control) {
|
||||||
|
$control.focus();
|
||||||
|
} else {
|
||||||
|
// Focus tab
|
||||||
|
$tab.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#focusDirection(direction: FocusDirection) {
|
||||||
|
const $tabs = this.$tabs!;
|
||||||
|
const $settings = this.$settings!;
|
||||||
|
|
||||||
|
// Get current focused element
|
||||||
|
let $focusing = document.activeElement as HTMLElement;
|
||||||
|
|
||||||
|
let focusContainer = FocusContainer.OUTSIDE;
|
||||||
|
if ($focusing) {
|
||||||
|
if ($settings.contains($focusing)) {
|
||||||
|
focusContainer = FocusContainer.SETTINGS;
|
||||||
|
} else if ($tabs.contains($focusing)) {
|
||||||
|
focusContainer = FocusContainer.TABS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not focusing any element or the focused element is not inside the dialog
|
||||||
|
if (focusContainer === FocusContainer.OUTSIDE) {
|
||||||
|
this.#focusFirstVisibleSetting();
|
||||||
|
return;
|
||||||
|
} else if (focusContainer === FocusContainer.SETTINGS) {
|
||||||
|
this.#handleSettingsNavigation($focusing, direction);
|
||||||
|
} else if (focusContainer === FocusContainer.TABS) {
|
||||||
|
this.#handleTabsNavigation($focusing, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event: Event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'keydown':
|
||||||
|
const $target = event.target as HTMLElement;
|
||||||
|
const keyboardEvent = event as KeyboardEvent;
|
||||||
|
const keyCode = keyboardEvent.code;
|
||||||
|
|
||||||
|
if (keyCode === 'ArrowUp' || keyCode === 'ArrowDown') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.#focusDirection(keyCode === 'ArrowUp' ? FocusDirection.UP : FocusDirection.DOWN);
|
||||||
|
} else if (keyCode === 'ArrowLeft' || keyCode === 'ArrowRight') {
|
||||||
|
if (($target as any).type !== 'range') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.#focusDirection(keyCode === 'ArrowLeft' ? FocusDirection.LEFT : FocusDirection.RIGHT);
|
||||||
|
}
|
||||||
|
} else if (keyCode === 'Enter' || keyCode === 'Space') {
|
||||||
|
if ($target instanceof SVGElement) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
$target.dispatchEvent(new Event('click'));
|
||||||
|
}
|
||||||
|
} else if (keyCode === 'Escape') {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#setupDialog() {
|
#setupDialog() {
|
||||||
let $tabs: HTMLElement;
|
let $tabs: HTMLElement;
|
||||||
let $settings: HTMLElement;
|
let $settings: HTMLElement;
|
||||||
|
|
||||||
const $overlay = CE('div', {'class': 'bx-stream-settings-overlay bx-gone'});
|
const $overlay = CE('div', {class: 'bx-stream-settings-overlay bx-gone'});
|
||||||
this.$overlay = $overlay;
|
this.$overlay = $overlay;
|
||||||
|
|
||||||
const $container = CE('div', {'class': 'bx-stream-settings-dialog bx-gone'},
|
const $container = CE('div', {class: 'bx-stream-settings-dialog bx-gone'},
|
||||||
$tabs = CE('div', {'class': 'bx-stream-settings-tabs'}),
|
$tabs = CE('div', {class: 'bx-stream-settings-tabs'}),
|
||||||
$settings = CE('div', {'class': 'bx-stream-settings-tab-contents'}),
|
$settings = CE('div', {
|
||||||
|
class: 'bx-stream-settings-tab-contents',
|
||||||
|
tabindex: 10,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.$container = $container;
|
this.$container = $container;
|
||||||
|
this.$tabs = $tabs;
|
||||||
|
this.$settings = $settings;
|
||||||
|
|
||||||
// Close dialog when clicking on the overlay
|
// Close dialog when clicking on the overlay
|
||||||
$overlay.addEventListener('click', e => {
|
$overlay.addEventListener('click', e => {
|
||||||
@ -296,10 +478,12 @@ export class StreamSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const $svg = createSvgIcon(settingTab.icon);
|
const $svg = createSvgIcon(settingTab.icon);
|
||||||
|
$svg.tabIndex = 0;
|
||||||
|
|
||||||
$svg.addEventListener('click', e => {
|
$svg.addEventListener('click', e => {
|
||||||
// Switch tab
|
// Switch tab
|
||||||
for (const $child of Array.from($settings.children)) {
|
for (const $child of Array.from($settings.children)) {
|
||||||
if ($child.getAttribute('data-group') === settingTab.group) {
|
if ($child.getAttribute('data-tab-group') === settingTab.group) {
|
||||||
$child.classList.remove('bx-gone');
|
$child.classList.remove('bx-gone');
|
||||||
} else {
|
} else {
|
||||||
$child.classList.add('bx-gone');
|
$child.classList.add('bx-gone');
|
||||||
@ -316,7 +500,7 @@ export class StreamSettings {
|
|||||||
|
|
||||||
$tabs.appendChild($svg);
|
$tabs.appendChild($svg);
|
||||||
|
|
||||||
const $group = CE('div', {'data-group': settingTab.group, 'class': 'bx-gone'});
|
const $group = CE('div', {'data-tab-group': settingTab.group, 'class': 'bx-gone'});
|
||||||
|
|
||||||
for (const settingGroup of settingTab.items) {
|
for (const settingGroup of settingTab.items) {
|
||||||
if (!settingGroup) {
|
if (!settingGroup) {
|
||||||
@ -327,9 +511,10 @@ export class StreamSettings {
|
|||||||
CE('span', {}, settingGroup.label),
|
CE('span', {}, settingGroup.label),
|
||||||
settingGroup.help_url && createButton({
|
settingGroup.help_url && createButton({
|
||||||
icon: BxIcon.QUESTION,
|
icon: BxIcon.QUESTION,
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
|
||||||
url: settingGroup.help_url,
|
url: settingGroup.help_url,
|
||||||
title: t('help'),
|
title: t('help'),
|
||||||
|
tabIndex: 0,
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
if (settingGroup.note) {
|
if (settingGroup.note) {
|
||||||
|
@ -203,6 +203,8 @@ export function setupSettingsUi() {
|
|||||||
$wrapper.appendChild($div);
|
$wrapper.appendChild($div);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let localeSwitchingTimeout: number | null;
|
||||||
|
|
||||||
const onChange = async (e: Event) => {
|
const onChange = async (e: Event) => {
|
||||||
// Clear PatcherCache;
|
// Clear PatcherCache;
|
||||||
PatcherCache.clear();
|
PatcherCache.clear();
|
||||||
@ -214,12 +216,17 @@ export function setupSettingsUi() {
|
|||||||
$btnHeaderSettings && $btnHeaderSettings.classList.add('bx-danger');
|
$btnHeaderSettings && $btnHeaderSettings.classList.add('bx-danger');
|
||||||
|
|
||||||
if ((e.target as HTMLElement).id === 'bx_setting_' + PrefKey.BETTER_XCLOUD_LOCALE) {
|
if ((e.target as HTMLElement).id === 'bx_setting_' + PrefKey.BETTER_XCLOUD_LOCALE) {
|
||||||
// Update locale
|
if (getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
|
||||||
Translations.refreshCurrentLocale();
|
localeSwitchingTimeout && window.clearTimeout(localeSwitchingTimeout);
|
||||||
await Translations.updateTranslations();
|
localeSwitchingTimeout = window.setTimeout(() => {
|
||||||
|
Translations.refreshCurrentLocale();
|
||||||
|
Translations.updateTranslations();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
// Update locale
|
||||||
|
Translations.refreshCurrentLocale();
|
||||||
|
await Translations.updateTranslations();
|
||||||
|
|
||||||
// Don't refresh the page when using controller-friendly UI
|
|
||||||
if (!getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
|
|
||||||
$btnReload.textContent = t('settings-reloading');
|
$btnReload.textContent = t('settings-reloading');
|
||||||
$btnReload.click();
|
$btnReload.click();
|
||||||
}
|
}
|
||||||
@ -390,9 +397,9 @@ export function setupSettingsUi() {
|
|||||||
|
|
||||||
if ($control instanceof HTMLSelectElement && getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
|
if ($control instanceof HTMLSelectElement && getPref(PrefKey.UI_CONTROLLER_FRIENDLY)) {
|
||||||
// Controller-friendly <select>
|
// Controller-friendly <select>
|
||||||
$elm = CE('div', {'class': 'bx-settings-row'},
|
$elm = CE('div', {'class': 'bx-settings-row', 'data-group': 0},
|
||||||
$label,
|
$label,
|
||||||
CE('div', {class: 'bx-setting-control', 'data-group': 0}, BxSelectElement.wrap($control)),
|
CE('div', {class: 'bx-setting-control'}, BxSelectElement.wrap($control)),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$elm = CE('div', {'class': 'bx-settings-row', 'data-group': 0},
|
$elm = CE('div', {'class': 'bx-settings-row', 'data-group': 0},
|
||||||
|
@ -9,6 +9,7 @@ type BxButton = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: EventListener;
|
onClick?: EventListener;
|
||||||
|
tabIndex?: number;
|
||||||
attributes?: {[key: string]: any},
|
attributes?: {[key: string]: any},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +95,7 @@ export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
|||||||
options.title && $btn.setAttribute('title', options.title);
|
options.title && $btn.setAttribute('title', options.title);
|
||||||
options.disabled && (($btn as HTMLButtonElement).disabled = true);
|
options.disabled && (($btn as HTMLButtonElement).disabled = true);
|
||||||
options.onClick && $btn.addEventListener('click', options.onClick);
|
options.onClick && $btn.addEventListener('click', options.onClick);
|
||||||
|
typeof options.tabIndex === 'number' && ($btn.tabIndex = options.tabIndex!);
|
||||||
|
|
||||||
for (const key in options.attributes) {
|
for (const key in options.attributes) {
|
||||||
if (!$btn.hasOwnProperty(key)) {
|
if (!$btn.hasOwnProperty(key)) {
|
||||||
|
@ -175,13 +175,15 @@ export class SettingElement {
|
|||||||
$btnDec = CE('button', {
|
$btnDec = CE('button', {
|
||||||
'data-type': 'dec',
|
'data-type': 'dec',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
tabindex: -1,
|
class: options.hideSlider ? 'bx-focusable' : '',
|
||||||
|
tabindex: options.hideSlider ? 0 : -1,
|
||||||
}, '-') as HTMLButtonElement,
|
}, '-') as HTMLButtonElement,
|
||||||
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
||||||
$btnInc = CE('button', {
|
$btnInc = CE('button', {
|
||||||
'data-type': 'inc',
|
'data-type': 'inc',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
tabindex: -1,
|
class: options.hideSlider ? 'bx-focusable' : '',
|
||||||
|
tabindex: options.hideSlider ? 0 : -1,
|
||||||
}, '+') as HTMLButtonElement,
|
}, '+') as HTMLButtonElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -371,6 +371,7 @@ export class Translations {
|
|||||||
static async updateTranslations(async=false) {
|
static async updateTranslations(async=false) {
|
||||||
// Don't have to download en-US
|
// Don't have to download en-US
|
||||||
if (Translations.#selectedLocale === Translations.#EN_US) {
|
if (Translations.#selectedLocale === Translations.#EN_US) {
|
||||||
|
localStorage.removeItem(Translations.#KEY_TRANSLATIONS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@ import { ButtonStyle, CE, createButton } from "@utils/html";
|
|||||||
|
|
||||||
export class BxSelectElement {
|
export class BxSelectElement {
|
||||||
static wrap($select: HTMLSelectElement) {
|
static wrap($select: HTMLSelectElement) {
|
||||||
|
// Remove "tabindex" attribute from <select>
|
||||||
|
$select.removeAttribute('tabindex');
|
||||||
|
|
||||||
const $btnPrev = createButton({
|
const $btnPrev = createButton({
|
||||||
label: '<',
|
label: '<',
|
||||||
style: ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FOCUSABLE,
|
||||||
@ -88,10 +91,10 @@ export class BxSelectElement {
|
|||||||
const disableNext = visibleIndex === $select.querySelectorAll('option').length - 1;
|
const disableNext = visibleIndex === $select.querySelectorAll('option').length - 1;
|
||||||
|
|
||||||
$btnPrev.classList.toggle('bx-inactive', disablePrev);
|
$btnPrev.classList.toggle('bx-inactive', disablePrev);
|
||||||
disablePrev && document.activeElement === $btnPrev && $btnNext.focus();
|
// disablePrev && document.activeElement === $btnPrev && $btnNext.focus();
|
||||||
|
|
||||||
$btnNext.classList.toggle('bx-inactive', disableNext);
|
$btnNext.classList.toggle('bx-inactive', disableNext);
|
||||||
disableNext && document.activeElement === $btnNext &&$btnPrev.focus();
|
// disableNext && document.activeElement === $btnNext &&$btnPrev.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeIndex = (index: number): number => {
|
const normalizeIndex = (index: number): number => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user