UNPKG

vue-a11y-directives

Version:

A comprehensive set of Vue 3 directives for building accessible web applications with WCAG compliance

431 lines (430 loc) 17.1 kB
const K = ["BUTTON", "A", "INPUT", "TEXTAREA", "SELECT", "IFRAME"], H = { mounted(e, a) { const t = typeof a.value == "object" ? a.value : { delay: a.value || 0 }, n = t.delay || 0, i = t.select || !1; K.includes(e.tagName) || e.hasAttribute("tabindex") || e.hasAttribute("contenteditable") || (e.setAttribute("tabindex", "0"), e.__a11yFocusAddedTabindex = !0), setTimeout(() => { e.focus && (e.focus(), i && (e.tagName === "INPUT" || e.tagName === "TEXTAREA") && e.select()); }, n); }, unmounted(e) { e.__a11yFocusAddedTabindex && (e.removeAttribute("tabindex"), delete e.__a11yFocusAddedTabindex); } }; function m(e) { if (!e) return []; const a = [ "a[href]", "button:not([disabled])", "textarea:not([disabled])", 'input:not([disabled]):not([type="hidden"])', "select:not([disabled])", '[tabindex]:not([tabindex="-1"])', '[contenteditable="true"]', "audio[controls]", "video[controls]" ]; return Array.from(e.querySelectorAll(a.join(", "))).filter((n) => { const i = window.getComputedStyle(n); return n.offsetWidth > 0 && n.offsetHeight > 0 && i.visibility !== "hidden" && i.display !== "none"; }); } function T(e, a) { Object.entries(a).forEach(([t, n]) => { n != null && e.setAttribute(t, n); }); } function $(e, a) { a.forEach((t) => { e.removeAttribute(t); }); } function F(e, a = "polite") { const t = document.createElement("div"); t.setAttribute("role", "status"), t.setAttribute("aria-live", a), t.setAttribute("aria-atomic", "true"), t.style.position = "absolute", t.style.left = "-10000px", t.style.width = "1px", t.style.height = "1px", t.style.overflow = "hidden", document.body.appendChild(t), setTimeout(() => { t.textContent = e; }, 100), setTimeout(() => { document.body.removeChild(t); }, 3e3); } function J(e) { const a = m(e); return a.length > 0 ? a[0] : null; } function G(e) { const a = m(e); return a.length > 0 ? a[a.length - 1] : null; } function z(e) { return e.contains(document.activeElement); } function Q() { return document.activeElement; } function Y(e) { e && e.focus && e.focus(); } function Z(e = "a11y") { return `${e}-${Math.random().toString(36).substr(2, 9)}`; } const C = { mounted(e, a) { const t = a.value || {}, n = t.autoFocus !== !1, i = t.onEscape || null, r = document.activeElement; e.__previousFocus = r, n && setTimeout(() => { const u = m(e); u.length > 0 && u[0].focus(); }, 100); const o = (u) => { if (u.key !== "Tab") return; const y = m(e); if (y.length === 0) return; const k = y[0], h = y[y.length - 1]; u.shiftKey && document.activeElement === k ? (u.preventDefault(), h.focus()) : !u.shiftKey && document.activeElement === h && (u.preventDefault(), k.focus()); }, d = (u) => { u.key === "Escape" && i && (u.preventDefault(), u.stopPropagation(), i()); }; e.addEventListener("keydown", o), e.__handleKeydown = o, i && (document.addEventListener("keydown", d, !0), e.__handleEscape = d); }, unmounted(e) { e.__handleKeydown && (e.removeEventListener("keydown", e.__handleKeydown), delete e.__handleKeydown), e.__handleEscape && (document.removeEventListener("keydown", e.__handleEscape, !0), delete e.__handleEscape), e.__previousFocus && (setTimeout(() => { if (e.__previousFocus && document.body.contains(e.__previousFocus) && typeof e.__previousFocus.focus == "function") try { e.__previousFocus.focus(); } catch (a) { console.warn("Could not restore focus:", a); } }, 0), delete e.__previousFocus); } }, N = { mounted(e, a) { const t = a.value || {}; console.log("Keyboard directive mounted with config:", t); const n = (i) => { console.log("Key pressed:", i.key, "Config:", t); const r = { Enter: "enter", " ": "space", Escape: "escape", ArrowUp: "arrowUp", ArrowDown: "arrowDown", ArrowLeft: "arrowLeft", ArrowRight: "arrowRight", Tab: "tab", Delete: "delete", Backspace: "backspace" }; let o = ""; (i.ctrlKey || i.metaKey) && (o += "ctrl+"), i.altKey && (o += "alt+"), i.shiftKey && (o += "shift+"), o += i.key.toLowerCase(); const d = r[i.key]; if (d && typeof t[d] == "function") { i.preventDefault(), t[d](i); return; } if (t[o] && typeof t[o] == "function") { i.preventDefault(), t[o](i); return; } if (t[i.key] && typeof t[i.key] == "function") { i.preventDefault(), t[i.key](i); return; } if (t.arrows && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(i.key) && typeof t.arrows == "function") { i.preventDefault(), t.arrows(i); return; } if (t.handlers) { if (t.handlers[i.key] && typeof t.handlers[i.key] == "function") { i.preventDefault(), t.handlers[i.key](i); return; } if (t.handlers[o] && typeof t.handlers[o] == "function") { i.preventDefault(), t.handlers[o](i); return; } } if (t.custom && typeof t.custom == "function") { t.custom(i); return; } t.enter === !0 && i.key === "Enter" && !["INPUT", "TEXTAREA", "SELECT"].includes(i.target.tagName) && (i.preventDefault(), e.click && e.click()), t.space === !0 && i.key === " " && ["BUTTON", "A"].includes(e.tagName) && (i.preventDefault(), e.click && e.click()); }; e.addEventListener("keydown", n), e.__keyboardHandler = n; }, unmounted(e) { e.__keyboardHandler && (e.removeEventListener("keydown", e.__keyboardHandler), delete e.__keyboardHandler); } }, q = { mounted(e, a) { if (!a.value) return; const t = typeof a.value == "string" ? { message: a.value, priority: "polite" } : a.value; t && t.message && F(t.message, t.priority || "polite"); }, updated(e, a) { if (a.value !== a.oldValue && a.value) { const t = typeof a.value == "string" ? { message: a.value, priority: "polite" } : a.value; t && t.message && F(t.message, t.priority || "polite"); } } }, U = { mounted(e, a) { const t = a.value, n = (i) => { i.preventDefault(); const r = document.querySelector(t); r && (r.hasAttribute("tabindex") || r.setAttribute("tabindex", "-1"), r.focus(), r.scrollIntoView({ behavior: "smooth", block: "start" })); }; e.addEventListener("click", n), e.__skipLinkHandler = n, e.setAttribute("role", "link"), t && e.setAttribute("aria-label", `Skip to ${t.replace("#", "")}`); }, unmounted(e) { e.__skipLinkHandler && (e.removeEventListener("click", e.__skipLinkHandler), delete e.__skipLinkHandler); } }, R = { mounted(e, a) { O(e, a); }, updated(e, a) { O(e, a); }, unmounted(e) { delete e.__a11ySkipOriginalTabIndex, delete e.__a11ySkipOriginalAriaHidden, delete e.__a11ySkipChildrenState; } }; function O(e, a) { if (a.value === void 0 ? !0 : !!a.value) { e.__a11ySkipOriginalTabIndex === void 0 && (e.__a11ySkipOriginalTabIndex = e.getAttribute("tabindex")), e.__a11ySkipOriginalAriaHidden === void 0 && (e.__a11ySkipOriginalAriaHidden = e.getAttribute("aria-hidden")), e.setAttribute("tabindex", "-1"), e.setAttribute("aria-hidden", "true"), e.setAttribute("data-a11y-skip", "true"); const n = e.querySelectorAll( 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])' ); e.__a11ySkipChildrenState || (e.__a11ySkipChildrenState = []), n.forEach((i, r) => { e.__a11ySkipChildrenState[r] || (e.__a11ySkipChildrenState[r] = { tabIndex: i.getAttribute("tabindex"), disabled: i.disabled }), i.setAttribute("tabindex", "-1"), "disabled" in i && (i.disabled = !0); }), a.modifiers.noInteraction && (e.style.pointerEvents = "none"), a.modifiers.visual && (e.style.opacity = "0.5", e.style.filter = "grayscale(100%)", e.style.pointerEvents = "none"); } else { e.__a11ySkipOriginalTabIndex !== void 0 ? e.__a11ySkipOriginalTabIndex === null ? e.removeAttribute("tabindex") : e.setAttribute("tabindex", e.__a11ySkipOriginalTabIndex) : e.removeAttribute("tabindex"), e.__a11ySkipOriginalAriaHidden !== void 0 ? e.__a11ySkipOriginalAriaHidden === null ? e.removeAttribute("aria-hidden") : e.setAttribute("aria-hidden", e.__a11ySkipOriginalAriaHidden) : e.removeAttribute("aria-hidden"), e.removeAttribute("data-a11y-skip"); const n = e.querySelectorAll( "a, button, input, select, textarea, [tabindex]" ); e.__a11ySkipChildrenState && n.forEach((i, r) => { const o = e.__a11ySkipChildrenState[r]; o && (o.tabIndex === null ? i.removeAttribute("tabindex") : i.setAttribute("tabindex", o.tabIndex), "disabled" in i && (i.disabled = o.disabled)); }), a.modifiers.noInteraction && (e.style.pointerEvents = ""), a.modifiers.visual && (e.style.opacity = "", e.style.filter = "", e.style.pointerEvents = ""); } } const j = { mounted(e, a) { const t = a.value || {}, n = { label: "aria-label", labelledby: "aria-labelledby", describedby: "aria-describedby", expanded: "aria-expanded", pressed: "aria-pressed", selected: "aria-selected", checked: "aria-checked", disabled: "aria-disabled", hidden: "aria-hidden", invalid: "aria-invalid", required: "aria-required", live: "aria-live", atomic: "aria-atomic", busy: "aria-busy", controls: "aria-controls", owns: "aria-owns", haspopup: "aria-haspopup", level: "aria-level", modal: "aria-modal", multiselectable: "aria-multiselectable", orientation: "aria-orientation", placeholder: "aria-placeholder", readonly: "aria-readonly", relevant: "aria-relevant", valuemax: "aria-valuemax", valuemin: "aria-valuemin", valuenow: "aria-valuenow", valuetext: "aria-valuetext" }, i = {}; Object.entries(t).forEach(([r, o]) => { const d = n[r] || r; i[d] = o; }), T(e, i), e.__ariaAttributes = Object.keys(i); }, updated(e, a) { if (JSON.stringify(a.value) !== JSON.stringify(a.oldValue)) { e.__ariaAttributes && e.__ariaAttributes.forEach((r) => e.removeAttribute(r)); const t = a.value || {}, n = { label: "aria-label", labelledby: "aria-labelledby", describedby: "aria-describedby", expanded: "aria-expanded", pressed: "aria-pressed", selected: "aria-selected", checked: "aria-checked", disabled: "aria-disabled", hidden: "aria-hidden", invalid: "aria-invalid", required: "aria-required", live: "aria-live", atomic: "aria-atomic", busy: "aria-busy", controls: "aria-controls", owns: "aria-owns", haspopup: "aria-haspopup", level: "aria-level", modal: "aria-modal" }, i = {}; Object.entries(t).forEach(([r, o]) => { const d = n[r] || r; i[d] = o; }), T(e, i), e.__ariaAttributes = Object.keys(i); } }, unmounted(e) { e.__ariaAttributes && (e.__ariaAttributes.forEach((a) => e.removeAttribute(a)), delete e.__ariaAttributes); } }, M = { mounted(e, a) { const t = a.value || {}, n = t.delay || 100, i = { // Panel selectors (where the calendar appears) panel: t.panelSelector || ".el-picker-panel, .el-date-picker, .v-picker, .v-date-picker, .ant-picker-dropdown, .p-datepicker", // Selected day selectors (current selected date) selectedDay: t.selectedSelector || "td.is-selected, td.selected, td.is-today.is-selected, .v-date-picker-table__current, .ant-picker-cell-selected, .p-highlight", // Available day selectors (any selectable date) availableDay: t.availableSelector || "td:not(.disabled):not(.is-disabled):not([disabled]), .v-date-picker-table__events, .ant-picker-cell:not(.ant-picker-cell-disabled), .p-datepicker-calendar td:not(.p-disabled)", // Input selectors (the input field) input: t.inputSelector || ".el-input__inner, input, .v-text-field__input, .ant-picker-input, .p-inputtext" }; let r = !1, o = null, d = null; const u = (c) => { const _ = c.getAttribute("aria-controls"); let s = null; if (_ && (s = document.getElementById(_)), !s) { const f = document.querySelectorAll(".el-picker-panel"); for (const v of f) if (v.offsetParent !== null) { s = v; break; } } if (!s) return !1; let l = null; const g = s.querySelector("td.is-selected"); if (g && (l = g), !l) { const f = s.querySelector("td.is-today"); f && (l = f); } if (!l) { const f = s.querySelector(".el-date-table tbody td:not(.disabled):not(.is-disabled)"); f && (l = f); } if (!l) return !1; l.setAttribute("tabindex", "0"); let w = 0; const L = 10, S = () => { if (w++, l.focus({ preventScroll: !0 }), document.activeElement === l) return !0; w < L && setTimeout(S, 10); }; S(); const x = (f) => { if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(f.key)) { f.preventDefault(), s.__a11yNavigatingWithKeyboard = !0; const v = (b) => (b.preventDefault(), b.stopPropagation(), b.stopImmediatePropagation(), !1), D = ["click", "mousedown", "mouseup", "pointerdown", "pointerup", "touchstart", "touchend"]; D.forEach((b) => { s.addEventListener(b, v, { capture: !0, once: !0 }); }), s.querySelectorAll("td").forEach((b) => { D.forEach((I) => { b.addEventListener(I, v, { capture: !0, once: !0 }); }); }), setTimeout(() => { s.__a11yNavigatingWithKeyboard = !1; }, 50); } }; return s.__a11yKeyboardHandlerAdded || (s.__a11yKeyboardHandlerAdded = !0, s.addEventListener("keydown", x, { capture: !0 }), s.__a11yKeyboardHandler = x), !0; }, y = (c) => { d && clearTimeout(d), requestAnimationFrame(() => { d = setTimeout(() => { u(c); }, n); }); }, k = (c) => { o = new MutationObserver((_) => { const s = document.querySelector(".el-picker-panel"); s && !r ? (r = !0, y(c)) : !s && r && (r = !1, d && (clearTimeout(d), d = null)); }), o.observe(document.body, { childList: !0, subtree: !0 }); }, p = (() => { let c = e.querySelector(i.input); return c || (c = e.querySelectorAll("input")[0]), c || e; })(); k(p); const A = () => { }; p.addEventListener("focus", A), p.addEventListener("click", A); const E = (c) => { c.key === "Enter" || c.key; }; p.addEventListener("keydown", E); const P = (() => { const c = new MutationObserver((_) => { _.forEach((s) => { s.type === "attributes" && s.attributeName === "aria-expanded" && p.getAttribute("aria-expanded") === "true" && y(p); }); }); return c.observe(p, { attributes: !0, attributeFilter: ["aria-expanded"] }), c; })(); e.__a11yDatePickerAriaObserver = P, e.__a11yDatePickerInput = p, e.__a11yDatePickerFocusHandler = A, e.__a11yDatePickerKeyHandler = E, e.__a11yDatePickerObserver = o; }, unmounted(e) { if (e.__a11yDatePickerTimeout && clearTimeout(e.__a11yDatePickerTimeout), e.__a11yDatePickerInput) { const a = e.__a11yDatePickerInput; e.__a11yDatePickerFocusHandler && (a.removeEventListener("focus", e.__a11yDatePickerFocusHandler), a.removeEventListener("click", e.__a11yDatePickerFocusHandler)), e.__a11yDatePickerKeyHandler && a.removeEventListener("keydown", e.__a11yDatePickerKeyHandler); } e.__a11yDatePickerObserver && e.__a11yDatePickerObserver.disconnect(), e.__a11yDatePickerAriaObserver && e.__a11yDatePickerAriaObserver.disconnect(), delete e.__a11yDatePickerInput, delete e.__a11yDatePickerFocusHandler, delete e.__a11yDatePickerKeyHandler, delete e.__a11yDatePickerObserver, delete e.__a11yDatePickerAriaObserver, delete e.__a11yDatePickerTimeout; } }, B = { "a11y-focus": H, "a11y-trap-focus": C, "a11y-keyboard": N, "a11y-announce": q, "a11y-skip-link": U, "a11y-skip": R, "a11y-aria": j, "a11y-date-picker": M }; function W(e) { Object.entries(B).forEach(([a, t]) => { e.directive(a, t); }); } const ee = { install: W }; export { B as a11yDirectives, F as announce, q as announceDirective, j as ariaDirective, z as containsActiveElement, M as datePickerDirective, ee as default, H as focusDirective, Z as generateId, J as getFirstFocusable, m as getFocusableElements, G as getLastFocusable, W as installA11yDirectives, N as keyboardDirective, $ as removeAriaAttributes, Y as restoreFocus, Q as saveFocus, T as setAriaAttributes, R as skipDirective, U as skipLinkDirective, C as trapFocusDirective };