From 47250c3a904f7a4a64f5e19bc70b4d01df1fa078 Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Tue, 26 Apr 2022 20:06:44 +0400 Subject: [PATCH] chore: use internal aria-hidden impl --- examples/vue-ts/package.json | 1 - packages/machines/combobox/package.json | 3 +- .../machines/combobox/src/combobox.machine.ts | 21 +++++++++++-- .../machines/combobox/src/combobox.types.ts | 4 +++ packages/machines/dialog/package.json | 4 +-- .../machines/dialog/src/dialog.machine.ts | 9 ++---- packages/machines/popover/package.json | 4 +-- .../machines/popover/src/popover.machine.ts | 10 ++---- packages/utilities/aria-hidden/src/index.ts | 31 ++++++++++--------- yarn.lock | 9 +----- 10 files changed, 52 insertions(+), 44 deletions(-) diff --git a/examples/vue-ts/package.json b/examples/vue-ts/package.json index e647aa0d26..cd1ae7a223 100644 --- a/examples/vue-ts/package.json +++ b/examples/vue-ts/package.json @@ -36,7 +36,6 @@ "@emotion/css", "@floating-ui/dom", "@vue/runtime-core", - "aria-hidden", "epic-spinners", "focus-trap", "form-serialize", diff --git a/packages/machines/combobox/package.json b/packages/machines/combobox/package.json index 033dfcd23b..2880867bc8 100644 --- a/packages/machines/combobox/package.json +++ b/packages/machines/combobox/package.json @@ -32,6 +32,7 @@ "@zag-js/core": "^0.0.0", "@zag-js/popper": "^0.0.0", "@zag-js/dom-utils": "^0.0.0", + "@zag-js/aria-hidden": "^0.0.0", "@zag-js/types": "^0.0.0", "scroll-into-view-if-needed": "^2.2.28" }, @@ -44,4 +45,4 @@ "test:ci": "yarn test --ci --runInBand", "test:watch": "yarn test --watch --updateSnapshot" } -} \ No newline at end of file +} diff --git a/packages/machines/combobox/src/combobox.machine.ts b/packages/machines/combobox/src/combobox.machine.ts index 71d0687785..2423bf5ba7 100644 --- a/packages/machines/combobox/src/combobox.machine.ts +++ b/packages/machines/combobox/src/combobox.machine.ts @@ -1,3 +1,4 @@ +import { ariaHidden } from "@zag-js/aria-hidden" import { createMachine, guards, ref } from "@zag-js/core" import { createLiveRegion, @@ -22,6 +23,7 @@ export function machine(ctx: UserDefinedContext = {}) { uid: "", loop: true, openOnClick: false, + ariaHidden: true, activeId: null, activeOptionData: null, inputValue: "", @@ -165,7 +167,13 @@ export function machine(ctx: UserDefinedContext = {}) { suggesting: { tags: ["open", "focused"], - activities: ["trackPointerDown", "scrollOptionIntoView", "computePlacement", "trackOptionNodes"], + activities: [ + "trackPointerDown", + "scrollOptionIntoView", + "computePlacement", + "trackOptionNodes", + "ariaHideOutside", + ], entry: ["focusInput", "invokeOnOpen"], on: { ARROW_DOWN: { @@ -226,7 +234,7 @@ export function machine(ctx: UserDefinedContext = {}) { interacting: { tags: ["open", "focused"], - activities: ["scrollOptionIntoView", "trackPointerDown", "computePlacement"], + activities: ["scrollOptionIntoView", "trackPointerDown", "computePlacement", "ariaHideOutside"], entry: "focusMatchingOption", on: { ARROW_DOWN: [ @@ -319,6 +327,10 @@ export function machine(ctx: UserDefinedContext = {}) { }, activities: { + ariaHideOutside(ctx) { + if (!ctx.ariaHidden) return + return ariaHidden([dom.getInputEl(ctx), dom.getListboxEl(ctx), dom.getToggleBtnEl(ctx)]) + }, computePlacement(ctx) { return getPlacement(dom.getControlEl(ctx), dom.getPositionerEl(ctx), { placement: "bottom", @@ -367,7 +379,10 @@ export function machine(ctx: UserDefinedContext = {}) { if (evt.doc) ctx.doc = ref(evt.doc) ctx.uid = evt.id nextTick(() => { - ctx.liveRegion = createLiveRegion({ level: "assertive", document: ctx.doc }) + ctx.liveRegion = createLiveRegion({ + level: "assertive", + document: ctx.doc, + }) }) }, setActiveId(ctx, evt) { diff --git a/packages/machines/combobox/src/combobox.types.ts b/packages/machines/combobox/src/combobox.types.ts index 08d486c432..7e7375262c 100644 --- a/packages/machines/combobox/src/combobox.types.ts +++ b/packages/machines/combobox/src/combobox.types.ts @@ -100,6 +100,10 @@ type PublicContext = DirectionProperty & { * Whether to allow custom values or free values in the input */ allowCustomValue?: boolean + /** + * Whether to hide all elements besides the combobox parts. Useful for accessibility + */ + ariaHidden?: boolean /** * Function called to validate the input value */ diff --git a/packages/machines/dialog/package.json b/packages/machines/dialog/package.json index e0d8b45497..b918d78e74 100644 --- a/packages/machines/dialog/package.json +++ b/packages/machines/dialog/package.json @@ -32,7 +32,7 @@ "@zag-js/core": "^0.0.0", "@zag-js/dom-utils": "^0.0.0", "@zag-js/types": "^0.0.0", - "aria-hidden": "^1.1.3", + "@zag-js/aria-hidden": "^0.0.0", "focus-trap": "^6.7.1", "scroll-into-view-if-needed": "^2.2.28" }, @@ -45,4 +45,4 @@ "test:ci": "yarn test --ci --runInBand", "test:watch": "yarn test --watch --updateSnapshot" } -} \ No newline at end of file +} diff --git a/packages/machines/dialog/src/dialog.machine.ts b/packages/machines/dialog/src/dialog.machine.ts index 644b92f82d..e7989a6aee 100644 --- a/packages/machines/dialog/src/dialog.machine.ts +++ b/packages/machines/dialog/src/dialog.machine.ts @@ -1,7 +1,7 @@ import { createMachine, guards, ref, subscribe } from "@zag-js/core" import { addDomEvent, nextTick, preventBodyScroll, trackPointerDown } from "@zag-js/dom-utils" import { runIfFn } from "@zag-js/utils" -import { hideOthers } from "aria-hidden" +import { ariaHidden } from "@zag-js/aria-hidden" import { createFocusTrap, FocusTrap } from "focus-trap" import { dom } from "./dialog.dom" import { store } from "./dialog.store" @@ -126,12 +126,9 @@ export function machine(ctx: UserDefinedContext = {}) { } }, hideContentBelow(ctx) { - let unhide: VoidFunction + let unhide: VoidFunction | undefined nextTick(() => { - const el = dom.getUnderlayEl(ctx) - try { - unhide = hideOthers(el) - } catch {} + unhide = ariaHidden([dom.getUnderlayEl(ctx)]) }) return () => unhide?.() }, diff --git a/packages/machines/popover/package.json b/packages/machines/popover/package.json index 2958c94222..75773e0991 100644 --- a/packages/machines/popover/package.json +++ b/packages/machines/popover/package.json @@ -34,7 +34,7 @@ "@zag-js/types": "^0.0.0", "@zag-js/popper": "^0.0.0", "@zag-js/utils": "^0.0.0", - "aria-hidden": "^1.1.3", + "@zag-js/aria-hidden": "^0.0.0", "focus-trap": "^6.7.1" }, "scripts": { @@ -46,4 +46,4 @@ "test:ci": "yarn test --ci --runInBand", "test:watch": "yarn test --watch --updateSnapshot" } -} \ No newline at end of file +} diff --git a/packages/machines/popover/src/popover.machine.ts b/packages/machines/popover/src/popover.machine.ts index c33570337e..d50a7257a2 100644 --- a/packages/machines/popover/src/popover.machine.ts +++ b/packages/machines/popover/src/popover.machine.ts @@ -9,7 +9,7 @@ import { } from "@zag-js/dom-utils" import { getPlacement } from "@zag-js/popper" import { next, runIfFn } from "@zag-js/utils" -import { hideOthers } from "aria-hidden" +import { ariaHidden } from "@zag-js/aria-hidden" import { createFocusTrap, FocusTrap } from "focus-trap" import { dom } from "./popover.dom" import { MachineContext, MachineState, UserDefinedContext } from "./popover.types" @@ -146,13 +146,9 @@ export function machine(ctx: UserDefinedContext = {}) { }, hideContentBelow(ctx) { if (!ctx.modal) return - let unhide: VoidFunction + let unhide: VoidFunction | undefined nextTick(() => { - const el = dom.getContentEl(ctx) - if (!el) return - try { - unhide = hideOthers(el) - } catch {} + unhide = ariaHidden([dom.getContentEl(ctx), dom.getTriggerEl(ctx)]) }) return () => unhide?.() }, diff --git a/packages/utilities/aria-hidden/src/index.ts b/packages/utilities/aria-hidden/src/index.ts index 9f07dfad6d..d09bb249b0 100644 --- a/packages/utilities/aria-hidden/src/index.ts +++ b/packages/utilities/aria-hidden/src/index.ts @@ -1,24 +1,27 @@ // Credits: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/ariaHideOutside.ts -let map = new WeakMap() +const elementCountMap = new WeakMap() -export type AriaHiddenOptions = { - exclude: HTMLElement[] - document: Document - root?: HTMLElement +function isLiveRegion(node: Node, win: typeof window): node is HTMLElement { + return node instanceof win.HTMLElement && node.dataset.liveAnnouncer === "true" } -export function ariaHidden(options: AriaHiddenOptions) { - const { exclude, document: doc, root = doc.body } = options +export function ariaHidden(targets: Array, rootEl?: HTMLElement) { + const exclude = targets.filter(Boolean) as HTMLElement[] + if (exclude.length === 0) return + + const doc = exclude[0].ownerDocument || document const win = doc.defaultView ?? window const visibleNodes = new Set(exclude) const hiddenNodes = new Set() + const root = rootEl ?? doc.body + const walker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, { acceptNode(node) { // If this node is a live announcer, add it to the set of nodes to keep visible. - if (node instanceof win.HTMLElement && node.dataset.liveAnnouncer === "true") { + if (isLiveRegion(node, win)) { visibleNodes.add(node) } @@ -44,7 +47,7 @@ export function ariaHidden(options: AriaHiddenOptions) { }) const hide = (node: Element) => { - let refCount = map.get(node) ?? 0 + let refCount = elementCountMap.get(node) ?? 0 // If already aria-hidden, and the ref count is zero, then this element // was already hidden and there's nothing for us to do. @@ -57,7 +60,7 @@ export function ariaHidden(options: AriaHiddenOptions) { } hiddenNodes.add(node) - map.set(node, refCount + 1) + elementCountMap.set(node, refCount + 1) } let node = walker.nextNode() as Element @@ -76,7 +79,7 @@ export function ariaHidden(options: AriaHiddenOptions) { if (![...visibleNodes, ...hiddenNodes].some((node) => node.contains(change.target))) { //@ts-expect-error for (const node of change.addedNodes) { - if (node instanceof win.HTMLElement && node.dataset.liveAnnouncer === "true") { + if (isLiveRegion(node, win)) { visibleNodes.add(node) } else if (node instanceof win.Element) { hide(node) @@ -92,16 +95,16 @@ export function ariaHidden(options: AriaHiddenOptions) { observer.disconnect() for (let node of hiddenNodes) { - let count = map.get(node) + let count = elementCountMap.get(node) if (count === 1) { node.removeAttribute("aria-hidden") - map.delete(node) + elementCountMap.delete(node) continue } if (count !== undefined) { - map.set(node, count - 1) + elementCountMap.set(node, count - 1) } } } diff --git a/yarn.lock b/yarn.lock index 2c4454a6e0..3bd6492d37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2613,13 +2613,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -aria-hidden@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254" - integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA== - dependencies: - tslib "^1.0.0" - aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -9838,7 +9831,7 @@ tslib@2.3.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@^1.0.0, tslib@^1.8.1: +tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==