@bquery/bquery 1.5.0 → 1.6.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 (106) hide show
  1. package/README.md +586 -546
  2. package/dist/component/component.d.ts +13 -5
  3. package/dist/component/component.d.ts.map +1 -1
  4. package/dist/component/html.d.ts +40 -3
  5. package/dist/component/html.d.ts.map +1 -1
  6. package/dist/component/index.d.ts +2 -2
  7. package/dist/component/index.d.ts.map +1 -1
  8. package/dist/component/library.d.ts.map +1 -1
  9. package/dist/component/types.d.ts +131 -16
  10. package/dist/component/types.d.ts.map +1 -1
  11. package/dist/component-BEQgt5hl.js +600 -0
  12. package/dist/component-BEQgt5hl.js.map +1 -0
  13. package/dist/component.es.mjs +7 -6
  14. package/dist/config-DRmZZno3.js.map +1 -1
  15. package/dist/core-BGQJVw0-.js +35 -0
  16. package/dist/core-BGQJVw0-.js.map +1 -0
  17. package/dist/{core-CK2Mfpf4.js → core-CCEabVHl.js} +2 -2
  18. package/dist/{core-CK2Mfpf4.js.map → core-CCEabVHl.js.map} +1 -1
  19. package/dist/core.es.mjs +1 -1
  20. package/dist/effect-AFRW_Plg.js +84 -0
  21. package/dist/effect-AFRW_Plg.js.map +1 -0
  22. package/dist/full.d.ts +4 -4
  23. package/dist/full.d.ts.map +1 -1
  24. package/dist/full.es.mjs +98 -94
  25. package/dist/full.iife.js +14 -14
  26. package/dist/full.iife.js.map +1 -1
  27. package/dist/full.umd.js +14 -14
  28. package/dist/full.umd.js.map +1 -1
  29. package/dist/index.es.mjs +143 -139
  30. package/dist/{motion-C5DRdPnO.js → motion-D9TcHxOF.js} +1 -1
  31. package/dist/{motion-C5DRdPnO.js.map → motion-D9TcHxOF.js.map} +1 -1
  32. package/dist/motion.es.mjs +1 -1
  33. package/dist/{platform-B7JhGBc7.js → platform-Dr9b6fsq.js} +21 -20
  34. package/dist/platform-Dr9b6fsq.js.map +1 -0
  35. package/dist/platform.es.mjs +1 -1
  36. package/dist/{reactive-BDya-ia8.js → reactive-DSkct0dO.js} +51 -50
  37. package/dist/reactive-DSkct0dO.js.map +1 -0
  38. package/dist/reactive.es.mjs +19 -17
  39. package/dist/{router-CijiICxt.js → router-CbDhl8rS.js} +3 -3
  40. package/dist/{router-CijiICxt.js.map → router-CbDhl8rS.js.map} +1 -1
  41. package/dist/router.es.mjs +1 -1
  42. package/dist/{sanitize-jyJ2ryE2.js → sanitize-Bs2dkMby.js} +94 -83
  43. package/dist/sanitize-Bs2dkMby.js.map +1 -0
  44. package/dist/security/index.d.ts +4 -2
  45. package/dist/security/index.d.ts.map +1 -1
  46. package/dist/security/sanitize.d.ts +4 -1
  47. package/dist/security/sanitize.d.ts.map +1 -1
  48. package/dist/security/trusted-html.d.ts +53 -0
  49. package/dist/security/trusted-html.d.ts.map +1 -0
  50. package/dist/security.es.mjs +10 -9
  51. package/dist/store/define-store.d.ts +1 -1
  52. package/dist/store/define-store.d.ts.map +1 -1
  53. package/dist/store/mapping.d.ts +1 -1
  54. package/dist/store/mapping.d.ts.map +1 -1
  55. package/dist/store/persisted.d.ts +1 -1
  56. package/dist/store/persisted.d.ts.map +1 -1
  57. package/dist/store/types.d.ts +2 -2
  58. package/dist/store/types.d.ts.map +1 -1
  59. package/dist/store/watch.d.ts +1 -1
  60. package/dist/store/watch.d.ts.map +1 -1
  61. package/dist/{store-CPK9E62U.js → store-BwDvI45q.js} +49 -48
  62. package/dist/{store-CPK9E62U.js.map → store-BwDvI45q.js.map} +1 -1
  63. package/dist/store.es.mjs +1 -1
  64. package/dist/storybook/index.d.ts +37 -0
  65. package/dist/storybook/index.d.ts.map +1 -0
  66. package/dist/storybook.es.mjs +151 -0
  67. package/dist/storybook.es.mjs.map +1 -0
  68. package/dist/untrack-B0rVscTc.js +7 -0
  69. package/dist/untrack-B0rVscTc.js.map +1 -0
  70. package/dist/{view-Cdi0g-qo.js → view-C70lA3vf.js} +29 -28
  71. package/dist/{view-Cdi0g-qo.js.map → view-C70lA3vf.js.map} +1 -1
  72. package/dist/view.es.mjs +9 -8
  73. package/package.json +141 -136
  74. package/src/component/component.ts +259 -54
  75. package/src/component/html.ts +153 -53
  76. package/src/component/index.ts +10 -2
  77. package/src/component/library.ts +42 -28
  78. package/src/component/types.ts +184 -19
  79. package/src/full.ts +8 -2
  80. package/src/motion/transition.ts +97 -97
  81. package/src/motion/types.ts +208 -208
  82. package/src/platform/announcer.ts +208 -208
  83. package/src/platform/config.ts +163 -163
  84. package/src/platform/cookies.ts +165 -165
  85. package/src/platform/index.ts +39 -39
  86. package/src/platform/meta.ts +168 -168
  87. package/src/reactive/async-data.ts +486 -486
  88. package/src/reactive/index.ts +37 -37
  89. package/src/reactive/signal.ts +29 -29
  90. package/src/security/constants.ts +211 -211
  91. package/src/security/index.ts +17 -10
  92. package/src/security/sanitize.ts +70 -66
  93. package/src/security/trusted-html.ts +71 -0
  94. package/src/store/define-store.ts +49 -48
  95. package/src/store/mapping.ts +74 -73
  96. package/src/store/persisted.ts +62 -61
  97. package/src/store/types.ts +92 -94
  98. package/src/store/watch.ts +53 -52
  99. package/src/storybook/index.ts +479 -0
  100. package/dist/component-CY5MVoYN.js +0 -531
  101. package/dist/component-CY5MVoYN.js.map +0 -1
  102. package/dist/core-DPdbItcq.js +0 -112
  103. package/dist/core-DPdbItcq.js.map +0 -1
  104. package/dist/platform-B7JhGBc7.js.map +0 -1
  105. package/dist/reactive-BDya-ia8.js.map +0 -1
  106. package/dist/sanitize-jyJ2ryE2.js.map +0 -1
package/README.md CHANGED
@@ -1,546 +1,586 @@
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
- </p>
20
-
21
- **The jQuery for the modern Web Platform.**
22
-
23
- bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, async data composables, Web Components, motion utilities, routing, stores, and declarative views — without a mandatory build step.
24
-
25
- ## Highlights
26
-
27
- - **Zero-build capable**: runs directly in the browser; build tools are optional.
28
- - **Async data built-in**: fetch and async state composables integrate directly with signals.
29
- - **Security-focused**: DOM writes are sanitized by default; Trusted Types supported.
30
- - **Modular**: the core stays small; extra modules are opt-in.
31
- - **TypeScript-first**: clear types and strong IDE support.
32
- - **Tree-shakeable**: import only what you need.
33
- - **Storybook-ready**: default components can be previewed and developed in Storybook.
34
-
35
- ## Installation
36
-
37
- ### Via npm/bun/pnpm
38
-
39
- ```bash
40
- # npm
41
- npm install @bquery/bquery
42
-
43
- # bun
44
- bun add @bquery/bquery
45
-
46
- # pnpm
47
- pnpm add @bquery/bquery
48
- ```
49
-
50
- ### Via CDN (Zero-build)
51
-
52
- #### ES Modules (recommended)
53
-
54
- ```html
55
- <script type="module">
56
- import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
57
-
58
- const count = signal(0);
59
- $('#counter').text(`Count: ${count.value}`);
60
- </script>
61
- ```
62
-
63
- #### UMD (global variable)
64
-
65
- ```html
66
- <script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
67
- <script>
68
- const { $, signal } = bQuery;
69
- const count = signal(0);
70
- </script>
71
- ```
72
-
73
- #### IIFE (self-executing)
74
-
75
- ```html
76
- <script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
77
- <script>
78
- const { $, $$ } = bQuery;
79
- $$('.items').addClass('loaded');
80
- </script>
81
- ```
82
-
83
- ## Import Strategies
84
-
85
- ```ts
86
- // Full bundle (all modules)
87
- import {
88
- $,
89
- signal,
90
- component,
91
- registerDefaultComponents,
92
- defineBqueryConfig,
93
- } from '@bquery/bquery';
94
-
95
- // Core only
96
- import { $, $$ } from '@bquery/bquery/core';
97
-
98
- // Core utilities (named exports, tree-shakeable)
99
- import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
100
-
101
- // Reactive only
102
- import {
103
- signal,
104
- computed,
105
- effect,
106
- linkedSignal,
107
- persistedSignal,
108
- useAsyncData,
109
- useFetch,
110
- createUseFetch,
111
- } from '@bquery/bquery/reactive';
112
-
113
- // Components only
114
- import {
115
- component,
116
- defineComponent,
117
- html,
118
- registerDefaultComponents,
119
- } from '@bquery/bquery/component';
120
-
121
- // Motion only
122
- import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
123
-
124
- // Security only
125
- import { sanitize } from '@bquery/bquery/security';
126
-
127
- // Platform only
128
- import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery/bquery/platform';
129
-
130
- // Router, Store, View
131
- import { createRouter, navigate } from '@bquery/bquery/router';
132
- import { createStore, defineStore } from '@bquery/bquery/store';
133
- import { mount, createTemplate } from '@bquery/bquery/view';
134
- ```
135
-
136
- ## Modules at a glance
137
-
138
- | Module | Description | Size (gzip) |
139
- | ------------- | -------------------------------------------------- | ----------- |
140
- | **Core** | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
141
- | **Reactive** | `signal`, `computed`, `effect`, async data/fetch | ~0.3 KB |
142
- | **Component** | Lightweight Web Components with props + defaults | ~1.9 KB |
143
- | **Motion** | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
144
- | **Security** | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
145
- | **Platform** | Storage, cache, cookies, meta, announcers, config | ~2.2 KB |
146
- | **Router** | SPA routing, navigation guards, history API | ~2.2 KB |
147
- | **Store** | Signal-based state management, persistence | ~0.3 KB |
148
- | **View** | Declarative DOM bindings, directives | ~4.3 KB |
149
-
150
- ## Quick examples
151
-
152
- ### Core DOM & events
153
-
154
- ```ts
155
- import { $, $$ } from '@bquery/bquery/core';
156
-
157
- $('#save').on('click', (event) => {
158
- console.log('Saved', event.type);
159
- });
160
-
161
- $('#list').delegate('click', '.item', (event, target) => {
162
- console.log('Item clicked', target.textContent);
163
- });
164
-
165
- $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
166
-
167
- const color = $('#box').css('color');
168
-
169
- if ($('#el').is('.active')) {
170
- console.log('Element is active');
171
- }
172
-
173
- $$('.container').find('.item').addClass('found');
174
- ```
175
-
176
- ### Reactive – signals
177
-
178
- ```ts
179
- import {
180
- signal,
181
- computed,
182
- effect,
183
- batch,
184
- watch,
185
- readonly,
186
- linkedSignal,
187
- } from '@bquery/bquery/reactive';
188
-
189
- const count = signal(0);
190
- const doubled = computed(() => count.value * 2);
191
-
192
- effect(() => {
193
- console.log('Count changed', count.value);
194
- });
195
-
196
- watch(count, (newVal, oldVal) => {
197
- console.log(`Changed from ${oldVal} to ${newVal}`);
198
- });
199
-
200
- const readOnlyCount = readonly(count);
201
-
202
- batch(() => {
203
- count.value++;
204
- count.value++;
205
- });
206
-
207
- count.dispose();
208
-
209
- const first = signal('Ada');
210
- const last = signal('Lovelace');
211
- const fullName = linkedSignal(
212
- () => `${first.value} ${last.value}`,
213
- (next) => {
214
- const [nextFirst, nextLast] = next.split(' ');
215
- first.value = nextFirst ?? '';
216
- last.value = nextLast ?? '';
217
- }
218
- );
219
-
220
- fullName.value = 'Grace Hopper';
221
- ```
222
-
223
- ### Reactive – async data & fetch
224
-
225
- ```ts
226
- import { signal, useFetch, createUseFetch } from '@bquery/bquery/reactive';
227
-
228
- const userId = signal(1);
229
-
230
- const user = useFetch<{ id: number; name: string }>(() => `/users/${userId.value}`, {
231
- baseUrl: 'https://api.example.com',
232
- watch: [userId],
233
- query: { include: 'profile' },
234
- });
235
-
236
- const useApiFetch = createUseFetch({
237
- baseUrl: 'https://api.example.com',
238
- headers: { 'x-client': 'bquery-readme' },
239
- });
240
-
241
- const settings = useApiFetch<{ theme: string }>('/settings');
242
-
243
- console.log(user.pending.value, user.data.value, settings.error.value);
244
- ```
245
-
246
- ### Components – Web Components
247
-
248
- ```ts
249
- import {
250
- component,
251
- defineComponent,
252
- html,
253
- registerDefaultComponents,
254
- } from '@bquery/bquery/component';
255
-
256
- component('user-card', {
257
- props: {
258
- username: { type: String, required: true },
259
- age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
260
- },
261
- beforeMount() {
262
- console.log('About to mount');
263
- },
264
- connected() {
265
- console.log('Mounted');
266
- },
267
- beforeUpdate(props) {
268
- return props.username !== '';
269
- },
270
- onError(error) {
271
- console.error('Component error:', error);
272
- },
273
- render({ props }) {
274
- return html`<div>Hello ${props.username}</div>`;
275
- },
276
- });
277
-
278
- const UserCard = defineComponent('user-card', {
279
- props: { username: { type: String, required: true } },
280
- render: ({ props }) => html`<div>Hello ${props.username}</div>`,
281
- });
282
-
283
- customElements.define('user-card', UserCard);
284
-
285
- const tags = registerDefaultComponents({ prefix: 'ui' });
286
- console.log(tags.button); // ui-button
287
- ```
288
-
289
- ### Motion – animations
290
-
291
- ```ts
292
- import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
293
-
294
- await transition({
295
- update: () => {
296
- $('#content').text('Updated');
297
- },
298
- classes: ['page-transition'],
299
- types: ['navigation'],
300
- skipOnReducedMotion: true,
301
- });
302
-
303
- await animate(card, {
304
- keyframes: keyframePresets.pop(),
305
- options: { duration: 240, easing: 'ease-out' },
306
- });
307
-
308
- const x = spring(0, { stiffness: 120, damping: 14 });
309
- x.onChange((value) => {
310
- element.style.transform = `translateX(${value}px)`;
311
- });
312
- await x.to(100);
313
- ```
314
-
315
- ### Security sanitizing
316
-
317
- ```ts
318
- import { sanitize, escapeHtml } from '@bquery/bquery/security';
319
-
320
- const safeHtml = sanitize(userInput);
321
- const safe = sanitize('<form id="cookie">...</form>');
322
- const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
323
- const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
324
- const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">');
325
- const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
326
- const escaped = escapeHtml('<script>alert(1)</script>');
327
- ```
328
-
329
- ### Platform config, cookies & accessibility
330
-
331
- ```ts
332
- import {
333
- defineBqueryConfig,
334
- useCookie,
335
- definePageMeta,
336
- useAnnouncer,
337
- storage,
338
- notifications,
339
- } from '@bquery/bquery/platform';
340
-
341
- defineBqueryConfig({
342
- fetch: { baseUrl: 'https://api.example.com' },
343
- transitions: { skipOnReducedMotion: true, classes: ['page-transition'] },
344
- components: { prefix: 'ui' },
345
- });
346
-
347
- const theme = useCookie<'light' | 'dark'>('theme', { defaultValue: 'light' });
348
- const cleanupMeta = definePageMeta({ title: 'Dashboard' });
349
- const announcer = useAnnouncer();
350
-
351
- theme.value = 'dark';
352
- announcer.announce('Preferences saved');
353
- cleanupMeta();
354
-
355
- const local = storage.local();
356
- await local.set('theme', theme.value);
357
-
358
- const permission = await notifications.requestPermission();
359
- if (permission === 'granted') {
360
- notifications.send('Build complete', {
361
- body: 'Your docs are ready.',
362
- });
363
- }
364
- ```
365
-
366
- ### Router SPA navigation
367
-
368
- ```ts
369
- import { effect } from '@bquery/bquery/reactive';
370
- import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
371
-
372
- const router = createRouter({
373
- routes: [
374
- { path: '/', name: 'home', component: HomePage },
375
- { path: '/user/:id', name: 'user', component: UserPage },
376
- { path: '*', component: NotFound },
377
- ],
378
- });
379
-
380
- router.beforeEach(async (to) => {
381
- if (to.path === '/admin' && !isAuthenticated()) {
382
- await navigate('/login');
383
- return false;
384
- }
385
- });
386
-
387
- effect(() => {
388
- console.log('Current path:', currentRoute.value.path);
389
- });
390
- ```
391
-
392
- ### Store – state management
393
-
394
- ```ts
395
- import {
396
- createStore,
397
- createPersistedStore,
398
- defineStore,
399
- mapGetters,
400
- watchStore,
401
- } from '@bquery/bquery/store';
402
-
403
- const counterStore = createStore({
404
- id: 'counter',
405
- state: () => ({ count: 0, name: 'Counter' }),
406
- getters: {
407
- doubled: (state) => state.count * 2,
408
- },
409
- actions: {
410
- increment() {
411
- this.count++;
412
- },
413
- },
414
- });
415
-
416
- const settingsStore = createPersistedStore({
417
- id: 'settings',
418
- state: () => ({ theme: 'dark', language: 'en' }),
419
- });
420
-
421
- const useCounter = defineStore('counter', {
422
- state: () => ({ count: 0 }),
423
- getters: {
424
- doubled: (state) => state.count * 2,
425
- },
426
- actions: {
427
- increment() {
428
- this.count++;
429
- },
430
- },
431
- });
432
-
433
- const counter = useCounter();
434
- const getters = mapGetters(counter, ['doubled']);
435
-
436
- watchStore(
437
- counter,
438
- (state) => state.count,
439
- (value) => {
440
- console.log('Count changed:', value, getters.doubled);
441
- }
442
- );
443
- ```
444
-
445
- ### View declarative bindings
446
-
447
- ```ts
448
- import { mount, createTemplate } from '@bquery/bquery/view';
449
- import { signal } from '@bquery/bquery/reactive';
450
-
451
- const count = signal(0);
452
- const items = signal(['Apple', 'Banana', 'Cherry']);
453
-
454
- mount('#app', {
455
- count,
456
- items,
457
- increment: () => count.value++,
458
- });
459
- ```
460
-
461
- ## Browser Support
462
-
463
- | Browser | Version | Support |
464
- | ------- | ------- | ------- |
465
- | Chrome | 90+ | ✅ Full |
466
- | Firefox | 90+ | ✅ Full |
467
- | Safari | 15+ | ✅ Full |
468
- | Edge | 90+ | ✅ Full |
469
-
470
- > **No IE support** by design.
471
-
472
- ## Documentation
473
-
474
- - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
475
- - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
476
- - **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
477
- - **Components**: [docs/guide/components.md](docs/guide/components.md)
478
- - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
479
- - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
480
- - **Security**: [docs/guide/security.md](docs/guide/security.md)
481
- - **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
482
- - **Router**: [docs/guide/router.md](docs/guide/router.md)
483
- - **Store**: [docs/guide/store.md](docs/guide/store.md)
484
- - **View**: [docs/guide/view.md](docs/guide/view.md)
485
-
486
- ## Local Development
487
-
488
- ```bash
489
- # Install dependencies
490
- bun install
491
-
492
- # Start VitePress docs
493
- bun run dev
494
-
495
- # Run Storybook
496
- bun run storybook
497
-
498
- # Run tests
499
- bun test
500
-
501
- # Build library
502
- bun run build
503
-
504
- # Build docs
505
- bun run build:docs
506
-
507
- # Generate API documentation
508
- bun run docs:api
509
- ```
510
-
511
- ## Project Structure
512
-
513
- ```text
514
- bQuery.js
515
- ├── src/
516
- │ ├── core/ # Selectors, DOM ops, events, utils
517
- │ ├── reactive/ # Signals, computed, effects, async data
518
- │ ├── component/ # Web Components helper + default library
519
- │ ├── motion/ # View transitions, FLIP, springs
520
- │ ├── security/ # Sanitizer, CSP, Trusted Types
521
- │ ├── platform/ # Storage, cache, cookies, meta, config
522
- │ ├── router/ # SPA routing, navigation guards
523
- │ ├── store/ # State management, persistence
524
- │ └── view/ # Declarative DOM bindings
525
- ├── docs/ # VitePress documentation
526
- ├── .storybook/ # Storybook config
527
- ├── stories/ # Component stories
528
- ├── tests/ # bun:test suites
529
- └── dist/ # Built files (ESM, UMD, IIFE)
530
- ```
531
-
532
- ## Contributing
533
-
534
- See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
535
-
536
- ## AI Agent Support
537
-
538
- This project provides dedicated context files for AI coding agents:
539
-
540
- - **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
541
- - **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
542
- - **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
543
-
544
- ## License
545
-
546
- 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
+ </p>
20
+
21
+ **The jQuery for the modern Web Platform.**
22
+
23
+ bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, async data composables, Web Components, motion utilities, routing, stores, and declarative views — without a mandatory build step.
24
+
25
+ ## Highlights
26
+
27
+ - **Zero-build capable**: runs directly in the browser; build tools are optional.
28
+ - **Async data built-in**: fetch and async state composables integrate directly with signals.
29
+ - **Security-focused**: DOM writes are sanitized by default; Trusted Types supported.
30
+ - **Modular**: the core stays small; extra modules are opt-in.
31
+ - **TypeScript-first**: clear types and strong IDE support.
32
+ - **Tree-shakeable**: import only what you need.
33
+ - **Storybook-ready**: default components can be previewed and developed in Storybook with dedicated story template helpers.
34
+
35
+ ## Installation
36
+
37
+ ### Via npm/bun/pnpm
38
+
39
+ ```bash
40
+ # npm
41
+ npm install @bquery/bquery
42
+
43
+ # bun
44
+ bun add @bquery/bquery
45
+
46
+ # pnpm
47
+ pnpm add @bquery/bquery
48
+ ```
49
+
50
+ ### Via CDN (Zero-build)
51
+
52
+ #### ES Modules (recommended)
53
+
54
+ ```html
55
+ <script type="module">
56
+ import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
57
+
58
+ const count = signal(0);
59
+ $('#counter').text(`Count: ${count.value}`);
60
+ </script>
61
+ ```
62
+
63
+ #### UMD (global variable)
64
+
65
+ ```html
66
+ <script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
67
+ <script>
68
+ const { $, signal } = bQuery;
69
+ const count = signal(0);
70
+ </script>
71
+ ```
72
+
73
+ #### IIFE (self-executing)
74
+
75
+ ```html
76
+ <script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
77
+ <script>
78
+ const { $, $$ } = bQuery;
79
+ $$('.items').addClass('loaded');
80
+ </script>
81
+ ```
82
+
83
+ ## Import Strategies
84
+
85
+ ```ts
86
+ // Full bundle (all modules)
87
+ import {
88
+ $,
89
+ signal,
90
+ component,
91
+ registerDefaultComponents,
92
+ defineBqueryConfig,
93
+ } from '@bquery/bquery';
94
+
95
+ // Core only
96
+ import { $, $$ } from '@bquery/bquery/core';
97
+
98
+ // Core utilities (named exports, tree-shakeable)
99
+ import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
100
+
101
+ // Reactive only
102
+ import {
103
+ signal,
104
+ computed,
105
+ effect,
106
+ linkedSignal,
107
+ persistedSignal,
108
+ useAsyncData,
109
+ useFetch,
110
+ createUseFetch,
111
+ } from '@bquery/bquery/reactive';
112
+
113
+ // Components only
114
+ import {
115
+ bool,
116
+ component,
117
+ defineComponent,
118
+ html,
119
+ registerDefaultComponents,
120
+ } from '@bquery/bquery/component';
121
+
122
+ // Motion only
123
+ import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
124
+
125
+ // Security only
126
+ import { sanitize, sanitizeHtml, trusted } from '@bquery/bquery/security';
127
+
128
+ // Platform only
129
+ import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery/bquery/platform';
130
+
131
+ // Router, Store, View
132
+ import { createRouter, navigate } from '@bquery/bquery/router';
133
+ import { createStore, defineStore } from '@bquery/bquery/store';
134
+ import { mount, createTemplate } from '@bquery/bquery/view';
135
+
136
+ // Storybook helpers
137
+ import { storyHtml, when } from '@bquery/bquery/storybook';
138
+ ```
139
+
140
+ ## Modules at a glance
141
+
142
+ | Module | Description | Size (gzip) |
143
+ | ------------- | -------------------------------------------------- | ----------- |
144
+ | **Core** | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
145
+ | **Reactive** | `signal`, `computed`, `effect`, async data/fetch | ~0.3 KB |
146
+ | **Component** | Lightweight Web Components with props + defaults | ~1.9 KB |
147
+ | **Motion** | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
148
+ | **Security** | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
149
+ | **Platform** | Storage, cache, cookies, meta, announcers, config | ~2.2 KB |
150
+ | **Router** | SPA routing, navigation guards, history API | ~2.2 KB |
151
+ | **Store** | Signal-based state management, persistence | ~0.3 KB |
152
+ | **View** | Declarative DOM bindings, directives | ~4.3 KB |
153
+
154
+ Storybook authoring helpers are also available as a dedicated entry point via `@bquery/bquery/storybook`.
155
+
156
+ ## Quick examples
157
+
158
+ ### Core – DOM & events
159
+
160
+ ```ts
161
+ import { $, $$ } from '@bquery/bquery/core';
162
+
163
+ $('#save').on('click', (event) => {
164
+ console.log('Saved', event.type);
165
+ });
166
+
167
+ $('#list').delegate('click', '.item', (event, target) => {
168
+ console.log('Item clicked', target.textContent);
169
+ });
170
+
171
+ $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
172
+
173
+ const color = $('#box').css('color');
174
+
175
+ if ($('#el').is('.active')) {
176
+ console.log('Element is active');
177
+ }
178
+
179
+ $$('.container').find('.item').addClass('found');
180
+ ```
181
+
182
+ ### Reactive – signals
183
+
184
+ ```ts
185
+ import {
186
+ signal,
187
+ computed,
188
+ effect,
189
+ batch,
190
+ watch,
191
+ readonly,
192
+ linkedSignal,
193
+ } from '@bquery/bquery/reactive';
194
+
195
+ const count = signal(0);
196
+ const doubled = computed(() => count.value * 2);
197
+
198
+ effect(() => {
199
+ console.log('Count changed', count.value);
200
+ });
201
+
202
+ watch(count, (newVal, oldVal) => {
203
+ console.log(`Changed from ${oldVal} to ${newVal}`);
204
+ });
205
+
206
+ const readOnlyCount = readonly(count);
207
+
208
+ batch(() => {
209
+ count.value++;
210
+ count.value++;
211
+ });
212
+
213
+ count.dispose();
214
+
215
+ const first = signal('Ada');
216
+ const last = signal('Lovelace');
217
+ const fullName = linkedSignal(
218
+ () => `${first.value} ${last.value}`,
219
+ (next) => {
220
+ const [nextFirst, nextLast] = next.split(' ');
221
+ first.value = nextFirst ?? '';
222
+ last.value = nextLast ?? '';
223
+ }
224
+ );
225
+
226
+ fullName.value = 'Grace Hopper';
227
+ ```
228
+
229
+ ### Reactive – async data & fetch
230
+
231
+ ```ts
232
+ import { signal, useFetch, createUseFetch } from '@bquery/bquery/reactive';
233
+
234
+ const userId = signal(1);
235
+
236
+ const user = useFetch<{ id: number; name: string }>(() => `/users/${userId.value}`, {
237
+ baseUrl: 'https://api.example.com',
238
+ watch: [userId],
239
+ query: { include: 'profile' },
240
+ });
241
+
242
+ const useApiFetch = createUseFetch({
243
+ baseUrl: 'https://api.example.com',
244
+ headers: { 'x-client': 'bquery-readme' },
245
+ });
246
+
247
+ const settings = useApiFetch<{ theme: string }>('/settings');
248
+
249
+ console.log(user.pending.value, user.data.value, settings.error.value);
250
+ ```
251
+
252
+ ### Components – Web Components
253
+
254
+ ```ts
255
+ import {
256
+ bool,
257
+ component,
258
+ defineComponent,
259
+ html,
260
+ registerDefaultComponents,
261
+ safeHtml,
262
+ } from '@bquery/bquery/component';
263
+ import { sanitizeHtml, trusted } from '@bquery/bquery/security';
264
+
265
+ const badge = trusted(sanitizeHtml('<span class="badge">Active</span>'));
266
+
267
+ component('user-card', {
268
+ props: {
269
+ username: { type: String, required: true },
270
+ age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
271
+ },
272
+ state: { count: 0 },
273
+ beforeMount() {
274
+ console.log('About to mount');
275
+ },
276
+ connected() {
277
+ console.log('Mounted');
278
+ },
279
+ beforeUpdate(newProps, oldProps) {
280
+ return newProps.username !== oldProps.username;
281
+ },
282
+ updated(change) {
283
+ console.log('Updated because of', change?.name ?? 'state/signal change');
284
+ },
285
+ onError(error) {
286
+ console.error('Component error:', error);
287
+ },
288
+ render({ props, state }) {
289
+ return safeHtml`
290
+ <button class="user-card" ${bool('disabled', state.count > 3)}>
291
+ ${badge}
292
+ <span>Hello ${props.username}</span>
293
+ </button>
294
+ `;
295
+ },
296
+ });
297
+
298
+ const UserCard = defineComponent('user-card-manual', {
299
+ props: { username: { type: String, required: true } },
300
+ render: ({ props }) => html`<div>Hello ${props.username}</div>`,
301
+ });
302
+
303
+ customElements.define('user-card-manual', UserCard);
304
+
305
+ const tags = registerDefaultComponents({ prefix: 'ui' });
306
+ console.log(tags.button); // ui-button
307
+ ```
308
+
309
+ ### Storybook – authoring helpers
310
+
311
+ ```ts
312
+ import { storyHtml, when } from '@bquery/bquery/storybook';
313
+
314
+ export const Primary = {
315
+ args: { disabled: false, label: 'Save' },
316
+ render: ({ disabled, label }) =>
317
+ storyHtml`
318
+ <ui-card>
319
+ <ui-button ?disabled=${disabled}>${label}</ui-button>
320
+ ${when(!disabled, '<small>Ready to submit</small>')}
321
+ </ui-card>
322
+ `,
323
+ };
324
+ ```
325
+
326
+ ### Motion animations
327
+
328
+ ```ts
329
+ import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
330
+
331
+ await transition({
332
+ update: () => {
333
+ $('#content').text('Updated');
334
+ },
335
+ classes: ['page-transition'],
336
+ types: ['navigation'],
337
+ skipOnReducedMotion: true,
338
+ });
339
+
340
+ await animate(card, {
341
+ keyframes: keyframePresets.pop(),
342
+ options: { duration: 240, easing: 'ease-out' },
343
+ });
344
+
345
+ const x = spring(0, { stiffness: 120, damping: 14 });
346
+ x.onChange((value) => {
347
+ element.style.transform = `translateX(${value}px)`;
348
+ });
349
+ await x.to(100);
350
+ ```
351
+
352
+ ### Security – sanitizing
353
+
354
+ ```ts
355
+ import { sanitize, escapeHtml, sanitizeHtml, trusted } from '@bquery/bquery/security';
356
+ import { safeHtml } from '@bquery/bquery/component';
357
+
358
+ const safeMarkup = sanitize(userInput);
359
+ const safe = sanitize('<form id="cookie">...</form>');
360
+ const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
361
+ const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
362
+ const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">');
363
+ const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
364
+ const escaped = escapeHtml('<script>alert(1)</script>');
365
+ const icon = trusted(sanitizeHtml('<span class="icon">♥</span>'));
366
+ const button = safeHtml`<button>${icon}<span>Save</span></button>`;
367
+ ```
368
+
369
+ ### Platform config, cookies & accessibility
370
+
371
+ ```ts
372
+ import {
373
+ defineBqueryConfig,
374
+ useCookie,
375
+ definePageMeta,
376
+ useAnnouncer,
377
+ storage,
378
+ notifications,
379
+ } from '@bquery/bquery/platform';
380
+
381
+ defineBqueryConfig({
382
+ fetch: { baseUrl: 'https://api.example.com' },
383
+ transitions: { skipOnReducedMotion: true, classes: ['page-transition'] },
384
+ components: { prefix: 'ui' },
385
+ });
386
+
387
+ const theme = useCookie<'light' | 'dark'>('theme', { defaultValue: 'light' });
388
+ const cleanupMeta = definePageMeta({ title: 'Dashboard' });
389
+ const announcer = useAnnouncer();
390
+
391
+ theme.value = 'dark';
392
+ announcer.announce('Preferences saved');
393
+ cleanupMeta();
394
+
395
+ const local = storage.local();
396
+ await local.set('theme', theme.value);
397
+
398
+ const permission = await notifications.requestPermission();
399
+ if (permission === 'granted') {
400
+ notifications.send('Build complete', {
401
+ body: 'Your docs are ready.',
402
+ });
403
+ }
404
+ ```
405
+
406
+ ### Router – SPA navigation
407
+
408
+ ```ts
409
+ import { effect } from '@bquery/bquery/reactive';
410
+ import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
411
+
412
+ const router = createRouter({
413
+ routes: [
414
+ { path: '/', name: 'home', component: HomePage },
415
+ { path: '/user/:id', name: 'user', component: UserPage },
416
+ { path: '*', component: NotFound },
417
+ ],
418
+ });
419
+
420
+ router.beforeEach(async (to) => {
421
+ if (to.path === '/admin' && !isAuthenticated()) {
422
+ await navigate('/login');
423
+ return false;
424
+ }
425
+ });
426
+
427
+ effect(() => {
428
+ console.log('Current path:', currentRoute.value.path);
429
+ });
430
+ ```
431
+
432
+ ### Store – state management
433
+
434
+ ```ts
435
+ import {
436
+ createStore,
437
+ createPersistedStore,
438
+ defineStore,
439
+ mapGetters,
440
+ watchStore,
441
+ } from '@bquery/bquery/store';
442
+
443
+ const counterStore = createStore({
444
+ id: 'counter',
445
+ state: () => ({ count: 0, name: 'Counter' }),
446
+ getters: {
447
+ doubled: (state) => state.count * 2,
448
+ },
449
+ actions: {
450
+ increment() {
451
+ this.count++;
452
+ },
453
+ },
454
+ });
455
+
456
+ const settingsStore = createPersistedStore({
457
+ id: 'settings',
458
+ state: () => ({ theme: 'dark', language: 'en' }),
459
+ });
460
+
461
+ const useCounter = defineStore('counter', {
462
+ state: () => ({ count: 0 }),
463
+ getters: {
464
+ doubled: (state) => state.count * 2,
465
+ },
466
+ actions: {
467
+ increment() {
468
+ this.count++;
469
+ },
470
+ },
471
+ });
472
+
473
+ const counter = useCounter();
474
+ const getters = mapGetters(counter, ['doubled']);
475
+
476
+ watchStore(
477
+ counter,
478
+ (state) => state.count,
479
+ (value) => {
480
+ console.log('Count changed:', value, getters.doubled);
481
+ }
482
+ );
483
+ ```
484
+
485
+ ### View – declarative bindings
486
+
487
+ ```ts
488
+ import { mount, createTemplate } from '@bquery/bquery/view';
489
+ import { signal } from '@bquery/bquery/reactive';
490
+
491
+ const count = signal(0);
492
+ const items = signal(['Apple', 'Banana', 'Cherry']);
493
+
494
+ mount('#app', {
495
+ count,
496
+ items,
497
+ increment: () => count.value++,
498
+ });
499
+ ```
500
+
501
+ ## Browser Support
502
+
503
+ | Browser | Version | Support |
504
+ | ------- | ------- | ------- |
505
+ | Chrome | 90+ | ✅ Full |
506
+ | Firefox | 90+ | ✅ Full |
507
+ | Safari | 15+ | ✅ Full |
508
+ | Edge | 90+ | ✅ Full |
509
+
510
+ > **No IE support** by design.
511
+
512
+ ## Documentation
513
+
514
+ - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
515
+ - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
516
+ - **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
517
+ - **Components**: [docs/guide/components.md](docs/guide/components.md)
518
+ - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
519
+ - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
520
+ - **Security**: [docs/guide/security.md](docs/guide/security.md)
521
+ - **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
522
+ - **Router**: [docs/guide/router.md](docs/guide/router.md)
523
+ - **Store**: [docs/guide/store.md](docs/guide/store.md)
524
+ - **View**: [docs/guide/view.md](docs/guide/view.md)
525
+
526
+ ## Local Development
527
+
528
+ ```bash
529
+ # Install dependencies
530
+ bun install
531
+
532
+ # Start VitePress docs
533
+ bun run dev
534
+
535
+ # Run Storybook
536
+ bun run storybook
537
+
538
+ # Run tests
539
+ bun test
540
+
541
+ # Build library
542
+ bun run build
543
+
544
+ # Build docs
545
+ bun run build:docs
546
+
547
+ # Generate API documentation
548
+ bun run docs:api
549
+ ```
550
+
551
+ ## Project Structure
552
+
553
+ ```text
554
+ bQuery.js
555
+ ├── src/
556
+ │ ├── core/ # Selectors, DOM ops, events, utils
557
+ │ ├── reactive/ # Signals, computed, effects, async data
558
+ │ ├── component/ # Web Components helper + default library
559
+ │ ├── motion/ # View transitions, FLIP, springs
560
+ │ ├── security/ # Sanitizer, CSP, Trusted Types
561
+ │ ├── platform/ # Storage, cache, cookies, meta, config
562
+ │ ├── router/ # SPA routing, navigation guards
563
+ │ ├── store/ # State management, persistence
564
+ │ └── view/ # Declarative DOM bindings
565
+ ├── docs/ # VitePress documentation
566
+ ├── .storybook/ # Storybook config
567
+ ├── stories/ # Component stories
568
+ ├── tests/ # bun:test suites
569
+ └── dist/ # Built files (ESM, UMD, IIFE)
570
+ ```
571
+
572
+ ## Contributing
573
+
574
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
575
+
576
+ ## AI Agent Support
577
+
578
+ This project provides dedicated context files for AI coding agents:
579
+
580
+ - **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
581
+ - **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
582
+ - **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
583
+
584
+ ## License
585
+
586
+ MIT – See [LICENSE.md](LICENSE.md) for details.