Skip to content

Commit

Permalink
Merge pull request #19 from dmitry-kurmanov/feature/17-focus-group-su…
Browse files Browse the repository at this point in the history
…pport-toolbar

PR: `focus-group` support `toolbar`
  • Loading branch information
ai authored Mar 27, 2024
2 parents 265ba44 + 57992fc commit 57f4a21
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 9 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ may contain images.
</ul>
```

Users will use <kbd>Tab</kbd> to get inside the menu, and will use either
Users will use <kbd>Tab</kbd> to get inside the listbox, and will use either
arrows or <kbd>Home</kbd>,
<kbd>End</kbd> or an item name to navigate inside.

Expand Down Expand Up @@ -288,7 +288,41 @@ The tab content should be marked by `[role="tabpanel']`.
</div>
```

Users will use <kbd>Tab</kbd> to get inside the menu, and will use either
Users will use <kbd>Tab</kbd> to get inside the tablist, and will use either
arrows or <kbd>Home</kbd>,
<kbd>End</kbd>.

To enable this feature, call `focusGroupKeyUX`.

```js
import { focusGroupKeyUX } from 'keyux'

startKeyUX(window, [
focusGroupKeyUX()
])
```


### Toolbar

The [`role="toolbar"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/toolbar_role)
defines the containing element as a collection of commonly used function buttons or controls represented in a compact visual forms.
Buttons inside the `toolbar` must have `type="button"` attribute because the default one is `submit`.

```html
<div role="toolbar">
<div>
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Cut</button>
</div>
<div>
<input type="checkbox" />
</div>
</div>
```

Users will use <kbd>Tab</kbd> to get inside the tablist, and will use either
arrows or <kbd>Home</kbd>,
<kbd>End</kbd>.

Expand Down
33 changes: 27 additions & 6 deletions focus-group.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const ROLES = {
button: ['toolbar'],
checkbox: ['toolbar'],
menuitem: ['menu', 'menubar'],
option: ['listbox'],
tab: ['tablist']
Expand All @@ -18,8 +20,10 @@ export function focusGroupKeyUX(options) {
}

function findGroupNodeByEventTarget(eventTarget) {
let itemRole = eventTarget.role
let groupRoles = ROLES[itemRole]
let itemRole = eventTarget.role || eventTarget.type || eventTarget.tagName
if (!itemRole) return null

let groupRoles = ROLES[itemRole.toLowerCase()]
if (!groupRoles) return null

for (let role of groupRoles) {
Expand All @@ -28,13 +32,30 @@ export function focusGroupKeyUX(options) {
}
}

function getItems(eventTarget, group) {
if (group.role === 'toolbar') return getToolbarItems(group)
return group.querySelectorAll(`[role=${eventTarget.role}]`)
}

function getToolbarItems(group) {
let items = [...group.querySelectorAll('*')]
return items.filter(item => {
return (
item.role === 'button' ||
item.type === 'button' ||
item.role === 'checkbox' ||
item.type === 'checkbox'
)
})
}

function isHorizontalOrientation(group) {
let ariaOrientation = group.getAttribute('aria-orientation')
if (ariaOrientation === 'vertical') return false
if (ariaOrientation === 'horizontal') return true

let role = group.role
return role === 'menubar' || role === 'tablist'
return role === 'menubar' || role === 'tablist' || role === 'toolbar'
}

function keyDown(event) {
Expand All @@ -45,7 +66,7 @@ export function focusGroupKeyUX(options) {
return
}

let items = group.querySelectorAll(`[role=${event.target.role}]`)
let items = getItems(event.target, group)
let index = Array.from(items).indexOf(event.target)

let nextKey = 'ArrowDown'
Expand Down Expand Up @@ -106,7 +127,7 @@ export function focusGroupKeyUX(options) {
inGroup = true
window.addEventListener('keydown', keyDown)
}
let items = group.querySelectorAll(`[role=${event.target.role}]`)
let items = getItems(event.target, group)
for (let item of items) {
if (item !== event.target) {
item.setAttribute('tabindex', -1)
Expand All @@ -126,7 +147,7 @@ export function focusGroupKeyUX(options) {
function click(event) {
let group = findGroupNodeByEventTarget(event.target)
if (group) {
let items = group.querySelectorAll(`[role=${event.target.role}]`)
let items = getItems(event.target, group)
for (let item of items) {
if (item !== event.target) {
item.setAttribute('tabindex', -1)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"import": {
"./index.js": "{ startKeyUX, hotkeyKeyUX, pressKeyUX, focusGroupKeyUX, jumpKeyUX, hiddenKeyUX, likelyWithKeyboard, getHotKeyHint }"
},
"limit": "1891 B"
"limit": "1960 B"
}
],
"clean-publish": {
Expand Down
24 changes: 24 additions & 0 deletions test/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@
border: 1px solid #aaa;
padding: 1em;
}
.toolbar {
margin-top: 1em;
display: flex;
gap: 16px;
align-items: center;
border: 1px solid #aaa;
padding: 0.5em 1em;
background: #eee;
}

.toolbar_group {
display: flex;
gap: 8px;
}

.toolbar_button {
background-color: white;
}

.toolbar_label {
display: flex;
gap: 4px;;
cursor: pointer;
}
</style>
</head>
<body>
Expand Down
21 changes: 21 additions & 0 deletions test/demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,26 @@ const Tabs: FC = () => {
)
}

const Toolbar: FC = () => {
return (
<>
<div className='toolbar' role="toolbar">
<div className="toolbar_group">
<button className="toolbar_button" type="button">Copy</button>
<button className="toolbar_button" type="button">Paste</button>
<button className="toolbar_button" type="button">Cut</button>
</div>
<div className="toolbar_group">
<label className="toolbar_label">
<input type="checkbox"/>
Night Mode
</label>
</div>
</div>
</>
)
}

const App: FC = () => {
let [, setUpdate] = useState({})
let [router, setRouter] = useState('home')
Expand All @@ -354,6 +374,7 @@ const App: FC = () => {
}}
/>
<Tabs />
<Toolbar />
</>
)
}
Expand Down
39 changes: 39 additions & 0 deletions test/focus-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,3 +423,42 @@ test('is ready to click after focus', () => {
'</nav>'
)
})

test('adds toolbar widget', () => {
let window = new JSDOM().window
startKeyUX(window, [focusGroupKeyUX()])
window.document.body.innerHTML =
'<div role="toolbar">' +
'<div>' +
'<button type="button">Copy</button>' +
'<button type="button">Paste</button>' +
'<button type="button">Cut</button>' +
'</div>' +
'<div>' +
'<input type="checkbox"/>' +
'</div>' +
'</div>'
let buttons = window.document.querySelectorAll('button')
let checkboxes = window.document.querySelectorAll('[type="checkbox"]')
buttons[0].focus()

equal(window.document.activeElement, buttons[0])

press(window, 'ArrowRight')
equal(window.document.activeElement, buttons[1])

press(window, 'ArrowLeft')
equal(window.document.activeElement, buttons[0])

press(window, 'End')
equal(window.document.activeElement, checkboxes[0])

press(window, 'Home')
equal(window.document.activeElement, buttons[0])

press(window, 'ArrowLeft')
equal(window.document.activeElement, checkboxes[0])

press(window, 'ArrowRight')
equal(window.document.activeElement, buttons[0])
})

0 comments on commit 57f4a21

Please sign in to comment.