Skip to content

Commit

Permalink
Merge pull request #20 from myandrienko/transformers
Browse files Browse the repository at this point in the history
add transformers api
  • Loading branch information
ai authored Apr 4, 2024
2 parents 69d079d + 7d5f224 commit 5713cff
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 36 deletions.
34 changes: 26 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,17 @@ Then add the `startKeyUX` call with the necessary features to the main JS file.
import {
hiddenKeyUX,
hotkeyKeyUX,
hotkeyOverrides,
jumpKeyUX,
focusGroupKeyUX,
pressKeyUX,
startKeyUX
} from 'keyux'

const overrides = {}
const overrides = hotkeyOverrides({})

startKeyUX(window, [
hotkeyKeyUX(overrides),
hotkeyKeyUX([overrides]),
focusGroupKeyUX(),
pressKeyUX('is-pressed'),
jumpKeyUX(),
Expand Down Expand Up @@ -127,6 +128,24 @@ external keyboard).
For instance, for `alt+b` it will return `Alt + B` on Windows/Linux or `⌥ B`
on Mac.

If you're using overrides, pass the same override config both to `hotkeyKeyUX()`
and `getHotKeyHint()` for accurate hints:

```js
import {
getHotKeyHint,
hotkeyOverrides,
hotkeyKeyUX,
startKeyUX
} from 'keyux'

let config = { 'alt+b': 'b' }

startKeyUX(window, [
hotkeyKeyUX([hotkeyOverrides(config)]) // Override B to Alt + B
])
getHotKeyHint(window, 'b', [hotkeyOverrides(config)]) // Alt + B
```

### Pressed State

Expand Down Expand Up @@ -161,8 +180,8 @@ to automatically add class for every `:active` state in your CSS.
Many users want to override hotkeys because your hotkeys can conflict with
their browser’s extensions, system, or screen reader.

KeyUX allows overriding hotkeys using the `overrides` object.
Both `hotkeyKeyUX()` and `getHotKeyHint()` accept it as an argument.
KeyUX allows to override hotkeys using tranforms. Use the `hotkeyOverrides()` tranformer
with `hotkeyKeyUX()` and `getHotKeyHint()`.

You will need to create some UI for users to fill this object like:

Expand All @@ -172,10 +191,9 @@ const overrides = {
}
```

Then KeyUX will click on `aria-keyshortcuts="b"` when
<kbd>Alt</kbd>+<kbd>B</kbd> is pressed, and
`getHotKeyHint(window, 'b', overrides)` will return `Alt + B`/`⌥ B`.

Then KeyUX will click on `aria-keyshortcuts="b"` when <kbd>Alt</kbd>+<kbd>B</kbd>
is pressed, and `getHotKeyHint(window, 'b', [hotkeyOverrides(overrides)])`
will return `Alt + B`/`⌥ B`.

### Hotkeys for List

Expand Down
27 changes: 15 additions & 12 deletions hotkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,38 @@ function findNonIgnored(activeElement, elements) {
}
}

function checkHotkey(where, code, overrides) {
let codeOverride = overrides[code]
if (Object.values(overrides).includes(code) && !codeOverride) return false
function checkHotkey(window, code, transformers) {
let realCode = code
for (let [transform] of transformers) {
realCode = transform(realCode, window)
if (!realCode) return false
}

let where = window.document
let activeElement = where.activeElement
let actualCode = codeOverride || code

let areaId = activeElement.getAttribute('data-keyux-hotkeys')
if (areaId) {
let area = where.querySelector(`#${areaId}`)
if (area) {
let element = area.querySelector(`[aria-keyshortcuts="${actualCode}" i]`)
let element = area.querySelector(`[aria-keyshortcuts="${realCode}" i]`)
if (element) return element
}
}

return (
findNonIgnored(
activeElement,
activeElement.querySelectorAll(`[aria-keyshortcuts="${actualCode}" i]`)
activeElement.querySelectorAll(`[aria-keyshortcuts="${realCode}" i]`)
) ||
findNonIgnored(
where,
where.querySelectorAll(`[aria-keyshortcuts="${actualCode}" i]`)
where.querySelectorAll(`[aria-keyshortcuts="${realCode}" i]`)
)
)
}

function findHotKey(event, where, overrides) {
function findHotKey(event, window, transformers) {
let prefix = ''
if (event.metaKey) prefix += 'meta+'
if (event.ctrlKey) prefix += 'ctrl+'
Expand All @@ -73,24 +76,24 @@ function findHotKey(event, where, overrides) {
code += event.key.toLowerCase()
}

let hotkey = checkHotkey(where, code, overrides)
let hotkey = checkHotkey(window, code, transformers)
if (
!hotkey &&
NON_ENGLISH_LAYOUT.test(event.key) &&
/^Key.$/.test(event.code)
) {
let enKey = event.code.replace(/^Key/, '').toLowerCase()
hotkey = checkHotkey(where, prefix + enKey, overrides)
hotkey = checkHotkey(window, prefix + enKey, transformers)
}

return hotkey
}

export function hotkeyKeyUX(overrides = {}) {
export function hotkeyKeyUX(transformers = []) {
return window => {
function keyDown(event) {
if (ignoreHotkeysIn(event.target)) return
let press = findHotKey(event, window.document, overrides)
let press = findHotKey(event, window, transformers)
if (press) press.click()
}

Expand Down
32 changes: 29 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ export interface FocusGroupKeyUXOptions {

export type HotkeyOverride = Record<string, string>

type SingleDirectionTransformer = (
code: string,
window: MinimalWindow
) => false | string

export type Transformer = [
tranformForward: SingleDirectionTransformer,
transformReverse: SingleDirectionTransformer
]

/**
* Press button/a according to `aria-keyshortcuts`.
*
Expand All @@ -49,7 +59,7 @@ export type HotkeyOverride = Record<string, string>
* ])
* ```
*/
export function hotkeyKeyUX(overrides?: HotkeyOverride): KeyUXModule
export function hotkeyKeyUX(transformers?: Transformer[]): KeyUXModule

/**
* Add arrow-navigation on `role="menu"`.
Expand Down Expand Up @@ -142,7 +152,7 @@ export function likelyWithKeyboard(window: MinimalWindow): boolean
/**
* Return text for `<kbd>` element with hint for hot key.
*
* It replaced `Ctrl` with `⌘` on Mac and respects overrides.
* It replaces `Meta` with `⌘` etc on Mac.
*
* ```js
* import { getHotKeyHint, likelyWithKeyboard } from 'keyux'
Expand All @@ -158,5 +168,21 @@ export function likelyWithKeyboard(window: MinimalWindow): boolean
export function getHotKeyHint(
window: MinimalWindow,
code: string,
overrides?: HotkeyOverride
transformers?: Transformer[]
): string

/**
* Provides a transformer for hotkey overrides that can be used
* with `hotkeyKeyUX()` and `getHotKeyHint()`.
*
* ```js
* import { getHotKeyHint, hotkeyKeyUX, hotkeyOverrides } from "keyux"
*
* let overrides = hotkeyOverrides({ 'alt+b': 'b' })
* startKeyUX(window, [
* hotkeyKeyUX([overrides])
* ])
* getHotKeyHint(window, 'b', [overrides])
* ```
*/
export function hotkeyOverrides(overrides: HotkeyOverride): Transformer
10 changes: 4 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './hotkey.js'
export * from './hidden.js'
export * from './press.js'
export * from './jump.js'
export * from './overrides.js'

export function startKeyUX(window, plugins) {
let unbinds = plugins.map(plugin => plugin(window))
Expand All @@ -16,13 +17,10 @@ export function likelyWithKeyboard(window = globalThis) {
return !['iphone', 'ipad', 'android'].some(device => agent.includes(device))
}

export function getHotKeyHint(window, code, overrides = {}) {
export function getHotKeyHint(window, code, transformers = []) {
let realCode = code
for (let i in overrides) {
if (overrides[i] === code) {
realCode = i
break
}
for (let transformer of transformers) {
realCode = transformer[1](realCode, window, 'r')
}
let prettyParts = realCode
.split('+')
Expand Down
18 changes: 18 additions & 0 deletions overrides.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function hotkeyOverrides(overrides) {
return [
code => {
let realCode = overrides[code]
if (Object.values(overrides).includes(code) && !realCode) return false
return realCode || code
},

code => {
for (let i in overrides) {
if (overrides[i] === code) {
return i
}
}
return code
}
]
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@
{
"name": "All modules",
"import": {
"./index.js": "{ startKeyUX, hotkeyKeyUX, pressKeyUX, focusGroupKeyUX, jumpKeyUX, hiddenKeyUX, likelyWithKeyboard, getHotKeyHint }"
"./index.js": "{ startKeyUX, hotkeyKeyUX, pressKeyUX, focusGroupKeyUX, jumpKeyUX, hiddenKeyUX, likelyWithKeyboard, getHotKeyHint, hotkeyOverrides }"
},
"limit": "1967 B"
"limit": "1996 B"
}
],
"clean-publish": {
Expand Down
6 changes: 4 additions & 2 deletions test/demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import {
getHotKeyHint,
hiddenKeyUX,
hotkeyKeyUX,
hotkeyOverrides,
jumpKeyUX,
likelyWithKeyboard,
pressKeyUX,
startKeyUX
} from '../../index.js'

let overrides: HotkeyOverride = {}
let overridesTransformer = hotkeyOverrides(overrides);

startKeyUX(window, [
hotkeyKeyUX(overrides),
hotkeyKeyUX([overridesTransformer]),
focusGroupKeyUX(),
pressKeyUX('is-pressed'),
jumpKeyUX(),
Expand All @@ -25,7 +27,7 @@ startKeyUX(window, [

const HotKeyHint: FC<{ hotkey: string }> = ({ hotkey }) => {
return likelyWithKeyboard(window) ? (
<kbd>{getHotKeyHint(window, hotkey, overrides)}</kbd>
<kbd>{getHotKeyHint(window, hotkey, [overridesTransformer])}</kbd>
) : null
}

Expand Down
4 changes: 2 additions & 2 deletions test/hotkey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { equal } from 'node:assert'
import { test } from 'node:test'

import type { HotkeyOverride } from '../index.js'
import { hotkeyKeyUX, startKeyUX } from '../index.js'
import { hotkeyKeyUX, hotkeyOverrides, startKeyUX } from '../index.js'

function press(
window: DOMWindow,
Expand Down Expand Up @@ -125,7 +125,7 @@ test('supports non-English keyboard layouts', () => {
test('allows to override hotkeys', () => {
let window = new JSDOM().window
let overrides: HotkeyOverride = {}
startKeyUX(window, [hotkeyKeyUX(overrides)])
startKeyUX(window, [hotkeyKeyUX([hotkeyOverrides(overrides)])])
window.document.body.innerHTML =
'<button aria-keyshortcuts="b"></button>' +
'<button aria-keyshortcuts="q"></button>'
Expand Down
4 changes: 3 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { test } from 'node:test'

import {
getHotKeyHint,
hotkeyOverrides,
likelyWithKeyboard,
type MinimalWindow
} from '../index.js'
Expand Down Expand Up @@ -34,7 +35,8 @@ test('makes hotkey hint prettier', () => {
getHotKeyHint(window, 'meta+ctrl+shift+alt+b'),
'Meta + Ctrl + Shift + Alt + B'
)
equal(getHotKeyHint(window, 'alt+b', { b: 'alt+b' }), 'B')
equal(getHotKeyHint(window, 'alt+b', [hotkeyOverrides({ b: 'alt+b' })]), 'B')
equal(getHotKeyHint(window, 'q', [hotkeyOverrides({ b: 'alt+b' })]), 'Q')
})

test('makes mac hotkey hint prettier', () => {
Expand Down

0 comments on commit 5713cff

Please sign in to comment.