Android build v1.0.60 stadium updated
This commit is contained in:
+60
-17
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user