@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
@@ -0,0 +1,360 @@
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
+ /**
39
+ * Defines a single prop's type and configuration.
40
+ *
41
+ * @template T - The TypeScript type of the prop value
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const myProp: PropDefinition<number> = {
46
+ * type: Number,
47
+ * required: false,
48
+ * default: 0,
49
+ * };
50
+ * ```
51
+ */
52
+ export type PropDefinition<T = unknown> = {
53
+ /** Constructor or converter function for the prop type */
54
+ type:
55
+ | StringConstructor
56
+ | NumberConstructor
57
+ | BooleanConstructor
58
+ | ObjectConstructor
59
+ | ArrayConstructor
60
+ | { new (value: unknown): T }
61
+ | ((value: unknown) => T);
62
+ /** Whether the prop must be provided */
63
+ required?: boolean;
64
+ /** Default value when prop is not provided */
65
+ default?: T;
66
+ };
67
+
68
+ /**
69
+ * Complete component definition including props, state, styles, and lifecycle.
70
+ *
71
+ * @template TProps - Type of the component's props
72
+ */
73
+ export type ComponentDefinition<TProps extends Record<string, unknown> = Record<string, unknown>> =
74
+ {
75
+ /** Prop definitions with types and defaults */
76
+ props?: Record<keyof TProps, PropDefinition>;
77
+ /** Initial internal state */
78
+ state?: Record<string, unknown>;
79
+ /** CSS styles scoped to the component's shadow DOM */
80
+ styles?: string;
81
+ /** Lifecycle hook called when component is added to DOM */
82
+ connected?: () => void;
83
+ /** Lifecycle hook called when component is removed from DOM */
84
+ disconnected?: () => void;
85
+ /** Lifecycle hook called after reactive updates trigger a render */
86
+ updated?: () => void;
87
+ /** Render function returning HTML string */
88
+ render: (context: {
89
+ props: TProps;
90
+ state: Record<string, unknown>;
91
+ emit: (event: string, detail?: unknown) => void;
92
+ }) => string;
93
+ };
94
+
95
+ /**
96
+ * Coerces a string attribute value into a typed prop value.
97
+ * Supports String, Number, Boolean, Object, Array, and custom converters.
98
+ *
99
+ * @internal
100
+ * @template T - The target type
101
+ * @param rawValue - The raw string value from the attribute
102
+ * @param config - The prop definition with type information
103
+ * @returns The coerced value of type T
104
+ */
105
+ const coercePropValue = <T>(rawValue: string, config: PropDefinition<T>): T => {
106
+ const { type } = config;
107
+
108
+ if (type === String) return rawValue as T;
109
+
110
+ if (type === Number) {
111
+ const parsed = Number(rawValue);
112
+ return (Number.isNaN(parsed) ? rawValue : parsed) as T;
113
+ }
114
+
115
+ if (type === Boolean) {
116
+ const normalized = rawValue.trim().toLowerCase();
117
+ if (normalized === '' || normalized === 'true' || normalized === '1') {
118
+ return true as T;
119
+ }
120
+ if (normalized === 'false' || normalized === '0') {
121
+ return false as T;
122
+ }
123
+ return Boolean(rawValue) as T;
124
+ }
125
+
126
+ if (type === Object || type === Array) {
127
+ try {
128
+ return JSON.parse(rawValue) as T;
129
+ } catch {
130
+ return rawValue as T;
131
+ }
132
+ }
133
+
134
+ if (typeof type === 'function') {
135
+ const callable = type as (value: unknown) => T;
136
+ const constructable = type as new (value: unknown) => T;
137
+ try {
138
+ return callable(rawValue);
139
+ } catch {
140
+ return new constructable(rawValue);
141
+ }
142
+ }
143
+
144
+ return rawValue as T;
145
+ };
146
+
147
+ /**
148
+ * Tagged template literal for creating HTML strings.
149
+ *
150
+ * This function handles interpolation of values into HTML templates,
151
+ * converting null/undefined to empty strings.
152
+ *
153
+ * @param strings - Template literal string parts
154
+ * @param values - Interpolated values
155
+ * @returns Combined HTML string
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * const name = 'World';
160
+ * const greeting = html`<h1>Hello, ${name}!</h1>`;
161
+ * // Result: '<h1>Hello, World!</h1>'
162
+ * ```
163
+ */
164
+ export const html = (strings: TemplateStringsArray, ...values: unknown[]): string => {
165
+ return strings.reduce((acc, part, index) => `${acc}${part}${values[index] ?? ''}`, '');
166
+ };
167
+
168
+ /**
169
+ * Escapes HTML entities in interpolated values for XSS prevention.
170
+ * Use this when you need to safely embed user content in templates.
171
+ *
172
+ * @param strings - Template literal string parts
173
+ * @param values - Interpolated values to escape
174
+ * @returns Combined HTML string with escaped values
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * const userInput = '<script>alert("xss")</script>';
179
+ * const safe = safeHtml`<div>${userInput}</div>`;
180
+ * // Result: '<div>&lt;script&gt;alert("xss")&lt;/script&gt;</div>'
181
+ * ```
182
+ */
183
+ export const safeHtml = (strings: TemplateStringsArray, ...values: unknown[]): string => {
184
+ const escapeMap: Record<string, string> = {
185
+ '&': '&amp;',
186
+ '<': '&lt;',
187
+ '>': '&gt;',
188
+ '"': '&quot;',
189
+ "'": '&#x27;',
190
+ '`': '&#x60;',
191
+ };
192
+
193
+ const escape = (value: unknown): string => {
194
+ const str = String(value ?? '');
195
+ return str.replace(/[&<>"'`]/g, (char) => escapeMap[char]);
196
+ };
197
+
198
+ return strings.reduce((acc, part, index) => `${acc}${part}${escape(values[index])}`, '');
199
+ };
200
+
201
+ /**
202
+ * Defines and registers a custom Web Component.
203
+ *
204
+ * This function creates a new custom element with the given tag name
205
+ * and configuration. The component uses Shadow DOM for encapsulation
206
+ * and automatically re-renders when observed attributes change.
207
+ *
208
+ * @template TProps - Type of the component's props
209
+ * @param tagName - The custom element tag name (must contain a hyphen)
210
+ * @param definition - The component configuration
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * component('counter-button', {
215
+ * props: {
216
+ * start: { type: Number, default: 0 },
217
+ * },
218
+ * state: { count: 0 },
219
+ * styles: `
220
+ * button { padding: 0.5rem 1rem; }
221
+ * `,
222
+ * connected() {
223
+ * console.log('Counter mounted');
224
+ * },
225
+ * render({ props, state, emit }) {
226
+ * return html`
227
+ * <button onclick="this.getRootNode().host.increment()">
228
+ * Count: ${state.count}
229
+ * </button>
230
+ * `;
231
+ * },
232
+ * });
233
+ * ```
234
+ */
235
+ export const component = <TProps extends Record<string, unknown>>(
236
+ tagName: string,
237
+ definition: ComponentDefinition<TProps>
238
+ ): void => {
239
+ /**
240
+ * Internal Web Component class created for each component definition.
241
+ * @internal
242
+ */
243
+ class BQueryComponent extends HTMLElement {
244
+ /** Internal state object for the component */
245
+ private readonly state = { ...(definition.state ?? {}) };
246
+ /** Typed props object populated from attributes */
247
+ private props = {} as TProps;
248
+
249
+ constructor() {
250
+ super();
251
+ this.attachShadow({ mode: 'open' });
252
+ this.syncProps();
253
+ }
254
+
255
+ /**
256
+ * Returns the list of attributes to observe for changes.
257
+ */
258
+ static get observedAttributes(): string[] {
259
+ return Object.keys(definition.props ?? {});
260
+ }
261
+
262
+ /**
263
+ * Called when the element is added to the DOM.
264
+ */
265
+ connectedCallback(): void {
266
+ definition.connected?.call(this);
267
+ this.render();
268
+ }
269
+
270
+ /**
271
+ * Called when the element is removed from the DOM.
272
+ */
273
+ disconnectedCallback(): void {
274
+ definition.disconnected?.call(this);
275
+ }
276
+
277
+ /**
278
+ * Called when an observed attribute changes.
279
+ */
280
+ attributeChangedCallback(): void {
281
+ this.syncProps();
282
+ this.render(true);
283
+ }
284
+
285
+ /**
286
+ * Updates a state property and triggers a re-render.
287
+ *
288
+ * @param key - The state property key
289
+ * @param value - The new value
290
+ */
291
+ setState(key: string, value: unknown): void {
292
+ this.state[key] = value;
293
+ this.render(true);
294
+ }
295
+
296
+ /**
297
+ * Gets a state property value.
298
+ *
299
+ * @param key - The state property key
300
+ * @returns The current value
301
+ */
302
+ getState<T = unknown>(key: string): T {
303
+ return this.state[key] as T;
304
+ }
305
+
306
+ /**
307
+ * Synchronizes props from attributes.
308
+ * @internal
309
+ */
310
+ private syncProps(): void {
311
+ const props = definition.props ?? {};
312
+ for (const [key, config] of Object.entries(props) as [string, PropDefinition][]) {
313
+ const attrValue = this.getAttribute(key);
314
+ if (attrValue == null) {
315
+ if (config.required && config.default === undefined) {
316
+ throw new Error(`bQuery component: missing required prop "${key}"`);
317
+ }
318
+ (this.props as Record<string, unknown>)[key] = config.default ?? undefined;
319
+ continue;
320
+ }
321
+ (this.props as Record<string, unknown>)[key] = coercePropValue(
322
+ attrValue,
323
+ config
324
+ ) as TProps[keyof TProps];
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Renders the component to its shadow root.
330
+ * @internal
331
+ */
332
+ private render(triggerUpdated = false): void {
333
+ /**
334
+ * Emits a custom event from the component.
335
+ */
336
+ const emit = (event: string, detail?: unknown): void => {
337
+ this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, composed: true }));
338
+ };
339
+
340
+ if (!this.shadowRoot) return;
341
+
342
+ const markup = definition.render({
343
+ props: this.props,
344
+ state: this.state,
345
+ emit,
346
+ });
347
+
348
+ const styles = definition.styles ? `<style>${definition.styles}</style>` : '';
349
+ this.shadowRoot.innerHTML = `${styles}${markup}`;
350
+
351
+ if (triggerUpdated) {
352
+ definition.updated?.call(this);
353
+ }
354
+ }
355
+ }
356
+
357
+ if (!customElements.get(tagName)) {
358
+ customElements.define(tagName, BQueryComponent);
359
+ }
360
+ };
@@ -0,0 +1,339 @@
1
+ import { sanitizeHtml } from '../security/sanitize';
2
+ import { BQueryElement } from './element';
3
+ import { applyAll } from './shared';
4
+
5
+ /**
6
+ * Wrapper for multiple DOM elements.
7
+ * Provides batch operations on a collection of elements with chainable API.
8
+ *
9
+ * This class enables jQuery-like operations across multiple elements:
10
+ * - All mutating methods apply to every element in the collection
11
+ * - Getter methods return data from the first element
12
+ * - Supports iteration via forEach, map, filter, and reduce
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * $$('.items')
17
+ * .addClass('highlight')
18
+ * .css({ opacity: '0.8' })
19
+ * .on('click', () => console.log('clicked'));
20
+ * ```
21
+ */
22
+ export class BQueryCollection {
23
+ /**
24
+ * Creates a new collection wrapper.
25
+ * @param elements - Array of DOM elements to wrap
26
+ */
27
+ constructor(public readonly elements: Element[]) {}
28
+
29
+ /**
30
+ * Gets the number of elements in the collection.
31
+ */
32
+ get length(): number {
33
+ return this.elements.length;
34
+ }
35
+
36
+ /**
37
+ * Gets the first element in the collection, if any.
38
+ * @internal
39
+ */
40
+ private first(): Element | undefined {
41
+ return this.elements[0];
42
+ }
43
+
44
+ /**
45
+ * Gets a single element as a BQueryElement wrapper.
46
+ *
47
+ * @param index - Zero-based index of the element
48
+ * @returns BQueryElement wrapper or undefined if out of range
49
+ */
50
+ eq(index: number): BQueryElement | undefined {
51
+ const el = this.elements[index];
52
+ return el ? new BQueryElement(el) : undefined;
53
+ }
54
+
55
+ /**
56
+ * Gets the first element as a BQueryElement wrapper.
57
+ *
58
+ * @returns BQueryElement wrapper or undefined if empty
59
+ */
60
+ firstEl(): BQueryElement | undefined {
61
+ return this.eq(0);
62
+ }
63
+
64
+ /**
65
+ * Gets the last element as a BQueryElement wrapper.
66
+ *
67
+ * @returns BQueryElement wrapper or undefined if empty
68
+ */
69
+ lastEl(): BQueryElement | undefined {
70
+ return this.eq(this.elements.length - 1);
71
+ }
72
+
73
+ /**
74
+ * Iterates over each element in the collection.
75
+ *
76
+ * @param callback - Function to call for each wrapped element
77
+ * @returns The instance for method chaining
78
+ */
79
+ each(callback: (element: BQueryElement, index: number) => void): this {
80
+ this.elements.forEach((element, index) => {
81
+ callback(new BQueryElement(element), index);
82
+ });
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Maps each element to a new value.
88
+ *
89
+ * @param callback - Function to transform each element
90
+ * @returns Array of transformed values
91
+ */
92
+ map<T>(callback: (element: Element, index: number) => T): T[] {
93
+ return this.elements.map(callback);
94
+ }
95
+
96
+ /**
97
+ * Filters elements based on a predicate.
98
+ *
99
+ * @param predicate - Function to test each element
100
+ * @returns New BQueryCollection with matching elements
101
+ */
102
+ filter(predicate: (element: Element, index: number) => boolean): BQueryCollection {
103
+ return new BQueryCollection(this.elements.filter(predicate));
104
+ }
105
+
106
+ /**
107
+ * Reduces the collection to a single value.
108
+ *
109
+ * @param callback - Reducer function
110
+ * @param initialValue - Initial accumulator value
111
+ * @returns Accumulated result
112
+ */
113
+ reduce<T>(callback: (accumulator: T, element: Element, index: number) => T, initialValue: T): T {
114
+ return this.elements.reduce(callback, initialValue);
115
+ }
116
+
117
+ /**
118
+ * Converts the collection to an array of BQueryElement wrappers.
119
+ *
120
+ * @returns Array of BQueryElement instances
121
+ */
122
+ toArray(): BQueryElement[] {
123
+ return this.elements.map((el) => new BQueryElement(el));
124
+ }
125
+
126
+ /** Add one or more classes to all elements. */
127
+ addClass(...classNames: string[]): this {
128
+ applyAll(this.elements, (el) => el.classList.add(...classNames));
129
+ return this;
130
+ }
131
+
132
+ /** Remove one or more classes from all elements. */
133
+ removeClass(...classNames: string[]): this {
134
+ applyAll(this.elements, (el) => el.classList.remove(...classNames));
135
+ return this;
136
+ }
137
+
138
+ /** Toggle a class on all elements. */
139
+ toggleClass(className: string, force?: boolean): this {
140
+ applyAll(this.elements, (el) => el.classList.toggle(className, force));
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Sets an attribute on all elements or gets from first.
146
+ *
147
+ * @param name - Attribute name
148
+ * @param value - Value to set (optional)
149
+ * @returns Attribute value when getting, instance when setting
150
+ */
151
+ attr(name: string, value?: string): string | this {
152
+ if (value === undefined) {
153
+ return this.first()?.getAttribute(name) ?? '';
154
+ }
155
+ applyAll(this.elements, (el) => el.setAttribute(name, value));
156
+ return this;
157
+ }
158
+
159
+ /**
160
+ * Removes an attribute from all elements.
161
+ *
162
+ * @param name - Attribute name to remove
163
+ * @returns The instance for method chaining
164
+ */
165
+ removeAttr(name: string): this {
166
+ applyAll(this.elements, (el) => el.removeAttribute(name));
167
+ return this;
168
+ }
169
+
170
+ /**
171
+ * Sets text content on all elements or gets from first.
172
+ *
173
+ * @param value - Text to set (optional)
174
+ * @returns Text content when getting, instance when setting
175
+ */
176
+ text(value?: string): string | this {
177
+ if (value === undefined) {
178
+ return this.first()?.textContent ?? '';
179
+ }
180
+ applyAll(this.elements, (el) => {
181
+ el.textContent = value;
182
+ });
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Sets sanitized HTML on all elements or gets from first.
188
+ *
189
+ * @param value - HTML to set (optional, will be sanitized)
190
+ * @returns HTML content when getting, instance when setting
191
+ */
192
+ html(value?: string): string | this {
193
+ if (value === undefined) {
194
+ return this.first()?.innerHTML ?? '';
195
+ }
196
+ const sanitized = sanitizeHtml(value);
197
+ applyAll(this.elements, (el) => {
198
+ el.innerHTML = sanitized;
199
+ });
200
+ return this;
201
+ }
202
+
203
+ /**
204
+ * Sets HTML on all elements without sanitization.
205
+ *
206
+ * @param value - Raw HTML to set
207
+ * @returns The instance for method chaining
208
+ * @warning Bypasses XSS protection
209
+ */
210
+ htmlUnsafe(value: string): this {
211
+ applyAll(this.elements, (el) => {
212
+ el.innerHTML = value;
213
+ });
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Applies CSS styles to all elements.
219
+ *
220
+ * @param property - Property name or object of properties
221
+ * @param value - Value when setting single property
222
+ * @returns The instance for method chaining
223
+ */
224
+ css(property: string | Record<string, string>, value?: string): this {
225
+ if (typeof property === 'string') {
226
+ if (value !== undefined) {
227
+ applyAll(this.elements, (el) => {
228
+ (el as HTMLElement).style.setProperty(property, value);
229
+ });
230
+ }
231
+ return this;
232
+ }
233
+
234
+ applyAll(this.elements, (el) => {
235
+ for (const [key, val] of Object.entries(property)) {
236
+ (el as HTMLElement).style.setProperty(key, val);
237
+ }
238
+ });
239
+ return this;
240
+ }
241
+
242
+ /**
243
+ * Shows all elements.
244
+ *
245
+ * @param display - Optional display value (default: '')
246
+ * @returns The instance for method chaining
247
+ */
248
+ show(display: string = ''): this {
249
+ applyAll(this.elements, (el) => {
250
+ el.removeAttribute('hidden');
251
+ (el as HTMLElement).style.display = display;
252
+ });
253
+ return this;
254
+ }
255
+
256
+ /**
257
+ * Hides all elements.
258
+ *
259
+ * @returns The instance for method chaining
260
+ */
261
+ hide(): this {
262
+ applyAll(this.elements, (el) => {
263
+ (el as HTMLElement).style.display = 'none';
264
+ });
265
+ return this;
266
+ }
267
+
268
+ /**
269
+ * Adds an event listener to all elements.
270
+ *
271
+ * @param event - Event type
272
+ * @param handler - Event handler
273
+ * @returns The instance for method chaining
274
+ */
275
+ on(event: string, handler: EventListenerOrEventListenerObject): this {
276
+ applyAll(this.elements, (el) => el.addEventListener(event, handler));
277
+ return this;
278
+ }
279
+
280
+ /**
281
+ * Adds a one-time event listener to all elements.
282
+ *
283
+ * @param event - Event type
284
+ * @param handler - Event handler
285
+ * @returns The instance for method chaining
286
+ */
287
+ once(event: string, handler: EventListener): this {
288
+ applyAll(this.elements, (el) => el.addEventListener(event, handler, { once: true }));
289
+ return this;
290
+ }
291
+
292
+ /**
293
+ * Removes an event listener from all elements.
294
+ *
295
+ * @param event - Event type
296
+ * @param handler - The handler to remove
297
+ * @returns The instance for method chaining
298
+ */
299
+ off(event: string, handler: EventListenerOrEventListenerObject): this {
300
+ applyAll(this.elements, (el) => el.removeEventListener(event, handler));
301
+ return this;
302
+ }
303
+
304
+ /**
305
+ * Triggers a custom event on all elements.
306
+ *
307
+ * @param event - Event type
308
+ * @param detail - Optional event detail
309
+ * @returns The instance for method chaining
310
+ */
311
+ trigger(event: string, detail?: unknown): this {
312
+ applyAll(this.elements, (el) => {
313
+ el.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true }));
314
+ });
315
+ return this;
316
+ }
317
+
318
+ /**
319
+ * Removes all elements from the DOM.
320
+ *
321
+ * @returns The instance for method chaining
322
+ */
323
+ remove(): this {
324
+ applyAll(this.elements, (el) => el.remove());
325
+ return this;
326
+ }
327
+
328
+ /**
329
+ * Clears all child nodes from all elements.
330
+ *
331
+ * @returns The instance for method chaining
332
+ */
333
+ empty(): this {
334
+ applyAll(this.elements, (el) => {
335
+ el.innerHTML = '';
336
+ });
337
+ return this;
338
+ }
339
+ }