@bquery/bquery 1.11.0 → 1.12.0
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 +79 -59
- package/dist/{a11y-DgUQ8-fI.js → a11y-IV_bfLyn.js} +3 -3
- package/dist/{a11y-DgUQ8-fI.js.map → a11y-IV_bfLyn.js.map} +1 -1
- package/dist/a11y.es.mjs +1 -1
- package/dist/{component-D8ydhe58.js → component-BtsRbf6c.js} +7 -7
- package/dist/{component-D8ydhe58.js.map → component-BtsRbf6c.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/{concurrency-BU1wPEsZ.js → concurrency-kycgAZvW.js} +3 -3
- package/dist/{concurrency-BU1wPEsZ.js.map → concurrency-kycgAZvW.js.map} +1 -1
- package/dist/concurrency.es.mjs +1 -1
- package/dist/{config-DhT9auRm.js → config-BP7KwiR5.js} +1 -1
- package/dist/{config-DhT9auRm.js.map → config-BP7KwiR5.js.map} +1 -1
- package/dist/constraints-Dlbx_m1b.js.map +1 -1
- package/dist/core-tOP6QOrY.js.map +1 -1
- package/dist/{core-CongXJuo.js → core-yg9rJXiR.js} +1 -1
- package/dist/{core-CongXJuo.js.map → core-yg9rJXiR.js.map} +1 -1
- package/dist/custom-directives-5DlKqvd2.js.map +1 -1
- package/dist/devtools-QosAqo0T.js.map +1 -1
- package/dist/dnd-d2OU4len.js.map +1 -1
- package/dist/{effect-Cc51IH91.js → effect-v8OIEmPs.js} +2 -2
- package/dist/{effect-Cc51IH91.js.map → effect-v8OIEmPs.js.map} +1 -1
- package/dist/env-PvwYHnJq.js.map +1 -1
- package/dist/{forms-BLx4ZzT7.js → forms-DYcdlk_h.js} +8 -8
- package/dist/{forms-BLx4ZzT7.js.map → forms-DYcdlk_h.js.map} +1 -1
- package/dist/forms.es.mjs +1 -1
- package/dist/full.d.ts +11 -11
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +324 -279
- package/dist/full.iife.js +26 -26
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +26 -26
- package/dist/full.umd.js.map +1 -1
- package/dist/function-Cybd57JV.js.map +1 -1
- package/dist/{i18n--p7PM-9r.js → i18n-unHU1jMo.js} +3 -3
- package/dist/{i18n--p7PM-9r.js.map → i18n-unHU1jMo.js.map} +1 -1
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.es.mjs +198 -196
- package/dist/match-CrZRVC4z.js.map +1 -1
- package/dist/{media-gjbWNq50.js → media-Chh6mA0v.js} +3 -3
- package/dist/{media-gjbWNq50.js.map → media-Chh6mA0v.js.map} +1 -1
- package/dist/media.es.mjs +1 -1
- package/dist/{motion-BBMso9Ir.js → motion-27Od9aFE.js} +2 -2
- package/dist/{motion-BBMso9Ir.js.map → motion-27Od9aFE.js.map} +1 -1
- package/dist/motion.es.mjs +1 -1
- package/dist/{mount-0A9qtcRJ.js → mount-DwUFujZ_.js} +4 -4
- package/dist/{mount-0A9qtcRJ.js.map → mount-DwUFujZ_.js.map} +1 -1
- package/dist/object-BCk-1c8T.js.map +1 -1
- package/dist/{platform-BPHIXbw8.js → platform-2YkFA11t.js} +4 -4
- package/dist/{platform-BPHIXbw8.js.map → platform-2YkFA11t.js.map} +1 -1
- package/dist/platform.es.mjs +2 -2
- package/dist/plugin-SZEirbwq.js.map +1 -1
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/signal.d.ts +7 -7
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/websocket.d.ts +15 -3
- package/dist/reactive/websocket.d.ts.map +1 -1
- package/dist/{reactive-BAd2hfl8.js → reactive-BvPR_FYA.js} +10 -10
- package/dist/{reactive-BAd2hfl8.js.map → reactive-BvPR_FYA.js.map} +1 -1
- package/dist/reactive.es.mjs +40 -40
- package/dist/{readonly-C0ZwS1Tf.js → readonly-Br-6pAgj.js} +2 -2
- package/dist/{readonly-C0ZwS1Tf.js.map → readonly-Br-6pAgj.js.map} +1 -1
- package/dist/registry-jpUQHf4E.js.map +1 -1
- package/dist/{router-C4weu0QL.js → router-CbnWKprL.js} +7 -7
- package/dist/{router-C4weu0QL.js.map → router-CbnWKprL.js.map} +1 -1
- package/dist/router.es.mjs +1 -1
- package/dist/sanitize-DOMkRO9G.js.map +1 -1
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/{server-QdyKtCS1.js → server-Dwiq_F49.js} +2 -2
- package/dist/server-Dwiq_F49.js.map +1 -0
- package/dist/server.es.mjs +1 -1
- package/dist/ssr/context.d.ts.map +1 -1
- package/dist/ssr/renderer.d.ts.map +1 -1
- package/dist/ssr/suspense.d.ts.map +1 -1
- package/dist/{ssr-Bt6BQA3J.js → ssr-CqJU1Ogp.js} +8 -8
- package/dist/ssr-CqJU1Ogp.js.map +1 -0
- package/dist/ssr.es.mjs +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/plugins.d.ts +38 -0
- package/dist/store/plugins.d.ts.map +1 -1
- package/dist/{store-DnXuu6Li.js → store-DzrhVQ29.js} +69 -62
- package/dist/store-DzrhVQ29.js.map +1 -0
- package/dist/store.es.mjs +13 -11
- package/dist/storybook.es.mjs.map +1 -1
- package/dist/{testing-CeMUwrRD.js → testing-ByjwS2_D.js} +3 -3
- package/dist/{testing-CeMUwrRD.js.map → testing-ByjwS2_D.js.map} +1 -1
- package/dist/testing.es.mjs +1 -1
- package/dist/type-guards-BMX2c0LP.js.map +1 -1
- package/dist/{untrack-bjWDNdyE.js → untrack-uzz3JDNK.js} +3 -3
- package/dist/{untrack-bjWDNdyE.js.map → untrack-uzz3JDNK.js.map} +1 -1
- package/dist/view.es.mjs +8 -8
- package/package.json +12 -11
- package/src/full.ts +75 -6
- package/src/index.ts +2 -2
- package/src/reactive/index.ts +5 -4
- package/src/reactive/signal.ts +7 -6
- package/src/reactive/websocket.ts +15 -2
- package/src/server/create-server.ts +10 -5
- package/src/server/types.ts +1 -5
- package/src/ssr/context.ts +6 -2
- package/src/ssr/renderer.ts +5 -2
- package/src/ssr/suspense.ts +17 -8
- package/src/store/index.ts +1 -1
- package/src/store/plugins.ts +48 -1
- package/dist/server-QdyKtCS1.js.map +0 -1
- package/dist/ssr-Bt6BQA3J.js.map +0 -1
- package/dist/store-DnXuu6Li.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,34 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
> **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.
|
|
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.12.0:** Store plugins now support teardown via `unregisterPlugin()` and `clearPlugins()`, Reactive WebSocket helpers expose the public `WebSocketSendData` payload type, and the `/full` bundle validation now catches runtime and type export drift before release. The runtime-agnostic SSR/server surface from 1.11.x remains first-class.
|
|
20
19
|
|
|
21
20
|
## Highlights
|
|
22
21
|
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **TypeScript-first**: clear types and strong IDE support.
|
|
30
|
-
- **Tree-shakeable**: import only what you need.
|
|
31
|
-
- **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.
|
|
32
28
|
|
|
33
29
|
## Installation
|
|
34
30
|
|
|
@@ -121,6 +117,7 @@ import {
|
|
|
121
117
|
createRequestQueue,
|
|
122
118
|
deduplicateRequest,
|
|
123
119
|
} from '@bquery/bquery/reactive';
|
|
120
|
+
import type { WebSocketSendData } from '@bquery/bquery/reactive';
|
|
124
121
|
|
|
125
122
|
// Concurrency only
|
|
126
123
|
import {
|
|
@@ -166,7 +163,7 @@ import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery
|
|
|
166
163
|
|
|
167
164
|
// Router, Store, View
|
|
168
165
|
import { createRouter, navigate } from '@bquery/bquery/router';
|
|
169
|
-
import { createStore, defineStore } from '@bquery/bquery/store';
|
|
166
|
+
import { clearPlugins, createStore, defineStore, unregisterPlugin } from '@bquery/bquery/store';
|
|
170
167
|
import { mount, createTemplate } from '@bquery/bquery/view';
|
|
171
168
|
|
|
172
169
|
// Forms, i18n, accessibility, drag & drop, media
|
|
@@ -187,7 +184,15 @@ import {
|
|
|
187
184
|
import { use } from '@bquery/bquery/plugin';
|
|
188
185
|
import { enableDevtools, inspectSignals } from '@bquery/bquery/devtools';
|
|
189
186
|
import { renderComponent, fireEvent, waitFor } from '@bquery/bquery/testing';
|
|
190
|
-
import {
|
|
187
|
+
import {
|
|
188
|
+
createSSRContext,
|
|
189
|
+
hydrateMount,
|
|
190
|
+
renderToResponse,
|
|
191
|
+
renderToStream,
|
|
192
|
+
renderToString,
|
|
193
|
+
renderToStringAsync,
|
|
194
|
+
serializeStoreState,
|
|
195
|
+
} from '@bquery/bquery/ssr';
|
|
191
196
|
import { createServer } from '@bquery/bquery/server';
|
|
192
197
|
|
|
193
198
|
// Storybook helpers
|
|
@@ -196,29 +201,29 @@ import { storyHtml, when } from '@bquery/bquery/storybook';
|
|
|
196
201
|
|
|
197
202
|
## Modules at a glance
|
|
198
203
|
|
|
199
|
-
| Module | Description |
|
|
200
|
-
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
201
|
-
| **Core** | Selectors, DOM manipulation, events, traversal, and typed utilities |
|
|
202
|
-
| **Reactive** | `signal`, `computed`, `effect`, `watchDebounce`, `watchThrottle`, async data, HTTP clients, polling, pagination, WebSocket / SSE, and REST helpers |
|
|
203
|
-
| **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 |
|
|
204
|
-
| **Component** | Typed Web Components with scoped reactivity and configurable Shadow DOM |
|
|
205
|
-
| **Storybook** | Safe story template helpers with boolean-attribute shorthand |
|
|
206
|
-
| **Motion** | View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines |
|
|
207
|
-
| **Security** | HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition |
|
|
208
|
-
| **Platform** | Storage, cache, cookies, page metadata, announcers, and shared runtime config |
|
|
209
|
-
| **Router** | SPA routing, constrained params, redirects, guards, `useRoute()`, and `<bq-link>` |
|
|
210
|
-
| **Store** | Signal-based state management, persistence, migrations, and
|
|
211
|
-
| **View** | Declarative DOM bindings with `bq-*` directives for content, classes, forms, errors, ARIA, and plugins |
|
|
212
|
-
| **Forms** | Reactive form state with sync/async validation and submit handling |
|
|
213
|
-
| **i18n** | Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting |
|
|
214
|
-
| **A11y** | Focus traps, live-region announcements, roving tabindex, skip links, and audits |
|
|
215
|
-
| **DnD** | Draggable elements, droppable zones, and sortable lists |
|
|
216
|
-
| **Media** | Reactive browser/device signals for viewport, network, battery, geolocation, clipboard, and DOM observers |
|
|
217
|
-
| **Plugin** | Global plugin registration for custom directives and Web Components |
|
|
218
|
-
| **Devtools** | Runtime inspection helpers for signals, stores, components, and timelines |
|
|
219
|
-
| **Testing** | Component mounting, mock signals/router helpers, and async test utilities |
|
|
220
|
-
| **SSR** | Runtime-agnostic server-side rendering (Node ≥ 24, Deno, Bun), streaming, async loaders, hydration islands, head/asset/CSP-nonce management, runtime adapters |
|
|
221
|
-
| **Server** | Express-inspired backend routing, middleware, safe response helpers, SSR-aware request handling, and runtime-agnostic WebSocket sessions |
|
|
204
|
+
| Module | Status | Description |
|
|
205
|
+
| --------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
206
|
+
| **Core** | Stable | Selectors, DOM manipulation, events, traversal, and typed utilities |
|
|
207
|
+
| **Reactive** | Stable | `signal`, `computed`, `effect`, `watchDebounce`, `watchThrottle`, async data, HTTP clients, polling, pagination, WebSocket / SSE, and REST helpers |
|
|
208
|
+
| **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 |
|
|
209
|
+
| **Component** | Stable | Typed Web Components with scoped reactivity and configurable Shadow DOM |
|
|
210
|
+
| **Storybook** | Beta | Safe story template helpers with boolean-attribute shorthand |
|
|
211
|
+
| **Motion** | Stable | View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines |
|
|
212
|
+
| **Security** | Stable | HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition |
|
|
213
|
+
| **Platform** | Stable | Storage, cache, cookies, page metadata, announcers, and shared runtime config |
|
|
214
|
+
| **Router** | Stable | SPA routing, constrained params, redirects, guards, `useRoute()`, and `<bq-link>` |
|
|
215
|
+
| **Store** | Stable | Signal-based state management, persistence, migrations, action hooks, and plugin lifecycle helpers |
|
|
216
|
+
| **View** | Beta | Declarative DOM bindings with `bq-*` directives for content, classes, forms, errors, ARIA, and plugins |
|
|
217
|
+
| **Forms** | Beta | Reactive form state with sync/async validation and submit handling |
|
|
218
|
+
| **i18n** | Beta | Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting |
|
|
219
|
+
| **A11y** | Beta | Focus traps, live-region announcements, roving tabindex, skip links, and audits |
|
|
220
|
+
| **DnD** | Beta | Draggable elements, droppable zones, and sortable lists |
|
|
221
|
+
| **Media** | Beta | Reactive browser/device signals for viewport, network, battery, geolocation, clipboard, and DOM observers |
|
|
222
|
+
| **Plugin** | Beta | Global plugin registration for custom directives and Web Components |
|
|
223
|
+
| **Devtools** | Beta | Runtime inspection helpers for signals, stores, components, and timelines |
|
|
224
|
+
| **Testing** | Beta | Component mounting, mock signals/router helpers, and async test utilities |
|
|
225
|
+
| **SSR** | Experimental | Runtime-agnostic server-side rendering (Node ≥ 24, Deno, Bun), streaming, async loaders, hydration islands, head/asset/CSP-nonce management, runtime adapters |
|
|
226
|
+
| **Server** | Experimental | Express-inspired backend routing, middleware, safe response helpers, SSR-aware request handling, and runtime-agnostic WebSocket sessions |
|
|
222
227
|
|
|
223
228
|
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`.
|
|
224
229
|
|
|
@@ -226,6 +231,8 @@ Reusable workers and pools can also opt into readonly signal mirrors such as `st
|
|
|
226
231
|
|
|
227
232
|
## Quick examples
|
|
228
233
|
|
|
234
|
+
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.
|
|
235
|
+
|
|
229
236
|
### Core – DOM & events
|
|
230
237
|
|
|
231
238
|
```ts
|
|
@@ -786,7 +793,7 @@ modalTrap.release();
|
|
|
786
793
|
import { use } from '@bquery/bquery/plugin';
|
|
787
794
|
import { enableDevtools, getTimeline } from '@bquery/bquery/devtools';
|
|
788
795
|
import { renderComponent, fireEvent } from '@bquery/bquery/testing';
|
|
789
|
-
import { renderToString } from '@bquery/bquery/ssr';
|
|
796
|
+
import { createSSRContext, renderToResponse, renderToString } from '@bquery/bquery/ssr';
|
|
790
797
|
import { createServer } from '@bquery/bquery/server';
|
|
791
798
|
|
|
792
799
|
use({
|
|
@@ -809,7 +816,6 @@ const app = createServer();
|
|
|
809
816
|
app.get('/hello/:name', (ctx) => ctx.json({ name: ctx.params.name, q: ctx.query.q }));
|
|
810
817
|
|
|
811
818
|
// Runtime-agnostic async render with head injection (works on Node, Deno, Bun):
|
|
812
|
-
import { createSSRContext, renderToResponse } from '@bquery/bquery/ssr';
|
|
813
819
|
const ctx = createSSRContext({ request: new Request('http://localhost/') });
|
|
814
820
|
ctx.head.add({ title: 'Home' });
|
|
815
821
|
ctx.assets.module('/app.js');
|
|
@@ -826,10 +832,13 @@ mounted.unmount();
|
|
|
826
832
|
|
|
827
833
|
```ts
|
|
828
834
|
import {
|
|
835
|
+
clearPlugins,
|
|
829
836
|
createStore,
|
|
830
837
|
createPersistedStore,
|
|
831
838
|
defineStore,
|
|
832
839
|
mapGetters,
|
|
840
|
+
registerPlugin,
|
|
841
|
+
unregisterPlugin,
|
|
833
842
|
watchStore,
|
|
834
843
|
} from '@bquery/bquery/store';
|
|
835
844
|
|
|
@@ -873,6 +882,14 @@ watchStore(
|
|
|
873
882
|
console.log('Count changed:', value, getters.doubled);
|
|
874
883
|
}
|
|
875
884
|
);
|
|
885
|
+
|
|
886
|
+
const logger = ({ store, options }) => {
|
|
887
|
+
store.$subscribe((state) => console.log(`[${options.id}]`, state));
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
registerPlugin(logger);
|
|
891
|
+
unregisterPlugin(logger); // true when the plugin registration was present
|
|
892
|
+
clearPlugins(); // useful in test teardown
|
|
876
893
|
```
|
|
877
894
|
|
|
878
895
|
### View – declarative bindings
|
|
@@ -895,12 +912,14 @@ mount('#app', {
|
|
|
895
912
|
|
|
896
913
|
| Browser | Version | Support |
|
|
897
914
|
| ------- | ------- | ------- |
|
|
898
|
-
| Chrome | 90+ | ✅ Full
|
|
899
|
-
| Firefox | 90+ | ✅ Full
|
|
900
|
-
| Safari | 15+ | ✅ Full
|
|
901
|
-
| Edge | 90+ | ✅ Full
|
|
915
|
+
| Chrome | 90+ | ✅ Full |
|
|
916
|
+
| Firefox | 90+ | ✅ Full |
|
|
917
|
+
| Safari | 15+ | ✅ Full |
|
|
918
|
+
| Edge | 90+ | ✅ Full |
|
|
902
919
|
|
|
903
920
|
> **No IE support** by design.
|
|
921
|
+
>
|
|
922
|
+
> Server-side runtimes: Node.js ≥ 24, Bun ≥ 1.3.13, and Deno 2 are supported for SSR and server modules.
|
|
904
923
|
|
|
905
924
|
## Documentation
|
|
906
925
|
|
|
@@ -1007,6 +1026,7 @@ This project provides dedicated context files for AI coding agents:
|
|
|
1007
1026
|
- **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
|
|
1008
1027
|
- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
|
|
1009
1028
|
- **`bun run check:ai-guidance`** — Lightweight sync check for version / engine / AI guidance drift
|
|
1029
|
+
- **`bun run check:full-bundle`** — Static guard for `/full` runtime and type export drift
|
|
1010
1030
|
|
|
1011
1031
|
## License
|
|
1012
1032
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as E } from "./core-
|
|
2
|
-
import { n as I } from "./readonly-
|
|
1
|
+
import { n as E } from "./core-yg9rJXiR.js";
|
|
2
|
+
import { n as I } from "./readonly-Br-6pAgj.js";
|
|
3
3
|
var w = /* @__PURE__ */ new Map(), A = /* @__PURE__ */ new Map(), q = 50, M = (e) => {
|
|
4
4
|
const t = w.get(e);
|
|
5
5
|
if (t && t.isConnected) return t;
|
|
@@ -418,4 +418,4 @@ export {
|
|
|
418
418
|
G as u
|
|
419
419
|
};
|
|
420
420
|
|
|
421
|
-
//# sourceMappingURL=a11y-
|
|
421
|
+
//# sourceMappingURL=a11y-IV_bfLyn.js.map
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"a11y-IV_bfLyn.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-IV_bfLyn.js";
|
|
2
2
|
export {
|
|
3
3
|
d as announceToScreenReader,
|
|
4
4
|
n as auditA11y,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { f as L, l as N, n as I, t as j, u as D } from "./sanitize-DOMkRO9G.js";
|
|
2
|
-
import { n as Q } from "./
|
|
3
|
-
import { n as U } from "./
|
|
4
|
-
import { t as q } from "./effect-
|
|
5
|
-
import { r as W, t as w } from "./untrack-
|
|
2
|
+
import { n as Q } from "./config-BP7KwiR5.js";
|
|
3
|
+
import { n as U } from "./core-yg9rJXiR.js";
|
|
4
|
+
import { t as q } from "./effect-v8OIEmPs.js";
|
|
5
|
+
import { r as W, t as w } from "./untrack-uzz3JDNK.js";
|
|
6
6
|
import { t as J } from "./env-PvwYHnJq.js";
|
|
7
7
|
var F = (r, e) => {
|
|
8
8
|
const { type: o } = e;
|
|
@@ -65,7 +65,7 @@ function R() {
|
|
|
65
65
|
function le(r) {
|
|
66
66
|
const e = y;
|
|
67
67
|
if (!e) throw new Error("bQuery component: useSignal() must be called inside a component lifecycle hook. Avoid calling it directly from render()");
|
|
68
|
-
const o =
|
|
68
|
+
const o = U(r);
|
|
69
69
|
return e.addDisposer(() => o.dispose()), o;
|
|
70
70
|
}
|
|
71
71
|
function ie(r) {
|
|
@@ -364,7 +364,7 @@ var z = /* @__PURE__ */ Symbol("bquery.booleanAttribute"), Y = /^[^\0-\x20"'/>=]
|
|
|
364
364
|
r.disabled ? " disabled" : "",
|
|
365
365
|
`>${c(r.value)}</textarea>`
|
|
366
366
|
].join(""), he = (r = {}) => {
|
|
367
|
-
const e = r.prefix ??
|
|
367
|
+
const e = r.prefix ?? Q().components?.prefix ?? "bq", o = {
|
|
368
368
|
button: `${e}-button`,
|
|
369
369
|
card: `${e}-card`,
|
|
370
370
|
input: `${e}-input`,
|
|
@@ -682,4 +682,4 @@ export {
|
|
|
682
682
|
he as t
|
|
683
683
|
};
|
|
684
684
|
|
|
685
|
-
//# sourceMappingURL=component-
|
|
685
|
+
//# sourceMappingURL=component-BtsRbf6c.js.map
|