@bquery/bquery 1.1.2 → 1.3.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 (307) hide show
  1. package/README.md +501 -323
  2. package/dist/batch-4LAvfLE7.js +13 -0
  3. package/dist/batch-4LAvfLE7.js.map +1 -0
  4. package/dist/component/component.d.ts +69 -0
  5. package/dist/component/component.d.ts.map +1 -0
  6. package/dist/component/html.d.ts +35 -0
  7. package/dist/component/html.d.ts.map +1 -0
  8. package/dist/component/index.d.ts +3 -126
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/props.d.ts +18 -0
  11. package/dist/component/props.d.ts.map +1 -0
  12. package/dist/component/types.d.ts +77 -0
  13. package/dist/component/types.d.ts.map +1 -0
  14. package/dist/component.es.mjs +90 -59
  15. package/dist/component.es.mjs.map +1 -1
  16. package/dist/core/collection.d.ts +36 -0
  17. package/dist/core/collection.d.ts.map +1 -1
  18. package/dist/core/dom.d.ts +6 -0
  19. package/dist/core/dom.d.ts.map +1 -0
  20. package/dist/core/element.d.ts +8 -0
  21. package/dist/core/element.d.ts.map +1 -1
  22. package/dist/core/index.d.ts +1 -0
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/utils/array.d.ts +74 -0
  25. package/dist/core/utils/array.d.ts.map +1 -0
  26. package/dist/core/utils/function.d.ts +70 -0
  27. package/dist/core/utils/function.d.ts.map +1 -0
  28. package/dist/core/utils/index.d.ts +70 -0
  29. package/dist/core/utils/index.d.ts.map +1 -0
  30. package/dist/core/utils/misc.d.ts +63 -0
  31. package/dist/core/utils/misc.d.ts.map +1 -0
  32. package/dist/core/utils/number.d.ts +65 -0
  33. package/dist/core/utils/number.d.ts.map +1 -0
  34. package/dist/core/utils/object.d.ts +133 -0
  35. package/dist/core/utils/object.d.ts.map +1 -0
  36. package/dist/core/utils/string.d.ts +80 -0
  37. package/dist/core/utils/string.d.ts.map +1 -0
  38. package/dist/core/utils/type-guards.d.ts +79 -0
  39. package/dist/core/utils/type-guards.d.ts.map +1 -0
  40. package/dist/core-COenAZjD.js +145 -0
  41. package/dist/core-COenAZjD.js.map +1 -0
  42. package/dist/core.es.mjs +411 -448
  43. package/dist/core.es.mjs.map +1 -1
  44. package/dist/full.d.ts +8 -2
  45. package/dist/full.d.ts.map +1 -1
  46. package/dist/full.es.mjs +86 -40
  47. package/dist/full.es.mjs.map +1 -1
  48. package/dist/full.iife.js +6 -1
  49. package/dist/full.iife.js.map +1 -1
  50. package/dist/full.umd.js +6 -1
  51. package/dist/full.umd.js.map +1 -1
  52. package/dist/index.d.ts +3 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.es.mjs +137 -44
  55. package/dist/index.es.mjs.map +1 -1
  56. package/dist/motion/animate.d.ts +25 -0
  57. package/dist/motion/animate.d.ts.map +1 -0
  58. package/dist/motion/easing.d.ts +30 -0
  59. package/dist/motion/easing.d.ts.map +1 -0
  60. package/dist/motion/flip.d.ts +55 -0
  61. package/dist/motion/flip.d.ts.map +1 -0
  62. package/dist/motion/index.d.ts +11 -138
  63. package/dist/motion/index.d.ts.map +1 -1
  64. package/dist/motion/keyframes.d.ts +21 -0
  65. package/dist/motion/keyframes.d.ts.map +1 -0
  66. package/dist/motion/reduced-motion.d.ts +12 -0
  67. package/dist/motion/reduced-motion.d.ts.map +1 -0
  68. package/dist/motion/scroll.d.ts +15 -0
  69. package/dist/motion/scroll.d.ts.map +1 -0
  70. package/dist/motion/spring.d.ts +42 -0
  71. package/dist/motion/spring.d.ts.map +1 -0
  72. package/dist/motion/stagger.d.ts +22 -0
  73. package/dist/motion/stagger.d.ts.map +1 -0
  74. package/dist/motion/timeline.d.ts +21 -0
  75. package/dist/motion/timeline.d.ts.map +1 -0
  76. package/dist/motion/transition.d.ts +22 -0
  77. package/dist/motion/transition.d.ts.map +1 -0
  78. package/dist/motion/types.d.ts +182 -0
  79. package/dist/motion/types.d.ts.map +1 -0
  80. package/dist/motion.es.mjs +320 -61
  81. package/dist/motion.es.mjs.map +1 -1
  82. package/dist/persisted-Dz_ryNuC.js +278 -0
  83. package/dist/persisted-Dz_ryNuC.js.map +1 -0
  84. package/dist/reactive/batch.d.ts +13 -0
  85. package/dist/reactive/batch.d.ts.map +1 -0
  86. package/dist/reactive/computed.d.ts +50 -0
  87. package/dist/reactive/computed.d.ts.map +1 -0
  88. package/dist/reactive/core.d.ts +60 -0
  89. package/dist/reactive/core.d.ts.map +1 -0
  90. package/dist/reactive/effect.d.ts +15 -0
  91. package/dist/reactive/effect.d.ts.map +1 -0
  92. package/dist/reactive/index.d.ts +2 -2
  93. package/dist/reactive/index.d.ts.map +1 -1
  94. package/dist/reactive/internals.d.ts +36 -0
  95. package/dist/reactive/internals.d.ts.map +1 -0
  96. package/dist/reactive/linked.d.ts +36 -0
  97. package/dist/reactive/linked.d.ts.map +1 -0
  98. package/dist/reactive/persisted.d.ts +14 -0
  99. package/dist/reactive/persisted.d.ts.map +1 -0
  100. package/dist/reactive/readonly.d.ts +26 -0
  101. package/dist/reactive/readonly.d.ts.map +1 -0
  102. package/dist/reactive/signal.d.ts +13 -305
  103. package/dist/reactive/signal.d.ts.map +1 -1
  104. package/dist/reactive/type-guards.d.ts +20 -0
  105. package/dist/reactive/type-guards.d.ts.map +1 -0
  106. package/dist/reactive/untrack.d.ts +29 -0
  107. package/dist/reactive/untrack.d.ts.map +1 -0
  108. package/dist/reactive/watch.d.ts +42 -0
  109. package/dist/reactive/watch.d.ts.map +1 -0
  110. package/dist/reactive.es.mjs +30 -154
  111. package/dist/reactive.es.mjs.map +1 -1
  112. package/dist/router/index.d.ts +41 -0
  113. package/dist/router/index.d.ts.map +1 -0
  114. package/dist/router/links.d.ts +44 -0
  115. package/dist/router/links.d.ts.map +1 -0
  116. package/dist/router/match.d.ts +20 -0
  117. package/dist/router/match.d.ts.map +1 -0
  118. package/dist/router/navigation.d.ts +45 -0
  119. package/dist/router/navigation.d.ts.map +1 -0
  120. package/dist/router/query.d.ts +16 -0
  121. package/dist/router/query.d.ts.map +1 -0
  122. package/dist/router/router.d.ts +34 -0
  123. package/dist/router/router.d.ts.map +1 -0
  124. package/dist/router/state.d.ts +27 -0
  125. package/dist/router/state.d.ts.map +1 -0
  126. package/dist/router/types.d.ts +88 -0
  127. package/dist/router/types.d.ts.map +1 -0
  128. package/dist/router/utils.d.ts +65 -0
  129. package/dist/router/utils.d.ts.map +1 -0
  130. package/dist/router.es.mjs +202 -0
  131. package/dist/router.es.mjs.map +1 -0
  132. package/dist/sanitize-1FBEPAFH.js +272 -0
  133. package/dist/sanitize-1FBEPAFH.js.map +1 -0
  134. package/dist/security/constants.d.ts +42 -0
  135. package/dist/security/constants.d.ts.map +1 -0
  136. package/dist/security/csp.d.ts +24 -0
  137. package/dist/security/csp.d.ts.map +1 -0
  138. package/dist/security/index.d.ts +4 -2
  139. package/dist/security/index.d.ts.map +1 -1
  140. package/dist/security/sanitize-core.d.ts +13 -0
  141. package/dist/security/sanitize-core.d.ts.map +1 -0
  142. package/dist/security/sanitize.d.ts +5 -57
  143. package/dist/security/sanitize.d.ts.map +1 -1
  144. package/dist/security/trusted-types.d.ts +25 -0
  145. package/dist/security/trusted-types.d.ts.map +1 -0
  146. package/dist/security/types.d.ts +36 -0
  147. package/dist/security/types.d.ts.map +1 -0
  148. package/dist/security.es.mjs +50 -277
  149. package/dist/security.es.mjs.map +1 -1
  150. package/dist/store/create-store.d.ts +15 -0
  151. package/dist/store/create-store.d.ts.map +1 -0
  152. package/dist/store/define-store.d.ts +28 -0
  153. package/dist/store/define-store.d.ts.map +1 -0
  154. package/dist/store/devtools.d.ts +22 -0
  155. package/dist/store/devtools.d.ts.map +1 -0
  156. package/dist/store/index.d.ts +12 -0
  157. package/dist/store/index.d.ts.map +1 -0
  158. package/dist/store/mapping.d.ts +28 -0
  159. package/dist/store/mapping.d.ts.map +1 -0
  160. package/dist/store/persisted.d.ts +13 -0
  161. package/dist/store/persisted.d.ts.map +1 -0
  162. package/dist/store/plugins.d.ts +13 -0
  163. package/dist/store/plugins.d.ts.map +1 -0
  164. package/dist/store/registry.d.ts +28 -0
  165. package/dist/store/registry.d.ts.map +1 -0
  166. package/dist/store/types.d.ts +71 -0
  167. package/dist/store/types.d.ts.map +1 -0
  168. package/dist/store/utils.d.ts +28 -0
  169. package/dist/store/utils.d.ts.map +1 -0
  170. package/dist/store/watch.d.ts +23 -0
  171. package/dist/store/watch.d.ts.map +1 -0
  172. package/dist/store.es.mjs +27 -0
  173. package/dist/store.es.mjs.map +1 -0
  174. package/dist/type-guards-DRma3-Kc.js +16 -0
  175. package/dist/type-guards-DRma3-Kc.js.map +1 -0
  176. package/dist/untrack-BuEQKH7_.js +6 -0
  177. package/dist/untrack-BuEQKH7_.js.map +1 -0
  178. package/dist/view/directives/bind.d.ts +7 -0
  179. package/dist/view/directives/bind.d.ts.map +1 -0
  180. package/dist/view/directives/class.d.ts +8 -0
  181. package/dist/view/directives/class.d.ts.map +1 -0
  182. package/dist/view/directives/for.d.ts +23 -0
  183. package/dist/view/directives/for.d.ts.map +1 -0
  184. package/dist/view/directives/html.d.ts +7 -0
  185. package/dist/view/directives/html.d.ts.map +1 -0
  186. package/dist/view/directives/if.d.ts +7 -0
  187. package/dist/view/directives/if.d.ts.map +1 -0
  188. package/dist/view/directives/index.d.ts +12 -0
  189. package/dist/view/directives/index.d.ts.map +1 -0
  190. package/dist/view/directives/model.d.ts +7 -0
  191. package/dist/view/directives/model.d.ts.map +1 -0
  192. package/dist/view/directives/on.d.ts +7 -0
  193. package/dist/view/directives/on.d.ts.map +1 -0
  194. package/dist/view/directives/ref.d.ts +7 -0
  195. package/dist/view/directives/ref.d.ts.map +1 -0
  196. package/dist/view/directives/show.d.ts +7 -0
  197. package/dist/view/directives/show.d.ts.map +1 -0
  198. package/dist/view/directives/style.d.ts +7 -0
  199. package/dist/view/directives/style.d.ts.map +1 -0
  200. package/dist/view/directives/text.d.ts +7 -0
  201. package/dist/view/directives/text.d.ts.map +1 -0
  202. package/dist/view/evaluate.d.ts +43 -0
  203. package/dist/view/evaluate.d.ts.map +1 -0
  204. package/dist/view/index.d.ts +111 -0
  205. package/dist/view/index.d.ts.map +1 -0
  206. package/dist/view/mount.d.ts +69 -0
  207. package/dist/view/mount.d.ts.map +1 -0
  208. package/dist/view/process.d.ts +26 -0
  209. package/dist/view/process.d.ts.map +1 -0
  210. package/dist/view/types.d.ts +36 -0
  211. package/dist/view/types.d.ts.map +1 -0
  212. package/dist/view.es.mjs +426 -0
  213. package/dist/view.es.mjs.map +1 -0
  214. package/dist/watch-CXyaBC_9.js +58 -0
  215. package/dist/watch-CXyaBC_9.js.map +1 -0
  216. package/package.json +26 -14
  217. package/src/component/component.ts +289 -0
  218. package/src/component/html.ts +53 -0
  219. package/src/component/index.ts +40 -414
  220. package/src/component/props.ts +116 -0
  221. package/src/component/types.ts +85 -0
  222. package/src/core/collection.ts +588 -454
  223. package/src/core/dom.ts +38 -0
  224. package/src/core/element.ts +746 -740
  225. package/src/core/index.ts +43 -0
  226. package/src/core/utils/array.ts +102 -0
  227. package/src/core/utils/function.ts +110 -0
  228. package/src/core/utils/index.ts +83 -0
  229. package/src/core/utils/misc.ts +82 -0
  230. package/src/core/utils/number.ts +78 -0
  231. package/src/core/utils/object.ts +206 -0
  232. package/src/core/utils/string.ts +112 -0
  233. package/src/core/utils/type-guards.ts +112 -0
  234. package/src/full.ts +187 -106
  235. package/src/index.ts +36 -27
  236. package/src/motion/animate.ts +113 -0
  237. package/src/motion/easing.ts +40 -0
  238. package/src/motion/flip.ts +176 -0
  239. package/src/motion/index.ts +41 -358
  240. package/src/motion/keyframes.ts +46 -0
  241. package/src/motion/reduced-motion.ts +17 -0
  242. package/src/motion/scroll.ts +57 -0
  243. package/src/motion/spring.ts +150 -0
  244. package/src/motion/stagger.ts +43 -0
  245. package/src/motion/timeline.ts +246 -0
  246. package/src/motion/transition.ts +51 -0
  247. package/src/motion/types.ts +198 -0
  248. package/src/reactive/batch.ts +22 -0
  249. package/src/reactive/computed.ts +92 -0
  250. package/src/reactive/core.ts +93 -0
  251. package/src/reactive/effect.ts +43 -0
  252. package/src/reactive/index.ts +23 -22
  253. package/src/reactive/internals.ts +105 -0
  254. package/src/reactive/linked.ts +56 -0
  255. package/src/reactive/persisted.ts +74 -0
  256. package/src/reactive/readonly.ts +35 -0
  257. package/src/reactive/signal.ts +20 -506
  258. package/src/reactive/type-guards.ts +22 -0
  259. package/src/reactive/untrack.ts +31 -0
  260. package/src/reactive/watch.ts +73 -0
  261. package/src/router/index.ts +41 -0
  262. package/src/router/links.ts +130 -0
  263. package/src/router/match.ts +106 -0
  264. package/src/router/navigation.ts +71 -0
  265. package/src/router/query.ts +35 -0
  266. package/src/router/router.ts +211 -0
  267. package/src/router/state.ts +46 -0
  268. package/src/router/types.ts +93 -0
  269. package/src/router/utils.ts +116 -0
  270. package/src/security/constants.ts +209 -0
  271. package/src/security/csp.ts +77 -0
  272. package/src/security/index.ts +4 -12
  273. package/src/security/sanitize-core.ts +343 -0
  274. package/src/security/sanitize.ts +66 -625
  275. package/src/security/trusted-types.ts +69 -0
  276. package/src/security/types.ts +40 -0
  277. package/src/store/create-store.ts +329 -0
  278. package/src/store/define-store.ts +48 -0
  279. package/src/store/devtools.ts +45 -0
  280. package/src/store/index.ts +22 -0
  281. package/src/store/mapping.ts +73 -0
  282. package/src/store/persisted.ts +61 -0
  283. package/src/store/plugins.ts +32 -0
  284. package/src/store/registry.ts +51 -0
  285. package/src/store/types.ts +94 -0
  286. package/src/store/utils.ts +141 -0
  287. package/src/store/watch.ts +52 -0
  288. package/src/view/directives/bind.ts +23 -0
  289. package/src/view/directives/class.ts +70 -0
  290. package/src/view/directives/for.ts +275 -0
  291. package/src/view/directives/html.ts +19 -0
  292. package/src/view/directives/if.ts +30 -0
  293. package/src/view/directives/index.ts +11 -0
  294. package/src/view/directives/model.ts +56 -0
  295. package/src/view/directives/on.ts +41 -0
  296. package/src/view/directives/ref.ts +41 -0
  297. package/src/view/directives/show.ts +26 -0
  298. package/src/view/directives/style.ts +47 -0
  299. package/src/view/directives/text.ts +15 -0
  300. package/src/view/evaluate.ts +274 -0
  301. package/src/view/index.ts +112 -0
  302. package/src/view/mount.ts +200 -0
  303. package/src/view/process.ts +92 -0
  304. package/src/view/types.ts +44 -0
  305. package/dist/core/utils.d.ts +0 -313
  306. package/dist/core/utils.d.ts.map +0 -1
  307. package/src/core/utils.ts +0 -444
@@ -1,740 +1,746 @@
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
- /** Handler signature for delegated events */
27
- type DelegatedHandler = (event: Event, target: Element) => void;
28
-
29
- export class BQueryElement {
30
- /**
31
- * Stores delegated event handlers for cleanup via undelegate().
32
- * Key format: `${event}:${selector}`
33
- * @internal
34
- */
35
- private readonly delegatedHandlers = new Map<string, Map<DelegatedHandler, EventListener>>();
36
-
37
- /**
38
- * Creates a new BQueryElement wrapper.
39
- * @param element - The DOM element to wrap
40
- */
41
- constructor(private readonly element: Element) {}
42
-
43
- /**
44
- * Exposes the raw DOM element when direct access is needed.
45
- * Use sparingly; prefer the wrapper methods for consistency.
46
- */
47
- get raw(): Element {
48
- return this.element;
49
- }
50
-
51
- /**
52
- * Exposes the underlying DOM element.
53
- * Provided for spec compatibility and read-only access.
54
- */
55
- get node(): Element {
56
- return this.element;
57
- }
58
-
59
- /** Add one or more classes. */
60
- addClass(...classNames: string[]): this {
61
- this.element.classList.add(...classNames);
62
- return this;
63
- }
64
-
65
- /** Remove one or more classes. */
66
- removeClass(...classNames: string[]): this {
67
- this.element.classList.remove(...classNames);
68
- return this;
69
- }
70
-
71
- /** Toggle a class by name. */
72
- toggleClass(className: string, force?: boolean): this {
73
- this.element.classList.toggle(className, force);
74
- return this;
75
- }
76
-
77
- /** Get or set an attribute. */
78
- attr(name: string, value?: string): string | this {
79
- if (value === undefined) {
80
- return this.element.getAttribute(name) ?? '';
81
- }
82
- this.element.setAttribute(name, value);
83
- return this;
84
- }
85
-
86
- /** Get or set a property. */
87
- prop<T extends keyof Element>(name: T, value?: Element[T]): Element[T] | this {
88
- if (value === undefined) {
89
- return this.element[name];
90
- }
91
- this.element[name] = value;
92
- return this;
93
- }
94
-
95
- /** Read or write data attributes in camelCase. */
96
- data(name: string, value?: string): string | this {
97
- const key = name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
98
- if (value === undefined) {
99
- return this.element.getAttribute(`data-${key}`) ?? '';
100
- }
101
- this.element.setAttribute(`data-${key}`, value);
102
- return this;
103
- }
104
-
105
- /** Get or set text content. */
106
- text(value?: string): string | this {
107
- if (value === undefined) {
108
- return this.element.textContent ?? '';
109
- }
110
- this.element.textContent = value;
111
- return this;
112
- }
113
-
114
- /** Set HTML content using a sanitized string. */
115
- /**
116
- * Sets sanitized HTML content on the element.
117
- * Uses the security module to sanitize input and prevent XSS attacks.
118
- *
119
- * @param value - The HTML string to set (will be sanitized)
120
- * @returns The instance for method chaining
121
- *
122
- * @example
123
- * ```ts
124
- * $('#content').html('<strong>Hello</strong>');
125
- * ```
126
- */
127
- html(value: string): this {
128
- this.element.innerHTML = sanitizeHtml(value);
129
- return this;
130
- }
131
-
132
- /**
133
- * Sets HTML content without sanitization.
134
- * Use only when you trust the HTML source completely.
135
- *
136
- * @param value - The raw HTML string to set
137
- * @returns The instance for method chaining
138
- *
139
- * @warning This method bypasses XSS protection. Use with caution.
140
- */
141
- htmlUnsafe(value: string): this {
142
- this.element.innerHTML = value;
143
- return this;
144
- }
145
-
146
- /**
147
- * Gets or sets CSS styles on the element.
148
- *
149
- * @param property - A CSS property name or an object of property-value pairs
150
- * @param value - The value when setting a single property
151
- * @returns The instance for method chaining
152
- *
153
- * @example
154
- * ```ts
155
- * // Single property
156
- * $('#box').css('color', 'red');
157
- *
158
- * // Multiple properties
159
- * $('#box').css({ color: 'red', 'font-size': '16px' });
160
- * ```
161
- */
162
- css(property: string | Record<string, string>, value?: string): this {
163
- if (typeof property === 'string') {
164
- if (value !== undefined) {
165
- (this.element as HTMLElement).style.setProperty(property, value);
166
- }
167
- return this;
168
- }
169
-
170
- for (const [key, val] of Object.entries(property)) {
171
- (this.element as HTMLElement).style.setProperty(key, val);
172
- }
173
- return this;
174
- }
175
-
176
- /**
177
- * Appends HTML or elements to the end of the element.
178
- *
179
- * @param content - HTML string or element(s) to append
180
- * @returns The instance for method chaining
181
- */
182
- append(content: string | Element | Element[]): this {
183
- this.insertContent(content, 'beforeend');
184
- return this;
185
- }
186
-
187
- /**
188
- * Prepends HTML or elements to the beginning of the element.
189
- *
190
- * @param content - HTML string or element(s) to prepend
191
- * @returns The instance for method chaining
192
- */
193
- prepend(content: string | Element | Element[]): this {
194
- this.insertContent(content, 'afterbegin');
195
- return this;
196
- }
197
-
198
- /**
199
- * Inserts content before this element.
200
- *
201
- * @param content - HTML string or element(s) to insert
202
- * @returns The instance for method chaining
203
- */
204
- before(content: string | Element | Element[]): this {
205
- this.insertContent(content, 'beforebegin');
206
- return this;
207
- }
208
-
209
- /**
210
- * Inserts content after this element.
211
- *
212
- * @param content - HTML string or element(s) to insert
213
- * @returns The instance for method chaining
214
- */
215
- after(content: string | Element | Element[]): this {
216
- this.insertContent(content, 'afterend');
217
- return this;
218
- }
219
-
220
- /**
221
- * Wraps the element with the specified wrapper element or tag.
222
- *
223
- * @param wrapper - Tag name string or Element to wrap with
224
- * @returns The instance for method chaining
225
- *
226
- * @example
227
- * ```ts
228
- * $('#content').wrap('div'); // Wraps with <div>
229
- * $('#content').wrap(document.createElement('section'));
230
- * ```
231
- */
232
- wrap(wrapper: string | Element): this {
233
- const wrapperEl = typeof wrapper === 'string' ? document.createElement(wrapper) : wrapper;
234
- this.element.parentNode?.insertBefore(wrapperEl, this.element);
235
- wrapperEl.appendChild(this.element);
236
- return this;
237
- }
238
-
239
- /**
240
- * Removes the parent element, keeping this element in its place.
241
- * Essentially the opposite of wrap().
242
- *
243
- * @returns The instance for method chaining
244
- *
245
- * @example
246
- * ```ts
247
- * // Before: <div><span id="text">Hello</span></div>
248
- * $('#text').unwrap();
249
- * // After: <span id="text">Hello</span>
250
- * ```
251
- */
252
- unwrap(): this {
253
- const parent = this.element.parentElement;
254
- if (parent && parent.parentNode) {
255
- parent.parentNode.insertBefore(this.element, parent);
256
- parent.remove();
257
- }
258
- return this;
259
- }
260
-
261
- /**
262
- * Replaces this element with new content.
263
- *
264
- * @param content - HTML string (sanitized) or Element to replace with
265
- * @returns A new BQueryElement wrapping the replacement element
266
- *
267
- * @example
268
- * ```ts
269
- * const newEl = $('#old').replaceWith('<div id="new">Replaced</div>');
270
- * ```
271
- */
272
- replaceWith(content: string | Element): BQueryElement {
273
- let newEl: Element;
274
- if (typeof content === 'string') {
275
- const template = document.createElement('template');
276
- template.innerHTML = sanitizeHtml(content);
277
- newEl = template.content.firstElementChild ?? document.createElement('div');
278
- } else {
279
- newEl = content;
280
- }
281
- this.element.replaceWith(newEl);
282
- return new BQueryElement(newEl);
283
- }
284
-
285
- /**
286
- * Scrolls the element into view with configurable behavior.
287
- *
288
- * @param options - ScrollIntoView options or boolean for legacy behavior
289
- * @returns The instance for method chaining
290
- *
291
- * @example
292
- * ```ts
293
- * $('#section').scrollTo(); // Smooth scroll
294
- * $('#section').scrollTo({ behavior: 'instant', block: 'start' });
295
- * ```
296
- */
297
- scrollTo(options: ScrollIntoViewOptions | boolean = { behavior: 'smooth' }): this {
298
- this.element.scrollIntoView(options);
299
- return this;
300
- }
301
-
302
- /**
303
- * Removes the element from the DOM.
304
- *
305
- * @returns The instance for method chaining (though element is now detached)
306
- */
307
- remove(): this {
308
- this.element.remove();
309
- return this;
310
- }
311
-
312
- /**
313
- * Clears all child nodes from the element.
314
- *
315
- * @returns The instance for method chaining
316
- */
317
- empty(): this {
318
- this.element.innerHTML = '';
319
- return this;
320
- }
321
-
322
- /**
323
- * Clones the element, optionally with all descendants.
324
- *
325
- * @param deep - If true, clone all descendants (default: true)
326
- * @returns A new BQueryElement wrapping the cloned element
327
- */
328
- clone(deep: boolean = true): BQueryElement {
329
- return new BQueryElement(this.element.cloneNode(deep) as Element);
330
- }
331
-
332
- /**
333
- * Finds all descendant elements matching the selector.
334
- *
335
- * @param selector - CSS selector to match
336
- * @returns Array of matching elements
337
- */
338
- find(selector: string): Element[] {
339
- return Array.from(this.element.querySelectorAll(selector));
340
- }
341
-
342
- /**
343
- * Finds the first descendant element matching the selector.
344
- *
345
- * @param selector - CSS selector to match
346
- * @returns The first matching element or null
347
- */
348
- findOne(selector: string): Element | null {
349
- return this.element.querySelector(selector);
350
- }
351
-
352
- /**
353
- * Finds the closest ancestor matching the selector.
354
- *
355
- * @param selector - CSS selector to match
356
- * @returns The matching ancestor or null
357
- */
358
- closest(selector: string): Element | null {
359
- return this.element.closest(selector);
360
- }
361
-
362
- /**
363
- * Gets the parent element.
364
- *
365
- * @returns The parent element or null
366
- */
367
- parent(): Element | null {
368
- return this.element.parentElement;
369
- }
370
-
371
- /**
372
- * Gets all child elements.
373
- *
374
- * @returns Array of child elements
375
- */
376
- children(): Element[] {
377
- return Array.from(this.element.children);
378
- }
379
-
380
- /**
381
- * Gets all sibling elements.
382
- *
383
- * @returns Array of sibling elements (excluding this element)
384
- */
385
- siblings(): Element[] {
386
- const parent = this.element.parentElement;
387
- if (!parent) return [];
388
- return Array.from(parent.children).filter((child) => child !== this.element);
389
- }
390
-
391
- /**
392
- * Gets the next sibling element.
393
- *
394
- * @returns The next sibling element or null
395
- */
396
- next(): Element | null {
397
- return this.element.nextElementSibling;
398
- }
399
-
400
- /**
401
- * Gets the previous sibling element.
402
- *
403
- * @returns The previous sibling element or null
404
- */
405
- prev(): Element | null {
406
- return this.element.previousElementSibling;
407
- }
408
-
409
- /**
410
- * Adds an event listener.
411
- *
412
- * @param event - Event type to listen for
413
- * @param handler - Event handler function
414
- * @returns The instance for method chaining
415
- */
416
- on(event: string, handler: EventListenerOrEventListenerObject): this {
417
- this.element.addEventListener(event, handler);
418
- return this;
419
- }
420
-
421
- /**
422
- * Adds a one-time event listener that removes itself after firing.
423
- *
424
- * @param event - Event type to listen for
425
- * @param handler - Event handler function
426
- * @returns The instance for method chaining
427
- */
428
- once(event: string, handler: EventListener): this {
429
- this.element.addEventListener(event, handler, { once: true });
430
- return this;
431
- }
432
-
433
- /**
434
- * Removes an event listener.
435
- *
436
- * @param event - Event type
437
- * @param handler - The handler to remove
438
- * @returns The instance for method chaining
439
- */
440
- off(event: string, handler: EventListenerOrEventListenerObject): this {
441
- this.element.removeEventListener(event, handler);
442
- return this;
443
- }
444
-
445
- /**
446
- * Triggers a custom event on the element.
447
- *
448
- * @param event - Event type to trigger
449
- * @param detail - Optional detail data to include with the event
450
- * @returns The instance for method chaining
451
- */
452
- trigger(event: string, detail?: unknown): this {
453
- this.element.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true }));
454
- return this;
455
- }
456
-
457
- /**
458
- * Adds a delegated event listener that only triggers for matching descendants.
459
- * More efficient than adding listeners to many elements individually.
460
- *
461
- * Use `undelegate()` to remove the listener later.
462
- *
463
- * @param event - Event type to listen for
464
- * @param selector - CSS selector to match against event targets
465
- * @param handler - Event handler function, receives the matched element as context
466
- * @returns The instance for method chaining
467
- *
468
- * @example
469
- * ```ts
470
- * // Instead of adding listeners to each button:
471
- * const handler = (e, target) => console.log('Clicked:', target.textContent);
472
- * $('#list').delegate('click', '.item', handler);
473
- *
474
- * // Later, remove the delegated listener:
475
- * $('#list').undelegate('click', '.item', handler);
476
- * ```
477
- */
478
- delegate(
479
- event: string,
480
- selector: string,
481
- handler: (event: Event, target: Element) => void
482
- ): this {
483
- const key = `${event}:${selector}`;
484
- const wrapper: EventListener = (e: Event) => {
485
- const target = (e.target as Element).closest(selector);
486
- if (target && this.element.contains(target)) {
487
- handler(e, target);
488
- }
489
- };
490
-
491
- // Store the wrapper so it can be removed later
492
- if (!this.delegatedHandlers.has(key)) {
493
- this.delegatedHandlers.set(key, new Map());
494
- }
495
- this.delegatedHandlers.get(key)!.set(handler, wrapper);
496
-
497
- this.element.addEventListener(event, wrapper);
498
- return this;
499
- }
500
-
501
- /**
502
- * Removes a delegated event listener previously added with `delegate()`.
503
- *
504
- * @param event - Event type that was registered
505
- * @param selector - CSS selector that was used
506
- * @param handler - The original handler function passed to delegate()
507
- * @returns The instance for method chaining
508
- *
509
- * @example
510
- * ```ts
511
- * const handler = (e, target) => console.log('Clicked:', target.textContent);
512
- * $('#list').delegate('click', '.item', handler);
513
- *
514
- * // Remove the delegated listener:
515
- * $('#list').undelegate('click', '.item', handler);
516
- * ```
517
- */
518
- undelegate(
519
- event: string,
520
- selector: string,
521
- handler: (event: Event, target: Element) => void
522
- ): this {
523
- const key = `${event}:${selector}`;
524
- const handlers = this.delegatedHandlers.get(key);
525
-
526
- if (handlers) {
527
- const wrapper = handlers.get(handler);
528
- if (wrapper) {
529
- this.element.removeEventListener(event, wrapper);
530
- handlers.delete(handler);
531
-
532
- // Clean up empty maps
533
- if (handlers.size === 0) {
534
- this.delegatedHandlers.delete(key);
535
- }
536
- }
537
- }
538
-
539
- return this;
540
- }
541
-
542
- /**
543
- * Checks if the element matches a CSS selector.
544
- *
545
- * @param selector - CSS selector to match against
546
- * @returns True if the element matches the selector
547
- */
548
- matches(selector: string): boolean {
549
- return this.element.matches(selector);
550
- }
551
-
552
- /**
553
- * Checks if the element has a specific class.
554
- *
555
- * @param className - Class name to check
556
- * @returns True if the element has the class
557
- */
558
- hasClass(className: string): boolean {
559
- return this.element.classList.contains(className);
560
- }
561
-
562
- /**
563
- * Shows the element by removing the hidden attribute and setting display.
564
- *
565
- * @param display - Optional display value (default: '')
566
- * @returns The instance for method chaining
567
- */
568
- show(display: string = ''): this {
569
- this.element.removeAttribute('hidden');
570
- (this.element as HTMLElement).style.display = display;
571
- return this;
572
- }
573
-
574
- /**
575
- * Hides the element by setting display to 'none'.
576
- *
577
- * @returns The instance for method chaining
578
- */
579
- hide(): this {
580
- (this.element as HTMLElement).style.display = 'none';
581
- return this;
582
- }
583
-
584
- /**
585
- * Toggles the visibility of the element.
586
- *
587
- * @param force - Optional force show (true) or hide (false)
588
- * @returns The instance for method chaining
589
- */
590
- toggle(force?: boolean): this {
591
- const isHidden = (this.element as HTMLElement).style.display === 'none';
592
- const shouldShow = force ?? isHidden;
593
- return shouldShow ? this.show() : this.hide();
594
- }
595
-
596
- /**
597
- * Focuses the element.
598
- *
599
- * @returns The instance for method chaining
600
- */
601
- focus(): this {
602
- (this.element as HTMLElement).focus();
603
- return this;
604
- }
605
-
606
- /**
607
- * Blurs (unfocuses) the element.
608
- *
609
- * @returns The instance for method chaining
610
- */
611
- blur(): this {
612
- (this.element as HTMLElement).blur();
613
- return this;
614
- }
615
-
616
- /**
617
- * Gets or sets the value of form elements.
618
- *
619
- * @param newValue - Optional value to set
620
- * @returns The current value when getting, or the instance when setting
621
- */
622
- val(newValue?: string): string | this {
623
- const input = this.element as HTMLInputElement;
624
- if (newValue === undefined) {
625
- return input.value ?? '';
626
- }
627
- input.value = newValue;
628
- return this;
629
- }
630
-
631
- /**
632
- * Serializes form data to a plain object.
633
- * Only works on form elements; returns empty object for non-forms.
634
- *
635
- * @returns Object with form field names as keys and values
636
- *
637
- * @example
638
- * ```ts
639
- * // For a form with <input name="email" value="test@example.com">
640
- * const data = $('#myForm').serialize();
641
- * // { email: 'test@example.com' }
642
- * ```
643
- */
644
- serialize(): Record<string, string | string[]> {
645
- const form = this.element as HTMLFormElement;
646
- if (form.tagName.toLowerCase() !== 'form') {
647
- return {};
648
- }
649
-
650
- const result: Record<string, string | string[]> = {};
651
- const formData = new FormData(form);
652
-
653
- for (const [key, value] of formData.entries()) {
654
- if (typeof value !== 'string') continue; // Skip File objects
655
-
656
- if (key in result) {
657
- // Handle multiple values (e.g., checkboxes)
658
- const existing = result[key];
659
- if (Array.isArray(existing)) {
660
- existing.push(value);
661
- } else {
662
- result[key] = [existing, value];
663
- }
664
- } else {
665
- result[key] = value;
666
- }
667
- }
668
-
669
- return result;
670
- }
671
-
672
- /**
673
- * Serializes form data to a URL-encoded query string.
674
- *
675
- * @returns URL-encoded string suitable for form submission
676
- *
677
- * @example
678
- * ```ts
679
- * const queryString = $('#myForm').serializeString();
680
- * // 'email=test%40example.com&name=John'
681
- * ```
682
- */
683
- serializeString(): string {
684
- const form = this.element as HTMLFormElement;
685
- if (form.tagName.toLowerCase() !== 'form') {
686
- return '';
687
- }
688
-
689
- const formData = new FormData(form);
690
- const params = new URLSearchParams();
691
-
692
- for (const [key, value] of formData.entries()) {
693
- if (typeof value === 'string') {
694
- params.append(key, value);
695
- }
696
- }
697
-
698
- return params.toString();
699
- }
700
-
701
- /**
702
- * Gets the bounding client rectangle of the element.
703
- *
704
- * @returns The element's bounding rectangle
705
- */
706
- rect(): DOMRect {
707
- return this.element.getBoundingClientRect();
708
- }
709
-
710
- /**
711
- * Gets the offset dimensions (width, height, top, left).
712
- *
713
- * @returns Object with offset dimensions
714
- */
715
- offset(): { width: number; height: number; top: number; left: number } {
716
- const el = this.element as HTMLElement;
717
- return {
718
- width: el.offsetWidth,
719
- height: el.offsetHeight,
720
- top: el.offsetTop,
721
- left: el.offsetLeft,
722
- };
723
- }
724
-
725
- /**
726
- * Internal method to insert content at a specified position.
727
- * @internal
728
- */
729
- private insertContent(content: string | Element | Element[], position: InsertPosition) {
730
- if (typeof content === 'string') {
731
- this.element.insertAdjacentHTML(position, sanitizeHtml(content));
732
- return;
733
- }
734
-
735
- const elements = toElementList(content);
736
- applyAll(elements, (el) => {
737
- this.element.insertAdjacentElement(position, el);
738
- });
739
- }
740
- }
1
+ import { createElementFromHtml, insertContent, setHtml } from './dom';
2
+
3
+ /**
4
+ * Wrapper for a single DOM element.
5
+ * Provides a chainable, jQuery-like API for DOM manipulation.
6
+ *
7
+ * This class encapsulates a DOM element and provides methods for:
8
+ * - Class manipulation (addClass, removeClass, toggleClass)
9
+ * - Attribute and property access (attr, prop, data)
10
+ * - Content manipulation (text, html, append, prepend)
11
+ * - Style manipulation (css)
12
+ * - Event handling (on, off, once, trigger)
13
+ * - DOM traversal (find, closest, parent, children, siblings)
14
+ *
15
+ * All mutating methods return `this` for method chaining.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * $('#button')
20
+ * .addClass('active')
21
+ * .css({ color: 'blue' })
22
+ * .on('click', () => console.log('clicked'));
23
+ * ```
24
+ */
25
+ /** Handler signature for delegated events */
26
+ type DelegatedHandler = (event: Event, target: Element) => void;
27
+
28
+ export class BQueryElement {
29
+ /**
30
+ * Stores delegated event handlers for cleanup via undelegate().
31
+ * Key format: `${event}:${selector}`
32
+ * @internal
33
+ */
34
+ private readonly delegatedHandlers = new Map<string, Map<DelegatedHandler, EventListener>>();
35
+
36
+ /**
37
+ * Creates a new BQueryElement wrapper.
38
+ * @param element - The DOM element to wrap
39
+ */
40
+ constructor(private readonly element: Element) {}
41
+
42
+ /**
43
+ * Exposes the raw DOM element when direct access is needed.
44
+ * Use sparingly; prefer the wrapper methods for consistency.
45
+ */
46
+ get raw(): Element {
47
+ return this.element;
48
+ }
49
+
50
+ /**
51
+ * Exposes the underlying DOM element.
52
+ * Provided for spec compatibility and read-only access.
53
+ */
54
+ get node(): Element {
55
+ return this.element;
56
+ }
57
+
58
+ /** Add one or more classes. */
59
+ addClass(...classNames: string[]): this {
60
+ this.element.classList.add(...classNames);
61
+ return this;
62
+ }
63
+
64
+ /** Remove one or more classes. */
65
+ removeClass(...classNames: string[]): this {
66
+ this.element.classList.remove(...classNames);
67
+ return this;
68
+ }
69
+
70
+ /** Toggle a class by name. */
71
+ toggleClass(className: string, force?: boolean): this {
72
+ this.element.classList.toggle(className, force);
73
+ return this;
74
+ }
75
+
76
+ /** Get or set an attribute. */
77
+ attr(name: string, value?: string): string | this {
78
+ if (value === undefined) {
79
+ return this.element.getAttribute(name) ?? '';
80
+ }
81
+ this.element.setAttribute(name, value);
82
+ return this;
83
+ }
84
+
85
+ /** Remove an attribute. */
86
+ removeAttr(name: string): this {
87
+ this.element.removeAttribute(name);
88
+ return this;
89
+ }
90
+
91
+ /** Toggle an attribute on/off. */
92
+ toggleAttr(name: string, force?: boolean): this {
93
+ const hasAttr = this.element.hasAttribute(name);
94
+ const shouldAdd = force ?? !hasAttr;
95
+ if (shouldAdd) {
96
+ this.element.setAttribute(name, '');
97
+ } else {
98
+ this.element.removeAttribute(name);
99
+ }
100
+ return this;
101
+ }
102
+
103
+ /** Get or set a property. */
104
+ prop<T extends keyof Element>(name: T, value?: Element[T]): Element[T] | this {
105
+ if (value === undefined) {
106
+ return this.element[name];
107
+ }
108
+ this.element[name] = value;
109
+ return this;
110
+ }
111
+
112
+ /** Read or write data attributes in camelCase. */
113
+ data(name: string, value?: string): string | this {
114
+ const key = name.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
115
+ if (value === undefined) {
116
+ return this.element.getAttribute(`data-${key}`) ?? '';
117
+ }
118
+ this.element.setAttribute(`data-${key}`, value);
119
+ return this;
120
+ }
121
+
122
+ /** Get or set text content. */
123
+ text(value?: string): string | this {
124
+ if (value === undefined) {
125
+ return this.element.textContent ?? '';
126
+ }
127
+ this.element.textContent = value;
128
+ return this;
129
+ }
130
+
131
+ /** Set HTML content using a sanitized string. */
132
+ /**
133
+ * Sets sanitized HTML content on the element.
134
+ * Uses the security module to sanitize input and prevent XSS attacks.
135
+ *
136
+ * @param value - The HTML string to set (will be sanitized)
137
+ * @returns The instance for method chaining
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * $('#content').html('<strong>Hello</strong>');
142
+ * ```
143
+ */
144
+ html(value: string): this {
145
+ setHtml(this.element, value);
146
+ return this;
147
+ }
148
+
149
+ /**
150
+ * Sets HTML content without sanitization.
151
+ * Use only when you trust the HTML source completely.
152
+ *
153
+ * @param value - The raw HTML string to set
154
+ * @returns The instance for method chaining
155
+ *
156
+ * @warning This method bypasses XSS protection. Use with caution.
157
+ */
158
+ htmlUnsafe(value: string): this {
159
+ this.element.innerHTML = value;
160
+ return this;
161
+ }
162
+
163
+ /**
164
+ * Gets or sets CSS styles on the element.
165
+ *
166
+ * @param property - A CSS property name or an object of property-value pairs
167
+ * @param value - The value when setting a single property
168
+ * @returns The instance for method chaining
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * // Single property
173
+ * $('#box').css('color', 'red');
174
+ *
175
+ * // Multiple properties
176
+ * $('#box').css({ color: 'red', 'font-size': '16px' });
177
+ * ```
178
+ */
179
+ css(property: string | Record<string, string>, value?: string): this {
180
+ if (typeof property === 'string') {
181
+ if (value !== undefined) {
182
+ (this.element as HTMLElement).style.setProperty(property, value);
183
+ }
184
+ return this;
185
+ }
186
+
187
+ for (const [key, val] of Object.entries(property)) {
188
+ (this.element as HTMLElement).style.setProperty(key, val);
189
+ }
190
+ return this;
191
+ }
192
+
193
+ /**
194
+ * Appends HTML or elements to the end of the element.
195
+ *
196
+ * @param content - HTML string or element(s) to append
197
+ * @returns The instance for method chaining
198
+ */
199
+ append(content: string | Element | Element[]): this {
200
+ this.insertContent(content, 'beforeend');
201
+ return this;
202
+ }
203
+
204
+ /**
205
+ * Prepends HTML or elements to the beginning of the element.
206
+ *
207
+ * @param content - HTML string or element(s) to prepend
208
+ * @returns The instance for method chaining
209
+ */
210
+ prepend(content: string | Element | Element[]): this {
211
+ this.insertContent(content, 'afterbegin');
212
+ return this;
213
+ }
214
+
215
+ /**
216
+ * Inserts content before this element.
217
+ *
218
+ * @param content - HTML string or element(s) to insert
219
+ * @returns The instance for method chaining
220
+ */
221
+ before(content: string | Element | Element[]): this {
222
+ this.insertContent(content, 'beforebegin');
223
+ return this;
224
+ }
225
+
226
+ /**
227
+ * Inserts content after this element.
228
+ *
229
+ * @param content - HTML string or element(s) to insert
230
+ * @returns The instance for method chaining
231
+ */
232
+ after(content: string | Element | Element[]): this {
233
+ this.insertContent(content, 'afterend');
234
+ return this;
235
+ }
236
+
237
+ /**
238
+ * Wraps the element with the specified wrapper element or tag.
239
+ *
240
+ * @param wrapper - Tag name string or Element to wrap with
241
+ * @returns The instance for method chaining
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * $('#content').wrap('div'); // Wraps with <div>
246
+ * $('#content').wrap(document.createElement('section'));
247
+ * ```
248
+ */
249
+ wrap(wrapper: string | Element): this {
250
+ const wrapperEl = typeof wrapper === 'string' ? document.createElement(wrapper) : wrapper;
251
+ this.element.parentNode?.insertBefore(wrapperEl, this.element);
252
+ wrapperEl.appendChild(this.element);
253
+ return this;
254
+ }
255
+
256
+ /**
257
+ * Removes the parent element, keeping this element in its place.
258
+ * Essentially the opposite of wrap().
259
+ *
260
+ * **Important**: This method only moves the current element out of its parent
261
+ * before removing the parent. Any sibling elements will be removed along with
262
+ * the parent. For unwrapping multiple siblings, use a collection: `$$(siblings).unwrap()`.
263
+ *
264
+ * @returns The instance for method chaining
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * // Before: <div><span id="text">Hello</span></div>
269
+ * $('#text').unwrap();
270
+ * // After: <span id="text">Hello</span>
271
+ * ```
272
+ */
273
+ unwrap(): this {
274
+ const parent = this.element.parentElement;
275
+ if (parent && parent.parentNode) {
276
+ parent.parentNode.insertBefore(this.element, parent);
277
+ parent.remove();
278
+ }
279
+ return this;
280
+ }
281
+
282
+ /**
283
+ * Replaces this element with new content.
284
+ *
285
+ * @param content - HTML string (sanitized) or Element to replace with
286
+ * @returns A new BQueryElement wrapping the replacement element
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * const newEl = $('#old').replaceWith('<div id="new">Replaced</div>');
291
+ * ```
292
+ */
293
+ replaceWith(content: string | Element): BQueryElement {
294
+ const newEl = typeof content === 'string' ? createElementFromHtml(content) : content;
295
+ this.element.replaceWith(newEl);
296
+ return new BQueryElement(newEl);
297
+ }
298
+
299
+ /**
300
+ * Scrolls the element into view with configurable behavior.
301
+ *
302
+ * @param options - ScrollIntoView options or boolean for legacy behavior
303
+ * @returns The instance for method chaining
304
+ *
305
+ * @example
306
+ * ```ts
307
+ * $('#section').scrollTo(); // Smooth scroll
308
+ * $('#section').scrollTo({ behavior: 'instant', block: 'start' });
309
+ * ```
310
+ */
311
+ scrollTo(options: ScrollIntoViewOptions | boolean = { behavior: 'smooth' }): this {
312
+ this.element.scrollIntoView(options);
313
+ return this;
314
+ }
315
+
316
+ /**
317
+ * Removes the element from the DOM.
318
+ *
319
+ * @returns The instance for method chaining (though element is now detached)
320
+ */
321
+ remove(): this {
322
+ this.element.remove();
323
+ return this;
324
+ }
325
+
326
+ /**
327
+ * Clears all child nodes from the element.
328
+ *
329
+ * @returns The instance for method chaining
330
+ */
331
+ empty(): this {
332
+ this.element.innerHTML = '';
333
+ return this;
334
+ }
335
+
336
+ /**
337
+ * Clones the element, optionally with all descendants.
338
+ *
339
+ * @param deep - If true, clone all descendants (default: true)
340
+ * @returns A new BQueryElement wrapping the cloned element
341
+ */
342
+ clone(deep: boolean = true): BQueryElement {
343
+ return new BQueryElement(this.element.cloneNode(deep) as Element);
344
+ }
345
+
346
+ /**
347
+ * Finds all descendant elements matching the selector.
348
+ *
349
+ * @param selector - CSS selector to match
350
+ * @returns Array of matching elements
351
+ */
352
+ find(selector: string): Element[] {
353
+ return Array.from(this.element.querySelectorAll(selector));
354
+ }
355
+
356
+ /**
357
+ * Finds the first descendant element matching the selector.
358
+ *
359
+ * @param selector - CSS selector to match
360
+ * @returns The first matching element or null
361
+ */
362
+ findOne(selector: string): Element | null {
363
+ return this.element.querySelector(selector);
364
+ }
365
+
366
+ /**
367
+ * Finds the closest ancestor matching the selector.
368
+ *
369
+ * @param selector - CSS selector to match
370
+ * @returns The matching ancestor or null
371
+ */
372
+ closest(selector: string): Element | null {
373
+ return this.element.closest(selector);
374
+ }
375
+
376
+ /**
377
+ * Gets the parent element.
378
+ *
379
+ * @returns The parent element or null
380
+ */
381
+ parent(): Element | null {
382
+ return this.element.parentElement;
383
+ }
384
+
385
+ /**
386
+ * Gets all child elements.
387
+ *
388
+ * @returns Array of child elements
389
+ */
390
+ children(): Element[] {
391
+ return Array.from(this.element.children);
392
+ }
393
+
394
+ /**
395
+ * Gets all sibling elements.
396
+ *
397
+ * @returns Array of sibling elements (excluding this element)
398
+ */
399
+ siblings(): Element[] {
400
+ const parent = this.element.parentElement;
401
+ if (!parent) return [];
402
+ return Array.from(parent.children).filter((child) => child !== this.element);
403
+ }
404
+
405
+ /**
406
+ * Gets the next sibling element.
407
+ *
408
+ * @returns The next sibling element or null
409
+ */
410
+ next(): Element | null {
411
+ return this.element.nextElementSibling;
412
+ }
413
+
414
+ /**
415
+ * Gets the previous sibling element.
416
+ *
417
+ * @returns The previous sibling element or null
418
+ */
419
+ prev(): Element | null {
420
+ return this.element.previousElementSibling;
421
+ }
422
+
423
+ /**
424
+ * Adds an event listener.
425
+ *
426
+ * @param event - Event type to listen for
427
+ * @param handler - Event handler function
428
+ * @returns The instance for method chaining
429
+ */
430
+ on(event: string, handler: EventListenerOrEventListenerObject): this {
431
+ this.element.addEventListener(event, handler);
432
+ return this;
433
+ }
434
+
435
+ /**
436
+ * Adds a one-time event listener that removes itself after firing.
437
+ *
438
+ * @param event - Event type to listen for
439
+ * @param handler - Event handler function
440
+ * @returns The instance for method chaining
441
+ */
442
+ once(event: string, handler: EventListener): this {
443
+ this.element.addEventListener(event, handler, { once: true });
444
+ return this;
445
+ }
446
+
447
+ /**
448
+ * Removes an event listener.
449
+ *
450
+ * @param event - Event type
451
+ * @param handler - The handler to remove
452
+ * @returns The instance for method chaining
453
+ */
454
+ off(event: string, handler: EventListenerOrEventListenerObject): this {
455
+ this.element.removeEventListener(event, handler);
456
+ return this;
457
+ }
458
+
459
+ /**
460
+ * Triggers a custom event on the element.
461
+ *
462
+ * @param event - Event type to trigger
463
+ * @param detail - Optional detail data to include with the event
464
+ * @returns The instance for method chaining
465
+ */
466
+ trigger(event: string, detail?: unknown): this {
467
+ this.element.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true }));
468
+ return this;
469
+ }
470
+
471
+ /**
472
+ * Adds a delegated event listener that only triggers for matching descendants.
473
+ * More efficient than adding listeners to many elements individually.
474
+ *
475
+ * Use `undelegate()` to remove the listener later.
476
+ *
477
+ * @param event - Event type to listen for
478
+ * @param selector - CSS selector to match against event targets
479
+ * @param handler - Event handler function, receives the matched element as context
480
+ * @returns The instance for method chaining
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * // Instead of adding listeners to each button:
485
+ * const handler = (e, target) => console.log('Clicked:', target.textContent);
486
+ * $('#list').delegate('click', '.item', handler);
487
+ *
488
+ * // Later, remove the delegated listener:
489
+ * $('#list').undelegate('click', '.item', handler);
490
+ * ```
491
+ */
492
+ delegate(
493
+ event: string,
494
+ selector: string,
495
+ handler: (event: Event, target: Element) => void
496
+ ): this {
497
+ const key = `${event}:${selector}`;
498
+ const wrapper: EventListener = (e: Event) => {
499
+ const target = (e.target as Element).closest(selector);
500
+ if (target && this.element.contains(target)) {
501
+ handler(e, target);
502
+ }
503
+ };
504
+
505
+ // Store the wrapper so it can be removed later
506
+ if (!this.delegatedHandlers.has(key)) {
507
+ this.delegatedHandlers.set(key, new Map());
508
+ }
509
+ this.delegatedHandlers.get(key)!.set(handler, wrapper);
510
+
511
+ this.element.addEventListener(event, wrapper);
512
+ return this;
513
+ }
514
+
515
+ /**
516
+ * Removes a delegated event listener previously added with `delegate()`.
517
+ *
518
+ * @param event - Event type that was registered
519
+ * @param selector - CSS selector that was used
520
+ * @param handler - The original handler function passed to delegate()
521
+ * @returns The instance for method chaining
522
+ *
523
+ * @example
524
+ * ```ts
525
+ * const handler = (e, target) => console.log('Clicked:', target.textContent);
526
+ * $('#list').delegate('click', '.item', handler);
527
+ *
528
+ * // Remove the delegated listener:
529
+ * $('#list').undelegate('click', '.item', handler);
530
+ * ```
531
+ */
532
+ undelegate(
533
+ event: string,
534
+ selector: string,
535
+ handler: (event: Event, target: Element) => void
536
+ ): this {
537
+ const key = `${event}:${selector}`;
538
+ const handlers = this.delegatedHandlers.get(key);
539
+
540
+ if (handlers) {
541
+ const wrapper = handlers.get(handler);
542
+ if (wrapper) {
543
+ this.element.removeEventListener(event, wrapper);
544
+ handlers.delete(handler);
545
+
546
+ // Clean up empty maps
547
+ if (handlers.size === 0) {
548
+ this.delegatedHandlers.delete(key);
549
+ }
550
+ }
551
+ }
552
+
553
+ return this;
554
+ }
555
+
556
+ /**
557
+ * Checks if the element matches a CSS selector.
558
+ *
559
+ * @param selector - CSS selector to match against
560
+ * @returns True if the element matches the selector
561
+ */
562
+ matches(selector: string): boolean {
563
+ return this.element.matches(selector);
564
+ }
565
+
566
+ /**
567
+ * Checks if the element has a specific class.
568
+ *
569
+ * @param className - Class name to check
570
+ * @returns True if the element has the class
571
+ */
572
+ hasClass(className: string): boolean {
573
+ return this.element.classList.contains(className);
574
+ }
575
+
576
+ /**
577
+ * Shows the element by removing the hidden attribute and setting display.
578
+ *
579
+ * @param display - Optional display value (default: '')
580
+ * @returns The instance for method chaining
581
+ */
582
+ show(display: string = ''): this {
583
+ this.element.removeAttribute('hidden');
584
+ (this.element as HTMLElement).style.display = display;
585
+ return this;
586
+ }
587
+
588
+ /**
589
+ * Hides the element by setting display to 'none'.
590
+ *
591
+ * @returns The instance for method chaining
592
+ */
593
+ hide(): this {
594
+ (this.element as HTMLElement).style.display = 'none';
595
+ return this;
596
+ }
597
+
598
+ /**
599
+ * Toggles the visibility of the element.
600
+ *
601
+ * @param force - Optional force show (true) or hide (false)
602
+ * @returns The instance for method chaining
603
+ */
604
+ toggle(force?: boolean): this {
605
+ const isHidden = (this.element as HTMLElement).style.display === 'none';
606
+ const shouldShow = force ?? isHidden;
607
+ return shouldShow ? this.show() : this.hide();
608
+ }
609
+
610
+ /**
611
+ * Focuses the element.
612
+ *
613
+ * @returns The instance for method chaining
614
+ */
615
+ focus(): this {
616
+ (this.element as HTMLElement).focus();
617
+ return this;
618
+ }
619
+
620
+ /**
621
+ * Blurs (unfocuses) the element.
622
+ *
623
+ * @returns The instance for method chaining
624
+ */
625
+ blur(): this {
626
+ (this.element as HTMLElement).blur();
627
+ return this;
628
+ }
629
+
630
+ /**
631
+ * Gets or sets the value of form elements.
632
+ *
633
+ * @param newValue - Optional value to set
634
+ * @returns The current value when getting, or the instance when setting
635
+ */
636
+ val(newValue?: string): string | this {
637
+ const input = this.element as HTMLInputElement;
638
+ if (newValue === undefined) {
639
+ return input.value ?? '';
640
+ }
641
+ input.value = newValue;
642
+ return this;
643
+ }
644
+
645
+ /**
646
+ * Serializes form data to a plain object.
647
+ * Only works on form elements; returns empty object for non-forms.
648
+ *
649
+ * @returns Object with form field names as keys and values
650
+ *
651
+ * @example
652
+ * ```ts
653
+ * // For a form with <input name="email" value="test@example.com">
654
+ * const data = $('#myForm').serialize();
655
+ * // { email: 'test@example.com' }
656
+ * ```
657
+ */
658
+ serialize(): Record<string, string | string[]> {
659
+ const form = this.element as HTMLFormElement;
660
+ if (form.tagName.toLowerCase() !== 'form') {
661
+ return {};
662
+ }
663
+
664
+ const result: Record<string, string | string[]> = {};
665
+ const formData = new FormData(form);
666
+
667
+ for (const [key, value] of formData.entries()) {
668
+ if (typeof value !== 'string') continue; // Skip File objects
669
+
670
+ if (key in result) {
671
+ // Handle multiple values (e.g., checkboxes)
672
+ const existing = result[key];
673
+ if (Array.isArray(existing)) {
674
+ existing.push(value);
675
+ } else {
676
+ result[key] = [existing, value];
677
+ }
678
+ } else {
679
+ result[key] = value;
680
+ }
681
+ }
682
+
683
+ return result;
684
+ }
685
+
686
+ /**
687
+ * Serializes form data to a URL-encoded query string.
688
+ *
689
+ * @returns URL-encoded string suitable for form submission
690
+ *
691
+ * @example
692
+ * ```ts
693
+ * const queryString = $('#myForm').serializeString();
694
+ * // 'email=test%40example.com&name=John'
695
+ * ```
696
+ */
697
+ serializeString(): string {
698
+ const form = this.element as HTMLFormElement;
699
+ if (form.tagName.toLowerCase() !== 'form') {
700
+ return '';
701
+ }
702
+
703
+ const formData = new FormData(form);
704
+ const params = new URLSearchParams();
705
+
706
+ for (const [key, value] of formData.entries()) {
707
+ if (typeof value === 'string') {
708
+ params.append(key, value);
709
+ }
710
+ }
711
+
712
+ return params.toString();
713
+ }
714
+
715
+ /**
716
+ * Gets the bounding client rectangle of the element.
717
+ *
718
+ * @returns The element's bounding rectangle
719
+ */
720
+ rect(): DOMRect {
721
+ return this.element.getBoundingClientRect();
722
+ }
723
+
724
+ /**
725
+ * Gets the offset dimensions (width, height, top, left).
726
+ *
727
+ * @returns Object with offset dimensions
728
+ */
729
+ offset(): { width: number; height: number; top: number; left: number } {
730
+ const el = this.element as HTMLElement;
731
+ return {
732
+ width: el.offsetWidth,
733
+ height: el.offsetHeight,
734
+ top: el.offsetTop,
735
+ left: el.offsetLeft,
736
+ };
737
+ }
738
+
739
+ /**
740
+ * Internal method to insert content at a specified position.
741
+ * @internal
742
+ */
743
+ private insertContent(content: string | Element | Element[], position: InsertPosition) {
744
+ insertContent(this.element, content, position);
745
+ }
746
+ }