@bquery/bquery 1.0.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 (80) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +266 -0
  3. package/dist/component/index.d.ts +155 -0
  4. package/dist/component/index.d.ts.map +1 -0
  5. package/dist/component.es.mjs +128 -0
  6. package/dist/component.es.mjs.map +1 -0
  7. package/dist/core/collection.d.ts +198 -0
  8. package/dist/core/collection.d.ts.map +1 -0
  9. package/dist/core/element.d.ts +301 -0
  10. package/dist/core/element.d.ts.map +1 -0
  11. package/dist/core/index.d.ts +5 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/selector.d.ts +11 -0
  14. package/dist/core/selector.d.ts.map +1 -0
  15. package/dist/core/shared.d.ts +7 -0
  16. package/dist/core/shared.d.ts.map +1 -0
  17. package/dist/core/utils.d.ts +300 -0
  18. package/dist/core/utils.d.ts.map +1 -0
  19. package/dist/core.es.mjs +1015 -0
  20. package/dist/core.es.mjs.map +1 -0
  21. package/dist/full.d.ts +48 -0
  22. package/dist/full.d.ts.map +1 -0
  23. package/dist/full.es.mjs +43 -0
  24. package/dist/full.es.mjs.map +1 -0
  25. package/dist/full.iife.js +2 -0
  26. package/dist/full.iife.js.map +1 -0
  27. package/dist/full.umd.js +2 -0
  28. package/dist/full.umd.js.map +1 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.es.mjs +43 -0
  32. package/dist/index.es.mjs.map +1 -0
  33. package/dist/motion/index.d.ts +145 -0
  34. package/dist/motion/index.d.ts.map +1 -0
  35. package/dist/motion.es.mjs +104 -0
  36. package/dist/motion.es.mjs.map +1 -0
  37. package/dist/platform/buckets.d.ts +44 -0
  38. package/dist/platform/buckets.d.ts.map +1 -0
  39. package/dist/platform/cache.d.ts +71 -0
  40. package/dist/platform/cache.d.ts.map +1 -0
  41. package/dist/platform/index.d.ts +15 -0
  42. package/dist/platform/index.d.ts.map +1 -0
  43. package/dist/platform/notifications.d.ts +52 -0
  44. package/dist/platform/notifications.d.ts.map +1 -0
  45. package/dist/platform/storage.d.ts +69 -0
  46. package/dist/platform/storage.d.ts.map +1 -0
  47. package/dist/platform.es.mjs +245 -0
  48. package/dist/platform.es.mjs.map +1 -0
  49. package/dist/reactive/index.d.ts +8 -0
  50. package/dist/reactive/index.d.ts.map +1 -0
  51. package/dist/reactive/signal.d.ts +204 -0
  52. package/dist/reactive/signal.d.ts.map +1 -0
  53. package/dist/reactive.es.mjs +123 -0
  54. package/dist/reactive.es.mjs.map +1 -0
  55. package/dist/security/index.d.ts +8 -0
  56. package/dist/security/index.d.ts.map +1 -0
  57. package/dist/security/sanitize.d.ts +99 -0
  58. package/dist/security/sanitize.d.ts.map +1 -0
  59. package/dist/security.es.mjs +194 -0
  60. package/dist/security.es.mjs.map +1 -0
  61. package/package.json +120 -0
  62. package/src/component/index.ts +360 -0
  63. package/src/core/collection.ts +339 -0
  64. package/src/core/element.ts +493 -0
  65. package/src/core/index.ts +4 -0
  66. package/src/core/selector.ts +29 -0
  67. package/src/core/shared.ts +13 -0
  68. package/src/core/utils.ts +425 -0
  69. package/src/full.ts +101 -0
  70. package/src/index.ts +27 -0
  71. package/src/motion/index.ts +365 -0
  72. package/src/platform/buckets.ts +115 -0
  73. package/src/platform/cache.ts +130 -0
  74. package/src/platform/index.ts +18 -0
  75. package/src/platform/notifications.ts +87 -0
  76. package/src/platform/storage.ts +208 -0
  77. package/src/reactive/index.ts +9 -0
  78. package/src/reactive/signal.ts +347 -0
  79. package/src/security/index.ts +18 -0
  80. package/src/security/sanitize.ts +446 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 @bQuery / Jonas Pfalzgraf
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # bQuery.js
2
+
3
+ [![Repo](https://img.shields.io/badge/github-bquery%2Fbquery-24292f?logo=github)](https://github.com/bquery/bquery)
4
+ [![Stars](https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github)](https://github.com/bquery/bquery/stargazers)
5
+ [![Issues](https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github)](https://github.com/bquery/bquery/issues)
6
+ [![License](https://img.shields.io/github/license/bquery/bquery?style=flat)](https://github.com/bquery/bquery/blob/main/LICENSE.md)
7
+ [![npm](https://img.shields.io/npm/v/bquery)](https://www.npmjs.com/package/bquery)
8
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/bquery)](https://bundlephobia.com/package/bquery)
9
+ [![unpkg](https://img.shields.io/badge/unpkg-browse-blue?logo=unpkg)](https://unpkg.com/bquery/)
10
+
11
+ **The jQuery for the modern Web Platform.**
12
+
13
+ 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.
14
+
15
+ ## Highlights
16
+
17
+ - **Zero‑build capable**: runs directly in the browser; build tools are optional.
18
+ - **Security‑focused**: DOM writes are sanitized by default; Trusted Types supported.
19
+ - **Modular**: the core stays small; extra modules are opt‑in.
20
+ - **TypeScript‑first**: clear types and strong IDE support.
21
+ - **Tree-shakeable**: import only what you need.
22
+
23
+ ## Installation
24
+
25
+ ### Via npm/bun/pnpm
26
+
27
+ ```bash
28
+ # npm
29
+ npm install bquery
30
+
31
+ # bun
32
+ bun add bquery
33
+
34
+ # pnpm
35
+ pnpm add bquery
36
+ ```
37
+
38
+ ### Via CDN (Zero-build)
39
+
40
+ #### ES Modules (recommended)
41
+
42
+ ```html
43
+ <script type="module">
44
+ import { $, signal } from 'https://unpkg.com/bquery@1/dist/full.es.mjs';
45
+
46
+ const count = signal(0);
47
+ $('#counter').text(`Count: ${count.value}`);
48
+ </script>
49
+ ```
50
+
51
+ #### UMD (global variable)
52
+
53
+ ```html
54
+ <script src="https://unpkg.com/bquery@1/dist/full.umd.js"></script>
55
+ <script>
56
+ const { $, signal } = bQuery;
57
+ const count = signal(0);
58
+ </script>
59
+ ```
60
+
61
+ #### IIFE (self-executing)
62
+
63
+ ```html
64
+ <script src="https://unpkg.com/bquery@1/dist/full.iife.js"></script>
65
+ <script>
66
+ const { $, $$ } = bQuery;
67
+ $$('.items').addClass('loaded');
68
+ </script>
69
+ ```
70
+
71
+ ### Import Strategies
72
+
73
+ ```ts
74
+ // Full bundle (all modules)
75
+ import { $, signal, component } from 'bquery';
76
+
77
+ // Core only
78
+ import { $, $$ } from 'bquery/core';
79
+
80
+ // À la carte (individual modules)
81
+ import { signal, computed, effect } from 'bquery/reactive';
82
+ import { component, html } from 'bquery/component';
83
+ import { transition, spring } from 'bquery/motion';
84
+ import { sanitize } from 'bquery/security';
85
+ import { storage, cache } from 'bquery/platform';
86
+ ```
87
+
88
+ ## Modules at a glance
89
+
90
+ | Module | Description | Size (gzip) |
91
+ | ------------- | ---------------------------------------------- | ----------- |
92
+ | **Core** | Selectors, DOM manipulation, events, utilities | ~6 KB |
93
+ | **Reactive** | `signal`, `computed`, `effect`, `batch` | ~1 KB |
94
+ | **Component** | Lightweight Web Components with props | ~1.3 KB |
95
+ | **Motion** | View transitions, FLIP animations, springs | ~1.2 KB |
96
+ | **Security** | HTML sanitizing, Trusted Types, CSP | ~1.6 KB |
97
+ | **Platform** | Storage, cache, notifications, buckets | ~1.7 KB |
98
+
99
+ ## Quick examples
100
+
101
+ ### Core – DOM & events
102
+
103
+ ```ts
104
+ import { $, $$ } from 'bquery/core';
105
+
106
+ // jQuery-style selectors
107
+ $('#save').on('click', (event) => {
108
+ console.log('Saved', event.type);
109
+ });
110
+
111
+ // Method chaining
112
+ $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
113
+
114
+ // Collections
115
+ $$('.items').addClass('highlight');
116
+ ```
117
+
118
+ ### Reactive – signals
119
+
120
+ ```ts
121
+ import { signal, computed, effect, batch } from 'bquery/reactive';
122
+
123
+ const count = signal(0);
124
+ const doubled = computed(() => count.value * 2);
125
+
126
+ effect(() => {
127
+ console.log('Count changed', count.value);
128
+ });
129
+
130
+ // Batch updates for performance
131
+ batch(() => {
132
+ count.value++;
133
+ count.value++;
134
+ });
135
+ ```
136
+
137
+ ### Components – Web Components
138
+
139
+ ```ts
140
+ import { component, html } from 'bquery/component';
141
+
142
+ component('user-card', {
143
+ props: {
144
+ username: { type: String, required: true },
145
+ },
146
+ render({ props }) {
147
+ return html`<div>Hello ${props.username}</div>`;
148
+ },
149
+ });
150
+ ```
151
+
152
+ ### Motion – animations
153
+
154
+ ```ts
155
+ import { transition, spring } from 'bquery/motion';
156
+
157
+ // View transitions (with fallback)
158
+ await transition(() => {
159
+ $('#content').text('Updated');
160
+ });
161
+
162
+ // Spring physics
163
+ const x = spring(0, { stiffness: 120, damping: 14 });
164
+ x.onChange((value) => {
165
+ element.style.transform = `translateX(${value}px)`;
166
+ });
167
+ await x.to(100);
168
+ ```
169
+
170
+ ### Security – sanitizing
171
+
172
+ ```ts
173
+ import { sanitize, escapeHtml } from 'bquery/security';
174
+
175
+ // Sanitize HTML (removes dangerous elements)
176
+ const safeHtml = sanitize(userInput);
177
+
178
+ // Escape for text display
179
+ const escaped = escapeHtml('<script>alert(1)</script>');
180
+ ```
181
+
182
+ ### Platform – storage & APIs
183
+
184
+ ```ts
185
+ import { storage, notifications } from 'bquery/platform';
186
+
187
+ // Unified storage API
188
+ const local = storage.local();
189
+ await local.set('theme', 'dark');
190
+ const theme = await local.get<string>('theme');
191
+
192
+ // Notifications
193
+ const permission = await notifications.requestPermission();
194
+ if (permission === 'granted') {
195
+ notifications.send('Build complete', {
196
+ body: 'Your docs are ready.',
197
+ });
198
+ }
199
+ ```
200
+
201
+ ## Browser Support
202
+
203
+ | Browser | Version | Support |
204
+ | ------- | ------- | ------- |
205
+ | Chrome | 90+ | ✅ Full |
206
+ | Firefox | 90+ | ✅ Full |
207
+ | Safari | 15+ | ✅ Full |
208
+ | Edge | 90+ | ✅ Full |
209
+
210
+ > **No IE support** by design.
211
+
212
+ ## Documentation
213
+
214
+ - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
215
+ - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
216
+ - **Components**: [docs/guide/components.md](docs/guide/components.md)
217
+ - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
218
+ - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
219
+ - **Security**: [docs/guide/security.md](docs/guide/security.md)
220
+
221
+ ## Local Development
222
+
223
+ ```bash
224
+ # Install dependencies
225
+ bun install
226
+
227
+ # Start VitePress docs
228
+ bun run dev
229
+
230
+ # Run Vite playground
231
+ bun run playground
232
+
233
+ # Run tests
234
+ bun test
235
+
236
+ # Build library
237
+ bun run build
238
+
239
+ # Generate API documentation
240
+ bun run docs:api
241
+ ```
242
+
243
+ ## Project Structure
244
+
245
+ ```text
246
+ bQuery.js
247
+ ├── src/
248
+ │ ├── core/ # Selectors, DOM ops, events, utils
249
+ │ ├── reactive/ # Signals, computed, effects
250
+ │ ├── component/ # Web Components helper
251
+ │ ├── motion/ # View transitions, FLIP, springs
252
+ │ ├── security/ # Sanitizer, CSP, Trusted Types
253
+ │ └── platform/ # Storage, cache, notifications
254
+ ├── docs/ # VitePress documentation
255
+ ├── playground/ # Vite demo app
256
+ ├── tests/ # bun:test suites
257
+ └── dist/ # Built files (ESM, UMD, IIFE)
258
+ ```
259
+
260
+ ## Contributing
261
+
262
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
263
+
264
+ ## License
265
+
266
+ MIT – See [LICENSE.md](LICENSE.md) for details.
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Minimal Web Component helper for building custom elements.
3
+ *
4
+ * This module provides a declarative API for defining Web Components
5
+ * without complex build steps. Features include:
6
+ * - Type-safe props with automatic attribute coercion
7
+ * - Reactive state management
8
+ * - Shadow DOM encapsulation with scoped styles
9
+ * - Lifecycle hooks (connected, disconnected)
10
+ * - Event emission helpers
11
+ *
12
+ * @module bquery/component
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { component, html } from 'bquery/component';
17
+ *
18
+ * component('user-card', {
19
+ * props: {
20
+ * username: { type: String, required: true },
21
+ * avatar: { type: String, default: '/default-avatar.png' },
22
+ * },
23
+ * styles: `
24
+ * .card { padding: 1rem; border: 1px solid #ccc; }
25
+ * `,
26
+ * render({ props }) {
27
+ * return html`
28
+ * <div class="card">
29
+ * <img src="${props.avatar}" alt="${props.username}" />
30
+ * <h3>${props.username}</h3>
31
+ * </div>
32
+ * `;
33
+ * },
34
+ * });
35
+ * ```
36
+ */
37
+ /**
38
+ * Defines a single prop's type and configuration.
39
+ *
40
+ * @template T - The TypeScript type of the prop value
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const myProp: PropDefinition<number> = {
45
+ * type: Number,
46
+ * required: false,
47
+ * default: 0,
48
+ * };
49
+ * ```
50
+ */
51
+ export type PropDefinition<T = unknown> = {
52
+ /** Constructor or converter function for the prop type */
53
+ type: StringConstructor | NumberConstructor | BooleanConstructor | ObjectConstructor | ArrayConstructor | {
54
+ new (value: unknown): T;
55
+ } | ((value: unknown) => T);
56
+ /** Whether the prop must be provided */
57
+ required?: boolean;
58
+ /** Default value when prop is not provided */
59
+ default?: T;
60
+ };
61
+ /**
62
+ * Complete component definition including props, state, styles, and lifecycle.
63
+ *
64
+ * @template TProps - Type of the component's props
65
+ */
66
+ export type ComponentDefinition<TProps extends Record<string, unknown> = Record<string, unknown>> = {
67
+ /** Prop definitions with types and defaults */
68
+ props?: Record<keyof TProps, PropDefinition>;
69
+ /** Initial internal state */
70
+ state?: Record<string, unknown>;
71
+ /** CSS styles scoped to the component's shadow DOM */
72
+ styles?: string;
73
+ /** Lifecycle hook called when component is added to DOM */
74
+ connected?: () => void;
75
+ /** Lifecycle hook called when component is removed from DOM */
76
+ disconnected?: () => void;
77
+ /** Lifecycle hook called after reactive updates trigger a render */
78
+ updated?: () => void;
79
+ /** Render function returning HTML string */
80
+ render: (context: {
81
+ props: TProps;
82
+ state: Record<string, unknown>;
83
+ emit: (event: string, detail?: unknown) => void;
84
+ }) => string;
85
+ };
86
+ /**
87
+ * Tagged template literal for creating HTML strings.
88
+ *
89
+ * This function handles interpolation of values into HTML templates,
90
+ * converting null/undefined to empty strings.
91
+ *
92
+ * @param strings - Template literal string parts
93
+ * @param values - Interpolated values
94
+ * @returns Combined HTML string
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const name = 'World';
99
+ * const greeting = html`<h1>Hello, ${name}!</h1>`;
100
+ * // Result: '<h1>Hello, World!</h1>'
101
+ * ```
102
+ */
103
+ export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => string;
104
+ /**
105
+ * Escapes HTML entities in interpolated values for XSS prevention.
106
+ * Use this when you need to safely embed user content in templates.
107
+ *
108
+ * @param strings - Template literal string parts
109
+ * @param values - Interpolated values to escape
110
+ * @returns Combined HTML string with escaped values
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const userInput = '<script>alert("xss")</script>';
115
+ * const safe = safeHtml`<div>${userInput}</div>`;
116
+ * // Result: '<div>&lt;script&gt;alert("xss")&lt;/script&gt;</div>'
117
+ * ```
118
+ */
119
+ export declare const safeHtml: (strings: TemplateStringsArray, ...values: unknown[]) => string;
120
+ /**
121
+ * Defines and registers a custom Web Component.
122
+ *
123
+ * This function creates a new custom element with the given tag name
124
+ * and configuration. The component uses Shadow DOM for encapsulation
125
+ * and automatically re-renders when observed attributes change.
126
+ *
127
+ * @template TProps - Type of the component's props
128
+ * @param tagName - The custom element tag name (must contain a hyphen)
129
+ * @param definition - The component configuration
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * component('counter-button', {
134
+ * props: {
135
+ * start: { type: Number, default: 0 },
136
+ * },
137
+ * state: { count: 0 },
138
+ * styles: `
139
+ * button { padding: 0.5rem 1rem; }
140
+ * `,
141
+ * connected() {
142
+ * console.log('Counter mounted');
143
+ * },
144
+ * render({ props, state, emit }) {
145
+ * return html`
146
+ * <button onclick="this.getRootNode().host.increment()">
147
+ * Count: ${state.count}
148
+ * </button>
149
+ * `;
150
+ * },
151
+ * });
152
+ * ```
153
+ */
154
+ export declare const component: <TProps extends Record<string, unknown>>(tagName: string, definition: ComponentDefinition<TProps>) => void;
155
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/component/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI;IACxC,0DAA0D;IAC1D,IAAI,EACA,iBAAiB,GACjB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,gBAAgB,GAChB;QAAE,KAAK,KAAK,EAAE,OAAO,GAAG,CAAC,CAAA;KAAE,GAC3B,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,CAAC,CAAC;CACb,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC9F;IACE,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7C,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,4CAA4C;IAC5C,MAAM,EAAE,CAAC,OAAO,EAAE;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;KACjD,KAAK,MAAM,CAAC;CACd,CAAC;AAsDJ;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,IAAI,GAAI,SAAS,oBAAoB,EAAE,GAAG,QAAQ,OAAO,EAAE,KAAG,MAE1E,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,oBAAoB,EAAE,GAAG,QAAQ,OAAO,EAAE,KAAG,MAgB9E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,SAAS,GAAI,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,SAAS,MAAM,EACf,YAAY,mBAAmB,CAAC,MAAM,CAAC,KACtC,IA0HF,CAAC"}
@@ -0,0 +1,128 @@
1
+ const i = (t, e) => {
2
+ const { type: o } = e;
3
+ if (o === String) return t;
4
+ if (o === Number) {
5
+ const s = Number(t);
6
+ return Number.isNaN(s) ? t : s;
7
+ }
8
+ if (o === Boolean) {
9
+ const s = t.trim().toLowerCase();
10
+ return s === "" || s === "true" || s === "1" ? !0 : s === "false" || s === "0" ? !1 : !!t;
11
+ }
12
+ if (o === Object || o === Array)
13
+ try {
14
+ return JSON.parse(t);
15
+ } catch {
16
+ return t;
17
+ }
18
+ if (typeof o == "function") {
19
+ const s = o, r = o;
20
+ try {
21
+ return s(t);
22
+ } catch {
23
+ return new r(t);
24
+ }
25
+ }
26
+ return t;
27
+ }, l = (t, ...e) => t.reduce((o, s, r) => `${o}${s}${e[r] ?? ""}`, ""), h = (t, ...e) => {
28
+ const o = {
29
+ "&": "&amp;",
30
+ "<": "&lt;",
31
+ ">": "&gt;",
32
+ '"': "&quot;",
33
+ "'": "&#x27;",
34
+ "`": "&#x60;"
35
+ }, s = (r) => String(r ?? "").replace(/[&<>"'`]/g, (c) => o[c]);
36
+ return t.reduce((r, n, c) => `${r}${n}${s(e[c])}`, "");
37
+ }, d = (t, e) => {
38
+ class o extends HTMLElement {
39
+ constructor() {
40
+ super(), this.state = { ...e.state ?? {} }, this.props = {}, this.attachShadow({ mode: "open" }), this.syncProps();
41
+ }
42
+ /**
43
+ * Returns the list of attributes to observe for changes.
44
+ */
45
+ static get observedAttributes() {
46
+ return Object.keys(e.props ?? {});
47
+ }
48
+ /**
49
+ * Called when the element is added to the DOM.
50
+ */
51
+ connectedCallback() {
52
+ e.connected?.call(this), this.render();
53
+ }
54
+ /**
55
+ * Called when the element is removed from the DOM.
56
+ */
57
+ disconnectedCallback() {
58
+ e.disconnected?.call(this);
59
+ }
60
+ /**
61
+ * Called when an observed attribute changes.
62
+ */
63
+ attributeChangedCallback() {
64
+ this.syncProps(), this.render(!0);
65
+ }
66
+ /**
67
+ * Updates a state property and triggers a re-render.
68
+ *
69
+ * @param key - The state property key
70
+ * @param value - The new value
71
+ */
72
+ setState(r, n) {
73
+ this.state[r] = n, this.render(!0);
74
+ }
75
+ /**
76
+ * Gets a state property value.
77
+ *
78
+ * @param key - The state property key
79
+ * @returns The current value
80
+ */
81
+ getState(r) {
82
+ return this.state[r];
83
+ }
84
+ /**
85
+ * Synchronizes props from attributes.
86
+ * @internal
87
+ */
88
+ syncProps() {
89
+ const r = e.props ?? {};
90
+ for (const [n, c] of Object.entries(r)) {
91
+ const u = this.getAttribute(n);
92
+ if (u == null) {
93
+ if (c.required && c.default === void 0)
94
+ throw new Error(`bQuery component: missing required prop "${n}"`);
95
+ this.props[n] = c.default ?? void 0;
96
+ continue;
97
+ }
98
+ this.props[n] = i(
99
+ u,
100
+ c
101
+ );
102
+ }
103
+ }
104
+ /**
105
+ * Renders the component to its shadow root.
106
+ * @internal
107
+ */
108
+ render(r = !1) {
109
+ const n = (p, a) => {
110
+ this.dispatchEvent(new CustomEvent(p, { detail: a, bubbles: !0, composed: !0 }));
111
+ };
112
+ if (!this.shadowRoot) return;
113
+ const c = e.render({
114
+ props: this.props,
115
+ state: this.state,
116
+ emit: n
117
+ }), u = e.styles ? `<style>${e.styles}</style>` : "";
118
+ this.shadowRoot.innerHTML = `${u}${c}`, r && e.updated?.call(this);
119
+ }
120
+ }
121
+ customElements.get(t) || customElements.define(t, o);
122
+ };
123
+ export {
124
+ d as component,
125
+ l as html,
126
+ h as safeHtml
127
+ };
128
+ //# sourceMappingURL=component.es.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.es.mjs","sources":["../src/component/index.ts"],"sourcesContent":["/**\n * Minimal Web Component helper for building custom elements.\n *\n * This module provides a declarative API for defining Web Components\n * without complex build steps. Features include:\n * - Type-safe props with automatic attribute coercion\n * - Reactive state management\n * - Shadow DOM encapsulation with scoped styles\n * - Lifecycle hooks (connected, disconnected)\n * - Event emission helpers\n *\n * @module bquery/component\n *\n * @example\n * ```ts\n * import { component, html } from 'bquery/component';\n *\n * component('user-card', {\n * props: {\n * username: { type: String, required: true },\n * avatar: { type: String, default: '/default-avatar.png' },\n * },\n * styles: `\n * .card { padding: 1rem; border: 1px solid #ccc; }\n * `,\n * render({ props }) {\n * return html`\n * <div class=\"card\">\n * <img src=\"${props.avatar}\" alt=\"${props.username}\" />\n * <h3>${props.username}</h3>\n * </div>\n * `;\n * },\n * });\n * ```\n */\n\n/**\n * Defines a single prop's type and configuration.\n *\n * @template T - The TypeScript type of the prop value\n *\n * @example\n * ```ts\n * const myProp: PropDefinition<number> = {\n * type: Number,\n * required: false,\n * default: 0,\n * };\n * ```\n */\nexport type PropDefinition<T = unknown> = {\n /** Constructor or converter function for the prop type */\n type:\n | StringConstructor\n | NumberConstructor\n | BooleanConstructor\n | ObjectConstructor\n | ArrayConstructor\n | { new (value: unknown): T }\n | ((value: unknown) => T);\n /** Whether the prop must be provided */\n required?: boolean;\n /** Default value when prop is not provided */\n default?: T;\n};\n\n/**\n * Complete component definition including props, state, styles, and lifecycle.\n *\n * @template TProps - Type of the component's props\n */\nexport type ComponentDefinition<TProps extends Record<string, unknown> = Record<string, unknown>> =\n {\n /** Prop definitions with types and defaults */\n props?: Record<keyof TProps, PropDefinition>;\n /** Initial internal state */\n state?: Record<string, unknown>;\n /** CSS styles scoped to the component's shadow DOM */\n styles?: string;\n /** Lifecycle hook called when component is added to DOM */\n connected?: () => void;\n /** Lifecycle hook called when component is removed from DOM */\n disconnected?: () => void;\n /** Lifecycle hook called after reactive updates trigger a render */\n updated?: () => void;\n /** Render function returning HTML string */\n render: (context: {\n props: TProps;\n state: Record<string, unknown>;\n emit: (event: string, detail?: unknown) => void;\n }) => string;\n };\n\n/**\n * Coerces a string attribute value into a typed prop value.\n * Supports String, Number, Boolean, Object, Array, and custom converters.\n *\n * @internal\n * @template T - The target type\n * @param rawValue - The raw string value from the attribute\n * @param config - The prop definition with type information\n * @returns The coerced value of type T\n */\nconst coercePropValue = <T>(rawValue: string, config: PropDefinition<T>): T => {\n const { type } = config;\n\n if (type === String) return rawValue as T;\n\n if (type === Number) {\n const parsed = Number(rawValue);\n return (Number.isNaN(parsed) ? rawValue : parsed) as T;\n }\n\n if (type === Boolean) {\n const normalized = rawValue.trim().toLowerCase();\n if (normalized === '' || normalized === 'true' || normalized === '1') {\n return true as T;\n }\n if (normalized === 'false' || normalized === '0') {\n return false as T;\n }\n return Boolean(rawValue) as T;\n }\n\n if (type === Object || type === Array) {\n try {\n return JSON.parse(rawValue) as T;\n } catch {\n return rawValue as T;\n }\n }\n\n if (typeof type === 'function') {\n const callable = type as (value: unknown) => T;\n const constructable = type as new (value: unknown) => T;\n try {\n return callable(rawValue);\n } catch {\n return new constructable(rawValue);\n }\n }\n\n return rawValue as T;\n};\n\n/**\n * Tagged template literal for creating HTML strings.\n *\n * This function handles interpolation of values into HTML templates,\n * converting null/undefined to empty strings.\n *\n * @param strings - Template literal string parts\n * @param values - Interpolated values\n * @returns Combined HTML string\n *\n * @example\n * ```ts\n * const name = 'World';\n * const greeting = html`<h1>Hello, ${name}!</h1>`;\n * // Result: '<h1>Hello, World!</h1>'\n * ```\n */\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): string => {\n return strings.reduce((acc, part, index) => `${acc}${part}${values[index] ?? ''}`, '');\n};\n\n/**\n * Escapes HTML entities in interpolated values for XSS prevention.\n * Use this when you need to safely embed user content in templates.\n *\n * @param strings - Template literal string parts\n * @param values - Interpolated values to escape\n * @returns Combined HTML string with escaped values\n *\n * @example\n * ```ts\n * const userInput = '<script>alert(\"xss\")</script>';\n * const safe = safeHtml`<div>${userInput}</div>`;\n * // Result: '<div>&lt;script&gt;alert(\"xss\")&lt;/script&gt;</div>'\n * ```\n */\nexport const safeHtml = (strings: TemplateStringsArray, ...values: unknown[]): string => {\n const escapeMap: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#x27;',\n '`': '&#x60;',\n };\n\n const escape = (value: unknown): string => {\n const str = String(value ?? '');\n return str.replace(/[&<>\"'`]/g, (char) => escapeMap[char]);\n };\n\n return strings.reduce((acc, part, index) => `${acc}${part}${escape(values[index])}`, '');\n};\n\n/**\n * Defines and registers a custom Web Component.\n *\n * This function creates a new custom element with the given tag name\n * and configuration. The component uses Shadow DOM for encapsulation\n * and automatically re-renders when observed attributes change.\n *\n * @template TProps - Type of the component's props\n * @param tagName - The custom element tag name (must contain a hyphen)\n * @param definition - The component configuration\n *\n * @example\n * ```ts\n * component('counter-button', {\n * props: {\n * start: { type: Number, default: 0 },\n * },\n * state: { count: 0 },\n * styles: `\n * button { padding: 0.5rem 1rem; }\n * `,\n * connected() {\n * console.log('Counter mounted');\n * },\n * render({ props, state, emit }) {\n * return html`\n * <button onclick=\"this.getRootNode().host.increment()\">\n * Count: ${state.count}\n * </button>\n * `;\n * },\n * });\n * ```\n */\nexport const component = <TProps extends Record<string, unknown>>(\n tagName: string,\n definition: ComponentDefinition<TProps>\n): void => {\n /**\n * Internal Web Component class created for each component definition.\n * @internal\n */\n class BQueryComponent extends HTMLElement {\n /** Internal state object for the component */\n private readonly state = { ...(definition.state ?? {}) };\n /** Typed props object populated from attributes */\n private props = {} as TProps;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n this.syncProps();\n }\n\n /**\n * Returns the list of attributes to observe for changes.\n */\n static get observedAttributes(): string[] {\n return Object.keys(definition.props ?? {});\n }\n\n /**\n * Called when the element is added to the DOM.\n */\n connectedCallback(): void {\n definition.connected?.call(this);\n this.render();\n }\n\n /**\n * Called when the element is removed from the DOM.\n */\n disconnectedCallback(): void {\n definition.disconnected?.call(this);\n }\n\n /**\n * Called when an observed attribute changes.\n */\n attributeChangedCallback(): void {\n this.syncProps();\n this.render(true);\n }\n\n /**\n * Updates a state property and triggers a re-render.\n *\n * @param key - The state property key\n * @param value - The new value\n */\n setState(key: string, value: unknown): void {\n this.state[key] = value;\n this.render(true);\n }\n\n /**\n * Gets a state property value.\n *\n * @param key - The state property key\n * @returns The current value\n */\n getState<T = unknown>(key: string): T {\n return this.state[key] as T;\n }\n\n /**\n * Synchronizes props from attributes.\n * @internal\n */\n private syncProps(): void {\n const props = definition.props ?? {};\n for (const [key, config] of Object.entries(props) as [string, PropDefinition][]) {\n const attrValue = this.getAttribute(key);\n if (attrValue == null) {\n if (config.required && config.default === undefined) {\n throw new Error(`bQuery component: missing required prop \"${key}\"`);\n }\n (this.props as Record<string, unknown>)[key] = config.default ?? undefined;\n continue;\n }\n (this.props as Record<string, unknown>)[key] = coercePropValue(\n attrValue,\n config\n ) as TProps[keyof TProps];\n }\n }\n\n /**\n * Renders the component to its shadow root.\n * @internal\n */\n private render(triggerUpdated = false): void {\n /**\n * Emits a custom event from the component.\n */\n const emit = (event: string, detail?: unknown): void => {\n this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, composed: true }));\n };\n\n if (!this.shadowRoot) return;\n\n const markup = definition.render({\n props: this.props,\n state: this.state,\n emit,\n });\n\n const styles = definition.styles ? `<style>${definition.styles}</style>` : '';\n this.shadowRoot.innerHTML = `${styles}${markup}`;\n\n if (triggerUpdated) {\n definition.updated?.call(this);\n }\n }\n }\n\n if (!customElements.get(tagName)) {\n customElements.define(tagName, BQueryComponent);\n }\n};\n"],"names":["coercePropValue","rawValue","config","type","parsed","normalized","callable","constructable","html","strings","values","acc","part","index","safeHtml","escapeMap","escape","value","char","component","tagName","definition","BQueryComponent","key","props","attrValue","triggerUpdated","emit","event","detail","markup","styles"],"mappings":"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"}