bbc / lrud

Left, Right, Up, Down. A spatial navigation library for devices with input via directional controls.
Apache License 2.0
94 stars 21 forks source link

Active parents don't always deactivate when in a nested tree #96

Closed EwanRoycroft closed 1 year ago

EwanRoycroft commented 2 years ago

Describe the bug Consider the following tree:

VerticalRoot
|-- HorizontalList
|   |-- Button1
|-- VerticalList
|   |-- Row1
|   |   |-- Button2
|   |-- Row2
|   |   |-- Button3
  1. Focus begins on Button1.
  2. The user presses down and focus moves to Button2. HorizontalList becomes inactive. VerticalList and Row1 become active.
  3. The user presses down and focus moves to Button3. Row1 becomes inactive. Row2 becomes active.
  4. The user presses up and focus moves to Button2. Row2 becomes inactive. Row1 becomes inactive.
  5. The user presses up and focus moves to Button1. VerticalList becomes inactive. HorizontalList becomes active. Row1 remains active, however.
  6. The user presses down and focus moves to Button2. VerticalList becomes active and emits an onActive event, but Row1 does not, as it was already active.

The above sequence is an issue if you expect some behaviour to happen when Row1 becomes active. In my case, I want the page to scroll to each of the rows when they become active. This happens most of the time, but not in the case of step 6.

To Reproduce Repeat the above steps using the following code:

const { Lrud } = require('Lrud');

const navigation = new Lrud();

const onFocus = (node) => console.log('focus:', node.id);
const onBlur = (node) => console.log('blur:', node.id);
const onActive = (node) => console.log('active:', node.id);
const onInactive = (node) => console.log('inactive:', node.id);

navigation
    .registerNode('VerticalRoot', { orientation: 'vertical' })
    .registerNode('HorizontalList', { orientation: 'horizontal', onActive, onInactive })
    .registerNode('Button1', { isFocusable: true, parent: 'HorizontalList', onFocus, onBlur })
    .registerNode('VerticalList', { orientation: 'vertical', onActive, onInactive })
    .registerNode('Row1', { orientation: 'horizontal', parent: 'VerticalList', onActive, onInactive })
    .registerNode('Button2', { isFocusable: true, parent: 'Row1', onFocus, onBlur })
    .registerNode('Row2', { orientation: 'horizontal', parent: 'VerticalList', onActive, onInactive })
    .registerNode('Button3', { isFocusable: true, parent: 'Row2', onFocus, onBlur });

navigation.assignFocus('Button1');

window.addEventListener('keydown', (event) => {
    console.log('key:', event.code);
    navigation.handleKeyEvent(event);
});

You should get the following log:

 1 active: HorizontalList
 2 focus: Button1
 3 key: ArrowDown
 4 blur: Button1
 5 active: Row1
 6 inactive: HorizontalList
 7 active: VerticalList
 8 focus: Button2
 9 key: ArrowDown
10 blur: Button2
11 inactive: Row1
12 active: Row2
13 focus: Button3
14 key: ArrowUp
15 blur: Button3
16 inactive: Row2
17 active: Row1
18 focus: Button2
19 key: ArrowUp
20 blur: Button2
21 inactive: VerticalList
22 active: HorizontalList
23 focus: Button1
24 key: ArrowDown
25 blur: Button1
26 inactive: HorizontalList
27 active: VerticalList
28 focus: Button2

Expected behaviour There should be inactive: Row1 at log line 21. There should be active: Row1 at line 26.