inclusive-elements 0.1.2 → 0.2.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/README.md CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  **Accessible, lightweight, unstyled implementations of common UI controls.**
4
4
 
5
- [**Demo**](https://tobyzerner.github.io/inclusive-elements/demo/index.html)
5
+ [**Demo**](https://tobyzerner.github.io/inclusive-elements/index.html)
6
6
 
7
7
  Basic interactive UI patterns like popup menus, tooltips, and modals are surprisingly hard to get right on the web. Component libraries tend to be weighty, opinionated, and come tightly coupled with styles you don't want. On the other hand, rolling your own components feels like you're reinventing the wheel and puts accessibility at risk.
8
8
 
9
9
  `inclusive-elements` provides those basic behaviors in the form of Custom Elements, without any of the cruft. They are:
10
10
 
11
- - **🦮 Accessible.** Designed following the [WAI-ARIA Authoring Practices](https://w3c.github.io/aria-practices) so everyone can use them.
12
- - **🌳 Lightweight.** 3kB gzipped with minimal dependencies. Less if you tree-shake only the elements you need.
13
- - **🎨 Unstyled.** Easily integrate with your design bottom-up without having to fight defaults.
14
- - **🚀 Easy to use.** Simple API, works with any framework that supports Custom Elements.
11
+ - **🦮 Accessible.** Designed following the [WAI-ARIA Authoring Practices](https://w3c.github.io/aria-practices) so everyone can use them.
12
+ - **🌳 Lightweight.** 3kB gzipped with minimal dependencies. Less if you tree-shake only the elements you need.
13
+ - **🎨 Unstyled.** Easily integrate with your design bottom-up without having to fight defaults.
14
+ - **🚀 Easy to use.** Simple API, works with any framework that supports Custom Elements.
15
15
 
16
16
  ## Installation
17
17
 
@@ -23,12 +23,12 @@ npm install inclusive-elements --save
23
23
 
24
24
  Detailed descriptions and usage instructions are contained within each element:
25
25
 
26
- - [Alerts](src/alerts)
27
- - [Menu](src/menu)
28
- - [Modal](src/modal)
29
- - [Popup](src/popup)
30
- - [Toolbar](src/toolbar)
31
- - [Tooltip](src/tooltip)
26
+ - [Alerts](src/alerts)
27
+ - [Menu](src/menu)
28
+ - [Modal](src/modal)
29
+ - [Popup](src/popup)
30
+ - [Toolbar](src/toolbar)
31
+ - [Tooltip](src/tooltip)
32
32
 
33
33
  ## Contributing
34
34
 
@@ -0,0 +1,400 @@
1
+ var T = Object.defineProperty;
2
+ var C = (a, o, t) => o in a ? T(a, o, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[o] = t;
3
+ var s = (a, o, t) => (C(a, typeof o != "symbol" ? o + "" : o, t), t);
4
+ import { move as E, hello as l, goodbye as u, cancel as k } from "hello-goodbye";
5
+ import { isFocusable as D, focusable as g } from "tabbable";
6
+ import { createFocusTrap as H } from "focus-trap";
7
+ import { autoUpdate as M, computePosition as A, shift as L, flip as x, size as O } from "@floating-ui/dom";
8
+ const v = class extends HTMLElement {
9
+ constructor() {
10
+ super(...arguments);
11
+ s(this, "timeouts", /* @__PURE__ */ new Map());
12
+ s(this, "index", 0);
13
+ }
14
+ connectedCallback() {
15
+ this.hasAttribute("role") || this.setAttribute("role", "status"), this.hasAttribute("aria-live") || this.setAttribute("aria-live", "polite"), this.hasAttribute("aria-relevant") || this.setAttribute("aria-relevant", "additions");
16
+ }
17
+ show(t, e = {}) {
18
+ const i = e.key || String(this.index++);
19
+ this.dismiss(i), t.dataset.key = i, E(this.children, () => {
20
+ this.append(t), l(t);
21
+ });
22
+ const n = typeof e.duration < "u" ? Number(e.duration) : v.duration;
23
+ return n > 0 && (this.startTimeout(t, n), t.addEventListener("mouseenter", this.clearTimeout.bind(this, t)), t.addEventListener("focusin", this.clearTimeout.bind(this, t)), t.addEventListener(
24
+ "mouseleave",
25
+ this.startTimeout.bind(this, t, n)
26
+ ), t.addEventListener(
27
+ "focusout",
28
+ this.startTimeout.bind(this, t, n)
29
+ )), i;
30
+ }
31
+ dismiss(t) {
32
+ if (typeof t == "string") {
33
+ this.querySelectorAll(
34
+ `[data-key="${t}"]`
35
+ ).forEach((e) => {
36
+ this.dismiss(e);
37
+ });
38
+ return;
39
+ }
40
+ E(this.children, () => {
41
+ u(t, {
42
+ finish: () => this.removeChild(t)
43
+ });
44
+ }), this.clearTimeout(t);
45
+ }
46
+ clear() {
47
+ Array.from(this.children).forEach((t) => {
48
+ this.dismiss(t);
49
+ });
50
+ }
51
+ speak(t) {
52
+ const e = document.createElement("div");
53
+ Object.assign(e.style, {
54
+ clip: "rect(0 0 0 0)",
55
+ clipPath: "inset(50%)",
56
+ height: "1px",
57
+ overflow: "hidden",
58
+ position: "absolute",
59
+ whiteSpace: "nowrap",
60
+ width: "1px"
61
+ }), e.textContent = t, this.show(e);
62
+ }
63
+ startTimeout(t, e) {
64
+ this.clearTimeout(t), this.timeouts.set(
65
+ t,
66
+ window.setTimeout(() => {
67
+ this.dismiss(t);
68
+ }, e)
69
+ );
70
+ }
71
+ clearTimeout(t) {
72
+ this.timeouts.has(t) && clearTimeout(this.timeouts.get(t));
73
+ }
74
+ };
75
+ let f = v;
76
+ s(f, "duration", 1e4);
77
+ const w = class extends HTMLElement {
78
+ constructor() {
79
+ super(...arguments);
80
+ s(this, "search", "");
81
+ s(this, "searchTimeout");
82
+ s(this, "onKeyDown", (t) => {
83
+ if (!this.hidden) {
84
+ if (t.key === "ArrowUp") {
85
+ this.navigate(-1), t.preventDefault();
86
+ return;
87
+ }
88
+ if (t.key === "ArrowDown") {
89
+ this.navigate(1), t.preventDefault();
90
+ return;
91
+ }
92
+ t.key.length > 1 || t.ctrlKey || t.metaKey || t.altKey || (this.search += t.key.toLowerCase(), t.preventDefault(), clearTimeout(this.searchTimeout), this.searchTimeout = window.setTimeout(() => {
93
+ this.search = "";
94
+ }, w.searchDelay), this.items.some((e) => {
95
+ var i;
96
+ return ((i = e.textContent) == null ? void 0 : i.trim().toLowerCase().indexOf(this.search)) === 0 ? (e.focus(), !0) : !1;
97
+ }));
98
+ }
99
+ });
100
+ }
101
+ connectedCallback() {
102
+ this.hasAttribute("role") || this.setAttribute("role", "menu"), this.hasAttribute("tabindex") || this.setAttribute("tabindex", "-1"), this.items.forEach((t) => {
103
+ t.hasAttribute("tabindex") || t.setAttribute("tabindex", "-1");
104
+ }), this.addEventListener("keydown", this.onKeyDown);
105
+ }
106
+ disconnectedCallback() {
107
+ this.removeEventListener("keydown", this.onKeyDown);
108
+ }
109
+ get items() {
110
+ return Array.from(
111
+ this.querySelectorAll("[role^=menuitem]")
112
+ ).filter((t) => D(t));
113
+ }
114
+ navigate(t) {
115
+ var n;
116
+ const e = this.items;
117
+ let i = (document.activeElement instanceof HTMLElement ? e.indexOf(document.activeElement) : -1) + t;
118
+ i < 0 && (i = e.length - 1), i >= e.length && (i = 0), (n = e[i]) == null || n.focus();
119
+ }
120
+ };
121
+ let m = w;
122
+ s(m, "searchDelay", 800);
123
+ const d = class extends HTMLElement {
124
+ constructor() {
125
+ super();
126
+ s(this, "focusTrap");
127
+ s(this, "connected", !1);
128
+ const t = document.createElement("template");
129
+ t.innerHTML = `
130
+ <div part="backdrop" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0"></div>
131
+ <div part="content" style="z-index: 1"><slot></slot></div>
132
+ `;
133
+ const e = this.attachShadow({ mode: "open" });
134
+ e.appendChild(t.content.cloneNode(!0)), this.backdrop.addEventListener("click", () => {
135
+ var i;
136
+ this.hasAttribute("static") ? (i = d.attention) == null || i.call(d, e.children[1]) : this.close();
137
+ }), this.focusTrap = H(this, {
138
+ escapeDeactivates: !1,
139
+ allowOutsideClick: !0
140
+ });
141
+ }
142
+ static get observedAttributes() {
143
+ return ["open"];
144
+ }
145
+ connectedCallback() {
146
+ var t, e, i, n, r, y;
147
+ this.connected = !0, (t = this.content) != null && t.hasAttribute("role") || (e = this.content) == null || e.setAttribute("role", "dialog"), (i = this.content) != null && i.hasAttribute("aria-modal") || (n = this.content) == null || n.setAttribute("aria-modal", "true"), (r = this.content) != null && r.hasAttribute("tabindex") || (y = this.content) == null || y.setAttribute("tabindex", "-1"), this.addEventListener("keydown", (p) => {
148
+ p.key === "Escape" && this.open && (p.preventDefault(), p.stopPropagation(), this.close());
149
+ }), this.open && this.wasOpened();
150
+ }
151
+ disconnectedCallback() {
152
+ this.connected = !1;
153
+ }
154
+ get open() {
155
+ return this.hasAttribute("open");
156
+ }
157
+ set open(t) {
158
+ t ? this.setAttribute("open", "") : this.removeAttribute("open");
159
+ }
160
+ close() {
161
+ if (!this.open)
162
+ return;
163
+ const t = new Event("beforeclose", { cancelable: !0 });
164
+ this.dispatchEvent(t) && (this.open = !1);
165
+ }
166
+ attributeChangedCallback(t, e, i) {
167
+ t !== "open" || !this.connected || (i !== null ? this.wasOpened() : this.wasClosed());
168
+ }
169
+ wasOpened() {
170
+ var t, e;
171
+ this.hidden = !1, l(this), (t = this.focusTrap) == null || t.activate(), (e = this.querySelector("[autofocus]")) == null || e.focus(), this.dispatchEvent(new Event("open"));
172
+ }
173
+ wasClosed() {
174
+ var t;
175
+ (t = this.focusTrap) == null || t.deactivate(), u(this, {
176
+ finish: () => this.hidden = !0
177
+ }), this.dispatchEvent(new Event("close"));
178
+ }
179
+ get backdrop() {
180
+ var t;
181
+ return (t = this.shadowRoot) == null ? void 0 : t.firstElementChild;
182
+ }
183
+ get content() {
184
+ return this.firstElementChild;
185
+ }
186
+ };
187
+ let b = d;
188
+ s(b, "attention", (t) => t.animate(
189
+ [
190
+ { transform: "scale(1)" },
191
+ { transform: "scale(1.1)" },
192
+ { transform: "scale(1)" }
193
+ ],
194
+ 300
195
+ ));
196
+ class F extends HTMLElement {
197
+ constructor() {
198
+ super();
199
+ s(this, "cleanup");
200
+ const t = document.createElement("template");
201
+ t.innerHTML = `
202
+ <div part="backdrop" hidden style="position: fixed; top: 0; left: 0; right: 0; bottom: 0"></div>
203
+ <slot></slot>
204
+ `, this.attachShadow({ mode: "open" }).appendChild(t.content.cloneNode(!0)), this.backdrop.onclick = () => this.open = !1;
205
+ }
206
+ static get observedAttributes() {
207
+ return ["open"];
208
+ }
209
+ connectedCallback() {
210
+ this.backdrop.hidden = !0, this.content.hidden = !0, this.open = !1, this.button.setAttribute("aria-expanded", "false"), setTimeout(() => {
211
+ this.content.getAttribute("role") === "menu" && this.button.setAttribute("aria-haspopup", "true");
212
+ }), this.button.addEventListener("click", () => {
213
+ this.open = !this.open;
214
+ }), this.button.addEventListener("keydown", (t) => {
215
+ var e;
216
+ t.key === "ArrowDown" && (t.preventDefault(), this.open = !0, (e = g(this.content)[0]) == null || e.focus());
217
+ }), this.addEventListener("keydown", (t) => {
218
+ t.key === "Escape" && this.open && (t.preventDefault(), t.stopPropagation(), this.open = !1, this.button.focus());
219
+ }), this.addEventListener("focusout", (t) => {
220
+ (!(t.relatedTarget instanceof Node) || !this.contains(t.relatedTarget)) && (this.open = !1);
221
+ }), this.content.addEventListener("click", (t) => {
222
+ t.target instanceof Element && t.target.closest("[role=menuitem], [role=menuitemradio]") && (this.open = !1, this.button.focus());
223
+ });
224
+ }
225
+ disconnectedCallback() {
226
+ k(this.backdrop), k(this.content);
227
+ }
228
+ get open() {
229
+ return this.hasAttribute("open");
230
+ }
231
+ set open(t) {
232
+ t ? this.setAttribute("open", "") : this.removeAttribute("open");
233
+ }
234
+ attributeChangedCallback(t, e, i) {
235
+ t === "open" && (i !== null ? this.wasOpened() : this.wasClosed());
236
+ }
237
+ wasOpened() {
238
+ if (!this.content.hidden)
239
+ return;
240
+ this.content.hidden = !1, this.backdrop.hidden = !1, l(this.content), l(this.backdrop), this.button.setAttribute("aria-expanded", "true"), this.content.style.position = "absolute", this.cleanup = M(
241
+ this.button,
242
+ this.content,
243
+ () => {
244
+ A(this.button, this.content, {
245
+ placement: this.getAttribute("placement") || "bottom",
246
+ middleware: [
247
+ L(),
248
+ x(),
249
+ O({
250
+ apply: ({ availableWidth: e, availableHeight: i }) => {
251
+ Object.assign(this.content.style, {
252
+ maxWidth: "",
253
+ maxHeight: ""
254
+ });
255
+ const n = getComputedStyle(this.content);
256
+ (n.maxWidth === "none" || e < parseInt(n.maxWidth)) && (this.content.style.maxWidth = `${e}px`), (n.maxHeight === "none" || i < parseInt(n.maxHeight)) && (this.content.style.maxHeight = `${i}px`);
257
+ }
258
+ })
259
+ ]
260
+ }).then(({ x: e, y: i, placement: n }) => {
261
+ Object.assign(this.content.style, {
262
+ left: `${e}px`,
263
+ top: `${i}px`
264
+ }), this.content.dataset.placement = n;
265
+ });
266
+ },
267
+ { ancestorScroll: !1 }
268
+ );
269
+ const t = this.content.querySelector("[autofocus]");
270
+ t ? t.focus() : this.content.focus(), this.dispatchEvent(new Event("open"));
271
+ }
272
+ wasClosed() {
273
+ var t;
274
+ this.content.hidden || (this.button.setAttribute("aria-expanded", "false"), (t = this.cleanup) == null || t.call(this), u(this.backdrop, {
275
+ finish: () => this.backdrop.hidden = !0
276
+ }), u(this.content, {
277
+ finish: () => this.content.hidden = !0
278
+ }), this.dispatchEvent(new Event("close")));
279
+ }
280
+ get backdrop() {
281
+ var t;
282
+ return (t = this.shadowRoot) == null ? void 0 : t.firstElementChild;
283
+ }
284
+ get button() {
285
+ return this.children[0];
286
+ }
287
+ get content() {
288
+ return this.children[1];
289
+ }
290
+ }
291
+ class $ extends HTMLElement {
292
+ constructor() {
293
+ super(...arguments);
294
+ s(this, "onInitialFocus", () => {
295
+ this.removeAttribute("tabindex"), this.focusControlAtIndex(0);
296
+ });
297
+ s(this, "onKeyDown", (t) => {
298
+ if (t.key !== "ArrowRight" && t.key !== "ArrowLeft" && t.key !== "Home" && t.key !== "End")
299
+ return;
300
+ const e = this.controls, i = this.controls.length, n = e.indexOf(t.target);
301
+ if (n === -1)
302
+ return;
303
+ let r = 0;
304
+ t.key === "ArrowLeft" && (r = n - 1), t.key === "ArrowRight" && (r = n + 1), t.key === "End" && (r = i - 1), r < 0 && (r = i - 1), r > i - 1 && (r = 0), this.focusControlAtIndex(r), t.preventDefault();
305
+ });
306
+ }
307
+ connectedCallback() {
308
+ this.hasAttribute("role") || this.setAttribute("role", "toolbar"), this.setAttribute("tabindex", "0"), this.addEventListener("focus", this.onInitialFocus, { once: !0 }), this.addEventListener("keydown", this.onKeyDown);
309
+ }
310
+ disconnectedCallback() {
311
+ this.removeEventListener("keydown", this.onKeyDown);
312
+ }
313
+ focusControlAtIndex(t) {
314
+ this.controls.forEach((e, i) => {
315
+ i === t ? (e.setAttribute("tabindex", "0"), e.focus()) : e.setAttribute("tabindex", "-1");
316
+ });
317
+ }
318
+ get controls() {
319
+ return g(this);
320
+ }
321
+ }
322
+ const c = class extends HTMLElement {
323
+ constructor() {
324
+ super(...arguments);
325
+ s(this, "parent");
326
+ s(this, "tooltip");
327
+ s(this, "timeout");
328
+ s(this, "observer");
329
+ s(this, "showing", !1);
330
+ s(this, "onMouseEnter", this.afterDelay.bind(this, this.show));
331
+ s(this, "onFocus", this.show.bind(this));
332
+ s(this, "onMouseLeave", this.afterDelay.bind(this, this.hide));
333
+ s(this, "onBlur", this.hide.bind(this));
334
+ s(this, "onKeyDown", (t) => {
335
+ t.key === "Escape" && this.hide();
336
+ });
337
+ }
338
+ connectedCallback() {
339
+ this.parent = this.parentNode, this.parent && (this.parent.addEventListener("mouseenter", this.onMouseEnter), this.parent.addEventListener("focus", this.onFocus), this.parent.addEventListener("mouseleave", this.onMouseLeave), this.parent.addEventListener("blur", this.onBlur), this.parent.addEventListener("click", this.onBlur), this.observer = new MutationObserver((t) => {
340
+ t.forEach((e) => {
341
+ e.attributeName === "disabled" && this.hide();
342
+ });
343
+ }), this.observer.observe(this.parent, { attributes: !0 })), document.addEventListener("keydown", this.onKeyDown), document.addEventListener("scroll", this.onBlur);
344
+ }
345
+ disconnectedCallback() {
346
+ var t;
347
+ this.tooltip && (this.tooltip.remove(), this.tooltip = void 0), this.parent && (this.parent.removeEventListener("mouseenter", this.onMouseEnter), this.parent.removeEventListener("focus", this.onFocus), this.parent.removeEventListener("mouseleave", this.onMouseLeave), this.parent.removeEventListener("blur", this.onBlur), this.parent.removeEventListener("click", this.onBlur), this.parent = void 0), document.removeEventListener("keydown", this.onKeyDown), document.removeEventListener("scroll", this.onBlur), (t = this.observer) == null || t.disconnect(), clearTimeout(this.timeout);
348
+ }
349
+ get disabled() {
350
+ return this.hasAttribute("disabled");
351
+ }
352
+ set disabled(t) {
353
+ t ? this.setAttribute("disabled", "") : this.removeAttribute("disabled");
354
+ }
355
+ show() {
356
+ if (this.disabled)
357
+ return;
358
+ const t = this.createTooltip();
359
+ clearTimeout(this.timeout), this.showing || (t.hidden = !1, l(t), this.showing = !0), t.innerHTML !== this.innerHTML && (t.innerHTML = this.innerHTML), t.style.position = "absolute", A(this.parent, t, {
360
+ placement: this.getAttribute("placement") || c.placement,
361
+ middleware: [L(), x()]
362
+ }).then(({ x: e, y: i, placement: n }) => {
363
+ Object.assign(t.style, {
364
+ left: `${e}px`,
365
+ top: `${i}px`
366
+ }), t.dataset.placement = n;
367
+ });
368
+ }
369
+ hide() {
370
+ clearTimeout(this.timeout), this.showing && (this.showing = !1, u(this.tooltip, {
371
+ finish: () => {
372
+ this.tooltip && (this.tooltip.hidden = !0);
373
+ }
374
+ }));
375
+ }
376
+ afterDelay(t) {
377
+ clearTimeout(this.timeout);
378
+ const e = parseInt(this.getAttribute("delay") || "");
379
+ this.timeout = window.setTimeout(
380
+ t.bind(this),
381
+ isNaN(e) ? c.delay : e
382
+ );
383
+ }
384
+ createTooltip() {
385
+ return this.tooltip || (this.tooltip = document.createElement("div"), this.tooltip.className = this.getAttribute("tooltip-class") || c.tooltipClass, this.tooltip.hidden = !0, this.tooltip.addEventListener("mouseenter", this.show.bind(this)), this.tooltip.addEventListener(
386
+ "mouseleave",
387
+ this.afterDelay.bind(this, this.hide)
388
+ ), document.body.appendChild(this.tooltip)), this.tooltip;
389
+ }
390
+ };
391
+ let h = c;
392
+ s(h, "delay", 100), s(h, "placement", "top"), s(h, "tooltipClass", "tooltip");
393
+ export {
394
+ f as AlertsElement,
395
+ m as MenuElement,
396
+ b as ModalElement,
397
+ F as PopupElement,
398
+ $ as ToolbarElement,
399
+ h as TooltipElement
400
+ };
File without changes
File without changes
File without changes
@@ -1,17 +1,17 @@
1
1
  export default class ModalElement extends HTMLElement {
2
2
  static attention: (el: Element) => void;
3
3
  static get observedAttributes(): string[];
4
- private trigger?;
5
4
  private focusTrap?;
5
+ private connected;
6
6
  constructor();
7
7
  connectedCallback(): void;
8
+ disconnectedCallback(): void;
8
9
  get open(): boolean;
9
10
  set open(val: boolean);
10
11
  close(): void;
11
12
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
12
13
  private wasOpened;
13
14
  private wasClosed;
14
- private focusContent;
15
15
  private get backdrop();
16
16
  private get content();
17
17
  }
@@ -1,4 +1,5 @@
1
1
  export default class PopupElement extends HTMLElement {
2
+ cleanup?: () => void;
2
3
  static get observedAttributes(): string[];
3
4
  constructor();
4
5
  connectedCallback(): void;
File without changes
File without changes
package/package.json CHANGED
@@ -1,54 +1,51 @@
1
1
  {
2
- "name": "inclusive-elements",
3
- "version": "0.1.2",
4
- "description": "Accessible, lightweight, unstyled implementations of common UI controls.",
5
- "main": "dist/index.js",
6
- "module": "dist/index.js",
7
- "type": "module",
8
- "types": "dist/index.d.ts",
9
- "license": "MIT",
10
- "repository": "tobyzerner/inclusive-elements",
11
- "files": [
12
- "dist",
13
- "README.md"
14
- ],
15
- "sideEffects": false,
16
- "scripts": {
17
- "build": "tsc && rollup -c",
18
- "build:watch": "rollup -cw",
19
- "build:demo": "rollup -c demo/rollup.config.js",
20
- "release": "release-it --npm.tag=latest"
21
- },
22
- "dependencies": {
23
- "@floating-ui/dom": "^0.2.0",
24
- "focus-trap": "^6.7.2",
25
- "hello-goodbye": "^0.1.0-beta.5",
26
- "tabbable": "^5.2.1"
27
- },
28
- "devDependencies": {
29
- "@release-it/keep-a-changelog": "^2.3.0",
30
- "@rollup/plugin-node-resolve": "^11.2.1",
31
- "@rollup/plugin-typescript": "^8.3.0",
32
- "release-it": "^14.12.4",
33
- "rollup": "^2.66.1",
34
- "rollup-plugin-filesize": "^9.1.2",
35
- "rollup-plugin-terser": "^7.0.2",
36
- "tslib": "^2.3.1",
37
- "typescript": "^4.5.5"
38
- },
39
- "release-it": {
40
- "github": {
41
- "release": true
2
+ "name": "inclusive-elements",
3
+ "version": "0.2.1",
4
+ "description": "Accessible, lightweight, unstyled implementations of common UI controls.",
5
+ "module": "dist/inclusive-elements.js",
6
+ "type": "module",
7
+ "types": "dist/types/index.d.ts",
8
+ "license": "MIT",
9
+ "repository": "tobyzerner/inclusive-elements",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "sideEffects": false,
14
+ "scripts": {
15
+ "dev": "vite",
16
+ "build": "tsc --noEmit && vite build && tsc --emitDeclarationOnly",
17
+ "preview": "vite preview",
18
+ "release": "release-it --npm.tag=latest"
42
19
  },
43
- "plugins": {
44
- "@release-it/keep-a-changelog": {
45
- "filename": "CHANGELOG.md",
46
- "addUnreleased": true,
47
- "addVersionUrl": true
48
- }
20
+ "dependencies": {
21
+ "@floating-ui/dom": "^1.0.0",
22
+ "focus-trap": "^6.7.2",
23
+ "hello-goodbye": "^0.1.0-beta.5",
24
+ "tabbable": "^5.2.1"
49
25
  },
50
- "hooks": {
51
- "after:bump": "npm run build"
26
+ "devDependencies": {
27
+ "@release-it/keep-a-changelog": "^2.3.0",
28
+ "prettier": "^2.6.2",
29
+ "release-it": "^14.12.4",
30
+ "typescript": "^4.5.5",
31
+ "vite": "^3.0.4"
32
+ },
33
+ "release-it": {
34
+ "github": {
35
+ "release": true
36
+ },
37
+ "plugins": {
38
+ "@release-it/keep-a-changelog": {
39
+ "filename": "CHANGELOG.md",
40
+ "addUnreleased": true,
41
+ "addVersionUrl": true
42
+ }
43
+ },
44
+ "hooks": {
45
+ "after:bump": "npm run build"
46
+ }
47
+ },
48
+ "prettier": {
49
+ "singleQuote": true
52
50
  }
53
- }
54
51
  }
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- import{move as t,hello as e,goodbye as i,cancel as s}from"hello-goodbye";import{isFocusable as n,focusable as o}from"tabbable";import{createFocusTrap as r}from"focus-trap";import{computePosition as a,shift as h,flip as d,size as c}from"@floating-ui/dom";class l extends HTMLElement{static duration=1e4;timeouts=new Map;index=0;connectedCallback(){this.hasAttribute("role")||this.setAttribute("role","status"),this.hasAttribute("aria-live")||this.setAttribute("aria-live","polite"),this.hasAttribute("aria-relevant")||this.setAttribute("aria-relevant","additions")}show(i,s={}){const n=s.key||String(this.index++);this.dismiss(n),i.dataset.key=n,t(this.children,(()=>{this.append(i),e(i)}));const o=void 0!==s.duration?Number(s.duration):l.duration;return o>0&&(this.startTimeout(i,o),i.addEventListener("mouseenter",this.clearTimeout.bind(this,i)),i.addEventListener("focusin",this.clearTimeout.bind(this,i)),i.addEventListener("mouseleave",this.startTimeout.bind(this,i,o)),i.addEventListener("focusout",this.startTimeout.bind(this,i,o))),n}dismiss(e){if("string"!=typeof e)t(this.children,(()=>{i(e,{finish:()=>this.removeChild(e)})})),this.clearTimeout(e);else{const t=this.querySelector(`[data-key="${e}"]`);t&&this.dismiss(t)}}clear(){for(const t of this.children)this.dismiss(t)}speak(t){const e=document.createElement("div");Object.assign(e.style,{clip:"rect(0 0 0 0)",clipPath:"inset(50%)",height:"1px",overflow:"hidden",position:"absolute",whiteSpace:"nowrap",width:"1px"}),e.textContent=t,this.show(e)}startTimeout(t,e){this.clearTimeout(t),this.timeouts.set(t,window.setTimeout((()=>{this.dismiss(t)}),e))}clearTimeout(t){this.timeouts.has(t)&&clearTimeout(this.timeouts.get(t))}}class u extends HTMLElement{static searchDelay=800;search="";searchTimeout;connectedCallback(){this.hasAttribute("role")||this.setAttribute("role","menu"),this.hasAttribute("tabindex")||this.setAttribute("tabindex","-1"),this.items.forEach((t=>{t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1")})),this.addEventListener("keydown",this.onKeyDown)}disconnectedCallback(){this.removeEventListener("keydown",this.onKeyDown)}get items(){return Array.from(this.querySelectorAll("[role^=menuitem]")).filter((t=>n(t)))}onKeyDown=t=>{if(!this.hidden)return"ArrowUp"===t.key?(this.navigate(-1),void t.preventDefault()):"ArrowDown"===t.key?(this.navigate(1),void t.preventDefault()):void(t.key.length>1||t.ctrlKey||t.metaKey||t.altKey||(this.search+=t.key.toLowerCase(),t.preventDefault(),clearTimeout(this.searchTimeout),this.searchTimeout=window.setTimeout((()=>{this.search=""}),u.searchDelay),this.items.some((t=>{if(0===t.textContent?.trim().toLowerCase().indexOf(this.search))return t.focus(),!0}))))};navigate(t){const e=this.items;let i=(document.activeElement instanceof HTMLElement?e.indexOf(document.activeElement):-1)+t;i<0&&(i=e.length-1),i>=e.length&&(i=0),e[i]?.focus()}}class p extends HTMLElement{static attention=t=>t.animate([{transform:"scale(1)"},{transform:"scale(1.1)"},{transform:"scale(1)"}],300);static get observedAttributes(){return["open"]}trigger;focusTrap;constructor(){super();const t=document.createElement("template");t.innerHTML='\n <div part="backdrop" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0"></div>\n <div part="content" style="z-index: 1"><slot></slot></div>\n ';const e=this.attachShadow({mode:"open"});e.appendChild(t.content.cloneNode(!0)),this.backdrop.addEventListener("click",(()=>{this.hasAttribute("static")?p.attention?.(e.children[1]):this.close()})),this.focusTrap=r(this,{escapeDeactivates:!1,allowOutsideClick:!0})}connectedCallback(){this.content?.hasAttribute("role")||this.content?.setAttribute("role","dialog"),this.content?.hasAttribute("aria-modal")||this.content?.setAttribute("aria-modal","true"),this.content?.hasAttribute("tabindex")||this.content?.setAttribute("tabindex","-1"),this.addEventListener("keydown",(t=>{"Escape"===t.key&&this.open&&(t.preventDefault(),t.stopPropagation(),this.close())})),this.open&&this.focusContent()}get open(){return this.hasAttribute("open")}set open(t){t?this.setAttribute("open",""):this.removeAttribute("open")}close(){if(!this.open)return;const t=new Event("beforeclose",{cancelable:!0});this.dispatchEvent(t)&&(this.open=!1)}attributeChangedCallback(t,e,i){"open"===t&&(null!==i?this.wasOpened():this.wasClosed())}wasOpened(){this.trigger=document.activeElement,this.hidden=!1,e(this),this.focusTrap.activate(),this.focusContent(),this.dispatchEvent(new Event("open"))}wasClosed(){this.focusTrap.deactivate(),this.trigger?.focus(),i(this,{finish:()=>this.hidden=!0}),this.dispatchEvent(new Event("close"))}focusContent(){const t=this.querySelector("[autofocus]");t?t.focus():this.content?.focus()}get backdrop(){return this.shadowRoot?.firstElementChild}get content(){return this.firstElementChild}}class m extends HTMLElement{static get observedAttributes(){return["open"]}constructor(){super();const t=document.createElement("template");t.innerHTML='\n <div part="backdrop" hidden style="position: fixed; top: 0; left: 0; right: 0; bottom: 0"></div>\n <slot></slot>\n ';this.attachShadow({mode:"open"}).appendChild(t.content.cloneNode(!0)),this.backdrop.onclick=()=>this.open=!1}connectedCallback(){this.backdrop.hidden=!0,this.content.hidden=!0,this.open=!1,this.button.setAttribute("aria-expanded","false"),setTimeout((()=>{"menu"===this.content.getAttribute("role")&&this.button.setAttribute("aria-haspopup","true")})),this.button.addEventListener("click",(()=>{this.open=!this.open})),this.button.addEventListener("keydown",(t=>{"ArrowDown"===t.key&&(t.preventDefault(),this.open=!0,o(this.content)[0]?.focus())})),this.addEventListener("keydown",(t=>{"Escape"===t.key&&this.open&&(t.preventDefault(),t.stopPropagation(),this.open=!1,this.button.focus())})),this.addEventListener("focusout",(t=>{t.relatedTarget instanceof Node&&this.contains(t.relatedTarget)||(this.open=!1)})),this.content.addEventListener("click",(t=>{t.target instanceof Element&&t.target.closest("[role=menuitem], [role=menuitemradio]")&&(this.open=!1,this.button.focus())}))}disconnectedCallback(){s(this.backdrop),s(this.content)}get open(){return this.hasAttribute("open")}set open(t){t?this.setAttribute("open",""):this.removeAttribute("open")}attributeChangedCallback(t,e,i){"open"===t&&(null!==i?this.wasOpened():this.wasClosed())}wasOpened(){if(!this.content.hidden)return;this.content.hidden=!1,this.backdrop.hidden=!1,e(this.content),e(this.backdrop),this.button.setAttribute("aria-expanded","true"),this.content.style.position="absolute",a(this.button,this.content,{placement:this.getAttribute("placement")||"bottom",middleware:[h(),d(),c({apply:({width:t,height:e})=>{Object.assign(this.content.style,{maxWidth:"",maxHeight:""});const i=getComputedStyle(this.content);(!i.maxWidth||t<parseInt(i.maxWidth))&&(this.content.style.maxWidth=`${t}px`),(!i.maxHeight||e<parseInt(i.maxHeight))&&(this.content.style.maxHeight=`${e}px`)}})]}).then((({x:t,y:e,placement:i})=>{Object.assign(this.content.style,{left:`${t}px`,top:`${e}px`}),this.content.dataset.placement=i}));const t=this.content.querySelector("[autofocus]");t?t.focus():this.content.focus(),this.dispatchEvent(new Event("open"))}wasClosed(){this.content.hidden||(this.button.setAttribute("aria-expanded","false"),i(this.backdrop,{finish:()=>this.backdrop.hidden=!0}),i(this.content,{finish:()=>this.content.hidden=!0}),this.dispatchEvent(new Event("close")))}get backdrop(){return this.shadowRoot?.firstElementChild}get button(){return this.children[0]}get content(){return this.children[1]}}class b extends HTMLElement{connectedCallback(){this.hasAttribute("role")||this.setAttribute("role","toolbar"),this.setAttribute("tabindex","0"),this.addEventListener("focus",this.onInitialFocus,{once:!0}),this.addEventListener("keydown",this.onKeyDown)}disconnectedCallback(){this.removeEventListener("keydown",this.onKeyDown)}onInitialFocus=t=>{this.removeAttribute("tabindex"),this.focusControlAtIndex(0)};onKeyDown=t=>{if("ArrowRight"!==t.key&&"ArrowLeft"!==t.key&&"Home"!==t.key&&"End"!==t.key)return;const e=this.controls,i=this.controls.length,s=e.indexOf(t.target);if(-1===s)return;let n=0;"ArrowLeft"===t.key&&(n=s-1),"ArrowRight"===t.key&&(n=s+1),"End"===t.key&&(n=i-1),n<0&&(n=i-1),n>i-1&&(n=0),this.focusControlAtIndex(n),t.preventDefault()};focusControlAtIndex(t){this.controls.forEach(((e,i)=>{i===t?(e.setAttribute("tabindex","0"),e.focus()):e.setAttribute("tabindex","-1")}))}get controls(){return o(this)}}class v extends HTMLElement{static delay=100;static placement="top";static tooltipClass="tooltip";parent;tooltip;timeout;observer;showing=!1;onMouseEnter=this.afterDelay.bind(this,this.show);onFocus=this.show.bind(this);onMouseLeave=this.afterDelay.bind(this,this.hide);onBlur=this.hide.bind(this);connectedCallback(){this.parent=this.parentNode,this.parent&&(this.parent.addEventListener("mouseenter",this.onMouseEnter),this.parent.addEventListener("focus",this.onFocus),this.parent.addEventListener("mouseleave",this.onMouseLeave),this.parent.addEventListener("blur",this.onBlur),this.parent.addEventListener("click",this.onBlur),this.observer=new MutationObserver((t=>{t.forEach((t=>{"disabled"===t.attributeName&&this.hide()}))})),this.observer.observe(this.parent,{attributes:!0})),document.addEventListener("keydown",this.onKeyDown),document.addEventListener("scroll",this.onBlur)}disconnectedCallback(){this.tooltip&&(this.tooltip.remove(),this.tooltip=void 0),this.parent&&(this.parent.removeEventListener("mouseenter",this.onMouseEnter),this.parent.removeEventListener("focus",this.onFocus),this.parent.removeEventListener("mouseleave",this.onMouseLeave),this.parent.removeEventListener("blur",this.onBlur),this.parent.removeEventListener("click",this.onBlur),this.parent=void 0),document.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("scroll",this.onBlur),this.observer.disconnect()}get disabled(){return this.hasAttribute("disabled")}set disabled(t){t?this.setAttribute("disabled",""):this.removeAttribute("disabled")}onKeyDown=t=>{"Escape"===t.key&&this.hide()};show(){if(this.disabled)return;const t=this.createTooltip();clearTimeout(this.timeout),this.showing||(t.hidden=!1,e(t),this.showing=!0),t.innerHTML!==this.innerHTML&&(t.innerHTML=this.innerHTML),t.style.position="absolute",a(this.parent,t,{placement:this.getAttribute("placement")||v.placement,middleware:[h(),d()]}).then((({x:e,y:i,placement:s})=>{Object.assign(t.style,{left:`${e}px`,top:`${i}px`}),t.dataset.placement=s}))}hide(){clearTimeout(this.timeout),this.showing&&(this.showing=!1,i(this.tooltip,{finish:()=>{this.tooltip&&(this.tooltip.hidden=!0)}}))}afterDelay(t){clearTimeout(this.timeout);const e=parseInt(this.getAttribute("delay")||"");this.timeout=window.setTimeout(t.bind(this),isNaN(e)?v.delay:e)}createTooltip(){return this.tooltip||(this.tooltip=document.createElement("div"),this.tooltip.className=this.getAttribute("tooltip-class")||v.tooltipClass,this.tooltip.hidden=!0,this.tooltip.addEventListener("mouseenter",this.show.bind(this)),this.tooltip.addEventListener("mouseleave",this.afterDelay.bind(this,this.hide)),document.body.appendChild(this.tooltip)),this.tooltip}}export{l as AlertsElement,u as MenuElement,p as ModalElement,m as PopupElement,b as ToolbarElement,v as TooltipElement};