{"_id":"@a11y-ngx/keyboard-navigation","_rev":"2-3654402436a283b85fb604b13da5983b","name":"@a11y-ngx/keyboard-navigation","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@a11y-ngx/keyboard-navigation","version":"1.0.0","keywords":["keyboard","navigation","menu","menubar","dropdown","listbox","tabs","radio","tree","toolbar","slider","directive","a11y","accessibility","accessible","angular"],"author":{"url":"https://github.com/LDV2k3/","name":"Luciano Del Vacchio","email":"lucho.development@gmail.com"},"license":"MPL-2.0","_id":"@a11y-ngx/keyboard-navigation@1.0.0","maintainers":[{"name":"ldv","email":"lucho.development@gmail.com"}],"homepage":"https://github.com/LDV2k3/a11y-libraries/tree/master/projects/a11y-ngx/keyboard-navigation#readme","bugs":{"url":"https://github.com/LDV2k3/a11y-libraries/issues","email":"lucho.development@gmail.com"},"dist":{"shasum":"f6754694f09d06fd991fff818f9baea3189f6002","tarball":"https://registry.npmjs.org/@a11y-ngx/keyboard-navigation/-/keyboard-navigation-1.0.0.tgz","fileCount":22,"integrity":"sha512-jjzM77mbzky05z66E71PEj/T6Cx/quZPERBy1itnFNGag1VF821QJQjHTD7pQhZAqOVCmy2rgkNnBDJIzgCbgg==","signatures":[{"sig":"MEQCICX3XvIZRjo0DITicIS5IwVYS1FKPz0perO+jgfTylDBAiAdTZPXRnDznhfa+BWYuKZ+hK9DMJs8WMLVEaGpqEGgVw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":360312},"main":"bundles/a11y-ngx-keyboard-navigation.umd.js","es2015":"fesm2015/a11y-ngx-keyboard-navigation.js","module":"fesm2015/a11y-ngx-keyboard-navigation.js","esm2015":"esm2015/a11y-ngx-keyboard-navigation.js","gitHead":"68b0ae383ce26e1f4b2e5dd0f6ddeedae3efc2ef","typings":"a11y-ngx-keyboard-navigation.d.ts","_npmUser":{"name":"ldv","email":"lucho.development@gmail.com"},"fesm2015":"fesm2015/a11y-ngx-keyboard-navigation.js","_npmVersion":"8.19.4","description":"An Angular keyboard navigation engine with preset strategies (menu, dropdown, tabs, trees) to implement in your custom components","directories":{},"sideEffects":false,"_nodeVersion":"16.20.2","dependencies":{"tslib":"^2.3.0"},"_hasShrinkwrap":false,"peerDependencies":{"@angular/core":">=12.2.0 <22.0.0","@angular/common":">=12.2.0 <22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/keyboard-navigation_1.0.0_1779301751763_0.9278850226970485","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@a11y-ngx/keyboard-navigation","version":"1.0.1","description":"An Angular keyboard navigation engine with preset strategies (menu, dropdown, tabs, trees) to implement in your custom components","keywords":["keyboard","navigation","menu","menubar","dropdown","listbox","tabs","radio","tree","toolbar","slider","directive","a11y","accessibility","accessible","angular"],"homepage":"https://github.com/LDV2k3/a11y-libraries/tree/master/projects/a11y-ngx/keyboard-navigation#readme","bugs":{"url":"https://github.com/LDV2k3/a11y-libraries/issues","email":"lucho.development@gmail.com"},"license":"MPL-2.0","author":{"name":"Luciano Del Vacchio","email":"lucho.development@gmail.com","url":"https://github.com/LDV2k3/"},"peerDependencies":{"@angular/common":">=12.2.0 <22.0.0","@angular/core":">=12.2.0 <22.0.0"},"dependencies":{"tslib":"^2.3.0"},"main":"bundles/a11y-ngx-keyboard-navigation.umd.js","module":"fesm2015/a11y-ngx-keyboard-navigation.js","es2015":"fesm2015/a11y-ngx-keyboard-navigation.js","esm2015":"esm2015/a11y-ngx-keyboard-navigation.js","fesm2015":"fesm2015/a11y-ngx-keyboard-navigation.js","typings":"a11y-ngx-keyboard-navigation.d.ts","sideEffects":false,"gitHead":"689ce6d101ac1234231cbea02b89d4a146b214c4","_id":"@a11y-ngx/keyboard-navigation@1.0.1","_nodeVersion":"16.20.2","_npmVersion":"8.19.4","dist":{"integrity":"sha512-jRanyP1S8NB4A2XgWyhNGC3IDiRr7WqzbW56xjhGp88RQB250HE7QZ4kBAzaxhDUN+4cArDe95TChKQXYwnrjw==","shasum":"f9e2b90159149b3c7abb1f4897268e511071c912","tarball":"https://registry.npmjs.org/@a11y-ngx/keyboard-navigation/-/keyboard-navigation-1.0.1.tgz","fileCount":22,"unpackedSize":359994,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCdqrIXGXGt2yPcmlvwKBFG7WXum/PAU6k6lda1TGcNLwIhALz5GWndSPLtVJbn4E8t/XvoHphLZvhYghwGrT5ZshSw"}]},"_npmUser":{"name":"ldv","email":"lucho.development@gmail.com"},"directories":{},"maintainers":[{"name":"ldv","email":"lucho.development@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/keyboard-navigation_1.0.1_1784246546459_0.44072941780580366"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-20T18:29:11.555Z","modified":"2026-07-17T00:02:26.895Z","1.0.0":"2026-05-20T18:29:11.971Z","1.0.1":"2026-07-17T00:02:26.614Z"},"bugs":{"url":"https://github.com/LDV2k3/a11y-libraries/issues","email":"lucho.development@gmail.com"},"author":{"name":"Luciano Del Vacchio","email":"lucho.development@gmail.com","url":"https://github.com/LDV2k3/"},"license":"MPL-2.0","homepage":"https://github.com/LDV2k3/a11y-libraries/tree/master/projects/a11y-ngx/keyboard-navigation#readme","keywords":["keyboard","navigation","menu","menubar","dropdown","listbox","tabs","radio","tree","toolbar","slider","directive","a11y","accessibility","accessible","angular"],"description":"An Angular keyboard navigation engine with preset strategies (menu, dropdown, tabs, trees) to implement in your custom components","maintainers":[{"name":"ldv","email":"lucho.development@gmail.com"}],"readme":"# Keyboard Navigation\r\n\r\nA flexible keyboard navigation system designed for modern Angular applications.\r\n\r\n> **IMPORTANT:** This is a **navigation engine**, not a UI component.\r\n\r\nKeyboard Navigation, hereafter \"KeyNav\", provides a comprehensive solution for implementing keyboard controls.\r\n\r\nWhether you're building menus, dropdowns, trees, or custom interactive components, this library handles the complexity of keyboard interaction patterns for you.\r\n\r\nIt centralizes all keyboard interaction logic (Arrow keys, Home/End, Enter, Escape, etc.) and exposes a clean API that can be used as:\r\n\r\n- A **directive** attached to any element\r\n- A **core utility** inside higher-level UI libraries (menus, dropdowns, trees, tabs, toolbars, etc.)\r\n\r\n**The core idea is simple:** provide the engine an array of flat or nested items and, as the user interacts with the _allowed_ keys for the desired navigation type, the engine emits the relevant data the user is navigating _from_ and _to_ (e.g. moving to the previous item, opening an item, etc.).\r\n\r\n![Angular support from version 12 up to version 21](https://img.shields.io/badge/Angular-v12_to_v21-darkgreen?logo=angular)\r\n\r\nThis library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0 to ensure compatibility with a wide range of Angular versions. It has been tested up to v21.\r\n\r\n## Changelog\r\n\r\nSee the complete [changelog](https://github.com/LDV2k3/a11y-libraries/blob/master/projects/a11y-ngx/keyboard-navigation/CHANGELOG.md) for details on updates and breaking changes.\r\n\r\n## Index\r\n\r\n- [Installation](#installation)\r\n- [The KeyNav Config](#the-keynav-config)\r\n  - [The Navigation Type](#the-navigation-type)\r\n  - [The Throttle](#the-throttle)\r\n  - [The Page Size](#the-page-size)\r\n  - [The Disabled Property](#the-disabled-property)\r\n  - [The Children Property](#the-children-property)\r\n  - [The Custom Strategy](#the-custom-strategy)\r\n  - [The Orientation](#the-orientation)\r\n  - [Allow Navigate Disabled Items](#allow-navigate-disabled-items)\r\n  - [Allow Select First Child](#allow-select-first-child)\r\n  - [Allow Repeated Events](#allow-repeated-events)\r\n- [The Navigation State](#the-navigation-state)\r\n- [The Strategies](#the-strategies)\r\n  - [How the Strategy Works](#how-the-strategy-works)\r\n  - [The Strategy: Loop](#the-strategy-loop)\r\n  - [The Strategy: Keys](#the-strategy-keys)\r\n    - [The Strategy: Orientation Keys](#the-strategy-orientation-keys)\r\n  - [Preset Strategies](#the-preset-strategies)\r\n- [The Directive](#the-directive)\r\n  - [The Directive Inputs](#the-directive-inputs)\r\n  - [The Directive Outputs](#the-directive-outputs)\r\n- [The Service](#the-service)\r\n  - [The Service Public Methods](#the-service-public-methods)\r\n    - [The `manageKeyDown()` Method](#the-service-managekeydown-method)\r\n    - [The `executeKey()` Method](#the-service-executekey-method)\r\n    - [The `resetLastNavigationState()` Method](#the-service-resetlastnavigationstate-method)\r\n    - [The `init()` Method](#the-service-init-method)\r\n    - [The `getItems()` and `setItems()` Methods](#the-service-getitems-and-setitems-methods)\r\n    - [The `getConfig()` and `setConfig()` Methods](#the-service-getconfig-and-setconfig-methods)\r\n    - [The `getCurrent()` and `setCurrent()` Methods](#the-service-getcurrent-and-setcurrent-methods)\r\n- [Examples](#examples)\r\n  - [The Use of the Directive](#the-use-of-the-directive)\r\n    - [The Radio Buttons Component](#the-radio-buttons-component)\r\n    - [The Volume Control](#the-volume-control)\r\n  - [The Use of the Service](#the-use-of-the-service)\r\n\r\n## Installation\r\n\r\n1. Install the npm package:\r\n\r\n   `npm install @a11y-ngx/keyboard-navigation --save`\r\n\r\n2. Import the Module or Service as needed:\r\n\r\n   - To use the directive, import `A11yKeyboardNavigationModule` into your module or standalone component.\r\n     > See [the Use of the Directive](#the-use-of-the-directive).\r\n\r\n   - To use the service, add `KeyboardNavigationService` to your component's or directive's `providers` array.\r\n     > See [the Use of the Service](#the-use-of-the-service).\r\n\r\n## The KeyNav Config\r\n\r\n- **Type:** `KeyboardNavigationConfig`.\r\n  - **Directive Input:** `[a11yKeyNavConfig]`.\r\n  - **Service Method:** `setConfig()`.\r\n\r\n| Property | Type | Default | Description |\r\n| :------- | :--- | :-----: | :---------- |\r\n| `type` | `KeyboardNavigationType` | `'menu'` | See [the Navigation Type](#the-navigation-type) |\r\n| `throttleMs` | `number` | `100` | See [the Throttle](#the-throttle) |\r\n| `pageSize` | `number` | `10` | See [the Page Size](#the-page-size) |\r\n| `disabledProperty` | `string` | `'disabled'` | See [the Disabled Property](#the-disabled-property) |\r\n| `childrenProperty` | `string` | `'children'` | See [the Children Property](#the-children-property) |\r\n| `customStrategy` | `KeyboardNavigationStrategy` | `undefined` | See [the Custom Strategy](#the-custom-strategy) |\r\n| `orientation` | `'horizontal'` or `'vertical'` | `'horizontal'` | See [the Orientation](#the-orientation) |\r\n| `allowNavigateDisabled` | `boolean` | `false` | See [Allow Navigate Disabled Items](#allow-navigate-disabled-items) |\r\n| `allowSelectFirstChild` | `boolean` | `false` | See [Allow Select First Child](#allow-select-first-child) |\r\n| `allowRepeatedEventsFor` | `KeyboardNavigationKey[]` | `[]` | See [Allow Repeated Events](#allow-repeated-events) |\r\n\r\n### The Navigation Type\r\n\r\nThe engine already has some predefined strategies ready to be used, or you can define your own (see [the Custom Strategy](#the-custom-strategy)).\r\n\r\n- **Config Property:** `type`.\r\n- **Type:** `KeyboardNavigationType`.\r\n- **Default:** `'menu'`.\r\n- **You can use:** `'custom'`, `'dropdown'`, `'menu'`, `'menubar'`, `'radio'`, `'tree'`, `'tabs'`, `'toolbar'`, `'slider'`.\r\n\r\n### The Throttle\r\n\r\nThrottle to prevent rapid key-repeat events (in milliseconds).\r\n\r\n- **Config Property:** `throttleMs`.\r\n- **Type:** `number`.\r\n- **Default:** `100`.\r\n\r\n### The Page Size\r\n\r\nThe amount of items to page up or down.\r\n\r\n- **Config Property:** `pageSize`.\r\n- **Type:** `number`.\r\n- **Default:** `10`.\r\n\r\n### The Disabled Property\r\n\r\nSince the item could be anything, when it is an object, you can have a property that indicates that the item _is_ disabled.\r\n\r\nBased on what you need for your UX, you might want to include or skip those items from the navigation, depending on [`allowNavigateDisabled` property](#allow-navigate-disabled-items).\r\n\r\n- **Config Property:** `disabledProperty`.\r\n- **Type:** `string`.\r\n- **Default:** `'disabled'`.\r\n\r\n> 💡 Let's say:\r\n>\r\n> 1. Your item looks like `{ name: 'Save', blocked: true }`.\r\n> 2. Where \"blocked\" is your property to indicate that _is_ disabled.\r\n> 3. So you'll set the config as `{ disabledProperty: 'blocked' }`.\r\n\r\n### The Children Property\r\n\r\nWhen your items are objects, this value defines whether the item has _that_ property with children (an array of _child_ items) or not.\r\n\r\nFor those cases where you need to navigate nested items (such as menus), you probably have a property defined with those \"children\" within your item.\r\n\r\nFor the engine to understand the structure, and to properly update the state when an open/close action is executed, you have to set the correct value of _that_ property.\r\n\r\n- **Config Property:** `childrenProperty`.\r\n- **Type:** `string`.\r\n- **Default:** `'children'`.\r\n\r\n> 💡 Let's say:\r\n>\r\n> 1. Your item looks like `{ name: 'Edit', subitems: [...] }`.\r\n> 2. Where \"subitems\" is your property to indicate that it contains a sublevel of items.\r\n> 3. So you'll set the config as `{ childrenProperty: 'subitems' }`.\r\n\r\n### The Custom Strategy\r\n\r\nTo provide a custom strategy (map of keys and their actions) for navigation.\r\n\r\n> 📘 **NOTE:** You have to define [the `type`](#the-navigation-type) as `'custom'` for this to work.\r\n\r\n- **Config Property:** `customStrategy`.\r\n- **Type:** [`KeyboardNavigationStrategy`](#the-strategies).\r\n- **Default:** `undefined`.\r\n\r\n### The Orientation\r\n\r\nTo define the orientation of the navigation, if the strategy keys allow it.\r\n\r\nUsually a `'horizontal'` orientation allows _left_ and _right_ keys to navigate, while `'vertical'` allows _up_ and _down_.\r\n\r\n- **Config Property:** `orientation`.\r\n- **Type:** `'horizontal'` or `'vertical'`.\r\n- **Default:** `'horizontal'`.\r\n\r\n### Allow Navigate Disabled Items\r\n\r\nTo allow navigate through disabled items.\r\n\r\nWhen `false`, disabled items are skipped during navigation.\r\n\r\n- **Config Property:** `allowNavigateDisabled`.\r\n- **Type:** `boolean`.\r\n- **Default:** `false`.\r\n\r\n### Allow Select First Child\r\n\r\nTo allow auto-selecting first available child item when _open_ a sublist of items.\r\n\r\n- **Config Property:** `allowSelectFirstChild`.\r\n- **Type:** `boolean`.\r\n- **Default:** `false`.\r\n\r\n> Let's say you are building a menu system.\r\n>\r\n> - When navigated by keyboard, menus are supposed to auto-select the first available child item of a submenu as soon as it opens.\r\n> - When navigated by mouse, this does not happen:\r\n>   - User hovers over an item with submenu\r\n>   - The submenu opens, but it won't auto-select its first child.\r\n>\r\n> So, you can set this option at runtime whenever you need.\r\n\r\n### Allow Repeated Events\r\n\r\nBy default, the engine does not emit repeated navigation events when the resolved action would result in no state change. This prevents emitting repeated events that would produce the same movement or action.\r\n\r\n- **Config Property:** `allowRepeatedEventsFor`.\r\n- **Type:** `KeyboardNavigationKey[]`.\r\n- **Default:** `[]`.\r\n\r\nFor example:\r\n\r\n- When a strategy does not allow looping and you have reached the last item using `ArrowRight`, if you keep pressing the same key, the engine does not have any \"next\" item to go to, so it will **not** emit the _same_ event again.\r\n- When a strategy allows \"open\" nested items, but the current item does **not** contain any children, the event will be emitted once (maybe you need to know the user's intention), but will be ignored on subsequent attempts.\r\n\r\n> 📘 **NOTE:** In some cases, you may need to allow specific keys to trigger actions repeatedly as the user continues pressing them. For those scenarios, set those keys within this option.\r\n>\r\n> ```typescript\r\n> {\r\n>     allowRepeatedEventsFor: ['ArrowDown', 'ArrowLeft']\r\n> }\r\n> ```\r\n\r\n## The Navigation State\r\n\r\nThe navigation state is an object that describes what just happened during a keyboard interaction.\r\n\r\nWhenever something changes in the state, the engine will emit a new event.\r\n\r\nThis allows you to react to navigation changes, update UI focus, track user position, and implement any custom behavior based on:\r\n\r\n- Which item they navigated _from_ and _to_\r\n- Which item they open or close\r\n- Which item they select or deselect\r\n\r\n> See also: [allowRepeatedEventsFor](#allow-repeated-events).\r\n\r\n- **Type:** `KeyboardNavigationEvent<T = unknown>`.\r\n- **Properties:**\r\n\r\n  | Property | Type | Description |\r\n  | :------- | :--- | :---------- |\r\n  | `key` | `KeyboardNavigationKey` | The pressed key. See [the Strategy: Keys](#the-strategy-keys) |\r\n  | `action` | `KeyboardNavigationAction` | The action taken. See [the Strategy: Keys](#the-strategy-keys) |\r\n  | `itemFrom` | `T` or `undefined` | The previous item |\r\n  | `itemTo` | `T` or `undefined` | The current item |\r\n  | `indexFrom` | `number` | The previous index |\r\n  | `indexTo` | `number` | The current index |\r\n  | `pathFrom` | `number[]` | The previous path |\r\n  | `pathTo` | `number[]` | The current path |\r\n\r\nSee also [how the Strategy works](#how-the-strategy-works) to better understand how \"item\", \"index\" and \"path\" are established.\r\n\r\n## The Strategies\r\n\r\nA navigation strategy defines how keyboard input is translated into navigation behavior for a specific component type.\r\n\r\nIt defines:\r\n\r\n- A map of the allowed _keys_ associated to their corresponding _actions_.\r\n  - Optionally, you can define maps for _horizontal_ vs _vertical_ navigations.\r\n- How navigation behaves at boundaries (looping or clamping).\r\n- How nested items are handled (open / close).\r\n\r\nPredefined strategies are ready to be used, such as `'dropdown'`, `'menu'`, `'menubar'`, `'radio'`, `'tree'`, `'tabs'`, `'toolbar'` and `'slider'`. All of those are simply presets that encode common and well-known navigation patterns. See [the Preset Strategies](#the-preset-strategies).\r\n\r\n> **IMPORTANT:** The engine itself is unaware of UI concepts such as menus or trees. It only applies the rules defined by the selected navigation strategy and keeps a record of the state.\r\n\r\n- **Type:** `KeyboardNavigationStrategy`.\r\n- **Properties:**\r\n\r\n  | Property | Mandatory | Type | Description |\r\n  | :------- | :-------: | :--- | :---------- |\r\n  | `loop` | ✔️ Yes | `boolean` | See [the Strategy: Loop](#the-strategy-loop) |\r\n  | `keys` | ✔️ Yes | `Record<Key, Action>` | See [the Strategy: Keys](#the-strategy-keys) |\r\n  | `keysHorizontal` | ❌ No | `Record<Key, Action>` | See [the Strategy: Orientation Keys](#the-strategy-orientation-keys) |\r\n  | `keysVertical` | ❌ No | `Record<Key, Action>` | See [the Strategy: Orientation Keys](#the-strategy-orientation-keys) |\r\n\r\nFor instance, if you want to create your own custom \"radio buttons\" component, you can simply set the `type: 'radio'`, which translates to:\r\n\r\n```typescript\r\n{\r\n    loop: true,  // radio buttons should loop\r\n    keys: {      // these are the only allowed keys for radio buttons\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n        ArrowLeft: 'previous',\r\n        ArrowRight: 'next',\r\n    },\r\n}\r\n```\r\n\r\n### How the Strategy Works\r\n\r\nThe state is the result of pressing a key and the action associated _doing something_.\r\n\r\nThat _something_ can take place and modify the current position (index and path) of the navigation inside the service, or just return a \"dummy\" action for you to execute (like `'select'` and `'deselect'`).\r\n\r\nOnce the engine processes everything, it will emit an event of type [`KeyboardNavigationEvent`](#the-navigation-state) or `null` (if no valid key was found).\r\n\r\nFrom that event, we can find:\r\n\r\n- The \"key\" and \"action\".\r\n- The \"item\", \"index\" and \"path\":\r\n  - These 3 have a \"from\" (previous) and \"to\" (current) states.\r\n\r\n**Example on a nested navigation:**\r\n\r\nFor this example we won't use any specific strategy, only the logic on how it navigates based on the action:\r\n\r\n```typescript\r\n// Our items\r\nconst items = [\r\n    { name: 'Save' },\r\n    { name: 'Undo' },\r\n    { name: 'Redo' },\r\n    { name: 'Find' },\r\n    {\r\n        name: 'Edit',\r\n        subitems: [\r\n            { name: 'Cut' },\r\n            { name: 'Copy' },\r\n            { name: 'Paste' },\r\n        ],\r\n    },\r\n]\r\n```\r\n\r\nLet's say our very first action executed is `'next'`, we get:\r\n\r\n```typescript\r\n{   ...,\r\n    itemFrom: undefined,\r\n    itemTo: { name: 'Save' },\r\n    indexFrom: -1,\r\n    indexTo: 0,\r\n    pathFrom: [], // root level\r\n    pathTo: [],   // root level\r\n}\r\n```\r\n\r\nOur next action is `'last'`, we get:\r\n\r\n```typescript\r\n{   ...,\r\n    itemFrom: { name: 'Save' },\r\n    itemTo: { name: 'Edit', subitems: [...] },\r\n    indexFrom: 0,\r\n    indexTo: 4,\r\n    pathFrom: [],\r\n    pathTo: [],\r\n}\r\n```\r\n\r\nOur next action now is `'open'`, we get:\r\n\r\n```typescript\r\n{   ...,\r\n    itemFrom: { name: 'Edit', subitems: [...] },\r\n    itemTo: { name: 'Cut' },\r\n    indexFrom: 4,\r\n    indexTo: 0,\r\n    pathFrom: [], // root level\r\n    pathTo: [4],  // item 4 in root level\r\n}\r\n```\r\n\r\nWe repeat the `'open'` action, we get:\r\n\r\n```typescript\r\n{   ...,\r\n    itemFrom: { name: 'Cut' },\r\n    itemTo: { name: 'Cut' },\r\n    indexFrom: 0,\r\n    indexTo: 0,\r\n    pathFrom: [4],\r\n    pathTo: [4],\r\n}\r\n```\r\n\r\n> 👆 **NOTE:** Since there's nothing else to _open_ for that last action, the event still emits and the \"from\" and \"to\" data are identical.\r\n>\r\n> 📘 **NOTE 2:** The paths represent the \"open\" indices.\r\n>\r\n> - If a path is `[4, 0, 2]`, it means the current item is inside (is child of) index 2, inside index 0, inside index 4 (root level).\r\n> - If `pathFrom` is different than `pathTo`, means that you either opened or closed a sublevel of items.\r\n\r\n**Example on a flat navigation:**\r\n\r\nLet's say now we use the strategy `'radio'` and we are navigating a simple flat array of 3 items: `['Red', 'Green', 'Blue']`.\r\n\r\n- Since that strategy only uses 4 keys for navigation (`ArrowUp`, `ArrowDown`, `ArrowLeft` and `ArrowRight`), any other key will be ignored.\r\n- Since those 4 keys are associated with 2 \"movement\" actions (`previous` and `next`), the engine will try to move to any \"previous\" or \"next\" item from the current position (the index).\r\n- Let's assume we already are positioned in the first item (index 0, aka \"Red\").\r\n- Now, either we press `ArrowRight` or `ArrowDown`, the engine will process the action `next` and will find the next available item.\r\n  - So, we will get the event emitted as:\r\n\r\n    ```typescript\r\n    {\r\n        key: 'ArrowRight', // The pressed key\r\n        action: 'next',    // The action taken\r\n        itemFrom: 'Red',   // The previous item\r\n        itemTo: 'Green',   // The current item\r\n        indexFrom: 0,      // The previous index\r\n        indexTo: 1,        // The current index\r\n        pathFrom: [],      // The previous path (for nested objects)\r\n        pathTo: [],        // The current path (for nested objects)\r\n    }\r\n    ```\r\n\r\n  - Once we press `ArrowRight` again, we'll get:\r\n\r\n    ```typescript\r\n    {   ...,\r\n        itemFrom: 'Green', // The previous item\r\n        itemTo: 'Blue',    // The current item\r\n        indexFrom: 1,      // The previous index\r\n        indexTo: 2,        // The current index\r\n    }\r\n    ```\r\n\r\n  - We press `ArrowRight` again:\r\n\r\n    ```typescript\r\n    {   ...,\r\n        itemFrom: 'Blue',  // The previous item\r\n        itemTo: 'Red',     // The current item (index 0)\r\n        indexFrom: 2,      // The previous index (last item)\r\n        indexTo: 0,        // The current index (first item)\r\n    }\r\n    ```\r\n\r\n### The Strategy: Loop\r\n\r\nIt defines whether navigation wraps or stops when reaching the first/last item. For instance, `'radio'` and `'menu'` strategies should loop, while `'dropdown'` should not.\r\n\r\n- **Property:** `loop`.\r\n- **Type:** `boolean`.\r\n\r\n### The Strategy: Keys\r\n\r\nThe \"keys\" are a map between the pressed key (coming from the `KeyboardEvent.code`) and its associated action.\r\n\r\nDepending on the action, the engine will consider what to do based on the items it has and it will update the state accordingly.\r\n\r\n- **Property:** `keys`.\r\n- **Type:** `Record<Key, Action>`.\r\n- **Available Keys:**\r\n\r\n  | Key | Common Use |\r\n  | :-- | :--------- |\r\n  | 'ArrowUp' | Navigation |\r\n  | 'ArrowDown' | Navigation |\r\n  | 'ArrowLeft' | Navigation |\r\n  | 'ArrowRight' | Navigation |\r\n  | 'Home' | Navigation |\r\n  | 'End' | Navigation |\r\n  | 'PageUp' | Navigation |\r\n  | 'PageDown' | Navigation |\r\n  | 'Escape' | Action |\r\n  | 'Space' | Action |\r\n  | 'Enter' | Action |\r\n\r\n- **Available Actions:**\r\n\r\n  | Action | What it does |\r\n  | :----- | :----------- |\r\n  | 'previous' | Looks for the previous _available (*)_ item |\r\n  | 'next' | Looks for the next _available (*)_ item |\r\n  | 'first' | Looks for the first _available (*)_ item |\r\n  | 'last' | Looks for the last _available (*)_ item |\r\n  | 'pageUp' | Looks for the first _available (*)_ item in the **previous** page |\r\n  | 'pageDown' | Looks for the first _available (*)_ item in the **next** page |\r\n  | 'open' | Enters a sublevel of items (if any) and moves current index to the _first available (**)_ item |\r\n  | 'close' | Leaves the current sublevel and moves current index at parent's item index |\r\n  | 'select' | Does nothing, just emits the action for you to decide what to do |\r\n  | 'deselect' | Does nothing, just emits the action for you to decide what to do |\r\n\r\n> **_(*)_** _Available_ means that it depends on if the item is disabled or not and how [`allowNavigateDisabled`](#allow-navigate-disabled-items) is configured.\r\n>\r\n> **_(\\*\\*)_** _First available_ means that it depends on if [`allowSelectFirstChild`](#allow-select-first-child) is set to `true`.\r\n>\r\n> **IMPORTANT:** Remember that the predefined strategies have the map of keys and actions already set, meaning that:\r\n>\r\n> - For a `'menu'`:\r\n>   - The `ArrowRight` will _always_ `'open'`.\r\n>   - The `ArrowLeft` will _always_ `'close'`.\r\n> - For a `'dropdown'`:\r\n>   - The `ArrowRight` and `ArrowLeft` do not exist.\r\n>   - The `Home` will _always_ go to `'first'`.\r\n>   - The `End` will _always_ go to `'last'`.\r\n>\r\n> Now, if you want to see the world burn 🔥🔥🔥, you can set your own custom strategy and establish that:\r\n>\r\n> - The `Home` key executes `'open'`.\r\n> - The `End` key executes `'previous'`.\r\n> - The `ArrowRight` key executes `'pageUp'`.\r\n> - And so on...\r\n\r\n#### The Strategy: Orientation Keys\r\n\r\nThis comes in handy when you want to provide an oriented navigation set of keys (such as in components like a `'toolbar'` or `'tabs'`), which can be used either as horizontal or vertical.\r\n\r\nFor horizontal navigation (default), use:\r\n\r\n- **Property:** `keysHorizontal`.\r\n- **Type:** `Record<Key, Action>`.\r\n\r\nFor vertical navigation, use:\r\n\r\n- **Property:** `keysVertical`.\r\n- **Type:** `Record<Key, Action>`.\r\n\r\n> 📘 **NOTE:** If both navigations share common keys with common actions, use the `keys` property.\r\n>\r\n> **Example:** You decide to embrace the idea of building your own toolbar component.\r\n>\r\n> The `'toolbar'` strategy includes both orientations.\r\n>\r\n> This is the strategy definition keys:\r\n>\r\n> ```typescript\r\n> {\r\n>     loop: true,\r\n>     keys: {\r\n>         Home: 'first',         // Shared key/action\r\n>         End: 'last',           // Shared key/action\r\n>     },\r\n>     keysHorizontal: {          // For horizontal navigation\r\n>         ArrowLeft: 'previous', // Left = previous\r\n>         ArrowRight: 'next',    // Right = next\r\n>     },\r\n>     keysVertical: {            // For vertical navigation\r\n>         ArrowUp: 'previous',   // Up = previous\r\n>         ArrowDown: 'next',     // Down = next\r\n>     },\r\n> }\r\n> ```\r\n>\r\n> - Set the KeyNav config as `{ type: 'toolbar' }`.\r\n> - You will provide the user the option to choose between `horizontal` and `vertical` orientations.\r\n>   - Let's assume the user chose `vertical`, then you'll set the config once again with `{ orientation: 'vertical' }`.\r\n> - The user starts pressing keys:\r\n>   - When the engine gets `ArrowLeft`, it won't find any actions associated, since it's not among `keys` nor `keysVertical`, then emits `null`.\r\n>   - When the engine gets `ArrowDown`, it will find the action associated within `keysVertical`, then emits the event.\r\n\r\n### The Preset Strategies\r\n\r\nHere is the default key mapping for each preset. If these do not fit your needs, you can build your own [custom strategy](#the-custom-strategy).\r\n\r\n<details>\r\n<summary>Dropdown Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: false,\r\n    keys: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n        Home: 'first',\r\n        End: 'last',\r\n        PageUp: 'pageUp',\r\n        PageDown: 'pageDown',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Menubar Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: true,\r\n    keys: {\r\n        Enter: 'open',\r\n        Home: 'first',\r\n        End: 'last',\r\n    },\r\n    keysHorizontal: {\r\n        ArrowLeft: 'previous',\r\n        ArrowRight: 'next',\r\n        ArrowUp: 'open',\r\n        ArrowDown: 'open',\r\n    },\r\n    keysVertical: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n        ArrowRight: 'open',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Menu Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: true,\r\n    keys: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n        ArrowLeft: 'close',\r\n        ArrowRight: 'open',\r\n        Escape: 'close',\r\n        Enter: 'open',\r\n        Home: 'first',\r\n        End: 'last',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Tabs Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: true,\r\n    keys: {\r\n        Home: 'first',\r\n        End: 'last',\r\n        Space: 'open',\r\n        Enter: 'open',\r\n    },\r\n    keysHorizontal: {\r\n        ArrowLeft: 'previous',\r\n        ArrowRight: 'next',\r\n    },\r\n    keysVertical: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Toolbar Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: true,\r\n    keys: {\r\n        Home: 'first',\r\n        End: 'last',\r\n    },\r\n    keysHorizontal: {\r\n        ArrowLeft: 'previous',\r\n        ArrowRight: 'next',\r\n    },\r\n    keysVertical: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Radio Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: true,\r\n    keys: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n        ArrowLeft: 'previous',\r\n        ArrowRight: 'next',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Slider Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: false,\r\n    keys: {\r\n        ArrowLeft: 'previous',\r\n        ArrowDown: 'previous',\r\n        ArrowUp: 'next',\r\n        ArrowRight: 'next',\r\n        Home: 'first',\r\n        End: 'last',\r\n        PageUp: 'pageDown',\r\n        PageDown: 'pageUp',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n<details>\r\n<summary>Tree Strategy</summary>\r\n\r\n```typescript\r\n{\r\n    loop: false,\r\n    keys: {\r\n        ArrowUp: 'previous',\r\n        ArrowDown: 'next',\r\n        ArrowLeft: 'close',\r\n        ArrowRight: 'open',\r\n        Home: 'first',\r\n        End: 'last',\r\n    },\r\n}\r\n```\r\n\r\n</details>\r\n\r\n## The Directive\r\n\r\nApply the directive to any element to enable keyboard navigation. When the element (or any of its children) receives focus, the directive automatically listens for keyboard input and handles navigation accordingly.\r\n\r\n- **Selector:** `[a11yKeyNav]`.\r\n- **Exported As:** `a11yKeyNav`.\r\n\r\nSee also:\r\n\r\n- [The Directive Inputs](#the-directive-inputs).\r\n- [The Directive Outputs](#the-directive-outputs).\r\n- [The Directive Examples](#the-use-of-the-directive).\r\n\r\n### The Directive Inputs\r\n\r\n| Name | Type | Description |\r\n| :--- | :--- | :---------- |\r\n| `a11yKeyNav` | `unknown[]` | The array of items to navigate |\r\n| `a11yKeyNavType` | `KeyboardNavigationType` | See [the Navigation Type](#the-navigation-type) |\r\n| `a11yKeyNavConfig` | `Partial<KeyboardNavigationConfig>` | See [the Configuration](#the-keynav-config) |\r\n| `a11yKeyNavCurrent` | `number` or `KeyboardNavigationCurrent` | See [set the current](#the-service-getcurrent-and-setcurrent-methods) |\r\n\r\n### The Directive Outputs\r\n\r\n| Name | Type | Description |\r\n| :--- | :--- | :---------- |\r\n| `navigate` | `EventEmitter<KeyboardNavigationEvent>` | See [the navigation state](#the-navigation-state) |\r\n\r\n## The Service\r\n\r\nUse the service for programmatic control of the engine.\r\n\r\nThe service is more helpful when:\r\n\r\n- You need programmatic control over navigation logic.\r\n- You need your own `(keydown)` logic before the engine's.\r\n- You need to conditionally enable/disable specific configuration at runtime.\r\n- You need to block certain keys before the engine analyzes anything.\r\n- Etc.\r\n\r\n> 📘 **NOTE:** This **is not a singleton** service; each component or directive gets its own instance when injected.\r\n\r\n### The Service Public Methods\r\n\r\n| Name | Type | Return Type | Description |\r\n| :--- | :--- | :---------- | :---------- |\r\n| `manageKeyDown()` | `method` | `KeyboardNavigationEvent` or `null` | See [the `manageKeyDown()` Method](#the-service-managekeydown-method) |\r\n| `executeKey()` | `method` | `KeyboardNavigationEvent` or `null` | See [the `executeKey()` Method](#the-service-executekey-method) |\r\n| `resetLastNavigationState()` | `method` | `void` | See [the `resetLastNavigationState()` Method](#the-service-resetlastnavigationstate-method) |\r\n| `init()` | `method` | `void` | See [the `init()` Method](#the-service-init-method) |\r\n| `getItems()` / `setItems()` | `method` | `(T \\| unknown)[]` | See [the `getItems()` and `setItems()` Methods](#the-service-getitems-and-setitems-methods) |\r\n| `getConfig()` / `setConfig()` | `method` | `KeyboardNavigationConfig` | See [the `getConfig()` and `setConfig()` Methods](#the-service-getconfig-and-setconfig-methods) |\r\n| `getCurrent()` / `setCurrent()` | `method` | `KeyboardNavigationCurrent` | See [the `getCurrent()` and `setCurrent()` Methods](#the-service-getcurrent-and-setcurrent-methods) |\r\n\r\n#### The Service: `manageKeyDown()` Method\r\n\r\nProcesses the keyboard `keydown` event and calculates navigation target through [the `executeKey()` method](#the-service-executekey-method).\r\n\r\n- Returns `KeyboardNavigationEvent` if the key/action are valid, or `null` otherwise.\r\n- Handles throttling to prevent rapid repeated events.\r\n\r\nAccepts a single parameter `event` of type `KeyboardEvent`.\r\n\r\n> 📘 **NOTE:** To strongly type the `current` and `previous` items in the returned state, pass your item type as a generic:\r\n>\r\n> ```typescript\r\n> const state = this.keyNav.manageKeyDown<MyType>(event);\r\n> ```\r\n\r\n#### The Service: `executeKey()` Method\r\n\r\nTo manually execute the pressing of a key (`ArrowDown`, `Home`, etc).\r\n\r\n- Returns `KeyboardNavigationEvent` if the key/action are valid, or `null` otherwise.\r\n- Prevents duplicate events from being emitted.\r\n- Updates the navigation state with the latest index and path.\r\n\r\nAccepts a single parameter `key` of type [`KeyboardNavigationKey`](#the-strategy-keys).\r\n\r\n> 📘 **NOTE:** To strongly type the `current` and `previous` items in the returned state, pass your item type as a generic:\r\n>\r\n> ```typescript\r\n> const state = this.keyNav.executeKey<MyType>('ArrowDown');\r\n> ```\r\n>\r\n> 💡 **TIP: Synchronizing state with mouse and touch events**\r\n>\r\n> Because `executeKey()` accepts a `KeyboardNavigationKey` instead of a real `KeyboardEvent`, it is the perfect tool for handling different input methods too. If a user interacts with your menu component using a mouse (e.g., clicking to open a submenu), you can programmatically call the equivalent key action like `this.keyNav.executeKey('ArrowRight')`.\r\n>\r\n> This keeps the engine's internal state perfectly in sync, ensuring a seamless handoff if the user switches back to the keyboard. [Check the example: The Use of the Service](#the-use-of-the-service).\r\n\r\n#### The Service: `resetLastNavigationState()` Method\r\n\r\nTo reset the last navigation state.\r\n\r\n> 📘 **NOTE:** Current index and path are not modified. For that [use the setCurrent() method](#the-service-getcurrent-and-setcurrent-methods).\r\n\r\n#### The Service: `init()` Method\r\n\r\nTo initialize the strategy and current items (only if you have not specified a [navigation type](#the-navigation-type) in your config).\r\n\r\n> 💡 **IMPORTANT:** If you set a configuration including a `type`, this method will be invoked automatically, no need to do it manually.\r\n\r\n#### The Service: `getItems()` and `setItems()` Methods\r\n\r\nTo get/set the items to be navigated.\r\n\r\n- The **_getter_** returns `(T | unknown)[]`.\r\n  - You can set your item type: `this.keyNav.getItems<MyType>()`\r\n- The **_setter_** accepts a single parameter `items` of type `unknown[]`.\r\n\r\n#### The Service: `getConfig()` and `setConfig()` Methods\r\n\r\nTo get/set [the configuration](#the-keynav-config).\r\n\r\n- The **_getter_** returns `KeyboardNavigationConfig`.\r\n- The **_setter_** accepts a single parameter `config` of type `Partial<KeyboardNavigationConfig>`.\r\n\r\n#### The Service: `getCurrent()` and `setCurrent()` Methods\r\n\r\nTo get/set the current index and path to navigate.\r\n\r\n- The **_getter_** returns `KeyboardNavigationCurrent`.\r\n- The **_setter_** accepts a single parameter `current` of type `number` or `KeyboardNavigationCurrent`.\r\n\r\n  > `current` could be:\r\n  >\r\n  > - A simple `number`: the index you want to define as _current_, or\r\n  > - A partial object `{ index: number; path: number[]; }`: Where you can define either one or both values.\r\n  >\r\n  > **⚠️ IMPORTANT:** The _setter_ method will only validate that the given path and/or index are valid within the items to be navigated. It won't consider disabled states.\r\n  >\r\n  > ```typescript\r\n  > const items = [\r\n  >     {\r\n  >         val: 'a',\r\n  >         children: [\r\n  >             { val: 'a-0', disabled: true },\r\n  >         ],\r\n  >     },\r\n  >     { val: 'b' },\r\n  >     {\r\n  >         val: 'c',\r\n  >         children: [\r\n  >             { val: 'c-0' },\r\n  >             { val: 'c-1' },\r\n  >         ],\r\n  >     },\r\n  >     { val: 'd', children: [] },\r\n  > ]\r\n  > ```\r\n  >\r\n  > Let's say your current position is item `'a'` (index `0` in root path `[]`):\r\n  >\r\n  > 1. Now you set `{ path: [2], index: 1 }` (aka children from item `'c'`):\r\n  >    - ✔️ path exists 👉 `'c'` 👉 it contains children 👉 path `[2]`\r\n  >    - ✔️ index exists in path 👉 index `1`\r\n  >      - `items[2].children[1].val` 👉 `'c-1'`\r\n  > 2. Now you set `{ path: [0] }` (aka children from item `'a'`):\r\n  >    - ✔️ path exists 👉 `'a'` 👉 it contains children 👉 path `[0]`\r\n  >    - ❌ previous index `1` is **out of range** in items within this path 👉 index `-1`\r\n  >      - `items[0].children[-1].val` 👉 `undefined`\r\n  > 3. Now you set `{ path: [1], index: 0 }` (aka children from item `'b'`):\r\n  >    - ❌ path exists 👉 `'b'` 👉 it **doesn't** contain children 👉 path `[]`\r\n  >    - ✔️ index exists in root path 👉 index `0`\r\n  >      - `items[0].val` 👉 `'a'`\r\n  > 4. Now you set `{ path: [0], index: 3 }` (aka children from item `'a'`):\r\n  >    - ✔️ path exists 👉 path `[0]`\r\n  >    - ❌ index `3` is **out of range** in items within this path 👉 index `-1`\r\n  >      - `items[0].children[-1].val` 👉 `undefined`\r\n  > 5. Now you set `{ path: [3], index: 3 }` (aka children from item `'d'`):\r\n  >    - ❌ path exists 👉 `'d'` 👉 it **doesn't** contain children (empty array) 👉 path `[]`\r\n  >    - ✔️ index exists in root path 👉 index `3`\r\n  >      - `items[3].val` 👉 `'d'`\r\n  >\r\n  > 🔑 Even if disabled navigation is not allowed and you set `{ path: [0], index: 0 }`, your item will be `'a-0'`, the path and index are valid values for the engine.\r\n\r\n## Examples\r\n\r\n### The Use of the Directive\r\n\r\nA couple of examples for the directive:\r\n\r\n- [The Radio Buttons Component](#the-radio-buttons-component).\r\n- [The Volume Control](#the-volume-control).\r\n\r\n#### The Radio Buttons Component\r\n\r\nThis is a really quick example of a custom radio-button component:\r\n\r\n> **IMPORTANT:** Radio buttons are a bit more complex to implement, this is just a basic example.\r\n\r\n**The Component:**\r\n\r\n1. We define our `RadioButton` type.\r\n2. We define our array of items in `radioButtonItems`.\r\n3. We define our current initial state in `radioButtonCurrent` as `-1`.\r\n4. Once the user presses any arrow key, the KeyNav directive executes the `keydown` event, the engine processes the key/action and emits the event and `selectSize()` gets invoked:\r\n   1. We update the `radioButtonCurrent` value with the current index from `indexTo`.\r\n   2. We set focus manually to the radio item through the `radioButtons` children.\r\n5. When the user clicks on an item, `clickSize()` gets invoked:\r\n   1. If the item is disabled, we don't do anything.\r\n   2. We notify the directive of the new \"current\" index state through the `radiosKeyNav` instance.\r\n   3. We update the `radioButtonCurrent` value with the clicked index.\r\n\r\n```typescript\r\nimport { A11yKeyboardNavigationModule, KeyboardNavigationEvent } from '@a11y-ngx/keyboard-navigation';\r\n\r\ntype RadioButton = {\r\n    label: string;\r\n    value: string;\r\n    disabled?: boolean;\r\n};\r\n\r\n@Component({\r\n    ...\r\n    imports: [A11yKeyboardNavigationModule],\r\n})\r\nexport class MyRadioComponent {\r\n    radioButtonItems: RadioButton[] = [\r\n        { label: 'Extra Small', value: 'xs' },\r\n        { label: 'Small', value: 'sm' },\r\n        { label: 'Medium', value: 'md', disabled: true },\r\n        { label: 'Large', value: 'lg' },\r\n        { label: 'Extra Large', value: 'xl' },\r\n    ];\r\n    radioButtonCurrent: number = -1;\r\n\r\n    @ViewChild('radiosKeyNav') private radiosKeyNav!: KeyboardNavigationDirective;\r\n    @ViewChildren('radioButton') private radioButtons!: QueryList<ElementRef<HTMLElement>>;\r\n\r\n    selectSize(event: KeyboardNavigationEvent<RadioButton>): void {\r\n        this.radioButtonCurrent = event.indexTo;\r\n        this.radioButtons.toArray()[event.indexTo].nativeElement.focus();\r\n    }\r\n\r\n    clickSize(index: number): void {\r\n        if (this.radioButtonItems[index].disabled) return;\r\n\r\n        this.radiosKeyNav.current = index;\r\n        this.radioButtonCurrent = index;\r\n    }\r\n}\r\n```\r\n\r\n**The Template:**\r\n\r\n> 📘 **NOTE:** The `role`, `aria-label` and `aria-checked` attributes are for accessibility purposes.\r\n\r\n- The radio group:\r\n  1. Provide the array of items through the `[a11yKeyNav]` input.\r\n  2. Provide the navigation type (strategy) through the `a11yKeyNavType` input.\r\n  3. Provide a one-time tabindex value of `0` only if there is no radio selected.\r\n     - This serves the purpose of setting focus the first time so the user can start interacting with the keyboard.\r\n     - ⚠️ This is not the normal behavior for the first focus on radio buttons, it's just for the example to work.\r\n  4. We _save_ the directive instance in `#radiosKeyNav`.\r\n     - This is to update the current index state when user interacts with the mouse.\r\n  5. We process the emitted event through the `selectSize()` method.\r\n- The radio item:\r\n  1. Provide a `tabindex` of `0` for the current item, or `-1` otherwise.\r\n  2. We select the item on `(click)` and `(keydown.space)` through the `clickSize()` method.\r\n  3. We _save_ the item instance in `#radioButton` (to set focus manually).\r\n\r\n```html\r\n<div\r\n    role=\"group\"\r\n    aria-label=\"Select Size\"\r\n    [attr.tabindex]=\"radioButtonCurrent === -1 ? 0 : null\"\r\n    [a11yKeyNav]=\"radioButtonItems\"\r\n    a11yKeyNavType=\"radio\"\r\n    #radiosKeyNav=\"a11yKeyNav\"\r\n    (navigate)=\"selectSize($event)\">\r\n    <span\r\n        *ngFor=\"let radio of radioButtonItems; let idx = index\"\r\n        [tabindex]=\"idx === radioButtonCurrent ? 0 : -1\"\r\n        [attr.aria-checked]=\"idx === radioButtonCurrent\"\r\n        [attr.aria-disabled]=\"radio.disabled\"\r\n        (click)=\"clickSize(idx)\"\r\n        (keydown.space)=\"clickSize(idx)\"\r\n        #radioButton\r\n        role=\"radio\">\r\n        {{ radio.label }}\r\n    </span>\r\n</div>\r\n```\r\n\r\n**Result:**\r\n\r\n![\"\"](https://raw.githubusercontent.com/LDV2k3/a11y-libraries/refs/heads/master/projects/a11y-ngx/keyboard-navigation/src/lib/images/example-radio-buttons.gif)\r\n\r\nOnce the focus is set in the radio group.\r\n\r\n1. `ArrowRight` => **Extra Small** selected.\r\n2. `ArrowRight` => **Small** selected.\r\n3. `ArrowDown` => **Large** selected.\r\n    - **Medium** gets ignored since [`allowNavigateDisabled`](#allow-navigate-disabled-items) is set to `false` by default (and _it should_, for this type of component).\r\n4. `ArrowLeft` => **Small** selected.\r\n5. `ArrowUp` => **Extra Small** selected.\r\n6. `ArrowUp` => **Extra Large** selected (looping).\r\n7. Etc.\r\n\r\n#### The Volume Control\r\n\r\nAnother quick example on how easy we can create a slider for keyboard navigation (mouse is not covered in this code).\r\n\r\n**The Component:**\r\n\r\n```typescript\r\nimport { A11yKeyboardNavigationModule, KeyboardNavigationEvent } from '@a11y-ngx/keyboard-navigation';\r\n\r\n@Component({\r\n    ...\r\n    imports: [A11yKeyboardNavigationModule],\r\n})\r\nexport class MyRadioComponent {\r\n    // An array from 0 to 100 => [0, 1, 2, ..., 99, 100]\r\n    volumeSlider: number[] = Array.from({ length: 101 }, (_, i) => i);\r\n    // Initial value 70\r\n    volumeSliderInitial: number = 70;\r\n    volumeSliderCurrent: number = 70;\r\n\r\n    volumeChanged(event: KeyboardNavigationEvent<number>): void {\r\n        this.volumeSliderCurrent = event.item;\r\n    }\r\n}\r\n```\r\n\r\n**The Template:**\r\n\r\n```html\r\n<div\r\n    role=\"slider\"\r\n    aria-label=\"Volume\"\r\n    tabindex=\"0\"\r\n    [attr.aria-valuemin]=\"0\"\r\n    [attr.aria-valuemax]=\"100\"\r\n    [attr.aria-valuenow]=\"volumeSliderCurrent\"\r\n    [a11yKeyNav]=\"volumeSlider\"\r\n    [a11yKeyNavCurrent]=\"volumeSliderInitial\"\r\n    a11yKeyNavType=\"slider\"\r\n    (navigate)=\"volumeChanged($event)\">\r\n    <i class=\"fa-solid fa-volume-high\"></i>\r\n    <div class=\"slider\" [style.--current-volume]=\"volumeSliderCurrent + '%'\">\r\n        <span>{{ volumeSliderCurrent }}%</span>\r\n    </div>\r\n</div>\r\n```\r\n\r\n**Result:**\r\n\r\n![\"\"](https://raw.githubusercontent.com/LDV2k3/a11y-libraries/refs/heads/master/projects/a11y-ngx/keyboard-navigation/src/lib/images/example-volume.gif)\r\n\r\n> **REMEMBER:** Any other interaction you add (the use of mouse `click` or `wheel`), you'll have to manage your own calculations and update the current index in the KeyNav directive instance, so when the user decides to use the keyboard after the mouse, the values are the right ones.\r\n\r\n### The Use of the Service\r\n\r\nLet's say we want to build a menu system.\r\n\r\nSince the KeyNav accepts nested item navigation, you can bind the `keydown` event in a wrapper component and just show/hide the menus within as they open/close. Since focus will be contained by the \"wrapper\", the KeyNav will work without a problem.\r\n\r\n> **REMEMBER:**\r\n>\r\n> - This is just a quick example, menus are **far** more complex, but with this you'll have all the keyboard scenarios covered.\r\n> - Mouse is not covered in this code.\r\n> - Since different keys execute the same action, you have to _understand_ what the user is trying to do:\r\n>   - `ArrowRight` and `Enter` executes the `'open'` action:\r\n>     - `ArrowRight` expands a submenu **if** the item has subitems, does nothing otherwise.\r\n>     - `Enter` expands a submenu **if** the item has subitems, selects (emits) the item otherwise.\r\n>   - `ArrowLeft` and `Escape` executes the `'close'` action:\r\n>     - `ArrowLeft` collapses a submenu **if** we are at a _higher_ sublevel (not root), does nothing otherwise (root level).\r\n>     - `Escape` collapses a submenu **if** we are at a _higher_ sublevel (not root), closes the main menu and set focus on the trigger otherwise (we were at root level).\r\n\r\n**The Component:**\r\n\r\n```typescript\r\nimport { Component, ViewChild, ViewChildren, ElementRef, QueryList, Output, EventEmitter } from '@angular/core';\r\n\r\nimport { KeyboardNavigationEvent, KeyboardNavigationService } from '@a11y-ngx/keyboard-navigation';\r\n\r\ntype MenuItem = {\r\n    name: string;\r\n    subitems?: MenuItem[];\r\n    blocked?: boolean;\r\n    menuOpen?: boolean;\r\n};\r\n\r\n@Component({\r\n    selector: 'menu-button',\r\n    templateUrl: '...',\r\n    providers: [KeyboardNavigationService],\r\n    host: {\r\n        '(keydown)': 'onKeyDown($event)',\r\n    },\r\n})\r\nexport class MenuButtonComponent {\r\n    // The items for our menu\r\n    items: MenuItem[] = [\r\n        { name: 'Save', blocked: true },\r\n        {\r\n            name: 'File',\r\n            subitems: [\r\n                { name: 'New' },\r\n                { name: 'Open' },\r\n                { name: 'Share', subitems: [{ name: 'Email' }, { name: 'WhatsApp' }, { name: 'Facebook' }] },\r\n            ],\r\n        },\r\n        {\r\n            name: 'Edit',\r\n            subitems: [{ name: 'Cut' }, { name: 'Copy' }, { name: 'Paste' }],\r\n        },\r\n    ];\r\n\r\n    @Output() itemSelected: EventEmitter<MenuItem> = new EventEmitter<MenuItem>();\r\n\r\n    // Main menu visibility\r\n    showMainMenu: boolean = false;\r\n\r\n    // The current ID, based on the current state\r\n    private get currentId(): string {\r\n        const { pathTo, indexTo } = this.state ?? {};\r\n        return pathTo === undefined || indexTo === undefined ? '' : this.itemId(pathTo, indexTo);\r\n    }\r\n\r\n    // Current state\r\n    private state: KeyboardNavigationEvent | undefined;\r\n\r\n    // Trigger element, to set focus when menu closes\r\n    @ViewChild('trigger') private trigger!: ElementRef<HTMLButtonElement>;\r\n    // Menus, to set focus to the main menu when opens\r\n    @ViewChildren('menu') private menus!: QueryList<ElementRef<HTMLElement>>;\r\n\r\n    constructor(private keyNav: KeyboardNavigationService) {\r\n        // Set the config\r\n        this.keyNav.setConfig({\r\n            // our \"disabled\" property\r\n            disabledProperty: 'blocked',\r\n            // our \"children\" property\r\n            childrenProperty: 'subitems',\r\n            // we do want to navigate disabled items\r\n            allowNavigateDisabled: true,\r\n            // we do want to auto-select first child when a submenu opens\r\n            allowSelectFirstChild: true,\r\n        });\r\n        // We pass the items to the KeyNav Service\r\n        this.keyNav.setItems(this.items);\r\n        // We initialize the Strategy\r\n        this.keyNav.init();\r\n    }\r\n\r\n    // To get the item's ID (path + index) => 'menu-item-0-1'\r\n    itemId(path: number[], index: number): string {\r\n        return `menu-item-${this.menuPath(path, index).join('-')}`;\r\n    }\r\n\r\n    // To get the menu path for submenus\r\n    menuPath(path: number[], index: number): number[] {\r\n        return [...path, index];\r\n    }\r\n\r\n    // To know if the given ID is active\r\n    // (so we can visually follow the \"active\" item path as we open new submenus)\r\n    isActiveItem(id: string): boolean {\r\n        return !this.currentId ? false : this.currentId.startsWith(id);\r\n    }\r\n\r\n    // The main entry for the `keydown` event\r\n    protected onKeyDown(event: KeyboardEvent): void {\r\n        // If the main menu is not visible\r\n        if (!this.showMainMenu) {\r\n            // If the pressed key is Enter or Space\r\n            if (['Enter', 'Space'].includes(event.code)) {\r\n                // We set the menu as visible\r\n                this.showMainMenu = true;\r\n                // We set focus on the menu\r\n                setTimeout(() => this.menus.get(0)!.nativeElement.focus(), 10);\r\n            }\r\n\r\n            return;\r\n        }\r\n\r\n        // When the menu is open, we block the Tab key and close the menu\r\n        if (event.code === 'Tab') {\r\n            event.preventDefault();\r\n            this.closeMenu();\r\n            return;\r\n        }\r\n\r\n        // We pass the event to the KeyNav to get the state\r\n        const state: KeyboardNavigationEvent<MenuItem> | null = this.keyNav.manageKeyDown<MenuItem>(event);\r\n        this.manageAction(state);\r\n    }\r\n\r\n    // To execute some states manually\r\n    private manageAction(state: KeyboardNavigationEvent<MenuItem> | null): void {\r\n        // If no state (no allowed key/action), do nothing\r\n        if (!state) return;\r\n\r\n        // We save the state\r\n        this.state = state;\r\n\r\n        const { key, action, itemFrom, itemTo, pathFrom, pathTo } = state;\r\n\r\n        // To know if it's the same path (means that key/action have changed),\r\n        // but inside the same menu level\r\n        const isSamePath: boolean = pathFrom.join('-') === pathTo.join('-');\r\n\r\n        // If \"open\"\r\n        if (action === 'open') {\r\n            // If the key was \"Enter\" and we are in the same path\r\n            // (meaning that the item has no submenu)\r\n            if (key === 'Enter' && isSamePath) {\r\n                // If there is no \"itemFrom\" (meaning that nothing is yet selected)\r\n                // we manually \"execute\" the 'ArrowDown' key so the first element gets highlighted\r\n                if (!itemFrom) {\r\n                    this.manageAction(this.keyNav.executeKey('ArrowDown'));\r\n                    return;\r\n                }\r\n\r\n                // If item is disabled, do not emit\r\n                if (itemFrom.blocked) return;\r\n\r\n                // Emit the item\r\n                this.itemSelected.emit(itemFrom);\r\n                // We close the menu\r\n                this.closeMenu();\r\n                return;\r\n            }\r\n\r\n            // If not in the same path (meaning the item has submenu)\r\n            // we update our \"previous\" item\r\n            if (!isSamePath) itemFrom!.menuOpen = true;\r\n        }\r\n        // If \"close\"\r\n        else if (action === 'close') {\r\n            // If the key was \"Escape\" and we are in the same path\r\n            // (meaning that we are at root level, there's nothing else to close but the main menu)\r\n            if (key === 'Escape' && isSamePath) {\r\n                // We close the menu\r\n                this.closeMenu();\r\n                return;\r\n            }\r\n\r\n            // Otherwise the key was \"Escape\"/\"ArrowLeft\", we update our \"current\" item\r\n            itemTo!.menuOpen = false;\r\n        }\r\n\r\n        // We set focus on our \"current\" item\r\n        setTimeout(() => document.getElementById(this.currentId).focus(), 10);\r\n    }\r\n\r\n    // When closing the menu\r\n    private closeMenu(): void {\r\n        // We set the menu as not visible\r\n        this.showMainMenu = false;\r\n        // We clear our local state\r\n        this.state = undefined;\r\n        // We tell the KeyNav to reset the state\r\n        this.keyNav.resetLastNavigationState();\r\n        // We tell the KeyNav to reset the current index and path\r\n        this.keyNav.setCurrent({ index: -1, path: [] });\r\n        // We set focus on our trigger\r\n        this.trigger.nativeElement.focus();\r\n    }\r\n}\r\n```\r\n\r\n**The Template:**\r\n\r\n```html\r\n<!-- The trigger -->\r\n<button type=\"button\" class=\"btn trigger\" #trigger>\r\n    <i class=\"fa-solid fa-ellipsis-vertical\"></i>\r\n</button>\r\n\r\n<!-- The root menu -->\r\n<ng-container *ngIf=\"showMainMenu\">\r\n    <ng-container *ngTemplateOutlet=\"menuTemplate; context: { $implicit: items, path: [] }\"></ng-container>\r\n</ng-container>\r\n\r\n<!-- The menu template -->\r\n<ng-template #menuTemplate let-items let-path=\"path\">\r\n    <div role=\"menu\" tabindex=\"-1\" #menu>\r\n        <!-- The item -->\r\n        <div\r\n            *ngFor=\"let item of items; let index = index\"\r\n            role=\"menuitem\"\r\n            tabindex=\"-1\"\r\n            [id]=\"itemId(path, index)\"\r\n            [class.blocked]=\"item.blocked\"\r\n            [class.active]=\"isActiveItem(itemId(path, index))\">\r\n            <span>{{ item.name }}</span>\r\n            <span *ngIf=\"item.subitems\" aria-hidden=\"true\">></span>\r\n\r\n            <!-- The submenu, when item.menuOpen = true -->\r\n            <ng-container *ngIf=\"item.menuOpen\">\r\n                <ng-container\r\n                    *ngTemplateOutlet=\"\r\n                        menuTemplate;\r\n                        context: { $implicit: item.subitems, path: menuPath(path, index) }\r\n                    \"></ng-container>\r\n            </ng-container>\r\n        </div>\r\n    </div>\r\n</ng-template>\r\n```\r\n\r\n**Result:**\r\n\r\n![\"\"](https://raw.githubusercontent.com/LDV2k3/a11y-libraries/refs/heads/master/projects/a11y-ngx/keyboard-navigation/src/lib/images/example-menu.gif)\r\n\r\n> 📘 **NOTE:** When the menu opens the first time, focus is set to the entire menu.\r\n>\r\n> At this point, the user can press any of the allowed keys and the menu should respond (or not).\r\n>\r\n> These are the allowed keys for the first time it opens (since focus is set to the entire menu wrapper):\r\n>\r\n> - `Escape` => closes the menu\r\n> - `ArrowDown` / `Home` => go to first available item\r\n> - `ArrowUp` / `End` => go to last available item\r\n> - `ArrowRight` / `ArrowLeft` => does nothing\r\n> - `Enter` => we decide to highlight the first available item by \"executing\" the `ArrowDown` key (through [the `executeKey()` method](#the-service-executekey-method))\r\n>\r\n>   > Our first step when the menu opened was to press `Enter`.\r\n>   >\r\n>   > At this point, the KeyNav state doesn't have anything just yet (current index is set to `-1`, so, no _current item_ so far), and we pressed `Enter` (a valid key and action), you will get the state and you have to decide what to do with that action.\r\n>   >\r\n>   > So, since the action was \"open\", the key was \"Enter\" and there is no \"itemFrom\" (`undefined`), we force the menu to go to the first item by feeding 'ArrowDown' directly into `executeKey()`:\r\n>   >\r\n>   > ```typescript\r\n>   > if (action === 'open') {\r\n>   >     if (key === 'Enter' && isSamePath) {\r\n>   >         if (!itemFrom) {\r\n>   >             this.manageAction(this.keyNav.executeKey('ArrowDown'));\r\n>   >             return;\r\n>   >         }\r\n>   >         ...\r\n>   >     }\r\n>   >     ...\r\n>   > }\r\n>   > ...\r\n>   > ```\r\n","readmeFilename":"README.md"}