diff --git a/src/assets/css/global-settings.styl b/src/assets/css/global-settings.styl index dc040de..adf4712 100644 --- a/src/assets/css/global-settings.styl +++ b/src/assets/css/global-settings.styl @@ -98,34 +98,56 @@ .bx-settings-row { display: flex; - margin-bottom: 8px; - padding: 2px 4px; + padding: 6px 12px; + position: relative; label { flex: 1; align-self: center; margin-bottom: 0; - padding-left: 10px; } - &:focus-within { - @media (hover: none) { - background-color: #242424; - } + &:hover, &:focus-within { + background-color: #242424; } input { align-self: center; accent-color: var(--bx-primary-button-color); + + &:focus { + accent-color: var(--bx-danger-button-color); + } } - select:disabled { - -webkit-appearance: none; - background: transparent; - text-align-last: right; - border: none; - color: #fff; + select { + &:disabled { + -webkit-appearance: none; + background: transparent; + text-align-last: right; + border: none; + color: #fff; + } + } + + 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) { + &::before { + content: ' '; + border-radius: 4px; + border: 2px solid #fff; + position: absolute; + top: 0; + left: 0; + bottom: 0; + } } } @@ -161,6 +183,10 @@ &:hover { color: #6dd72b; } + + &:focus { + text-decoration: underline; + } } .bx-settings-custom-user-agent { diff --git a/src/modules/ui/global-settings.ts b/src/modules/ui/global-settings.ts index fdd4e0d..3e14e71 100644 --- a/src/modules/ui/global-settings.ts +++ b/src/modules/ui/global-settings.ts @@ -131,7 +131,12 @@ export function setupSettingsUi() { 'href': SCRIPT_HOME, 'target': '_blank', }, 'Better xCloud ' + SCRIPT_VERSION), - createButton({icon: BxIcon.QUESTION, label: t('help'), url: 'https://better-xcloud.github.io/features/'}), + createButton({ + icon: BxIcon.QUESTION, + style: ButtonStyle.FOCUSABLE, + label: t('help'), + url: 'https://better-xcloud.github.io/features/', + }), ) ); $updateAvailable = CE('a', { @@ -238,7 +243,9 @@ export function setupSettingsUi() { let $control: any; let $inpCustomUserAgent: HTMLInputElement; - let labelAttrs = {}; + let labelAttrs: any = { + tabindex: '-1', + }; if (settingId === PrefKey.USER_AGENT_PROFILE) { let defaultUserAgent = (window.navigator as any).orgUserAgent || window.navigator.userAgent; @@ -271,7 +278,7 @@ export function setupSettingsUi() { } else if (settingId === PrefKey.SERVER_REGION) { let selectedValue; - $control = CE('select', {id: `bx_setting_${settingId}`}); + $control = CE('select', {id: `bx_setting_${settingId}`, tabindex: 0}); $control.name = $control.id; $control.addEventListener('change', (e: Event) => { @@ -317,7 +324,7 @@ export function setupSettingsUi() { } else { $control = toPrefElement(settingId, onChange); } - labelAttrs = {'for': $control.id, 'tabindex': 0}; + labelAttrs['for'] = $control.id; } // Disable unsupported settings @@ -325,14 +332,19 @@ export function setupSettingsUi() { ($control as HTMLInputElement).disabled = true; } + // Make disabled control elements un-focusable + if ($control.disabled && !!$control.getAttribute('tabindex')) { + $control.setAttribute('tabindex', -1); + } + const $label = CE('label', labelAttrs, settingLabel); if (settingNote) { $label.appendChild(CE('b', {}, settingNote)); } const $elm = CE('div', {'class': 'bx-settings-row'}, - $label, - $control - ); + $label, + $control, + ); $wrapper.appendChild($elm); @@ -361,7 +373,12 @@ export function setupSettingsUi() { $wrapper.appendChild($reloadBtnWrapper); // Donation link - const $donationLink = CE('a', {'class': 'bx-donation-link', href: 'https://ko-fi.com/redphx', target: '_blank'}, `❤️ ${t('support-better-xcloud')}`); + const $donationLink = CE('a', { + 'class': 'bx-donation-link', + href: 'https://ko-fi.com/redphx', + target: '_blank', + tabindex: 0, + }, `❤️ ${t('support-better-xcloud')}`); $wrapper.appendChild($donationLink); // Show Game Pass app version diff --git a/src/utils/settings.ts b/src/utils/settings.ts index 6d9e578..a7af348 100644 --- a/src/utils/settings.ts +++ b/src/utils/settings.ts @@ -26,7 +26,7 @@ export enum SettingElementType { export class SettingElement { static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) { - const $control = CE('select') as HTMLSelectElement; + const $control = CE('select', {tabindex: 0}) as HTMLSelectElement; for (let value in setting.options) { const label = setting.options[value]; @@ -50,7 +50,7 @@ export class SettingElement { } static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}) { - const $control = CE('select', {'multiple': true}); + const $control = CE('select', {multiple: true, tabindex: 0}); if (params && params.size) { $control.setAttribute('size', params.size.toString()); } @@ -93,7 +93,7 @@ export class SettingElement { } static #renderNumber(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) { - const $control = CE('input', {'type': 'number', 'min': setting.min, 'max': setting.max}) as HTMLInputElement; + const $control = CE('input', {'tabindex': 0, 'type': 'number', 'min': setting.min, 'max': setting.max}) as HTMLInputElement; $control.value = currentValue; onChange && $control.addEventListener('change', (e: Event) => { const target = e.target as HTMLInputElement; @@ -108,7 +108,7 @@ export class SettingElement { } static #renderCheckbox(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) { - const $control = CE('input', {'type': 'checkbox'}) as HTMLInputElement; + const $control = CE('input', {'type': 'checkbox', 'tabindex': 0}) as HTMLInputElement; $control.checked = currentValue; onChange && $control.addEventListener('change', e => { @@ -149,13 +149,21 @@ export class SettingElement { }; const $wrapper = CE('div', {'class': 'bx-number-stepper'}, - $decBtn = CE('button', {'data-type': 'dec'}, '-') as HTMLButtonElement, - $text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement, - $incBtn = CE('button', {'data-type': 'inc'}, '+') as HTMLButtonElement, - ); + $decBtn = CE('button', {'data-type': 'dec', tabindex: -1}, '-') as HTMLButtonElement, + $text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement, + $incBtn = CE('button', {'data-type': 'inc', tabindex: -1}, '+') as HTMLButtonElement, + ); if (!options.disabled && !options.hideSlider) { - $range = CE('input', {'type': 'range', 'min': MIN, 'max': MAX, 'value': value, 'step': STEPS}) as HTMLInputElement; + $range = CE('input', { + type: 'range', + min: MIN, + max: MAX, + value: value, + step: STEPS, + tabindex: 0, + }) as HTMLInputElement; + $range.addEventListener('input', e => { value = parseInt((e.target as HTMLInputElement).value); $text.textContent = renderTextValue(value);