@bquery/bquery 1.4.0 → 1.5.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 (127) hide show
  1. package/README.md +139 -120
  2. package/dist/component/component.d.ts.map +1 -1
  3. package/dist/component/index.d.ts +2 -0
  4. package/dist/component/index.d.ts.map +1 -1
  5. package/dist/component/library.d.ts +34 -0
  6. package/dist/component/library.d.ts.map +1 -0
  7. package/dist/component/types.d.ts +10 -6
  8. package/dist/component/types.d.ts.map +1 -1
  9. package/dist/component-CY5MVoYN.js +531 -0
  10. package/dist/component-CY5MVoYN.js.map +1 -0
  11. package/dist/component.es.mjs +6 -184
  12. package/dist/config-DRmZZno3.js +40 -0
  13. package/dist/config-DRmZZno3.js.map +1 -0
  14. package/dist/core-CK2Mfpf4.js +648 -0
  15. package/dist/core-CK2Mfpf4.js.map +1 -0
  16. package/dist/core-DPdbItcq.js +112 -0
  17. package/dist/core-DPdbItcq.js.map +1 -0
  18. package/dist/core.es.mjs +45 -1261
  19. package/dist/full.d.ts +6 -6
  20. package/dist/full.d.ts.map +1 -1
  21. package/dist/full.es.mjs +98 -92
  22. package/dist/full.iife.js +173 -3
  23. package/dist/full.iife.js.map +1 -1
  24. package/dist/full.umd.js +173 -3
  25. package/dist/full.umd.js.map +1 -1
  26. package/dist/index.es.mjs +143 -139
  27. package/dist/motion/transition.d.ts +1 -1
  28. package/dist/motion/transition.d.ts.map +1 -1
  29. package/dist/motion/types.d.ts +11 -1
  30. package/dist/motion/types.d.ts.map +1 -1
  31. package/dist/motion-C5DRdPnO.js +415 -0
  32. package/dist/motion-C5DRdPnO.js.map +1 -0
  33. package/dist/motion.es.mjs +25 -361
  34. package/dist/object-qGpWr6-J.js +38 -0
  35. package/dist/object-qGpWr6-J.js.map +1 -0
  36. package/dist/platform/announcer.d.ts +59 -0
  37. package/dist/platform/announcer.d.ts.map +1 -0
  38. package/dist/platform/config.d.ts +92 -0
  39. package/dist/platform/config.d.ts.map +1 -0
  40. package/dist/platform/cookies.d.ts +45 -0
  41. package/dist/platform/cookies.d.ts.map +1 -0
  42. package/dist/platform/index.d.ts +8 -0
  43. package/dist/platform/index.d.ts.map +1 -1
  44. package/dist/platform/meta.d.ts +62 -0
  45. package/dist/platform/meta.d.ts.map +1 -0
  46. package/dist/platform-B7JhGBc7.js +361 -0
  47. package/dist/platform-B7JhGBc7.js.map +1 -0
  48. package/dist/platform.es.mjs +11 -248
  49. package/dist/reactive/async-data.d.ts +114 -0
  50. package/dist/reactive/async-data.d.ts.map +1 -0
  51. package/dist/reactive/index.d.ts +2 -2
  52. package/dist/reactive/index.d.ts.map +1 -1
  53. package/dist/reactive/signal.d.ts +2 -0
  54. package/dist/reactive/signal.d.ts.map +1 -1
  55. package/dist/reactive-BDya-ia8.js +253 -0
  56. package/dist/reactive-BDya-ia8.js.map +1 -0
  57. package/dist/reactive.es.mjs +18 -34
  58. package/dist/router-CijiICxt.js +188 -0
  59. package/dist/router-CijiICxt.js.map +1 -0
  60. package/dist/router.es.mjs +11 -200
  61. package/dist/sanitize-jyJ2ryE2.js +302 -0
  62. package/dist/sanitize-jyJ2ryE2.js.map +1 -0
  63. package/dist/security/constants.d.ts.map +1 -1
  64. package/dist/security.es.mjs +10 -56
  65. package/dist/store-CPK9E62U.js +262 -0
  66. package/dist/store-CPK9E62U.js.map +1 -0
  67. package/dist/store.es.mjs +12 -25
  68. package/dist/view-Cdi0g-qo.js +396 -0
  69. package/dist/view-Cdi0g-qo.js.map +1 -0
  70. package/dist/view.es.mjs +10 -430
  71. package/package.json +15 -11
  72. package/src/component/component.ts +319 -289
  73. package/src/component/index.ts +42 -40
  74. package/src/component/library.ts +504 -0
  75. package/src/component/types.ts +91 -85
  76. package/src/core/collection.ts +628 -628
  77. package/src/core/element.ts +774 -774
  78. package/src/core/index.ts +48 -48
  79. package/src/core/utils/function.ts +151 -151
  80. package/src/full.ts +223 -187
  81. package/src/motion/animate.ts +113 -113
  82. package/src/motion/flip.ts +176 -176
  83. package/src/motion/scroll.ts +57 -57
  84. package/src/motion/spring.ts +150 -150
  85. package/src/motion/timeline.ts +246 -246
  86. package/src/motion/transition.ts +53 -7
  87. package/src/motion/types.ts +208 -198
  88. package/src/platform/announcer.ts +208 -0
  89. package/src/platform/config.ts +163 -0
  90. package/src/platform/cookies.ts +165 -0
  91. package/src/platform/index.ts +39 -18
  92. package/src/platform/meta.ts +168 -0
  93. package/src/platform/storage.ts +215 -215
  94. package/src/reactive/async-data.ts +486 -0
  95. package/src/reactive/core.ts +114 -114
  96. package/src/reactive/effect.ts +54 -54
  97. package/src/reactive/index.ts +37 -23
  98. package/src/reactive/internals.ts +122 -122
  99. package/src/reactive/signal.ts +29 -20
  100. package/src/security/constants.ts +211 -209
  101. package/src/security/sanitize-core.ts +364 -364
  102. package/src/view/evaluate.ts +290 -290
  103. package/dist/batch-x7b2eZST.js +0 -13
  104. package/dist/batch-x7b2eZST.js.map +0 -1
  105. package/dist/component.es.mjs.map +0 -1
  106. package/dist/core-BhpuvPhy.js +0 -170
  107. package/dist/core-BhpuvPhy.js.map +0 -1
  108. package/dist/core.es.mjs.map +0 -1
  109. package/dist/full.es.mjs.map +0 -1
  110. package/dist/index.es.mjs.map +0 -1
  111. package/dist/motion.es.mjs.map +0 -1
  112. package/dist/persisted-DHoi3uEs.js +0 -278
  113. package/dist/persisted-DHoi3uEs.js.map +0 -1
  114. package/dist/platform.es.mjs.map +0 -1
  115. package/dist/reactive.es.mjs.map +0 -1
  116. package/dist/router.es.mjs.map +0 -1
  117. package/dist/sanitize-Cxvxa-DX.js +0 -283
  118. package/dist/sanitize-Cxvxa-DX.js.map +0 -1
  119. package/dist/security.es.mjs.map +0 -1
  120. package/dist/store.es.mjs.map +0 -1
  121. package/dist/type-guards-BdKlYYlS.js +0 -32
  122. package/dist/type-guards-BdKlYYlS.js.map +0 -1
  123. package/dist/untrack-DNnnqdlR.js +0 -6
  124. package/dist/untrack-DNnnqdlR.js.map +0 -1
  125. package/dist/view.es.mjs.map +0 -1
  126. package/dist/watch-DXXv3iAI.js +0 -58
  127. package/dist/watch-DXXv3iAI.js.map +0 -1
@@ -1,40 +1,42 @@
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
- export { component, defineComponent } from './component';
39
- export { html, safeHtml } from './html';
40
- export type { ComponentDefinition, ComponentRenderContext, PropDefinition } from './types';
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
+ export { component, defineComponent } from './component';
39
+ export { html, safeHtml } from './html';
40
+ export { registerDefaultComponents } from './library';
41
+ export type { DefaultComponentLibraryOptions, RegisteredDefaultComponents } from './library';
42
+ export type { ComponentDefinition, ComponentRenderContext, PropDefinition } from './types';
@@ -0,0 +1,504 @@
1
+ /**
2
+ * Default component library based on native Web Components.
3
+ *
4
+ * @module bquery/component
5
+ */
6
+
7
+ import { getBqueryConfig } from '../platform/config';
8
+ import { escapeHtml } from '../security';
9
+ import { component } from './component';
10
+ import { html } from './html';
11
+
12
+ /** Options for registering the default component library. */
13
+ export interface DefaultComponentLibraryOptions {
14
+ /** Prefix used for all registered component tags. Defaults to `bq`. */
15
+ prefix?: string;
16
+ }
17
+
18
+ /** Tag names returned by registerDefaultComponents(). */
19
+ export interface RegisteredDefaultComponents {
20
+ /** Button component tag name. */
21
+ button: string;
22
+ /** Card component tag name. */
23
+ card: string;
24
+ /** Input component tag name. */
25
+ input: string;
26
+ /** Textarea component tag name. */
27
+ textarea: string;
28
+ /** Checkbox component tag name. */
29
+ checkbox: string;
30
+ }
31
+
32
+ const baseStyles = `
33
+ :host {
34
+ color: inherit;
35
+ font: inherit;
36
+ }
37
+ `;
38
+
39
+ const controlStyles = `
40
+ ${baseStyles}
41
+ .field {
42
+ display: inline-flex;
43
+ flex-direction: column;
44
+ gap: 0.375rem;
45
+ width: 100%;
46
+ }
47
+ .label {
48
+ color: #334155;
49
+ font-size: 0.875rem;
50
+ font-weight: 600;
51
+ }
52
+ .control {
53
+ border: 1px solid #cbd5e1;
54
+ border-radius: 0.75rem;
55
+ box-sizing: border-box;
56
+ font: inherit;
57
+ min-height: 2.75rem;
58
+ outline: none;
59
+ padding: 0.75rem 0.875rem;
60
+ width: 100%;
61
+ background: #fff;
62
+ color: #0f172a;
63
+ transition: border-color 160ms ease, box-shadow 160ms ease;
64
+ }
65
+ .control:focus {
66
+ border-color: #2563eb;
67
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
68
+ }
69
+ .control:disabled {
70
+ background: #f8fafc;
71
+ color: #94a3b8;
72
+ cursor: not-allowed;
73
+ }
74
+ `;
75
+
76
+ const escapeProp = (value: string): string => escapeHtml(value);
77
+
78
+ const handlerStore = new WeakMap<HTMLElement, Record<string, EventListener>>();
79
+
80
+ const readHandler = (element: HTMLElement, key: string): EventListener | undefined => {
81
+ return handlerStore.get(element)?.[key];
82
+ };
83
+
84
+ const storeHandler = (element: HTMLElement, key: string, value: EventListener): void => {
85
+ const handlers = handlerStore.get(element) ?? {};
86
+ handlers[key] = value;
87
+ handlerStore.set(element, handlers);
88
+ };
89
+
90
+ const getShadowLabelText = (element: HTMLElement): string => {
91
+ return element.shadowRoot?.querySelector('.label')?.textContent ?? '';
92
+ };
93
+
94
+ /**
95
+ * Detect a value-only input update, patch the live control in place, and
96
+ * return whether the component can skip a full shadow DOM re-render.
97
+ *
98
+ * @param element - The host custom element whose shadow DOM is being updated
99
+ * @param props - The next reflected input props for the pending update
100
+ */
101
+ const canSkipInputRender = (
102
+ element: HTMLElement,
103
+ props: {
104
+ label: string;
105
+ type: string;
106
+ value: string;
107
+ placeholder: string;
108
+ name: string;
109
+ disabled: boolean;
110
+ }
111
+ ): boolean => {
112
+ const control = element.shadowRoot?.querySelector('input.control') as HTMLInputElement | null;
113
+ if (!control) return false;
114
+
115
+ if (getShadowLabelText(element) !== props.label) return false;
116
+ if ((control.getAttribute('type') ?? 'text') !== props.type) return false;
117
+ if ((control.getAttribute('placeholder') ?? '') !== props.placeholder) return false;
118
+ if ((control.getAttribute('name') ?? '') !== props.name) return false;
119
+ if (control.disabled !== props.disabled) return false;
120
+
121
+ if (control.value !== props.value) {
122
+ control.value = props.value;
123
+ }
124
+
125
+ return true;
126
+ };
127
+
128
+ /**
129
+ * Detect a value-only textarea update, patch the live control in place, and
130
+ * return whether the component can skip a full shadow DOM re-render.
131
+ *
132
+ * @param element - The host custom element whose shadow DOM is being updated
133
+ * @param props - The next reflected textarea props for the pending update
134
+ */
135
+ const canSkipTextareaRender = (
136
+ element: HTMLElement,
137
+ props: {
138
+ label: string;
139
+ value: string;
140
+ placeholder: string;
141
+ name: string;
142
+ rows: number;
143
+ disabled: boolean;
144
+ }
145
+ ): boolean => {
146
+ const control = element.shadowRoot?.querySelector(
147
+ 'textarea.control'
148
+ ) as HTMLTextAreaElement | null;
149
+ if (!control) return false;
150
+
151
+ if (getShadowLabelText(element) !== props.label) return false;
152
+ if ((control.getAttribute('placeholder') ?? '') !== props.placeholder) return false;
153
+ if ((control.getAttribute('name') ?? '') !== props.name) return false;
154
+ if (control.getAttribute('rows') !== String(props.rows)) return false;
155
+ if (control.disabled !== props.disabled) return false;
156
+
157
+ if (control.value !== props.value) {
158
+ control.value = props.value;
159
+ }
160
+ return true;
161
+ };
162
+
163
+ const renderTextareaControl = (props: {
164
+ value: string;
165
+ placeholder: string;
166
+ name: string;
167
+ rows: number;
168
+ disabled: boolean;
169
+ }): string => {
170
+ return [
171
+ '<textarea',
172
+ ' part="control"',
173
+ ' class="control"',
174
+ ` placeholder="${escapeProp(props.placeholder)}"`,
175
+ ` name="${escapeProp(props.name)}"`,
176
+ ` rows="${props.rows}"`,
177
+ props.disabled ? ' disabled' : '',
178
+ `>${escapeProp(props.value)}</textarea>`,
179
+ ].join('');
180
+ };
181
+
182
+ /**
183
+ * Register a default set of foundational UI components.
184
+ *
185
+ * The library is intentionally small and dependency-free, providing common
186
+ * primitives that can be themed via shadow parts and CSS custom properties.
187
+ *
188
+ * @param options - Optional registration settings such as a custom tag prefix
189
+ * @returns The registered tag names for each component
190
+ */
191
+ export const registerDefaultComponents = (
192
+ options: DefaultComponentLibraryOptions = {}
193
+ ): RegisteredDefaultComponents => {
194
+ const prefix = options.prefix ?? getBqueryConfig().components?.prefix ?? 'bq';
195
+ const tags: RegisteredDefaultComponents = {
196
+ button: `${prefix}-button`,
197
+ card: `${prefix}-card`,
198
+ input: `${prefix}-input`,
199
+ textarea: `${prefix}-textarea`,
200
+ checkbox: `${prefix}-checkbox`,
201
+ };
202
+
203
+ component<{
204
+ label: string;
205
+ variant: string;
206
+ size: string;
207
+ type: string;
208
+ disabled: boolean;
209
+ }>(tags.button, {
210
+ props: {
211
+ label: { type: String, default: '' },
212
+ variant: { type: String, default: 'primary' },
213
+ size: { type: String, default: 'md' },
214
+ type: { type: String, default: 'button' },
215
+ disabled: { type: Boolean, default: false },
216
+ },
217
+ styles: `
218
+ ${baseStyles}
219
+ button {
220
+ appearance: none;
221
+ border: 0;
222
+ border-radius: 999px;
223
+ cursor: pointer;
224
+ display: inline-flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ font: inherit;
228
+ font-weight: 600;
229
+ gap: 0.5rem;
230
+ min-height: 2.5rem;
231
+ padding: 0.65rem 1rem;
232
+ transition: transform 160ms ease, opacity 160ms ease, background 160ms ease;
233
+ background: #2563eb;
234
+ color: #fff;
235
+ }
236
+ button[data-variant='secondary'] {
237
+ background: #e2e8f0;
238
+ color: #0f172a;
239
+ }
240
+ button[data-size='sm'] {
241
+ min-height: 2.125rem;
242
+ padding: 0.5rem 0.875rem;
243
+ }
244
+ button[data-size='lg'] {
245
+ min-height: 3rem;
246
+ padding: 0.875rem 1.25rem;
247
+ }
248
+ button:hover:not(:disabled) {
249
+ transform: translateY(-1px);
250
+ }
251
+ button:disabled {
252
+ cursor: not-allowed;
253
+ opacity: 0.6;
254
+ }
255
+ `,
256
+ render: ({ props }) => html`
257
+ <button
258
+ part="button"
259
+ type="${escapeProp(props.type)}"
260
+ data-variant="${escapeProp(props.variant)}"
261
+ data-size="${escapeProp(props.size)}"
262
+ ${props.disabled ? 'disabled' : ''}
263
+ >
264
+ <slot>${escapeProp(props.label)}</slot>
265
+ </button>
266
+ `,
267
+ });
268
+
269
+ component<{ title: string; footer: string; elevated: boolean }>(tags.card, {
270
+ props: {
271
+ title: { type: String, default: '' },
272
+ footer: { type: String, default: '' },
273
+ elevated: { type: Boolean, default: true },
274
+ },
275
+ styles: `
276
+ ${baseStyles}
277
+ article {
278
+ background: #fff;
279
+ border: 1px solid #e2e8f0;
280
+ border-radius: 1rem;
281
+ box-shadow: 0 10px 25px rgba(15, 23, 42, 0.08);
282
+ color: #0f172a;
283
+ display: block;
284
+ padding: 1rem;
285
+ }
286
+ article[data-elevated='false'] {
287
+ box-shadow: none;
288
+ }
289
+ header, footer {
290
+ color: #475569;
291
+ font-size: 0.95rem;
292
+ font-weight: 600;
293
+ }
294
+ header {
295
+ margin-bottom: 0.75rem;
296
+ }
297
+ footer {
298
+ margin-top: 0.75rem;
299
+ }
300
+ `,
301
+ render: ({ props }) => html`
302
+ <article part="card" data-elevated="${String(props.elevated)}">
303
+ ${props.title ? `<header part="header">${escapeProp(props.title)}</header>` : ''}
304
+ <section part="body"><slot></slot></section>
305
+ ${props.footer ? `<footer part="footer">${escapeProp(props.footer)}</footer>` : ''}
306
+ </article>
307
+ `,
308
+ });
309
+
310
+ component<{
311
+ label: string;
312
+ type: string;
313
+ value: string;
314
+ placeholder: string;
315
+ name: string;
316
+ disabled: boolean;
317
+ }>(tags.input, {
318
+ props: {
319
+ label: { type: String, default: '' },
320
+ type: { type: String, default: 'text' },
321
+ value: { type: String, default: '' },
322
+ placeholder: { type: String, default: '' },
323
+ name: { type: String, default: '' },
324
+ disabled: { type: Boolean, default: false },
325
+ },
326
+ styles: controlStyles,
327
+ /**
328
+ * Skip the full shadow DOM re-render when only the reflected input value
329
+ * changed, because the live control has already been patched in place.
330
+ */
331
+ beforeUpdate(props) {
332
+ if (canSkipInputRender(this, props)) {
333
+ return false;
334
+ }
335
+ return true;
336
+ },
337
+ connected() {
338
+ const handleInput = (event: Event) => {
339
+ const target = event.target as HTMLInputElement | null;
340
+ if (!target?.matches('input')) return;
341
+ event.stopPropagation();
342
+ this.setAttribute('value', target.value);
343
+ this.dispatchEvent(
344
+ new CustomEvent('input', {
345
+ detail: { value: target.value },
346
+ bubbles: true,
347
+ composed: true,
348
+ })
349
+ );
350
+ };
351
+ storeHandler(this, '__bqueryInputHandler', handleInput);
352
+ this.shadowRoot?.addEventListener('input', handleInput);
353
+ },
354
+ disconnected() {
355
+ const handleInput = readHandler(this, '__bqueryInputHandler');
356
+ if (handleInput) {
357
+ this.shadowRoot?.removeEventListener('input', handleInput);
358
+ }
359
+ },
360
+ render: ({ props }) => html`
361
+ <label part="field" class="field">
362
+ ${props.label ? `<span part="label" class="label">${escapeProp(props.label)}</span>` : ''}
363
+ <input
364
+ part="control"
365
+ class="control"
366
+ type="${escapeProp(props.type)}"
367
+ value="${escapeProp(props.value)}"
368
+ placeholder="${escapeProp(props.placeholder)}"
369
+ name="${escapeProp(props.name)}"
370
+ ${props.disabled ? 'disabled' : ''}
371
+ />
372
+ </label>
373
+ `,
374
+ });
375
+
376
+ component<{
377
+ label: string;
378
+ value: string;
379
+ placeholder: string;
380
+ name: string;
381
+ rows: number;
382
+ disabled: boolean;
383
+ }>(tags.textarea, {
384
+ props: {
385
+ label: { type: String, default: '' },
386
+ value: { type: String, default: '' },
387
+ placeholder: { type: String, default: '' },
388
+ name: { type: String, default: '' },
389
+ rows: { type: Number, default: 4 },
390
+ disabled: { type: Boolean, default: false },
391
+ },
392
+ styles: `${controlStyles}
393
+ textarea.control {
394
+ min-height: 6rem;
395
+ resize: vertical;
396
+ }
397
+ `,
398
+ /**
399
+ * Skip the full shadow DOM re-render when only the reflected textarea value
400
+ * changed, because the live control has already been patched in place.
401
+ */
402
+ beforeUpdate(props) {
403
+ if (canSkipTextareaRender(this, props)) {
404
+ return false;
405
+ }
406
+ return true;
407
+ },
408
+ connected() {
409
+ const handleInput = (event: Event) => {
410
+ const target = event.target as HTMLTextAreaElement | null;
411
+ if (!target?.matches('textarea')) return;
412
+ event.stopPropagation();
413
+ this.setAttribute('value', target.value);
414
+ this.dispatchEvent(
415
+ new CustomEvent('input', {
416
+ detail: { value: target.value },
417
+ bubbles: true,
418
+ composed: true,
419
+ })
420
+ );
421
+ };
422
+ storeHandler(this, '__bqueryTextareaHandler', handleInput);
423
+ this.shadowRoot?.addEventListener('input', handleInput);
424
+ },
425
+ disconnected() {
426
+ const handleInput = readHandler(this, '__bqueryTextareaHandler');
427
+ if (handleInput) {
428
+ this.shadowRoot?.removeEventListener('input', handleInput);
429
+ }
430
+ },
431
+ render: ({ props }) => html`
432
+ <label part="field" class="field">
433
+ ${props.label ? `<span part="label" class="label">${escapeProp(props.label)}</span>` : ''}
434
+ ${renderTextareaControl(props)}
435
+ </label>
436
+ `,
437
+ });
438
+
439
+ component<{ label: string; checked: boolean; disabled: boolean }>(tags.checkbox, {
440
+ props: {
441
+ label: { type: String, default: '' },
442
+ checked: { type: Boolean, default: false },
443
+ disabled: { type: Boolean, default: false },
444
+ },
445
+ styles: `
446
+ ${baseStyles}
447
+ label {
448
+ align-items: center;
449
+ color: #0f172a;
450
+ cursor: pointer;
451
+ display: inline-flex;
452
+ gap: 0.625rem;
453
+ }
454
+ input {
455
+ accent-color: #2563eb;
456
+ block-size: 1rem;
457
+ inline-size: 1rem;
458
+ }
459
+ input:disabled {
460
+ cursor: not-allowed;
461
+ }
462
+ `,
463
+ connected() {
464
+ const handleChange = (event: Event) => {
465
+ const target = event.target as HTMLInputElement | null;
466
+ if (!target?.matches('input[type="checkbox"]')) return;
467
+ event.stopPropagation();
468
+ if (target.checked) {
469
+ this.setAttribute('checked', 'true');
470
+ } else {
471
+ this.removeAttribute('checked');
472
+ }
473
+ this.dispatchEvent(
474
+ new CustomEvent('change', {
475
+ detail: { checked: target.checked },
476
+ bubbles: true,
477
+ composed: true,
478
+ })
479
+ );
480
+ };
481
+ storeHandler(this, '__bqueryCheckboxHandler', handleChange);
482
+ this.shadowRoot?.addEventListener('change', handleChange);
483
+ },
484
+ disconnected() {
485
+ const handleChange = readHandler(this, '__bqueryCheckboxHandler');
486
+ if (handleChange) {
487
+ this.shadowRoot?.removeEventListener('change', handleChange);
488
+ }
489
+ },
490
+ render: ({ props }) => html`
491
+ <label part="label">
492
+ <input
493
+ part="control"
494
+ type="checkbox"
495
+ ${props.checked ? 'checked' : ''}
496
+ ${props.disabled ? 'disabled' : ''}
497
+ />
498
+ <span part="text"><slot>${escapeProp(props.label)}</slot></span>
499
+ </label>
500
+ `,
501
+ });
502
+
503
+ return tags;
504
+ };