inclusive-elements 0.5.5 → 0.6.1
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/CHANGELOG.md +367 -0
- package/README.md +24 -20
- package/dist/index.js +1003 -0
- package/dist/types/alerts/alerts.d.ts +14 -1
- package/dist/types/index.d.ts +1 -4
- package/dist/types/menu/menu.d.ts +33 -3
- package/dist/types/tabs/tabs.d.ts +10 -3
- package/dist/types/toolbar/toolbar.d.ts +5 -2
- package/dist/types/tooltip/tooltip.d.ts +26 -18
- package/dist/types/utils.d.ts +15 -4
- package/package.json +21 -13
- package/src/accordion/README.md +36 -0
- package/src/alerts/README.md +133 -0
- package/src/dialog/README.md +57 -0
- package/src/disclosure/README.md +28 -0
- package/src/menu/README.md +103 -0
- package/src/popover/README.md +94 -0
- package/src/tabs/README.md +90 -0
- package/src/toolbar/README.md +68 -0
- package/src/tooltip/README.md +97 -0
- package/dist/inclusive-elements.js +0 -20
- package/dist/inclusive-elements10.js +0 -96
- package/dist/inclusive-elements11.js +0 -6
- package/dist/inclusive-elements2.js +0 -28
- package/dist/inclusive-elements3.js +0 -71
- package/dist/inclusive-elements4.js +0 -49
- package/dist/inclusive-elements5.js +0 -67
- package/dist/inclusive-elements6.js +0 -83
- package/dist/inclusive-elements7.js +0 -127
- package/dist/inclusive-elements8.js +0 -77
- package/dist/inclusive-elements9.js +0 -38
- package/dist/types/accordion/accordion.d.ts +0 -6
- package/dist/types/disclosure/disclosure.d.ts +0 -14
- package/dist/types/modal/modal.d.ts +0 -17
- package/dist/types/popup/popup.d.ts +0 -21
|
@@ -1,17 +1,30 @@
|
|
|
1
|
+
export type AlertPoliteness = 'polite' | 'assertive';
|
|
1
2
|
export type AlertOptions = {
|
|
2
3
|
key?: string;
|
|
3
4
|
duration?: number;
|
|
5
|
+
politeness?: AlertPoliteness;
|
|
4
6
|
};
|
|
5
7
|
export default class AlertsElement extends HTMLElement {
|
|
6
8
|
static duration: number;
|
|
9
|
+
static politeness: AlertPoliteness;
|
|
7
10
|
private timeouts;
|
|
8
11
|
private index;
|
|
9
12
|
connectedCallback(): void;
|
|
13
|
+
disconnectedCallback(): void;
|
|
10
14
|
show(el: HTMLElement, options?: AlertOptions): string;
|
|
11
15
|
dismiss(el: HTMLElement): void;
|
|
12
16
|
dismiss(key: string): void;
|
|
13
17
|
clear(): void;
|
|
14
|
-
speak(message: string): void;
|
|
18
|
+
speak(message: string, options?: AlertOptions): void;
|
|
19
|
+
private dismissAlert;
|
|
20
|
+
private leaveAlert;
|
|
15
21
|
private startTimeout;
|
|
16
22
|
private clearTimeout;
|
|
23
|
+
private pauseOnInteraction;
|
|
24
|
+
private resumeOnInteraction;
|
|
25
|
+
private handleInteraction;
|
|
26
|
+
private getAlerts;
|
|
27
|
+
private syncAlertPoliteness;
|
|
28
|
+
private getAuthoredPoliteness;
|
|
29
|
+
private getAlertFromTarget;
|
|
17
30
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
export { default as AccordionElement } from './accordion/accordion';
|
|
2
1
|
export { default as AlertsElement } from './alerts/alerts';
|
|
3
|
-
export {
|
|
2
|
+
export type { AlertOptions, AlertPoliteness } from './alerts/alerts';
|
|
4
3
|
export { default as MenuElement } from './menu/menu';
|
|
5
|
-
export { default as ModalElement } from './modal/modal';
|
|
6
|
-
export { default as PopupElement } from './popup/popup';
|
|
7
4
|
export { default as TabsElement } from './tabs/tabs';
|
|
8
5
|
export { default as ToolbarElement } from './toolbar/toolbar';
|
|
9
6
|
export { default as TooltipElement } from './tooltip/tooltip';
|
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
export default class MenuElement extends HTMLElement {
|
|
2
2
|
static searchDelay: number;
|
|
3
|
+
static observedAttributes: string[];
|
|
4
|
+
private itemSync?;
|
|
3
5
|
private search;
|
|
4
6
|
private searchTimeout?;
|
|
5
7
|
connectedCallback(): void;
|
|
6
8
|
disconnectedCallback(): void;
|
|
7
|
-
|
|
9
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
8
10
|
private get horizontal();
|
|
11
|
+
private syncDocumentListeners;
|
|
12
|
+
private removeDocumentListeners;
|
|
13
|
+
private onDocumentClick;
|
|
14
|
+
private onDocumentKeyDown;
|
|
9
15
|
private onKeyDown;
|
|
10
|
-
private
|
|
11
|
-
private
|
|
16
|
+
private onClick;
|
|
17
|
+
private onMouseEnter;
|
|
18
|
+
private isActive;
|
|
19
|
+
private syncItems;
|
|
20
|
+
private getItems;
|
|
21
|
+
private getEnabledItems;
|
|
22
|
+
private isItemEnabled;
|
|
23
|
+
private belongsToThisMenu;
|
|
24
|
+
private getActiveItem;
|
|
25
|
+
private getItemForEvent;
|
|
26
|
+
private getRootTriggerForEvent;
|
|
27
|
+
private getTriggers;
|
|
28
|
+
private getParentMenuTrigger;
|
|
29
|
+
private getParentMenu;
|
|
30
|
+
private getSubmenu;
|
|
31
|
+
private openSubmenu;
|
|
32
|
+
private closeSubmenus;
|
|
33
|
+
private closeSelfAndDescendants;
|
|
34
|
+
private closeMenuTree;
|
|
35
|
+
private focusRootMenuTrigger;
|
|
36
|
+
private getRootMenuTrigger;
|
|
37
|
+
private handleTypeahead;
|
|
38
|
+
private updateTypeaheadSearch;
|
|
39
|
+
private findTypeaheadMatch;
|
|
40
|
+
private getItemLabel;
|
|
41
|
+
private showPopoverWithSource;
|
|
12
42
|
}
|
|
@@ -4,14 +4,21 @@ type SelectTabOptions = {
|
|
|
4
4
|
};
|
|
5
5
|
export default class TabsElement extends HTMLElement {
|
|
6
6
|
private idPrefix;
|
|
7
|
+
private structureSync?;
|
|
8
|
+
private managedPanelTabindex;
|
|
7
9
|
connectedCallback(): void;
|
|
8
10
|
disconnectedCallback(): void;
|
|
9
11
|
selectTab(index: number, options?: SelectTabOptions): boolean;
|
|
10
12
|
private onKeyDown;
|
|
11
13
|
private onClick;
|
|
12
|
-
private
|
|
14
|
+
private syncStructure;
|
|
15
|
+
private get vertical();
|
|
13
16
|
private get tablist();
|
|
14
|
-
private
|
|
15
|
-
private
|
|
17
|
+
private tabPairs;
|
|
18
|
+
private getSelectedIndex;
|
|
19
|
+
private getEventTabIndex;
|
|
20
|
+
private applySelection;
|
|
21
|
+
private syncPanelTabindex;
|
|
22
|
+
private hasAuthorManagedTabindex;
|
|
16
23
|
}
|
|
17
24
|
export {};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export default class ToolbarElement extends HTMLElement {
|
|
2
|
+
private structureSync?;
|
|
2
3
|
connectedCallback(): void;
|
|
3
4
|
disconnectedCallback(): void;
|
|
4
|
-
private
|
|
5
|
+
private onFocus;
|
|
5
6
|
private onKeyDown;
|
|
6
|
-
private
|
|
7
|
+
private syncControls;
|
|
8
|
+
private syncCurrentControl;
|
|
9
|
+
private get vertical();
|
|
7
10
|
private get controls();
|
|
8
11
|
}
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
export default class TooltipElement extends HTMLElement {
|
|
2
2
|
static delay: number;
|
|
3
|
-
static
|
|
4
|
-
|
|
5
|
-
private parent?;
|
|
6
|
-
private tooltip?;
|
|
3
|
+
static observedAttributes: string[];
|
|
4
|
+
private owner?;
|
|
7
5
|
private timeout?;
|
|
8
|
-
private
|
|
9
|
-
private
|
|
10
|
-
private
|
|
11
|
-
private
|
|
12
|
-
private focusKeyPressed;
|
|
13
|
-
private onMouseEnter;
|
|
14
|
-
private onFocus;
|
|
15
|
-
private onMouseLeave;
|
|
16
|
-
private onBlur;
|
|
6
|
+
private sync;
|
|
7
|
+
private ownerObserver?;
|
|
8
|
+
private siblingObserver?;
|
|
9
|
+
private managedDescription?;
|
|
17
10
|
connectedCallback(): void;
|
|
18
11
|
disconnectedCallback(): void;
|
|
12
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
19
13
|
get disabled(): boolean;
|
|
20
14
|
set disabled(val: boolean);
|
|
21
|
-
|
|
22
|
-
private onKeyUp;
|
|
23
|
-
private isFocusTriggerKey;
|
|
24
|
-
show(): void;
|
|
15
|
+
show(owner?: HTMLElement | undefined): void;
|
|
25
16
|
hide(): void;
|
|
17
|
+
private onToggle;
|
|
18
|
+
private onDocumentKeyDown;
|
|
19
|
+
private syncOwner;
|
|
20
|
+
private resolveOwner;
|
|
21
|
+
private resolveExplicitOwner;
|
|
22
|
+
private setOwner;
|
|
23
|
+
private onOwnerFocusIn;
|
|
24
|
+
private onOwnerFocusOut;
|
|
25
|
+
private onOwnerPointerEnter;
|
|
26
|
+
private onTooltipPointerEnter;
|
|
27
|
+
private onInteractivePointerLeave;
|
|
28
|
+
private onOwnerClick;
|
|
26
29
|
private afterDelay;
|
|
27
|
-
private
|
|
30
|
+
private containsInteractiveTarget;
|
|
31
|
+
private updateManagedDescription;
|
|
32
|
+
private getDescriptionTokens;
|
|
33
|
+
private ensureTooltipId;
|
|
34
|
+
private supportsHoverInteraction;
|
|
35
|
+
private isOwnerDisabled;
|
|
28
36
|
}
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export declare function createQueuedSync(element: HTMLElement, sync: () => void): {
|
|
2
|
+
queue: () => void;
|
|
3
|
+
disconnect(): void;
|
|
4
|
+
};
|
|
5
|
+
export declare function createQueuedElementSync(element: HTMLElement, options: MutationObserverInit, sync: () => void): {
|
|
6
|
+
observe(): void;
|
|
7
|
+
disconnect(): void;
|
|
8
|
+
queue: () => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function getLookupRoot(element: HTMLElement): Document | ShadowRoot;
|
|
11
|
+
export declare function getRovingIndexForKey(key: string, options: {
|
|
12
|
+
currentIndex: number;
|
|
13
|
+
length: number;
|
|
14
|
+
vertical: boolean;
|
|
15
|
+
}): number | undefined;
|
package/package.json
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inclusive-elements",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Accessible, lightweight, unstyled implementations of
|
|
5
|
-
"module": "dist/inclusive-elements.js",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Accessible, lightweight, unstyled implementations of UI patterns that still need runtime help.",
|
|
6
5
|
"type": "module",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
8
15
|
"license": "MIT",
|
|
9
16
|
"repository": "tobyzerner/inclusive-elements",
|
|
10
17
|
"files": [
|
|
11
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"src/*/README.md"
|
|
12
21
|
],
|
|
13
22
|
"sideEffects": false,
|
|
14
23
|
"scripts": {
|
|
15
24
|
"dev": "vite",
|
|
16
25
|
"build": "tsc --noEmit && vite build && tsc --emitDeclarationOnly",
|
|
26
|
+
"prepare": "pnpm build",
|
|
27
|
+
"test": "vitest run",
|
|
17
28
|
"preview": "vite preview",
|
|
18
29
|
"release": "release-it"
|
|
19
30
|
},
|
|
20
31
|
"dependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"hello-goodbye": "^0.2.2",
|
|
24
|
-
"tabbable": "^6.2.0"
|
|
32
|
+
"tabbable": "^6.4.0",
|
|
33
|
+
"transition-kit": "^1.0.0"
|
|
25
34
|
},
|
|
26
35
|
"devDependencies": {
|
|
27
36
|
"@release-it/conventional-changelog": "^5.1.1",
|
|
37
|
+
"jsdom": "^29.0.1",
|
|
28
38
|
"prettier": "^2.8.8",
|
|
29
39
|
"release-it": "^15.11.0",
|
|
30
|
-
"typescript": "^5.9.
|
|
31
|
-
"vite": "^
|
|
40
|
+
"typescript": "^5.9.3",
|
|
41
|
+
"vite": "^8.0.1",
|
|
42
|
+
"vitest": "^4.1.0"
|
|
32
43
|
},
|
|
33
44
|
"release-it": {
|
|
34
45
|
"git": {
|
|
@@ -45,9 +56,6 @@
|
|
|
45
56
|
"infile": "CHANGELOG.md",
|
|
46
57
|
"header": "# Changelog"
|
|
47
58
|
}
|
|
48
|
-
},
|
|
49
|
-
"hooks": {
|
|
50
|
-
"after:bump": "npm run build"
|
|
51
59
|
}
|
|
52
60
|
},
|
|
53
61
|
"prettier": {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
**A native-first accordion pattern using `<details name>`.**
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<details name="faq">
|
|
9
|
+
<summary>Returns</summary>
|
|
10
|
+
<div>
|
|
11
|
+
<p>30 day returns.</p>
|
|
12
|
+
</div>
|
|
13
|
+
</details>
|
|
14
|
+
|
|
15
|
+
<details name="faq">
|
|
16
|
+
<summary>Warranty</summary>
|
|
17
|
+
<div>
|
|
18
|
+
<p>Two years included.</p>
|
|
19
|
+
</div>
|
|
20
|
+
</details>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Platform Requirements
|
|
24
|
+
|
|
25
|
+
- Requires modern browser support for grouped `<details name>` behavior.
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
- Giving related `<details>` elements the same `name` makes them behave like an accordion, where opening one closes the others.
|
|
30
|
+
- The browser provides the disclosure semantics and keyboard interaction.
|
|
31
|
+
- This keeps the pattern lightweight and easy to style, without a custom wrapper element.
|
|
32
|
+
|
|
33
|
+
## Further Reading
|
|
34
|
+
|
|
35
|
+
- [MDN: `<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/details)
|
|
36
|
+
- [ARIA Authoring Practices Guide: Accordion Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Alerts
|
|
2
|
+
|
|
3
|
+
**An accessible alert pattern with a supporting custom element.**
|
|
4
|
+
|
|
5
|
+
`ui-alerts` manages a stack of alert elements, normalizes their announcement semantics, and handles timed dismissal.
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { AlertsElement } from 'inclusive-elements';
|
|
11
|
+
|
|
12
|
+
window.customElements.define('ui-alerts', AlertsElement);
|
|
13
|
+
|
|
14
|
+
const alerts = document.createElement('ui-alerts');
|
|
15
|
+
document.body.appendChild(alerts);
|
|
16
|
+
|
|
17
|
+
function createAlert(type, message) {
|
|
18
|
+
const el = document.createElement('div');
|
|
19
|
+
el.className = type;
|
|
20
|
+
el.textContent = message;
|
|
21
|
+
return el;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
alerts.show(createAlert('error', 'There was an error'));
|
|
25
|
+
alerts.speak('Hello stranger!');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Behavior
|
|
29
|
+
|
|
30
|
+
### Container
|
|
31
|
+
|
|
32
|
+
- `<ui-alerts>` is the container and timing manager for a stack of alert elements.
|
|
33
|
+
- Any alerts already present as children when the element connects are reprocessed in place so they are announced and animated.
|
|
34
|
+
|
|
35
|
+
### Announcement Semantics
|
|
36
|
+
|
|
37
|
+
- Each alert can be announced politely or assertively.
|
|
38
|
+
- `show(..., { politeness })` takes precedence. Otherwise, existing authored live-region semantics on the alert are preserved before falling back to the element default.
|
|
39
|
+
- Polite alerts use `role="status"` and `aria-live="polite"`.
|
|
40
|
+
- Assertive alerts use `role="alert"` and `aria-live="assertive"`.
|
|
41
|
+
|
|
42
|
+
### Timing And Replacement
|
|
43
|
+
|
|
44
|
+
- Alerts auto-dismiss after their configured duration.
|
|
45
|
+
- Hovering or focusing an alert clears its dismissal timer, and the full duration starts again when pointer or focus leaves the alert.
|
|
46
|
+
- Showing a new alert with an existing `key` dismisses the previous alert with that key before the new one is announced.
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import type { AlertOptions } from 'inclusive-elements';
|
|
52
|
+
|
|
53
|
+
// Default dismissal time for alerts that do not provide one.
|
|
54
|
+
// Use 0 or a negative value to disable auto-dismiss by default.
|
|
55
|
+
AlertsElement.duration = 10000;
|
|
56
|
+
|
|
57
|
+
// Default politeness for alerts that do not provide one.
|
|
58
|
+
AlertsElement.politeness = 'polite';
|
|
59
|
+
|
|
60
|
+
const options: AlertOptions = {
|
|
61
|
+
// Reuse this key to replace an earlier alert with the same key.
|
|
62
|
+
key: 'save-complete',
|
|
63
|
+
|
|
64
|
+
// Override the default dismissal time for this alert only.
|
|
65
|
+
// Use 0 or a negative value to require manual dismissal.
|
|
66
|
+
duration: 5000,
|
|
67
|
+
|
|
68
|
+
// Use 'assertive' for urgent announcements that should interrupt.
|
|
69
|
+
politeness: 'assertive',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Appends the element, announces it, starts dismissal timing,
|
|
73
|
+
// and returns the key used for this alert.
|
|
74
|
+
const key = alerts.show(el, options);
|
|
75
|
+
|
|
76
|
+
// Dismiss a specific alert element.
|
|
77
|
+
alerts.dismiss(el);
|
|
78
|
+
|
|
79
|
+
// Dismiss any current alerts using this key.
|
|
80
|
+
alerts.dismiss(key);
|
|
81
|
+
|
|
82
|
+
// Dismiss every alert in the container.
|
|
83
|
+
alerts.clear();
|
|
84
|
+
|
|
85
|
+
// Announce plain text without creating your own element.
|
|
86
|
+
alerts.speak(message, { politeness: 'polite' });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Styling
|
|
90
|
+
|
|
91
|
+
`ui-alerts` is unstyled. The supported animation hooks are:
|
|
92
|
+
|
|
93
|
+
- New alerts receive `enter-*` classes so CSS can animate them in.
|
|
94
|
+
- Dismissed alerts animate out using `leave-*` classes and are then removed.
|
|
95
|
+
- Existing alerts animate into their new positions using the `move` class.
|
|
96
|
+
|
|
97
|
+
```css
|
|
98
|
+
ui-alerts {
|
|
99
|
+
position: fixed;
|
|
100
|
+
top: 0;
|
|
101
|
+
right: 0;
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
align-items: flex-end;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ui-alerts > * {
|
|
109
|
+
pointer-events: auto;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
113
|
+
ui-alerts > .enter-active,
|
|
114
|
+
ui-alerts > .leave-active,
|
|
115
|
+
ui-alerts > .move {
|
|
116
|
+
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
ui-alerts > .move {
|
|
120
|
+
transition: transform 0.2s ease;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ui-alerts > .enter-from,
|
|
124
|
+
ui-alerts > .leave-to {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
transform: translateY(-0.5rem);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Further Reading
|
|
132
|
+
|
|
133
|
+
- [ARIA Authoring Practices Guide: Alert Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alert/)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Dialog
|
|
2
|
+
|
|
3
|
+
**A native-first modal dialog pattern using `<dialog>`.**
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<button command="show-modal" commandfor="delete-dialog">Delete</button>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button type="button" id="delete-trigger">Delete</button>
|
|
13
|
+
|
|
14
|
+
<script type="module">
|
|
15
|
+
document
|
|
16
|
+
.getElementById('delete-trigger')
|
|
17
|
+
?.addEventListener('click', () => {
|
|
18
|
+
document.getElementById('delete-dialog')?.showModal();
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<dialog id="delete-dialog" aria-labelledby="delete-title">
|
|
25
|
+
<h2 id="delete-title">Delete item?</h2>
|
|
26
|
+
<p>This action cannot be undone.</p>
|
|
27
|
+
|
|
28
|
+
<form method="dialog">
|
|
29
|
+
<button value="cancel" autofocus>Cancel</button>
|
|
30
|
+
<button value="confirm">Delete</button>
|
|
31
|
+
</form>
|
|
32
|
+
</dialog>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Platform Requirements
|
|
36
|
+
|
|
37
|
+
- Requires modern browser support for `<dialog>`.
|
|
38
|
+
- Declarative `command="show-modal"` and `commandfor` buttons are newer platform features. Use `showModal()` when you need the imperative fallback.
|
|
39
|
+
|
|
40
|
+
## Behavior
|
|
41
|
+
|
|
42
|
+
- `<dialog>` provides the modal container and top-layer behavior.
|
|
43
|
+
- Declarative `command="show-modal"` / `commandfor` buttons and imperative `showModal()` calls are both valid ways to open the dialog.
|
|
44
|
+
- The browser manages focus movement, Escape handling, and inert background behavior.
|
|
45
|
+
|
|
46
|
+
## Accessibility Notes
|
|
47
|
+
|
|
48
|
+
- Give the dialog an accessible name with visible text and `aria-labelledby`, or with `aria-label` when no visible title fits.
|
|
49
|
+
- Provide an obvious dismiss action inside the dialog, not just Escape or backdrop dismissal.
|
|
50
|
+
- Use `<form method="dialog">` or `close()` intentionally so the close behavior is explicit in your markup and scripts.
|
|
51
|
+
- Native modal dialogs usually restore focus appropriately when they close. Only manage focus yourself when the opener is gone or your workflow needs a different destination.
|
|
52
|
+
- This pattern is for modal tasks that need blocking attention. For non-modal surfaces, use a popover or another native pattern instead.
|
|
53
|
+
|
|
54
|
+
## Further Reading
|
|
55
|
+
|
|
56
|
+
- [MDN: `<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog)
|
|
57
|
+
- [ARIA Authoring Practices Guide: Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Disclosure
|
|
2
|
+
|
|
3
|
+
**A native-first disclosure pattern using `<details>` and `<summary>`.**
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<details>
|
|
9
|
+
<summary>Shipping information</summary>
|
|
10
|
+
<div>
|
|
11
|
+
<p>Ships in 2–3 business days.</p>
|
|
12
|
+
</div>
|
|
13
|
+
</details>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
- `<summary>` acts as the built-in control for toggling the disclosure.
|
|
19
|
+
- The browser manages the open state and accessibility semantics.
|
|
20
|
+
|
|
21
|
+
## Accessibility Notes
|
|
22
|
+
|
|
23
|
+
- Write summary text that clearly describes the hidden content, not generic labels such as "More" without context.
|
|
24
|
+
|
|
25
|
+
## Further Reading
|
|
26
|
+
|
|
27
|
+
- [MDN: `<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/details)
|
|
28
|
+
- [ARIA Authoring Practices Guide: Disclosure Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
|
|
3
|
+
**An accessible menu pattern built on native popovers with a supporting custom element.**
|
|
4
|
+
|
|
5
|
+
`ui-menu` keeps the popup surface native and adds the menu behaviors the platform does not provide by itself, including arrow-key navigation, typeahead, and submenu coordination.
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { MenuElement } from 'inclusive-elements';
|
|
11
|
+
|
|
12
|
+
window.customElements.define('ui-menu', MenuElement);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<button type="button" popovertarget="actions-menu" aria-haspopup="menu">
|
|
17
|
+
Actions
|
|
18
|
+
</button>
|
|
19
|
+
|
|
20
|
+
<ui-menu id="actions-menu" popover data-placement="bottom-start">
|
|
21
|
+
<button type="button" role="menuitem">Edit</button>
|
|
22
|
+
<button type="button" role="menuitem">Archive</button>
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
role="menuitem"
|
|
26
|
+
popovertarget="more-menu"
|
|
27
|
+
aria-haspopup="menu"
|
|
28
|
+
>
|
|
29
|
+
More
|
|
30
|
+
</button>
|
|
31
|
+
</ui-menu>
|
|
32
|
+
|
|
33
|
+
<ui-menu id="more-menu" popover data-placement="right-start">
|
|
34
|
+
<button type="button" role="menuitem">Duplicate</button>
|
|
35
|
+
<button type="button" role="menuitem">Delete</button>
|
|
36
|
+
</ui-menu>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Platform Requirements
|
|
40
|
+
|
|
41
|
+
- Requires modern browser support for the Popover API.
|
|
42
|
+
- The styling examples use CSS anchor positioning.
|
|
43
|
+
|
|
44
|
+
## Behavior
|
|
45
|
+
|
|
46
|
+
### Structure
|
|
47
|
+
|
|
48
|
+
- Add `popover` to the root `<ui-menu>` and point a button at it with `popovertarget` and `aria-haspopup="menu"`.
|
|
49
|
+
- Give direct children a menu item role such as `role="menuitem"`. `ui-menu` adds `role="menu"` and `tabindex="-1"` to the menu itself and manages direct menuitem children with `tabindex="-1"` as well.
|
|
50
|
+
|
|
51
|
+
### Navigation
|
|
52
|
+
|
|
53
|
+
- By default, menus are vertical. Arrow Down and Arrow Up move through the enabled items and wrap at the ends.
|
|
54
|
+
- Pressing Arrow Down or Arrow Up on the root trigger opens the menu and focuses the first or last enabled item.
|
|
55
|
+
- Typeahead matches and moves focus to enabled items by their `aria-label` when present, otherwise by their text content.
|
|
56
|
+
|
|
57
|
+
### Horizontal Menus
|
|
58
|
+
|
|
59
|
+
- To make a horizontal popup menu, set `aria-orientation="horizontal"` on `<ui-menu>`. This keeps the element a `menu`, not a `menubar`, and works well for patterns such as reaction pickers.
|
|
60
|
+
- In a horizontal popup menu, Left and Right move through the enabled items and wrap at the ends.
|
|
61
|
+
|
|
62
|
+
### Submenus
|
|
63
|
+
|
|
64
|
+
- To create a submenu, make a menu item that points at another `<ui-menu>` with `popovertarget` and `aria-haspopup="menu"`.
|
|
65
|
+
- Submenus open on click, hover, Enter, or Space, Right Arrow in vertical menus, and Down Arrow in horizontal menus.
|
|
66
|
+
- Submenus close with Left Arrow in vertical menus and Up Arrow in horizontal menus.
|
|
67
|
+
|
|
68
|
+
### Closing
|
|
69
|
+
|
|
70
|
+
- Clicking a regular menu item closes the open menu tree.
|
|
71
|
+
- Escape also closes the open menu tree.
|
|
72
|
+
- Tab closes the open menu tree before normal tab order continues.
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// Time in milliseconds before the accumulated typeahead search resets.
|
|
78
|
+
MenuElement.searchDelay = 800;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Styling
|
|
82
|
+
|
|
83
|
+
`ui-menu` is unstyled. Position the popover surface with native anchor positioning:
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
[popover] {
|
|
87
|
+
position: fixed;
|
|
88
|
+
position-anchor: auto;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[popover][data-placement='bottom-start'] {
|
|
92
|
+
position-area: block-end span-inline-start;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
[popover][data-placement='right-start'] {
|
|
96
|
+
position-area: inline-end span-block-start;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Further Reading
|
|
101
|
+
|
|
102
|
+
- [ARIA Authoring Practices Guide: Menu and Menubar Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/)
|
|
103
|
+
- [MDN: Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using)
|