arrowtab 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# ArrowTab
|
|
2
2
|
|
|
3
|
-
**ArrowTab** allows you to navigate a webpage using the arrow keys
|
|
3
|
+
**ArrowTab** allows you to **navigate** a webpage **using the arrow keys**, automatically selecting the nearest focusable element. This enables fast and efficient keyboard-only navigation across the page.
|
|
4
4
|
|
|
5
5
|
ArrowTab is particularly **useful for business applications**, where power users need to prioritize **high productivity**.
|
|
6
6
|
|
|
7
7
|
Instead of hitting `Tab` repeatedly, you can simply use the arrow keys to indicate the direction and jump to the next input, button, or link you want to select.
|
|
8
8
|
|
|
9
|
-
ArrowTab's default settings work well, but they are not always a plug-and-play solution. It helps if you build your app with ArrowTab in mind
|
|
9
|
+
ArrowTab's default settings work well, but they are not always a plug-and-play solution. It helps if you **build your app with ArrowTab in mind**. For example, aligning your focusable elements into a grid and considering the placement of your navigation. The idea is to integrate ArrowTab into your app from the beginning, not to add it as an afterthought.
|
|
10
10
|
|
|
11
11
|
## Input Behavior Changes
|
|
12
12
|
|
|
@@ -80,7 +80,10 @@ cleanup()
|
|
|
80
80
|
You can pass options to the `initArrowTab` function. For example:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
|
-
initArrowTab({
|
|
83
|
+
initArrowTab({
|
|
84
|
+
debug: true,
|
|
85
|
+
autoDetectHistory: true,
|
|
86
|
+
})
|
|
84
87
|
```
|
|
85
88
|
|
|
86
89
|
### debug
|
|
@@ -103,6 +106,12 @@ If you click on a focusable element, debug information will be logged to the con
|
|
|
103
106
|
|
|
104
107
|
To deactivate the visual debug mode, press `Esc`.
|
|
105
108
|
|
|
109
|
+
### autoDetectHistory
|
|
110
|
+
|
|
111
|
+
default: `false`
|
|
112
|
+
|
|
113
|
+
If `true`, ArrowTab will automatically detect dialogs and refocuses the last focused element when the dialog closes.
|
|
114
|
+
|
|
106
115
|
## data-arrowtab
|
|
107
116
|
|
|
108
117
|
You can control the behavior of ArrowTab by adding a `data-arrowtab` attribute to any element. Separate the keywords with a space. For example: `<div data-arrowtab="disable-left disable-right">`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const setHistoryCandidate: ({ element }: {
|
|
2
|
+
element: Element;
|
|
3
|
+
}) => void;
|
|
4
|
+
export declare const pushCandidateIntoHistory: () => void;
|
|
5
|
+
export declare const focusPrevious: () => void;
|
|
6
|
+
export declare const startAutoDetectHistory: ({ active }: {
|
|
7
|
+
active: boolean;
|
|
8
|
+
}) => {
|
|
9
|
+
cleanup: () => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const focusHistory = [];
|
|
2
|
+
let historyCandidate;
|
|
3
|
+
export const setHistoryCandidate = ({ element }) => {
|
|
4
|
+
if (!(element instanceof HTMLElement)) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
historyCandidate = element;
|
|
8
|
+
};
|
|
9
|
+
export const pushCandidateIntoHistory = () => {
|
|
10
|
+
if (historyCandidate) {
|
|
11
|
+
focusHistory.push(historyCandidate);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const focusPrevious = () => {
|
|
15
|
+
const lastHistoryElement = focusHistory.pop();
|
|
16
|
+
if (lastHistoryElement) {
|
|
17
|
+
lastHistoryElement.focus();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const startAutoDetectHistory = ({ active }) => {
|
|
21
|
+
if (!active) {
|
|
22
|
+
return {
|
|
23
|
+
cleanup: () => { },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
let dialogObserver = null;
|
|
27
|
+
// TODO: Support multiple dialogs
|
|
28
|
+
let lastDialogElement = null;
|
|
29
|
+
dialogObserver = new MutationObserver((mutationList) => {
|
|
30
|
+
for (const mutation of mutationList) {
|
|
31
|
+
if (mutation.type === 'childList') {
|
|
32
|
+
for (const node of mutation.addedNodes) {
|
|
33
|
+
if (node.nodeType === 1 &&
|
|
34
|
+
node instanceof HTMLElement &&
|
|
35
|
+
node.getAttribute('role') === 'dialog') {
|
|
36
|
+
lastDialogElement = node;
|
|
37
|
+
pushCandidateIntoHistory();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const node of mutation.removedNodes) {
|
|
41
|
+
if (node === lastDialogElement || node.contains(lastDialogElement)) {
|
|
42
|
+
lastDialogElement = null;
|
|
43
|
+
focusPrevious();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
dialogObserver.observe(document.body, {
|
|
51
|
+
childList: true,
|
|
52
|
+
subtree: true,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
cleanup: () => {
|
|
56
|
+
dialogObserver?.disconnect();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=focusHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focusHistory.js","sourceRoot":"","sources":["../../src/lib/focusHistory.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAAkB,EAAE,CAAA;AACtC,IAAI,gBAAyC,CAAA;AAE7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAAE,OAAO,EAAwB,EAAE,EAAE;IACvE,IAAI,CAAC,CAAC,OAAO,YAAY,WAAW,CAAC,EAAE,CAAC;QACtC,OAAM;IACR,CAAC;IACD,gBAAgB,GAAG,OAAO,CAAA;AAC5B,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,EAAE;IAC3C,IAAI,gBAAgB,EAAE,CAAC;QACrB,YAAY,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IACrC,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE;IAChC,MAAM,kBAAkB,GAAG,YAAY,CAAC,GAAG,EAAE,CAAA;IAC7C,IAAI,kBAAkB,EAAE,CAAC;QACvB,kBAAkB,CAAC,KAAK,EAAE,CAAA;IAC5B,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,EAAE,MAAM,EAAuB,EAAE,EAAE;IACxE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;SAClB,CAAA;IACH,CAAC;IACD,IAAI,cAAc,GAA4B,IAAI,CAAA;IAClD,iCAAiC;IACjC,IAAI,iBAAiB,GAAuB,IAAI,CAAA;IAChD,cAAc,GAAG,IAAI,gBAAgB,CAAC,CAAC,YAAY,EAAE,EAAE;QACrD,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;YACpC,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAClC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;oBACvC,IACE,IAAI,CAAC,QAAQ,KAAK,CAAC;wBACnB,IAAI,YAAY,WAAW;wBAC3B,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,EACtC,CAAC;wBACD,iBAAiB,GAAG,IAAI,CAAA;wBACxB,wBAAwB,EAAE,CAAA;oBAC5B,CAAC;gBACH,CAAC;gBAED,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;oBACzC,IAAI,IAAI,KAAK,iBAAiB,IAAI,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;wBACnE,iBAAiB,GAAG,IAAI,CAAA;wBACxB,aAAa,EAAE,CAAA;wBACf,OAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;QACpC,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,IAAI;KACd,CAAC,CAAA;IAEF,OAAO;QACL,OAAO,EAAE,GAAG,EAAE;YACZ,cAAc,EAAE,UAAU,EAAE,CAAA;QAC9B,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
package/dist/lib/initArrowTab.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { activateDebugMode, deactivateDebugMode, isInDebugMode, } from './debugMode.js';
|
|
2
|
+
import { setHistoryCandidate, startAutoDetectHistory } from './focusHistory.js';
|
|
2
3
|
import { getFocusable } from './getFocusable.js';
|
|
3
4
|
import { hasDisabledKeys } from './hasDisabledKeys.js';
|
|
4
5
|
import { hasTextSelection } from './hasTextSelection.js';
|
|
@@ -16,7 +17,7 @@ import { getByGrid } from './strategies.js';
|
|
|
16
17
|
// TODO: Check if tab focuses the same elements as arrowTab
|
|
17
18
|
// TODO: select, details, summary, iframe
|
|
18
19
|
// TODO: Investigate links that don't show focus outline sometimes
|
|
19
|
-
export const initArrowTab = ({ debug = false } = {}) => {
|
|
20
|
+
export const initArrowTab = ({ debug = false, autoDetectHistory = false, } = {}) => {
|
|
20
21
|
const onKeyDown = (event) => {
|
|
21
22
|
if (event.key === 'Escape' && isInDebugMode()) {
|
|
22
23
|
deactivateDebugMode();
|
|
@@ -90,12 +91,17 @@ export const initArrowTab = ({ debug = false } = {}) => {
|
|
|
90
91
|
console.log('focusing element', nearest);
|
|
91
92
|
}
|
|
92
93
|
nearest.element.focus();
|
|
94
|
+
setHistoryCandidate({ element: nearest.element });
|
|
93
95
|
}
|
|
94
96
|
};
|
|
95
97
|
document.addEventListener('keydown', onKeyDown, { capture: true });
|
|
98
|
+
const { cleanup: cleanupAutoDetectHistory } = startAutoDetectHistory({
|
|
99
|
+
active: autoDetectHistory,
|
|
100
|
+
});
|
|
96
101
|
return {
|
|
97
102
|
cleanup: () => {
|
|
98
103
|
document.removeEventListener('keydown', onKeyDown, { capture: true });
|
|
104
|
+
cleanupAutoDetectHistory();
|
|
99
105
|
},
|
|
100
106
|
};
|
|
101
107
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initArrowTab.js","sourceRoot":"","sources":["../../src/lib/initArrowTab.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAA;AAChF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,gBAAgB;AAChB,0BAA0B;AAC1B,mDAAmD;AACnD,qCAAqC;AACrC,uBAAuB;AACvB,gCAAgC;AAChC,6DAA6D;AAC7D,yCAAyC;AACzC,wDAAwD;AAExD,2DAA2D;AAC3D,yCAAyC;AACzC,kEAAkE;AAElE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"initArrowTab.js","sourceRoot":"","sources":["../../src/lib/initArrowTab.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arrowtab",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Use arrow keys to \"tab\" between focusable elements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arrowkeys",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"format": "prettier --ignore-path ./node_modules/prettier-ignore/prettierignore --write .",
|
|
44
44
|
"check-format": "prettier --ignore-path ./node_modules/prettier-ignore/prettierignore --check .",
|
|
45
45
|
"check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
|
|
46
|
-
"local-release": "changeset version && changeset publish"
|
|
46
|
+
"local-release": "changeset version && changeset publish",
|
|
47
|
+
"new-changeset": "changeset"
|
|
47
48
|
}
|
|
48
49
|
}
|