Android build v1.0.60 stadium updated

This commit is contained in:
Warren H
2026-06-23 13:13:57 -04:00
parent c2888c287b
commit cbe42b6164
6 changed files with 184 additions and 26 deletions
+60 -17
View File
@@ -125,6 +125,9 @@ const STORAGE_KEY = 'ashen-halls-input-bindings-v1'
const PREFERENCES_STORAGE_KEY = 'ashen-halls-input-preferences-v1'
const GAME_ACTION_EVENT = 'ashen-halls-game-action'
const NATIVE_CONTROLLER_EVENT = 'ashen-halls-native-controller'
const FOCUSABLE_SELECTOR = 'button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])'
let lastControllerFocus: HTMLElement | null = null
type CaptureState = {
device: InputDevice
@@ -234,25 +237,41 @@ function focusableElements() {
).find(isVisible)
const scope: ParentNode = keyboard ?? pauseMenu ?? dialog ?? document
return Array.from(
scope.querySelectorAll<HTMLElement>(
'button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])',
),
scope.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
).filter(isVisible)
}
function rememberFocusableControl(element: HTMLElement) {
lastControllerFocus = element
}
function focusControl(element: HTMLElement) {
rememberFocusableControl(element)
element.focus({ preventScroll: true })
}
function currentFocusableControl(candidates = focusableElements()) {
const active = document.activeElement
if (active instanceof HTMLElement && candidates.includes(active)) {
rememberFocusableControl(active)
return active
}
if (lastControllerFocus && candidates.includes(lastControllerFocus) && isVisible(lastControllerFocus)) {
return lastControllerFocus
}
return null
}
export function focusFirstControl() {
const first = focusableElements()[0]
first?.focus({ preventScroll: true })
if (first) focusControl(first)
return first
}
function moveFocus(action: InputAction) {
const candidates = focusableElements()
if (candidates.length === 0) return
const current = document.activeElement instanceof HTMLElement
&& candidates.includes(document.activeElement)
? document.activeElement
: null
const current = currentFocusableControl(candidates)
if (!current) {
focusFirstControl()
return
@@ -279,7 +298,7 @@ function moveFocus(action: InputAction) {
const next = ranked[0]?.candidate
if (!next) return
next.focus({ preventScroll: true })
focusControl(next)
}
function hasUiOverlay() {
@@ -456,12 +475,12 @@ export function InputProvider({ children }: { children: ReactNode }) {
if (action.startsWith('navigate')) {
if (uiOverlay || !combatActive) moveFocus(action)
} else if (action === 'confirm') {
const active = document.activeElement
const active = currentFocusableControl()
if (isTextInput(active)) {
setKeyboardInput(active)
window.requestAnimationFrame(() => focusFirstControl())
} else if (
active instanceof HTMLElement
active
&& active.matches('button:not(:disabled), [role="button"]')
&& isVisible(active)
) {
@@ -581,6 +600,29 @@ export function InputProvider({ children }: { children: ReactNode }) {
return () => window.removeEventListener('keydown', onKeyDown)
}, [assignBinding, dispatchAction])
useEffect(() => {
const onFocusIn = (event: FocusEvent) => {
const target = event.target
if (!(target instanceof HTMLElement)) return
if (!target.matches(FOCUSABLE_SELECTOR) || !isVisible(target)) return
rememberFocusableControl(target)
}
const onPointerDown = (event: PointerEvent) => {
document.documentElement.dataset.inputDevice = 'pc'
const target = event.target
if (!(target instanceof Element)) return
const control = target.closest<HTMLElement>(FOCUSABLE_SELECTOR)
if (!control || !isVisible(control)) return
rememberFocusableControl(control)
}
document.addEventListener('focusin', onFocusIn)
document.addEventListener('pointerdown', onPointerDown, { capture: true })
return () => {
document.removeEventListener('focusin', onFocusIn)
document.removeEventListener('pointerdown', onPointerDown, { capture: true })
}
}, [])
useEffect(() => {
const listener = (event: Event) => {
const detail = (event as CustomEvent<{ token: string; repeat?: boolean }>).detail
@@ -595,16 +637,17 @@ export function InputProvider({ children }: { children: ReactNode }) {
const combatActive = document.querySelector('[data-combat-active="true"]')
if (combatActive) return
const candidates = focusableElements()
const active = document.activeElement
const activeIsUsable = active instanceof HTMLElement
&& candidates.includes(active)
&& isVisible(active)
const activeControl = currentFocusableControl(candidates)
if (
(!activeIsUsable || document.activeElement === document.body)
(!activeControl || document.activeElement === document.body)
&& !keyboardInputRef.current
&& !captureRef.current
) {
focusFirstControl()
if (activeControl) {
focusControl(activeControl)
} else {
focusFirstControl()
}
}
}
const observer = new MutationObserver(() => {