Focus / Key Handling (or Remotes)
activeElement
At any time there is only one element that can have focus, the activeElement. activeElement
is a global Solid Signal that points to the element with focus. You can also set focus to any element using setActiveElement
or calling elm.setFocus()
import { createEffect, on } from "solid-js";
import { activeElement, setActiveElement } from "@lightningjs/solid";
// Get notified whenever the activeElement changes
createEffect(on(activeElement, (elm) => {
focusRingRef.x = elm.x;
}, { defer: true}))
// autofocus attribute will setActiveElement on this when intially created
<Button autofocus>TV Shows</Button>
let myButton;
onMount(() => {
myButton.setFocus();
// DONT Do This (prefer to use setFocus so forwardFocus works)
setActiveElement(myButton)
})
<Button ref={myButton}>Sports</Button>
import { createEffect, on } from "solid-js";
import { activeElement, setActiveElement } from "@lightningjs/solid";
// Get notified whenever the activeElement changes
createEffect(on(activeElement, (elm) => {
focusRingRef.x = elm.x;
}, { defer: true}))
// autofocus attribute will setActiveElement on this when intially created
<Button autofocus>TV Shows</Button>
let myButton;
onMount(() => {
myButton.setFocus();
// DONT Do This (prefer to use setFocus so forwardFocus works)
setActiveElement(myButton)
})
<Button ref={myButton}>Sports</Button>
forwardFocus
Sometimes an element is being focused via setFocus
but you really want a child element to receive focus. In that case forwardFocus={childIndexNumber}
will skip setting activeElement on this element and instead setFocus on this.children[childIndexNumber]
. If you have more complicated needs, forwardFocus
also takes a function for you to setFocus on any element you want or you can return false from the function for that element to receive focus.
// Focus on the column but then focus on first child
<Column autofocus forwardFocus={0}>
// Focus on the column but then focus on first child
<Column autofocus forwardFocus={0}>
Key Handling
In order to handle input from users, you'll need to add the useFocusManager
primitive.
> npm i @lightningjs/solid-primitives
> npm i @lightningjs/solid-primitives
useFocusManager
useFocusManager
adds key handling, focusPath tracking, and focus and blur events on components, and is setup once on app initialization. It returns a signal, focusPath which is an array of elements that currently have focus. When activeElement
changes, the focusPath will be recalculated. During which all elements in focus will have a focus
state added and onFocus(currentFocusedElm, prevFocusedElm)
event called. Elements losing focus will have focus
state removed and onBlur(currentFocusedElm, prevFocusedElm)
called.
import { useFocusManager } from "@lightningjs/solid-primitives";
const App = () => {
const focusPath = useFocusManager({
Announcer: 'a',
Menu: 'm',
Text: 't',
Buttons: 'b',
});
...
}
import { useFocusManager } from "@lightningjs/solid-primitives";
const App = () => {
const focusPath = useFocusManager({
Announcer: 'a',
Menu: 'm',
Text: 't',
Buttons: 'b',
});
...
}
The calculated focusPath is used for handling key events. When a key is pressed, the keyMap
looks for the keyName and corresponding value to call on${key}
first, then a generic onKeyPress
on the activeElement, and then every parent until the keypress is handled. To handle a keypress and stop propagation, the handler must return true
. Any other return value or no return value will continue the going through the focusPath looking for additional handlers. If you have multiple keys to use for a single event pass in an Array of keys. (Left: ['ArrowLeft', 37]).
The custom keys object will be merged with the default key mapping:
const defaultKeyMap = {
ArrowLeft: 'Left',
ArrowRight: 'Right',
ArrowUp: 'Up',
ArrowDown: 'Down',
Enter: 'Enter',
' ': 'Space',
Backspace: 'Back',
Escape: 'Escape',
37: 'Left',
39: 'Right',
38: 'Up',
40: 'Down',
13: 'Enter',
32: 'Space',
8: 'Back',
27: 'Escape',
};
const defaultKeyMap = {
ArrowLeft: 'Left',
ArrowRight: 'Right',
ArrowUp: 'Up',
ArrowDown: 'Down',
Enter: 'Enter',
' ': 'Space',
Backspace: 'Back',
Escape: 'Escape',
37: 'Left',
39: 'Right',
38: 'Up',
40: 'Down',
13: 'Enter',
32: 'Space',
8: 'Back',
27: 'Escape',
};