Socket
Socket
Sign inDemoInstall

@zag-js/aria-hidden

Package Overview
Dependencies
Maintainers
1
Versions
686
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@zag-js/aria-hidden - npm Package Compare versions

Comparing version 0.65.1 to 0.66.0

11

dist/index.d.ts

@@ -1,10 +0,9 @@

interface AriaHiddenOptions {
rootEl?: HTMLElement;
defer?: boolean;
}
type MaybeElement = HTMLElement | null;
type Targets = Array<MaybeElement>;
type TargetsOrFn = Targets | (() => Targets);
declare function ariaHidden(targetsOrFn: TargetsOrFn, options?: AriaHiddenOptions): () => void;
type Options = {
defer?: boolean;
};
declare function ariaHidden(targetsOrFn: TargetsOrFn, options?: Options): () => void;
export { type AriaHiddenOptions, ariaHidden };
export { ariaHidden };

@@ -26,113 +26,10 @@ "use strict";

module.exports = __toCommonJS(src_exports);
var import_dom_query = require("@zag-js/dom-query");
var refCountMap = /* @__PURE__ */ new WeakMap();
var observerStack = [];
function ariaHiddenImpl(targets, options = {}) {
const { rootEl } = options;
const exclude = targets.filter(Boolean);
if (exclude.length === 0) return;
const doc = exclude[0].ownerDocument || document;
const win = doc.defaultView ?? window;
const visibleNodes = new Set(exclude);
const hiddenNodes = /* @__PURE__ */ new Set();
const root = rootEl ?? doc.body;
let walk = (root2) => {
for (let element of root2.querySelectorAll("[data-live-announcer], [data-zag-top-layer]")) {
visibleNodes.add(element);
}
let acceptNode = (node) => {
if (visibleNodes.has(node) || hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute("role") !== "row") {
return NodeFilter.FILTER_REJECT;
}
for (let target of visibleNodes) {
if (node.contains(target)) {
return NodeFilter.FILTER_SKIP;
}
}
return NodeFilter.FILTER_ACCEPT;
};
let walker = doc.createTreeWalker(root2, NodeFilter.SHOW_ELEMENT, { acceptNode });
let acceptRoot = acceptNode(root2);
if (acceptRoot === NodeFilter.FILTER_ACCEPT) {
hide(root2);
}
if (acceptRoot !== NodeFilter.FILTER_REJECT) {
let node = walker.nextNode();
while (node != null) {
hide(node);
node = walker.nextNode();
}
}
};
let hide = (node) => {
let refCount = refCountMap.get(node) ?? 0;
if (node.getAttribute("aria-hidden") === "true" && refCount === 0) {
return;
}
if (refCount === 0) {
node.setAttribute("aria-hidden", "true");
}
hiddenNodes.add(node);
refCountMap.set(node, refCount + 1);
};
if (observerStack.length) {
observerStack[observerStack.length - 1].disconnect();
}
walk(root);
const observer = new win.MutationObserver((changes) => {
for (let change of changes) {
if (change.type !== "childList" || change.addedNodes.length === 0) {
continue;
}
if (![...visibleNodes, ...hiddenNodes].some((node) => node.contains(change.target))) {
for (let node of change.removedNodes) {
if (node instanceof win.Element) {
visibleNodes.delete(node);
hiddenNodes.delete(node);
}
}
for (let node of change.addedNodes) {
if ((node instanceof win.HTMLElement || node instanceof win.SVGElement) && (node.dataset.liveAnnouncer === "true" || node.dataset.zagTopLayer === "true")) {
visibleNodes.add(node);
} else if (node instanceof win.Element) {
walk(node);
}
}
}
}
});
observer.observe(root, { childList: true, subtree: true });
let observerWrapper = {
observe() {
observer.observe(root, { childList: true, subtree: true });
},
disconnect() {
observer.disconnect();
}
};
observerStack.push(observerWrapper);
return () => {
observer.disconnect();
for (let node of hiddenNodes) {
let count = refCountMap.get(node);
if (count === 1) {
node.removeAttribute("aria-hidden");
refCountMap.delete(node);
} else {
refCountMap.set(node, count - 1);
}
}
if (observerWrapper === observerStack[observerStack.length - 1]) {
observerStack.pop();
if (observerStack.length) {
observerStack[observerStack.length - 1].observe();
}
} else {
observerStack.splice(observerStack.indexOf(observerWrapper), 1);
}
};
}
var import_aria_hidden = require("aria-hidden");
var raf = (fn) => {
const frameId = requestAnimationFrame(() => fn());
return () => cancelAnimationFrame(frameId);
};
function ariaHidden(targetsOrFn, options = {}) {
const { defer } = options;
const func = defer ? import_dom_query.raf : (v) => v();
const { defer = true } = options;
const func = defer ? raf : (v) => v();
const cleanups = [];

@@ -142,3 +39,4 @@ cleanups.push(

const targets = typeof targetsOrFn === "function" ? targetsOrFn() : targetsOrFn;
cleanups.push(ariaHiddenImpl(targets, options));
const elements = targets.filter(Boolean);
cleanups.push((0, import_aria_hidden.hideOthers)(elements));
})

@@ -145,0 +43,0 @@ );

{
"name": "@zag-js/aria-hidden",
"version": "0.65.1",
"version": "0.66.0",
"description": "Hide targets from screen readers",

@@ -28,3 +28,3 @@ "keywords": [

"dependencies": {
"@zag-js/dom-query": "0.65.1"
"aria-hidden": "1.2.4"
},

@@ -31,0 +31,0 @@ "devDependencies": {

@@ -1,10 +0,6 @@

// Credits: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/ariaHideOutside.ts
import { raf } from "@zag-js/dom-query"
import { hideOthers } from "aria-hidden"
const refCountMap = new WeakMap<Element, number>()
const observerStack: any[] = []
export interface AriaHiddenOptions {
rootEl?: HTMLElement
defer?: boolean
const raf = (fn: VoidFunction) => {
const frameId = requestAnimationFrame(() => fn())
return () => cancelAnimationFrame(frameId)
}

@@ -16,154 +12,8 @@

function ariaHiddenImpl(targets: Targets, options: AriaHiddenOptions = {}) {
const { rootEl } = options
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<Element>(exclude)
const hiddenNodes = new Set<Element>()
const root = rootEl ?? doc.body
let walk = (root: Element) => {
// Keep live announcer and top layer elements (e.g. toasts) visible.
for (let element of root.querySelectorAll("[data-live-announcer], [data-zag-top-layer]")) {
visibleNodes.add(element)
}
let acceptNode = (node: Element) => {
// Skip this node and its children if it is one of the target nodes, or a live announcer.
// Also skip children of already hidden nodes, as aria-hidden is recursive. An exception is
// made for elements with role="row" since VoiceOver on iOS has issues hiding elements with role="row".
// For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
if (
visibleNodes.has(node) ||
(hiddenNodes.has(node.parentElement!) && node.parentElement!.getAttribute("role") !== "row")
) {
return NodeFilter.FILTER_REJECT
}
// Skip this node but continue to children if one of the targets is inside the node.
for (let target of visibleNodes) {
if (node.contains(target)) {
return NodeFilter.FILTER_SKIP
}
}
return NodeFilter.FILTER_ACCEPT
}
let walker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, { acceptNode })
// TreeWalker does not include the root.
let acceptRoot = acceptNode(root)
if (acceptRoot === NodeFilter.FILTER_ACCEPT) {
hide(root)
}
if (acceptRoot !== NodeFilter.FILTER_REJECT) {
let node = walker.nextNode() as Element
while (node != null) {
hide(node)
node = walker.nextNode() as Element
}
}
}
let hide = (node: Element) => {
let refCount = refCountMap.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.
if (node.getAttribute("aria-hidden") === "true" && refCount === 0) {
return
}
if (refCount === 0) {
node.setAttribute("aria-hidden", "true")
}
hiddenNodes.add(node)
refCountMap.set(node, refCount + 1)
}
if (observerStack.length) {
observerStack[observerStack.length - 1].disconnect()
}
walk(root)
const observer = new win.MutationObserver((changes) => {
for (let change of changes) {
if (change.type !== "childList" || change.addedNodes.length === 0) {
continue
}
// If the parent element of the added nodes is not within one of the targets,
// and not already inside a hidden node, hide all of the new children.
if (![...visibleNodes, ...hiddenNodes].some((node) => node.contains(change.target))) {
for (let node of change.removedNodes) {
if (node instanceof win.Element) {
visibleNodes.delete(node)
hiddenNodes.delete(node)
}
}
for (let node of change.addedNodes) {
if (
(node instanceof win.HTMLElement || node instanceof win.SVGElement) &&
(node.dataset.liveAnnouncer === "true" || node.dataset.zagTopLayer === "true")
) {
visibleNodes.add(node)
} else if (node instanceof win.Element) {
walk(node)
}
}
}
}
})
observer.observe(root, { childList: true, subtree: true })
let observerWrapper = {
observe() {
observer.observe(root, { childList: true, subtree: true })
},
disconnect() {
observer.disconnect()
},
}
observerStack.push(observerWrapper)
return () => {
observer.disconnect()
for (let node of hiddenNodes) {
let count = refCountMap.get(node)
if (count === 1) {
node.removeAttribute("aria-hidden")
refCountMap.delete(node)
} else {
refCountMap.set(node, count! - 1)
}
}
// Remove this observer from the stack, and start the previous one.
if (observerWrapper === observerStack[observerStack.length - 1]) {
observerStack.pop()
if (observerStack.length) {
observerStack[observerStack.length - 1].observe()
}
} else {
observerStack.splice(observerStack.indexOf(observerWrapper), 1)
}
}
type Options = {
defer?: boolean
}
export function ariaHidden(targetsOrFn: TargetsOrFn, options: AriaHiddenOptions = {}) {
const { defer } = options
export function ariaHidden(targetsOrFn: TargetsOrFn, options: Options = {}) {
const { defer = true } = options
const func = defer ? raf : (v: any) => v()

@@ -174,3 +24,4 @@ const cleanups: (VoidFunction | undefined)[] = []

const targets = typeof targetsOrFn === "function" ? targetsOrFn() : targetsOrFn
cleanups.push(ariaHiddenImpl(targets, options))
const elements = targets.filter(Boolean) as HTMLElement[]
cleanups.push(hideOthers(elements))
}),

@@ -177,0 +28,0 @@ )

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc