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.
@@ -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
  }
@@ -1,9 +1,6 @@
1
- export { default as AccordionElement } from './accordion/accordion';
2
1
  export { default as AlertsElement } from './alerts/alerts';
3
- export { default as DisclosureElement } from './disclosure/disclosure';
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
- private get items();
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 navigate;
11
- private focusItemAtIndex;
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 get initialTabIndex();
14
+ private syncStructure;
15
+ private get vertical();
13
16
  private get tablist();
14
- private get tabs();
15
- private get tabpanels();
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 onInitialFocus;
5
+ private onFocus;
5
6
  private onKeyDown;
6
- private focusControlAtIndex;
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 placement: string;
4
- static tooltipClass: string;
5
- private parent?;
6
- private tooltip?;
3
+ static observedAttributes: string[];
4
+ private owner?;
7
5
  private timeout?;
8
- private disabledObserver?;
9
- private showing;
10
- private cleanup?;
11
- private prevInnerHTML?;
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
- private onKeyDown;
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 createTooltip;
30
+ private containsInteractiveTarget;
31
+ private updateManagedDescription;
32
+ private getDescriptionTokens;
33
+ private ensureTooltipId;
34
+ private supportsHoverInteraction;
35
+ private isOwnerDisabled;
28
36
  }
@@ -1,4 +1,15 @@
1
- /**
2
- * Determine whether the user is trying to open a link in a new tab.
3
- */
4
- export declare function shouldOpenInNewTab(e: MouseEvent): boolean;
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.5.5",
4
- "description": "Accessible, lightweight, unstyled implementations of common UI controls.",
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
- "@floating-ui/dom": "^1.7.4",
22
- "focus-trap": "^7.6.5",
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.2",
31
- "vite": "^4.5.14"
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)