Skip to content

Commit

Permalink
New docs
Browse files Browse the repository at this point in the history
  • Loading branch information
edoardocavazza committed Nov 10, 2023
1 parent 93702f5 commit 95d9721
Show file tree
Hide file tree
Showing 21 changed files with 1,203 additions and 294 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
dist
public
docs/.vitepress/dist
docs/.vitepress/cache
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true

- name: Build Documentation
run: yarn vite build --base=/loock/
run: yarn docs:build

- name: Upload artifact
uses: actions/upload-pages-artifact@v1
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ yarn-error.log
dist
types
public
!docs/public
docs/.vitepress/dist
docs/.vitepress/cache
68 changes: 1 addition & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</p>

<p align="center">
<strong>Loock</strong> • A focus trap helper for keyboard navigation on the web.
<strong>Loock</strong> • Refined keyboard navigation for websites and components.
</p>

<p align="center">
Expand Down Expand Up @@ -55,72 +55,6 @@ dialog.addEventListener('open', () => {
});
```

### Options

#### `elements`

An array of nodes or a function that returns an array of nodes to use as tabbable elements.

Default: `undefined`.

#### `include`

A list of selectors of tabbable elements to include in the context.

Default: `a[href], area[href], button, input, select, textarea, video[controls], audio[controls], embed, iframe, summary, [contenteditable], [tabindex]`.

#### `exclude`

A list of selectors of elements to exclude from the context.

Default: `[tabindex="-1"], [disabled], [hidden], [aria-hidden="true"], [aria-disabled="true"], [inert], details:not([open]) *:not(summary)`.

#### `inert`

Should add the `inert` attribute to the excluded elements when a focus context is active.
This may be useful for screen readers but it may cause a bit of overhead.

Default: `false`.

#### `restore`

Should restore the focus to the previously focused element when the context is exited.

Default: `true`.

#### `focusContainer`

Focus the context container when the context is entered.

Default: `false`.

#### `onEnter`

A callback that is called when the context is entering.
The return value is awaited before the context is entered.

#### `onExit`

A callback that is called when the context is exiting.
The return value is awaited before the context is exited.

#### `beforeExit`

A callback that is called before the context is exiting.
The return value is awaited. If the return value is `false`, the context is not exited.
It can be used to block the context exit when the `ESC` key is pressed.

### How "trap" works

Loock attaches a ShadowRoot to the context container. Inside the ShadowRoot, it appends 3 elements:

- a `<span>` used to detect when the focus ring needs to be moved to the last element of the context when pressing `Shift+TAB` on the first tabbable element
- a `<slot>` used to render the context content
- a `<span>` used to detect when the focus ring needs to be moved to the first element of the context when pressing `TAB` on the last tabbable element

The container contents is rendered the same way as before context initialization, but the keyboard navigation is now "wrapped" by the two `span` elements.
When one of the wrapping elements gets the focus, it redirect the focus ring to the correct sibling.

---

## Development
Expand Down
85 changes: 85 additions & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { defineConfig } from 'vitepress';

// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'Loock',
description: 'Refined keyboard navigation for websites and components.',
base: '/loock/',
outDir: '../public',

head: [['link', { rel: 'icon', href: 'https://www.chialab.it/favicon.png' }]],

themeConfig: {
logo: 'https://raw.githubusercontent.com/chialab/loock/main/logo.svg',

// https://vitepress.dev/reference/default-theme-config
nav: [
{
text: 'Home',
link: '/',
},
{
text: 'Guide',
link: '/guide/',
},
{
text: 'Chialab.io',
link: 'https://www.chialab.io',
},
],

sidebar: [
{
text: 'Guide',
items: [
{
text: 'Get started',
link: '/guide/',
},
{
text: 'focusManager',
link: '/guide/focus-manager',
},
{
text: 'focusTrapBehavior',
link: '/guide/focus-trap-behavior',
},
{
text: 'focusEnterBehavior',
link: '/guide/focus-enter-behavior',
},
{
text: 'focusFirstChildBehavior',
link: '/guide/focus-first-child-behavior',
},
{
text: 'keyboardNavigationBehavior',
link: '/guide/keyboard-navigation-behavior',
},
],
},
{
text: 'Demos',
items: [
{
text: 'focusTrapBehavior',
link: 'https://chialab.github.io/loock/demo/focusTrapBehavior.html',
},
],
},
],

socialLinks: [
{
icon: 'github',
link: 'https://github.com/chialab/loock',
},
],

footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2023 - DNA project - Chialab',
},
},
lastUpdated: true,
});
6 changes: 6 additions & 0 deletions docs/.vitepress/theme/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import DefaultTheme from 'vitepress/theme';
import './theme.css';

export default {
extends: DefaultTheme,
};
7 changes: 7 additions & 0 deletions docs/.vitepress/theme/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:root {
--vp-c-brand-1: #f07c00;
--vp-c-brand-2: #d56e00;
--vp-button-brand-bg: var(--vp-c-brand-1);
--vp-button-brand-hover-bg: #d56e00;
--vp-button-brand-active-bg: #ce5900;
}
61 changes: 61 additions & 0 deletions docs/guide/focus-enter-behavior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# focusEnterBehavior

A behavior to handle focus entering or exiting a context. It can be useful to change the state of the root container when a context is active.

## Example

```ts
import { focusEnterBehavior } from '@chialab/loock';

const root = document.querySelector('dialog');

class MyCustomElement extends HTMLElement {
_internals = this.attachInternals();

focusEnterBehavior = focusEnterBehavior(root, {
onEnter: () => {
this._internals.states.add('--focused');
},
onExit: () => {
this._internals.states.delete('--focused');
},
});

connectedCallback() {
this.focusEnterBehavior.connect();
}

disconnectedCallback() {
this.focusEnterBehavior.disconnect();
}
}
```

## How it works

This behavior acts differently from listening `focus` and `blur` events. In facts, those events fire only when the target is the node itself, not when the focus is moving from a child to another elements.

Loock attaches two listeners for the `focusin` and the `focusout` events:

- when `focusin` fires, Loock triggers then `onEnter` callback if the context was not already entered
- when `focusout` fires, Loock triggers then `onExit` callback if not followed by another `focusin` event.

## Options

### `onEnter`

A callback that is called when the context is entering.

### `onExit`

A callback that is called when the context is exiting.

## Methods

### `connect()`

Connect the behavior to the context.

### `disconnect()`

Disconnect the behavior from the context.
34 changes: 34 additions & 0 deletions docs/guide/focus-first-child-behavior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# focusFirstChildBehavior

This behavior focuses the first child element when entering a context. If the user navigates using `TAB` or the `.focus()` method of the root is invoked programmatically, the focus is automatically moved to the first child element.

## Example

```ts
import { focusFirstChildBehavior } from '@chialab/loock';

const dialog = document.querySelector('dialog');
const trap = focusFirstChildBehavior(dialog);

dialog.addEventListener('open', () => {
trap.connect();
});

dialog.addEventListener('close', () => {
trap.disconnect();
});
```

## Options

The `focusFirstChildBehavior` inherits all the options from [`focusManager`](./focus-manager) in order to detect the first child element to focus.

## Methods

### `connect()`

Connect the behavior to the context.

### `disconnect()`

Disconnect the behavior from the context.
89 changes: 89 additions & 0 deletions docs/guide/focus-manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# focusManager

The focus manager create a context to manage focusable elements inside a root. You can use the focus manager to retrieve all focusable elements inside the node or to make the first/last child to grab the focus. Other helper method could be added in the future.

## Example

```ts
import { focusManager } from '@chialab/loock';

const root = document.querySelector('dialog');
const manager = focusManager(root);

manager.focusFirst();
```

## Options

Options can be passed as second argument of the `focusManager` function in order to configure the child elements that should be considered as focusable.

### `include`

A list of selectors of tabbable elements to include in the context.

```ts
const manager = focusManager(root, {
include: 'input, button',
});
```

Default list:

- `a[href]`
- `area[href]`
- `button`
- `input`
- `select`
- `textarea`
- `video[controls]`
- `audio[controls]`
- `embed`
- `iframe`
- `summary`
- `[contenteditable]`
- `[tabindex]`

### `exclude`

A list of selectors of elements to exclude from the context.

```ts
const manager = focusManager(root, {
include: 'input, button',
exclude: '[disabled]',
});
```

Default list:

- `[tabindex="-1"]`
- `[disabled]`
- `[hidden]`
- `[aria-hidden="true"]`
- `[aria-disabled="true"]`
- `[inert]`
- `details:not([open]) *:not(summary)`

### `elements`

An array of nodes or a function that returns an array of nodes to use as tabbable elements. It takes precedence over `include` and `exclude` options.

```ts
const manager = focusManager(root, {
elements: () => Array.from(root.querySelectorAll('input, button')),
});
```

## Methods

### `focusFirst()`

Focus the first child element.

### `focusLast()`

Focus the last child element.

### `findFocusable()`

Find all focusable elements inside the root.
Loading

0 comments on commit 95d9721

Please sign in to comment.