@bquery/bquery 1.3.0 → 1.4.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 (71) hide show
  1. package/README.md +527 -501
  2. package/dist/{batch-4LAvfLE7.js → batch-x7b2eZST.js} +2 -2
  3. package/dist/{batch-4LAvfLE7.js.map → batch-x7b2eZST.js.map} +1 -1
  4. package/dist/component.es.mjs +1 -1
  5. package/dist/core/collection.d.ts +19 -3
  6. package/dist/core/collection.d.ts.map +1 -1
  7. package/dist/core/element.d.ts +23 -4
  8. package/dist/core/element.d.ts.map +1 -1
  9. package/dist/core/index.d.ts +1 -0
  10. package/dist/core/index.d.ts.map +1 -1
  11. package/dist/core/utils/function.d.ts +21 -4
  12. package/dist/core/utils/function.d.ts.map +1 -1
  13. package/dist/{core-COenAZjD.js → core-BhpuvPhy.js} +62 -37
  14. package/dist/core-BhpuvPhy.js.map +1 -0
  15. package/dist/core.es.mjs +174 -131
  16. package/dist/core.es.mjs.map +1 -1
  17. package/dist/full.es.mjs +7 -7
  18. package/dist/full.iife.js +2 -2
  19. package/dist/full.iife.js.map +1 -1
  20. package/dist/full.umd.js +2 -2
  21. package/dist/full.umd.js.map +1 -1
  22. package/dist/index.es.mjs +7 -7
  23. package/dist/motion.es.mjs.map +1 -1
  24. package/dist/{persisted-Dz_ryNuC.js → persisted-DHoi3uEs.js} +4 -4
  25. package/dist/{persisted-Dz_ryNuC.js.map → persisted-DHoi3uEs.js.map} +1 -1
  26. package/dist/platform/storage.d.ts.map +1 -1
  27. package/dist/platform.es.mjs +12 -7
  28. package/dist/platform.es.mjs.map +1 -1
  29. package/dist/reactive/core.d.ts +12 -0
  30. package/dist/reactive/core.d.ts.map +1 -1
  31. package/dist/reactive/effect.d.ts.map +1 -1
  32. package/dist/reactive/internals.d.ts +6 -0
  33. package/dist/reactive/internals.d.ts.map +1 -1
  34. package/dist/reactive.es.mjs +6 -6
  35. package/dist/router.es.mjs +1 -1
  36. package/dist/{sanitize-1FBEPAFH.js → sanitize-Cxvxa-DX.js} +50 -39
  37. package/dist/sanitize-Cxvxa-DX.js.map +1 -0
  38. package/dist/security/sanitize-core.d.ts.map +1 -1
  39. package/dist/security.es.mjs +2 -2
  40. package/dist/store.es.mjs +2 -2
  41. package/dist/type-guards-BdKlYYlS.js +32 -0
  42. package/dist/type-guards-BdKlYYlS.js.map +1 -0
  43. package/dist/untrack-DNnnqdlR.js +6 -0
  44. package/dist/{untrack-BuEQKH7_.js.map → untrack-DNnnqdlR.js.map} +1 -1
  45. package/dist/view/evaluate.d.ts.map +1 -1
  46. package/dist/view.es.mjs +157 -151
  47. package/dist/view.es.mjs.map +1 -1
  48. package/dist/{watch-CXyaBC_9.js → watch-DXXv3iAI.js} +3 -3
  49. package/dist/{watch-CXyaBC_9.js.map → watch-DXXv3iAI.js.map} +1 -1
  50. package/package.json +132 -132
  51. package/src/core/collection.ts +628 -588
  52. package/src/core/element.ts +774 -746
  53. package/src/core/index.ts +48 -47
  54. package/src/core/utils/function.ts +151 -110
  55. package/src/motion/animate.ts +113 -113
  56. package/src/motion/flip.ts +176 -176
  57. package/src/motion/scroll.ts +57 -57
  58. package/src/motion/spring.ts +150 -150
  59. package/src/motion/timeline.ts +246 -246
  60. package/src/motion/transition.ts +51 -51
  61. package/src/platform/storage.ts +215 -208
  62. package/src/reactive/core.ts +114 -93
  63. package/src/reactive/effect.ts +54 -43
  64. package/src/reactive/internals.ts +122 -105
  65. package/src/security/sanitize-core.ts +364 -343
  66. package/src/view/evaluate.ts +290 -274
  67. package/dist/core-COenAZjD.js.map +0 -1
  68. package/dist/sanitize-1FBEPAFH.js.map +0 -1
  69. package/dist/type-guards-DRma3-Kc.js +0 -16
  70. package/dist/type-guards-DRma3-Kc.js.map +0 -1
  71. package/dist/untrack-BuEQKH7_.js +0 -6
package/README.md CHANGED
@@ -1,501 +1,527 @@
1
- <p align="center">
2
- <img src="assets/bquerry-logo.svg" alt="bQuery.js Logo" width="120" />
3
- </p>
4
-
5
- <h1 align="center">bQuery.js</h1>
6
-
7
- <p align="center">
8
-
9
- [![Repo](https://img.shields.io/badge/github-bquery%2Fbquery-24292f?logo=github)](https://github.com/bQuery/bQuery)
10
- [![Stars](https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/stargazers)
11
- [![Issues](https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/issues)
12
- [![License](https://img.shields.io/github/license/bquery/bquery?style=flat)](https://github.com/bQuery/bQuery/blob/main/LICENSE.md)
13
- [![npm](https://img.shields.io/npm/v/@bquery/bquery)](https://www.npmjs.com/package/@bquery/bquery)
14
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@bquery/bquery)](https://bundlephobia.com/package/@bquery/bquery)
15
- [![unpkg](https://img.shields.io/badge/unpkg-browse-blue?logo=unpkg)](https://unpkg.com/@bquery/bquery)
16
- [![CodeFactor](https://www.codefactor.io/repository/github/bquery/bquery/badge)](https://www.codefactor.io/repository/github/bquery/bquery)
17
- [![JsDelivr](https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge)](https://www.jsdelivr.com/package/npm/@bquery/bquery)
18
-
19
- **The jQuery for the modern Web Platform.**
20
-
21
- bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, Web Components, and motion utilities — without a mandatory build step.
22
-
23
- ## Highlights
24
-
25
- - **Zero‑build capable**: runs directly in the browser; build tools are optional.
26
- - **Security‑focused**: DOM writes are sanitized by default; Trusted Types supported.
27
- - **Modular**: the core stays small; extra modules are opt‑in.
28
- - **TypeScript‑first**: clear types and strong IDE support.
29
- - **Tree-shakeable**: import only what you need.
30
-
31
- ## Installation
32
-
33
- ### Via npm/bun/pnpm
34
-
35
- ```bash
36
- # npm
37
- npm install @bquery/bquery
38
-
39
- # bun
40
- bun add @bquery/bquery
41
-
42
- # pnpm
43
- pnpm add @bquery/bquery
44
- ```
45
-
46
- ### Via CDN (Zero-build)
47
-
48
- #### ES Modules (recommended)
49
-
50
- ```html
51
- <script type="module">
52
- import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
53
-
54
- const count = signal(0);
55
- $('#counter').text(`Count: ${count.value}`);
56
- </script>
57
- ```
58
-
59
- #### UMD (global variable)
60
-
61
- ```html
62
- <script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
63
- <script>
64
- const { $, signal } = bQuery;
65
- const count = signal(0);
66
- </script>
67
- ```
68
-
69
- #### IIFE (self-executing)
70
-
71
- ```html
72
- <script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
73
- <script>
74
- const { $, $$ } = bQuery;
75
- $$('.items').addClass('loaded');
76
- </script>
77
- ```
78
-
79
- ### Import Strategies
80
-
81
- ```ts
82
- // Full bundle (all modules)
83
- import { $, signal, component } from '@bquery/bquery';
84
-
85
- // Core only
86
- import { $, $$ } from '@bquery/bquery/core';
87
-
88
- // Core utilities (named exports, tree-shakeable)
89
- import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
90
-
91
- // À la carte (individual modules)
92
- import { signal, computed, effect, linkedSignal, persistedSignal } from '@bquery/bquery/reactive';
93
- import { component, defineComponent, html } from '@bquery/bquery/component';
94
- import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
95
- import { sanitize } from '@bquery/bquery/security';
96
- import { storage, cache } from '@bquery/bquery/platform';
97
- import { createRouter, navigate } from '@bquery/bquery/router';
98
- import { createStore, defineStore } from '@bquery/bquery/store';
99
- import { mount, createTemplate } from '@bquery/bquery/view';
100
- ```
101
-
102
- ## Modules at a glance
103
-
104
- | Module | Description | Size (gzip) |
105
- | ------------- | -------------------------------------------------- | ----------- |
106
- | **Core** | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
107
- | **Reactive** | `signal`, `computed`, `effect`, `batch` | ~0.3 KB |
108
- | **Component** | Lightweight Web Components with props | ~1.9 KB |
109
- | **Motion** | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
110
- | **Security** | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
111
- | **Platform** | Storage, cache, notifications, buckets | ~2.2 KB |
112
- | **Router** | SPA routing, navigation guards, history API | ~2.2 KB |
113
- | **Store** | Signal-based state management, persistence | ~0.3 KB |
114
- | **View** | Declarative DOM bindings, directives | ~4.3 KB |
115
-
116
- ## Quick examples
117
-
118
- ### Core – DOM & events
119
-
120
- ```ts
121
- import { $, $$ } from '@bquery/bquery/core';
122
-
123
- // jQuery-style selectors
124
- $('#save').on('click', (event) => {
125
- console.log('Saved', event.type);
126
- });
127
-
128
- // Event delegation for dynamic content
129
- $('#list').delegate('click', '.item', (event, target) => {
130
- console.log('Item clicked', target.textContent);
131
- });
132
-
133
- // Method chaining
134
- $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
135
-
136
- // DOM manipulation
137
- $('#content').wrap('div');
138
- $('#content').unwrap(); // Remove parent wrapper
139
-
140
- // Attribute helpers
141
- $('#dialog').toggleAttr('open');
142
-
143
- // Smooth scrolling
144
- $('#section').scrollTo({ behavior: 'smooth' });
145
-
146
- // Form serialization
147
- const formData = $('form').serialize(); // Returns object
148
- const queryString = $('form').serializeString(); // Returns URL-encoded string
149
-
150
- // Collections
151
- $$('.items').addClass('highlight');
152
- $$('.items').append('<li class="item">New</li>');
153
- ```
154
-
155
- ### Reactive signals
156
-
157
- ```ts
158
- import {
159
- signal,
160
- computed,
161
- effect,
162
- batch,
163
- watch,
164
- readonly,
165
- linkedSignal,
166
- } from '@bquery/bquery/reactive';
167
-
168
- const count = signal(0);
169
- const doubled = computed(() => count.value * 2);
170
-
171
- effect(() => {
172
- console.log('Count changed', count.value);
173
- });
174
-
175
- // Watch with cleanup support
176
- const stop = watch(count, (newVal, oldVal) => {
177
- console.log(`Changed from ${oldVal} to ${newVal}`);
178
- });
179
-
180
- // Read-only signal wrapper
181
- const readOnlyCount = readonly(count);
182
-
183
- // Batch updates for performance
184
- batch(() => {
185
- count.value++;
186
- count.value++;
187
- });
188
-
189
- // Writable computed (linked signal)
190
- const first = signal('Ada');
191
- const last = signal('Lovelace');
192
- const fullName = linkedSignal(
193
- () => `${first.value} ${last.value}`,
194
- (next) => {
195
- const [nextFirst, nextLast] = next.split(' ');
196
- first.value = nextFirst ?? '';
197
- last.value = nextLast ?? '';
198
- }
199
- );
200
-
201
- fullName.value = 'Grace Hopper';
202
- ```
203
-
204
- ### Components Web Components
205
-
206
- ```ts
207
- import { component, defineComponent, html } from '@bquery/bquery/component';
208
-
209
- component('user-card', {
210
- props: {
211
- username: { type: String, required: true },
212
- age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
213
- },
214
- // Extended lifecycle hooks
215
- beforeMount() {
216
- console.log('About to mount');
217
- },
218
- connected() {
219
- console.log('Mounted');
220
- },
221
- beforeUpdate(props) {
222
- // Return false to prevent update
223
- return props.username !== '';
224
- },
225
- onError(error) {
226
- console.error('Component error:', error);
227
- },
228
- render({ props }) {
229
- return html`<div>Hello ${props.username}</div>`;
230
- },
231
- });
232
-
233
- // Optional: create the class without auto-registration
234
- const UserCard = defineComponent('user-card', {
235
- props: { username: { type: String, required: true } },
236
- render: ({ props }) => html`<div>Hello ${props.username}</div>`,
237
- });
238
- customElements.define('user-card', UserCard);
239
- ```
240
-
241
- ### Motion – animations
242
-
243
- ```ts
244
- import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
245
-
246
- // View transitions (with fallback)
247
- await transition(() => {
248
- $('#content').text('Updated');
249
- });
250
-
251
- // Web Animations helper
252
- await animate(card, {
253
- keyframes: keyframePresets.pop(),
254
- options: { duration: 240, easing: 'ease-out' },
255
- });
256
-
257
- // Spring physics
258
- const x = spring(0, { stiffness: 120, damping: 14 });
259
- x.onChange((value) => {
260
- element.style.transform = `translateX(${value}px)`;
261
- });
262
- await x.to(100);
263
- ```
264
-
265
- ### Security – sanitizing
266
-
267
- Internally modularized (sanitize core, Trusted Types, CSP helpers) — the public API remains unchanged. For legacy deep imports, `@bquery/bquery/security/sanitize` also re-exports `generateNonce()` and `isTrustedTypesSupported()`.
268
-
269
- ```ts
270
- import { sanitize, escapeHtml } from '@bquery/bquery/security';
271
-
272
- // Sanitize HTML (removes dangerous elements like script, iframe, svg)
273
- const safeHtml = sanitize(userInput);
274
-
275
- // DOM clobbering protection (reserved IDs are blocked)
276
- const safe = sanitize('<form id="cookie">...</form>'); // id stripped
277
-
278
- // Unicode bypass protection in URLs
279
- const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
280
-
281
- // Automatic link security (adds rel="noopener noreferrer" to external/target="_blank" links)
282
- const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
283
-
284
- // Escape for text display
285
- const escaped = escapeHtml('<script>alert(1)</script>');
286
- ```
287
-
288
- ### Platform storage & APIs
289
-
290
- ```ts
291
- import { storage, notifications } from '@bquery/bquery/platform';
292
-
293
- // Unified storage API
294
- const local = storage.local();
295
- await local.set('theme', 'dark');
296
- const theme = await local.get<string>('theme');
297
-
298
- // Notifications
299
- const permission = await notifications.requestPermission();
300
- if (permission === 'granted') {
301
- notifications.send('Build complete', {
302
- body: 'Your docs are ready.',
303
- });
304
- }
305
- ```
306
-
307
- ### Router – SPA navigation
308
-
309
- Internally, the router has been split into focused submodules (matching, navigation, state, links, utilities) with no public API changes.
310
-
311
- ```ts
312
- import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
313
-
314
- // Create router with routes
315
- const router = createRouter({
316
- routes: [
317
- { path: '/', name: 'home', component: HomePage },
318
- { path: '/user/:id', name: 'user', component: UserPage },
319
- { path: '*', component: NotFound },
320
- ],
321
- });
322
-
323
- // Navigation guards
324
- router.beforeEach(async (to, from) => {
325
- if (to.path === '/admin' && !isAuthenticated()) {
326
- await navigate('/login'); // Redirect
327
- return false; // Cancel original navigation
328
- }
329
- });
330
-
331
- // Navigate programmatically
332
- await navigate('/user/42');
333
- await navigate('/search?q=bquery'); // Query params in path
334
- await navigate('/login', { replace: true }); // Replace history entry
335
-
336
- // Reactive current route
337
- effect(() => {
338
- console.log('Current path:', currentRoute.value.path);
339
- });
340
- ```
341
-
342
- ### Store state management
343
-
344
- ```ts
345
- import { createStore, createPersistedStore } from '@bquery/bquery/store';
346
-
347
- // Create a store (returns the store instance directly)
348
- const counterStore = createStore({
349
- id: 'counter',
350
- state: () => ({ count: 0, name: 'Counter' }),
351
- getters: {
352
- doubled: (state) => state.count * 2,
353
- },
354
- actions: {
355
- increment() {
356
- this.count++;
357
- },
358
- async fetchCount() {
359
- this.count = await api.getCount();
360
- },
361
- },
362
- });
363
-
364
- // Use the store
365
- counterStore.increment();
366
- console.log(counterStore.doubled); // Reactive getter
367
-
368
- // Persisted store (localStorage)
369
- const settingsStore = createPersistedStore({
370
- id: 'settings',
371
- state: () => ({ theme: 'dark', language: 'en' }),
372
- });
373
-
374
- // Factory-style store definition (Pinia-style)
375
- import { defineStore, mapGetters, watchStore } from '@bquery/bquery/store';
376
-
377
- const useCounter = defineStore('counter', {
378
- state: () => ({ count: 0 }),
379
- getters: {
380
- doubled: (state) => state.count * 2,
381
- },
382
- actions: {
383
- increment() {
384
- this.count++;
385
- },
386
- },
387
- });
388
-
389
- const counter = useCounter();
390
- const getters = mapGetters(counter, ['doubled']);
391
-
392
- watchStore(
393
- counter,
394
- (state) => state.count,
395
- (value) => {
396
- console.log('Count changed:', value, getters.doubled);
397
- }
398
- );
399
- ```
400
-
401
- ### View – declarative bindings
402
-
403
- Internally modularized into focused submodules; the public API remains unchanged.
404
-
405
- ```ts
406
- import { mount, createTemplate } from '@bquery/bquery/view';
407
- import { signal } from '@bquery/bquery/reactive';
408
-
409
- // Mount reactive bindings to DOM
410
- const count = signal(0);
411
- const items = signal(['Apple', 'Banana', 'Cherry']);
412
-
413
- const app = mount('#app', {
414
- count,
415
- items,
416
- increment: () => count.value++,
417
- });
418
-
419
- // In HTML:
420
- // <p bq-text="count"></p>
421
- // <button bq-on:click="increment">+1</button>
422
- // <ul><li bq-for="item in items" bq-text="item"></li></ul>
423
- // <input bq-model="count" type="number" />
424
- // <div bq-if="count > 5">Count is high!</div>
425
- // <div bq-class="{ active: count > 0 }"></div>
426
- ```
427
-
428
- ## Browser Support
429
-
430
- | Browser | Version | Support |
431
- | ------- | ------- | ------- |
432
- | Chrome | 90+ | ✅ Full |
433
- | Firefox | 90+ | ✅ Full |
434
- | Safari | 15+ | Full |
435
- | Edge | 90+ | ✅ Full |
436
-
437
- > **No IE support** by design.
438
-
439
- ## Documentation
440
-
441
- - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
442
- - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
443
- - **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
444
- - **Components**: [docs/guide/components.md](docs/guide/components.md)
445
- - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
446
- - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
447
- - **Security**: [docs/guide/security.md](docs/guide/security.md)
448
- - **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
449
- - **Router**: [docs/guide/router.md](docs/guide/router.md)
450
- - **Store**: [docs/guide/store.md](docs/guide/store.md)
451
- - **View**: [docs/guide/view.md](docs/guide/view.md)
452
-
453
- ## Local Development
454
-
455
- ```bash
456
- # Install dependencies
457
- bun install
458
-
459
- # Start VitePress docs
460
- bun run dev
461
-
462
- # Run Vite playground
463
- bun run playground
464
-
465
- # Run tests
466
- bun test
467
-
468
- # Build library
469
- bun run build
470
-
471
- # Generate API documentation
472
- bun run docs:api
473
- ```
474
-
475
- ## Project Structure
476
-
477
- ```text
478
- bQuery.js
479
- ├── src/
480
- │ ├── core/ # Selectors, DOM ops, events, utils
481
- │ ├── reactive/ # Signals, computed, effects
482
- │ ├── component/ # Web Components helper
483
- │ ├── motion/ # View transitions, FLIP, springs
484
- │ ├── security/ # Sanitizer, CSP, Trusted Types
485
- │ ├── platform/ # Storage, cache, notifications
486
- │ ├── router/ # SPA routing, navigation guards
487
- │ ├── store/ # State management, persistence
488
- │ └── view/ # Declarative DOM bindings
489
- ├── docs/ # VitePress documentation
490
- ├── playground/ # Vite demo app
491
- ├── tests/ # bun:test suites
492
- └── dist/ # Built files (ESM, UMD, IIFE)
493
- ```
494
-
495
- ## Contributing
496
-
497
- See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
498
-
499
- ## License
500
-
501
- MIT See [LICENSE.md](LICENSE.md) for details.
1
+ <p align="center">
2
+ <img src="assets/bquerry-logo.svg" alt="bQuery.js Logo" width="120" />
3
+ </p>
4
+
5
+ <h1 align="center">bQuery.js</h1>
6
+
7
+ <p align="center">
8
+
9
+ [![Repo](https://img.shields.io/badge/github-bquery%2Fbquery-24292f?logo=github)](https://github.com/bQuery/bQuery)
10
+ [![Stars](https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/stargazers)
11
+ [![Issues](https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/issues)
12
+ [![License](https://img.shields.io/github/license/bquery/bquery?style=flat)](https://github.com/bQuery/bQuery/blob/main/LICENSE.md)
13
+ [![npm](https://img.shields.io/npm/v/@bquery/bquery)](https://www.npmjs.com/package/@bquery/bquery)
14
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@bquery/bquery)](https://bundlephobia.com/package/@bquery/bquery)
15
+ [![unpkg](https://img.shields.io/badge/unpkg-browse-blue?logo=unpkg)](https://unpkg.com/@bquery/bquery)
16
+ [![CodeFactor](https://www.codefactor.io/repository/github/bquery/bquery/badge)](https://www.codefactor.io/repository/github/bquery/bquery)
17
+ [![JsDelivr](https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge)](https://www.jsdelivr.com/package/npm/@bquery/bquery)
18
+
19
+ **The jQuery for the modern Web Platform.**
20
+
21
+ bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, Web Components, and motion utilities — without a mandatory build step.
22
+
23
+ ## Highlights
24
+
25
+ - **Zero‑build capable**: runs directly in the browser; build tools are optional.
26
+ - **Security‑focused**: DOM writes are sanitized by default; Trusted Types supported.
27
+ - **Modular**: the core stays small; extra modules are opt‑in.
28
+ - **TypeScript‑first**: clear types and strong IDE support.
29
+ - **Tree-shakeable**: import only what you need.
30
+
31
+ ## Installation
32
+
33
+ ### Via npm/bun/pnpm
34
+
35
+ ```bash
36
+ # npm
37
+ npm install @bquery/bquery
38
+
39
+ # bun
40
+ bun add @bquery/bquery
41
+
42
+ # pnpm
43
+ pnpm add @bquery/bquery
44
+ ```
45
+
46
+ ### Via CDN (Zero-build)
47
+
48
+ #### ES Modules (recommended)
49
+
50
+ ```html
51
+ <script type="module">
52
+ import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
53
+
54
+ const count = signal(0);
55
+ $('#counter').text(`Count: ${count.value}`);
56
+ </script>
57
+ ```
58
+
59
+ #### UMD (global variable)
60
+
61
+ ```html
62
+ <script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
63
+ <script>
64
+ const { $, signal } = bQuery;
65
+ const count = signal(0);
66
+ </script>
67
+ ```
68
+
69
+ #### IIFE (self-executing)
70
+
71
+ ```html
72
+ <script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
73
+ <script>
74
+ const { $, $$ } = bQuery;
75
+ $$('.items').addClass('loaded');
76
+ </script>
77
+ ```
78
+
79
+ ### Import Strategies
80
+
81
+ ```ts
82
+ // Full bundle (all modules)
83
+ import { $, signal, component } from '@bquery/bquery';
84
+
85
+ // Core only
86
+ import { $, $$ } from '@bquery/bquery/core';
87
+
88
+ // Core utilities (named exports, tree-shakeable)
89
+ import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
90
+
91
+ // À la carte (individual modules)
92
+ import { signal, computed, effect, linkedSignal, persistedSignal } from '@bquery/bquery/reactive';
93
+ import { component, defineComponent, html } from '@bquery/bquery/component';
94
+ import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
95
+ import { sanitize } from '@bquery/bquery/security';
96
+ import { storage, cache } from '@bquery/bquery/platform';
97
+ import { createRouter, navigate } from '@bquery/bquery/router';
98
+ import { createStore, defineStore } from '@bquery/bquery/store';
99
+ import { mount, createTemplate } from '@bquery/bquery/view';
100
+ ```
101
+
102
+ ## Modules at a glance
103
+
104
+ | Module | Description | Size (gzip) |
105
+ | ------------- | -------------------------------------------------- | ----------- |
106
+ | **Core** | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
107
+ | **Reactive** | `signal`, `computed`, `effect`, `batch` | ~0.3 KB |
108
+ | **Component** | Lightweight Web Components with props | ~1.9 KB |
109
+ | **Motion** | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
110
+ | **Security** | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
111
+ | **Platform** | Storage, cache, notifications, buckets | ~2.2 KB |
112
+ | **Router** | SPA routing, navigation guards, history API | ~2.2 KB |
113
+ | **Store** | Signal-based state management, persistence | ~0.3 KB |
114
+ | **View** | Declarative DOM bindings, directives | ~4.3 KB |
115
+
116
+ ## Quick examples
117
+
118
+ ### Core – DOM & events
119
+
120
+ ```ts
121
+ import { $, $$ } from '@bquery/bquery/core';
122
+
123
+ // jQuery-style selectors
124
+ $('#save').on('click', (event) => {
125
+ console.log('Saved', event.type);
126
+ });
127
+
128
+ // Event delegation for dynamic content
129
+ $('#list').delegate('click', '.item', (event, target) => {
130
+ console.log('Item clicked', target.textContent);
131
+ });
132
+
133
+ // Method chaining
134
+ $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
135
+
136
+ // CSS getter (computed style)
137
+ const color = $('#box').css('color'); // Returns computed style value
138
+
139
+ // Selector matching
140
+ if ($('#el').is('.active')) {
141
+ console.log('Element is active');
142
+ }
143
+
144
+ // DOM manipulation
145
+ $('#content').wrap('div');
146
+ $('#content').unwrap(); // Remove parent wrapper
147
+
148
+ // Attribute helpers
149
+ $('#dialog').toggleAttr('open');
150
+
151
+ // Smooth scrolling
152
+ $('#section').scrollTo({ behavior: 'smooth' });
153
+
154
+ // Form serialization
155
+ const formData = $('form').serialize(); // Returns object
156
+ const queryString = $('form').serializeString(); // Returns URL-encoded string
157
+
158
+ // Collections
159
+ $$('.items').addClass('highlight');
160
+ $$('.items').append('<li class="item">New</li>');
161
+ $$('.container').find('.item').addClass('found'); // Find descendants across collection
162
+ ```
163
+
164
+ ### Reactive – signals
165
+
166
+ ```ts
167
+ import {
168
+ signal,
169
+ computed,
170
+ effect,
171
+ batch,
172
+ watch,
173
+ readonly,
174
+ linkedSignal,
175
+ } from '@bquery/bquery/reactive';
176
+
177
+ const count = signal(0);
178
+ const doubled = computed(() => count.value * 2);
179
+
180
+ effect(() => {
181
+ console.log('Count changed', count.value);
182
+ });
183
+
184
+ // Watch with cleanup support
185
+ const stop = watch(count, (newVal, oldVal) => {
186
+ console.log(`Changed from ${oldVal} to ${newVal}`);
187
+ });
188
+
189
+ // Read-only signal wrapper
190
+ const readOnlyCount = readonly(count);
191
+
192
+ // Batch updates for performance
193
+ batch(() => {
194
+ count.value++;
195
+ count.value++;
196
+ });
197
+
198
+ // Dispose signal (remove all subscribers)
199
+ count.dispose();
200
+
201
+ // Writable computed (linked signal)
202
+ const first = signal('Ada');
203
+ const last = signal('Lovelace');
204
+ const fullName = linkedSignal(
205
+ () => `${first.value} ${last.value}`,
206
+ (next) => {
207
+ const [nextFirst, nextLast] = next.split(' ');
208
+ first.value = nextFirst ?? '';
209
+ last.value = nextLast ?? '';
210
+ }
211
+ );
212
+
213
+ fullName.value = 'Grace Hopper';
214
+ ```
215
+
216
+ ### Components – Web Components
217
+
218
+ ```ts
219
+ import { component, defineComponent, html } from '@bquery/bquery/component';
220
+
221
+ component('user-card', {
222
+ props: {
223
+ username: { type: String, required: true },
224
+ age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
225
+ },
226
+ // Extended lifecycle hooks
227
+ beforeMount() {
228
+ console.log('About to mount');
229
+ },
230
+ connected() {
231
+ console.log('Mounted');
232
+ },
233
+ beforeUpdate(props) {
234
+ // Return false to prevent update
235
+ return props.username !== '';
236
+ },
237
+ onError(error) {
238
+ console.error('Component error:', error);
239
+ },
240
+ render({ props }) {
241
+ return html`<div>Hello ${props.username}</div>`;
242
+ },
243
+ });
244
+
245
+ // Optional: create the class without auto-registration
246
+ const UserCard = defineComponent('user-card', {
247
+ props: { username: { type: String, required: true } },
248
+ render: ({ props }) => html`<div>Hello ${props.username}</div>`,
249
+ });
250
+ customElements.define('user-card', UserCard);
251
+ ```
252
+
253
+ ### Motion – animations
254
+
255
+ ```ts
256
+ import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
257
+
258
+ // View transitions (with fallback)
259
+ await transition(() => {
260
+ $('#content').text('Updated');
261
+ });
262
+
263
+ // Web Animations helper
264
+ await animate(card, {
265
+ keyframes: keyframePresets.pop(),
266
+ options: { duration: 240, easing: 'ease-out' },
267
+ });
268
+
269
+ // Spring physics
270
+ const x = spring(0, { stiffness: 120, damping: 14 });
271
+ x.onChange((value) => {
272
+ element.style.transform = `translateX(${value}px)`;
273
+ });
274
+ await x.to(100);
275
+ ```
276
+
277
+ ### Security – sanitizing
278
+
279
+ Internally modularized (sanitize core, Trusted Types, CSP helpers) — the public API remains unchanged. For legacy deep imports, `@bquery/bquery/security/sanitize` also re-exports `generateNonce()` and `isTrustedTypesSupported()`.
280
+
281
+ ```ts
282
+ import { sanitize, escapeHtml } from '@bquery/bquery/security';
283
+
284
+ // Sanitize HTML (removes dangerous elements like script, iframe, svg)
285
+ const safeHtml = sanitize(userInput);
286
+
287
+ // DOM clobbering protection (reserved IDs are blocked)
288
+ const safe = sanitize('<form id="cookie">...</form>'); // id stripped
289
+
290
+ // Unicode bypass protection in URLs
291
+ const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
292
+
293
+ // Automatic link security (adds rel="noopener noreferrer" to external/target="_blank" links)
294
+ const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
295
+
296
+ // srcset validation (per-URL; entire attribute removed if any entry is unsafe)
297
+ const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">'); // <img>
298
+
299
+ // Form action validation (blocks javascript: protocol)
300
+ const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
301
+
302
+ // Escape for text display
303
+ const escaped = escapeHtml('<script>alert(1)</script>');
304
+ ```
305
+
306
+ ### Platform – storage & APIs
307
+
308
+ ```ts
309
+ import { storage, notifications } from '@bquery/bquery/platform';
310
+
311
+ // Unified storage API
312
+ const local = storage.local();
313
+ await local.set('theme', 'dark');
314
+ const theme = await local.get<string>('theme');
315
+
316
+ // Notifications
317
+ const permission = await notifications.requestPermission();
318
+ if (permission === 'granted') {
319
+ notifications.send('Build complete', {
320
+ body: 'Your docs are ready.',
321
+ });
322
+ }
323
+ ```
324
+
325
+ ### Router SPA navigation
326
+
327
+ Internally, the router has been split into focused submodules (matching, navigation, state, links, utilities) with no public API changes.
328
+
329
+ ```ts
330
+ import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
331
+
332
+ // Create router with routes
333
+ const router = createRouter({
334
+ routes: [
335
+ { path: '/', name: 'home', component: HomePage },
336
+ { path: '/user/:id', name: 'user', component: UserPage },
337
+ { path: '*', component: NotFound },
338
+ ],
339
+ });
340
+
341
+ // Navigation guards
342
+ router.beforeEach(async (to, from) => {
343
+ if (to.path === '/admin' && !isAuthenticated()) {
344
+ await navigate('/login'); // Redirect
345
+ return false; // Cancel original navigation
346
+ }
347
+ });
348
+
349
+ // Navigate programmatically
350
+ await navigate('/user/42');
351
+ await navigate('/search?q=bquery'); // Query params in path
352
+ await navigate('/login', { replace: true }); // Replace history entry
353
+
354
+ // Reactive current route
355
+ effect(() => {
356
+ console.log('Current path:', currentRoute.value.path);
357
+ });
358
+ ```
359
+
360
+ ### Store – state management
361
+
362
+ ```ts
363
+ import { createStore, createPersistedStore } from '@bquery/bquery/store';
364
+
365
+ // Create a store (returns the store instance directly)
366
+ const counterStore = createStore({
367
+ id: 'counter',
368
+ state: () => ({ count: 0, name: 'Counter' }),
369
+ getters: {
370
+ doubled: (state) => state.count * 2,
371
+ },
372
+ actions: {
373
+ increment() {
374
+ this.count++;
375
+ },
376
+ async fetchCount() {
377
+ this.count = await api.getCount();
378
+ },
379
+ },
380
+ });
381
+
382
+ // Use the store
383
+ counterStore.increment();
384
+ console.log(counterStore.doubled); // Reactive getter
385
+
386
+ // Persisted store (localStorage)
387
+ const settingsStore = createPersistedStore({
388
+ id: 'settings',
389
+ state: () => ({ theme: 'dark', language: 'en' }),
390
+ });
391
+
392
+ // Factory-style store definition (Pinia-style)
393
+ import { defineStore, mapGetters, watchStore } from '@bquery/bquery/store';
394
+
395
+ const useCounter = defineStore('counter', {
396
+ state: () => ({ count: 0 }),
397
+ getters: {
398
+ doubled: (state) => state.count * 2,
399
+ },
400
+ actions: {
401
+ increment() {
402
+ this.count++;
403
+ },
404
+ },
405
+ });
406
+
407
+ const counter = useCounter();
408
+ const getters = mapGetters(counter, ['doubled']);
409
+
410
+ watchStore(
411
+ counter,
412
+ (state) => state.count,
413
+ (value) => {
414
+ console.log('Count changed:', value, getters.doubled);
415
+ }
416
+ );
417
+ ```
418
+
419
+ ### View – declarative bindings
420
+
421
+ Internally modularized into focused submodules; the public API remains unchanged.
422
+
423
+ ```ts
424
+ import { mount, createTemplate } from '@bquery/bquery/view';
425
+ import { signal } from '@bquery/bquery/reactive';
426
+
427
+ // Mount reactive bindings to DOM
428
+ const count = signal(0);
429
+ const items = signal(['Apple', 'Banana', 'Cherry']);
430
+
431
+ const app = mount('#app', {
432
+ count,
433
+ items,
434
+ increment: () => count.value++,
435
+ });
436
+
437
+ // In HTML:
438
+ // <p bq-text="count"></p>
439
+ // <button bq-on:click="increment">+1</button>
440
+ // <ul><li bq-for="item in items" bq-text="item"></li></ul>
441
+ // <input bq-model="count" type="number" />
442
+ // <div bq-if="count > 5">Count is high!</div>
443
+ // <div bq-class="{ active: count > 0 }"></div>
444
+ ```
445
+
446
+ ## Browser Support
447
+
448
+ | Browser | Version | Support |
449
+ | ------- | ------- | ------- |
450
+ | Chrome | 90+ | ✅ Full |
451
+ | Firefox | 90+ | ✅ Full |
452
+ | Safari | 15+ | ✅ Full |
453
+ | Edge | 90+ | ✅ Full |
454
+
455
+ > **No IE support** by design.
456
+
457
+ ## Documentation
458
+
459
+ - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
460
+ - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
461
+ - **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
462
+ - **Components**: [docs/guide/components.md](docs/guide/components.md)
463
+ - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
464
+ - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
465
+ - **Security**: [docs/guide/security.md](docs/guide/security.md)
466
+ - **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
467
+ - **Router**: [docs/guide/router.md](docs/guide/router.md)
468
+ - **Store**: [docs/guide/store.md](docs/guide/store.md)
469
+ - **View**: [docs/guide/view.md](docs/guide/view.md)
470
+
471
+ ## Local Development
472
+
473
+ ```bash
474
+ # Install dependencies
475
+ bun install
476
+
477
+ # Start VitePress docs
478
+ bun run dev
479
+
480
+ # Run Vite playground
481
+ bun run playground
482
+
483
+ # Run tests
484
+ bun test
485
+
486
+ # Build library
487
+ bun run build
488
+
489
+ # Generate API documentation
490
+ bun run docs:api
491
+ ```
492
+
493
+ ## Project Structure
494
+
495
+ ```text
496
+ bQuery.js
497
+ ├── src/
498
+ │ ├── core/ # Selectors, DOM ops, events, utils
499
+ │ ├── reactive/ # Signals, computed, effects
500
+ │ ├── component/ # Web Components helper
501
+ │ ├── motion/ # View transitions, FLIP, springs
502
+ │ ├── security/ # Sanitizer, CSP, Trusted Types
503
+ │ ├── platform/ # Storage, cache, notifications
504
+ │ ├── router/ # SPA routing, navigation guards
505
+ │ ├── store/ # State management, persistence
506
+ │ └── view/ # Declarative DOM bindings
507
+ ├── docs/ # VitePress documentation
508
+ ├── playground/ # Vite demo app
509
+ ├── tests/ # bun:test suites
510
+ └── dist/ # Built files (ESM, UMD, IIFE)
511
+ ```
512
+
513
+ ## Contributing
514
+
515
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
516
+
517
+ ## AI Agent Support
518
+
519
+ This project provides dedicated context files for AI coding agents:
520
+
521
+ - **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
522
+ - **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
523
+ - **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
524
+
525
+ ## License
526
+
527
+ MIT – See [LICENSE.md](LICENSE.md) for details.