/** Development detection works in Vite and native React build pipelines. */ function a11yDevelopment(): boolean { return (import.meta as { env?: { DEV?: boolean } }).env?.DEV ?? (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV === "development"; } // Framework-independent accessible-name diagnostics. Never changes DOM semantics. const warned = new Set(); /** * Warn, once per session and only in development, that `component` broke an * accessibility contract. `problem` says what is missing, `fix` the props that * supply it. The sibling of `warnDeprecated`, for the same audience. */ export function warnA11y(component: string, problem: string, fix: string): void { if (!a11yDevelopment()) return; const key = `${component}\u0000${problem}`; if (warned.has(key)) return; warned.add(key); console.warn(`[reddb ${component}] ${problem}. ${fix}`); } function nonEmpty(value: string | null | undefined): boolean { return typeof value === "string" && value.trim() !== ""; } /** Text a subtree contributes to a name, skipping what is hidden from assistive technology. */ function namedContent(node: Node): boolean { if (node.nodeType === 3) return nonEmpty(node.textContent); if (node.nodeType !== 1) return false; const element = node as Element; if (element.getAttribute("aria-hidden") === "true") return false; if (nonEmpty(element.getAttribute("aria-label"))) return true; if (element.tagName === "IMG" && nonEmpty(element.getAttribute("alt"))) return true; if (element.tagName.toLowerCase() === "title") return nonEmpty(element.textContent); for (const child of element.childNodes) if (namedContent(child)) return true; return false; } /** * Whether `element` has an accessible name a screen reader can announce: * `aria-label`, an `aria-labelledby` that resolves to text, a native `