Compare commits

..

16 Commits

25 changed files with 413 additions and 79 deletions

View File

@ -1,5 +1,5 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 5.4.0
// @version 5.4.1
// ==/UserScript==

File diff suppressed because one or more lines are too long

View File

@ -69,7 +69,7 @@
height: var(--bx-button-height);
&:not(:only-child) {
margin-right: 4px;
margin-right: 10px;
}
}
@ -117,3 +117,7 @@ button.bx-inactive {
opacity: 0.2;
background: transparent !important;
}
.bx-button-shortcut {
margin-top: 10px;
}

View File

@ -203,3 +203,23 @@
display: block;
width: 100%;
}
.bx-debug-info {
button {
margin-top: 10px;
}
pre {
margin-top: 10px;
cursor: copy;
color: white;
padding: 8px;
border: 1px solid #2d2d2d;
background: #212121;
white-space: break-spaces;
&:hover {
background: #272727;
}
}
}

View File

@ -5,7 +5,7 @@
--bx-monospaced-font: Consolas, "Courier New", Courier, monospace;
--bx-promptfont-font: promptfont;
--bx-button-height: 36px;
--bx-button-height: 40px;
--bx-default-button-color: #2d3036;
--bx-default-button-hover-color: #515863;

View File

@ -1,6 +1,9 @@
.bx-stream-settings-dialog {
display: flex;
position: fixed;
top: 0;
right: 0;
bottom: 0;
z-index: var(--bx-stream-settings-z-index);
opacity: 0.98;
user-select: none;
@ -22,10 +25,8 @@
}
.bx-stream-settings-tabs {
position: fixed;
top: 0;
right: 420px;
display: flex;
position: fixed;
flex-direction: column;
border-radius: 0 0 0 8px;
box-shadow: 0px 0px 6px #000;
@ -60,10 +61,6 @@
.bx-stream-settings-tab-contents {
flex-direction: column;
position: fixed;
right: 0;
top: 0;
bottom: 0;
padding: 14px 14px 0;
width: 420px;
background: #1a1b1e;
@ -74,6 +71,8 @@
text-align: center;
box-shadow: 0px 0px 6px #000;
overflow: overlay;
margin-left: 56px;
z-index: 1;
> div[data-tab-group=mkb] {
display: flex;
@ -108,6 +107,11 @@
}
}
@media screen and (max-width: 500px) {
.bx-stream-settings-tab-contents {
width: calc(100vw - 56px);
}
}
.bx-stream-settings-row {
display: flex;

View File

@ -0,0 +1,4 @@
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
<path d="M13.253 3.639c0-.758-.615-1.373-1.373-1.373H3.639c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V3.639zm0 16.481c0-.758-.615-1.373-1.373-1.373H3.639c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V20.12zm16.481 0c0-.758-.615-1.373-1.373-1.373H20.12c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V20.12zM19.262 7.76h9.957"/>
<path d="M24.24 2.781v9.957"/>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@ -35,6 +35,7 @@ import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
import { UiSection } from "./enums/ui-sections";
import { HeaderSection } from "./modules/ui/header";
import { GameTile } from "./modules/ui/game-tile";
import { ProductDetailsPage } from "./modules/ui/product-details";
// Handle login page
@ -198,6 +199,13 @@ window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
});
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
const component = (e as any).component;
if (component === 'product-details') {
ProductDetailsPage.injectShortcutButton();
}
});
function unload() {
if (!STATES.isPlaying) {
return;

View File

@ -183,14 +183,14 @@ export class ControllerShortcut {
$fragment.appendChild($option);
}
$container.dataset.hasGamepad = hasGamepad.toString();
if (hasGamepad) {
$select.appendChild($fragment);
$select.selectedIndex = 0;
$select.dispatchEvent(new Event('change'));
$select.dispatchEvent(new Event('input'));
}
$container.dataset.hasGamepad = hasGamepad.toString();
}
static #switchProfile(profile: string) {
@ -205,9 +205,9 @@ export class ControllerShortcut {
const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!;
$select.value = actions[button] || '';
BxEvent.dispatch($select, 'change', {
ignoreOnChange: true,
});
BxEvent.dispatch($select, 'input', {
ignoreOnChange: true,
});
}
}

View File

@ -1,4 +1,4 @@
import { SCRIPT_VERSION, STATES } from "@utils/global";
import { AppInterface, SCRIPT_VERSION, STATES } from "@utils/global";
import { BX_FLAGS } from "@utils/bx-flags";
import { getPref, PrefKey } from "@utils/preferences";
import { VibrationManager } from "@modules/vibration-manager";
@ -799,6 +799,22 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
str = str.substring(0, index) + codeSetCurrentlyFocusedInteractable + str.substring(index);
return str;
},
// product-details-page.js#2388, 24.17.20
detectProductDetailsPage(str: string) {
let index = str.indexOf('{location:"ProductDetailPage",');
if (index === -1) {
return false;
}
index = str.indexOf('return', index - 40);
if (index === -1) {
return false;
}
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, {component: "product-details"});' + str.substring(index);
return str;
},
};
let PATCH_ORDERS: PatchArray = [
@ -820,6 +836,7 @@ let PATCH_ORDERS: PatchArray = [
'exposeDialogRoutes',
'enableTvRoutes',
AppInterface && 'detectProductDetailsPage',
'overrideStorageGetSettings',
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentlyFocusedInteractable',

View File

@ -1,6 +1,7 @@
import vertClarityBoost from "./shaders/clarity_boost.vert" with { type: "text" };
import fsClarityBoost from "./shaders/clarity_boost.fs" with { type: "text" };
import { BxLogger } from "@/utils/bx-logger";
import { getPref, PrefKey } from "@/utils/preferences";
const LOG_TAG = 'WebGL2Player';
@ -120,11 +121,13 @@ export class WebGL2Player {
}
#setupShaders() {
BxLogger.info(LOG_TAG, 'Setting up', getPref(PrefKey.VIDEO_POWER_PREFERENCE));
const gl = this.#$canvas.getContext('webgl2', {
isBx: true,
antialias: true,
alpha: false,
powerPreference: 'high-performance',
powerPreference: getPref(PrefKey.VIDEO_POWER_PREFERENCE),
}) as WebGL2RenderingContext;
this.#gl = gl;

View File

@ -260,9 +260,20 @@ export class StreamPlayer {
this.#resizePlayer();
}
reloadPlayer() {
this.#cleanUpWebGL2Player();
this.#playerType = StreamPlayerType.VIDEO;
this.setPlayerType(StreamPlayerType.WEBGL2, false);
}
#cleanUpWebGL2Player() {
// Clean up WebGL2 Player
this.#webGL2Player?.destroy();
this.#webGL2Player = null;
}
destroy() {
// Cleanup WebGL2 Player
this.#webGL2Player?.destroy();
this.#webGL2Player = null;
this.#cleanUpWebGL2Player();
}
}

View File

@ -8,6 +8,7 @@ export function onChangeVideoPlayerType() {
const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE);
const $videoProcessing = document.getElementById('bx_setting_video_processing') as HTMLSelectElement;
const $videoSharpness = document.getElementById('bx_setting_video_sharpness') as HTMLElement;
const $videoPowerPreference = document.getElementById('bx_setting_video_power_preference') as HTMLElement;
let isDisabled = false;
@ -28,6 +29,9 @@ export function onChangeVideoPlayerType() {
$videoProcessing.disabled = isDisabled;
$videoSharpness.dataset.disabled = isDisabled.toString();
// Hide Power Preference setting if renderer isn't WebGL2
$videoPowerPreference.closest('.bx-stream-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2);
updateVideoPlayer();
}

View File

@ -112,6 +112,17 @@ export class StreamSettings {
}, {
pref: PrefKey.VIDEO_PROCESSING,
onChange: updateVideoPlayer,
}, {
pref: PrefKey.VIDEO_POWER_PREFERENCE,
onChange: () => {
const streamPlayer = STATES.currentStream.streamPlayer;
if (!streamPlayer) {
return;
}
streamPlayer.reloadPlayer();
updateVideoPlayer();
},
}, {
pref: PrefKey.VIDEO_SHARPNESS,
onChange: updateVideoPlayer,
@ -324,6 +335,9 @@ export class StreamSettings {
(window as any).BX_EXPOSED.disableGamepadPolling = true;
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
// Update video's settings
onChangeVideoPlayerType();
}
hide() {
@ -476,6 +490,11 @@ export class StreamSettings {
$sibling && $sibling.focus();
return;
}
// If it's the first/last item -> loop around
const pseudo = direction === NavigationDirection.UP ? 'last-of-type' : 'first-of-type';
const $target = this.$tabs!.querySelector(`svg:not(.bx-gone):${pseudo}`);
$target && ($target as HTMLElement).focus();
} else if (direction === NavigationDirection.RIGHT) {
this.#focusFirstVisibleSetting();
}
@ -513,6 +532,12 @@ export class StreamSettings {
return;
}
}
// If it's the first/last item -> loop around
// TODO: bugged if pseudo is "first-of-type" and the first setting is disabled
const pseudo = direction === NavigationDirection.UP ? ':last-of-type' : '';
const $target = this.$settings!.querySelector(`div[data-tab-group]:not(.bx-gone) div[data-focus-container]${pseudo} [tabindex="0"]:not(:disabled):last-of-type`);
$target && ($target as HTMLElement).focus();
} else if (direction === NavigationDirection.LEFT || direction === NavigationDirection.RIGHT) {
// Find all child elements with tabindex
const children = Array.from($parent.querySelectorAll('[tabindex="0"]'));
@ -643,6 +668,15 @@ export class StreamSettings {
this.hide();
});
// Close dialog when not clicking on any child elements in the dialog
$container.addEventListener('click', e => {
if (e.target === $container) {
e.preventDefault();
e.stopPropagation();
this.hide();
}
});
for (const settingTab of this.SETTINGS_UI) {
if (!settingTab) {
continue;
@ -753,8 +787,5 @@ export class StreamSettings {
document.documentElement.appendChild($overlay);
document.documentElement.appendChild($container);
// Update video's settings
onChangeVideoPlayerType();
}
}

View File

@ -1,4 +1,4 @@
import { STATES, AppInterface, SCRIPT_VERSION } from "@utils/global";
import { STATES, AppInterface, SCRIPT_VERSION, deepClone } from "@utils/global";
import { CE, createButton, ButtonStyle } from "@utils/html";
import { BxIcon } from "@utils/bx-icon";
import { getPreferredServerRegion } from "@utils/region";
@ -9,6 +9,8 @@ import { PatcherCache } from "../patcher";
import { UserAgentProfile } from "@enums/user-agent";
import { BxSelectElement } from "@/web-components/bx-select";
import { StreamSettings } from "../stream/stream-settings";
import { BX_FLAGS } from "@/utils/bx-flags";
import { Toast } from "@/utils/toast";
const SETTINGS_UI = {
'Better xCloud': {
@ -455,6 +457,47 @@ export function setupSettingsUi() {
$wrapper.appendChild(CE('div', {'class': 'bx-settings-app-version'}, `xCloud website version ${appVersion} (${appDate})`));
} catch (e) {}
// Show Debug info
const debugInfo = deepClone(BX_FLAGS.DeviceInfo);
const debugSettings = [
PrefKey.STREAM_TARGET_RESOLUTION,
PrefKey.STREAM_CODEC_PROFILE,
PrefKey.VIDEO_PLAYER_TYPE,
PrefKey.VIDEO_PROCESSING,
PrefKey.VIDEO_SHARPNESS,
];
debugInfo['settings'] = {};
for (const key of debugSettings) {
debugInfo['settings'][key] = getPref(key);
}
const $debugInfo = CE('div', {class: 'bx-debug-info'},
createButton({
label: 'Debug info',
style: ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
console.log(e);
(e.target as HTMLElement).closest('button')?.nextElementSibling?.classList.toggle('bx-gone');
},
}),
CE('pre', {
class: 'bx-gone',
on: {
click: async (e: Event) => {
try {
await navigator.clipboard.writeText((e.target as HTMLElement).innerText);
Toast.show('Copied to clipboard', '', {instant: true});
} catch (err) {
console.error('Failed to copy: ', err);
}
},
},
}, '```\n' + JSON.stringify(debugInfo, null, ' ') + '\n```'),
);
$wrapper.appendChild($debugInfo);
$container.appendChild($wrapper);
// Add Settings UI to the web page

View File

@ -89,15 +89,15 @@ export class GuideMenu {
// "Stream settings" button
buttons.push(GuideMenu.#BUTTONS.streamSetting);
// "App settings" & "Close app" buttons
if (AppInterface) {
buttons.push(GuideMenu.#BUTTONS.appSettings);
buttons.push(GuideMenu.#BUTTONS.closeApp);
}
// "App settings" button
AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
// Reload page
// "Reload page" button
buttons.push(GuideMenu.#BUTTONS.reloadPage);
// "Close app" buttons
AppInterface && buttons.push(GuideMenu.#BUTTONS.closeApp);
const $buttons = GuideMenu.#renderButtons(buttons);
const $lastDivider = $dividers[$dividers.length - 1];

View File

@ -0,0 +1,36 @@
import { BX_FLAGS } from "@/utils/bx-flags";
import { BxIcon } from "@/utils/bx-icon";
import { AppInterface } from "@/utils/global";
import { ButtonStyle, createButton } from "@/utils/html";
import { t } from "@/utils/translation";
export class ProductDetailsPage {
private static $btnShortcut = createButton({
classes: ['bx-button-shortcut'],
icon: BxIcon.CREATE_SHORTCUT,
label: t('create-shortcut'),
style: ButtonStyle.FOCUSABLE,
tabIndex: 0,
onClick: e => {
AppInterface && AppInterface.createShortcut(window.location.pathname.substring(6));
},
});
private static shortcutTimeoutId: number | null = null;
static injectShortcutButton() {
if (!AppInterface || BX_FLAGS.DeviceInfo?.deviceType !== 'android') {
return;
}
ProductDetailsPage.shortcutTimeoutId && clearTimeout(ProductDetailsPage.shortcutTimeoutId);
ProductDetailsPage.shortcutTimeoutId = window.setTimeout(() => {
// Find action buttons container
const $container = document.querySelector('div[class*=ActionButtons-module__container]');
if ($container) {
this.$btnShortcut.style.width = $container.getBoundingClientRect().width + 'px';
$container.parentElement?.appendChild(ProductDetailsPage.$btnShortcut);
}
}, 500);
}
}

View File

@ -48,6 +48,8 @@ export enum BxEvent {
XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown',
XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed',
XCLOUD_RENDERING_COMPONENT = 'bx-xcloud-rendering-page',
}
export enum XcloudEvent {

View File

@ -11,6 +11,11 @@ type BxFlags = Partial<{
FeatureGates: {[key: string]: boolean} | null,
IsSupportedTvBrowser: boolean,
DeviceInfo: Partial<{
deviceType: 'android' | 'android-tv' | 'webos' | 'unknown',
userAgent?: string,
}>,
}>
// Setup flags
@ -25,6 +30,10 @@ const DEFAULT_FLAGS: BxFlags = {
ForceNativeMkbTitles: [],
FeatureGates: null,
DeviceInfo: {
deviceType: 'unknown',
},
}
export const BX_FLAGS: BxFlags = Object.assign(DEFAULT_FLAGS, window.BX_FLAGS || {});
@ -32,4 +41,8 @@ try {
delete window.BX_FLAGS;
} catch (e) {}
if (!BX_FLAGS.DeviceInfo!.userAgent) {
BX_FLAGS.DeviceInfo!.userAgent = window.navigator.userAgent;
}
export const NATIVE_FETCH = window.fetch;

View File

@ -1,6 +1,7 @@
import iconCommand from "@assets/svg/command.svg" with { type: "text" };
import iconController from "@assets/svg/controller.svg" with { type: "text" };
import iconCopy from "@assets/svg/copy.svg" with { type: "text" };
import iconCreateShortcut from "@assets/svg/create-shortcut.svg" with { type: "text" };
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
import iconHome from "@assets/svg/home.svg" with { type: "text" };
@ -11,9 +12,9 @@ import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
import iconTouchControlDisable from "@assets/svg/touch-control-disable.svg" with { type: "text" };
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
import iconVirtualController from "@assets/svg/virtual-controller.svg" with { type: "text" };
// Game Bar
@ -37,6 +38,7 @@ export const BxIcon = {
STREAM_STATS: iconStreamStats,
COMMAND: iconCommand,
CONTROLLER: iconController,
CREATE_SHORTCUT: iconCreateShortcut,
DISPLAY: iconDisplay,
HOME: iconHome,
NATIVE_MKB: iconNativeMkb,

View File

@ -4,6 +4,8 @@ import { getPref, PrefKey } from "./preferences";
export let FeatureGates: {[key: string]: boolean} = {
'PwaPrompt': false,
'EnableWifiWarnings': false,
'EnableUpdateRequiredPage': false,
'ShowForcedUpdateScreen': false,
};
// Disable context menu in Home page

View File

@ -35,18 +35,23 @@ function createElement<T=HTMLElement>(elmName: string, props: {[index: string]:
if (hasNs) {
$elm.setAttributeNS(null, key, props[key]);
} else {
$elm.setAttribute(key, props[key]);
if (key === 'on') {
for (const eventName in props[key]) {
$elm.addEventListener(eventName, props[key][eventName]);
}
} else {
$elm.setAttribute(key, props[key]);
}
}
}
for (let i = 2, size = arguments.length; i < size; i++) {
const arg = arguments[i];
const argType = typeof arg;
if (argType === 'string' || argType === 'number') {
$elm.appendChild(document.createTextNode(arg));
} else if (arg) {
if (arg instanceof Node) {
$elm.appendChild(arg);
} else if (arg !== null && typeof arg !== 'undefined') {
$elm.appendChild(document.createTextNode(arg));
}
}

View File

@ -9,6 +9,7 @@ import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
import { UserAgentProfile } from "@/enums/user-agent";
import { UiSection } from "@/enums/ui-sections";
import { BypassServers } from "@/enums/bypass-servers";
import { BX_FLAGS } from "./bx-flags";
export enum PrefKey {
LAST_UPDATE_CHECK = 'version_last_check',
@ -82,6 +83,7 @@ export enum PrefKey {
VIDEO_PLAYER_TYPE = 'video_player_type',
VIDEO_PROCESSING = 'video_processing',
VIDEO_POWER_PREFERENCE = 'video_power_preference',
VIDEO_SHARPNESS = 'video_sharpness',
VIDEO_RATIO = 'video_ratio',
VIDEO_BRIGHTNESS = 'video_brightness',
@ -557,7 +559,7 @@ export class Preferences {
[PrefKey.UI_CONTROLLER_FRIENDLY]: {
label: t('controller-friendly-ui'),
default: false,
default: !STATES.browser.capabilities.touch || BX_FLAGS.DeviceInfo?.deviceType === "android-tv",
},
[PrefKey.UI_LAYOUT]: {
@ -610,7 +612,7 @@ export class Preferences {
[PrefKey.USER_AGENT_PROFILE]: {
label: t('user-agent-profile'),
note: '⚠️ ' + t('unexpected-behavior'),
default: 'default',
default: BX_FLAGS.DeviceInfo?.deviceType === 'android-tv' ? UserAgentProfile.VR_OCULUS : 'default',
options: {
[UserAgentProfile.DEFAULT]: t('default'),
[UserAgentProfile.WINDOWS_EDGE]: 'Edge + Windows',
@ -637,6 +639,15 @@ export class Preferences {
[StreamVideoProcessing.CAS]: t('amd-fidelity-cas'),
},
},
[PrefKey.VIDEO_POWER_PREFERENCE]: {
label: t('gpu-configuration'),
default: 'default',
options: {
'default': t('default'),
'high-performance': t('high-performance'),
'low-power': t('low-power'),
},
},
[PrefKey.VIDEO_SHARPNESS]: {
label: t('sharpness'),
type: SettingElementType.NUMBER_STEPPER,

View File

@ -76,6 +76,7 @@ const Texts = {
"controller-shortcuts-xbox-note": "Button to open the Guide menu",
"controller-vibration": "Controller vibration",
"copy": "Copy",
"create-shortcut": "Create shortcut",
"custom": "Custom",
"deadzone-counterweight": "Deadzone counterweight",
"decrease": "Decrease",
@ -109,6 +110,7 @@ const Texts = {
"fortnite-force-console-version": "Fortnite: force console version",
"game-bar": "Game Bar",
"getting-consoles-list": "Getting the list of consoles...",
"gpu-configuration": "GPU configuration",
"help": "Help",
"hide": "Hide",
"hide-idle-cursor": "Hide mouse cursor on idle",
@ -116,6 +118,7 @@ const Texts = {
"hide-sections": "Hide sections",
"hide-system-menu-icon": "Hide System menu's icon",
"hide-touch-controller": "Hide touch controller",
"high-performance": "High performance",
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
"horizontal-sensitivity": "Horizontal sensitivity",
"ignore": "Ignore",
@ -129,6 +132,7 @@ const Texts = {
"left-stick": "Left stick",
"loading-screen": "Loading screen",
"local-co-op": "Local co-op",
"low-power": "Low power",
"map-mouse-to": "Map mouse to",
"may-not-work-properly": "May not work properly!",
"menu": "Menu",

View File

@ -22,7 +22,6 @@ export class BxSelectElement {
});
const isMultiple = $select.multiple;
let visibleIndex = $select.selectedIndex;
let $checkBox: HTMLInputElement;
let $label: HTMLElement;
@ -42,7 +41,7 @@ export class BxSelectElement {
});
$checkBox.addEventListener('input', e => {
const $option = getOptionAtIndex(visibleIndex);
const $option = getOptionAtIndex($select.selectedIndex);
$option && ($option.selected = (e.target as HTMLInputElement).checked);
$select.dispatchEvent(new Event('input'));
@ -61,7 +60,7 @@ export class BxSelectElement {
const render = () => {
// console.log('options', this.options, 'selectedIndices', this.selectedIndices, 'selectedOptions', this.selectedOptions);
visibleIndex = normalizeIndex(visibleIndex);
const visibleIndex = normalizeIndex($select.selectedIndex);
const $option = getOptionAtIndex(visibleIndex);
let content = '';
@ -108,11 +107,10 @@ export class BxSelectElement {
const onPrevNext = (e: Event) => {
const goNext = e.target === $btnNext;
const currentIndex = visibleIndex;
const currentIndex = $select.selectedIndex;
let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
newIndex = normalizeIndex(newIndex);
visibleIndex = newIndex;
if (!isMultiple && newIndex !== currentIndex) {
$select.selectedIndex = newIndex;
}