@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,493 @@
1
+ import { sanitizeHtml } from '../security/sanitize';
2
+ import { applyAll, toElementList } from './shared';
3
+
4
+ /**
5
+ * Wrapper for a single DOM element.
6
+ * Provides a chainable, jQuery-like API for DOM manipulation.
7
+ *
8
+ * This class encapsulates a DOM element and provides methods for:
9
+ * - Class manipulation (addClass, removeClass, toggleClass)
10
+ * - Attribute and property access (attr, prop, data)
11
+ * - Content manipulation (text, html, append, prepend)
12
+ * - Style manipulation (css)
13
+ * - Event handling (on, off, once, trigger)
14
+ * - DOM traversal (find, closest, parent, children, siblings)
15
+ *
16
+ * All mutating methods return `this` for method chaining.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * $('#button')
21
+ * .addClass('active')
22
+ * .css({ color: 'blue' })
23
+ * .on('click', () => console.log('clicked'));
24
+ * ```
25
+ */
26
+ export class BQueryElement {
27
+ /**
28
+ * Creates a new BQueryElement wrapper.
29
+ * @param element - The DOM element to wrap
30
+ */
31
+ constructor(private readonly element: Element) {}
32
+
33
+ /**
34
+ * Exposes the raw DOM element when direct access is needed.
35
+ * Use sparingly; prefer the wrapper methods for consistency.
36
+ */
37
+ get raw(): Element {
38
+ return this.element;
39
+ }
40
+
41
+ /**
42
+ * Exposes the underlying DOM element.
43
+ * Provided for spec compatibility and read-only access.
44
+ */
45
+ get node(): Element {
46
+ return this.element;
47
+ }
48
+
49
+ /** Add one or more classes. */
50
+ addClass(...classNames: string[]): this {
51
+ this.element.classList.add(...classNames);
52
+ return this;
53
+ }
54
+
55
+ /** Remove one or more classes. */
56
+ removeClass(...classNames: string[]): this {
57
+ this.element.classList.remove(...classNames);
58
+ return this;
59
+ }
60
+
61
+ /** Toggle a class by name. */
62
+ toggleClass(className: string, force?: boolean): this {
63
+ this.element.classList.toggle(className, force);
64
+ return this;
65
+ }
66
+
67
+ /** Get or set an attribute. */
68
+ attr(name: string, value?: string): string | this {
69
+ if (value === undefined) {
70
+ return this.element.getAttribute(name) ?? '';
71
+ }
72
+ this.element.setAttribute(name, value);
73
+ return this;
74
+ }
75
+
76
+ /** Get or set a property. */
77
+ prop<T extends keyof Element>(name: T, value?: Element[T]): Element[T] | this {
78
+ if (value === undefined) {
79
+ return this.element[name];
80
+ }
81
+ this.element[name] = value;
82
+ return this;
83
+ }
84
+
85
+ /** Read or write data attributes in camelCase. */
86
+ data(name: string, value?: string): string | this {
87
+ const key = name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
88
+ if (value === undefined) {
89
+ return this.element.getAttribute(`data-${key}`) ?? '';
90
+ }
91
+ this.element.setAttribute(`data-${key}`, value);
92
+ return this;
93
+ }
94
+
95
+ /** Get or set text content. */
96
+ text(value?: string): string | this {
97
+ if (value === undefined) {
98
+ return this.element.textContent ?? '';
99
+ }
100
+ this.element.textContent = value;
101
+ return this;
102
+ }
103
+
104
+ /** Set HTML content using a sanitized string. */
105
+ /**
106
+ * Sets sanitized HTML content on the element.
107
+ * Uses the security module to sanitize input and prevent XSS attacks.
108
+ *
109
+ * @param value - The HTML string to set (will be sanitized)
110
+ * @returns The instance for method chaining
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * $('#content').html('<strong>Hello</strong>');
115
+ * ```
116
+ */
117
+ html(value: string): this {
118
+ this.element.innerHTML = sanitizeHtml(value);
119
+ return this;
120
+ }
121
+
122
+ /**
123
+ * Sets HTML content without sanitization.
124
+ * Use only when you trust the HTML source completely.
125
+ *
126
+ * @param value - The raw HTML string to set
127
+ * @returns The instance for method chaining
128
+ *
129
+ * @warning This method bypasses XSS protection. Use with caution.
130
+ */
131
+ htmlUnsafe(value: string): this {
132
+ this.element.innerHTML = value;
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Gets or sets CSS styles on the element.
138
+ *
139
+ * @param property - A CSS property name or an object of property-value pairs
140
+ * @param value - The value when setting a single property
141
+ * @returns The instance for method chaining
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * // Single property
146
+ * $('#box').css('color', 'red');
147
+ *
148
+ * // Multiple properties
149
+ * $('#box').css({ color: 'red', 'font-size': '16px' });
150
+ * ```
151
+ */
152
+ css(property: string | Record<string, string>, value?: string): this {
153
+ if (typeof property === 'string') {
154
+ if (value !== undefined) {
155
+ (this.element as HTMLElement).style.setProperty(property, value);
156
+ }
157
+ return this;
158
+ }
159
+
160
+ for (const [key, val] of Object.entries(property)) {
161
+ (this.element as HTMLElement).style.setProperty(key, val);
162
+ }
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ * Appends HTML or elements to the end of the element.
168
+ *
169
+ * @param content - HTML string or element(s) to append
170
+ * @returns The instance for method chaining
171
+ */
172
+ append(content: string | Element | Element[]): this {
173
+ this.insertContent(content, 'beforeend');
174
+ return this;
175
+ }
176
+
177
+ /**
178
+ * Prepends HTML or elements to the beginning of the element.
179
+ *
180
+ * @param content - HTML string or element(s) to prepend
181
+ * @returns The instance for method chaining
182
+ */
183
+ prepend(content: string | Element | Element[]): this {
184
+ this.insertContent(content, 'afterbegin');
185
+ return this;
186
+ }
187
+
188
+ /**
189
+ * Inserts content before this element.
190
+ *
191
+ * @param content - HTML string or element(s) to insert
192
+ * @returns The instance for method chaining
193
+ */
194
+ before(content: string | Element | Element[]): this {
195
+ this.insertContent(content, 'beforebegin');
196
+ return this;
197
+ }
198
+
199
+ /**
200
+ * Inserts content after this element.
201
+ *
202
+ * @param content - HTML string or element(s) to insert
203
+ * @returns The instance for method chaining
204
+ */
205
+ after(content: string | Element | Element[]): this {
206
+ this.insertContent(content, 'afterend');
207
+ return this;
208
+ }
209
+
210
+ /**
211
+ * Removes the element from the DOM.
212
+ *
213
+ * @returns The instance for method chaining (though element is now detached)
214
+ */
215
+ remove(): this {
216
+ this.element.remove();
217
+ return this;
218
+ }
219
+
220
+ /**
221
+ * Clears all child nodes from the element.
222
+ *
223
+ * @returns The instance for method chaining
224
+ */
225
+ empty(): this {
226
+ this.element.innerHTML = '';
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * Clones the element, optionally with all descendants.
232
+ *
233
+ * @param deep - If true, clone all descendants (default: true)
234
+ * @returns A new BQueryElement wrapping the cloned element
235
+ */
236
+ clone(deep: boolean = true): BQueryElement {
237
+ return new BQueryElement(this.element.cloneNode(deep) as Element);
238
+ }
239
+
240
+ /**
241
+ * Finds all descendant elements matching the selector.
242
+ *
243
+ * @param selector - CSS selector to match
244
+ * @returns Array of matching elements
245
+ */
246
+ find(selector: string): Element[] {
247
+ return Array.from(this.element.querySelectorAll(selector));
248
+ }
249
+
250
+ /**
251
+ * Finds the first descendant element matching the selector.
252
+ *
253
+ * @param selector - CSS selector to match
254
+ * @returns The first matching element or null
255
+ */
256
+ findOne(selector: string): Element | null {
257
+ return this.element.querySelector(selector);
258
+ }
259
+
260
+ /**
261
+ * Finds the closest ancestor matching the selector.
262
+ *
263
+ * @param selector - CSS selector to match
264
+ * @returns The matching ancestor or null
265
+ */
266
+ closest(selector: string): Element | null {
267
+ return this.element.closest(selector);
268
+ }
269
+
270
+ /**
271
+ * Gets the parent element.
272
+ *
273
+ * @returns The parent element or null
274
+ */
275
+ parent(): Element | null {
276
+ return this.element.parentElement;
277
+ }
278
+
279
+ /**
280
+ * Gets all child elements.
281
+ *
282
+ * @returns Array of child elements
283
+ */
284
+ children(): Element[] {
285
+ return Array.from(this.element.children);
286
+ }
287
+
288
+ /**
289
+ * Gets all sibling elements.
290
+ *
291
+ * @returns Array of sibling elements (excluding this element)
292
+ */
293
+ siblings(): Element[] {
294
+ const parent = this.element.parentElement;
295
+ if (!parent) return [];
296
+ return Array.from(parent.children).filter((child) => child !== this.element);
297
+ }
298
+
299
+ /**
300
+ * Gets the next sibling element.
301
+ *
302
+ * @returns The next sibling element or null
303
+ */
304
+ next(): Element | null {
305
+ return this.element.nextElementSibling;
306
+ }
307
+
308
+ /**
309
+ * Gets the previous sibling element.
310
+ *
311
+ * @returns The previous sibling element or null
312
+ */
313
+ prev(): Element | null {
314
+ return this.element.previousElementSibling;
315
+ }
316
+
317
+ /**
318
+ * Adds an event listener.
319
+ *
320
+ * @param event - Event type to listen for
321
+ * @param handler - Event handler function
322
+ * @returns The instance for method chaining
323
+ */
324
+ on(event: string, handler: EventListenerOrEventListenerObject): this {
325
+ this.element.addEventListener(event, handler);
326
+ return this;
327
+ }
328
+
329
+ /**
330
+ * Adds a one-time event listener that removes itself after firing.
331
+ *
332
+ * @param event - Event type to listen for
333
+ * @param handler - Event handler function
334
+ * @returns The instance for method chaining
335
+ */
336
+ once(event: string, handler: EventListener): this {
337
+ this.element.addEventListener(event, handler, { once: true });
338
+ return this;
339
+ }
340
+
341
+ /**
342
+ * Removes an event listener.
343
+ *
344
+ * @param event - Event type
345
+ * @param handler - The handler to remove
346
+ * @returns The instance for method chaining
347
+ */
348
+ off(event: string, handler: EventListenerOrEventListenerObject): this {
349
+ this.element.removeEventListener(event, handler);
350
+ return this;
351
+ }
352
+
353
+ /**
354
+ * Triggers a custom event on the element.
355
+ *
356
+ * @param event - Event type to trigger
357
+ * @param detail - Optional detail data to include with the event
358
+ * @returns The instance for method chaining
359
+ */
360
+ trigger(event: string, detail?: unknown): this {
361
+ this.element.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true }));
362
+ return this;
363
+ }
364
+
365
+ /**
366
+ * Checks if the element matches a CSS selector.
367
+ *
368
+ * @param selector - CSS selector to match against
369
+ * @returns True if the element matches the selector
370
+ */
371
+ matches(selector: string): boolean {
372
+ return this.element.matches(selector);
373
+ }
374
+
375
+ /**
376
+ * Checks if the element has a specific class.
377
+ *
378
+ * @param className - Class name to check
379
+ * @returns True if the element has the class
380
+ */
381
+ hasClass(className: string): boolean {
382
+ return this.element.classList.contains(className);
383
+ }
384
+
385
+ /**
386
+ * Shows the element by removing the hidden attribute and setting display.
387
+ *
388
+ * @param display - Optional display value (default: '')
389
+ * @returns The instance for method chaining
390
+ */
391
+ show(display: string = ''): this {
392
+ this.element.removeAttribute('hidden');
393
+ (this.element as HTMLElement).style.display = display;
394
+ return this;
395
+ }
396
+
397
+ /**
398
+ * Hides the element by setting display to 'none'.
399
+ *
400
+ * @returns The instance for method chaining
401
+ */
402
+ hide(): this {
403
+ (this.element as HTMLElement).style.display = 'none';
404
+ return this;
405
+ }
406
+
407
+ /**
408
+ * Toggles the visibility of the element.
409
+ *
410
+ * @param force - Optional force show (true) or hide (false)
411
+ * @returns The instance for method chaining
412
+ */
413
+ toggle(force?: boolean): this {
414
+ const isHidden = (this.element as HTMLElement).style.display === 'none';
415
+ const shouldShow = force ?? isHidden;
416
+ return shouldShow ? this.show() : this.hide();
417
+ }
418
+
419
+ /**
420
+ * Focuses the element.
421
+ *
422
+ * @returns The instance for method chaining
423
+ */
424
+ focus(): this {
425
+ (this.element as HTMLElement).focus();
426
+ return this;
427
+ }
428
+
429
+ /**
430
+ * Blurs (unfocuses) the element.
431
+ *
432
+ * @returns The instance for method chaining
433
+ */
434
+ blur(): this {
435
+ (this.element as HTMLElement).blur();
436
+ return this;
437
+ }
438
+
439
+ /**
440
+ * Gets or sets the value of form elements.
441
+ *
442
+ * @param newValue - Optional value to set
443
+ * @returns The current value when getting, or the instance when setting
444
+ */
445
+ val(newValue?: string): string | this {
446
+ const input = this.element as HTMLInputElement;
447
+ if (newValue === undefined) {
448
+ return input.value ?? '';
449
+ }
450
+ input.value = newValue;
451
+ return this;
452
+ }
453
+
454
+ /**
455
+ * Gets the bounding client rectangle of the element.
456
+ *
457
+ * @returns The element's bounding rectangle
458
+ */
459
+ rect(): DOMRect {
460
+ return this.element.getBoundingClientRect();
461
+ }
462
+
463
+ /**
464
+ * Gets the offset dimensions (width, height, top, left).
465
+ *
466
+ * @returns Object with offset dimensions
467
+ */
468
+ offset(): { width: number; height: number; top: number; left: number } {
469
+ const el = this.element as HTMLElement;
470
+ return {
471
+ width: el.offsetWidth,
472
+ height: el.offsetHeight,
473
+ top: el.offsetTop,
474
+ left: el.offsetLeft,
475
+ };
476
+ }
477
+
478
+ /**
479
+ * Internal method to insert content at a specified position.
480
+ * @internal
481
+ */
482
+ private insertContent(content: string | Element | Element[], position: InsertPosition) {
483
+ if (typeof content === 'string') {
484
+ this.element.insertAdjacentHTML(position, sanitizeHtml(content));
485
+ return;
486
+ }
487
+
488
+ const elements = toElementList(content);
489
+ applyAll(elements, (el) => {
490
+ this.element.insertAdjacentElement(position, el);
491
+ });
492
+ }
493
+ }
@@ -0,0 +1,4 @@
1
+ export { BQueryCollection } from './collection';
2
+ export { BQueryElement } from './element';
3
+ export { $, $$ } from './selector';
4
+ export { utils } from './utils';
@@ -0,0 +1,29 @@
1
+ import { BQueryCollection } from './collection';
2
+ import { BQueryElement } from './element';
3
+
4
+ /**
5
+ * Select a single element. Returns a wrapper for chainable operations.
6
+ */
7
+ export const $ = (selector: string | Element): BQueryElement => {
8
+ if (typeof selector !== 'string') {
9
+ return new BQueryElement(selector);
10
+ }
11
+ const element = document.querySelector(selector);
12
+ if (!element) {
13
+ throw new Error(`bQuery: element not found for selector "${selector}"`);
14
+ }
15
+ return new BQueryElement(element);
16
+ };
17
+
18
+ /**
19
+ * Select multiple elements. Returns a collection wrapper.
20
+ */
21
+ export const $$ = (selector: string | Element[] | NodeListOf<Element>): BQueryCollection => {
22
+ if (Array.isArray(selector)) {
23
+ return new BQueryCollection(selector);
24
+ }
25
+ if (selector instanceof NodeList) {
26
+ return new BQueryCollection(Array.from(selector));
27
+ }
28
+ return new BQueryCollection(Array.from(document.querySelectorAll(selector)));
29
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared helpers for element wrappers.
3
+ */
4
+ export type ElementList = Element[];
5
+
6
+ export const toElementList = (input: Element | ElementList): ElementList =>
7
+ Array.isArray(input) ? input : [input];
8
+
9
+ export const applyAll = (elements: ElementList, action: (el: Element) => void) => {
10
+ for (const el of elements) {
11
+ action(el);
12
+ }
13
+ };