@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.
Files changed (110) hide show
  1. package/README.md +79 -59
  2. package/dist/{a11y-DgUQ8-fI.js → a11y-IV_bfLyn.js} +3 -3
  3. package/dist/{a11y-DgUQ8-fI.js.map → a11y-IV_bfLyn.js.map} +1 -1
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/{component-D8ydhe58.js → component-BtsRbf6c.js} +7 -7
  6. package/dist/{component-D8ydhe58.js.map → component-BtsRbf6c.js.map} +1 -1
  7. package/dist/component.es.mjs +1 -1
  8. package/dist/{concurrency-BU1wPEsZ.js → concurrency-kycgAZvW.js} +3 -3
  9. package/dist/{concurrency-BU1wPEsZ.js.map → concurrency-kycgAZvW.js.map} +1 -1
  10. package/dist/concurrency.es.mjs +1 -1
  11. package/dist/{config-DhT9auRm.js → config-BP7KwiR5.js} +1 -1
  12. package/dist/{config-DhT9auRm.js.map → config-BP7KwiR5.js.map} +1 -1
  13. package/dist/constraints-Dlbx_m1b.js.map +1 -1
  14. package/dist/core-tOP6QOrY.js.map +1 -1
  15. package/dist/{core-CongXJuo.js → core-yg9rJXiR.js} +1 -1
  16. package/dist/{core-CongXJuo.js.map → core-yg9rJXiR.js.map} +1 -1
  17. package/dist/custom-directives-5DlKqvd2.js.map +1 -1
  18. package/dist/devtools-QosAqo0T.js.map +1 -1
  19. package/dist/dnd-d2OU4len.js.map +1 -1
  20. package/dist/{effect-Cc51IH91.js → effect-v8OIEmPs.js} +2 -2
  21. package/dist/{effect-Cc51IH91.js.map → effect-v8OIEmPs.js.map} +1 -1
  22. package/dist/env-PvwYHnJq.js.map +1 -1
  23. package/dist/{forms-BLx4ZzT7.js → forms-DYcdlk_h.js} +8 -8
  24. package/dist/{forms-BLx4ZzT7.js.map → forms-DYcdlk_h.js.map} +1 -1
  25. package/dist/forms.es.mjs +1 -1
  26. package/dist/full.d.ts +11 -11
  27. package/dist/full.d.ts.map +1 -1
  28. package/dist/full.es.mjs +324 -279
  29. package/dist/full.iife.js +26 -26
  30. package/dist/full.iife.js.map +1 -1
  31. package/dist/full.umd.js +26 -26
  32. package/dist/full.umd.js.map +1 -1
  33. package/dist/function-Cybd57JV.js.map +1 -1
  34. package/dist/{i18n--p7PM-9r.js → i18n-unHU1jMo.js} +3 -3
  35. package/dist/{i18n--p7PM-9r.js.map → i18n-unHU1jMo.js.map} +1 -1
  36. package/dist/i18n.es.mjs +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.es.mjs +198 -196
  39. package/dist/match-CrZRVC4z.js.map +1 -1
  40. package/dist/{media-gjbWNq50.js → media-Chh6mA0v.js} +3 -3
  41. package/dist/{media-gjbWNq50.js.map → media-Chh6mA0v.js.map} +1 -1
  42. package/dist/media.es.mjs +1 -1
  43. package/dist/{motion-BBMso9Ir.js → motion-27Od9aFE.js} +2 -2
  44. package/dist/{motion-BBMso9Ir.js.map → motion-27Od9aFE.js.map} +1 -1
  45. package/dist/motion.es.mjs +1 -1
  46. package/dist/{mount-0A9qtcRJ.js → mount-DwUFujZ_.js} +4 -4
  47. package/dist/{mount-0A9qtcRJ.js.map → mount-DwUFujZ_.js.map} +1 -1
  48. package/dist/object-BCk-1c8T.js.map +1 -1
  49. package/dist/{platform-BPHIXbw8.js → platform-2YkFA11t.js} +4 -4
  50. package/dist/{platform-BPHIXbw8.js.map → platform-2YkFA11t.js.map} +1 -1
  51. package/dist/platform.es.mjs +2 -2
  52. package/dist/plugin-SZEirbwq.js.map +1 -1
  53. package/dist/reactive/index.d.ts +2 -2
  54. package/dist/reactive/index.d.ts.map +1 -1
  55. package/dist/reactive/signal.d.ts +7 -7
  56. package/dist/reactive/signal.d.ts.map +1 -1
  57. package/dist/reactive/websocket.d.ts +15 -3
  58. package/dist/reactive/websocket.d.ts.map +1 -1
  59. package/dist/{reactive-BAd2hfl8.js → reactive-BvPR_FYA.js} +10 -10
  60. package/dist/{reactive-BAd2hfl8.js.map → reactive-BvPR_FYA.js.map} +1 -1
  61. package/dist/reactive.es.mjs +40 -40
  62. package/dist/{readonly-C0ZwS1Tf.js → readonly-Br-6pAgj.js} +2 -2
  63. package/dist/{readonly-C0ZwS1Tf.js.map → readonly-Br-6pAgj.js.map} +1 -1
  64. package/dist/registry-jpUQHf4E.js.map +1 -1
  65. package/dist/{router-C4weu0QL.js → router-CbnWKprL.js} +7 -7
  66. package/dist/{router-C4weu0QL.js.map → router-CbnWKprL.js.map} +1 -1
  67. package/dist/router.es.mjs +1 -1
  68. package/dist/sanitize-DOMkRO9G.js.map +1 -1
  69. package/dist/server/create-server.d.ts.map +1 -1
  70. package/dist/server/types.d.ts.map +1 -1
  71. package/dist/{server-QdyKtCS1.js → server-Dwiq_F49.js} +2 -2
  72. package/dist/server-Dwiq_F49.js.map +1 -0
  73. package/dist/server.es.mjs +1 -1
  74. package/dist/ssr/context.d.ts.map +1 -1
  75. package/dist/ssr/renderer.d.ts.map +1 -1
  76. package/dist/ssr/suspense.d.ts.map +1 -1
  77. package/dist/{ssr-Bt6BQA3J.js → ssr-CqJU1Ogp.js} +8 -8
  78. package/dist/ssr-CqJU1Ogp.js.map +1 -0
  79. package/dist/ssr.es.mjs +1 -1
  80. package/dist/store/index.d.ts +1 -1
  81. package/dist/store/index.d.ts.map +1 -1
  82. package/dist/store/plugins.d.ts +38 -0
  83. package/dist/store/plugins.d.ts.map +1 -1
  84. package/dist/{store-DnXuu6Li.js → store-DzrhVQ29.js} +69 -62
  85. package/dist/store-DzrhVQ29.js.map +1 -0
  86. package/dist/store.es.mjs +13 -11
  87. package/dist/storybook.es.mjs.map +1 -1
  88. package/dist/{testing-CeMUwrRD.js → testing-ByjwS2_D.js} +3 -3
  89. package/dist/{testing-CeMUwrRD.js.map → testing-ByjwS2_D.js.map} +1 -1
  90. package/dist/testing.es.mjs +1 -1
  91. package/dist/type-guards-BMX2c0LP.js.map +1 -1
  92. package/dist/{untrack-bjWDNdyE.js → untrack-uzz3JDNK.js} +3 -3
  93. package/dist/{untrack-bjWDNdyE.js.map → untrack-uzz3JDNK.js.map} +1 -1
  94. package/dist/view.es.mjs +8 -8
  95. package/package.json +12 -11
  96. package/src/full.ts +75 -6
  97. package/src/index.ts +2 -2
  98. package/src/reactive/index.ts +5 -4
  99. package/src/reactive/signal.ts +7 -6
  100. package/src/reactive/websocket.ts +15 -2
  101. package/src/server/create-server.ts +10 -5
  102. package/src/server/types.ts +1 -5
  103. package/src/ssr/context.ts +6 -2
  104. package/src/ssr/renderer.ts +5 -2
  105. package/src/ssr/suspense.ts +17 -8
  106. package/src/store/index.ts +1 -1
  107. package/src/store/plugins.ts +48 -1
  108. package/dist/server-QdyKtCS1.js.map +0 -1
  109. package/dist/ssr-Bt6BQA3J.js.map +0 -1
  110. package/dist/store-DnXuu6Li.js.map +0 -1
package/README.md CHANGED
@@ -1,34 +1,30 @@
1
- # bQuery.js
2
-
3
- ![bQuery.js Logo](https://raw.githubusercontent.com/bQuery/bQuery/main/assets/bquerry-logo.svg)
4
-
5
- [![Repo](https://img.shields.io/badge/github-bquery%2Fbquery-24292f?logo=github)](https://github.com/bQuery/bQuery)
6
- [![Stars](https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/stargazers)
7
- [![Issues](https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/issues)
8
- [![License](https://img.shields.io/github/license/bquery/bquery?style=flat)](https://github.com/bQuery/bQuery/blob/main/LICENSE.md)
9
- [![npm](https://img.shields.io/npm/v/@bquery/bquery)](https://www.npmjs.com/package/@bquery/bquery)
10
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@bquery/bquery)](https://bundlephobia.com/package/@bquery/bquery)
11
- [![unpkg](https://img.shields.io/badge/unpkg-browse-blue?logo=unpkg)](https://unpkg.com/@bquery/bquery)
12
- [![CodeFactor](https://www.codefactor.io/repository/github/bquery/bquery/badge)](https://www.codefactor.io/repository/github/bquery/bquery)
13
- [![JsDelivr](https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge)](https://www.jsdelivr.com/package/npm/@bquery/bquery)
14
-
15
- **The jQuery for the modern Web Platform.**
16
-
17
- bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, zero-build worker tasks, async data composables, HTTP clients, polling and pagination helpers, realtime transports, REST workflows, lightweight server middleware and WebSocket session routing, Web Components, motion utilities, routing, stores, declarative views, accessibility helpers, forms, i18n, media signals, drag-and-drop, plugins, devtools, testing utilities, and SSR — without a mandatory build step.
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>&nbsp;
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>&nbsp;
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>&nbsp;
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>&nbsp;
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>&nbsp;
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
- - **Zero-build capable**: runs directly in the browser; build tools are optional.
24
- - **Transport-ready reactive data**: fetch composables, HTTP clients, polling, pagination, debounced/throttled watchers, WebSocket / SSE, REST helpers, and request coordination plug directly into signals.
25
- - **Explicit concurrency primitives**: zero-build worker tasks, RPC helpers, bounded pools, reactive worker state mirrors, and collection helpers keep off-main-thread work predictable.
26
- - **Declarative UI bindings**: built-in directives now cover content, events, forms, error output, and reactive ARIA attributes.
27
- - **Security-focused**: DOM writes are sanitized by default; Trusted Types supported.
28
- - **Modular**: the core stays small; extra modules are opt-in.
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 { renderToString, hydrateMount, serializeStoreState } from '@bquery/bquery/ssr';
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 action hooks |
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-CongXJuo.js";
2
- import { n as I } from "./readonly-C0ZwS1Tf.js";
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-DgUQ8-fI.js.map
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":";;AAYA,IAAM,IAAc,oBAAI,IAAA,GAClB,IAAuB,oBAAI,IAAA,GAS3B,IAAwB,IASxB,IAAA,CAAyB,MAA4C;AACzE,QAAM,IAAW,EAAY,IAAI,CAAA;AACjC,MAAI,KAAY,EAAS,YACvB,QAAO;AAGT,QAAM,IAAK,SAAS,cAAc,KAAA;AAClC,SAAA,EAAG,aAAa,aAAa,CAAA,GAC7B,EAAG,aAAa,eAAe,MAAA,GAC/B,EAAG,aAAa,QAAQ,MAAa,cAAc,UAAU,QAAA,GAG7D,OAAO,OAAO,EAAG,OAAO;AAAA,IACtB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,QAAQ;AAAA,GACT,GAED,SAAS,KAAK,YAAY,CAAA,GAC1B,EAAY,IAAI,GAAU,CAAA,GAEnB;GAuBI,IAAA,CACX,GACA,IAA6B,aACpB;AAET,MADI,CAAC,KACD,OAAO,WAAa,OAAe,CAAC,SAAS,KAAM;AAEvD,QAAM,IAAS,EAAsB,CAAA,GAC/B,IAAiB,EAAqB,IAAI,CAAA;AAChD,EAAI,MAAmB,UACrB,aAAa,CAAA,GAKf,EAAO,cAAc;AAGrB,QAAM,IAAU,WAAA,MAAiB;AAC/B,IAAA,EAAqB,OAAO,CAAA,GACxB,EAAO,gBACT,EAAO,cAAc;AAAA,KAEtB,CAAA;AAEH,EAAA,EAAqB,IAAI,GAAU,CAAA;GAcxB,IAAA,MAAiC;AAC5C,aAAW,KAAW,EAAqB,OAAA,EACzC,cAAa,CAAA;AAEf,EAAA,EAAqB,MAAA;AAErB,aAAW,CAAA,EAAG,CAAA,KAAO,EACnB,CAAA,EAAG,OAAA;AAEL,EAAA,EAAY,MAAA;GCjHR,IAAA,CACJ,GACA,GACA,GACA,OACkB;AAAA,EAClB,UAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;IAOI,IAAA,CAAe,MAAuC;AAC1D,QAAM,IAA2B,CAAA,GAC3B,IAAS,EAAU,iBAAiB,KAAA;AAE1C,aAAW,KAAO,EAChB,CAAK,EAAI,aAAa,KAAA,IASX,EAAI,aAAa,KAAA,MAAW,MAAM,CAAC,EAAI,aAAa,MAAA,KAC7D,EAAS,KACP,EACE,QACA,gFACA,GACA,gBAAA,CACD,IAfH,EAAS,KACP,EACE,SACA,kGACA,GACA,SAAA,CACD;AAcP,SAAO;GAOH,IAAA,CAAmB,MAAuC;AAC9D,QAAM,IAA2B,CAAA,GAC3B,IAAS,EAAU,iBAAiB,yBAAA;AAE1C,aAAW,KAAS,GAAQ;AAC1B,UAAM,IAAO,EAAM,aAAa,MAAA;AAGhC,QAAI,MAAS,YAAY,MAAS,YAAY,MAAS,YAAY,MAAS,QAC1E;AAGF,UAAM,IAAK,EAAM,aAAa,IAAA,GACxB,IAAW,IAAK,CAAC,CAAC,EAAU,cAAc,cAAc,CAAA,IAAG,IAAO,IAClE,IAAe,EAAM,aAAa,YAAA,KAAiB,EAAM,aAAa,iBAAA,GACtE,IAAW,EAAM,aAAa,OAAA,GAC9B,IAAmB,EAAM,QAAQ,OAAA,MAAa;AAEpD,IAAI,CAAC,KAAY,CAAC,KAAgB,CAAC,KAAY,CAAC,KAC9C,EAAS,KACP,EACE,SACA,oGACA,GACA,aAAA,CACD;AAAA;AAKP,SAAO;GAOH,IAAA,CAA4B,MAAuC;AACvE,QAAM,IAA2B,CAAA,GAG3B,IAAU,EAAU,iBAAiB,QAAA;AAC3C,aAAW,KAAO,GAAS;AACzB,UAAM,KAAW,EAAI,eAAe,IAAI,KAAA,EAAO,SAAS,GAClD,IAAe,EAAI,aAAa,YAAA,KAAiB,EAAI,aAAa,iBAAA,GAClE,IAAW,EAAI,aAAa,OAAA;AAElC,IAAI,CAAC,KAAW,CAAC,KAAgB,CAAC,KAChC,EAAS,KACP,EACE,SACA,0EACA,GACA,aAAA,CACD;AAAA;AAMP,QAAM,IAAQ,EAAU,iBAAiB,SAAA;AACzC,aAAW,KAAQ,GAAO;AACxB,UAAM,KAAW,EAAK,eAAe,IAAI,KAAA,EAAO,SAAS,GACnD,IAAe,EAAK,aAAa,YAAA,KAAiB,EAAK,aAAa,iBAAA,GACpE,IAAW,EAAK,aAAa,OAAA,GAC7B,IAAW,EAAK,cAAc,UAAA,MAAgB;AAEpD,IAAI,CAAC,KAAW,CAAC,KAAgB,CAAC,KAAY,CAAC,KAC7C,EAAS,KACP,EACE,SACA,wEACA,GACA,WAAA,CACD;AAAA;AAKP,SAAO;GAOH,IAAA,CAAiB,MAAuC;AAC5D,QAAM,IAA2B,CAAA,GAC3B,IAAW,EAAU,iBAAiB,wBAAA;AAE5C,MAAI,IAAgB;AAEpB,aAAW,KAAW,GAAU;AAC9B,UAAM,IAAQ,SAAS,EAAQ,QAAQ,OAAO,CAAA,GAAI,EAAA;AAElD,IAAI,IAAgB,KAAK,IAAQ,IAAgB,KAC/C,EAAS,KACP,EACE,WACA,2BAA2B,EAAQ,QAAQ,YAAA,CAAa,eAAe,CAAA,iCACvE,GACA,eAAA,CACD,IAIA,EAAQ,eAAe,IAAI,KAAA,EAAO,WAAW,KAChD,EAAS,KAAK,EAAQ,WAAW,6BAA6B,GAAS,eAAA,CAAgB,GAGzF,IAAgB;AAAA;AAGlB,SAAO;GAOH,IAAA,CAAa,MAAuC;AACxD,QAAM,IAA2B,CAAA,GAG3B,IAAW,EAAU,iBAAiB,mBAAA;AAC5C,aAAW,KAAM,GAAU;AACzB,UAAM,KAAO,EAAG,aAAa,iBAAA,KAAsB,IAAI,MAAM,KAAA;AAC7D,eAAW,KAAM,EACf,CAAI,KAAM,CAAC,SAAS,eAAe,CAAA,KACjC,EAAS,KACP,EACE,SACA,+BAA+B,CAAA,2CAC/B,GACA,qBAAA,CACD;AAAA;AAOT,QAAM,IAAY,EAAU,iBAAiB,oBAAA;AAC7C,aAAW,KAAM,GAAW;AAC1B,UAAM,KAAO,EAAG,aAAa,kBAAA,KAAuB,IAAI,MAAM,KAAA;AAC9D,eAAW,KAAM,EACf,CAAI,KAAM,CAAC,SAAS,eAAe,CAAA,KACjC,EAAS,KACP,EACE,SACA,gCAAgC,CAAA,2CAChC,GACA,sBAAA,CACD;AAAA;AAMT,SAAO;GAOH,IAAA,CAAkB,MAAuC;AAC7D,QAAM,IAA2B,CAAA;AAGjC,UAAI,MAAc,SAAS,QAAQ,MAAc,SAAS,qBACtC,EAAU,cAAc,MAAA,KAAa,EAAU,cAAc,eAAA,KAG7E,EAAS,KACP,EACE,WACA,6FACA,GACA,eAAA,CACD,IAKA;GAiCI,IAAA,CAAa,MAAqC;AAC7D,MAAI,OAAO,WAAa,OAAe,CAAC,SAAS,KAC/C,QAAO;AAAA,IACL,UAAU,CAAA;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA;AAIZ,QAAM,IAAS,KAAa,SAAS,MAE/B,IAA8B;AAAA,IAClC,GAAG,EAAY,CAAA;AAAA,IACf,GAAG,EAAgB,CAAA;AAAA,IACnB,GAAG,EAAyB,CAAA;AAAA,IAC5B,GAAG,EAAc,CAAA;AAAA,IACjB,GAAG,EAAU,CAAA;AAAA,IACb,GAAG,EAAe,CAAA;AAAA,KAGd,IAAS,EAAY,OAAA,CAAQ,MAAM,EAAE,aAAa,OAAA,EAAS;AAGjE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QAAA;AAAA,IACA,UALe,EAAY,OAAA,CAAQ,MAAM,EAAE,aAAa,SAAA,EAAW;AAAA,IAMnE,QAAQ,MAAW;AAAA;GCtSjB,IAAA,CACJ,GACA,MAC6B;AAC7B,MAAI,OAAO,EAAI,oBAAqB;AAClC,WAAA,EAAI,iBAAiB,UAAU,CAAA,GAC/B,MAAmB;AACjB,MAAA,EAAI,oBAAoB,UAAU,CAAA;AAAA;AAItC,QAAM,IAAY;AAClB,MAAI,OAAO,EAAU,eAAgB;AACnC,WAAA,EAAU,YAAY,CAAA,GACtB,MAAmB;AACjB,MAAA,EAAU,iBAAiB,CAAA;AAAA;GAO3B,IAAA,CACJ,GACA,MAC6B;AAC7B,MAAI,IAAc;AAClB,QAAM,IAAS;AACf,gBAAO,eAAe,GAAQ,WAAW;AAAA,IACvC,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,OAAA,MAAmB;AACjB,YAAM,IAAiB;AAEvB,MAAA,IAAA,MAA0B;AAAA,MAAA,GAC1B,EAAA;AAAA;GAEH,GACM;GAWH,IAAA,CACJ,GACA,MACmC;AACnC,QAAM,IAAI,EAAO,CAAA;AACjB,MAAI,IAAA,MAAsB;AACxB,IAAA,EAAE,QAAA;AAAA;AAGJ,MAAI,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe,WAChE,KAAI;AACF,UAAM,IAAM,OAAO,WAAW,CAAA;AAC9B,IAAA,EAAE,QAAQ,EAAI;AAMd,UAAM,IAAa,EAAuB,GAJpC,CAAW,MAAkD;AACjE,MAAA,EAAE,QAAQ,EAAE;AAAA,KAGiC;AAC/C,IAAI,MACF,IAAA,MAAsB;AACpB,MAAA,EAAA,GACA,EAAE,QAAA;AAAA;UAGA;AAAA,EAAA;AAKV,SAAO,EAAY,EAAS,CAAA,GAAI,CAAA;GAwBrB,IAAA,MACJ,EAAkB,oCAAoC,EAAA,GAsBlD,IAAA,MAA+D;AAC1E,QAAM,IAAI,EAAoB,OAAA;AAC9B,MAAI,IAAA,MAAsB;AACxB,IAAA,EAAE,QAAA;AAAA;AAGJ,MAAI,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe,WAChE,KAAI;AACF,UAAM,IAAM,OAAO,WAAW,8BAAA;AAC9B,IAAA,EAAE,QAAQ,EAAI,UAAU,SAAS;AAMjC,UAAM,IAAa,EAAuB,GAJpC,CAAW,MAAkD;AACjE,MAAA,EAAE,QAAQ,EAAE,UAAU,SAAS;AAAA,KAGc;AAC/C,IAAI,MACF,IAAA,MAAsB;AACpB,MAAA,EAAA,GACA,EAAE,QAAA;AAAA;UAGA;AAAA,EAAA;AAKV,SAAO,EAAY,EAAS,CAAA,GAAI,CAAA;GA2BrB,IAAA,MAAmE;AAC9E,QAAM,IAAI,EAA2B,eAAA;AACrC,MAAI,IAAA,MAAsB;AACxB,IAAA,EAAE,QAAA;AAAA;AAGJ,MAAI,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe,YAAY;AAC5E,QAAI,GACA,GACA;AAEJ,UAAM,IAAA,MAAqB;AAGzB,MAAI,CAAC,KAAO,CAAC,KAAW,CAAC,MAIrB,EAAI,UACN,EAAE,QAAQ,SACD,EAAQ,UACjB,EAAE,QAAQ,SACD,EAAU,UACnB,EAAE,QAAQ,WAEV,EAAE,QAAQ;AAAA;AAKd,QAAI;AACF,MAAA,IAAM,OAAO,WAAW,0BAAA,GACxB,IAAU,OAAO,WAAW,0BAAA,GAC5B,IAAY,OAAO,WAAW,4BAAA,GAC9B,EAAA;AACA,YAAM,IAAa;AAAA,QAAC;AAAA,QAAK;AAAA,QAAS;AAAA,QAC/B,IAAA,CAAK,MACJ,EAAuB,GAAA,MAAa;AAClC,QAAA,EAAA;AAAA,QACA,EAEH,OAAA,CAAQ,MAAmC,MAAY,MAAA;AAE1D,MAAI,EAAW,SAAS,MACtB,IAAA,MAAsB;AACpB,mBAAW,KAAW,EACpB,CAAA,EAAA;AAEF,QAAA,EAAE,QAAA;AAAA;YAGA;AAAA,IAAA;AAAA;AAKV,SAAO,EAAY,EAAS,CAAA,GAAI,CAAA;GCvNrB,IAAA,CACX,GACA,GACA,IAAiC,CAAA,MACR;AACzB,QAAM,EAAE,MAAA,IAAO,IAAM,aAAA,IAAc,YAAY,YAAA,EAAA,IAAe;AAE9D,MAAI,IAAe;AACnB,QAAM,IAAqB,oBAAI,IAAA,GAEzB,IAAA,MACG,MAAM,KAAK,EAAU,iBAAiB,CAAA,CAAa,GAGtD,IAAA,CAAc,MAA+B;AACjD,eAAW,KAAQ,EACjB,CAAK,EAAmB,IAAI,CAAA,KAC1B,EAAmB,IAAI,GAAM,EAAK,aAAa,UAAA,CAAW;AAAA,KAK1D,IAAA,CAAiB,MAAwB;AAC7C,UAAM,IAAQ,EAAA;AACd,QAAI,EAAM,WAAW,EAAG;AACxB,IAAA,EAAW,CAAA;AAGX,UAAM,IAAe,KAAK,IAAI,GAAG,KAAK,IAAI,GAAO,EAAM,SAAS,CAAA,CAAE;AAGlE,aAAS,IAAI,GAAG,IAAI,EAAM,QAAQ,IAChC,CAAA,EAAM,CAAA,EAAG,aAAa,YAAY,MAAM,IAAe,MAAM,IAAA;AAG/D,IAAA,IAAe,GACf,EAAM,CAAA,EAAc,MAAA,GACpB,IAAa,EAAM,CAAA,GAAe,CAAA;AAAA,KAG9B,IAAA,CAAiB,MAAyB;AAC9C,QAAI,MAAQ,UAAU,MAAQ,MAAO,QAAO;AAE5C,YAAQ,GAAR;AAAA,MACE,KAAK;AACH,eAAO,MAAQ,eAAe,MAAQ;AAAA,MACxC,KAAK;AACH,eAAO,MAAQ,aAAa,MAAQ;AAAA,MACtC,KAAK;AACH,eACE,MAAQ,eAAe,MAAQ,gBAAgB,MAAQ,aAAa,MAAQ;AAAA,MAEhF;AACE,eAAO;AAAA;KAIP,IAAA,CAAiB,MAA+B;AACpD,QAAI,CAAC,EAAc,EAAM,GAAA,EAAM;AAE/B,UAAM,IAAQ,EAAA;AACd,QAAI,EAAM,WAAW,EAAG;AAExB,IAAA,EAAM,eAAA;AAEN,QAAI,IAAY;AAEhB,YAAQ,EAAM,KAAd;AAAA,MACE,KAAK;AAAA,MACL,KAAK;AACH,QAAA,IAAY,IAAe,GACvB,KAAa,EAAM,WACrB,IAAY,IAAO,IAAI,EAAM,SAAS;AAExC;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,QAAA,IAAY,IAAe,GACvB,IAAY,MACd,IAAY,IAAO,EAAM,SAAS,IAAI;AAExC;AAAA,MAEF,KAAK;AACH,QAAA,IAAY;AACZ;AAAA,MAEF,KAAK;AACH,QAAA,IAAY,EAAM,SAAS;AAC3B;AAAA;AAGJ,IAAA,EAAc,CAAA;AAAA,KAIV,IAAQ,EAAA;AACd,EAAA,EAAW,CAAA;AACX,WAAS,IAAI,GAAG,IAAI,EAAM,QAAQ,IAChC,CAAA,EAAM,CAAA,EAAG,aAAa,YAAY,MAAM,IAAI,MAAM,IAAA;AAGpD,SAAA,EAAU,iBAAiB,WAAW,CAAA,GAE/B;AAAA,IACL,SAAA,MAAe;AACb,MAAA,EAAU,oBAAoB,WAAW,CAAA;AAEzC,iBAAW,CAAC,GAAM,CAAA,KAAqB,EACrC,CAAI,MAAqB,OACvB,EAAK,gBAAgB,UAAA,IAErB,EAAK,aAAa,YAAY,CAAA;AAGlC,MAAA,EAAmB,MAAA;AAAA;IAGrB,WAAA,CAAY,MAAkB;AAC5B,MAAA,EAAc,CAAA;AAAA;IAGhB,aAAA,MAAmB;AAAA;GCpJjB,IAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAejB,IAAiB;AAAA;AAAA;AAAA,GAKnB,IAAsB,GACpB,IAA0B,oBAAI,IAAA,GAE9B,IAAA,MACA,OAAO,WAAa,MACf,KAIP,OAAO,SAAS,iBAAkB,cAClC,OAAO,SAAS,iBAAkB,cAClC,OAAO,SAAS,kBAAmB,cACnC,SAAS,SAAS,QAClB,SAAS,SAAS,QAIhB,IAAA,OAAkD;AAAA,EACtD,SAAA,MAAe;AAAA,EAAA;AAAA,EACf,SAAS;IA4BE,IAAA,CAAY,GAAwB,IAA2B,CAAA,MAAuB;AACjG,MAAI,CAAC,EAAA,EACH,QAAO,EAAA;AAGT,QAAM,EAAE,MAAA,IAAO,wBAAwB,WAAA,IAAY,eAAA,IAAmB;AACtE,MAAI,GACA;AAGJ,QAAM,IAAA,CAAqB,MAAyC;AAClE,QAAI;AACF,aAAO,SAAS,cAAc,CAAA;AAAA,YACxB;AACN,aAAO;AAAA;KAGL,IAAA,MAA8C;AAClD,QAAI,CAAC,EAA0B;AAE/B,UAAM,IAAQ,EAAwB,IAAI,CAAA,GACpC,KAAiB,GAAO,SAAS,KAAK;AAC5C,IAAI,KAAiB,KACnB,EAAwB,OAAO,CAAA,GAC3B,GAAO,OAAO,eAAe,EAAM,OAAO,OAAO,KACnD,EAAM,OAAO,gBAAgB,IAAA,KAG/B,EAAwB,IAAI,GAA0B;AAAA,MACpD,OAAO;AAAA,MACP,QAAQ,EAAO;AAAA,KAChB,GAGH,IAA2B;AAAA,KAEvB,IAAA,MAAwC;AAC5C,QAAI,CAAC,EAAoB;AAEzB,UAAM,EAAE,QAAA,GAAQ,aAAA,GAAa,kBAAA,EAAA,IAAqB;AAClD,IAAI,EAAO,gBACL,IACF,EAAO,aAAa,YAAY,KAAoB,EAAA,IAEpD,EAAO,gBAAgB,UAAA,IAI3B,IAAqB;AAAA,KAEjB,IAAA,CAAyB,MAA8B;AAC3D,QAAI,GAAoB,WAAW,GAMnC;AAAA,UAFA,EAAA,GAEI,EAAO,aAAa,UAAA,GAAa;AACnC,QAAA,IAAqB;AAAA,UACnB,QAAA;AAAA,UACA,aAAa;AAAA,UACb,kBAAkB,EAAO,aAAa,UAAA;AAAA;AAExC;AAAA;AAGF,MAAI,EAAO,aAAa,OAIxB,IAAqB;AAAA,QACnB,QAAA;AAAA,QACA,aAAa;AAAA,QACb,kBAAkB;AAAA,SAEpB,EAAO,aAAa,YAAY,IAAA;AAAA;AAAA,KAE5B,IAAA,CAA0B,GAAqB,MAAqB;AACxE,QAAI,MAA6B,EAAI;AACrC,IAAA,EAAA;AACA,UAAM,IAAQ,EAAwB,IAAI,CAAA;AAC1C,IAAA,EAAwB,IAAI,GAAI;AAAA,MAC9B,QAAQ,GAAO,SAAS,KAAK;AAAA,MAC7B,QAAA;AAAA,KACD,GACD,IAA2B;AAAA,KAEvB,IAAA,MAA0C;AAC9C,QAAI,EAAe,WAAW,GAAA,GAAM;AAClC,YAAM,IAAK,EAAe,MAAM,CAAA,GAC1B,IAAO,IAAM,SAAS,eAAe,CAAA,IAA6B;AACxE,aAAI,KAIG,EAAkB,CAAA;AAAA;AAG3B,UAAM,IAAO,SAAS,eAAe,CAAA;AACrC,WAAI,KAIG,EAAkB,CAAA;AAAA,KAGrB,IAAA,CAAkB,MAAgC;AACtD,QAAI,EAAO;AAET,aADwB,EAAwB,IAAI,EAAO,EAAA,GACtC,WAAW,KAC9B,EAAuB,GAAQ,EAAO,EAAA,GAEjC,EAAO;AAGhB,QAAI;AACJ;AACE,MAAA,KAAuB,GACvB,IAAoB,kBAAkB,CAAA;AAAA,WAC/B,SAAS,eAAe,CAAA,MAAuB;AAExD,WAAA,EAAO,KAAK,GACZ,EAAuB,GAAQ,CAAA,GACxB;AAAA,KAGH,IAAO,SAAS,cAAc,GAAA,GAC9B,IAAgB,EAAA;AACtB,SAAA,EAAK,OAAO,EAAe,WAAW,GAAA,IAClC,IACA,IACE,IAAI,EAAe,CAAA,CAAc,KACjC,IAAI,CAAA,IACV,EAAK,cAAc,GACnB,EAAK,YAAY,GACjB,EAAK,aAAa,SAAS,CAAA,GAE3B,EAAK,iBAAiB,SAAA,MAAe;AACnC,IAAA,EAAK,aAAa,SAAS,IAAiB,CAAA;AAAA,MAG9C,EAAK,iBAAiB,QAAA,MAAc;AAClC,IAAA,EAAK,aAAa,SAAS,CAAA;AAAA,MAG7B,EAAK,iBAAiB,SAAA,CAAU,MAAM;AACpC,IAAA,EAAE,eAAA;AAEF,UAAM,IAAS,EAAA;AACf,IAAK,MAIL,EAAK,OAAO,IAAI,EAAe,CAAA,CAAO,IAEtC,EAAsB,CAAA,GACtB,EAAO,MAAA;AAAA,MAIL,SAAS,KAAK,aAChB,SAAS,KAAK,aAAa,GAAM,SAAS,KAAK,UAAA,IAE/C,SAAS,KAAK,YAAY,CAAA,GAGrB;AAAA,IACL,SAAA,MAAe;AACb,MAAA,EAAA,GACA,EAAA,GACA,EAAK,OAAA;AAAA;IAEP,SAAS;AAAA;GChPP,IAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;EACA,KAAK,IAAA,GASM,IAAA,CAAwB,MAClB,MAAM,KAAK,EAAU,iBAAiB,CAAA,CAAmB,EAC1D,OAAA,CACb,MAAO,CAAC,EAAG,aAAa,UAAA,KAAe,EAAG,aAAa,MAAM,EAAG,eAAA,EAAiB,SAAS,CAAA,GAQzF,IAAA,CACJ,GACA,MAEK,IACD,OAAO,KAAW,WACb,EAAU,cAAc,CAAA,IAE1B,IAJa,MA6BT,IAAA,CACX,GACA,IAA4B,CAAA,MACR;AACpB,QAAM,EAAE,mBAAA,IAAoB,IAAM,UAAA,GAAU,cAAA,GAAc,aAAA,EAAA,IAAgB;AAE1E,MACE,OAAO,WAAa,OACpB,OAAO,SAAS,oBAAqB,cACrC,OAAO,SAAS,uBAAwB,YACxC;AACA,QAAI,IAAS;AACb,WAAO;AAAA,MACL,IAAI,SAAS;AACX,eAAO;AAAA;MAET,SAAA,MAAe;AACb,QAAA,IAAS;AAAA;;;AAKf,QAAM,IAAoB,SAAS;AACnC,MAAI,IAAS;AAEb,QAAM,IAAA,CAAiB,MAA+B;AACpD,QAAI,CAAC,EAAQ;AAEb,QAAI,EAAM,QAAQ,YAAY,GAAmB;AAC/C,MAAA,EAAM,eAAA,GACN,EAAO,QAAA,GACP,IAAA;AACA;AAAA;AAGF,QAAI,EAAM,QAAQ,MAAO;AAEzB,UAAM,IAAY,EAAqB,CAAA;AACvC,QAAI,EAAU,WAAW,GAAG;AAC1B,MAAA,EAAM,eAAA;AACN;AAAA;AAGF,UAAM,IAAQ,EAAU,CAAA,GAClB,IAAO,EAAU,EAAU,SAAS,CAAA;AAE1C,IAAI,EAAM,YAEJ,SAAS,kBAAkB,KAAS,CAAC,EAAU,SAAS,SAAS,aAAA,OACnE,EAAM,eAAA,GACN,EAAK,MAAA,MAIH,SAAS,kBAAkB,KAAQ,CAAC,EAAU,SAAS,SAAS,aAAA,OAClE,EAAM,eAAA,GACN,EAAM,MAAA;AAAA;AAMZ,WAAS,iBAAiB,WAAW,GAAe,EAAA;AAGpD,QAAM,IAAY,EAAe,GAAc,CAAA;AAC/C,MAAI,EACF,CAAA,EAAU,MAAA;AAAA,OACL;AACL,UAAM,IAAY,EAAqB,CAAA;AACvC,IAAI,EAAU,SAAS,KACrB,EAAU,CAAA,EAAG,MAAA;AAAA;AAIjB,QAAM,IAA0B;AAAA,IAC9B,IAAI,SAAS;AACX,aAAO;AAAA;IAGT,SAAA,MAAe;AACb,UAAI,CAAC,EAAQ;AACb,MAAA,IAAS,IACT,SAAS,oBAAoB,WAAW,GAAe,EAAA;AAGvD,YAAM,IAAW,EAAe,GAAa,SAAS,IAAA;AACtD,MAAI,IACF,EAAS,MAAA,IACA,KAAqB,EAAkB,SAChD,EAAkB,MAAA;AAAA;;AAKxB,SAAO;GAUI,KAAA,CAAgB,MAAkC;AAC7D,EAAA,EAAO,QAAA"}
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-DgUQ8-fI.js";
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 "./core-CongXJuo.js";
3
- import { n as U } from "./config-DhT9auRm.js";
4
- import { t as q } from "./effect-Cc51IH91.js";
5
- import { r as W, t as w } from "./untrack-bjWDNdyE.js";
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 = Q(r);
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 ?? U().components?.prefix ?? "bq", o = {
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-D8ydhe58.js.map
685
+ //# sourceMappingURL=component-BtsRbf6c.js.map