Below you can find the Lrud setup for various common scenarios. Hopefully these should help illuminate various points of registering nodes in order to get the desired behaviour.
A miniature version of a search keyboard - utilising a grid and some buttons that are wider than others.
navigation.registerNode('keyboard', { orientation: 'vertical', isIndexAlign: true })
navigation
.registerNode('row-1', { orientation: 'horizontal' }) // note we don't explicitly set the parent - so Lrud assumes the root node
.registerNode('A', { parent: 'row-1', isFocusable: true })
.registerNode('B', { parent: 'row-1', isFocusable: true })
.registerNode('C', { parent: 'row-1', isFocusable: true })
.registerNode('D', { parent: 'row-1', isFocusable: true })
.registerNode('E', { parent: 'row-1', isFocusable: true })
.registerNode('F', { parent: 'row-1', isFocusable: true })
navigation
.registerNode('row-2', { orientation: 'horizontal' })
.registerNode('G', { parent: 'row-2', isFocusable: true })
.registerNode('H', { parent: 'row-2', isFocusable: true })
.registerNode('I', { parent: 'row-2', isFocusable: true })
.registerNode('J', { parent: 'row-2', isFocusable: true })
.registerNode('K', { parent: 'row-2', isFocusable: true })
.registerNode('L', { parent: 'row-2', isFocusable: true })
navigation
.registerNode('row-3', { orientation: 'horizontal' })
.registerNode('Space', { parent: 'row-3', indexRange: [1, 3], isFocusable: true }) // these buttons are wider, so are given index ranges
.registerNode('Delete', { parent: 'row-3', indexRange: [4, 6], isFocusable: true }) // these buttons are wider, so are given index ranges
## Recipe 2 - A series of wrapping rows
Representing multiple horizontal rows of content that a user could be browsing, where navigating past the end of a row should return the user focus to the start of that row. But, the rows are not a grid, and going down from the middle of one row should put the user focus to the start of the next row.
navigation.registerNode('root', { orientation: 'vertical' })
navigation
.registerNode('row-1', { parent: 'root', orientation: 'horizontal', isWrapping: true })
.registerNode('row-1-item-1', { parent: 'row-1', isFocusable: true })
.registerNode('row-1-item-2', { parent: 'row-1', isFocusable: true })
navigation
.registerNode('row-2', { parent: 'root', orientation: 'horizontal', isWrapping: true })
.registerNode('row-2-item-1', { parent: 'row-2', isFocusable: true })
.registerNode('row-2-item-2', { parent: 'row-2', isFocusable: true })
isIndexAlign: true
nodes e.g nested gridsSee docs/test-diagrams/fig-2.png
for the diagram of how this looks rendered out.
We sometimes want to have 2 nodes that are affected by their parent's isIndexAlign: true
, that are themselves also isIndexAlign: true
.
This could be thought of as "nested grids".
const navigation = new Lrud()
navigation.registerNode('root', { orientation: 'vertical', isIndexAlign: true })
navigation
.registerNode('grid-a', { parent: 'root', orientation: 'vertical', isIndexAlign: true })
.registerNode('grid-a-row-1', { parent: 'grid-a', orientation: 'horizontal' })
.registerNode('grid-a-row-1-col-1', { parent: 'grid-a-row-1', isFocusable: true })
.registerNode('grid-a-row-1-col-2', { parent: 'grid-a-row-1', isFocusable: true })
.registerNode('grid-a-row-2', { parent: 'grid-a', orientation: 'horizontal' })
.registerNode('grid-a-row-2-col-1', { parent: 'grid-a-row-2', isFocusable: true })
.registerNode('grid-a-row-2-col-2', { parent: 'grid-a-row-2', isFocusable: true })
navigation
.registerNode('grid-b', { parent: 'root', orientation: 'vertical', isIndexAlign: true })
.registerNode('grid-b-row-1', { parent: 'grid-b', orientation: 'horizontal' })
.registerNode('grid-b-row-1-col-1', { parent: 'grid-b-row-1', isFocusable: true })
.registerNode('grid-b-row-1-col-2', { parent: 'grid-b-row-1', isFocusable: true })
.registerNode('grid-b-row-2', { parent: 'grid-b', orientation: 'horizontal' })
.registerNode('grid-b-row-2-col-1', { parent: 'grid-b-row-2', isFocusable: true })
.registerNode('grid-b-row-2-col-2', { parent: 'grid-b-row-2', isFocusable: true })
## Recipe 4 - Cancelling moves due to external business logic
Perhaps you have a system where you only want a user to be able to navigate to a specific section of a page/app if some external logic authorizes and allows that move.
Thanks to shouldCancel
functions, we can block that movement.
const shouldCancelEnterItem = () => {
return !userPermissions.canSelectItem();
}
navigation.registerNode('root', { orientation: 'horizontal' })
navigation
.registerNode('left-col', { orientation: 'vertical' })
.registerNode('item-1', { parent: 'left-col', isFocusable: true })
.registerNode('item-2', { parent: 'left-col', isFocusable: true })
navigation
.registerNode('right-col', { orientation: 'vertical' })
.registerNode('item-a', { parent: 'right-col', shouldCancelEnter: shouldCancelEnterItem, isFocusable: true })
.registerNode('item-b', { parent: 'right-col', shouldCancelEnter: shouldCancelEnterItem, isFocusable: true })
navigation.assignFocus('item-1')
With the setup above, if the user attempted to select item-a
, or item-b
, shouldCancelEnterItem()
would be run. If this function returned true
, that movement would be blocked, and focus would remain on item-1
.