@bquery/bquery 1.10.0 → 1.11.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 +91 -65
- package/dist/{a11y-DG2i4iZN.js → a11y-DgUQ8-fI.js} +1 -1
- package/dist/{a11y-DG2i4iZN.js.map → a11y-DgUQ8-fI.js.map} +1 -1
- package/dist/a11y.es.mjs +1 -1
- package/dist/{component-DRotf1hl.js → component-D8ydhe58.js} +2 -2
- package/dist/{component-DRotf1hl.js.map → component-D8ydhe58.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/concurrency-BU1wPEsZ.js.map +1 -1
- package/dist/{constraints-CqjhmpZC.js → constraints-Dlbx_m1b.js} +1 -1
- package/dist/{constraints-CqjhmpZC.js.map → constraints-Dlbx_m1b.js.map} +1 -1
- package/dist/{core-EMYSLzaT.js → core-tOP6QOrY.js} +2 -2
- package/dist/{core-EMYSLzaT.js.map → core-tOP6QOrY.js.map} +1 -1
- package/dist/core.es.mjs +1 -1
- package/dist/{custom-directives-BjFzFhuf.js → custom-directives-5DlKqvd2.js} +1 -1
- package/dist/{custom-directives-BjFzFhuf.js.map → custom-directives-5DlKqvd2.js.map} +1 -1
- package/dist/{devtools-C5FExMwv.js → devtools-QosAqo0T.js} +2 -2
- package/dist/{devtools-C5FExMwv.js.map → devtools-QosAqo0T.js.map} +1 -1
- package/dist/devtools.es.mjs +1 -1
- package/dist/{dnd-BAqzPlSo.js → dnd-d2OU4len.js} +1 -1
- package/dist/{dnd-BAqzPlSo.js.map → dnd-d2OU4len.js.map} +1 -1
- package/dist/dnd.es.mjs +1 -1
- package/dist/{forms-Dx1Scvh0.js → forms-BLx4ZzT7.js} +1 -1
- package/dist/{forms-Dx1Scvh0.js.map → forms-BLx4ZzT7.js.map} +1 -1
- package/dist/forms.es.mjs +1 -1
- package/dist/full.d.ts +4 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +258 -219
- package/dist/full.iife.js +41 -37
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +41 -37
- package/dist/full.umd.js.map +1 -1
- package/dist/{i18n-Cazyk9RD.js → i18n--p7PM-9r.js} +1 -1
- package/dist/{i18n-Cazyk9RD.js.map → i18n--p7PM-9r.js.map} +1 -1
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.mjs +291 -252
- package/dist/match-CrZRVC4z.js +174 -0
- package/dist/match-CrZRVC4z.js.map +1 -0
- package/dist/{media-dAKIGPk3.js → media-gjbWNq50.js} +1 -1
- package/dist/{media-dAKIGPk3.js.map → media-gjbWNq50.js.map} +1 -1
- package/dist/media.es.mjs +1 -1
- package/dist/motion-BBMso9Ir.js.map +1 -1
- package/dist/{mount-C8O2vXkQ.js → mount-0A9qtcRJ.js} +3 -3
- package/dist/{mount-C8O2vXkQ.js.map → mount-0A9qtcRJ.js.map} +1 -1
- package/dist/platform-BPHIXbw8.js.map +1 -1
- package/dist/{plugin-DjTqWg-P.js → plugin-SZEirbwq.js} +2 -2
- package/dist/{plugin-DjTqWg-P.js.map → plugin-SZEirbwq.js.map} +1 -1
- package/dist/plugin.es.mjs +1 -1
- package/dist/reactive-BAd2hfl8.js.map +1 -1
- package/dist/{registry-Cr6VH8CR.js → registry-jpUQHf4E.js} +1 -1
- package/dist/{registry-Cr6VH8CR.js.map → registry-jpUQHf4E.js.map} +1 -1
- package/dist/router-C4weu0QL.js +333 -0
- package/dist/router-C4weu0QL.js.map +1 -0
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-B1V4JswB.js → sanitize-DOMkRO9G.js} +12 -7
- package/dist/{sanitize-B1V4JswB.js.map → sanitize-DOMkRO9G.js.map} +1 -1
- package/dist/security.es.mjs +1 -1
- package/dist/server/create-server.d.ts +25 -0
- package/dist/server/create-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/types.d.ts +396 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server-QdyKtCS1.js +349 -0
- package/dist/server-QdyKtCS1.js.map +1 -0
- package/dist/server.es.mjs +6 -0
- package/dist/ssr/adapters.d.ts +74 -0
- package/dist/ssr/adapters.d.ts.map +1 -0
- package/dist/ssr/async.d.ts +40 -0
- package/dist/ssr/async.d.ts.map +1 -0
- package/dist/ssr/config.d.ts +60 -0
- package/dist/ssr/config.d.ts.map +1 -0
- package/dist/ssr/context.d.ts +73 -0
- package/dist/ssr/context.d.ts.map +1 -0
- package/dist/ssr/defer-brand.d.ts +5 -0
- package/dist/ssr/defer-brand.d.ts.map +1 -0
- package/dist/ssr/escape.d.ts +17 -0
- package/dist/ssr/escape.d.ts.map +1 -0
- package/dist/ssr/expression.d.ts +44 -0
- package/dist/ssr/expression.d.ts.map +1 -0
- package/dist/ssr/hash.d.ts +39 -0
- package/dist/ssr/hash.d.ts.map +1 -0
- package/dist/ssr/head.d.ts +102 -0
- package/dist/ssr/head.d.ts.map +1 -0
- package/dist/ssr/html-parser.d.ts +58 -0
- package/dist/ssr/html-parser.d.ts.map +1 -0
- package/dist/ssr/index.d.ts +49 -43
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/mismatch.d.ts +60 -0
- package/dist/ssr/mismatch.d.ts.map +1 -0
- package/dist/ssr/render-async.d.ts +84 -0
- package/dist/ssr/render-async.d.ts.map +1 -0
- package/dist/ssr/render.d.ts.map +1 -1
- package/dist/ssr/renderer.d.ts +25 -0
- package/dist/ssr/renderer.d.ts.map +1 -0
- package/dist/ssr/resumability.d.ts +65 -0
- package/dist/ssr/resumability.d.ts.map +1 -0
- package/dist/ssr/router-bridge.d.ts +101 -0
- package/dist/ssr/router-bridge.d.ts.map +1 -0
- package/dist/ssr/runtime.d.ts +63 -0
- package/dist/ssr/runtime.d.ts.map +1 -0
- package/dist/ssr/serialize.d.ts.map +1 -1
- package/dist/ssr/store-snapshot.d.ts +87 -0
- package/dist/ssr/store-snapshot.d.ts.map +1 -0
- package/dist/ssr/strategies.d.ts +43 -0
- package/dist/ssr/strategies.d.ts.map +1 -0
- package/dist/ssr/suspense.d.ts +47 -0
- package/dist/ssr/suspense.d.ts.map +1 -0
- package/dist/ssr/types.d.ts +17 -0
- package/dist/ssr/types.d.ts.map +1 -1
- package/dist/ssr-Bt6BQA3J.js +2127 -0
- package/dist/ssr-Bt6BQA3J.js.map +1 -0
- package/dist/ssr.es.mjs +42 -7
- package/dist/{store-CjmEeX9-.js → store-DnXuu6Li.js} +2 -2
- package/dist/{store-CjmEeX9-.js.map → store-DnXuu6Li.js.map} +1 -1
- package/dist/store.es.mjs +2 -2
- package/dist/storybook.es.mjs +1 -1
- package/dist/{testing-TdfaL7VE.js → testing-CeMUwrRD.js} +2 -2
- package/dist/{testing-TdfaL7VE.js.map → testing-CeMUwrRD.js.map} +1 -1
- package/dist/testing.es.mjs +1 -1
- package/dist/view.es.mjs +1 -1
- package/package.json +19 -14
- package/src/full.ts +99 -0
- package/src/index.ts +5 -2
- package/src/server/create-server.ts +754 -0
- package/src/server/index.ts +33 -0
- package/src/server/types.ts +490 -0
- package/src/ssr/adapters.ts +330 -0
- package/src/ssr/async.ts +125 -0
- package/src/ssr/config.ts +86 -0
- package/src/ssr/context.ts +245 -0
- package/src/ssr/defer-brand.ts +3 -0
- package/src/ssr/escape.ts +25 -0
- package/src/ssr/expression.ts +669 -0
- package/src/ssr/hash.ts +71 -0
- package/src/ssr/head.ts +240 -0
- package/src/ssr/html-parser.ts +387 -0
- package/src/ssr/index.ts +136 -43
- package/src/ssr/mismatch.ts +110 -0
- package/src/ssr/render-async.ts +286 -0
- package/src/ssr/render.ts +130 -59
- package/src/ssr/renderer.ts +453 -0
- package/src/ssr/resumability.ts +142 -0
- package/src/ssr/router-bridge.ts +177 -0
- package/src/ssr/runtime.ts +131 -0
- package/src/ssr/serialize.ts +1 -27
- package/src/ssr/store-snapshot.ts +209 -0
- package/src/ssr/strategies.ts +245 -0
- package/src/ssr/suspense.ts +504 -0
- package/src/ssr/types.ts +18 -0
- package/dist/router-CCepRMpC.js +0 -493
- package/dist/router-CCepRMpC.js.map +0 -1
- package/dist/ssr-D-1IPcfw.js +0 -248
- package/dist/ssr-D-1IPcfw.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,40 +1,31 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
</
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
**The jQuery for the modern Web Platform.**
|
|
22
|
-
|
|
23
|
-
bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, zero-build worker tasks, async data composables, HTTP clients, polling and pagination helpers, realtime transports, REST workflows, Web Components, motion utilities, routing, stores, declarative views, accessibility helpers, forms, i18n, media signals, drag-and-drop, plugins, devtools, testing utilities, and SSR — without a mandatory build step.
|
|
24
|
-
|
|
25
|
-
> **New in 1.10.0:** The Concurrency module now adds explicit RPC workers, bounded task/RPC pools, opt-in reactive worker state wrappers, and high-level helpers such as `parallel()`, `map()`, `filter()`, `reduce()`, and `pipeline()`.
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/bQuery/bQuery/main/assets/bquerry-logo.svg" alt="bQuery.js" height="80" /><br/><br/>
|
|
3
|
+
<h1>bQuery.js</h1>
|
|
4
|
+
<p><strong>The full-stack web framework that speaks jQuery.</strong></p>
|
|
5
|
+
<p><em>Batteries-included TypeScript framework for the modern web — signals, SSR, Web Components,<br/>routing, and more — with a jQuery-inspired API and zero mandatory build step.</em></p>
|
|
6
|
+
<br/>
|
|
7
|
+
<a href="https://www.npmjs.com/package/@bquery/bquery"><img src="https://img.shields.io/npm/v/@bquery/bquery?style=flat&logo=npm" alt="npm version"/></a>
|
|
8
|
+
<a href="https://bundlephobia.com/package/@bquery/bquery"><img src="https://img.shields.io/bundlephobia/minzip/@bquery/bquery?style=flat" alt="Bundle Size"/></a>
|
|
9
|
+
<a href="https://github.com/bQuery/bQuery/blob/main/LICENSE.md"><img src="https://img.shields.io/github/license/bquery/bquery?style=flat" alt="License"/></a><br/>
|
|
10
|
+
<a href="https://github.com/bQuery/bQuery/stargazers"><img src="https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github" alt="Stars"/></a>
|
|
11
|
+
<a href="https://github.com/bQuery/bQuery/issues"><img src="https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github" alt="Issues"/></a>
|
|
12
|
+
<a href="https://www.codefactor.io/repository/github/bquery/bquery"><img src="https://www.codefactor.io/repository/github/bquery/bquery/badge" alt="CodeFactor"/></a>
|
|
13
|
+
<a href="https://www.jsdelivr.com/package/npm/@bquery/bquery"><img src="https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge" alt="jsDelivr"/></a>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
> **New in 1.11.0:** Runtime-agnostic SSR now adds DOM-free fallback rendering, `renderToStringAsync()`, `renderToStream()`, `renderToResponse()`, runtime adapters, hydration strategies, store snapshots, and resumability hooks, alongside the new `@bquery/bquery/server` entry point for dependency-free backend routing and WebSocket sessions.
|
|
26
19
|
|
|
27
20
|
## Highlights
|
|
28
21
|
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
|
|
36
|
-
- **Tree-shakeable**: import only what you need.
|
|
37
|
-
- **Storybook-ready**: default components can be previewed and developed in Storybook with dedicated story template helpers.
|
|
22
|
+
- **Full-stack by default**: signals, SSR, routing, server middleware, Web Components, and 15+ modules ship together — use only what you need, or bring everything.
|
|
23
|
+
- **Zero mandatory build step**: start in plain HTML with the CDN entry points, or use your preferred bundler without changing the API surface.
|
|
24
|
+
- **Reactive data across the stack**: fetch composables, HTTP clients, polling, pagination, WebSocket / SSE, REST helpers, and request coordination plug directly into signals.
|
|
25
|
+
- **From UI to backend**: declarative views, forms, accessibility helpers, plugins, devtools, testing utilities, SSR, and server routing cover the full app lifecycle.
|
|
26
|
+
- **TypeScript-first and tree-shakeable**: explicit entry points keep large apps typed while letting smaller apps import focused modules.
|
|
27
|
+
- **Security-focused**: DOM writes are sanitized by default, with Trusted Types and CSP helpers built in.
|
|
28
|
+
|
|
38
29
|
|
|
39
30
|
## Installation
|
|
40
31
|
|
|
@@ -189,11 +180,12 @@ import {
|
|
|
189
180
|
clipboard,
|
|
190
181
|
} from '@bquery/bquery/media';
|
|
191
182
|
|
|
192
|
-
// Plugins, devtools, testing, SSR
|
|
183
|
+
// Plugins, devtools, testing, SSR, server
|
|
193
184
|
import { use } from '@bquery/bquery/plugin';
|
|
194
185
|
import { enableDevtools, inspectSignals } from '@bquery/bquery/devtools';
|
|
195
186
|
import { renderComponent, fireEvent, waitFor } from '@bquery/bquery/testing';
|
|
196
187
|
import { renderToString, hydrateMount, serializeStoreState } from '@bquery/bquery/ssr';
|
|
188
|
+
import { createServer } from '@bquery/bquery/server';
|
|
197
189
|
|
|
198
190
|
// Storybook helpers
|
|
199
191
|
import { storyHtml, when } from '@bquery/bquery/storybook';
|
|
@@ -201,35 +193,38 @@ import { storyHtml, when } from '@bquery/bquery/storybook';
|
|
|
201
193
|
|
|
202
194
|
## Modules at a glance
|
|
203
195
|
|
|
204
|
-
| Module | Description |
|
|
205
|
-
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
206
|
-
| **Core** | Selectors, DOM manipulation, events, traversal, and typed utilities |
|
|
207
|
-
| **Reactive** | `signal`, `computed`, `effect`, `watchDebounce`, `watchThrottle`, async data, HTTP clients, polling, pagination, WebSocket / SSE, and REST helpers |
|
|
208
|
-
| **Concurrency** | Zero-build worker tasks, explicit RPC helpers, optional reactive state wrappers, bounded worker pools, high-level collection helpers, and an optional fluent pipeline layer |
|
|
209
|
-
| **Component** | Typed Web Components with scoped reactivity and configurable Shadow DOM |
|
|
210
|
-
| **Storybook** | Safe story template helpers with boolean-attribute shorthand |
|
|
211
|
-
| **Motion** | View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines |
|
|
212
|
-
| **Security** | HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition |
|
|
213
|
-
| **Platform** | Storage, cache, cookies, page metadata, announcers, and shared runtime config |
|
|
214
|
-
| **Router** | SPA routing, constrained params, redirects, guards, `useRoute()`, and `<bq-link>` |
|
|
215
|
-
| **Store** | Signal-based state management, persistence, migrations, and action hooks |
|
|
216
|
-
| **View** | Declarative DOM bindings with `bq-*` directives for content, classes, forms, errors, ARIA, and plugins |
|
|
217
|
-
| **Forms** | Reactive form state with sync/async validation and submit handling |
|
|
218
|
-
| **i18n** | Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting |
|
|
219
|
-
| **A11y** | Focus traps, live-region announcements, roving tabindex, skip links, and audits |
|
|
220
|
-
| **DnD** | Draggable elements, droppable zones, and sortable lists |
|
|
221
|
-
| **Media** | Reactive browser/device signals for viewport, network, battery, geolocation, clipboard, and DOM observers |
|
|
222
|
-
| **Plugin** | Global plugin registration for custom directives and Web Components |
|
|
223
|
-
| **Devtools** | Runtime inspection helpers for signals, stores, components, and timelines |
|
|
224
|
-
| **Testing** | Component mounting, mock signals/router helpers, and async test utilities |
|
|
225
|
-
| **SSR** |
|
|
226
|
-
|
|
227
|
-
|
|
196
|
+
| Module | Status | Description |
|
|
197
|
+
| --------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| **Core** | Stable | Selectors, DOM manipulation, events, traversal, and typed utilities |
|
|
199
|
+
| **Reactive** | Stable | `signal`, `computed`, `effect`, `watchDebounce`, `watchThrottle`, async data, HTTP clients, polling, pagination, WebSocket / SSE, and REST helpers |
|
|
200
|
+
| **Concurrency** | Experimental | Zero-build worker tasks, explicit RPC helpers, optional reactive state wrappers, bounded worker pools, high-level collection helpers, and an optional fluent pipeline layer |
|
|
201
|
+
| **Component** | Stable | Typed Web Components with scoped reactivity and configurable Shadow DOM |
|
|
202
|
+
| **Storybook** | Beta | Safe story template helpers with boolean-attribute shorthand |
|
|
203
|
+
| **Motion** | Stable | View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines |
|
|
204
|
+
| **Security** | Stable | HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition |
|
|
205
|
+
| **Platform** | Stable | Storage, cache, cookies, page metadata, announcers, and shared runtime config |
|
|
206
|
+
| **Router** | Stable | SPA routing, constrained params, redirects, guards, `useRoute()`, and `<bq-link>` |
|
|
207
|
+
| **Store** | Stable | Signal-based state management, persistence, migrations, and action hooks |
|
|
208
|
+
| **View** | Beta | Declarative DOM bindings with `bq-*` directives for content, classes, forms, errors, ARIA, and plugins |
|
|
209
|
+
| **Forms** | Beta | Reactive form state with sync/async validation and submit handling |
|
|
210
|
+
| **i18n** | Beta | Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting |
|
|
211
|
+
| **A11y** | Beta | Focus traps, live-region announcements, roving tabindex, skip links, and audits |
|
|
212
|
+
| **DnD** | Beta | Draggable elements, droppable zones, and sortable lists |
|
|
213
|
+
| **Media** | Beta | Reactive browser/device signals for viewport, network, battery, geolocation, clipboard, and DOM observers |
|
|
214
|
+
| **Plugin** | Beta | Global plugin registration for custom directives and Web Components |
|
|
215
|
+
| **Devtools** | Beta | Runtime inspection helpers for signals, stores, components, and timelines |
|
|
216
|
+
| **Testing** | Beta | Component mounting, mock signals/router helpers, and async test utilities |
|
|
217
|
+
| **SSR** | Experimental | Runtime-agnostic server-side rendering (Node ≥ 24, Deno, Bun), streaming, async loaders, hydration islands, head/asset/CSP-nonce management, runtime adapters |
|
|
218
|
+
| **Server** | Experimental | Express-inspired backend routing, middleware, safe response helpers, SSR-aware request handling, and runtime-agnostic WebSocket sessions |
|
|
219
|
+
|
|
220
|
+
Storybook authoring helpers are also available as a dedicated entry point via `@bquery/bquery/storybook`. Worker-task, RPC, worker-pool, high-level task-list / collection helpers, and the optional fluent pipeline layer ship as a dedicated entry point via `@bquery/bquery/concurrency`. Server-side middleware, HTTP routing, and runtime-agnostic WebSocket session helpers ship as a dedicated entry point via `@bquery/bquery/server`.
|
|
228
221
|
|
|
229
222
|
Reusable workers and pools can also opt into readonly signal mirrors such as `state$`, `busy$`, `pending$`, and `size$` through the `createReactive*()` concurrency wrappers.
|
|
230
223
|
|
|
231
224
|
## Quick examples
|
|
232
225
|
|
|
226
|
+
bQuery.js covers the full development lifecycle — from interactive DOM scripting to server-side rendering. The examples below show each layer independently; in practice they compose seamlessly.
|
|
227
|
+
|
|
233
228
|
### Core – DOM & events
|
|
234
229
|
|
|
235
230
|
```ts
|
|
@@ -784,13 +779,14 @@ drag.destroy();
|
|
|
784
779
|
modalTrap.release();
|
|
785
780
|
```
|
|
786
781
|
|
|
787
|
-
### Plugins, devtools, testing, and
|
|
782
|
+
### Plugins, devtools, testing, SSR, and server
|
|
788
783
|
|
|
789
784
|
```ts
|
|
790
785
|
import { use } from '@bquery/bquery/plugin';
|
|
791
786
|
import { enableDevtools, getTimeline } from '@bquery/bquery/devtools';
|
|
792
787
|
import { renderComponent, fireEvent } from '@bquery/bquery/testing';
|
|
793
788
|
import { renderToString } from '@bquery/bquery/ssr';
|
|
789
|
+
import { createServer } from '@bquery/bquery/server';
|
|
794
790
|
|
|
795
791
|
use({
|
|
796
792
|
name: 'focus-plugin',
|
|
@@ -808,6 +804,20 @@ fireEvent(mounted.el, 'click');
|
|
|
808
804
|
const { html } = renderToString('<p bq-text="label"></p>', { label: 'Hello SSR' });
|
|
809
805
|
console.log(html);
|
|
810
806
|
|
|
807
|
+
const app = createServer();
|
|
808
|
+
app.get('/hello/:name', (ctx) => ctx.json({ name: ctx.params.name, q: ctx.query.q }));
|
|
809
|
+
|
|
810
|
+
// Runtime-agnostic async render with head injection (works on Node, Deno, Bun):
|
|
811
|
+
import { createSSRContext, renderToResponse } from '@bquery/bquery/ssr';
|
|
812
|
+
const ctx = createSSRContext({ request: new Request('http://localhost/') });
|
|
813
|
+
ctx.head.add({ title: 'Home' });
|
|
814
|
+
ctx.assets.module('/app.js');
|
|
815
|
+
const response = await renderToResponse(
|
|
816
|
+
'<html><head></head><body><p bq-text="label"></p></body></html>',
|
|
817
|
+
{ label: 'Hello' },
|
|
818
|
+
{ context: ctx, etag: true }
|
|
819
|
+
);
|
|
820
|
+
|
|
811
821
|
mounted.unmount();
|
|
812
822
|
```
|
|
813
823
|
|
|
@@ -884,12 +894,14 @@ mount('#app', {
|
|
|
884
894
|
|
|
885
895
|
| Browser | Version | Support |
|
|
886
896
|
| ------- | ------- | ------- |
|
|
887
|
-
| Chrome | 90+ | ✅ Full
|
|
888
|
-
| Firefox | 90+ | ✅ Full
|
|
889
|
-
| Safari | 15+ | ✅ Full
|
|
890
|
-
| Edge | 90+ | ✅ Full
|
|
897
|
+
| Chrome | 90+ | ✅ Full |
|
|
898
|
+
| Firefox | 90+ | ✅ Full |
|
|
899
|
+
| Safari | 15+ | ✅ Full |
|
|
900
|
+
| Edge | 90+ | ✅ Full |
|
|
891
901
|
|
|
892
902
|
> **No IE support** by design.
|
|
903
|
+
>
|
|
904
|
+
> Server-side runtimes: Node.js ≥ 24, Bun ≥ 1.3.13, and Deno 2 are supported for SSR and server modules.
|
|
893
905
|
|
|
894
906
|
## Documentation
|
|
895
907
|
|
|
@@ -914,9 +926,12 @@ mount('#app', {
|
|
|
914
926
|
- **Devtools**: [docs/guide/devtools.md](docs/guide/devtools.md)
|
|
915
927
|
- **Testing Utilities**: [docs/guide/testing.md](docs/guide/testing.md)
|
|
916
928
|
- **SSR / Hydration**: [docs/guide/ssr.md](docs/guide/ssr.md)
|
|
929
|
+
- **Server**: [docs/guide/server.md](docs/guide/server.md)
|
|
917
930
|
|
|
918
931
|
## Local Development
|
|
919
932
|
|
|
933
|
+
The cross-runtime SSR examples in [`examples/`](examples/) import directly from `src/`, so you can run them from a repo checkout without building `dist/` first.
|
|
934
|
+
|
|
920
935
|
```bash
|
|
921
936
|
# Install dependencies
|
|
922
937
|
bun install
|
|
@@ -933,11 +948,19 @@ bun test
|
|
|
933
948
|
# Build library
|
|
934
949
|
bun run build
|
|
935
950
|
|
|
951
|
+
# Verify AI guidance / release metadata sync
|
|
952
|
+
bun run check:ai-guidance
|
|
953
|
+
|
|
936
954
|
# Build docs
|
|
937
955
|
bun run build:docs
|
|
938
956
|
|
|
939
957
|
# Generate API documentation
|
|
940
958
|
bun run docs:api
|
|
959
|
+
|
|
960
|
+
# Run the cross-runtime SSR examples directly from source
|
|
961
|
+
bun examples/ssr-bun/serve.ts
|
|
962
|
+
deno run -A examples/ssr-deno/serve.ts
|
|
963
|
+
node --experimental-strip-types examples/ssr-node/serve.ts
|
|
941
964
|
```
|
|
942
965
|
|
|
943
966
|
## Project Structure
|
|
@@ -947,6 +970,7 @@ bQuery.js
|
|
|
947
970
|
├── src/
|
|
948
971
|
│ ├── core/ # Selectors, DOM ops, events, utils
|
|
949
972
|
│ ├── reactive/ # Signals, computed, effects, async data
|
|
973
|
+
│ ├── concurrency/ # Zero-build worker tasks, RPC, pools, collection helpers
|
|
950
974
|
│ ├── component/ # Web Components helper + default library
|
|
951
975
|
│ ├── storybook/ # Story template helpers
|
|
952
976
|
│ ├── motion/ # View transitions, FLIP, springs
|
|
@@ -963,7 +987,8 @@ bQuery.js
|
|
|
963
987
|
│ ├── plugin/ # Global plugin system
|
|
964
988
|
│ ├── devtools/ # Runtime inspection helpers
|
|
965
989
|
│ ├── testing/ # Test utilities
|
|
966
|
-
│
|
|
990
|
+
│ ├── ssr/ # Runtime-agnostic server-side rendering + hydration
|
|
991
|
+
│ └── server/ # Backend helpers and WebSocket sessions
|
|
967
992
|
├── docs/ # VitePress documentation
|
|
968
993
|
├── .storybook/ # Storybook config
|
|
969
994
|
├── stories/ # Component stories
|
|
@@ -982,6 +1007,7 @@ This project provides dedicated context files for AI coding agents:
|
|
|
982
1007
|
- **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
|
|
983
1008
|
- **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
|
|
984
1009
|
- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
|
|
1010
|
+
- **`bun run check:ai-guidance`** — Lightweight sync check for version / engine / AI guidance drift
|
|
985
1011
|
|
|
986
1012
|
## License
|
|
987
1013
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a11y-DG2i4iZN.js","names":[],"sources":["../src/a11y/announce.ts","../src/a11y/audit.ts","../src/a11y/media-preferences.ts","../src/a11y/roving-tab-index.ts","../src/a11y/skip-link.ts","../src/a11y/trap-focus.ts"],"sourcesContent":["/**\n * Screen reader announcement utility using ARIA live regions.\n *\n * Creates and manages off-screen live regions to announce dynamic\n * content changes to assistive technologies.\n *\n * @module bquery/a11y\n */\n\nimport type { AnnouncePriority } from './types';\n\n/** Cache for live region containers, keyed by priority. */\nconst liveRegions = new Map<AnnouncePriority, HTMLElement>();\nconst pendingAnnouncements = new Map<AnnouncePriority, ReturnType<typeof setTimeout>>();\n\n/**\n * Delay in milliseconds before updating the live region text.\n * This ensures screen readers detect the content change even when\n * the same message is announced consecutively — clearing first and\n * setting after a short timer delay forces a new live-region mutation event.\n * @internal\n */\nconst ANNOUNCEMENT_DELAY_MS = 50;\n\n/**\n * Gets or creates a visually-hidden ARIA live region for the given priority.\n *\n * @param priority - The aria-live priority level\n * @returns The live region element\n * @internal\n */\nconst getOrCreateLiveRegion = (priority: AnnouncePriority): HTMLElement => {\n const existing = liveRegions.get(priority);\n if (existing && existing.isConnected) {\n return existing;\n }\n\n const el = document.createElement('div');\n el.setAttribute('aria-live', priority);\n el.setAttribute('aria-atomic', 'true');\n el.setAttribute('role', priority === 'assertive' ? 'alert' : 'status');\n\n // Visually hidden but accessible to screen readers\n Object.assign(el.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: '0',\n });\n\n document.body.appendChild(el);\n liveRegions.set(priority, el);\n\n return el;\n};\n\n/**\n * Announces a message to screen readers via an ARIA live region.\n *\n * The message is injected into a visually-hidden live region element.\n * Screen readers will pick up the change and announce it to the user.\n *\n * @param message - The text message to announce\n * @param priority - The urgency level: `'polite'` (default) or `'assertive'`\n *\n * @example\n * ```ts\n * import { announceToScreenReader } from '@bquery/bquery/a11y';\n *\n * // Polite announcement (waits for idle)\n * announceToScreenReader('3 search results found');\n *\n * // Assertive announcement (interrupts current speech)\n * announceToScreenReader('Error: Please fix the form', 'assertive');\n * ```\n */\nexport const announceToScreenReader = (\n message: string,\n priority: AnnouncePriority = 'polite'\n): void => {\n if (!message) return;\n if (typeof document === 'undefined' || !document.body) return;\n\n const region = getOrCreateLiveRegion(priority);\n const pendingTimeout = pendingAnnouncements.get(priority);\n if (pendingTimeout !== undefined) {\n clearTimeout(pendingTimeout);\n }\n\n // Clear first, then set after a short timer delay to ensure screen readers\n // detect the change even if the same message is announced twice.\n region.textContent = '';\n\n // Use setTimeout to ensure the DOM update triggers a live region change event\n const timeout = setTimeout(() => {\n pendingAnnouncements.delete(priority);\n if (region.isConnected) {\n region.textContent = message;\n }\n }, ANNOUNCEMENT_DELAY_MS);\n\n pendingAnnouncements.set(priority, timeout);\n};\n\n/**\n * Removes all live region elements created by `announceToScreenReader`.\n * Useful for cleanup in tests or when unmounting an application.\n *\n * @example\n * ```ts\n * import { clearAnnouncements } from '@bquery/bquery/a11y';\n *\n * clearAnnouncements();\n * ```\n */\nexport const clearAnnouncements = (): void => {\n for (const timeout of pendingAnnouncements.values()) {\n clearTimeout(timeout);\n }\n pendingAnnouncements.clear();\n\n for (const [, el] of liveRegions) {\n el.remove();\n }\n liveRegions.clear();\n};\n","/**\n * Development-time accessibility audit utility.\n *\n * Scans DOM elements for common accessibility issues such as missing\n * alt text on images, missing labels on form inputs, empty links/buttons,\n * and incorrect ARIA usage.\n *\n * @module bquery/a11y\n */\n\nimport type { AuditFinding, AuditResult, AuditSeverity } from './types';\n\n/**\n * Creates a finding object.\n * @internal\n */\nconst finding = (\n severity: AuditSeverity,\n message: string,\n element: Element,\n rule: string\n): AuditFinding => ({\n severity,\n message,\n element,\n rule,\n});\n\n/**\n * Checks images for missing alt attributes.\n * @internal\n */\nconst auditImages = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n const images = container.querySelectorAll('img');\n\n for (const img of images) {\n if (!img.hasAttribute('alt')) {\n findings.push(\n finding(\n 'error',\n 'Image is missing an alt attribute. Add alt=\"\" for decorative images or a descriptive alt text.',\n img,\n 'img-alt'\n )\n );\n } else if (img.getAttribute('alt') === '' && !img.hasAttribute('role')) {\n findings.push(\n finding(\n 'info',\n 'Image has empty alt text. Consider adding role=\"presentation\" if decorative.',\n img,\n 'img-decorative'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Checks form inputs for missing labels.\n * @internal\n */\nconst auditFormInputs = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n const inputs = container.querySelectorAll('input, select, textarea');\n\n for (const input of inputs) {\n const type = input.getAttribute('type');\n\n // Hidden, submit, and button inputs don't need labels\n if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'reset') {\n continue;\n }\n\n const id = input.getAttribute('id');\n const hasLabel = id ? !!container.querySelector(`label[for=\"${id}\"]`) : false;\n const hasAriaLabel = input.hasAttribute('aria-label') || input.hasAttribute('aria-labelledby');\n const hasTitle = input.hasAttribute('title');\n const isWrappedInLabel = input.closest('label') !== null;\n\n if (!hasLabel && !hasAriaLabel && !hasTitle && !isWrappedInLabel) {\n findings.push(\n finding(\n 'error',\n `Form input is missing a label. Add a <label for=\"id\">, aria-label, or aria-labelledby attribute.`,\n input,\n 'input-label'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Checks for empty interactive elements (buttons, links).\n * @internal\n */\nconst auditInteractiveElements = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n\n // Check buttons\n const buttons = container.querySelectorAll('button');\n for (const btn of buttons) {\n const hasText = (btn.textContent ?? '').trim().length > 0;\n const hasAriaLabel = btn.hasAttribute('aria-label') || btn.hasAttribute('aria-labelledby');\n const hasTitle = btn.hasAttribute('title');\n\n if (!hasText && !hasAriaLabel && !hasTitle) {\n findings.push(\n finding(\n 'error',\n 'Button has no accessible name. Add text content, aria-label, or title.',\n btn,\n 'button-name'\n )\n );\n }\n }\n\n // Check links\n const links = container.querySelectorAll('a[href]');\n for (const link of links) {\n const hasText = (link.textContent ?? '').trim().length > 0;\n const hasAriaLabel = link.hasAttribute('aria-label') || link.hasAttribute('aria-labelledby');\n const hasTitle = link.hasAttribute('title');\n const hasImage = link.querySelector('img[alt]') !== null;\n\n if (!hasText && !hasAriaLabel && !hasTitle && !hasImage) {\n findings.push(\n finding(\n 'error',\n 'Link has no accessible name. Add text content, aria-label, or title.',\n link,\n 'link-name'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Checks heading hierarchy for skipped levels.\n * @internal\n */\nconst auditHeadings = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n const headings = container.querySelectorAll('h1, h2, h3, h4, h5, h6');\n\n let previousLevel = 0;\n\n for (const heading of headings) {\n const level = parseInt(heading.tagName.charAt(1), 10);\n\n if (previousLevel > 0 && level > previousLevel + 1) {\n findings.push(\n finding(\n 'warning',\n `Heading level skipped: <${heading.tagName.toLowerCase()}> follows <h${previousLevel}>. Don't skip heading levels.`,\n heading,\n 'heading-order'\n )\n );\n }\n\n if ((heading.textContent ?? '').trim().length === 0) {\n findings.push(finding('warning', 'Heading element is empty.', heading, 'heading-empty'));\n }\n\n previousLevel = level;\n }\n\n return findings;\n};\n\n/**\n * Checks for valid ARIA attribute usage.\n * @internal\n */\nconst auditAria = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n\n // Check aria-labelledby references exist\n const labelled = container.querySelectorAll('[aria-labelledby]');\n for (const el of labelled) {\n const ids = (el.getAttribute('aria-labelledby') ?? '').split(/\\s+/);\n for (const id of ids) {\n if (id && !document.getElementById(id)) {\n findings.push(\n finding(\n 'error',\n `aria-labelledby references \"${id}\" which does not exist in the document.`,\n el,\n 'aria-labelledby-ref'\n )\n );\n }\n }\n }\n\n // Check aria-describedby references exist\n const described = container.querySelectorAll('[aria-describedby]');\n for (const el of described) {\n const ids = (el.getAttribute('aria-describedby') ?? '').split(/\\s+/);\n for (const id of ids) {\n if (id && !document.getElementById(id)) {\n findings.push(\n finding(\n 'error',\n `aria-describedby references \"${id}\" which does not exist in the document.`,\n el,\n 'aria-describedby-ref'\n )\n );\n }\n }\n }\n\n return findings;\n};\n\n/**\n * Checks for sufficient document landmarks.\n * @internal\n */\nconst auditLandmarks = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n\n // Only audit the document body or top-level container\n if (container === document.body || container === document.documentElement) {\n const hasMain = !!container.querySelector('main') || !!container.querySelector('[role=\"main\"]');\n\n if (!hasMain) {\n findings.push(\n finding(\n 'warning',\n 'Page is missing a <main> landmark. Add <main> or role=\"main\" to the primary content area.',\n container,\n 'landmark-main'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Runs a development-time accessibility audit on a container element.\n *\n * Checks for common accessibility issues including:\n * - Missing alt text on images\n * - Missing labels on form inputs\n * - Empty buttons and links\n * - Heading hierarchy issues\n * - Invalid ARIA references\n * - Missing document landmarks\n *\n * This is intended as a development tool — not a replacement for\n * manual testing or professional accessibility audits.\n *\n * @param container - The element to audit (defaults to `document.body`)\n * @returns An audit result with findings, counts, and pass/fail status\n *\n * @example\n * ```ts\n * import { auditA11y } from '@bquery/bquery/a11y';\n *\n * const result = auditA11y();\n * if (!result.passed) {\n * console.warn(`Found ${result.errors} accessibility errors:`);\n * for (const f of result.findings) {\n * console.warn(`[${f.severity}] ${f.message}`, f.element);\n * }\n * }\n * ```\n */\nexport const auditA11y = (container?: Element): AuditResult => {\n if (typeof document === 'undefined' || !document.body) {\n return {\n findings: [],\n errors: 0,\n warnings: 0,\n passed: true,\n };\n }\n\n const target = container ?? document.body;\n\n const allFindings: AuditFinding[] = [\n ...auditImages(target),\n ...auditFormInputs(target),\n ...auditInteractiveElements(target),\n ...auditHeadings(target),\n ...auditAria(target),\n ...auditLandmarks(target),\n ];\n\n const errors = allFindings.filter((f) => f.severity === 'error').length;\n const warnings = allFindings.filter((f) => f.severity === 'warning').length;\n\n return {\n findings: allFindings,\n errors,\n warnings,\n passed: errors === 0,\n };\n};\n","/**\n * Reactive media preference signals for accessibility.\n *\n * Provides reactive signals that track the user's system-level\n * accessibility preferences (reduced motion, color scheme, contrast).\n *\n * @module bquery/a11y\n */\n\nimport { readonly, signal, type ReadonlySignal } from '../reactive/index';\nimport type { ColorScheme, ContrastPreference, MediaPreferenceSignal } from './types';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent | MediaQueryList) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent | MediaQueryList) => void) => void;\n};\n\nconst bindMediaQueryListener = (\n mql: MediaQueryList,\n handler: (event: MediaQueryListEvent | MediaQueryList) => void\n): (() => void) | undefined => {\n if (typeof mql.addEventListener === 'function') {\n mql.addEventListener('change', handler);\n return (): void => {\n mql.removeEventListener('change', handler);\n };\n }\n\n const legacyMql = mql as LegacyMediaQueryList;\n if (typeof legacyMql.addListener === 'function') {\n legacyMql.addListener(handler);\n return (): void => {\n legacyMql.removeListener?.(handler);\n };\n }\n\n return undefined;\n};\n\nconst withDestroy = <T>(\n signalHandle: ReadonlySignal<T>,\n cleanup: () => void\n): MediaPreferenceSignal<T> => {\n let destroyImpl = cleanup;\n const handle = signalHandle as MediaPreferenceSignal<T>;\n Object.defineProperty(handle, 'destroy', {\n configurable: true,\n enumerable: false,\n value: (): void => {\n const currentDestroy = destroyImpl;\n // Make cleanup idempotent so repeated destroy() calls from user code stay safe.\n destroyImpl = (): void => {};\n currentDestroy();\n },\n });\n return handle;\n};\n\n/**\n * Creates a reactive signal that tracks a CSS media query.\n *\n * @param query - The media query string\n * @param initialValue - Fallback value when `matchMedia` is unavailable\n * @returns A readonly signal handle that updates when the query match changes\n * @internal\n */\nconst createMediaSignal = (\n query: string,\n initialValue: boolean\n): MediaPreferenceSignal<boolean> => {\n const s = signal(initialValue);\n let destroy = (): void => {\n s.dispose();\n };\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n try {\n const mql = window.matchMedia(query);\n s.value = mql.matches;\n\n const handler = (e: MediaQueryListEvent | MediaQueryList): void => {\n s.value = e.matches;\n };\n\n const cleanupMql = bindMediaQueryListener(mql, handler);\n if (cleanupMql) {\n destroy = (): void => {\n cleanupMql();\n s.dispose();\n };\n }\n } catch {\n // matchMedia may throw in non-browser environments\n }\n }\n\n return withDestroy(readonly(s), destroy);\n};\n\n/**\n * Returns a reactive signal indicating whether the user prefers reduced motion.\n *\n * Tracks the `(prefers-reduced-motion: reduce)` media query. Returns `true`\n * when the user has requested reduced motion in their system settings.\n *\n * @returns A readonly reactive signal handle. Call `destroy()` to remove listeners.\n *\n * @example\n * ```ts\n * import { prefersReducedMotion } from '@bquery/bquery/a11y';\n * import { effect } from '@bquery/bquery/reactive';\n *\n * const reduced = prefersReducedMotion();\n * effect(() => {\n * if (reduced.value) {\n * console.log('User prefers reduced motion');\n * }\n * });\n * ```\n */\nexport const prefersReducedMotion = (): MediaPreferenceSignal<boolean> => {\n return createMediaSignal('(prefers-reduced-motion: reduce)', false);\n};\n\n/**\n * Returns a reactive signal tracking the user's preferred color scheme.\n *\n * Tracks the `(prefers-color-scheme: dark)` media query. Returns `'dark'`\n * when the user prefers a dark color scheme, `'light'` otherwise.\n *\n * @returns A readonly reactive signal handle with `'light'` or `'dark'`\n *\n * @example\n * ```ts\n * import { prefersColorScheme } from '@bquery/bquery/a11y';\n * import { effect } from '@bquery/bquery/reactive';\n *\n * const scheme = prefersColorScheme();\n * effect(() => {\n * document.body.dataset.theme = scheme.value;\n * });\n * ```\n */\nexport const prefersColorScheme = (): MediaPreferenceSignal<ColorScheme> => {\n const s = signal<ColorScheme>('light');\n let destroy = (): void => {\n s.dispose();\n };\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n try {\n const mql = window.matchMedia('(prefers-color-scheme: dark)');\n s.value = mql.matches ? 'dark' : 'light';\n\n const handler = (e: MediaQueryListEvent | MediaQueryList): void => {\n s.value = e.matches ? 'dark' : 'light';\n };\n\n const cleanupMql = bindMediaQueryListener(mql, handler);\n if (cleanupMql) {\n destroy = (): void => {\n cleanupMql();\n s.dispose();\n };\n }\n } catch {\n // matchMedia may throw in non-browser environments\n }\n }\n\n return withDestroy(readonly(s), destroy);\n};\n\n/**\n * Returns a reactive signal tracking the user's contrast preference.\n *\n * Tracks the `(prefers-contrast)` media query. Returns:\n * - `'more'` — user prefers higher contrast\n * - `'less'` — user prefers lower contrast\n * - `'custom'` — user has set a custom contrast level\n * - `'no-preference'` — no explicit preference\n *\n * @returns A readonly reactive signal handle\n *\n * @example\n * ```ts\n * import { prefersContrast } from '@bquery/bquery/a11y';\n * import { effect } from '@bquery/bquery/reactive';\n *\n * const contrast = prefersContrast();\n * effect(() => {\n * if (contrast.value === 'more') {\n * document.body.classList.add('high-contrast');\n * }\n * });\n * ```\n */\nexport const prefersContrast = (): MediaPreferenceSignal<ContrastPreference> => {\n const s = signal<ContrastPreference>('no-preference');\n let destroy = (): void => {\n s.dispose();\n };\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n let mql: MediaQueryList | undefined;\n let mqlLess: MediaQueryList | undefined;\n let mqlCustom: MediaQueryList | undefined;\n\n const update = (): void => {\n // Defensive guard for environments where matchMedia setup fails before\n // listeners are attached; update() is only expected to run after init.\n if (!mql || !mqlLess || !mqlCustom) {\n return;\n }\n\n if (mql.matches) {\n s.value = 'more';\n } else if (mqlLess.matches) {\n s.value = 'less';\n } else if (mqlCustom.matches) {\n s.value = 'custom';\n } else {\n s.value = 'no-preference';\n }\n };\n\n // Listen for changes on the contrast preference variants\n try {\n mql = window.matchMedia('(prefers-contrast: more)');\n mqlLess = window.matchMedia('(prefers-contrast: less)');\n mqlCustom = window.matchMedia('(prefers-contrast: custom)');\n update();\n const cleanupFns = [mql, mqlLess, mqlCustom]\n .map((entry) =>\n bindMediaQueryListener(entry, () => {\n update();\n })\n )\n .filter((cleanup): cleanup is () => void => cleanup !== undefined);\n\n if (cleanupFns.length > 0) {\n destroy = (): void => {\n for (const cleanup of cleanupFns) {\n cleanup();\n }\n s.dispose();\n };\n }\n } catch {\n // matchMedia may throw in non-browser environments\n }\n }\n\n return withDestroy(readonly(s), destroy);\n};\n","/**\n * Roving tab index utility for arrow-key navigation within groups.\n *\n * Implements the WAI-ARIA roving tabindex pattern: only one item\n * in the group has tabindex=\"0\" (the active item), while all others\n * have tabindex=\"-1\". Arrow keys move focus between items.\n *\n * @module bquery/a11y\n */\n\nimport type { RovingTabIndexHandle, RovingTabIndexOptions } from './types';\n\n/**\n * Sets up roving tab index navigation for a group of elements.\n *\n * Only the active item receives `tabindex=\"0\"`, making it the only\n * tabbable element in the group. Arrow keys move focus between items,\n * and Home/End jump to the first/last item.\n *\n * @param container - The parent element containing the navigable items\n * @param itemSelector - CSS selector for the navigable items within the container\n * @param options - Configuration options\n * @returns A handle with `destroy()`, `focusItem()`, and `activeIndex()`\n *\n * @example\n * ```ts\n * import { rovingTabIndex } from '@bquery/bquery/a11y';\n *\n * const toolbar = document.querySelector('[role=\"toolbar\"]');\n * const handle = rovingTabIndex(toolbar, 'button', {\n * orientation: 'horizontal',\n * wrap: true,\n * });\n *\n * // Later, clean up\n * handle.destroy();\n * ```\n */\nexport const rovingTabIndex = (\n container: HTMLElement,\n itemSelector: string,\n options: RovingTabIndexOptions = {}\n): RovingTabIndexHandle => {\n const { wrap = true, orientation = 'vertical', onActivate } = options;\n\n let currentIndex = 0;\n const originalTabIndexes = new Map<HTMLElement, string | null>();\n\n const getItems = (): HTMLElement[] => {\n return Array.from(container.querySelectorAll(itemSelector)) as HTMLElement[];\n };\n\n const trackItems = (items: HTMLElement[]): void => {\n for (const item of items) {\n if (!originalTabIndexes.has(item)) {\n originalTabIndexes.set(item, item.getAttribute('tabindex'));\n }\n }\n };\n\n const setActiveItem = (index: number): void => {\n const items = getItems();\n if (items.length === 0) return;\n trackItems(items);\n\n // Clamp index\n const clampedIndex = Math.max(0, Math.min(index, items.length - 1));\n\n // Update tabindex on all items\n for (let i = 0; i < items.length; i++) {\n items[i].setAttribute('tabindex', i === clampedIndex ? '0' : '-1');\n }\n\n currentIndex = clampedIndex;\n items[clampedIndex].focus();\n onActivate?.(items[clampedIndex], clampedIndex);\n };\n\n const isRelevantKey = (key: string): boolean => {\n if (key === 'Home' || key === 'End') return true;\n\n switch (orientation) {\n case 'horizontal':\n return key === 'ArrowLeft' || key === 'ArrowRight';\n case 'vertical':\n return key === 'ArrowUp' || key === 'ArrowDown';\n case 'both':\n return (\n key === 'ArrowLeft' || key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowDown'\n );\n default:\n return false;\n }\n };\n\n const handleKeyDown = (event: KeyboardEvent): void => {\n if (!isRelevantKey(event.key)) return;\n\n const items = getItems();\n if (items.length === 0) return;\n\n event.preventDefault();\n\n let nextIndex = currentIndex;\n\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n nextIndex = currentIndex + 1;\n if (nextIndex >= items.length) {\n nextIndex = wrap ? 0 : items.length - 1;\n }\n break;\n\n case 'ArrowUp':\n case 'ArrowLeft':\n nextIndex = currentIndex - 1;\n if (nextIndex < 0) {\n nextIndex = wrap ? items.length - 1 : 0;\n }\n break;\n\n case 'Home':\n nextIndex = 0;\n break;\n\n case 'End':\n nextIndex = items.length - 1;\n break;\n }\n\n setActiveItem(nextIndex);\n };\n\n // Initialize: set tabindex on all items\n const items = getItems();\n trackItems(items);\n for (let i = 0; i < items.length; i++) {\n items[i].setAttribute('tabindex', i === 0 ? '0' : '-1');\n }\n\n container.addEventListener('keydown', handleKeyDown);\n\n return {\n destroy: () => {\n container.removeEventListener('keydown', handleKeyDown);\n // Restore original tabindex values\n for (const [item, originalTabIndex] of originalTabIndexes) {\n if (originalTabIndex === null) {\n item.removeAttribute('tabindex');\n } else {\n item.setAttribute('tabindex', originalTabIndex);\n }\n }\n originalTabIndexes.clear();\n },\n\n focusItem: (index: number) => {\n setActiveItem(index);\n },\n\n activeIndex: () => currentIndex,\n };\n};\n","/**\n * Auto-generated skip navigation link utility.\n *\n * Creates a visually-hidden (but keyboard-focusable) \"Skip to content\"\n * link that becomes visible on focus, letting keyboard users bypass\n * repeated navigation.\n *\n * @module bquery/a11y\n */\n\nimport type { SkipLinkHandle, SkipLinkOptions } from './types';\n\n/** Default CSS for the skip link — visually hidden until focused. */\nconst DEFAULT_STYLES = `\n position: absolute;\n top: -9999px;\n left: -9999px;\n z-index: 999999;\n padding: 0.5em 1em;\n background: #000;\n color: #fff;\n font-size: 1rem;\n text-decoration: none;\n border-radius: 0 0 4px 0;\n outline: 2px solid #4A90D9;\n outline-offset: 2px;\n`;\n\nconst FOCUSED_STYLES = `\n top: 0;\n left: 0;\n`;\n\nlet skipTargetIdCounter = 0;\nconst generatedSkipTargetRefs = new Map<string, { count: number; target: HTMLElement }>();\n\nconst hasSkipLinkEnvironment = (): boolean => {\n if (typeof document === 'undefined') {\n return false;\n }\n\n return (\n typeof document.createElement === 'function' &&\n typeof document.querySelector === 'function' &&\n typeof document.getElementById === 'function' &&\n document.body !== null &&\n document.body !== undefined\n );\n};\n\nconst createNoopSkipLinkHandle = (): SkipLinkHandle => ({\n destroy: () => {},\n element: null,\n});\n\n/**\n * Creates a skip navigation link that jumps to the specified target.\n *\n * The link is visually hidden by default and becomes visible when\n * it receives keyboard focus. This follows the WCAG 2.4.1 \"Bypass Blocks\"\n * success criterion.\n *\n * @param targetSelector - CSS selector for the main content area (e.g. `'#main'`, `'main'`)\n * @param options - Configuration options\n * @returns A handle with `destroy()` method and reference to the created element\n *\n * @example\n * ```ts\n * import { skipLink } from '@bquery/bquery/a11y';\n *\n * // Creates a \"Skip to main content\" link pointing to <main>\n * const handle = skipLink('#main-content');\n *\n * // Custom text\n * const handle2 = skipLink('#content', { text: 'Jump to content' });\n *\n * // Remove when no longer needed\n * handle.destroy();\n * ```\n */\nexport const skipLink = (targetSelector: string, options: SkipLinkOptions = {}): SkipLinkHandle => {\n if (!hasSkipLinkEnvironment()) {\n return createNoopSkipLinkHandle();\n }\n\n const { text = 'Skip to main content', className = 'bq-skip-link' } = options;\n let trackedGeneratedTargetId: string | undefined;\n let trackedFocusTarget:\n | { target: HTMLElement; hadTabIndex: boolean; previousTabIndex: string | null }\n | undefined;\n const safeQuerySelector = (selector: string): HTMLElement | null => {\n try {\n return document.querySelector(selector) as HTMLElement | null;\n } catch {\n return null;\n }\n };\n const releaseTrackedGeneratedTargetId = (): void => {\n if (!trackedGeneratedTargetId) return;\n\n const entry = generatedSkipTargetRefs.get(trackedGeneratedTargetId);\n const remainingRefs = (entry?.count ?? 0) - 1;\n if (remainingRefs <= 0) {\n generatedSkipTargetRefs.delete(trackedGeneratedTargetId);\n if (entry?.target.isConnected && entry.target.id === trackedGeneratedTargetId) {\n entry.target.removeAttribute('id');\n }\n } else {\n generatedSkipTargetRefs.set(trackedGeneratedTargetId, {\n count: remainingRefs,\n target: entry!.target,\n });\n }\n\n trackedGeneratedTargetId = undefined;\n };\n const restoreTrackedFocusTarget = (): void => {\n if (!trackedFocusTarget) return;\n\n const { target, hadTabIndex, previousTabIndex } = trackedFocusTarget;\n if (target.isConnected) {\n if (hadTabIndex) {\n target.setAttribute('tabindex', previousTabIndex ?? '');\n } else {\n target.removeAttribute('tabindex');\n }\n }\n\n trackedFocusTarget = undefined;\n };\n const ensureTargetFocusable = (target: HTMLElement): void => {\n if (trackedFocusTarget?.target === target) {\n return;\n }\n\n restoreTrackedFocusTarget();\n\n if (target.hasAttribute('tabindex')) {\n trackedFocusTarget = {\n target,\n hadTabIndex: true,\n previousTabIndex: target.getAttribute('tabindex'),\n };\n return;\n }\n\n if (target.tabIndex !== -1) {\n return;\n }\n\n trackedFocusTarget = {\n target,\n hadTabIndex: false,\n previousTabIndex: null,\n };\n target.setAttribute('tabindex', '-1');\n };\n const trackGeneratedTargetId = (target: HTMLElement, id: string): void => {\n if (trackedGeneratedTargetId === id) return;\n releaseTrackedGeneratedTargetId();\n const entry = generatedSkipTargetRefs.get(id);\n generatedSkipTargetRefs.set(id, {\n count: (entry?.count ?? 0) + 1,\n target,\n });\n trackedGeneratedTargetId = id;\n };\n const resolveTarget = (): HTMLElement | null => {\n if (targetSelector.startsWith('#')) {\n const id = targetSelector.slice(1);\n const byId = id ? (document.getElementById(id) as HTMLElement | null) : null;\n if (byId) {\n return byId;\n }\n\n return safeQuerySelector(targetSelector);\n }\n\n const byId = document.getElementById(targetSelector) as HTMLElement | null;\n if (byId) {\n return byId;\n }\n\n return safeQuerySelector(targetSelector);\n };\n\n const ensureTargetId = (target: HTMLElement): string => {\n if (target.id) {\n const generatedTarget = generatedSkipTargetRefs.get(target.id);\n if (generatedTarget?.target === target) {\n trackGeneratedTargetId(target, target.id);\n }\n return target.id;\n }\n\n let generatedTargetId: string;\n do {\n skipTargetIdCounter += 1;\n generatedTargetId = `bq-skip-target-${skipTargetIdCounter}`;\n } while (document.getElementById(generatedTargetId) !== null);\n\n target.id = generatedTargetId;\n trackGeneratedTargetId(target, generatedTargetId);\n return generatedTargetId;\n };\n\n const link = document.createElement('a');\n const initialTarget = resolveTarget();\n link.href = targetSelector.startsWith('#')\n ? targetSelector\n : initialTarget\n ? `#${ensureTargetId(initialTarget)}`\n : `#${targetSelector}`;\n link.textContent = text;\n link.className = className;\n link.setAttribute('style', DEFAULT_STYLES);\n\n link.addEventListener('focus', () => {\n link.setAttribute('style', DEFAULT_STYLES + FOCUSED_STYLES);\n });\n\n link.addEventListener('blur', () => {\n link.setAttribute('style', DEFAULT_STYLES);\n });\n\n link.addEventListener('click', (e) => {\n e.preventDefault();\n\n const target = resolveTarget();\n if (!target) {\n return;\n }\n\n link.href = `#${ensureTargetId(target)}`;\n // Make the target focusable if it isn't already\n ensureTargetFocusable(target);\n target.focus();\n });\n\n // Insert as the first child of <body>\n if (document.body.firstChild) {\n document.body.insertBefore(link, document.body.firstChild);\n } else {\n document.body.appendChild(link);\n }\n\n return {\n destroy: () => {\n restoreTrackedFocusTarget();\n releaseTrackedGeneratedTargetId();\n link.remove();\n },\n element: link,\n };\n};\n","/**\n * Focus trapping utility for modals, dialogs, and popover content.\n *\n * Constrains keyboard focus within a container so that Tab and Shift+Tab\n * cycle only through the container's focusable elements.\n *\n * @module bquery/a11y\n */\n\nimport type { FocusTrapHandle, TrapFocusOptions } from './types';\n\n/** Selector for elements that can receive focus. */\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n 'details > summary',\n 'audio[controls]',\n 'video[controls]',\n].join(', ');\n\n/**\n * Gets all focusable elements within a container.\n *\n * @param container - The container element\n * @returns Array of focusable elements\n * @internal\n */\nexport const getFocusableElements = (container: Element): HTMLElement[] => {\n const elements = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)) as HTMLElement[];\n return elements.filter(\n (el) => !el.hasAttribute('disabled') && el.tabIndex !== -1 && el.getClientRects().length > 0\n );\n};\n\n/**\n * Resolves an element from a string selector or returns the element directly.\n * @internal\n */\nconst resolveElement = (\n target: HTMLElement | string | undefined,\n container: Element\n): HTMLElement | null => {\n if (!target) return null;\n if (typeof target === 'string') {\n return container.querySelector(target) as HTMLElement | null;\n }\n return target;\n};\n\n/**\n * Traps keyboard focus within a container element.\n *\n * When activated, Tab and Shift+Tab will cycle only through focusable\n * elements within the container. Useful for modals, dialogs, and\n * dropdown menus.\n *\n * @param container - The DOM element to trap focus within\n * @param options - Configuration options\n * @returns A handle with a `release()` method to deactivate the trap\n *\n * @example\n * ```ts\n * import { trapFocus } from '@bquery/bquery/a11y';\n *\n * const dialog = document.querySelector('#my-dialog');\n * const trap = trapFocus(dialog, { escapeDeactivates: true });\n *\n * // Later, release the trap\n * trap.release();\n * ```\n */\nexport const trapFocus = (\n container: HTMLElement,\n options: TrapFocusOptions = {}\n): FocusTrapHandle => {\n const { escapeDeactivates = true, onEscape, initialFocus, returnFocus } = options;\n\n if (\n typeof document === 'undefined' ||\n typeof document.addEventListener !== 'function' ||\n typeof document.removeEventListener !== 'function'\n ) {\n let active = false;\n return {\n get active() {\n return active;\n },\n release: () => {\n active = false;\n },\n };\n }\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n let active = true;\n\n const handleKeyDown = (event: KeyboardEvent): void => {\n if (!active) return;\n\n if (event.key === 'Escape' && escapeDeactivates) {\n event.preventDefault();\n handle.release();\n onEscape?.();\n return;\n }\n\n if (event.key !== 'Tab') return;\n\n const focusable = getFocusableElements(container);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (event.shiftKey) {\n // Shift+Tab: if at first element, wrap to last\n if (document.activeElement === first || !container.contains(document.activeElement)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n // Tab: if at last element, wrap to first\n if (document.activeElement === last || !container.contains(document.activeElement)) {\n event.preventDefault();\n first.focus();\n }\n }\n };\n\n // Attach the event listener\n document.addEventListener('keydown', handleKeyDown, true);\n\n // Set initial focus\n const initialEl = resolveElement(initialFocus, container);\n if (initialEl) {\n initialEl.focus();\n } else {\n const focusable = getFocusableElements(container);\n if (focusable.length > 0) {\n focusable[0].focus();\n }\n }\n\n const handle: FocusTrapHandle = {\n get active() {\n return active;\n },\n\n release: () => {\n if (!active) return;\n active = false;\n document.removeEventListener('keydown', handleKeyDown, true);\n\n // Return focus\n const returnEl = resolveElement(returnFocus, document.body);\n if (returnEl) {\n returnEl.focus();\n } else if (previouslyFocused && previouslyFocused.focus) {\n previouslyFocused.focus();\n }\n },\n };\n\n return handle;\n};\n\n/**\n * Releases a focus trap handle.\n * This is a convenience function — in most cases, use the `release()`\n * method on the individual trap handle directly.\n *\n * @deprecated Prefer using the handle returned by `trapFocus()` directly.\n */\nexport const releaseFocus = (handle: FocusTrapHandle): void => {\n handle.release();\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"a11y-DgUQ8-fI.js","names":[],"sources":["../src/a11y/announce.ts","../src/a11y/audit.ts","../src/a11y/media-preferences.ts","../src/a11y/roving-tab-index.ts","../src/a11y/skip-link.ts","../src/a11y/trap-focus.ts"],"sourcesContent":["/**\n * Screen reader announcement utility using ARIA live regions.\n *\n * Creates and manages off-screen live regions to announce dynamic\n * content changes to assistive technologies.\n *\n * @module bquery/a11y\n */\n\nimport type { AnnouncePriority } from './types';\n\n/** Cache for live region containers, keyed by priority. */\nconst liveRegions = new Map<AnnouncePriority, HTMLElement>();\nconst pendingAnnouncements = new Map<AnnouncePriority, ReturnType<typeof setTimeout>>();\n\n/**\n * Delay in milliseconds before updating the live region text.\n * This ensures screen readers detect the content change even when\n * the same message is announced consecutively — clearing first and\n * setting after a short timer delay forces a new live-region mutation event.\n * @internal\n */\nconst ANNOUNCEMENT_DELAY_MS = 50;\n\n/**\n * Gets or creates a visually-hidden ARIA live region for the given priority.\n *\n * @param priority - The aria-live priority level\n * @returns The live region element\n * @internal\n */\nconst getOrCreateLiveRegion = (priority: AnnouncePriority): HTMLElement => {\n const existing = liveRegions.get(priority);\n if (existing && existing.isConnected) {\n return existing;\n }\n\n const el = document.createElement('div');\n el.setAttribute('aria-live', priority);\n el.setAttribute('aria-atomic', 'true');\n el.setAttribute('role', priority === 'assertive' ? 'alert' : 'status');\n\n // Visually hidden but accessible to screen readers\n Object.assign(el.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: '0',\n });\n\n document.body.appendChild(el);\n liveRegions.set(priority, el);\n\n return el;\n};\n\n/**\n * Announces a message to screen readers via an ARIA live region.\n *\n * The message is injected into a visually-hidden live region element.\n * Screen readers will pick up the change and announce it to the user.\n *\n * @param message - The text message to announce\n * @param priority - The urgency level: `'polite'` (default) or `'assertive'`\n *\n * @example\n * ```ts\n * import { announceToScreenReader } from '@bquery/bquery/a11y';\n *\n * // Polite announcement (waits for idle)\n * announceToScreenReader('3 search results found');\n *\n * // Assertive announcement (interrupts current speech)\n * announceToScreenReader('Error: Please fix the form', 'assertive');\n * ```\n */\nexport const announceToScreenReader = (\n message: string,\n priority: AnnouncePriority = 'polite'\n): void => {\n if (!message) return;\n if (typeof document === 'undefined' || !document.body) return;\n\n const region = getOrCreateLiveRegion(priority);\n const pendingTimeout = pendingAnnouncements.get(priority);\n if (pendingTimeout !== undefined) {\n clearTimeout(pendingTimeout);\n }\n\n // Clear first, then set after a short timer delay to ensure screen readers\n // detect the change even if the same message is announced twice.\n region.textContent = '';\n\n // Use setTimeout to ensure the DOM update triggers a live region change event\n const timeout = setTimeout(() => {\n pendingAnnouncements.delete(priority);\n if (region.isConnected) {\n region.textContent = message;\n }\n }, ANNOUNCEMENT_DELAY_MS);\n\n pendingAnnouncements.set(priority, timeout);\n};\n\n/**\n * Removes all live region elements created by `announceToScreenReader`.\n * Useful for cleanup in tests or when unmounting an application.\n *\n * @example\n * ```ts\n * import { clearAnnouncements } from '@bquery/bquery/a11y';\n *\n * clearAnnouncements();\n * ```\n */\nexport const clearAnnouncements = (): void => {\n for (const timeout of pendingAnnouncements.values()) {\n clearTimeout(timeout);\n }\n pendingAnnouncements.clear();\n\n for (const [, el] of liveRegions) {\n el.remove();\n }\n liveRegions.clear();\n};\n","/**\n * Development-time accessibility audit utility.\n *\n * Scans DOM elements for common accessibility issues such as missing\n * alt text on images, missing labels on form inputs, empty links/buttons,\n * and incorrect ARIA usage.\n *\n * @module bquery/a11y\n */\n\nimport type { AuditFinding, AuditResult, AuditSeverity } from './types';\n\n/**\n * Creates a finding object.\n * @internal\n */\nconst finding = (\n severity: AuditSeverity,\n message: string,\n element: Element,\n rule: string\n): AuditFinding => ({\n severity,\n message,\n element,\n rule,\n});\n\n/**\n * Checks images for missing alt attributes.\n * @internal\n */\nconst auditImages = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n const images = container.querySelectorAll('img');\n\n for (const img of images) {\n if (!img.hasAttribute('alt')) {\n findings.push(\n finding(\n 'error',\n 'Image is missing an alt attribute. Add alt=\"\" for decorative images or a descriptive alt text.',\n img,\n 'img-alt'\n )\n );\n } else if (img.getAttribute('alt') === '' && !img.hasAttribute('role')) {\n findings.push(\n finding(\n 'info',\n 'Image has empty alt text. Consider adding role=\"presentation\" if decorative.',\n img,\n 'img-decorative'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Checks form inputs for missing labels.\n * @internal\n */\nconst auditFormInputs = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n const inputs = container.querySelectorAll('input, select, textarea');\n\n for (const input of inputs) {\n const type = input.getAttribute('type');\n\n // Hidden, submit, and button inputs don't need labels\n if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'reset') {\n continue;\n }\n\n const id = input.getAttribute('id');\n const hasLabel = id ? !!container.querySelector(`label[for=\"${id}\"]`) : false;\n const hasAriaLabel = input.hasAttribute('aria-label') || input.hasAttribute('aria-labelledby');\n const hasTitle = input.hasAttribute('title');\n const isWrappedInLabel = input.closest('label') !== null;\n\n if (!hasLabel && !hasAriaLabel && !hasTitle && !isWrappedInLabel) {\n findings.push(\n finding(\n 'error',\n `Form input is missing a label. Add a <label for=\"id\">, aria-label, or aria-labelledby attribute.`,\n input,\n 'input-label'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Checks for empty interactive elements (buttons, links).\n * @internal\n */\nconst auditInteractiveElements = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n\n // Check buttons\n const buttons = container.querySelectorAll('button');\n for (const btn of buttons) {\n const hasText = (btn.textContent ?? '').trim().length > 0;\n const hasAriaLabel = btn.hasAttribute('aria-label') || btn.hasAttribute('aria-labelledby');\n const hasTitle = btn.hasAttribute('title');\n\n if (!hasText && !hasAriaLabel && !hasTitle) {\n findings.push(\n finding(\n 'error',\n 'Button has no accessible name. Add text content, aria-label, or title.',\n btn,\n 'button-name'\n )\n );\n }\n }\n\n // Check links\n const links = container.querySelectorAll('a[href]');\n for (const link of links) {\n const hasText = (link.textContent ?? '').trim().length > 0;\n const hasAriaLabel = link.hasAttribute('aria-label') || link.hasAttribute('aria-labelledby');\n const hasTitle = link.hasAttribute('title');\n const hasImage = link.querySelector('img[alt]') !== null;\n\n if (!hasText && !hasAriaLabel && !hasTitle && !hasImage) {\n findings.push(\n finding(\n 'error',\n 'Link has no accessible name. Add text content, aria-label, or title.',\n link,\n 'link-name'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Checks heading hierarchy for skipped levels.\n * @internal\n */\nconst auditHeadings = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n const headings = container.querySelectorAll('h1, h2, h3, h4, h5, h6');\n\n let previousLevel = 0;\n\n for (const heading of headings) {\n const level = parseInt(heading.tagName.charAt(1), 10);\n\n if (previousLevel > 0 && level > previousLevel + 1) {\n findings.push(\n finding(\n 'warning',\n `Heading level skipped: <${heading.tagName.toLowerCase()}> follows <h${previousLevel}>. Don't skip heading levels.`,\n heading,\n 'heading-order'\n )\n );\n }\n\n if ((heading.textContent ?? '').trim().length === 0) {\n findings.push(finding('warning', 'Heading element is empty.', heading, 'heading-empty'));\n }\n\n previousLevel = level;\n }\n\n return findings;\n};\n\n/**\n * Checks for valid ARIA attribute usage.\n * @internal\n */\nconst auditAria = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n\n // Check aria-labelledby references exist\n const labelled = container.querySelectorAll('[aria-labelledby]');\n for (const el of labelled) {\n const ids = (el.getAttribute('aria-labelledby') ?? '').split(/\\s+/);\n for (const id of ids) {\n if (id && !document.getElementById(id)) {\n findings.push(\n finding(\n 'error',\n `aria-labelledby references \"${id}\" which does not exist in the document.`,\n el,\n 'aria-labelledby-ref'\n )\n );\n }\n }\n }\n\n // Check aria-describedby references exist\n const described = container.querySelectorAll('[aria-describedby]');\n for (const el of described) {\n const ids = (el.getAttribute('aria-describedby') ?? '').split(/\\s+/);\n for (const id of ids) {\n if (id && !document.getElementById(id)) {\n findings.push(\n finding(\n 'error',\n `aria-describedby references \"${id}\" which does not exist in the document.`,\n el,\n 'aria-describedby-ref'\n )\n );\n }\n }\n }\n\n return findings;\n};\n\n/**\n * Checks for sufficient document landmarks.\n * @internal\n */\nconst auditLandmarks = (container: Element): AuditFinding[] => {\n const findings: AuditFinding[] = [];\n\n // Only audit the document body or top-level container\n if (container === document.body || container === document.documentElement) {\n const hasMain = !!container.querySelector('main') || !!container.querySelector('[role=\"main\"]');\n\n if (!hasMain) {\n findings.push(\n finding(\n 'warning',\n 'Page is missing a <main> landmark. Add <main> or role=\"main\" to the primary content area.',\n container,\n 'landmark-main'\n )\n );\n }\n }\n\n return findings;\n};\n\n/**\n * Runs a development-time accessibility audit on a container element.\n *\n * Checks for common accessibility issues including:\n * - Missing alt text on images\n * - Missing labels on form inputs\n * - Empty buttons and links\n * - Heading hierarchy issues\n * - Invalid ARIA references\n * - Missing document landmarks\n *\n * This is intended as a development tool — not a replacement for\n * manual testing or professional accessibility audits.\n *\n * @param container - The element to audit (defaults to `document.body`)\n * @returns An audit result with findings, counts, and pass/fail status\n *\n * @example\n * ```ts\n * import { auditA11y } from '@bquery/bquery/a11y';\n *\n * const result = auditA11y();\n * if (!result.passed) {\n * console.warn(`Found ${result.errors} accessibility errors:`);\n * for (const f of result.findings) {\n * console.warn(`[${f.severity}] ${f.message}`, f.element);\n * }\n * }\n * ```\n */\nexport const auditA11y = (container?: Element): AuditResult => {\n if (typeof document === 'undefined' || !document.body) {\n return {\n findings: [],\n errors: 0,\n warnings: 0,\n passed: true,\n };\n }\n\n const target = container ?? document.body;\n\n const allFindings: AuditFinding[] = [\n ...auditImages(target),\n ...auditFormInputs(target),\n ...auditInteractiveElements(target),\n ...auditHeadings(target),\n ...auditAria(target),\n ...auditLandmarks(target),\n ];\n\n const errors = allFindings.filter((f) => f.severity === 'error').length;\n const warnings = allFindings.filter((f) => f.severity === 'warning').length;\n\n return {\n findings: allFindings,\n errors,\n warnings,\n passed: errors === 0,\n };\n};\n","/**\n * Reactive media preference signals for accessibility.\n *\n * Provides reactive signals that track the user's system-level\n * accessibility preferences (reduced motion, color scheme, contrast).\n *\n * @module bquery/a11y\n */\n\nimport { readonly, signal, type ReadonlySignal } from '../reactive/index';\nimport type { ColorScheme, ContrastPreference, MediaPreferenceSignal } from './types';\n\ntype LegacyMediaQueryList = MediaQueryList & {\n addListener?: (listener: (event: MediaQueryListEvent | MediaQueryList) => void) => void;\n removeListener?: (listener: (event: MediaQueryListEvent | MediaQueryList) => void) => void;\n};\n\nconst bindMediaQueryListener = (\n mql: MediaQueryList,\n handler: (event: MediaQueryListEvent | MediaQueryList) => void\n): (() => void) | undefined => {\n if (typeof mql.addEventListener === 'function') {\n mql.addEventListener('change', handler);\n return (): void => {\n mql.removeEventListener('change', handler);\n };\n }\n\n const legacyMql = mql as LegacyMediaQueryList;\n if (typeof legacyMql.addListener === 'function') {\n legacyMql.addListener(handler);\n return (): void => {\n legacyMql.removeListener?.(handler);\n };\n }\n\n return undefined;\n};\n\nconst withDestroy = <T>(\n signalHandle: ReadonlySignal<T>,\n cleanup: () => void\n): MediaPreferenceSignal<T> => {\n let destroyImpl = cleanup;\n const handle = signalHandle as MediaPreferenceSignal<T>;\n Object.defineProperty(handle, 'destroy', {\n configurable: true,\n enumerable: false,\n value: (): void => {\n const currentDestroy = destroyImpl;\n // Make cleanup idempotent so repeated destroy() calls from user code stay safe.\n destroyImpl = (): void => {};\n currentDestroy();\n },\n });\n return handle;\n};\n\n/**\n * Creates a reactive signal that tracks a CSS media query.\n *\n * @param query - The media query string\n * @param initialValue - Fallback value when `matchMedia` is unavailable\n * @returns A readonly signal handle that updates when the query match changes\n * @internal\n */\nconst createMediaSignal = (\n query: string,\n initialValue: boolean\n): MediaPreferenceSignal<boolean> => {\n const s = signal(initialValue);\n let destroy = (): void => {\n s.dispose();\n };\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n try {\n const mql = window.matchMedia(query);\n s.value = mql.matches;\n\n const handler = (e: MediaQueryListEvent | MediaQueryList): void => {\n s.value = e.matches;\n };\n\n const cleanupMql = bindMediaQueryListener(mql, handler);\n if (cleanupMql) {\n destroy = (): void => {\n cleanupMql();\n s.dispose();\n };\n }\n } catch {\n // matchMedia may throw in non-browser environments\n }\n }\n\n return withDestroy(readonly(s), destroy);\n};\n\n/**\n * Returns a reactive signal indicating whether the user prefers reduced motion.\n *\n * Tracks the `(prefers-reduced-motion: reduce)` media query. Returns `true`\n * when the user has requested reduced motion in their system settings.\n *\n * @returns A readonly reactive signal handle. Call `destroy()` to remove listeners.\n *\n * @example\n * ```ts\n * import { prefersReducedMotion } from '@bquery/bquery/a11y';\n * import { effect } from '@bquery/bquery/reactive';\n *\n * const reduced = prefersReducedMotion();\n * effect(() => {\n * if (reduced.value) {\n * console.log('User prefers reduced motion');\n * }\n * });\n * ```\n */\nexport const prefersReducedMotion = (): MediaPreferenceSignal<boolean> => {\n return createMediaSignal('(prefers-reduced-motion: reduce)', false);\n};\n\n/**\n * Returns a reactive signal tracking the user's preferred color scheme.\n *\n * Tracks the `(prefers-color-scheme: dark)` media query. Returns `'dark'`\n * when the user prefers a dark color scheme, `'light'` otherwise.\n *\n * @returns A readonly reactive signal handle with `'light'` or `'dark'`\n *\n * @example\n * ```ts\n * import { prefersColorScheme } from '@bquery/bquery/a11y';\n * import { effect } from '@bquery/bquery/reactive';\n *\n * const scheme = prefersColorScheme();\n * effect(() => {\n * document.body.dataset.theme = scheme.value;\n * });\n * ```\n */\nexport const prefersColorScheme = (): MediaPreferenceSignal<ColorScheme> => {\n const s = signal<ColorScheme>('light');\n let destroy = (): void => {\n s.dispose();\n };\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n try {\n const mql = window.matchMedia('(prefers-color-scheme: dark)');\n s.value = mql.matches ? 'dark' : 'light';\n\n const handler = (e: MediaQueryListEvent | MediaQueryList): void => {\n s.value = e.matches ? 'dark' : 'light';\n };\n\n const cleanupMql = bindMediaQueryListener(mql, handler);\n if (cleanupMql) {\n destroy = (): void => {\n cleanupMql();\n s.dispose();\n };\n }\n } catch {\n // matchMedia may throw in non-browser environments\n }\n }\n\n return withDestroy(readonly(s), destroy);\n};\n\n/**\n * Returns a reactive signal tracking the user's contrast preference.\n *\n * Tracks the `(prefers-contrast)` media query. Returns:\n * - `'more'` — user prefers higher contrast\n * - `'less'` — user prefers lower contrast\n * - `'custom'` — user has set a custom contrast level\n * - `'no-preference'` — no explicit preference\n *\n * @returns A readonly reactive signal handle\n *\n * @example\n * ```ts\n * import { prefersContrast } from '@bquery/bquery/a11y';\n * import { effect } from '@bquery/bquery/reactive';\n *\n * const contrast = prefersContrast();\n * effect(() => {\n * if (contrast.value === 'more') {\n * document.body.classList.add('high-contrast');\n * }\n * });\n * ```\n */\nexport const prefersContrast = (): MediaPreferenceSignal<ContrastPreference> => {\n const s = signal<ContrastPreference>('no-preference');\n let destroy = (): void => {\n s.dispose();\n };\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n let mql: MediaQueryList | undefined;\n let mqlLess: MediaQueryList | undefined;\n let mqlCustom: MediaQueryList | undefined;\n\n const update = (): void => {\n // Defensive guard for environments where matchMedia setup fails before\n // listeners are attached; update() is only expected to run after init.\n if (!mql || !mqlLess || !mqlCustom) {\n return;\n }\n\n if (mql.matches) {\n s.value = 'more';\n } else if (mqlLess.matches) {\n s.value = 'less';\n } else if (mqlCustom.matches) {\n s.value = 'custom';\n } else {\n s.value = 'no-preference';\n }\n };\n\n // Listen for changes on the contrast preference variants\n try {\n mql = window.matchMedia('(prefers-contrast: more)');\n mqlLess = window.matchMedia('(prefers-contrast: less)');\n mqlCustom = window.matchMedia('(prefers-contrast: custom)');\n update();\n const cleanupFns = [mql, mqlLess, mqlCustom]\n .map((entry) =>\n bindMediaQueryListener(entry, () => {\n update();\n })\n )\n .filter((cleanup): cleanup is () => void => cleanup !== undefined);\n\n if (cleanupFns.length > 0) {\n destroy = (): void => {\n for (const cleanup of cleanupFns) {\n cleanup();\n }\n s.dispose();\n };\n }\n } catch {\n // matchMedia may throw in non-browser environments\n }\n }\n\n return withDestroy(readonly(s), destroy);\n};\n","/**\n * Roving tab index utility for arrow-key navigation within groups.\n *\n * Implements the WAI-ARIA roving tabindex pattern: only one item\n * in the group has tabindex=\"0\" (the active item), while all others\n * have tabindex=\"-1\". Arrow keys move focus between items.\n *\n * @module bquery/a11y\n */\n\nimport type { RovingTabIndexHandle, RovingTabIndexOptions } from './types';\n\n/**\n * Sets up roving tab index navigation for a group of elements.\n *\n * Only the active item receives `tabindex=\"0\"`, making it the only\n * tabbable element in the group. Arrow keys move focus between items,\n * and Home/End jump to the first/last item.\n *\n * @param container - The parent element containing the navigable items\n * @param itemSelector - CSS selector for the navigable items within the container\n * @param options - Configuration options\n * @returns A handle with `destroy()`, `focusItem()`, and `activeIndex()`\n *\n * @example\n * ```ts\n * import { rovingTabIndex } from '@bquery/bquery/a11y';\n *\n * const toolbar = document.querySelector('[role=\"toolbar\"]');\n * const handle = rovingTabIndex(toolbar, 'button', {\n * orientation: 'horizontal',\n * wrap: true,\n * });\n *\n * // Later, clean up\n * handle.destroy();\n * ```\n */\nexport const rovingTabIndex = (\n container: HTMLElement,\n itemSelector: string,\n options: RovingTabIndexOptions = {}\n): RovingTabIndexHandle => {\n const { wrap = true, orientation = 'vertical', onActivate } = options;\n\n let currentIndex = 0;\n const originalTabIndexes = new Map<HTMLElement, string | null>();\n\n const getItems = (): HTMLElement[] => {\n return Array.from(container.querySelectorAll(itemSelector)) as HTMLElement[];\n };\n\n const trackItems = (items: HTMLElement[]): void => {\n for (const item of items) {\n if (!originalTabIndexes.has(item)) {\n originalTabIndexes.set(item, item.getAttribute('tabindex'));\n }\n }\n };\n\n const setActiveItem = (index: number): void => {\n const items = getItems();\n if (items.length === 0) return;\n trackItems(items);\n\n // Clamp index\n const clampedIndex = Math.max(0, Math.min(index, items.length - 1));\n\n // Update tabindex on all items\n for (let i = 0; i < items.length; i++) {\n items[i].setAttribute('tabindex', i === clampedIndex ? '0' : '-1');\n }\n\n currentIndex = clampedIndex;\n items[clampedIndex].focus();\n onActivate?.(items[clampedIndex], clampedIndex);\n };\n\n const isRelevantKey = (key: string): boolean => {\n if (key === 'Home' || key === 'End') return true;\n\n switch (orientation) {\n case 'horizontal':\n return key === 'ArrowLeft' || key === 'ArrowRight';\n case 'vertical':\n return key === 'ArrowUp' || key === 'ArrowDown';\n case 'both':\n return (\n key === 'ArrowLeft' || key === 'ArrowRight' || key === 'ArrowUp' || key === 'ArrowDown'\n );\n default:\n return false;\n }\n };\n\n const handleKeyDown = (event: KeyboardEvent): void => {\n if (!isRelevantKey(event.key)) return;\n\n const items = getItems();\n if (items.length === 0) return;\n\n event.preventDefault();\n\n let nextIndex = currentIndex;\n\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n nextIndex = currentIndex + 1;\n if (nextIndex >= items.length) {\n nextIndex = wrap ? 0 : items.length - 1;\n }\n break;\n\n case 'ArrowUp':\n case 'ArrowLeft':\n nextIndex = currentIndex - 1;\n if (nextIndex < 0) {\n nextIndex = wrap ? items.length - 1 : 0;\n }\n break;\n\n case 'Home':\n nextIndex = 0;\n break;\n\n case 'End':\n nextIndex = items.length - 1;\n break;\n }\n\n setActiveItem(nextIndex);\n };\n\n // Initialize: set tabindex on all items\n const items = getItems();\n trackItems(items);\n for (let i = 0; i < items.length; i++) {\n items[i].setAttribute('tabindex', i === 0 ? '0' : '-1');\n }\n\n container.addEventListener('keydown', handleKeyDown);\n\n return {\n destroy: () => {\n container.removeEventListener('keydown', handleKeyDown);\n // Restore original tabindex values\n for (const [item, originalTabIndex] of originalTabIndexes) {\n if (originalTabIndex === null) {\n item.removeAttribute('tabindex');\n } else {\n item.setAttribute('tabindex', originalTabIndex);\n }\n }\n originalTabIndexes.clear();\n },\n\n focusItem: (index: number) => {\n setActiveItem(index);\n },\n\n activeIndex: () => currentIndex,\n };\n};\n","/**\n * Auto-generated skip navigation link utility.\n *\n * Creates a visually-hidden (but keyboard-focusable) \"Skip to content\"\n * link that becomes visible on focus, letting keyboard users bypass\n * repeated navigation.\n *\n * @module bquery/a11y\n */\n\nimport type { SkipLinkHandle, SkipLinkOptions } from './types';\n\n/** Default CSS for the skip link — visually hidden until focused. */\nconst DEFAULT_STYLES = `\n position: absolute;\n top: -9999px;\n left: -9999px;\n z-index: 999999;\n padding: 0.5em 1em;\n background: #000;\n color: #fff;\n font-size: 1rem;\n text-decoration: none;\n border-radius: 0 0 4px 0;\n outline: 2px solid #4A90D9;\n outline-offset: 2px;\n`;\n\nconst FOCUSED_STYLES = `\n top: 0;\n left: 0;\n`;\n\nlet skipTargetIdCounter = 0;\nconst generatedSkipTargetRefs = new Map<string, { count: number; target: HTMLElement }>();\n\nconst hasSkipLinkEnvironment = (): boolean => {\n if (typeof document === 'undefined') {\n return false;\n }\n\n return (\n typeof document.createElement === 'function' &&\n typeof document.querySelector === 'function' &&\n typeof document.getElementById === 'function' &&\n document.body !== null &&\n document.body !== undefined\n );\n};\n\nconst createNoopSkipLinkHandle = (): SkipLinkHandle => ({\n destroy: () => {},\n element: null,\n});\n\n/**\n * Creates a skip navigation link that jumps to the specified target.\n *\n * The link is visually hidden by default and becomes visible when\n * it receives keyboard focus. This follows the WCAG 2.4.1 \"Bypass Blocks\"\n * success criterion.\n *\n * @param targetSelector - CSS selector for the main content area (e.g. `'#main'`, `'main'`)\n * @param options - Configuration options\n * @returns A handle with `destroy()` method and reference to the created element\n *\n * @example\n * ```ts\n * import { skipLink } from '@bquery/bquery/a11y';\n *\n * // Creates a \"Skip to main content\" link pointing to <main>\n * const handle = skipLink('#main-content');\n *\n * // Custom text\n * const handle2 = skipLink('#content', { text: 'Jump to content' });\n *\n * // Remove when no longer needed\n * handle.destroy();\n * ```\n */\nexport const skipLink = (targetSelector: string, options: SkipLinkOptions = {}): SkipLinkHandle => {\n if (!hasSkipLinkEnvironment()) {\n return createNoopSkipLinkHandle();\n }\n\n const { text = 'Skip to main content', className = 'bq-skip-link' } = options;\n let trackedGeneratedTargetId: string | undefined;\n let trackedFocusTarget:\n | { target: HTMLElement; hadTabIndex: boolean; previousTabIndex: string | null }\n | undefined;\n const safeQuerySelector = (selector: string): HTMLElement | null => {\n try {\n return document.querySelector(selector) as HTMLElement | null;\n } catch {\n return null;\n }\n };\n const releaseTrackedGeneratedTargetId = (): void => {\n if (!trackedGeneratedTargetId) return;\n\n const entry = generatedSkipTargetRefs.get(trackedGeneratedTargetId);\n const remainingRefs = (entry?.count ?? 0) - 1;\n if (remainingRefs <= 0) {\n generatedSkipTargetRefs.delete(trackedGeneratedTargetId);\n if (entry?.target.isConnected && entry.target.id === trackedGeneratedTargetId) {\n entry.target.removeAttribute('id');\n }\n } else {\n generatedSkipTargetRefs.set(trackedGeneratedTargetId, {\n count: remainingRefs,\n target: entry!.target,\n });\n }\n\n trackedGeneratedTargetId = undefined;\n };\n const restoreTrackedFocusTarget = (): void => {\n if (!trackedFocusTarget) return;\n\n const { target, hadTabIndex, previousTabIndex } = trackedFocusTarget;\n if (target.isConnected) {\n if (hadTabIndex) {\n target.setAttribute('tabindex', previousTabIndex ?? '');\n } else {\n target.removeAttribute('tabindex');\n }\n }\n\n trackedFocusTarget = undefined;\n };\n const ensureTargetFocusable = (target: HTMLElement): void => {\n if (trackedFocusTarget?.target === target) {\n return;\n }\n\n restoreTrackedFocusTarget();\n\n if (target.hasAttribute('tabindex')) {\n trackedFocusTarget = {\n target,\n hadTabIndex: true,\n previousTabIndex: target.getAttribute('tabindex'),\n };\n return;\n }\n\n if (target.tabIndex !== -1) {\n return;\n }\n\n trackedFocusTarget = {\n target,\n hadTabIndex: false,\n previousTabIndex: null,\n };\n target.setAttribute('tabindex', '-1');\n };\n const trackGeneratedTargetId = (target: HTMLElement, id: string): void => {\n if (trackedGeneratedTargetId === id) return;\n releaseTrackedGeneratedTargetId();\n const entry = generatedSkipTargetRefs.get(id);\n generatedSkipTargetRefs.set(id, {\n count: (entry?.count ?? 0) + 1,\n target,\n });\n trackedGeneratedTargetId = id;\n };\n const resolveTarget = (): HTMLElement | null => {\n if (targetSelector.startsWith('#')) {\n const id = targetSelector.slice(1);\n const byId = id ? (document.getElementById(id) as HTMLElement | null) : null;\n if (byId) {\n return byId;\n }\n\n return safeQuerySelector(targetSelector);\n }\n\n const byId = document.getElementById(targetSelector) as HTMLElement | null;\n if (byId) {\n return byId;\n }\n\n return safeQuerySelector(targetSelector);\n };\n\n const ensureTargetId = (target: HTMLElement): string => {\n if (target.id) {\n const generatedTarget = generatedSkipTargetRefs.get(target.id);\n if (generatedTarget?.target === target) {\n trackGeneratedTargetId(target, target.id);\n }\n return target.id;\n }\n\n let generatedTargetId: string;\n do {\n skipTargetIdCounter += 1;\n generatedTargetId = `bq-skip-target-${skipTargetIdCounter}`;\n } while (document.getElementById(generatedTargetId) !== null);\n\n target.id = generatedTargetId;\n trackGeneratedTargetId(target, generatedTargetId);\n return generatedTargetId;\n };\n\n const link = document.createElement('a');\n const initialTarget = resolveTarget();\n link.href = targetSelector.startsWith('#')\n ? targetSelector\n : initialTarget\n ? `#${ensureTargetId(initialTarget)}`\n : `#${targetSelector}`;\n link.textContent = text;\n link.className = className;\n link.setAttribute('style', DEFAULT_STYLES);\n\n link.addEventListener('focus', () => {\n link.setAttribute('style', DEFAULT_STYLES + FOCUSED_STYLES);\n });\n\n link.addEventListener('blur', () => {\n link.setAttribute('style', DEFAULT_STYLES);\n });\n\n link.addEventListener('click', (e) => {\n e.preventDefault();\n\n const target = resolveTarget();\n if (!target) {\n return;\n }\n\n link.href = `#${ensureTargetId(target)}`;\n // Make the target focusable if it isn't already\n ensureTargetFocusable(target);\n target.focus();\n });\n\n // Insert as the first child of <body>\n if (document.body.firstChild) {\n document.body.insertBefore(link, document.body.firstChild);\n } else {\n document.body.appendChild(link);\n }\n\n return {\n destroy: () => {\n restoreTrackedFocusTarget();\n releaseTrackedGeneratedTargetId();\n link.remove();\n },\n element: link,\n };\n};\n","/**\n * Focus trapping utility for modals, dialogs, and popover content.\n *\n * Constrains keyboard focus within a container so that Tab and Shift+Tab\n * cycle only through the container's focusable elements.\n *\n * @module bquery/a11y\n */\n\nimport type { FocusTrapHandle, TrapFocusOptions } from './types';\n\n/** Selector for elements that can receive focus. */\nconst FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n 'details > summary',\n 'audio[controls]',\n 'video[controls]',\n].join(', ');\n\n/**\n * Gets all focusable elements within a container.\n *\n * @param container - The container element\n * @returns Array of focusable elements\n * @internal\n */\nexport const getFocusableElements = (container: Element): HTMLElement[] => {\n const elements = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)) as HTMLElement[];\n return elements.filter(\n (el) => !el.hasAttribute('disabled') && el.tabIndex !== -1 && el.getClientRects().length > 0\n );\n};\n\n/**\n * Resolves an element from a string selector or returns the element directly.\n * @internal\n */\nconst resolveElement = (\n target: HTMLElement | string | undefined,\n container: Element\n): HTMLElement | null => {\n if (!target) return null;\n if (typeof target === 'string') {\n return container.querySelector(target) as HTMLElement | null;\n }\n return target;\n};\n\n/**\n * Traps keyboard focus within a container element.\n *\n * When activated, Tab and Shift+Tab will cycle only through focusable\n * elements within the container. Useful for modals, dialogs, and\n * dropdown menus.\n *\n * @param container - The DOM element to trap focus within\n * @param options - Configuration options\n * @returns A handle with a `release()` method to deactivate the trap\n *\n * @example\n * ```ts\n * import { trapFocus } from '@bquery/bquery/a11y';\n *\n * const dialog = document.querySelector('#my-dialog');\n * const trap = trapFocus(dialog, { escapeDeactivates: true });\n *\n * // Later, release the trap\n * trap.release();\n * ```\n */\nexport const trapFocus = (\n container: HTMLElement,\n options: TrapFocusOptions = {}\n): FocusTrapHandle => {\n const { escapeDeactivates = true, onEscape, initialFocus, returnFocus } = options;\n\n if (\n typeof document === 'undefined' ||\n typeof document.addEventListener !== 'function' ||\n typeof document.removeEventListener !== 'function'\n ) {\n let active = false;\n return {\n get active() {\n return active;\n },\n release: () => {\n active = false;\n },\n };\n }\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n let active = true;\n\n const handleKeyDown = (event: KeyboardEvent): void => {\n if (!active) return;\n\n if (event.key === 'Escape' && escapeDeactivates) {\n event.preventDefault();\n handle.release();\n onEscape?.();\n return;\n }\n\n if (event.key !== 'Tab') return;\n\n const focusable = getFocusableElements(container);\n if (focusable.length === 0) {\n event.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (event.shiftKey) {\n // Shift+Tab: if at first element, wrap to last\n if (document.activeElement === first || !container.contains(document.activeElement)) {\n event.preventDefault();\n last.focus();\n }\n } else {\n // Tab: if at last element, wrap to first\n if (document.activeElement === last || !container.contains(document.activeElement)) {\n event.preventDefault();\n first.focus();\n }\n }\n };\n\n // Attach the event listener\n document.addEventListener('keydown', handleKeyDown, true);\n\n // Set initial focus\n const initialEl = resolveElement(initialFocus, container);\n if (initialEl) {\n initialEl.focus();\n } else {\n const focusable = getFocusableElements(container);\n if (focusable.length > 0) {\n focusable[0].focus();\n }\n }\n\n const handle: FocusTrapHandle = {\n get active() {\n return active;\n },\n\n release: () => {\n if (!active) return;\n active = false;\n document.removeEventListener('keydown', handleKeyDown, true);\n\n // Return focus\n const returnEl = resolveElement(returnFocus, document.body);\n if (returnEl) {\n returnEl.focus();\n } else if (previouslyFocused && previouslyFocused.focus) {\n previouslyFocused.focus();\n }\n },\n };\n\n return handle;\n};\n\n/**\n * Releases a focus trap handle.\n * This is a convenience function — in most cases, use the `release()`\n * method on the individual trap handle directly.\n *\n * @deprecated Prefer using the handle returned by `trapFocus()` directly.\n */\nexport const releaseFocus = (handle: FocusTrapHandle): void => {\n handle.release();\n};\n"],"mappings":"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"}
|
package/dist/a11y.es.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as s, c as a, d as r, i as o, l as n, n as t, o as c, r as u, s as i, t as p, u as d } from "./a11y-
|
|
1
|
+
import { a as s, c as a, d as r, i as o, l as n, n as t, o as c, r as u, s as i, t as p, u as d } from "./a11y-DgUQ8-fI.js";
|
|
2
2
|
export {
|
|
3
3
|
d as announceToScreenReader,
|
|
4
4
|
n as auditA11y,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as L, l as N, n as I, t as j, u as D } from "./sanitize-
|
|
1
|
+
import { f as L, l as N, n as I, t as j, u as D } from "./sanitize-DOMkRO9G.js";
|
|
2
2
|
import { n as Q } from "./core-CongXJuo.js";
|
|
3
3
|
import { n as U } from "./config-DhT9auRm.js";
|
|
4
4
|
import { t as q } from "./effect-Cc51IH91.js";
|
|
@@ -682,4 +682,4 @@ export {
|
|
|
682
682
|
he as t
|
|
683
683
|
};
|
|
684
684
|
|
|
685
|
-
//# sourceMappingURL=component-
|
|
685
|
+
//# sourceMappingURL=component-D8ydhe58.js.map
|