tiny-essentials 1.15.0 → 1.16.1

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 (47) hide show
  1. package/dist/v1/TinyAfterScrollWatcher.js +196 -0
  2. package/dist/v1/TinyAfterScrollWatcher.min.js +1 -0
  3. package/dist/v1/TinyBasicsEs.js +5058 -745
  4. package/dist/v1/TinyBasicsEs.min.js +1 -1
  5. package/dist/v1/TinyDragger.js +4874 -546
  6. package/dist/v1/TinyDragger.min.js +1 -1
  7. package/dist/v1/TinyEssentials.js +5299 -750
  8. package/dist/v1/TinyEssentials.min.js +1 -1
  9. package/dist/v1/TinyHtml.js +4815 -0
  10. package/dist/v1/TinyHtml.min.js +1 -0
  11. package/dist/v1/TinyUploadClicker.js +5093 -298
  12. package/dist/v1/TinyUploadClicker.min.js +1 -1
  13. package/dist/v1/basics/html.cjs +5 -187
  14. package/dist/v1/basics/html.d.mts +2 -53
  15. package/dist/v1/basics/html.mjs +5 -161
  16. package/dist/v1/basics/html_deprecated.cjs +124 -0
  17. package/dist/v1/basics/html_deprecated.d.mts +40 -0
  18. package/dist/v1/basics/html_deprecated.mjs +97 -0
  19. package/dist/v1/basics/index.cjs +8 -7
  20. package/dist/v1/basics/index.d.mts +7 -7
  21. package/dist/v1/basics/index.mjs +2 -1
  22. package/dist/v1/build/TinyAfterScrollWatcher.cjs +7 -0
  23. package/dist/v1/build/TinyAfterScrollWatcher.d.mts +3 -0
  24. package/dist/v1/build/TinyAfterScrollWatcher.mjs +2 -0
  25. package/dist/v1/build/TinyHtml.cjs +7 -0
  26. package/dist/v1/build/TinyHtml.d.mts +3 -0
  27. package/dist/v1/build/TinyHtml.mjs +2 -0
  28. package/dist/v1/index.cjs +12 -7
  29. package/dist/v1/index.d.mts +10 -8
  30. package/dist/v1/index.mjs +5 -2
  31. package/dist/v1/libs/TinyAfterScrollWatcher.cjs +157 -0
  32. package/dist/v1/libs/TinyAfterScrollWatcher.d.mts +86 -0
  33. package/dist/v1/libs/TinyAfterScrollWatcher.mjs +138 -0
  34. package/dist/v1/libs/TinyDragger.cjs +91 -16
  35. package/dist/v1/libs/TinyDragger.d.mts +78 -2
  36. package/dist/v1/libs/TinyDragger.mjs +93 -17
  37. package/dist/v1/libs/TinyHtml.cjs +4347 -0
  38. package/dist/v1/libs/TinyHtml.d.mts +2301 -0
  39. package/dist/v1/libs/TinyHtml.mjs +3926 -0
  40. package/dist/v1/libs/TinyUploadClicker.cjs +1 -0
  41. package/docs/v1/README.md +11 -10
  42. package/docs/v1/basics/html.md +0 -130
  43. package/docs/v1/basics/html_deprecated.md +127 -0
  44. package/docs/v1/libs/TinyAfterScrollWatcher.md +181 -0
  45. package/docs/v1/libs/TinyDragger.md +45 -15
  46. package/docs/v1/libs/TinyHtml.md +1658 -0
  47. package/package.json +4 -1
@@ -0,0 +1,4815 @@
1
+ /******/ (() => { // webpackBootstrap
2
+ /******/ "use strict";
3
+ /******/ // The require scope
4
+ /******/ var __webpack_require__ = {};
5
+ /******/
6
+ /************************************************************************/
7
+ /******/ /* webpack/runtime/define property getters */
8
+ /******/ (() => {
9
+ /******/ // define getter functions for harmony exports
10
+ /******/ __webpack_require__.d = (exports, definition) => {
11
+ /******/ for(var key in definition) {
12
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
13
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
14
+ /******/ }
15
+ /******/ }
16
+ /******/ };
17
+ /******/ })();
18
+ /******/
19
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
20
+ /******/ (() => {
21
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
22
+ /******/ })();
23
+ /******/
24
+ /******/ /* webpack/runtime/make namespace object */
25
+ /******/ (() => {
26
+ /******/ // define __esModule on exports
27
+ /******/ __webpack_require__.r = (exports) => {
28
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
29
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
30
+ /******/ }
31
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
32
+ /******/ };
33
+ /******/ })();
34
+ /******/
35
+ /************************************************************************/
36
+ var __webpack_exports__ = {};
37
+
38
+ // EXPORTS
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ TinyHtml: () => (/* reexport */ libs_TinyHtml)
41
+ });
42
+
43
+ // NAMESPACE OBJECT: ./src/v1/basics/collision.mjs
44
+ var collision_namespaceObject = {};
45
+ __webpack_require__.r(collision_namespaceObject);
46
+ __webpack_require__.d(collision_namespaceObject, {
47
+ areElsCollBottom: () => (areElsCollBottom),
48
+ areElsCollLeft: () => (areElsCollLeft),
49
+ areElsCollPerfBottom: () => (areElsCollPerfBottom),
50
+ areElsCollPerfLeft: () => (areElsCollPerfLeft),
51
+ areElsCollPerfRight: () => (areElsCollPerfRight),
52
+ areElsCollPerfTop: () => (areElsCollPerfTop),
53
+ areElsCollRight: () => (areElsCollRight),
54
+ areElsCollTop: () => (areElsCollTop),
55
+ areElsColliding: () => (areElsColliding),
56
+ areElsPerfColliding: () => (areElsPerfColliding),
57
+ getElsCollDetails: () => (getElsCollDetails),
58
+ getElsCollDirDepth: () => (getElsCollDirDepth),
59
+ getElsCollOverlap: () => (getElsCollOverlap),
60
+ getElsCollOverlapPos: () => (getElsCollOverlapPos),
61
+ getElsColliding: () => (getElsColliding),
62
+ getElsPerfColliding: () => (getElsPerfColliding),
63
+ getElsRelativeCenterOffset: () => (getElsRelativeCenterOffset),
64
+ getRectCenter: () => (getRectCenter)
65
+ });
66
+
67
+ ;// ./src/v1/basics/collision.mjs
68
+ /**
69
+ * A direction relative to a rectangle.
70
+ *
71
+ * Represents one of the four cardinal directions from the perspective of the element.
72
+ *
73
+ * @typedef {'top' | 'bottom' | 'left' | 'right'} Dirs
74
+ */
75
+
76
+ /**
77
+ * Represents all directional aspects of a collision.
78
+ *
79
+ * @typedef {Object} CollDirs
80
+ * @property {Dirs | 'center' | null} in - The dominant direction of entry. `'center'` if all sides are equally overlapped. `null` if no collision.
81
+ * @property {Dirs | null} x - The horizontal direction (`'left'` or `'right'`) the collision is biased toward, or `null`.
82
+ * @property {Dirs | null} y - The vertical direction (`'top'` or `'bottom'`) the collision is biased toward, or `null`.
83
+ */
84
+
85
+ /**
86
+ * Indicates if a collision is in the negative direction (rect2 is outside rect1).
87
+ *
88
+ * @typedef {Object} NegCollDirs
89
+ * @property {Dirs | null} x - Horizontal direction of negative overlap, if any.
90
+ * @property {Dirs | null} y - Vertical direction of negative overlap, if any.
91
+ */
92
+
93
+ /**
94
+ * Collision depth values from each side of rect2 inside rect1.
95
+ *
96
+ * Positive values indicate penetration; negative values indicate gaps.
97
+ *
98
+ * @typedef {Object} CollData
99
+ * @property {number} top - Depth from rect2's top into rect1.
100
+ * @property {number} bottom - Depth from rect2's bottom into rect1.
101
+ * @property {number} left - Depth from rect2's left into rect1.
102
+ * @property {number} right - Depth from rect2's right into rect1.
103
+ */
104
+
105
+ /**
106
+ * X and Y offset representing center difference between two rectangles.
107
+ *
108
+ * Useful to measure how far one element's center is from another.
109
+ *
110
+ * @typedef {Object} CollCenter
111
+ * @property {number} x - Horizontal distance in pixels from rect1's center to rect2's center.
112
+ * @property {number} y - Vertical distance in pixels from rect1's center to rect2's center.
113
+ */
114
+
115
+ /**
116
+ * Represents a rectangular area in absolute pixel values.
117
+ *
118
+ * Similar to `DOMRect`, this object describes the dimensions and position of a box
119
+ * in the 2D plane, typically representing an element's bounding box.
120
+ *
121
+ * @typedef {Object} ObjRect
122
+ * @property {number} height - The total height of the rectangle in pixels.
123
+ * @property {number} width - The total width of the rectangle in pixels.
124
+ * @property {number} top - The Y-coordinate of the top edge of the rectangle.
125
+ * @property {number} bottom - The Y-coordinate of the bottom edge of the rectangle.
126
+ * @property {number} left - The X-coordinate of the left edge of the rectangle.
127
+ * @property {number} right - The X-coordinate of the right edge of the rectangle.
128
+ */
129
+
130
+ // Normal collision checks (loose overlap detection)
131
+
132
+ /**
133
+ * Checks if rect1 is completely above rect2 (no vertical overlap).
134
+ *
135
+ * @param {ObjRect} rect1 - The bounding rectangle of the first element.
136
+ * @param {ObjRect} rect2 - The bounding rectangle of the second element.
137
+ * @returns {boolean} True if rect1 is entirely above rect2.
138
+ */
139
+ const areElsCollTop = (rect1, rect2) => rect1.bottom < rect2.top;
140
+
141
+ /**
142
+ * Checks if rect1 is completely below rect2 (no vertical overlap).
143
+ *
144
+ * @param {ObjRect} rect1
145
+ * @param {ObjRect} rect2
146
+ * @returns {boolean} True if rect1 is entirely below rect2.
147
+ */
148
+ const areElsCollBottom = (rect1, rect2) => rect1.top > rect2.bottom;
149
+
150
+ /**
151
+ * Checks if rect1 is completely to the left of rect2 (no horizontal overlap).
152
+ *
153
+ * @param {ObjRect} rect1
154
+ * @param {ObjRect} rect2
155
+ * @returns {boolean} True if rect1 is entirely to the left of rect2.
156
+ */
157
+ const areElsCollLeft = (rect1, rect2) => rect1.right < rect2.left;
158
+
159
+ /**
160
+ * Checks if rect1 is completely to the right of rect2 (no horizontal overlap).
161
+ *
162
+ * @param {ObjRect} rect1
163
+ * @param {ObjRect} rect2
164
+ * @returns {boolean} True if rect1 is entirely to the right of rect2.
165
+ */
166
+ const areElsCollRight = (rect1, rect2) => rect1.left > rect2.right;
167
+
168
+ // Perfect collision checks (touch included)
169
+
170
+ /**
171
+ * Checks if rect1 is perfectly above rect2 (no vertical touch or overlap).
172
+ *
173
+ * @param {ObjRect} rect1
174
+ * @param {ObjRect} rect2
175
+ * @returns {boolean} True if rect1 is fully above or touching rect2's top.
176
+ */
177
+ const areElsCollPerfTop = (rect1, rect2) => rect1.bottom <= rect2.top;
178
+
179
+ /**
180
+ * Checks if rect1 is perfectly below rect2 (no vertical touch or overlap).
181
+ *
182
+ * @param {ObjRect} rect1
183
+ * @param {ObjRect} rect2
184
+ * @returns {boolean} True if rect1 is fully below or touching rect2's bottom.
185
+ */
186
+ const areElsCollPerfBottom = (rect1, rect2) => rect1.top >= rect2.bottom;
187
+
188
+ /**
189
+ * Checks if rect1 is perfectly to the left of rect2 (no horizontal touch or overlap).
190
+ *
191
+ * @param {ObjRect} rect1
192
+ * @param {ObjRect} rect2
193
+ * @returns {boolean} True if rect1 is fully left or touching rect2's left.
194
+ */
195
+ const areElsCollPerfLeft = (rect1, rect2) => rect1.right <= rect2.left;
196
+
197
+ /**
198
+ * Checks if rect1 is perfectly to the right of rect2 (no horizontal touch or overlap).
199
+ *
200
+ * @param {ObjRect} rect1
201
+ * @param {ObjRect} rect2
202
+ * @returns {boolean} True if rect1 is fully right or touching rect2's right.
203
+ */
204
+ const areElsCollPerfRight = (rect1, rect2) => rect1.left >= rect2.right;
205
+
206
+ // Main collision check
207
+
208
+ /**
209
+ * Returns true if rect1 and rect2 are colliding (partially or fully overlapping).
210
+ *
211
+ * @param {ObjRect} rect1
212
+ * @param {ObjRect} rect2
213
+ * @returns {boolean} True if there's any collision between rect1 and rect2.
214
+ */
215
+ const areElsColliding = (rect1, rect2) =>
216
+ !(
217
+ areElsCollLeft(rect1, rect2) ||
218
+ areElsCollRight(rect1, rect2) ||
219
+ areElsCollTop(rect1, rect2) ||
220
+ areElsCollBottom(rect1, rect2)
221
+ );
222
+
223
+ /**
224
+ * Returns true if rect1 and rect2 are colliding or perfectly touching.
225
+ *
226
+ * @param {ObjRect} rect1
227
+ * @param {ObjRect} rect2
228
+ * @returns {boolean} True if there's any contact or overlap.
229
+ */
230
+ const areElsPerfColliding = (rect1, rect2) =>
231
+ !(
232
+ areElsCollPerfLeft(rect1, rect2) ||
233
+ areElsCollPerfRight(rect1, rect2) ||
234
+ areElsCollPerfTop(rect1, rect2) ||
235
+ areElsCollPerfBottom(rect1, rect2)
236
+ );
237
+
238
+ // Collision direction guess (loose and perfect)
239
+
240
+ /**
241
+ * Attempts to determine the direction rect1 entered rect2 based on loose overlap rules.
242
+ *
243
+ * @param {ObjRect} rect1
244
+ * @param {ObjRect} rect2
245
+ * @returns {string|null} 'top' | 'bottom' | 'left' | 'right' | null
246
+ */
247
+ const getElsColliding = (rect1, rect2) => {
248
+ if (areElsCollLeft(rect1, rect2)) return 'left';
249
+ else if (areElsCollRight(rect1, rect2)) return 'right';
250
+ else if (areElsCollTop(rect1, rect2)) return 'top';
251
+ else if (areElsCollBottom(rect1, rect2)) return 'bottom';
252
+ return null;
253
+ };
254
+
255
+ /**
256
+ * Attempts to determine the direction rect1 touched or entered rect2 using perfect mode.
257
+ *
258
+ * @param {ObjRect} rect1
259
+ * @param {ObjRect} rect2
260
+ * @returns {'top' | 'bottom' | 'left' | 'right' | null}
261
+ */
262
+ const getElsPerfColliding = (rect1, rect2) => {
263
+ if (areElsCollPerfLeft(rect1, rect2)) return 'left';
264
+ else if (areElsCollPerfRight(rect1, rect2)) return 'right';
265
+ else if (areElsCollPerfTop(rect1, rect2)) return 'top';
266
+ else if (areElsCollPerfBottom(rect1, rect2)) return 'bottom';
267
+ return null;
268
+ };
269
+
270
+ // Overlap Calculation
271
+
272
+ /**
273
+ * Calculates overlap values between rect1 and rect2 in all directions.
274
+ *
275
+ * @param {ObjRect} rect1
276
+ * @param {ObjRect} rect2
277
+ * @returns {{
278
+ * overlapLeft: number,
279
+ * overlapRight: number,
280
+ * overlapTop: number,
281
+ * overlapBottom: number
282
+ * }} Distance of overlap from each direction (can be negative).
283
+ */
284
+ const getElsCollOverlap = (rect1, rect2) => ({
285
+ overlapLeft: rect2.right - rect1.left,
286
+ overlapRight: rect1.right - rect2.left,
287
+ overlapTop: rect2.bottom - rect1.top,
288
+ overlapBottom: rect1.bottom - rect2.top,
289
+ });
290
+
291
+ /**
292
+ * Determines directional collision based on overlap depth.
293
+ *
294
+ * @param {Object} [settings={}]
295
+ * @param {number} [settings.overlapLeft]
296
+ * @param {number} [settings.overlapRight]
297
+ * @param {number} [settings.overlapTop]
298
+ * @param {number} [settings.overlapBottom]
299
+ * @returns {{ dirX: Dirs, dirY: Dirs }} Direction of strongest X/Y overlap.
300
+ */
301
+ const getElsCollOverlapPos = ({
302
+ overlapLeft = -1,
303
+ overlapRight = -1,
304
+ overlapTop = -1,
305
+ overlapBottom = -1,
306
+ } = {}) => ({
307
+ dirX: overlapLeft < overlapRight ? 'right' : 'left',
308
+ dirY: overlapTop < overlapBottom ? 'bottom' : 'top',
309
+ });
310
+
311
+ // Center utils
312
+
313
+ /**
314
+ * Calculates the center point (X and Y) of a given Rect.
315
+ *
316
+ * @param {ObjRect} rect - The bounding rectangle of the element.
317
+ * @returns {{ x: number, y: number }} An object with the `x` and `y` coordinates of the center.
318
+ */
319
+ const getRectCenter = (rect) => ({
320
+ x: rect.left + rect.width / 2,
321
+ y: rect.top + rect.height / 2,
322
+ });
323
+
324
+ /**
325
+ * Calculates the offset between the center of rect2 and the center of rect1.
326
+ *
327
+ * The values will be 0 when rect1 is perfectly centered over rect2.
328
+ *
329
+ * @param {ObjRect} rect1 - The bounding rectangle of the reference element.
330
+ * @param {ObjRect} rect2 - The bounding rectangle of the element being compared.
331
+ * @returns {{
332
+ * x: number,
333
+ * y: number
334
+ * }} An object with the X and Y offset in pixels from rect1's center to rect2's center.
335
+ */
336
+ function getElsRelativeCenterOffset(rect1, rect2) {
337
+ const center1X = rect1.left + rect1.width / 2;
338
+ const center1Y = rect1.top + rect1.height / 2;
339
+
340
+ const center2X = rect2.left + rect2.width / 2;
341
+ const center2Y = rect2.top + rect2.height / 2;
342
+
343
+ return {
344
+ x: center2X - center1X,
345
+ y: center2Y - center1Y,
346
+ };
347
+ }
348
+
349
+ // Direction & Depth detection
350
+
351
+ /**
352
+ * Detects the direction of the dominant collision between two elements
353
+ * and calculates how deep the overlap is in both x and y axes.
354
+ *
355
+ * @param {ObjRect} rect1 - The bounding rectangle of the first element.
356
+ * @param {ObjRect} rect2 - The bounding rectangle of the second element.
357
+ * @returns {{
358
+ * inDir: Dirs | null;
359
+ * dirX: Dirs | null;
360
+ * dirY: Dirs | null;
361
+ * depthX: number;
362
+ * depthY: number;
363
+ * }} An object containing the collision direction and how deep the overlap is.
364
+ */
365
+ function getElsCollDirDepth(rect1, rect2) {
366
+ if (!areElsPerfColliding(rect1, rect2))
367
+ return {
368
+ inDir: null,
369
+ dirX: null,
370
+ dirY: null,
371
+ depthX: 0,
372
+ depthY: 0,
373
+ };
374
+
375
+ const { overlapLeft, overlapRight, overlapTop, overlapBottom } = getElsCollOverlap(rect1, rect2);
376
+ const { dirX, dirY } = getElsCollOverlapPos({
377
+ overlapLeft,
378
+ overlapRight,
379
+ overlapTop,
380
+ overlapBottom,
381
+ });
382
+ const depthX = Math.min(overlapLeft, overlapRight);
383
+ const depthY = Math.min(overlapTop, overlapBottom);
384
+
385
+ /** @type {Dirs} */
386
+ let inDir;
387
+
388
+ if (depthX < depthY) inDir = dirX;
389
+ else inDir = dirY;
390
+ return { inDir, dirX, dirY, depthX, depthY };
391
+ }
392
+
393
+ // Full detail report
394
+
395
+ /**
396
+ * Detects the collision direction and depth between two DOMRects.
397
+ *
398
+ * @param {ObjRect} rect1 - The bounding rectangle of the first element.
399
+ * @param {ObjRect} rect2 - The bounding rectangle of the second element.
400
+ * @returns {{ depth: CollData; dirs: CollDirs; isNeg: NegCollDirs; }} Collision info or null if no collision is detected.
401
+ */
402
+ function getElsCollDetails(rect1, rect2) {
403
+ const isColliding = areElsPerfColliding(rect1, rect2);
404
+
405
+ /** @type {CollDirs} */
406
+ const dirs = { in: null, x: null, y: null };
407
+
408
+ /** @type {NegCollDirs} */
409
+ const isNeg = { y: null, x: null };
410
+
411
+ /** @type {Record<Dirs, number>} */
412
+ const depth = { top: 0, bottom: 0, left: 0, right: 0 };
413
+
414
+ // Depth
415
+ // Yes, it's actually reversed the values orders
416
+ const { overlapLeft, overlapRight, overlapTop, overlapBottom } = getElsCollOverlap(rect2, rect1);
417
+ depth.top = overlapTop;
418
+ depth.bottom = overlapBottom;
419
+ depth.left = overlapLeft;
420
+ depth.right = overlapRight;
421
+
422
+ // Dirs
423
+ /**
424
+ * Detect the direction with the smallest positive overlap (entry point)
425
+ * @type {[Dirs, number][]}
426
+ */
427
+ // @ts-ignore
428
+ const entries = Object.entries(depth)
429
+ .filter(([, val]) => val > 0)
430
+ .sort((a, b) => a[1] - b[1]);
431
+
432
+ // Yes, it's actually reversed the values orders here too
433
+ const { dirX, dirY } = getElsCollOverlapPos({
434
+ overlapLeft: overlapRight,
435
+ overlapRight: overlapLeft,
436
+ overlapTop: overlapBottom,
437
+ overlapBottom: overlapTop,
438
+ });
439
+ dirs.y = dirY;
440
+ dirs.x = dirX;
441
+
442
+ // isNeg
443
+ if (depth.bottom < 0) isNeg.y = 'bottom';
444
+ else if (depth.top < 0) isNeg.y = 'top';
445
+ if (depth.left < 0) isNeg.x = 'left';
446
+ else if (depth.right < 0) isNeg.x = 'right';
447
+
448
+ // Inside Dir
449
+ dirs.in = isColliding
450
+ ? depth.top === depth.bottom && depth.bottom === depth.left && depth.left === depth.right
451
+ ? 'center'
452
+ : entries.length
453
+ ? entries[0][0]
454
+ : 'top'
455
+ : null; // fallback in case of exact match
456
+
457
+ // Complete
458
+ return { dirs, depth, isNeg };
459
+ }
460
+
461
+ ;// ./src/v1/libs/TinyHtml.mjs
462
+
463
+
464
+ const {
465
+ areElsColliding: TinyHtml_areElsColliding,
466
+ areElsPerfColliding: TinyHtml_areElsPerfColliding,
467
+ areElsCollTop: TinyHtml_areElsCollTop,
468
+ areElsCollBottom: TinyHtml_areElsCollBottom,
469
+ areElsCollLeft: TinyHtml_areElsCollLeft,
470
+ areElsCollRight: TinyHtml_areElsCollRight,
471
+ } = collision_namespaceObject;
472
+
473
+ /**
474
+ * Represents a raw Node element or an instance of TinyHtml.
475
+ * This type is used to abstract interactions with both plain elements
476
+ * and wrapped elements via the TinyHtml class.
477
+ *
478
+ * @typedef {Node|TinyHtml|null} TinyNode
479
+ */
480
+
481
+ /**
482
+ * Represents a raw DOM element or an instance of TinyHtml.
483
+ * This type is used to abstract interactions with both plain elements
484
+ * and wrapped elements via the TinyHtml class.
485
+ *
486
+ * @typedef {Element|TinyHtml} TinyElement
487
+ */
488
+
489
+ /**
490
+ * Represents a raw DOM html element or an instance of TinyHtml.
491
+ * This type is used to abstract interactions with both plain elements
492
+ * and wrapped elements via the TinyHtml class.
493
+ *
494
+ * @typedef {HTMLElement|TinyHtml} TinyHtmlElement
495
+ */
496
+
497
+ /**
498
+ * Represents a raw DOM event target element or an instance of TinyHtml.
499
+ * This type is used to abstract interactions with both plain elements
500
+ * and wrapped elements via the TinyHtml class.
501
+ *
502
+ * @typedef {EventTarget|TinyHtml} TinyEventTarget
503
+ */
504
+
505
+ /**
506
+ * Represents a raw DOM input element or an instance of TinyHtml.
507
+ * This type is used to abstract interactions with both plain elements
508
+ * and wrapped elements via the TinyHtml class.
509
+ *
510
+ * @typedef {InputElement|TinyHtml} TinyInputElement
511
+ */
512
+
513
+ /**
514
+ * Represents a raw DOM element/window or an instance of TinyHtml.
515
+ * This type is used to abstract interactions with both plain elements
516
+ * and wrapped elements via the TinyHtml class.
517
+ *
518
+ * @typedef {ElementAndWindow|TinyHtml} TinyElementAndWindow
519
+ */
520
+
521
+ /**
522
+ * Represents a value that can be either a DOM Element or the global Window object.
523
+ * Useful for functions that operate generically on scrollable or measurable targets.
524
+ *
525
+ * @typedef {Element|Window} ElementAndWindow
526
+ */
527
+
528
+ /**
529
+ * A parameter type used for filtering or matching elements.
530
+ * It can be:
531
+ * - A string (CSS selector),
532
+ * - A raw DOM element,
533
+ * - An array of raw DOM elements,
534
+ * - A filtering function that receives an index and element,
535
+ * and returns true if it matches.
536
+ *
537
+ * @typedef {string|Element|Element[]|((index: number, el: Element) => boolean)} WinnowRequest
538
+ */
539
+
540
+ /**
541
+ * Elements accepted as constructor values for TinyHtml.
542
+ * These include common DOM elements and root containers.
543
+ *
544
+ * @typedef {Window|Element|Document} ConstructorElValues
545
+ */
546
+
547
+ /**
548
+ * The handler function used in event listeners.
549
+ *
550
+ * @typedef {(e: Event) => any} EventRegistryHandle
551
+ */
552
+
553
+ /**
554
+ * Options passed to `addEventListener` or `removeEventListener`.
555
+ * Can be a boolean or an object of type `AddEventListenerOptions`.
556
+ *
557
+ * @typedef {boolean|AddEventListenerOptions} EventRegistryOptions
558
+ */
559
+
560
+ /**
561
+ * Structure describing a registered event callback and its options.
562
+ *
563
+ * @typedef {Object} EventRegistryItem
564
+ * @property {EventRegistryHandle} handler - The function to be executed when the event is triggered.
565
+ * @property {EventRegistryOptions} [options] - Optional configuration passed to the listener.
566
+ */
567
+
568
+ /**
569
+ * Maps event names (e.g., `"click"`, `"keydown"`) to a list of registered handlers and options.
570
+ *
571
+ * @typedef {Record<string, EventRegistryItem[]>} EventRegistryList
572
+ */
573
+
574
+ /**
575
+ * WeakMap storing all event listeners per element.
576
+ * Each element has a registry mapping event names to their handler lists.
577
+ *
578
+ * @type {WeakMap<ConstructorElValues|EventTarget, EventRegistryList>}
579
+ */
580
+ const __eventRegistry = new WeakMap();
581
+
582
+ /**
583
+ * A key-value store associated with a specific DOM element.
584
+ * Keys are strings, and values can be of any type.
585
+ *
586
+ * @typedef {Record<string, *>} ElementDataStore
587
+ */
588
+
589
+ /**
590
+ * WeakMap to hold private data for elements
591
+ *
592
+ * @type {WeakMap<ConstructorElValues, ElementDataStore>}
593
+ */
594
+ const __elementDataMap = new WeakMap();
595
+
596
+ /**
597
+ * Stores directional collision locks separately.
598
+ * Each direction has its own WeakMap to allow independent locking.
599
+ *
600
+ * @type {{
601
+ * top: WeakMap<Element, true>,
602
+ * bottom: WeakMap<Element, true>,
603
+ * left: WeakMap<Element, true>,
604
+ * right: WeakMap<Element, true>
605
+ * }}
606
+ */
607
+ const __elemCollision = {
608
+ top: new WeakMap(),
609
+ bottom: new WeakMap(),
610
+ left: new WeakMap(),
611
+ right: new WeakMap(),
612
+ };
613
+
614
+ /**
615
+ * Possible directions from which a collision was detected and locked.
616
+ *
617
+ * @typedef {'top'|'bottom'|'left'|'right'} CollisionDirLock
618
+ */
619
+
620
+ /**
621
+ * @typedef {Object} HtmlElBoxSides
622
+ * @property {number} x - Total horizontal size (left + right)
623
+ * @property {number} y - Total vertical size (top + bottom)
624
+ * @property {number} left
625
+ * @property {number} right
626
+ * @property {number} top
627
+ * @property {number} bottom
628
+ */
629
+
630
+ /**
631
+ * @typedef {string | number | Date | boolean | null} SetValueBase - Primitive types accepted as input values.
632
+ */
633
+
634
+ /**
635
+ * @typedef {'string' | 'date' | 'number'} GetValueTypes
636
+ * Types of value extractors supported by TinyHtml._valTypes.
637
+ */
638
+
639
+ /**
640
+ * @typedef {SetValueBase|SetValueBase[]} SetValueList - A single value or an array of values to be assigned to the input element.
641
+ */
642
+
643
+ /**
644
+ * A list of HTML form elements that can have a `.value` property used by TinyHtml.
645
+ * Includes common input types used in forms.
646
+ *
647
+ * @typedef {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLOptionElement} InputElement
648
+ */
649
+
650
+ /**
651
+ * TinyHtml is a utility class that provides static and instance-level methods
652
+ * for precise dimension and position computations on HTML elements.
653
+ * It mimics some jQuery functionalities while using native browser APIs.
654
+ *
655
+ * Inspired by the jQuery project's open source implementations of element dimension
656
+ * and offset utilities. This class serves as a lightweight alternative using modern DOM APIs.
657
+ *
658
+ * @class
659
+ */
660
+ class TinyHtml {
661
+ /** @typedef {import('../basics/collision.mjs').ObjRect} ObjRect */
662
+
663
+ static Utils = { ...collision_namespaceObject };
664
+
665
+ /**
666
+ * Queries the document for the first element matching the CSS selector and wraps it in a TinyHtml instance.
667
+ *
668
+ * @param {string} selector - A valid CSS selector string.
669
+ * @returns {TinyHtml} A TinyHtml instance wrapping the matched element.
670
+ * @throws {Error} If no element matches the selector.
671
+ */
672
+ static query(selector) {
673
+ const newEl = document.querySelector(selector);
674
+ if (!newEl) throw new Error(`[TinyHtml] query(): No element found for selector "${selector}".`);
675
+ return new TinyHtml(newEl);
676
+ }
677
+
678
+ /**
679
+ * Queries the document for all elements matching the CSS selector and wraps them in TinyHtml instances.
680
+ *
681
+ * @param {string} selector - A valid CSS selector string.
682
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the matched elements.
683
+ */
684
+ static queryAll(selector) {
685
+ const newEls = document.querySelectorAll(selector);
686
+ return TinyHtml.toTinyElm([...newEls]);
687
+ }
688
+
689
+ /**
690
+ * Retrieves an element by its ID and wraps it in a TinyHtml instance.
691
+ *
692
+ * @param {string} selector - The ID of the element to retrieve.
693
+ * @returns {TinyHtml} A TinyHtml instance wrapping the found element.
694
+ * @throws {Error} If no element is found with the specified ID.
695
+ */
696
+ static getById(selector) {
697
+ const newEl = document.getElementById(selector);
698
+ if (!newEl) throw new Error(`[TinyHtml] getById(): No element found with ID "${selector}".`);
699
+ return new TinyHtml(newEl);
700
+ }
701
+
702
+ /**
703
+ * Retrieves all elements with the specified class name and wraps them in TinyHtml instances.
704
+ *
705
+ * @param {string} selector - The class name to search for.
706
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
707
+ */
708
+ static getByClassName(selector) {
709
+ const newEls = document.getElementsByClassName(selector);
710
+ return TinyHtml.toTinyElm([...newEls]);
711
+ }
712
+
713
+ /**
714
+ * Retrieves all elements with the specified name attribute and wraps them in TinyHtml instances.
715
+ *
716
+ * @param {string} selector - The name attribute to search for.
717
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
718
+ */
719
+ static getByName(selector) {
720
+ const newEls = document.getElementsByName(selector);
721
+ return TinyHtml.toTinyElm([...newEls]);
722
+ }
723
+
724
+ /**
725
+ * Retrieves all elements with the specified local tag name within the given namespace URI,
726
+ * and wraps them in TinyHtml instances.
727
+ *
728
+ * @param {string} localName - The local name (tag) of the elements to search for.
729
+ * @param {string|null} [namespaceURI='http://www.w3.org/1999/xhtml'] - The namespace URI to search within.
730
+ * @returns {TinyHtml[]} An array of TinyHtml instances wrapping the found elements.
731
+ */
732
+ static getByTagNameNS(localName, namespaceURI = 'http://www.w3.org/1999/xhtml') {
733
+ const newEls = document.getElementsByTagNameNS(namespaceURI, localName);
734
+ return TinyHtml.toTinyElm([...newEls]);
735
+ }
736
+
737
+ //////////////////////////////////////////////////////////////////
738
+
739
+ /**
740
+ * Returns the current target held by this instance.
741
+ *
742
+ * @returns {ConstructorElValues} - The instance's target element.
743
+ */
744
+ get() {
745
+ return this.#el;
746
+ }
747
+
748
+ /**
749
+ * Returns the current Element held by this instance.
750
+ *
751
+ * @param {string} where - The method name or context calling this.
752
+ * @returns {ConstructorElValues} - The instance's element.
753
+ * @readonly
754
+ */
755
+ _getElement(where) {
756
+ if (
757
+ !(this.#el instanceof Element) &&
758
+ !(this.#el instanceof Window) &&
759
+ !(this.#el instanceof Document)
760
+ )
761
+ throw new Error(`[TinyHtml] Invalid Element in ${where}().`);
762
+ return this.#el;
763
+ }
764
+
765
+ //////////////////////////////////////////////////////
766
+
767
+ /**
768
+ * @param {TinyElement|EventTarget|null|(TinyElement|EventTarget|null)[]} elems
769
+ * @param {string} where
770
+ * @param {any[]} TheTinyElements
771
+ * @param {string[]} elemName
772
+ * @returns {any[]}
773
+ * @readonly
774
+ */
775
+ static _preElemsTemplate(elems, where, TheTinyElements, elemName) {
776
+ /** @param {(TinyElement|EventTarget|null)[]} item */
777
+ const checkElement = (item) =>
778
+ item.map((elem) => {
779
+ const result = elem instanceof TinyHtml ? elem._getElement(where) : elem;
780
+ let allowed = false;
781
+ for (const TheTinyElement of TheTinyElements) {
782
+ if (result instanceof TheTinyElement) {
783
+ allowed = true;
784
+ break;
785
+ }
786
+ }
787
+ if (!allowed)
788
+ throw new Error(
789
+ `[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`,
790
+ );
791
+ return result;
792
+ });
793
+ if (!Array.isArray(elems)) return checkElement([elems]);
794
+ return checkElement(elems);
795
+ }
796
+
797
+ /**
798
+ * @param {TinyElement|EventTarget|null|(TinyElement|EventTarget|null)[]} elems
799
+ * @param {string} where
800
+ * @param {any[]} TheTinyElements
801
+ * @param {string[]} elemName
802
+ * @param {boolean} [canNull=false]
803
+ * @returns {any}
804
+ * @readonly
805
+ */
806
+ static _preElemTemplate(elems, where, TheTinyElements, elemName, canNull = false) {
807
+ /** @param {(TinyElement|EventTarget|null)[]} item */
808
+ const checkElement = (item) => {
809
+ const elem = item[0];
810
+ let result = elem instanceof TinyHtml ? elem._getElement(where) : elem;
811
+ let allowed = false;
812
+ for (const TheTinyElement of TheTinyElements) {
813
+ if (result instanceof TheTinyElement) {
814
+ allowed = true;
815
+ break;
816
+ }
817
+ }
818
+
819
+ if (canNull && (result === null || typeof result === 'undefined')) {
820
+ result = null;
821
+ allowed = true;
822
+ }
823
+
824
+ if (!allowed)
825
+ throw new Error(
826
+ `[TinyHtml] Invalid element of the list "${elemName.join(',')}" in ${where}().`,
827
+ );
828
+ return result;
829
+ };
830
+ if (!Array.isArray(elems)) return checkElement([elems]);
831
+ if (elems.length > 1)
832
+ throw new Error(
833
+ `[TinyHtml] Invalid element amount in ${where}() (Received ${elems.length}/1).`,
834
+ );
835
+ return checkElement(elems);
836
+ }
837
+
838
+ /**
839
+ * Ensures the input is returned as an array.
840
+ * Useful to normalize operations across multiple or single elements.
841
+ *
842
+ * @param {TinyElement|TinyElement[]} elems - A single element or array of elements.
843
+ * @param {string} where - The method or context name where validation is being called.
844
+ * @returns {Element[]} - Always returns an array of elements.
845
+ * @readonly
846
+ */
847
+ static _preElems(elems, where) {
848
+ return TinyHtml._preElemsTemplate(elems, where, [Element], ['Element']);
849
+ }
850
+
851
+ /**
852
+ * Ensures the input is returned as an single element.
853
+ * Useful to normalize operations across multiple or single elements.
854
+ *
855
+ * @param {TinyElement|TinyElement[]} elems - A single element or array of elements.
856
+ * @param {string} where - The method or context name where validation is being called.
857
+ * @returns {Element} - Always returns an single element.
858
+ * @readonly
859
+ */
860
+ static _preElem(elems, where) {
861
+ return TinyHtml._preElemTemplate(elems, where, [Element], ['Element']);
862
+ }
863
+
864
+ /**
865
+ * Ensures the input is returned as an array.
866
+ * Useful to normalize operations across multiple or single nodes.
867
+ *
868
+ * @param {TinyNode|TinyNode[]} elems - A single node or array of nodes.
869
+ * @param {string} where - The method or context name where validation is being called.
870
+ * @returns {Node[]} - Always returns an array of nodes.
871
+ * @readonly
872
+ */
873
+ static _preNodeElems(elems, where) {
874
+ return TinyHtml._preElemsTemplate(elems, where, [Node], ['Node']);
875
+ }
876
+
877
+ /**
878
+ * Ensures the input is returned as an single node.
879
+ * Useful to normalize operations across multiple or single nodes.
880
+ *
881
+ * @param {TinyNode|TinyNode[]} elems - A single node or array of nodes.
882
+ * @param {string} where - The method or context name where validation is being called.
883
+ * @returns {Node} - Always returns an single node.
884
+ * @readonly
885
+ */
886
+ static _preNodeElem(elems, where) {
887
+ return TinyHtml._preElemTemplate(elems, where, [Node], ['Node']);
888
+ }
889
+
890
+ /**
891
+ * Ensures the input is returned as an single node.
892
+ * Useful to normalize operations across multiple or single nodes.
893
+ *
894
+ * @param {TinyNode|TinyNode[]} elems - A single node or array of nodes.
895
+ * @param {string} where - The method or context name where validation is being called.
896
+ * @returns {Node|null} - Always returns an single node or null.
897
+ * @readonly
898
+ */
899
+ static _preNodeElemWithNull(elems, where) {
900
+ return TinyHtml._preElemTemplate(elems, where, [Node], ['Node'], true);
901
+ }
902
+
903
+ /**
904
+ * Ensures the input is returned as an array.
905
+ * Useful to normalize operations across multiple or single html elements.
906
+ *
907
+ * @param {TinyElement|TinyElement[]} elems - A single html element or array of html elements.
908
+ * @param {string} where - The method or context name where validation is being called.
909
+ * @returns {HTMLElement[]} - Always returns an array of html elements.
910
+ * @readonly
911
+ */
912
+ static _preHtmlElems(elems, where) {
913
+ return TinyHtml._preElemsTemplate(elems, where, [HTMLElement], ['HTMLElement']);
914
+ }
915
+
916
+ /**
917
+ * Ensures the input is returned as an single html element.
918
+ * Useful to normalize operations across multiple or single html elements.
919
+ *
920
+ * @param {TinyElement|TinyElement[]} elems - A single html element or array of html elements.
921
+ * @param {string} where - The method or context name where validation is being called.
922
+ * @returns {HTMLElement} - Always returns an single html element.
923
+ * @readonly
924
+ */
925
+ static _preHtmlElem(elems, where) {
926
+ return TinyHtml._preElemTemplate(elems, where, [HTMLElement], ['HTMLElement']);
927
+ }
928
+
929
+ /**
930
+ * Ensures the input is returned as an array.
931
+ * Useful to normalize operations across multiple or single event target elements.
932
+ *
933
+ * @param {TinyInputElement|TinyInputElement[]} elems - A single event target element or array of html elements.
934
+ * @param {string} where - The method or context name where validation is being called.
935
+ * @returns {InputElement[]} - Always returns an array of event target elements.
936
+ * @readonly
937
+ */
938
+ static _preInputElems(elems, where) {
939
+ return TinyHtml._preElemsTemplate(
940
+ elems,
941
+ where,
942
+ [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLOptionElement],
943
+ ['HTMLInputElement', 'HTMLSelectElement', 'HTMLTextAreaElement', 'HTMLOptionElement'],
944
+ );
945
+ }
946
+
947
+ /**
948
+ * Ensures the input is returned as an single event target element.
949
+ * Useful to normalize operations across multiple or single event target elements.
950
+ *
951
+ * @param {TinyInputElement|TinyInputElement[]} elems - A single event target element or array of html elements.
952
+ * @param {string} where - The method or context name where validation is being called.
953
+ * @returns {InputElement} - Always returns an single event target element.
954
+ * @readonly
955
+ */
956
+ static _preInputElem(elems, where) {
957
+ return TinyHtml._preElemTemplate(
958
+ elems,
959
+ where,
960
+ [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLOptionElement],
961
+ ['HTMLInputElement', 'HTMLSelectElement', 'HTMLTextAreaElement', 'HTMLOptionElement'],
962
+ );
963
+ }
964
+
965
+ /**
966
+ * Ensures the input is returned as an array.
967
+ * Useful to normalize operations across multiple or single event target elements.
968
+ *
969
+ * @param {TinyEventTarget|TinyEventTarget[]} elems - A single event target element or array of html elements.
970
+ * @param {string} where - The method or context name where validation is being called.
971
+ * @returns {EventTarget[]} - Always returns an array of event target elements.
972
+ * @readonly
973
+ */
974
+ static _preEventTargetElems(elems, where) {
975
+ return TinyHtml._preElemsTemplate(elems, where, [EventTarget], ['EventTarget']);
976
+ }
977
+
978
+ /**
979
+ * Ensures the input is returned as an single event target element.
980
+ * Useful to normalize operations across multiple or single event target elements.
981
+ *
982
+ * @param {TinyEventTarget|TinyEventTarget[]} elems - A single event target element or array of html elements.
983
+ * @param {string} where - The method or context name where validation is being called.
984
+ * @returns {EventTarget} - Always returns an single event target element.
985
+ * @readonly
986
+ */
987
+ static _preEventTargetElem(elems, where) {
988
+ return TinyHtml._preElemTemplate(elems, where, [EventTarget], ['EventTarget']);
989
+ }
990
+
991
+ /**
992
+ * Ensures the input is returned as an array.
993
+ * Useful to normalize operations across multiple or single element/window elements.
994
+ *
995
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} elems - A single element/window element or array of html elements.
996
+ * @param {string} where - The method or context name where validation is being called.
997
+ * @returns {ElementAndWindow[]} - Always returns an array of element/window elements.
998
+ * @readonly
999
+ */
1000
+ static _preElemsAndWindow(elems, where) {
1001
+ return TinyHtml._preElemsTemplate(elems, where, [Element, Window], ['Element', 'Window']);
1002
+ }
1003
+
1004
+ /**
1005
+ * Ensures the input is returned as an single element/window element.
1006
+ * Useful to normalize operations across multiple or single element/window elements.
1007
+ *
1008
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} elems - A single element/window element or array of html elements.
1009
+ * @param {string} where - The method or context name where validation is being called.
1010
+ * @returns {ElementAndWindow} - Always returns an single element/window element.
1011
+ * @readonly
1012
+ */
1013
+ static _preElemAndWindow(elems, where) {
1014
+ return TinyHtml._preElemTemplate(elems, where, [Element, Window], ['Element', 'Window']);
1015
+ }
1016
+
1017
+ /**
1018
+ * Normalizes and converts one or more DOM elements (or TinyHtml instances)
1019
+ * into an array of `TinyHtml` instances.
1020
+ *
1021
+ * - If a plain DOM element is passed, it is wrapped into a `TinyHtml` instance.
1022
+ * - If a `TinyHtml` instance is already passed, it is preserved.
1023
+ * - If an array is passed, all elements inside are converted accordingly.
1024
+ *
1025
+ * This ensures consistent access to methods of the `TinyHtml` class regardless
1026
+ * of the input form.
1027
+ *
1028
+ * @param {TinyElement|TinyElement[]} elems - A single element or an array of elements (DOM or TinyHtml).
1029
+ * @returns {TinyHtml[]} An array of TinyHtml instances corresponding to the input elements.
1030
+ */
1031
+ static toTinyElm(elems) {
1032
+ /** @param {TinyElement[]} item */
1033
+ const checkElement = (item) =>
1034
+ item.map((elem) => (!(elem instanceof TinyHtml) ? new TinyHtml(elem) : elem));
1035
+ if (!Array.isArray(elems)) return checkElement([elems]);
1036
+ return checkElement(elems);
1037
+ }
1038
+
1039
+ /**
1040
+ * Extracts native `Element` instances from one or more elements,
1041
+ * which can be either raw DOM elements or wrapped in `TinyHtml`.
1042
+ *
1043
+ * - If a `TinyHtml` instance is passed, its internal DOM element is extracted.
1044
+ * - If a raw DOM element is passed, it is returned as-is.
1045
+ * - If an array is passed, each element is processed accordingly.
1046
+ *
1047
+ * This function guarantees that the return value is always an array of
1048
+ * raw `Element` objects, regardless of whether the input was
1049
+ * a mix of `TinyHtml` or native DOM elements.
1050
+ *
1051
+ * @param {TinyElement|TinyElement[]} elems - A single element or an array of elements (DOM or TinyHtml`).
1052
+ * @returns {Element[]} An array of Element instances extracted from the input.
1053
+ */
1054
+ static fromTinyElm(elems) {
1055
+ /** @param {TinyElement[]} item */
1056
+ const checkElement = (item) =>
1057
+ item.map(
1058
+ (elem) =>
1059
+ /** @type {Element} */ (
1060
+ elem instanceof TinyHtml ? elem._getElement('fromTinyElm') : elem
1061
+ ),
1062
+ );
1063
+ if (!Array.isArray(elems)) return checkElement([elems]);
1064
+ return checkElement(elems);
1065
+ }
1066
+
1067
+ /**
1068
+ * Filters an array of elements based on a selector, function, element, or array of elements.
1069
+ *
1070
+ * @param {TinyElement|TinyElement[]} elems
1071
+ * @param {WinnowRequest} qualifier
1072
+ * @param {string} where - The context/method name using this validation.
1073
+ * @param {boolean} not Whether to invert the result (used for .not())
1074
+ * @returns {Element[]}
1075
+ */
1076
+ static winnow(elems, qualifier, where, not = false) {
1077
+ if (typeof not !== 'boolean') throw new TypeError('The "not" must be a boolean.');
1078
+ if (typeof qualifier === 'function') {
1079
+ return TinyHtml._preElems(elems, where).filter(
1080
+ (el, i) => !!qualifier.call(el, i, el) !== not,
1081
+ );
1082
+ }
1083
+
1084
+ if (qualifier instanceof Element) {
1085
+ return TinyHtml._preElems(elems, where).filter((el) => (el === qualifier) !== not);
1086
+ }
1087
+
1088
+ if (
1089
+ Array.isArray(qualifier) ||
1090
+ (typeof qualifier !== 'string' &&
1091
+ // @ts-ignore
1092
+ qualifier.length != null)
1093
+ ) {
1094
+ return TinyHtml._preElems(elems, where).filter((el) => qualifier.includes(el) !== not);
1095
+ }
1096
+
1097
+ // Assume it's a selector string
1098
+ let selector = qualifier;
1099
+ if (not) selector = `:not(${selector})`;
1100
+ return TinyHtml._preElems(elems, where).filter(
1101
+ (el) => el.nodeType === 1 && el.matches(selector),
1102
+ );
1103
+ }
1104
+
1105
+ /**
1106
+ * Filters a set of elements by a CSS selector.
1107
+ *
1108
+ * @param {TinyElement|TinyElement[]} elems
1109
+ * @param {string} selector
1110
+ * @param {boolean} not
1111
+ * @returns {Element[]}
1112
+ */
1113
+ static filter(elems, selector, not = false) {
1114
+ if (not) selector = `:not(${selector})`;
1115
+ return TinyHtml._preElems(elems, 'filter').filter(
1116
+ (el) => el.nodeType === 1 && el.matches(selector),
1117
+ );
1118
+ }
1119
+
1120
+ /**
1121
+ * Returns only the elements matching the given selector or function.
1122
+ *
1123
+ * @param {TinyElement|TinyElement[]} elems
1124
+ * @param {WinnowRequest} selector
1125
+ * @returns {Element[]}
1126
+ */
1127
+ static filterOnly(elems, selector) {
1128
+ return TinyHtml.winnow(elems, selector, 'filterOnly', false);
1129
+ }
1130
+
1131
+ /**
1132
+ * Returns only the elements **not** matching the given selector or function.
1133
+ *
1134
+ * @param {TinyElement|TinyElement[]} elems
1135
+ * @param {WinnowRequest} selector
1136
+ * @returns {Element[]}
1137
+ */
1138
+ static not(elems, selector) {
1139
+ return TinyHtml.winnow(elems, selector, 'not', true);
1140
+ }
1141
+
1142
+ /**
1143
+ * Returns only the elements **not** matching the given selector or function.
1144
+ *
1145
+ * @param {WinnowRequest} selector
1146
+ * @returns {Element[]}
1147
+ */
1148
+ not(selector) {
1149
+ return TinyHtml.not(this, selector);
1150
+ }
1151
+
1152
+ /**
1153
+ * Finds elements matching a selector within a context.
1154
+ *
1155
+ * @param {TinyElement|TinyElement[]} context
1156
+ * @param {string} selector
1157
+ * @returns {Element[]}
1158
+ */
1159
+ static find(context, selector) {
1160
+ const result = [];
1161
+ for (const el of TinyHtml._preElems(context, 'find')) {
1162
+ result.push(...el.querySelectorAll(selector));
1163
+ }
1164
+ return [...new Set(result)];
1165
+ }
1166
+
1167
+ /**
1168
+ * Finds elements in your element matching a selector within a context.
1169
+ *
1170
+ * @param {string} selector
1171
+ * @returns {Element[]}
1172
+ */
1173
+ find(selector) {
1174
+ return TinyHtml.find(this, selector);
1175
+ }
1176
+
1177
+ /**
1178
+ * Checks if at least one element matches the selector.
1179
+ *
1180
+ * @param {TinyElement|TinyElement[]} elems
1181
+ * @param {WinnowRequest} selector
1182
+ * @returns {boolean}
1183
+ */
1184
+ static is(elems, selector) {
1185
+ return TinyHtml.winnow(elems, selector, 'is', false).length > 0;
1186
+ }
1187
+
1188
+ /**
1189
+ * Checks if the element matches the selector.
1190
+ *
1191
+ * @param {WinnowRequest} selector
1192
+ * @returns {boolean}
1193
+ */
1194
+ is(selector) {
1195
+ return TinyHtml.is(this, selector);
1196
+ }
1197
+
1198
+ /**
1199
+ * Returns elements from the current list that contain the given target(s).
1200
+ * @param {TinyElement|TinyElement[]} roots - A single element or an array of elements (DOM or TinyHtml).
1201
+ * @param {string|TinyElement|TinyElement[]} target - Selector or DOM element(s).
1202
+ * @returns {Element[]} Elements that contain the target.
1203
+ */
1204
+ static has(roots, target) {
1205
+ const targets =
1206
+ typeof target === 'string'
1207
+ ? [...document.querySelectorAll(target)]
1208
+ : TinyHtml._preElems(target, 'has');
1209
+
1210
+ return TinyHtml._preElems(roots, 'has').filter((root) =>
1211
+ targets.some((t) => root && root.contains(t)),
1212
+ );
1213
+ }
1214
+
1215
+ /**
1216
+ * Return if the element has the target(s).
1217
+ * @param {string|TinyElement|TinyElement[]} target - Selector or DOM element(s).
1218
+ * @returns {boolean} Elements that contain the target.
1219
+ */
1220
+ has(target) {
1221
+ return TinyHtml.has(this, target).length > 0;
1222
+ }
1223
+
1224
+ /**
1225
+ * Finds the closest ancestor (including self) that matches the selector.
1226
+ *
1227
+ * @param {TinyElement|TinyElement[]} els - A single element or an array of elements (DOM or TinyHtml).
1228
+ * @param {string|Element} selector - A selector string or DOM element to match.
1229
+ * @param {Element|null} [context] - An optional context to stop searching.
1230
+ * @returns {Element[]}
1231
+ */
1232
+ static closest(els, selector, context) {
1233
+ const matched = [];
1234
+
1235
+ for (const el of TinyHtml._preElems(els, 'closest')) {
1236
+ /** @type {Element | null} */
1237
+ let current = el;
1238
+ while (current && current !== context) {
1239
+ if (
1240
+ current.nodeType === 1 &&
1241
+ (typeof selector === 'string' ? current.matches(selector) : current === selector)
1242
+ ) {
1243
+ matched.push(current);
1244
+ break;
1245
+ }
1246
+ current = current.parentElement;
1247
+ }
1248
+ }
1249
+
1250
+ return [...new Set(matched)];
1251
+ }
1252
+
1253
+ /**
1254
+ * Finds the closest ancestor (including self) that matches the selector.
1255
+ *
1256
+ * @param {string|Element} selector - A selector string or DOM element to match.
1257
+ * @param {Element|null} [context] - An optional context to stop searching.
1258
+ * @returns {Element[]}
1259
+ */
1260
+ closest(selector, context) {
1261
+ return TinyHtml.closest(this, selector, context);
1262
+ }
1263
+
1264
+ /**
1265
+ * Compares two DOM elements to determine if they refer to the same node in the document.
1266
+ *
1267
+ * This performs a strict equality check (`===`) between the two elements.
1268
+ *
1269
+ * @param {TinyNode} elem - The first DOM element to compare.
1270
+ * @param {TinyNode} otherElem - The second DOM element to compare.
1271
+ * @returns {boolean} `true` if both elements are the same DOM node; otherwise, `false`.
1272
+ */
1273
+ static isSameDom(elem, otherElem) {
1274
+ return (
1275
+ TinyHtml._preNodeElem(elem, 'isSameDom') === TinyHtml._preNodeElem(otherElem, 'isSameDom')
1276
+ );
1277
+ }
1278
+
1279
+ /**
1280
+ * Compares two DOM elements to determine if they refer to the same node in the document.
1281
+ *
1282
+ * This performs a strict equality check (`===`) between the two elements.
1283
+ *
1284
+ * @param {TinyNode} elem - The DOM element to compare.
1285
+ * @returns {boolean} `true` if both elements are the same DOM node; otherwise, `false`.
1286
+ */
1287
+ isSameDom(elem) {
1288
+ return TinyHtml.isSameDom(this, elem);
1289
+ }
1290
+
1291
+ //////////////////////////////////////////////////////////////////
1292
+
1293
+ /** @type {ElementDataStore} */
1294
+ _data = {};
1295
+
1296
+ /**
1297
+ * Internal data selectors for accessing public or private data stores.
1298
+ *
1299
+ * @type {Record<string, (where: string, elem: TinyElement) => ElementDataStore>}
1300
+ * @readonly
1301
+ */
1302
+ static _dataSelector = {
1303
+ public: (where, el) => {
1304
+ const elem = TinyHtml._preElem(el, where);
1305
+ let data = __elementDataMap.get(elem);
1306
+ if (!data) {
1307
+ data = {};
1308
+ __elementDataMap.set(elem, data);
1309
+ }
1310
+ return data;
1311
+ },
1312
+ private: (where, el) => {
1313
+ if (!(el instanceof TinyHtml))
1314
+ throw new Error(`Element must be a TinyHtml instance to execute ${where}().`);
1315
+ return el._data;
1316
+ },
1317
+ };
1318
+
1319
+ /**
1320
+ * Retrieves data associated with a DOM element.
1321
+ *
1322
+ * If a `key` is provided, the corresponding value is returned.
1323
+ * If no `key` is given, a shallow copy of all stored data is returned.
1324
+ *
1325
+ * @param {TinyElement} el - The DOM element.
1326
+ * @param {string|null} [key] - The specific key to retrieve from the data store.
1327
+ * @param {boolean} [isPrivate=false] - Whether to access the private data store.
1328
+ * @returns {ElementDataStore|undefined|any} - The stored value, all data, or undefined if the key doesn't exist.
1329
+ */
1330
+ static data(el, key, isPrivate = false) {
1331
+ // Get or initialize the data object
1332
+ const data = TinyHtml._dataSelector[!isPrivate ? 'public' : 'private']('data', el);
1333
+
1334
+ // Getter for all
1335
+ if (key === undefined || key === null) return { ...data };
1336
+
1337
+ // Getter for specific key
1338
+ if (typeof key !== 'string') throw new TypeError('The key must be a string.');
1339
+ return data.hasOwnProperty(key) ? data[key] : undefined;
1340
+ }
1341
+
1342
+ /**
1343
+ * Retrieves data associated with a DOM element.
1344
+ *
1345
+ * If a `key` is provided, the corresponding value is returned.
1346
+ * If no `key` is given, a shallow copy of all stored data is returned.
1347
+ *
1348
+ * @param {string} [key] - The specific key to retrieve from the data store.
1349
+ * @param {boolean} [isPrivate=false] - Whether to access the private data store.
1350
+ * @returns {ElementDataStore|undefined|any} - The stored value, all data, or undefined if the key doesn't exist.
1351
+ */
1352
+ data(key, isPrivate) {
1353
+ return TinyHtml.data(this, key, isPrivate);
1354
+ }
1355
+
1356
+ /**
1357
+ * Stores a value associated with a specific key for a DOM element.
1358
+ *
1359
+ * @param {TinyElement} el - The DOM element.
1360
+ * @param {string} key - The key under which the data will be stored.
1361
+ * @param {any} value - The value to store.
1362
+ * @param {boolean} [isPrivate=false] - Whether to store the data in the private store.
1363
+ * @returns {void}
1364
+ */
1365
+ static setData(el, key, value, isPrivate = false) {
1366
+ const data = TinyHtml._dataSelector[!isPrivate ? 'public' : 'private']('setData', el);
1367
+ if (typeof key !== 'string') throw new TypeError('The key must be a string.');
1368
+ data[key] = value;
1369
+ }
1370
+
1371
+ /**
1372
+ * Stores a value associated with a specific key for a DOM element.
1373
+ *
1374
+ * @param {string} key - The key under which the data will be stored.
1375
+ * @param {any} value - The value to store.
1376
+ * @param {boolean} [isPrivate=false] - Whether to store the data in the private store.
1377
+ * @returns {void}
1378
+ */
1379
+ setData(key, value, isPrivate = false) {
1380
+ return TinyHtml.setData(this, key, value, isPrivate);
1381
+ }
1382
+
1383
+ //////////////////////////////////////////////////////
1384
+
1385
+ /**
1386
+ * Get the sibling element in a given direction.
1387
+ *
1388
+ * @param {TinyNode} el
1389
+ * @param {"previousSibling"|"nextSibling"} direction
1390
+ * @param {string} where
1391
+ * @returns {ChildNode|null}
1392
+ * @readonly
1393
+ */
1394
+ static _getSibling(el, direction, where) {
1395
+ /** @type {Node|null} */
1396
+ let newCurrent = TinyHtml._preNodeElemWithNull(el, where);
1397
+ while (newCurrent && (newCurrent = newCurrent[direction]) && newCurrent.nodeType !== 1) {}
1398
+ if (!(newCurrent instanceof Node)) return null;
1399
+ return /** @type {ChildNode} */ (newCurrent);
1400
+ }
1401
+
1402
+ /**
1403
+ * Get all sibling elements excluding the given one.
1404
+ *
1405
+ * @param {Node|null} start
1406
+ * @param {Node|null} [exclude]
1407
+ * @returns {ChildNode[]}
1408
+ * @readonly
1409
+ */
1410
+ static _getSiblings(start, exclude) {
1411
+ /** @type {Node|null} */
1412
+ let st = start;
1413
+ const siblings = [];
1414
+ for (; st; st = st.nextSibling) {
1415
+ if (st.nodeType === 1 && st !== exclude) {
1416
+ siblings.push(st);
1417
+ }
1418
+ }
1419
+ return /** @type {ChildNode[]} */ (siblings);
1420
+ }
1421
+
1422
+ /**
1423
+ * Traverse DOM in a direction collecting elements.
1424
+ *
1425
+ * @param {TinyNode} el
1426
+ * @param {"parentNode"|"nextSibling"|"previousSibling"} direction
1427
+ * @param {TinyNode|string} [until]
1428
+ * @param {string} [where='domDir']
1429
+ * @returns {ChildNode[]}
1430
+ */
1431
+ static domDir(el, direction, until, where = 'domDir') {
1432
+ if (typeof direction !== 'string') throw new TypeError('The "direction" must be a string.');
1433
+ let elem = TinyHtml._preNodeElemWithNull(el, where);
1434
+ const matched = [];
1435
+ // @ts-ignore
1436
+ while (elem && (elem = elem[direction])) {
1437
+ if (elem.nodeType !== 1) continue;
1438
+ if (
1439
+ until &&
1440
+ (typeof until === 'string'
1441
+ ? // @ts-ignore
1442
+ elem.matches(until)
1443
+ : elem === until)
1444
+ )
1445
+ break;
1446
+ matched.push(elem);
1447
+ }
1448
+ return /** @type {ChildNode[]} */ (matched);
1449
+ }
1450
+
1451
+ /**
1452
+ * Returns the direct parent node of the given element, excluding document fragments.
1453
+ *
1454
+ * @param {TinyNode} el - The DOM node to find the parent of.
1455
+ * @returns {ParentNode|null} The parent node or null if not found.
1456
+ */
1457
+ static parent(el) {
1458
+ let elem = TinyHtml._preNodeElemWithNull(el, 'parent');
1459
+ const parent = elem ? elem.parentNode : null;
1460
+ return parent && parent.nodeType !== 11 ? parent : null;
1461
+ }
1462
+
1463
+ /**
1464
+ * Returns the direct parent node of the given element, excluding document fragments.
1465
+ *
1466
+ * @returns {ParentNode|null} The parent node or null if not found.
1467
+ */
1468
+ parent() {
1469
+ return TinyHtml.parent(this);
1470
+ }
1471
+
1472
+ /**
1473
+ * Returns all ancestor nodes of the given element, optionally stopping before a specific ancestor.
1474
+ *
1475
+ * @param {TinyNode} el - The DOM node to start from.
1476
+ * @param {TinyNode|string} [until] - A node or selector to stop before.
1477
+ * @returns {ChildNode[]} An array of ancestor nodes.
1478
+ */
1479
+ static parents(el, until) {
1480
+ return TinyHtml.domDir(el, 'parentNode', until, 'parents');
1481
+ }
1482
+
1483
+ /**
1484
+ * Returns all ancestor nodes of the given element, optionally stopping before a specific ancestor.
1485
+ *
1486
+ * @param {TinyNode|string} [until] - A node or selector to stop before.
1487
+ * @returns {ChildNode[]} An array of ancestor nodes.
1488
+ */
1489
+ parents(until) {
1490
+ return TinyHtml.parents(this, until);
1491
+ }
1492
+
1493
+ /**
1494
+ * Returns the next sibling of the given element.
1495
+ *
1496
+ * @param {TinyNode} el - The DOM node to start from.
1497
+ * @returns {ChildNode|null} The next sibling or null if none found.
1498
+ */
1499
+ static next(el) {
1500
+ return TinyHtml._getSibling(el, 'nextSibling', 'next');
1501
+ }
1502
+
1503
+ /**
1504
+ * Returns the next sibling of the given element.
1505
+ *
1506
+ * @returns {ChildNode|null} The next sibling or null if none found.
1507
+ */
1508
+ next() {
1509
+ return TinyHtml.next(this);
1510
+ }
1511
+
1512
+ /**
1513
+ * Returns the previous sibling of the given element.
1514
+ *
1515
+ * @param {TinyNode} el - The DOM node to start from.
1516
+ * @returns {ChildNode|null} The previous sibling or null if none found.
1517
+ */
1518
+ static prev(el) {
1519
+ return TinyHtml._getSibling(el, 'previousSibling', 'prev');
1520
+ }
1521
+
1522
+ /**
1523
+ * Returns the previous sibling of the given element.
1524
+ *
1525
+ * @returns {ChildNode|null} The previous sibling or null if none found.
1526
+ */
1527
+ prev() {
1528
+ return TinyHtml.prev(this);
1529
+ }
1530
+
1531
+ /**
1532
+ * Returns all next sibling nodes after the given element.
1533
+ *
1534
+ * @param {TinyNode} el - The DOM node to start from.
1535
+ * @returns {ChildNode[]} An array of next sibling nodes.
1536
+ */
1537
+ static nextAll(el) {
1538
+ return TinyHtml.domDir(el, 'nextSibling', undefined, 'nextAll');
1539
+ }
1540
+
1541
+ /**
1542
+ * Returns all next sibling nodes after the given element.
1543
+ *
1544
+ * @returns {ChildNode[]} An array of next sibling nodes.
1545
+ */
1546
+ nextAll() {
1547
+ return TinyHtml.nextAll(this);
1548
+ }
1549
+
1550
+ /**
1551
+ * Returns all previous sibling nodes before the given element.
1552
+ *
1553
+ * @param {TinyNode} el - The DOM node to start from.
1554
+ * @returns {ChildNode[]} An array of previous sibling nodes.
1555
+ */
1556
+ static prevAll(el) {
1557
+ return TinyHtml.domDir(el, 'previousSibling', undefined, 'prevAll');
1558
+ }
1559
+
1560
+ /**
1561
+ * Returns all previous sibling nodes before the given element.
1562
+ *
1563
+ * @returns {ChildNode[]} An array of previous sibling nodes.
1564
+ */
1565
+ prevAll() {
1566
+ return TinyHtml.prevAll(this);
1567
+ }
1568
+
1569
+ /**
1570
+ * Returns all next sibling nodes up to (but not including) the node matched by a selector or element.
1571
+ *
1572
+ * @param {TinyNode} el - The DOM node to start from.
1573
+ * @param {TinyNode|string} [until] - A node or selector to stop before.
1574
+ * @returns {ChildNode[]} An array of next sibling nodes.
1575
+ */
1576
+ static nextUntil(el, until) {
1577
+ return TinyHtml.domDir(el, 'nextSibling', until, 'nextUtil');
1578
+ }
1579
+
1580
+ /**
1581
+ * Returns all next sibling nodes up to (but not including) the node matched by a selector or element.
1582
+ *
1583
+ * @param {TinyNode|string} [until] - A node or selector to stop before.
1584
+ * @returns {ChildNode[]} An array of next sibling nodes.
1585
+ */
1586
+ nextUntil(until) {
1587
+ return TinyHtml.nextUntil(this, until);
1588
+ }
1589
+
1590
+ /**
1591
+ * Returns all previous sibling nodes up to (but not including) the node matched by a selector or element.
1592
+ *
1593
+ * @param {TinyNode} el - The DOM node to start from.
1594
+ * @param {TinyNode|string} [until] - A node or selector to stop before.
1595
+ * @returns {ChildNode[]} An array of previous sibling nodes.
1596
+ */
1597
+ static prevUntil(el, until) {
1598
+ return TinyHtml.domDir(el, 'previousSibling', until, 'prevUtil');
1599
+ }
1600
+
1601
+ /**
1602
+ * Returns all previous sibling nodes up to (but not including) the node matched by a selector or element.
1603
+ *
1604
+ * @param {TinyNode|string} [until] - A node or selector to stop before.
1605
+ * @returns {ChildNode[]} An array of previous sibling nodes.
1606
+ */
1607
+ prevUntil(until) {
1608
+ return TinyHtml.prevUntil(this, until);
1609
+ }
1610
+
1611
+ /**
1612
+ * Returns all sibling nodes of the given element, excluding itself.
1613
+ *
1614
+ * @param {TinyNode} el - The DOM node to find siblings of.
1615
+ * @returns {ChildNode[]} An array of sibling nodes.
1616
+ */
1617
+ static siblings(el) {
1618
+ const elem = TinyHtml._preNodeElemWithNull(el, 'siblings');
1619
+ return TinyHtml._getSiblings(elem && elem.parentNode ? elem.parentNode.firstChild : null, elem);
1620
+ }
1621
+
1622
+ /**
1623
+ * Returns all sibling nodes of the given element, excluding itself.
1624
+ *
1625
+ * @returns {ChildNode[]} An array of sibling nodes.
1626
+ */
1627
+ siblings() {
1628
+ return TinyHtml.siblings(this);
1629
+ }
1630
+
1631
+ /**
1632
+ * Returns all child nodes of the given element.
1633
+ *
1634
+ * @param {TinyNode} el - The DOM node to get children from.
1635
+ * @returns {ChildNode[]} An array of child nodes.
1636
+ */
1637
+ static children(el) {
1638
+ const elem = TinyHtml._preNodeElemWithNull(el, 'children');
1639
+ return TinyHtml._getSiblings(elem ? elem.firstChild : null);
1640
+ }
1641
+
1642
+ /**
1643
+ * Returns all child nodes of the given element.
1644
+ *
1645
+ * @returns {ChildNode[]} An array of child nodes.
1646
+ */
1647
+ children() {
1648
+ return TinyHtml.children(this);
1649
+ }
1650
+
1651
+ /**
1652
+ * Returns the contents of the given node. For `<template>` it returns its content; for `<iframe>`, the document.
1653
+ *
1654
+ * @param {TinyNode} el - The DOM node to get contents from.
1655
+ * @returns {(ChildNode|DocumentFragment)[]|Document[]} An array of child nodes or the content document of an iframe.
1656
+ */
1657
+ static contents(el) {
1658
+ const elem = TinyHtml._preNodeElemWithNull(el, 'contents');
1659
+ if (
1660
+ elem instanceof HTMLIFrameElement &&
1661
+ elem.contentDocument != null &&
1662
+ Object.getPrototypeOf(elem.contentDocument)
1663
+ ) {
1664
+ return [elem.contentDocument];
1665
+ }
1666
+
1667
+ if (elem instanceof HTMLTemplateElement) {
1668
+ return Array.from((elem.content || elem).childNodes);
1669
+ }
1670
+
1671
+ if (elem) return Array.from(elem.childNodes);
1672
+ return [];
1673
+ }
1674
+
1675
+ /**
1676
+ * Returns the contents of the given node. For `<template>` it returns its content; for `<iframe>`, the document.
1677
+ *
1678
+ * @returns {(ChildNode|DocumentFragment)[]|Document[]} An array of child nodes or the content document of an iframe.
1679
+ */
1680
+ contents() {
1681
+ return TinyHtml.contents(this);
1682
+ }
1683
+
1684
+ /**
1685
+ * Clone each element.
1686
+ * @param {TinyNode|TinyNode[]} el
1687
+ * @param {boolean} [deep=true]
1688
+ * @returns {Node[]}
1689
+ */
1690
+ static clone(el, deep = true) {
1691
+ if (typeof deep !== 'boolean') throw new TypeError('The "deep" must be a boolean.');
1692
+ return TinyHtml._preNodeElems(el, 'clone').map((el) => el.cloneNode(deep));
1693
+ }
1694
+
1695
+ /**
1696
+ * Clone the element.
1697
+ * @param {boolean} [deep=true]
1698
+ * @returns {Node}
1699
+ */
1700
+ clone(deep) {
1701
+ return TinyHtml.clone(this, deep)[0];
1702
+ }
1703
+
1704
+ /**
1705
+ * Normalize and validate nodes before DOM insertion.
1706
+ * Converts TinyNode-like structures or strings into DOM-compatible nodes.
1707
+ * @type {(where: string, ...nodes: (TinyNode | TinyNode[] | string)[]) => (Node | string)[]}
1708
+ * @readonly
1709
+ */
1710
+ static _appendChecker(where, ...nodes) {
1711
+ const results = [];
1712
+ const nds = [...nodes];
1713
+ for (const index in nds) {
1714
+ if (typeof nds[index] !== 'string') {
1715
+ results.push(TinyHtml._preNodeElem(nds[index], where));
1716
+ } else results.push(nds[index]);
1717
+ }
1718
+ return results;
1719
+ }
1720
+
1721
+ /**
1722
+ * Appends child elements or strings to the end of the target element(s).
1723
+ *
1724
+ * @param {TinyElement | TinyElement[]} el - The target element(s) to receive children.
1725
+ * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to append.
1726
+ */
1727
+ static append(el, ...children) {
1728
+ const elem = TinyHtml._preElem(el, 'append');
1729
+ elem.append(...TinyHtml._appendChecker('append', ...children));
1730
+ }
1731
+
1732
+ /**
1733
+ * Appends child elements or strings to the end of the target element(s).
1734
+ *
1735
+ * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to append.
1736
+ */
1737
+ append(...children) {
1738
+ return TinyHtml.append(this, ...children);
1739
+ }
1740
+
1741
+ /**
1742
+ * Prepends child elements or strings to the beginning of the target element(s).
1743
+ *
1744
+ * @param {TinyElement | TinyElement[]} el - The target element(s) to receive children.
1745
+ * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to prepend.
1746
+ */
1747
+ static prepend(el, ...children) {
1748
+ const elem = TinyHtml._preElem(el, 'prepend');
1749
+ elem.prepend(...TinyHtml._appendChecker('prepend', ...children));
1750
+ }
1751
+
1752
+ /**
1753
+ * Prepends child elements or strings to the beginning of the target element(s).
1754
+ *
1755
+ * @param {...(TinyNode | TinyNode[] | string)} children - The child elements or text to prepend.
1756
+ */
1757
+ prepend(...children) {
1758
+ return TinyHtml.prepend(this, ...children);
1759
+ }
1760
+
1761
+ /**
1762
+ * Inserts elements or strings immediately before the target element(s) in the DOM.
1763
+ *
1764
+ * @param {TinyElement | TinyElement[]} el - The target element(s) before which new content is inserted.
1765
+ * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert before the target.
1766
+ */
1767
+ static before(el, ...children) {
1768
+ const elem = TinyHtml._preElem(el, 'before');
1769
+ elem.before(...TinyHtml._appendChecker('before', ...children));
1770
+ }
1771
+
1772
+ /**
1773
+ * Inserts elements or strings immediately before the target element(s) in the DOM.
1774
+ *
1775
+ * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert before the target.
1776
+ */
1777
+ before(...children) {
1778
+ return TinyHtml.before(this, ...children);
1779
+ }
1780
+
1781
+ /**
1782
+ * Inserts elements or strings immediately after the target element(s) in the DOM.
1783
+ *
1784
+ * @param {TinyElement | TinyElement[]} el - The target element(s) after which new content is inserted.
1785
+ * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert after the target.
1786
+ */
1787
+ static after(el, ...children) {
1788
+ const elem = TinyHtml._preElem(el, 'after');
1789
+ elem.after(...TinyHtml._appendChecker('after', ...children));
1790
+ }
1791
+
1792
+ /**
1793
+ * Inserts elements or strings immediately after the target element(s) in the DOM.
1794
+ *
1795
+ * @param {...(TinyNode | TinyNode[] | string)} children - Elements or text to insert after the target.
1796
+ */
1797
+ after(...children) {
1798
+ return TinyHtml.after(this, ...children);
1799
+ }
1800
+
1801
+ /**
1802
+ * Replaces the target element(s) in the DOM with new elements or text.
1803
+ *
1804
+ * @param {TinyElement | TinyElement[]} el - The element(s) to be replaced.
1805
+ * @param {...(TinyNode | TinyNode[] | string)} newNodes - New elements or text to replace the target.
1806
+ */
1807
+ static replaceWith(el, ...newNodes) {
1808
+ const elem = TinyHtml._preElem(el, 'replaceWith');
1809
+ elem.replaceWith(...TinyHtml._appendChecker('replaceWith', ...newNodes));
1810
+ }
1811
+
1812
+ /**
1813
+ * Replaces the target element(s) in the DOM with new elements or text.
1814
+ *
1815
+ * @param {...(TinyNode | TinyNode[] | string)} newNodes - New elements or text to replace the target.
1816
+ */
1817
+ replaceWith(...newNodes) {
1818
+ return TinyHtml.replaceWith(this, ...newNodes);
1819
+ }
1820
+
1821
+ /**
1822
+ * Appends the given element(s) to each target element in sequence.
1823
+ *
1824
+ * @param {TinyNode | TinyNode[]} el - The element(s) to append.
1825
+ * @param {TinyNode | TinyNode[]} targets - Target element(s) where content will be appended.
1826
+ */
1827
+ static appendTo(el, targets) {
1828
+ const elems = TinyHtml._preNodeElems(el, 'appendTo');
1829
+ const tars = TinyHtml._preNodeElems(targets, 'appendTo');
1830
+ tars.forEach((target, i) => {
1831
+ elems.forEach((elem) =>
1832
+ target.appendChild(i === tars.length - 1 ? elem : elem.cloneNode(true)),
1833
+ );
1834
+ });
1835
+ }
1836
+
1837
+ /**
1838
+ * Appends the given element(s) to each target element in sequence.
1839
+ *
1840
+ * @param {TinyNode | TinyNode[]} targets - Target element(s) where content will be appended.
1841
+ */
1842
+ appendTo(targets) {
1843
+ return TinyHtml.appendTo(this, targets);
1844
+ }
1845
+
1846
+ /**
1847
+ * Prepends the given element(s) to each target element in sequence.
1848
+ *
1849
+ * @param {TinyElement | TinyElement[]} el - The element(s) to prepend.
1850
+ * @param {TinyElement | TinyElement[]} targets - Target element(s) where content will be prepended.
1851
+ */
1852
+ static prependTo(el, targets) {
1853
+ const elems = TinyHtml._preElems(el, 'prependTo');
1854
+ const tars = TinyHtml._preElems(targets, 'prependTo');
1855
+ tars.forEach((target, i) => {
1856
+ elems
1857
+ .slice()
1858
+ .reverse()
1859
+ .forEach((elem) => target.prepend(i === tars.length - 1 ? elem : elem.cloneNode(true)));
1860
+ });
1861
+ }
1862
+
1863
+ /**
1864
+ * Prepends the given element(s) to each target element in sequence.
1865
+ *
1866
+ * @param {TinyElement | TinyElement[]} targets - Target element(s) where content will be prepended.
1867
+ */
1868
+ prependTo(targets) {
1869
+ return TinyHtml.prependTo(this, targets);
1870
+ }
1871
+
1872
+ /**
1873
+ * Inserts the element before a child of a given target, or before the target itself.
1874
+ *
1875
+ * @param {TinyNode | TinyNode[]} el - The element(s) to insert.
1876
+ * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
1877
+ * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert before, defaults to target.
1878
+ */
1879
+ static insertBefore(el, target, child = null) {
1880
+ const elem = TinyHtml._preNodeElem(el, 'insertBefore');
1881
+ const targ = TinyHtml._preNodeElem(target, 'insertBefore');
1882
+ const childNode = TinyHtml._preNodeElemWithNull(child, 'insertBefore');
1883
+ if (!targ.parentNode) throw new Error('');
1884
+ targ.parentNode.insertBefore(elem, childNode || targ);
1885
+ }
1886
+
1887
+ /**
1888
+ * Inserts the element before a child of a given target, or before the target itself.
1889
+ *
1890
+ * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
1891
+ * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert before, defaults to target.
1892
+ */
1893
+ insertBefore(target, child) {
1894
+ return TinyHtml.insertBefore(this, target, child);
1895
+ }
1896
+
1897
+ /**
1898
+ * Inserts the element after a child of a given target, or after the target itself.
1899
+ *
1900
+ * @param {TinyNode | TinyNode[]} el - The element(s) to insert.
1901
+ * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
1902
+ * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert after, defaults to target.
1903
+ */
1904
+ static insertAfter(el, target, child = null) {
1905
+ const elem = TinyHtml._preNodeElem(el, 'insertAfter');
1906
+ const targ = TinyHtml._preNodeElem(target, 'insertBefore');
1907
+ const childNode = TinyHtml._preNodeElemWithNull(child, 'insertBefore');
1908
+ if (!targ.parentNode) throw new Error('');
1909
+ targ.parentNode.insertBefore(elem, childNode || targ.nextSibling);
1910
+ }
1911
+
1912
+ /**
1913
+ * Inserts the element after a child of a given target, or after the target itself.
1914
+ *
1915
+ * @param {TinyNode | TinyNode[]} target - The reference element where insertion happens.
1916
+ * @param {TinyNode | TinyNode[] | null} [child=null] - Optional child to insert after, defaults to target.
1917
+ */
1918
+ insertAfter(target, child) {
1919
+ return TinyHtml.insertAfter(this, target, child);
1920
+ }
1921
+
1922
+ /**
1923
+ * Replaces all target elements with the provided element(s).
1924
+ * If multiple targets exist, the inserted elements are cloned accordingly.
1925
+ *
1926
+ * @param {TinyNode | TinyNode[]} el - The new element(s) to insert.
1927
+ * @param {TinyNode | TinyNode[]} targets - The elements to be replaced.
1928
+ */
1929
+ static replaceAll(el, targets) {
1930
+ const elems = TinyHtml._preNodeElems(el, 'replaceAll');
1931
+ const tars = TinyHtml._preNodeElems(targets, 'replaceAll');
1932
+ tars.forEach((target, i) => {
1933
+ const parent = target.parentNode;
1934
+ elems.forEach((elem) => {
1935
+ if (parent)
1936
+ parent.replaceChild(i === tars.length - 1 ? elem : elem.cloneNode(true), target);
1937
+ });
1938
+ });
1939
+ }
1940
+
1941
+ /**
1942
+ * Replaces all target elements with the provided element(s).
1943
+ * If multiple targets exist, the inserted elements are cloned accordingly.
1944
+ *
1945
+ * @param {TinyNode | TinyNode[]} targets - The elements to be replaced.
1946
+ */
1947
+ replaceAll(targets) {
1948
+ return TinyHtml.replaceAll(this, targets);
1949
+ }
1950
+
1951
+ //////////////////////////////////////////////////////
1952
+
1953
+ /**
1954
+ * The target HTML element for instance-level operations.
1955
+ * @type {ConstructorElValues}
1956
+ */
1957
+ #el;
1958
+
1959
+ /**
1960
+ * Creates an instance of TinyHtml for a specific Element.
1961
+ * Useful when you want to operate repeatedly on the same element using instance methods.
1962
+ * @param {ConstructorElValues} el - The element to wrap and manipulate.
1963
+ */
1964
+ constructor(el) {
1965
+ if (el instanceof TinyHtml)
1966
+ throw new Error(
1967
+ `[TinyHtml] You are trying to put a TinyHtml inside another TinyHtml in constructor.`,
1968
+ );
1969
+ if (!(el instanceof Element) && !(el instanceof Window) && !(el instanceof Document))
1970
+ throw new Error(`[TinyHtml] Invalid Target in constructor.`);
1971
+ this.#el = el;
1972
+ }
1973
+
1974
+ /**
1975
+ * Checks whether the given object is a window.
1976
+ * @param {*} obj - The object to test.
1977
+ * @returns {obj is Window} - True if it's a Window.
1978
+ */
1979
+ static isWindow(obj) {
1980
+ return obj != null && obj === obj.window;
1981
+ }
1982
+
1983
+ /////////////////////////////////////////////////////
1984
+
1985
+ /**
1986
+ * Returns the full computed CSS styles for the given element.
1987
+ *
1988
+ * @param {TinyElement} el - The element to retrieve styles from.
1989
+ * @returns {CSSStyleDeclaration} The computed style object for the element.
1990
+ */
1991
+ static css(el) {
1992
+ const elem = TinyHtml._preElem(el, 'css');
1993
+ return window.getComputedStyle(elem);
1994
+ }
1995
+
1996
+ /**
1997
+ * Returns the full computed CSS styles for the given element.
1998
+ *
1999
+ * @returns {CSSStyleDeclaration} The computed style object for the element.
2000
+ */
2001
+ css() {
2002
+ return TinyHtml.css(this);
2003
+ }
2004
+
2005
+ /**
2006
+ * Returns the value of a specific computed CSS property from the given element as a string.
2007
+ *
2008
+ * @param {TinyElement} el - The element to retrieve the style property from.
2009
+ * @param {string} prop - The name of the CSS property (camelCase or kebab-case).
2010
+ * @returns {string|null} The value of the CSS property as a string, or null if not found or invalid.
2011
+ */
2012
+ static cssString(el, prop) {
2013
+ const elem = TinyHtml._preElem(el, 'cssString');
2014
+ if (typeof prop !== 'string') throw new TypeError('The prop must be a string.');
2015
+ // @ts-ignore
2016
+ const val = window.getComputedStyle(elem)[prop];
2017
+ return typeof val === 'string' ? val : typeof val === 'number' ? val.toString() : null;
2018
+ }
2019
+
2020
+ /**
2021
+ * Returns the value of a specific computed CSS property from the given element as a string.
2022
+ *
2023
+ * @param {string} prop - The name of the CSS property (camelCase or kebab-case).
2024
+ * @returns {string|null} The value of the CSS property as a string, or null if not found or invalid.
2025
+ */
2026
+ cssString(prop) {
2027
+ return TinyHtml.cssString(this, prop);
2028
+ }
2029
+
2030
+ /**
2031
+ * Returns a subset of computed CSS styles based on the given list of properties.
2032
+ *
2033
+ * @param {TinyElement} el - The element to retrieve styles from.
2034
+ * @param {string[]} prop - An array of CSS property names to retrieve.
2035
+ * @returns {Partial<CSSStyleDeclaration>} An object containing the requested styles.
2036
+ */
2037
+ static cssList(el, prop) {
2038
+ const elem = TinyHtml._preElem(el, 'cssList');
2039
+ if (!Array.isArray(prop)) throw new TypeError('The prop must be an array of strings.');
2040
+
2041
+ const css = window.getComputedStyle(elem);
2042
+ /** @type {Partial<CSSStyleDeclaration>} */
2043
+ const result = {};
2044
+
2045
+ for (const p of prop) {
2046
+ if (typeof p !== 'undefined') {
2047
+ // @ts-ignore
2048
+ result[p] = css.getPropertyValue(p);
2049
+ }
2050
+ }
2051
+
2052
+ return result;
2053
+ }
2054
+
2055
+ /**
2056
+ * Returns a subset of computed CSS styles based on the given list of properties.
2057
+ *
2058
+ * @param {string[]} prop - An array of CSS property names to retrieve.
2059
+ * @returns {Partial<CSSStyleDeclaration>} An object containing the requested styles.
2060
+ */
2061
+ cssList(prop) {
2062
+ return TinyHtml.cssList(this, prop);
2063
+ }
2064
+
2065
+ /**
2066
+ * Returns the computed CSS float value of a property.
2067
+ * @param {TinyElement} el - The element to inspect.
2068
+ * @param {string} prop - The CSS property.
2069
+ * @returns {number} - The parsed float value.
2070
+ */
2071
+ static cssFloat(el, prop) {
2072
+ const elem = TinyHtml._preElem(el, 'cssFloat');
2073
+ if (typeof prop !== 'string') throw new TypeError('The prop must be a string.');
2074
+ // @ts-ignore
2075
+ const val = window.getComputedStyle(elem)[prop];
2076
+ return parseFloat(val) || 0;
2077
+ }
2078
+
2079
+ /**
2080
+ * Returns the computed CSS float value of a property.
2081
+ * @param {string} prop - The CSS property.
2082
+ * @returns {number} - The parsed float value.
2083
+ */
2084
+ cssFloat(prop) {
2085
+ return TinyHtml.cssFloat(this, prop);
2086
+ }
2087
+
2088
+ /**
2089
+ * Returns computed float values of multiple CSS properties.
2090
+ * @param {TinyElement} el - The element to inspect.
2091
+ * @param {string[]} prop - An array of CSS properties.
2092
+ * @returns {Record<string, number>} - Map of property to float value.
2093
+ */
2094
+ static cssFloats(el, prop) {
2095
+ const elem = TinyHtml._preElem(el, 'cssFloats');
2096
+ if (!Array.isArray(prop)) throw new TypeError('The prop must be an array of strings.');
2097
+ const css = window.getComputedStyle(elem);
2098
+ /** @type {Record<string, number>} */
2099
+ const result = {};
2100
+ for (const name of prop) {
2101
+ // @ts-ignore
2102
+ result[name] = parseFloat(css[name]) || 0;
2103
+ }
2104
+ return result;
2105
+ }
2106
+
2107
+ /**
2108
+ * Returns computed float values of multiple CSS properties.
2109
+ * @param {string[]} prop - An array of CSS properties.
2110
+ * @returns {Record<string, number>} - Map of property to float value.
2111
+ */
2112
+ cssFloats(prop) {
2113
+ return TinyHtml.cssFloats(this, prop);
2114
+ }
2115
+
2116
+ //////////////////////////////////////////////////////////////////////
2117
+
2118
+ /**
2119
+ * Stores camelCase to kebab-case CSS property aliases.
2120
+ *
2121
+ * Used to normalize property names when interacting with `element.style` or `getComputedStyle`.
2122
+ *
2123
+ * ⚠️ This object should not be modified directly. Use `TinyHtml.cssPropAliases` instead to ensure reverse mappings stay in sync.
2124
+ *
2125
+ * Example of how to add a new alias:
2126
+ *
2127
+ * ```js
2128
+ * TinyHtml.cssPropAliases.tinyPudding = 'tiny-pudding';
2129
+ * ```
2130
+ *
2131
+ * This will automatically update `TinyHtml.cssPropRevAliases['tiny-pudding']` with `'tinyPudding'`.
2132
+ *
2133
+ * @type {Record<string | symbol, string>}
2134
+ */
2135
+ static #cssPropAliases = {
2136
+ alignContent: 'align-content',
2137
+ alignItems: 'align-items',
2138
+ alignSelf: 'align-self',
2139
+ animationDelay: 'animation-delay',
2140
+ animationDirection: 'animation-direction',
2141
+ animationDuration: 'animation-duration',
2142
+ animationFillMode: 'animation-fill-mode',
2143
+ animationIterationCount: 'animation-iteration-count',
2144
+ animationName: 'animation-name',
2145
+ animationPlayState: 'animation-play-state',
2146
+ animationTimingFunction: 'animation-timing-function',
2147
+ backfaceVisibility: 'backface-visibility',
2148
+ backgroundAttachment: 'background-attachment',
2149
+ backgroundBlendMode: 'background-blend-mode',
2150
+ backgroundClip: 'background-clip',
2151
+ backgroundColor: 'background-color',
2152
+ backgroundImage: 'background-image',
2153
+ backgroundOrigin: 'background-origin',
2154
+ backgroundPosition: 'background-position',
2155
+ backgroundRepeat: 'background-repeat',
2156
+ backgroundSize: 'background-size',
2157
+ borderBottom: 'border-bottom',
2158
+ borderBottomColor: 'border-bottom-color',
2159
+ borderBottomLeftRadius: 'border-bottom-left-radius',
2160
+ borderBottomRightRadius: 'border-bottom-right-radius',
2161
+ borderBottomStyle: 'border-bottom-style',
2162
+ borderBottomWidth: 'border-bottom-width',
2163
+ borderCollapse: 'border-collapse',
2164
+ borderColor: 'border-color',
2165
+ borderImage: 'border-image',
2166
+ borderImageOutset: 'border-image-outset',
2167
+ borderImageRepeat: 'border-image-repeat',
2168
+ borderImageSlice: 'border-image-slice',
2169
+ borderImageSource: 'border-image-source',
2170
+ borderImageWidth: 'border-image-width',
2171
+ borderLeft: 'border-left',
2172
+ borderLeftColor: 'border-left-color',
2173
+ borderLeftStyle: 'border-left-style',
2174
+ borderLeftWidth: 'border-left-width',
2175
+ borderRadius: 'border-radius',
2176
+ borderRight: 'border-right',
2177
+ borderRightColor: 'border-right-color',
2178
+ borderRightStyle: 'border-right-style',
2179
+ borderRightWidth: 'border-right-width',
2180
+ borderSpacing: 'border-spacing',
2181
+ borderStyle: 'border-style',
2182
+ borderTop: 'border-top',
2183
+ borderTopColor: 'border-top-color',
2184
+ borderTopLeftRadius: 'border-top-left-radius',
2185
+ borderTopRightRadius: 'border-top-right-radius',
2186
+ borderTopStyle: 'border-top-style',
2187
+ borderTopWidth: 'border-top-width',
2188
+ borderWidth: 'border-width',
2189
+ boxDecorationBreak: 'box-decoration-break',
2190
+ boxShadow: 'box-shadow',
2191
+ boxSizing: 'box-sizing',
2192
+ breakAfter: 'break-after',
2193
+ breakBefore: 'break-before',
2194
+ breakInside: 'break-inside',
2195
+ captionSide: 'caption-side',
2196
+ caretColor: 'caret-color',
2197
+ clipPath: 'clip-path',
2198
+ columnCount: 'column-count',
2199
+ columnFill: 'column-fill',
2200
+ columnGap: 'column-gap',
2201
+ columnRule: 'column-rule',
2202
+ columnRuleColor: 'column-rule-color',
2203
+ columnRuleStyle: 'column-rule-style',
2204
+ columnRuleWidth: 'column-rule-width',
2205
+ columnSpan: 'column-span',
2206
+ columnWidth: 'column-width',
2207
+ counterIncrement: 'counter-increment',
2208
+ counterReset: 'counter-reset',
2209
+ emptyCells: 'empty-cells',
2210
+ flexBasis: 'flex-basis',
2211
+ flexDirection: 'flex-direction',
2212
+ flexFlow: 'flex-flow',
2213
+ flexGrow: 'flex-grow',
2214
+ flexShrink: 'flex-shrink',
2215
+ flexWrap: 'flex-wrap',
2216
+ fontFamily: 'font-family',
2217
+ fontFeatureSettings: 'font-feature-settings',
2218
+ fontKerning: 'font-kerning',
2219
+ fontLanguageOverride: 'font-language-override',
2220
+ fontSize: 'font-size',
2221
+ fontSizeAdjust: 'font-size-adjust',
2222
+ fontStretch: 'font-stretch',
2223
+ fontStyle: 'font-style',
2224
+ fontSynthesis: 'font-synthesis',
2225
+ fontVariant: 'font-variant',
2226
+ fontVariantAlternates: 'font-variant-alternates',
2227
+ fontVariantCaps: 'font-variant-caps',
2228
+ fontVariantEastAsian: 'font-variant-east-asian',
2229
+ fontVariantLigatures: 'font-variant-ligatures',
2230
+ fontVariantNumeric: 'font-variant-numeric',
2231
+ fontVariantPosition: 'font-variant-position',
2232
+ fontWeight: 'font-weight',
2233
+ gridArea: 'grid-area',
2234
+ gridAutoColumns: 'grid-auto-columns',
2235
+ gridAutoFlow: 'grid-auto-flow',
2236
+ gridAutoRows: 'grid-auto-rows',
2237
+ gridColumn: 'grid-column',
2238
+ gridColumnEnd: 'grid-column-end',
2239
+ gridColumnGap: 'grid-column-gap',
2240
+ gridColumnStart: 'grid-column-start',
2241
+ gridGap: 'grid-gap',
2242
+ gridRow: 'grid-row',
2243
+ gridRowEnd: 'grid-row-end',
2244
+ gridRowGap: 'grid-row-gap',
2245
+ gridRowStart: 'grid-row-start',
2246
+ gridTemplate: 'grid-template',
2247
+ gridTemplateAreas: 'grid-template-areas',
2248
+ gridTemplateColumns: 'grid-template-columns',
2249
+ gridTemplateRows: 'grid-template-rows',
2250
+ imageRendering: 'image-rendering',
2251
+ justifyContent: 'justify-content',
2252
+ letterSpacing: 'letter-spacing',
2253
+ lineBreak: 'line-break',
2254
+ lineHeight: 'line-height',
2255
+ listStyle: 'list-style',
2256
+ listStyleImage: 'list-style-image',
2257
+ listStylePosition: 'list-style-position',
2258
+ listStyleType: 'list-style-type',
2259
+ marginBottom: 'margin-bottom',
2260
+ marginLeft: 'margin-left',
2261
+ marginRight: 'margin-right',
2262
+ marginTop: 'margin-top',
2263
+ maskClip: 'mask-clip',
2264
+ maskComposite: 'mask-composite',
2265
+ maskImage: 'mask-image',
2266
+ maskMode: 'mask-mode',
2267
+ maskOrigin: 'mask-origin',
2268
+ maskPosition: 'mask-position',
2269
+ maskRepeat: 'mask-repeat',
2270
+ maskSize: 'mask-size',
2271
+ maskType: 'mask-type',
2272
+ maxHeight: 'max-height',
2273
+ maxWidth: 'max-width',
2274
+ minHeight: 'min-height',
2275
+ minWidth: 'min-width',
2276
+ mixBlendMode: 'mix-blend-mode',
2277
+ objectFit: 'object-fit',
2278
+ objectPosition: 'object-position',
2279
+ offsetAnchor: 'offset-anchor',
2280
+ offsetDistance: 'offset-distance',
2281
+ offsetPath: 'offset-path',
2282
+ offsetRotate: 'offset-rotate',
2283
+ outlineColor: 'outline-color',
2284
+ outlineOffset: 'outline-offset',
2285
+ outlineStyle: 'outline-style',
2286
+ outlineWidth: 'outline-width',
2287
+ overflowAnchor: 'overflow-anchor',
2288
+ overflowWrap: 'overflow-wrap',
2289
+ overflowX: 'overflow-x',
2290
+ overflowY: 'overflow-y',
2291
+ paddingBottom: 'padding-bottom',
2292
+ paddingLeft: 'padding-left',
2293
+ paddingRight: 'padding-right',
2294
+ paddingTop: 'padding-top',
2295
+ pageBreakAfter: 'page-break-after',
2296
+ pageBreakBefore: 'page-break-before',
2297
+ pageBreakInside: 'page-break-inside',
2298
+ perspectiveOrigin: 'perspective-origin',
2299
+ placeContent: 'place-content',
2300
+ placeItems: 'place-items',
2301
+ placeSelf: 'place-self',
2302
+ pointerEvents: 'pointer-events',
2303
+ rowGap: 'row-gap',
2304
+ scrollBehavior: 'scroll-behavior',
2305
+ scrollMargin: 'scroll-margin',
2306
+ scrollMarginBlock: 'scroll-margin-block',
2307
+ scrollMarginBlockEnd: 'scroll-margin-block-end',
2308
+ scrollMarginBlockStart: 'scroll-margin-block-start',
2309
+ scrollMarginBottom: 'scroll-margin-bottom',
2310
+ scrollMarginInline: 'scroll-margin-inline',
2311
+ scrollMarginInlineEnd: 'scroll-margin-inline-end',
2312
+ scrollMarginInlineStart: 'scroll-margin-inline-start',
2313
+ scrollMarginLeft: 'scroll-margin-left',
2314
+ scrollMarginRight: 'scroll-margin-right',
2315
+ scrollMarginTop: 'scroll-margin-top',
2316
+ scrollPadding: 'scroll-padding',
2317
+ scrollPaddingBlock: 'scroll-padding-block',
2318
+ scrollPaddingBlockEnd: 'scroll-padding-block-end',
2319
+ scrollPaddingBlockStart: 'scroll-padding-block-start',
2320
+ scrollPaddingBottom: 'scroll-padding-bottom',
2321
+ scrollPaddingInline: 'scroll-padding-inline',
2322
+ scrollPaddingInlineEnd: 'scroll-padding-inline-end',
2323
+ scrollPaddingInlineStart: 'scroll-padding-inline-start',
2324
+ scrollPaddingLeft: 'scroll-padding-left',
2325
+ scrollPaddingRight: 'scroll-padding-right',
2326
+ scrollPaddingTop: 'scroll-padding-top',
2327
+ scrollSnapAlign: 'scroll-snap-align',
2328
+ scrollSnapStop: 'scroll-snap-stop',
2329
+ scrollSnapType: 'scroll-snap-type',
2330
+ shapeImageThreshold: 'shape-image-threshold',
2331
+ shapeMargin: 'shape-margin',
2332
+ shapeOutside: 'shape-outside',
2333
+ tabSize: 'tab-size',
2334
+ tableLayout: 'table-layout',
2335
+ textAlign: 'text-align',
2336
+ textAlignLast: 'text-align-last',
2337
+ textCombineUpright: 'text-combine-upright',
2338
+ textDecoration: 'text-decoration',
2339
+ textDecorationColor: 'text-decoration-color',
2340
+ textDecorationLine: 'text-decoration-line',
2341
+ textDecorationStyle: 'text-decoration-style',
2342
+ textIndent: 'text-indent',
2343
+ textJustify: 'text-justify',
2344
+ textOrientation: 'text-orientation',
2345
+ textOverflow: 'text-overflow',
2346
+ textShadow: 'text-shadow',
2347
+ textTransform: 'text-transform',
2348
+ transformBox: 'transform-box',
2349
+ transformOrigin: 'transform-origin',
2350
+ transformStyle: 'transform-style',
2351
+ transitionDelay: 'transition-delay',
2352
+ transitionDuration: 'transition-duration',
2353
+ transitionProperty: 'transition-property',
2354
+ transitionTimingFunction: 'transition-timing-function',
2355
+ unicodeBidi: 'unicode-bidi',
2356
+ userSelect: 'user-select',
2357
+ verticalAlign: 'vertical-align',
2358
+ whiteSpace: 'white-space',
2359
+ willChange: 'will-change',
2360
+ wordBreak: 'word-break',
2361
+ wordSpacing: 'word-spacing',
2362
+ wordWrap: 'word-wrap',
2363
+ writingMode: 'writing-mode',
2364
+ zIndex: 'z-index',
2365
+ WebkitTransform: '-webkit-transform',
2366
+ WebkitTransition: '-webkit-transition',
2367
+ WebkitBoxShadow: '-webkit-box-shadow',
2368
+ MozBoxShadow: '-moz-box-shadow',
2369
+ MozTransform: '-moz-transform',
2370
+ MozTransition: '-moz-transition',
2371
+ msTransform: '-ms-transform',
2372
+ msTransition: '-ms-transition',
2373
+ };
2374
+
2375
+ /** @type {Record<string | symbol, string>} */
2376
+ static cssPropAliases = new Proxy(TinyHtml.#cssPropAliases, {
2377
+ set(target, camelCaseKey, kebabValue) {
2378
+ target[camelCaseKey] = kebabValue;
2379
+ // @ts-ignore
2380
+ TinyHtml.cssPropRevAliases[kebabValue] = camelCaseKey;
2381
+ return true;
2382
+ },
2383
+ });
2384
+
2385
+ /** @type {Record<string | symbol, string>} */
2386
+ static cssPropRevAliases = Object.fromEntries(
2387
+ Object.entries(TinyHtml.#cssPropAliases).map(([camel, kebab]) => [kebab, camel]),
2388
+ );
2389
+
2390
+ /**
2391
+ * Converts a camelCase string to kebab-case
2392
+ * @param {string} str
2393
+ * @returns {string}
2394
+ */
2395
+ static toStyleKc(str) {
2396
+ if (typeof TinyHtml.cssPropAliases[str] === 'string') return TinyHtml.cssPropAliases[str];
2397
+ return str;
2398
+ }
2399
+
2400
+ /**
2401
+ * Converts a kebab-case string to camelCase
2402
+ * @param {string} str
2403
+ * @returns {string}
2404
+ */
2405
+ static toStyleCc(str) {
2406
+ if (typeof TinyHtml.cssPropRevAliases[str] === 'string') return TinyHtml.cssPropRevAliases[str];
2407
+ return str;
2408
+ }
2409
+
2410
+ /**
2411
+ * Sets one or more CSS inline style properties on the given element(s).
2412
+ *
2413
+ * - If `prop` is a string, the `value` will be applied to that property.
2414
+ * - If `prop` is an object, each key-value pair will be applied as a CSS property and value.
2415
+ *
2416
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - The element to inspect.
2417
+ * @param {string|Object} prop - The property name or an object with key-value pairs
2418
+ * @param {string|null} [value=null] - The value to set (if `prop` is a string)
2419
+ */
2420
+ static setStyle(el, prop, value = null) {
2421
+ TinyHtml._preHtmlElems(el, 'setStyle').forEach((elem) => {
2422
+ if (typeof prop === 'object') {
2423
+ for (const [k, v] of Object.entries(prop)) {
2424
+ elem.style.setProperty(
2425
+ TinyHtml.toStyleKc(k),
2426
+ typeof v === 'string' ? v : typeof v === 'number' ? `${v}px` : String(v),
2427
+ );
2428
+ }
2429
+ } else elem.style.setProperty(TinyHtml.toStyleKc(prop), value);
2430
+ });
2431
+ }
2432
+
2433
+ /**
2434
+ * Sets one or more CSS inline style properties on the given element(s).
2435
+ *
2436
+ * - If `prop` is a string, the `value` will be applied to that property.
2437
+ * - If `prop` is an object, each key-value pair will be applied as a CSS property and value.
2438
+ *
2439
+ * @param {string|Object} prop - The property name or an object with key-value pairs
2440
+ * @param {string|null} [value=null] - The value to set (if `prop` is a string)
2441
+ */
2442
+ setStyle(prop, value) {
2443
+ return TinyHtml.setStyle(this, prop, value);
2444
+ }
2445
+
2446
+ /**
2447
+ * Gets the value of a specific inline style property.
2448
+ *
2449
+ * Returns only the value set directly via the `style` attribute.
2450
+ *
2451
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element to inspect.
2452
+ * @param {string} prop - The style property name to retrieve.
2453
+ * @returns {string} The style value of the specified property.
2454
+ */
2455
+ static getStyle(el, prop) {
2456
+ return TinyHtml._preHtmlElem(el, 'getStyle').style.getPropertyValue(TinyHtml.toStyleKc(prop));
2457
+ }
2458
+
2459
+ /**
2460
+ * Gets the value of a specific inline style property.
2461
+ *
2462
+ * Returns only the value set directly via the `style` attribute.
2463
+ *
2464
+ * @param {string} prop - The style property name to retrieve.
2465
+ * @returns {string} The style value of the specified property.
2466
+ */
2467
+ getStyle(prop) {
2468
+ return TinyHtml.getStyle(this, prop);
2469
+ }
2470
+
2471
+ /**
2472
+ * Gets all inline styles defined directly on the element (`style` attribute).
2473
+ *
2474
+ * Returns an object with all property-value pairs in kebab-case format.
2475
+ *
2476
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element to inspect.
2477
+ * @param {Object} [settings={}] - Optional configuration settings.
2478
+ * @param {boolean} [settings.camelCase=false] - If `true`, the property names will be converted to camelCase.
2479
+ * @param {boolean} [settings.rawAttr=false] - If `true`, reads the style string from the `style` attribute instead of using the style object.
2480
+ * @returns {Record<string, string>} All inline styles as an object.
2481
+ *
2482
+ * @throws {TypeError} If `camelCase` or `rawAttr` is not a boolean.
2483
+ */
2484
+ static style(el, { camelCase = false, rawAttr = false } = {}) {
2485
+ if (typeof camelCase !== 'boolean')
2486
+ throw new TypeError(`"camelCase" must be a boolean. Received: ${typeof camelCase}`);
2487
+ if (typeof rawAttr !== 'boolean')
2488
+ throw new TypeError(`"rawAttr" must be a boolean. Received: ${typeof rawAttr}`);
2489
+
2490
+ const elem = TinyHtml._preHtmlElem(el, 'style');
2491
+ /** @type {Record<string, string>} */
2492
+ const result = {};
2493
+
2494
+ if (rawAttr) {
2495
+ const raw = elem.getAttribute('style') || '';
2496
+ const entries = raw.split(';');
2497
+ for (const entry of entries) {
2498
+ const [rawProp, rawVal] = entry.split(':');
2499
+ if (!rawProp || !rawVal) continue;
2500
+
2501
+ const prop = rawProp.trim();
2502
+ const value = rawVal.trim();
2503
+ result[camelCase ? TinyHtml.toStyleCc(prop) : prop] = value;
2504
+ }
2505
+ } else {
2506
+ const styles = elem.style;
2507
+ for (let i = 0; i < styles.length; i++) {
2508
+ const prop = styles[i]; // Already in kebab-case
2509
+ const value = styles.getPropertyValue(prop);
2510
+ result[camelCase ? TinyHtml.toStyleCc(prop) : prop] = value;
2511
+ }
2512
+ }
2513
+
2514
+ return result;
2515
+ }
2516
+
2517
+ /**
2518
+ * Gets all inline styles defined directly on the element (`style` attribute).
2519
+ *
2520
+ * Returns an object with all property-value pairs in kebab-case format.
2521
+ *
2522
+ * @param {Object} [settings={}] - Optional configuration settings.
2523
+ * @param {boolean} [settings.camelCase=false] - If `true`, the property names will be converted to camelCase.
2524
+ * @param {boolean} [settings.rawAttr=false] - If `true`, reads the style string from the `style` attribute instead of using the style object.
2525
+ * @returns {Record<string, string>} All inline styles as an object.
2526
+ */
2527
+ style(settings) {
2528
+ return TinyHtml.style(this, settings);
2529
+ }
2530
+
2531
+ /**
2532
+ * Removes one or more inline CSS properties from the given element(s).
2533
+ *
2534
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element or an array of elements.
2535
+ * @param {string|string[]} prop - A property name or an array of property names to remove.
2536
+ */
2537
+ static removeStyle(el, prop) {
2538
+ TinyHtml._preHtmlElems(el, 'removeStyle').forEach((elem) => {
2539
+ if (Array.isArray(prop)) {
2540
+ for (const p of prop) {
2541
+ elem.style.removeProperty(TinyHtml.toStyleKc(p));
2542
+ }
2543
+ } else elem.style.removeProperty(TinyHtml.toStyleKc(prop));
2544
+ });
2545
+ }
2546
+
2547
+ /**
2548
+ * Removes one or more inline CSS properties from the given element(s).
2549
+ *
2550
+ * @param {string|string[]} prop - A property name or an array of property names to remove.
2551
+ */
2552
+ removeStyle(prop) {
2553
+ return TinyHtml.removeStyle(this, prop);
2554
+ }
2555
+
2556
+ /**
2557
+ * Toggles a CSS property value between two given values.
2558
+ *
2559
+ * The current computed value is compared to `val1`. If it matches, the property is set to `val2`. Otherwise, it is set to `val1`.
2560
+ *
2561
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element or an array of elements.
2562
+ * @param {string} prop - The CSS property to toggle.
2563
+ * @param {string} val1 - The first value (used as "current" check).
2564
+ * @param {string} val2 - The second value (used as the "alternative").
2565
+ */
2566
+ static toggleStyle(el, prop, val1, val2) {
2567
+ TinyHtml._preHtmlElems(el, 'toggleStyle').forEach((elem) => {
2568
+ const current = TinyHtml.getStyle(elem, prop).trim();
2569
+ const newVal = current === TinyHtml.toStyleKc(val1) ? val2 : val1;
2570
+ TinyHtml.setStyle(elem, prop, newVal);
2571
+ });
2572
+ }
2573
+
2574
+ /**
2575
+ * Toggles a CSS property value between two given values.
2576
+ *
2577
+ * The current computed value is compared to `val1`. If it matches, the property is set to `val2`. Otherwise, it is set to `val1`.
2578
+ *
2579
+ * @param {string} prop - The CSS property to toggle.
2580
+ * @param {string} val1 - The first value (used as "current" check).
2581
+ * @param {string} val2 - The second value (used as the "alternative").
2582
+ */
2583
+ toggleStyle(prop, val1, val2) {
2584
+ return TinyHtml.toggleStyle(this, prop, val1, val2);
2585
+ }
2586
+
2587
+ /**
2588
+ * Removes all inline styles (`style` attribute) from the given element(s).
2589
+ *
2590
+ * @param {TinyElement|TinyElement[]} el - A single element or an array of elements.
2591
+ */
2592
+ static clearStyle(el) {
2593
+ TinyHtml._preElems(el, 'clearStyle').forEach((elem) => elem.removeAttribute('style'));
2594
+ }
2595
+
2596
+ /**
2597
+ * Removes all inline styles (`style` attribute) from the given element(s).
2598
+ *
2599
+ */
2600
+ clearStyle() {
2601
+ return TinyHtml.clearStyle(this);
2602
+ }
2603
+
2604
+ //////////////////////////////////////////////////////////////////////
2605
+
2606
+ /**
2607
+ * Focus the element.
2608
+ *
2609
+ * @param {TinyHtmlElement} el - The element or a selector string.
2610
+ */
2611
+ static focus(el) {
2612
+ const elem = TinyHtml._preHtmlElem(el, 'focus');
2613
+ elem.focus();
2614
+ }
2615
+
2616
+ /**
2617
+ * Focus the element.
2618
+ */
2619
+ focus() {
2620
+ return TinyHtml.focus(this);
2621
+ }
2622
+
2623
+ /**
2624
+ * Blur the element.
2625
+ *
2626
+ * @param {TinyHtmlElement} el - The element or a selector string.
2627
+ */
2628
+ static blur(el) {
2629
+ const elem = TinyHtml._preHtmlElem(el, 'blur');
2630
+ elem.blur();
2631
+ }
2632
+
2633
+ /**
2634
+ * Blur the element.
2635
+ */
2636
+ blur() {
2637
+ return TinyHtml.blur(this);
2638
+ }
2639
+
2640
+ //////////////////////////////////////////////////////////////////////
2641
+
2642
+ /**
2643
+ * Sets the vertical scroll position of the window.
2644
+ * @param {number} value - Sets the scroll position.
2645
+ */
2646
+ static setWinScrollTop(value) {
2647
+ if (typeof value !== 'number') throw new TypeError('The value must be a number.');
2648
+ window.scrollTo({ top: value });
2649
+ }
2650
+
2651
+ /**
2652
+ * Sets the horizontal scroll position of the window.
2653
+ * @param {number} value - Sets the scroll position.
2654
+ */
2655
+ static setWinScrollLeft(value) {
2656
+ if (typeof value !== 'number') throw new TypeError('The value must be a number.');
2657
+ window.scrollTo({ left: value });
2658
+ }
2659
+
2660
+ /**
2661
+ * Gets the vertical scroll position of the window.
2662
+ * @returns {number} - The current scroll top value.
2663
+ */
2664
+ static winScrollTop() {
2665
+ return window.scrollY || document.documentElement.scrollTop;
2666
+ }
2667
+
2668
+ /**
2669
+ * Gets the horizontal scroll position of the window.
2670
+ * @returns {number} - The current scroll left value.
2671
+ */
2672
+ static winScrollLeft() {
2673
+ return window.scrollX || document.documentElement.scrollLeft;
2674
+ }
2675
+
2676
+ /**
2677
+ * Returns the current height of the viewport.
2678
+ * @returns {number} - Viewport height in pixels.
2679
+ */
2680
+ static winInnerHeight() {
2681
+ return window.innerHeight || document.documentElement.clientHeight;
2682
+ }
2683
+
2684
+ /**
2685
+ * Returns the current width of the viewport.
2686
+ * @returns {number} - Viewport width in pixels.
2687
+ */
2688
+ static winInnerWidth() {
2689
+ return window.innerWidth || document.documentElement.clientWidth;
2690
+ }
2691
+
2692
+ /**
2693
+ * Gets the width or height of an element based on the box model.
2694
+ * @param {TinyElementAndWindow} el - The element or window.
2695
+ * @param {"width"|"height"} type - Dimension type.
2696
+ * @param {"content"|"padding"|"border"|"margin"} [extra='content'] - Box model context.
2697
+ * @returns {number} - Computed dimension.
2698
+ * @throws {TypeError} If `type` or `extra` is not a string.
2699
+ */
2700
+ static getDimension(el, type, extra = 'content') {
2701
+ const elem = TinyHtml._preElemAndWindow(el, 'getDimension');
2702
+ if (typeof type !== 'string') throw new TypeError('The type must be a string.');
2703
+ if (typeof extra !== 'string') throw new TypeError('The extra must be a string.');
2704
+
2705
+ const name = type === 'width' ? 'Width' : 'Height';
2706
+
2707
+ if (TinyHtml.isWindow(elem)) {
2708
+ return extra === 'margin'
2709
+ ? // @ts-ignore
2710
+ elem['inner' + name]
2711
+ : // @ts-ignore
2712
+ elem.document.documentElement['client' + name];
2713
+ }
2714
+ /** @type {Element} */
2715
+ const elHtml = elem;
2716
+
2717
+ if (elHtml.nodeType === 9) {
2718
+ // @ts-ignore
2719
+ const doc = elHtml.documentElement;
2720
+ return Math.max(
2721
+ // @ts-ignore
2722
+ elHtml.body['scroll' + name],
2723
+ doc['scroll' + name],
2724
+ // @ts-ignore
2725
+ elHtml.body['offset' + name],
2726
+ doc['offset' + name],
2727
+ doc['client' + name],
2728
+ );
2729
+ }
2730
+
2731
+ let size = elHtml.getBoundingClientRect()[type];
2732
+
2733
+ /**
2734
+ * Auxiliary function to add measures on one side and the other
2735
+ * @param {string} prefix
2736
+ */
2737
+ function sumSides(prefix) {
2738
+ if (type === 'width') {
2739
+ return (
2740
+ TinyHtml.cssFloat(elHtml, prefix + 'Left') + TinyHtml.cssFloat(elHtml, prefix + 'Right')
2741
+ );
2742
+ } else {
2743
+ return (
2744
+ TinyHtml.cssFloat(elHtml, prefix + 'Top') + TinyHtml.cssFloat(elHtml, prefix + 'Bottom')
2745
+ );
2746
+ }
2747
+ }
2748
+
2749
+ switch (extra) {
2750
+ case 'content':
2751
+ // remove padding + border
2752
+ size -= sumSides('padding');
2753
+ size -= sumSides('border');
2754
+ break;
2755
+
2756
+ case 'padding':
2757
+ // remove border only (padding included in the bounding rect)
2758
+ size -= sumSides('border');
2759
+ break;
2760
+
2761
+ case 'border':
2762
+ // bounding rect already includes border + padding, so do not move the size
2763
+ break;
2764
+
2765
+ case 'margin':
2766
+ // adds margin (margin is out of bounding rect)
2767
+ size += sumSides('margin');
2768
+ break;
2769
+ }
2770
+
2771
+ return size;
2772
+ }
2773
+
2774
+ /**
2775
+ * Gets the width or height of an element based on the box model.
2776
+ * @param {"width"|"height"} type - Dimension type.
2777
+ * @param {"content"|"padding"|"border"|"margin"} extra - Box model context.
2778
+ * @returns {number} - Computed dimension.
2779
+ */
2780
+ getDimension(type, extra) {
2781
+ return TinyHtml.getDimension(this, type, extra);
2782
+ }
2783
+
2784
+ /**
2785
+ * Sets the height of the element.
2786
+ * @param {TinyHtmlElement} el - Target element.
2787
+ * @param {string|number} value - Height value.
2788
+ * @throws {TypeError} If `value` is neither a string nor number.
2789
+ */
2790
+ static setHeight(el, value) {
2791
+ const elem = TinyHtml._preHtmlElem(el, 'setHeight');
2792
+ if (typeof value !== 'number' && typeof value !== 'string')
2793
+ throw new TypeError('The value must be a string or number.');
2794
+ elem.style.height = typeof value === 'number' ? `${value}px` : value;
2795
+ }
2796
+
2797
+ /**
2798
+ * Sets the height of the element.
2799
+ * @param {string|number} value - Height value.
2800
+ */
2801
+ setHeight(value) {
2802
+ return TinyHtml.setHeight(this, value);
2803
+ }
2804
+
2805
+ /**
2806
+ * Sets the width of the element.
2807
+ * @param {TinyHtmlElement} el - Target element.
2808
+ * @param {string|number} value - Width value.
2809
+ * @throws {TypeError} If `value` is neither a string nor number.
2810
+ */
2811
+ static setWidth(el, value) {
2812
+ const elem = TinyHtml._preHtmlElem(el, 'setWidth');
2813
+ if (typeof value !== 'number' && typeof value !== 'string')
2814
+ throw new TypeError('The value must be a string or number.');
2815
+ elem.style.width = typeof value === 'number' ? `${value}px` : value;
2816
+ }
2817
+
2818
+ /**
2819
+ * Sets the width of the element.
2820
+ * @param {string|number} value - Width value.
2821
+ */
2822
+ setWidth(value) {
2823
+ return TinyHtml.setWidth(this, value);
2824
+ }
2825
+
2826
+ /**
2827
+ * Returns content box height.
2828
+ * @param {TinyElementAndWindow} el - Target element.
2829
+ * @returns {number}
2830
+ */
2831
+ static height(el) {
2832
+ const elem = TinyHtml._preElemAndWindow(el, 'height');
2833
+ return TinyHtml.getDimension(elem, 'height', 'content');
2834
+ }
2835
+
2836
+ /**
2837
+ * Returns content box height.
2838
+ * @returns {number}
2839
+ */
2840
+ height() {
2841
+ return TinyHtml.height(this);
2842
+ }
2843
+
2844
+ /**
2845
+ * Returns content box width.
2846
+ * @param {TinyElementAndWindow} el - Target element.
2847
+ * @returns {number}
2848
+ */
2849
+ static width(el) {
2850
+ const elem = TinyHtml._preElemAndWindow(el, 'width');
2851
+ return TinyHtml.getDimension(elem, 'width', 'content');
2852
+ }
2853
+
2854
+ /**
2855
+ * Returns content box width.
2856
+ * @returns {number}
2857
+ */
2858
+ width() {
2859
+ return TinyHtml.width(this);
2860
+ }
2861
+
2862
+ /**
2863
+ * Returns padding box height.
2864
+ * @param {TinyElementAndWindow} el - Target element.
2865
+ * @returns {number}
2866
+ */
2867
+ static innerHeight(el) {
2868
+ const elem = TinyHtml._preElemAndWindow(el, 'innerHeight');
2869
+ return TinyHtml.getDimension(elem, 'height', 'padding');
2870
+ }
2871
+
2872
+ /**
2873
+ * Returns padding box height.
2874
+ * @returns {number}
2875
+ */
2876
+ innerHeight() {
2877
+ return TinyHtml.innerHeight(this);
2878
+ }
2879
+
2880
+ /**
2881
+ * Returns padding box width.
2882
+ * @param {TinyElementAndWindow} el - Target element.
2883
+ * @returns {number}
2884
+ */
2885
+ static innerWidth(el) {
2886
+ const elem = TinyHtml._preElemAndWindow(el, 'innerWidth');
2887
+ return TinyHtml.getDimension(elem, 'width', 'padding');
2888
+ }
2889
+
2890
+ /**
2891
+ * Returns padding box width.
2892
+ * @returns {number}
2893
+ */
2894
+ innerWidth() {
2895
+ return TinyHtml.innerWidth(this);
2896
+ }
2897
+
2898
+ /**
2899
+ * Returns outer height of the element, optionally including margin.
2900
+ * @param {TinyElementAndWindow} el - Target element.
2901
+ * @param {boolean} [includeMargin=false] - Whether to include margin.
2902
+ * @returns {number}
2903
+ */
2904
+ static outerHeight(el, includeMargin = false) {
2905
+ if (typeof includeMargin !== 'boolean')
2906
+ throw new TypeError('The "includeMargin" must be a boolean.');
2907
+ const elem = TinyHtml._preElemAndWindow(el, 'outerHeight');
2908
+ return TinyHtml.getDimension(elem, 'height', includeMargin ? 'margin' : 'border');
2909
+ }
2910
+
2911
+ /**
2912
+ * Returns outer height of the element, optionally including margin.
2913
+ * @param {boolean} [includeMargin=false] - Whether to include margin.
2914
+ * @returns {number}
2915
+ */
2916
+ outerHeight(includeMargin) {
2917
+ return TinyHtml.outerHeight(this, includeMargin);
2918
+ }
2919
+
2920
+ /**
2921
+ * Returns outer width of the element, optionally including margin.
2922
+ * @param {TinyElementAndWindow} el - Target element.
2923
+ * @param {boolean} [includeMargin=false] - Whether to include margin.
2924
+ * @returns {number}
2925
+ */
2926
+ static outerWidth(el, includeMargin = false) {
2927
+ if (typeof includeMargin !== 'boolean')
2928
+ throw new TypeError('The "includeMargin" must be a boolean.');
2929
+ const elem = TinyHtml._preElemAndWindow(el, 'outerWidth');
2930
+ return TinyHtml.getDimension(elem, 'width', includeMargin ? 'margin' : 'border');
2931
+ }
2932
+
2933
+ /**
2934
+ * Returns outer width of the element, optionally including margin.
2935
+ * @param {boolean} [includeMargin=false] - Whether to include margin.
2936
+ * @returns {number}
2937
+ */
2938
+ outerWidth(includeMargin) {
2939
+ return TinyHtml.outerWidth(this, includeMargin);
2940
+ }
2941
+
2942
+ //////////////////////////////////////////////////
2943
+
2944
+ /**
2945
+ * Gets the offset of the element relative to the document.
2946
+ * @param {TinyElement} el - Target element.
2947
+ * @returns {{top: number, left: number}}
2948
+ */
2949
+ static offset(el) {
2950
+ const elem = TinyHtml._preElem(el, 'offset');
2951
+ const rect = elem.getBoundingClientRect();
2952
+ const scrollTop = window.scrollY || document.documentElement.scrollTop;
2953
+ const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
2954
+
2955
+ return {
2956
+ top: rect.top + scrollTop,
2957
+ left: rect.left + scrollLeft,
2958
+ };
2959
+ }
2960
+
2961
+ /**
2962
+ * Gets the offset of the element relative to the document.
2963
+ * @returns {{top: number, left: number}}
2964
+ */
2965
+ offset() {
2966
+ return TinyHtml.offset(this);
2967
+ }
2968
+
2969
+ /**
2970
+ * Gets the position of the element relative to its offset parent.
2971
+ * @param {TinyHtmlElement} el - Target element.
2972
+ * @returns {{top: number, left: number}}
2973
+ */
2974
+ static position(el) {
2975
+ const elem = TinyHtml._preHtmlElem(el, 'position');
2976
+
2977
+ let offsetParent;
2978
+ let offset;
2979
+ let parentOffset = { top: 0, left: 0 };
2980
+
2981
+ const computedStyle = window.getComputedStyle(elem);
2982
+
2983
+ if (computedStyle.position === 'fixed') {
2984
+ offset = elem.getBoundingClientRect();
2985
+ } else {
2986
+ offset = TinyHtml.offset(elem);
2987
+
2988
+ offsetParent = elem.offsetParent || document.documentElement;
2989
+ const { position } = window.getComputedStyle(offsetParent);
2990
+
2991
+ while (
2992
+ offsetParent instanceof HTMLElement &&
2993
+ (offsetParent === document.body || offsetParent === document.documentElement) &&
2994
+ position === 'static'
2995
+ ) {
2996
+ offsetParent = offsetParent.parentNode;
2997
+ }
2998
+
2999
+ if (
3000
+ offsetParent instanceof HTMLElement &&
3001
+ offsetParent !== elem &&
3002
+ offsetParent.nodeType === 1
3003
+ ) {
3004
+ const { borderTopWidth, borderLeftWidth } = TinyHtml.cssFloats(offsetParent, [
3005
+ 'borderTopWidth',
3006
+ 'borderLeftWidth',
3007
+ ]);
3008
+ parentOffset = TinyHtml.offset(offsetParent);
3009
+ parentOffset.top += borderTopWidth;
3010
+ parentOffset.left += borderLeftWidth;
3011
+ }
3012
+ }
3013
+
3014
+ return {
3015
+ top: offset.top - parentOffset.top - TinyHtml.cssFloat(elem, 'marginTop'),
3016
+ left: offset.left - parentOffset.left - TinyHtml.cssFloat(elem, 'marginLeft'),
3017
+ };
3018
+ }
3019
+
3020
+ /**
3021
+ * Gets the position of the element relative to its offset parent.
3022
+ * @returns {{top: number, left: number}}
3023
+ */
3024
+ position() {
3025
+ return TinyHtml.position(this);
3026
+ }
3027
+
3028
+ /**
3029
+ * Gets the closest positioned ancestor element.
3030
+ * @param {TinyHtmlElement} el - Target element.
3031
+ * @returns {HTMLElement} - Offset parent element.
3032
+ */
3033
+ static offsetParent(el) {
3034
+ const elem = TinyHtml._preHtmlElem(el, 'offsetParent');
3035
+ let offsetParent = elem.offsetParent;
3036
+
3037
+ while (
3038
+ offsetParent instanceof HTMLElement &&
3039
+ window.getComputedStyle(offsetParent).position === 'static'
3040
+ ) {
3041
+ offsetParent = offsetParent.offsetParent;
3042
+ }
3043
+
3044
+ // Fallback to document.documentElement
3045
+ return offsetParent instanceof HTMLElement ? offsetParent : document.documentElement;
3046
+ }
3047
+
3048
+ /**
3049
+ * Gets the closest positioned ancestor element.
3050
+ * @returns {HTMLElement} - Offset parent element.
3051
+ */
3052
+ offsetParent() {
3053
+ return TinyHtml.offsetParent(this);
3054
+ }
3055
+
3056
+ /**
3057
+ * Gets the vertical scroll position.
3058
+ * @param {TinyElementAndWindow} el - Element or window.
3059
+ * @returns {number}
3060
+ */
3061
+ static scrollTop(el) {
3062
+ const elem = TinyHtml._preElemAndWindow(el, 'scrollTop');
3063
+ if (TinyHtml.isWindow(elem)) return elem.pageYOffset;
3064
+ // @ts-ignore
3065
+ if (elem.nodeType === 9) return elem.defaultView.pageYOffset;
3066
+ return elem.scrollTop;
3067
+ }
3068
+
3069
+ /**
3070
+ * Gets the vertical scroll position.
3071
+ * @returns {number}
3072
+ */
3073
+ scrollTop() {
3074
+ return TinyHtml.scrollTop(this);
3075
+ }
3076
+
3077
+ /**
3078
+ * Gets the horizontal scroll position.
3079
+ * @param {TinyElementAndWindow} el - Element or window.
3080
+ * @returns {number}
3081
+ */
3082
+ static scrollLeft(el) {
3083
+ const elem = TinyHtml._preElemAndWindow(el, 'scrollLeft');
3084
+ if (TinyHtml.isWindow(elem)) return elem.pageXOffset;
3085
+ // @ts-ignore
3086
+ if (elem.nodeType === 9) return elem.defaultView.pageXOffset;
3087
+ return elem.scrollLeft;
3088
+ }
3089
+
3090
+ /**
3091
+ * Gets the horizontal scroll position.
3092
+ * @returns {number}
3093
+ */
3094
+ scrollLeft() {
3095
+ return TinyHtml.scrollLeft(this);
3096
+ }
3097
+
3098
+ /**
3099
+ * Sets the vertical scroll position.
3100
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
3101
+ * @param {number} value - Scroll top value.
3102
+ */
3103
+ static setScrollTop(el, value) {
3104
+ if (typeof value !== 'number') throw new TypeError('ScrollTop value must be a number.');
3105
+ TinyHtml._preElemsAndWindow(el, 'setScrollTop').forEach((elem) => {
3106
+ if (TinyHtml.isWindow(elem)) {
3107
+ elem.scrollTo(elem.pageXOffset, value);
3108
+ } else if (elem.nodeType === 9) {
3109
+ // @ts-ignore
3110
+ elem.defaultView.scrollTo(elem.defaultView.pageXOffset, value);
3111
+ } else {
3112
+ elem.scrollTop = value;
3113
+ }
3114
+ });
3115
+ }
3116
+
3117
+ /**
3118
+ * Sets the vertical scroll position.
3119
+ * @param {number} value - Scroll top value.
3120
+ */
3121
+ setScrollTop(value) {
3122
+ return TinyHtml.setScrollTop(this, value);
3123
+ }
3124
+
3125
+ /**
3126
+ * Sets the horizontal scroll position.
3127
+ * @param {TinyElementAndWindow|TinyElementAndWindow[]} el - Element or window.
3128
+ * @param {number} value - Scroll left value.
3129
+ */
3130
+ static setScrollLeft(el, value) {
3131
+ if (typeof value !== 'number') throw new TypeError('ScrollLeft value must be a number.');
3132
+ TinyHtml._preElemsAndWindow(el, 'setScrollLeft').forEach((elem) => {
3133
+ if (TinyHtml.isWindow(elem)) {
3134
+ elem.scrollTo(value, elem.pageYOffset);
3135
+ } else if (elem.nodeType === 9) {
3136
+ // @ts-ignore
3137
+ elem.defaultView.scrollTo(value, elem.defaultView.pageYOffset);
3138
+ } else {
3139
+ elem.scrollLeft = value;
3140
+ }
3141
+ });
3142
+ }
3143
+
3144
+ /**
3145
+ * Sets the horizontal scroll position.
3146
+ * @param {number} value - Scroll left value.
3147
+ */
3148
+ setScrollLeft(value) {
3149
+ return TinyHtml.setScrollLeft(this, value);
3150
+ }
3151
+
3152
+ /**
3153
+ * Returns the total border width and individual sides from `border{Side}Width` CSS properties.
3154
+ *
3155
+ * @param {TinyElement} el - The target DOM element.
3156
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border widths, and each side individually.
3157
+ */
3158
+ static borderWidth(el) {
3159
+ const elem = TinyHtml._preElem(el, 'borderWidth');
3160
+ const {
3161
+ borderLeftWidth: left,
3162
+ borderRightWidth: right,
3163
+ borderTopWidth: top,
3164
+ borderBottomWidth: bottom,
3165
+ } = TinyHtml.cssFloats(elem, [
3166
+ 'borderLeftWidth',
3167
+ 'borderRightWidth',
3168
+ 'borderTopWidth',
3169
+ 'borderBottomWidth',
3170
+ ]);
3171
+ const x = left + right;
3172
+ const y = top + bottom;
3173
+
3174
+ return { x, y, left, right, top, bottom };
3175
+ }
3176
+
3177
+ /**
3178
+ * Returns the total border width and individual sides from `border{Side}Width` CSS properties.
3179
+ *
3180
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border widths, and each side individually.
3181
+ */
3182
+ borderWidth() {
3183
+ return TinyHtml.borderWidth(this);
3184
+ }
3185
+
3186
+ /**
3187
+ * Returns the total border size and individual sides from `border{Side}` CSS properties.
3188
+ *
3189
+ * @param {TinyElement} el - The target DOM element.
3190
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border sizes, and each side individually.
3191
+ */
3192
+ static border(el) {
3193
+ const elem = TinyHtml._preElem(el, 'border');
3194
+ const {
3195
+ borderLeft: left,
3196
+ borderRight: right,
3197
+ borderTop: top,
3198
+ borderBottom: bottom,
3199
+ } = TinyHtml.cssFloats(elem, ['borderLeft', 'borderRight', 'borderTop', 'borderBottom']);
3200
+ const x = left + right;
3201
+ const y = top + bottom;
3202
+
3203
+ return { x, y, left, right, top, bottom };
3204
+ }
3205
+
3206
+ /**
3207
+ * Returns the total border size and individual sides from `border{Side}` CSS properties.
3208
+ *
3209
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border sizes, and each side individually.
3210
+ */
3211
+ border() {
3212
+ return TinyHtml.border(this);
3213
+ }
3214
+
3215
+ /**
3216
+ * Returns the total margin and individual sides from `margin{Side}` CSS properties.
3217
+ *
3218
+ * @param {TinyElement} el - The target DOM element.
3219
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) margins, and each side individually.
3220
+ */
3221
+ static margin(el) {
3222
+ const elem = TinyHtml._preElem(el, 'margin');
3223
+ const {
3224
+ marginLeft: left,
3225
+ marginRight: right,
3226
+ marginTop: top,
3227
+ marginBottom: bottom,
3228
+ } = TinyHtml.cssFloats(elem, ['marginLeft', 'marginRight', 'marginTop', 'marginBottom']);
3229
+ const x = left + right;
3230
+ const y = top + bottom;
3231
+
3232
+ return { x, y, left, right, top, bottom };
3233
+ }
3234
+
3235
+ /**
3236
+ * Returns the total margin and individual sides from `margin{Side}` CSS properties.
3237
+ *
3238
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) margins, and each side individually.
3239
+ */
3240
+ margin() {
3241
+ return TinyHtml.margin(this);
3242
+ }
3243
+
3244
+ /**
3245
+ * Returns the total padding and individual sides from `padding{Side}` CSS properties.
3246
+ *
3247
+ * @param {TinyElement} el - The target DOM element.
3248
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) paddings, and each side individually.
3249
+ */
3250
+ static padding(el) {
3251
+ const elem = TinyHtml._preElem(el, 'padding');
3252
+ const {
3253
+ paddingLeft: left,
3254
+ paddingRight: right,
3255
+ paddingTop: top,
3256
+ paddingBottom: bottom,
3257
+ } = TinyHtml.cssFloats(elem, ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom']);
3258
+ const x = left + right;
3259
+ const y = top + bottom;
3260
+
3261
+ return { x, y, left, right, top, bottom };
3262
+ }
3263
+
3264
+ /**
3265
+ * Returns the total padding and individual sides from `padding{Side}` CSS properties.
3266
+ *
3267
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) paddings, and each side individually.
3268
+ */
3269
+ padding() {
3270
+ return TinyHtml.padding(this);
3271
+ }
3272
+
3273
+ /////////////////////////////////////////////
3274
+
3275
+ /**
3276
+ * Adds one or more CSS class names to the element.
3277
+ * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => void} - One or more class names to add.
3278
+ */
3279
+ static addClass(el, ...args) {
3280
+ TinyHtml._preElems(el, 'addClass').forEach((elem) => elem.classList.add(...args));
3281
+ }
3282
+
3283
+ /**
3284
+ * Adds one or more CSS class names to the element.
3285
+ * @type {(...tokens: string[]) => void} - One or more class names to add.
3286
+ */
3287
+ addClass(...args) {
3288
+ return TinyHtml.addClass(this, ...args);
3289
+ }
3290
+
3291
+ /**
3292
+ * Removes one or more CSS class names from the element.
3293
+ * @type {(el: TinyElement|TinyElement[], ...tokens: string[]) => void} - One or more class names to remove.
3294
+ */
3295
+ static removeClass(el, ...args) {
3296
+ TinyHtml._preElems(el, 'removeClass').forEach((elem) => elem.classList.remove(...args));
3297
+ }
3298
+
3299
+ /**
3300
+ * Removes one or more CSS class names from the element.
3301
+ * @type {(...tokens: string[]) => void} - One or more class names to remove.
3302
+ */
3303
+ removeClass(...args) {
3304
+ return TinyHtml.removeClass(this, ...args);
3305
+ }
3306
+
3307
+ /**
3308
+ * Replaces an existing class name with a new one.
3309
+ * @param {TinyElement|TinyElement[]} el - Target element.
3310
+ * @param {string} token - The class name to be replaced.
3311
+ * @param {string} newToken - The new class name to apply.
3312
+ * @returns {boolean[]} Whether the replacement was successful.
3313
+ * @throws {TypeError} If either argument is not a string.
3314
+ */
3315
+ static replaceClass(el, token, newToken) {
3316
+ if (typeof token !== 'string') throw new TypeError('The "token" parameter must be a string.');
3317
+ if (typeof newToken !== 'string')
3318
+ throw new TypeError('The "newToken" parameter must be a string.');
3319
+ /** @type {boolean[]} */
3320
+ const result = [];
3321
+ TinyHtml._preElems(el, 'replaceClass').forEach((elem) =>
3322
+ result.push(elem.classList.replace(token, newToken)),
3323
+ );
3324
+ return result;
3325
+ }
3326
+
3327
+ /**
3328
+ * Replaces an existing class name with a new one.
3329
+ * @param {string} token - The class name to be replaced.
3330
+ * @param {string} newToken - The new class name to apply.
3331
+ * @returns {boolean} Whether the replacement was successful.
3332
+ * @throws {TypeError} If either argument is not a string.
3333
+ */
3334
+ replaceClass(token, newToken) {
3335
+ return TinyHtml.replaceClass(this, token, newToken)[0];
3336
+ }
3337
+
3338
+ /**
3339
+ * Returns the class name at the specified index.
3340
+ * @param {TinyElement} el - Target element.
3341
+ * @param {number} index - The index of the class name.
3342
+ * @returns {string|null} The class name at the index or null if not found.
3343
+ * @throws {TypeError} If the index is not a number.
3344
+ */
3345
+ static classItem(el, index) {
3346
+ const elem = TinyHtml._preElem(el, 'classItem');
3347
+ if (typeof index !== 'number') throw new TypeError('The "index" parameter must be a number.');
3348
+ return elem.classList.item(index);
3349
+ }
3350
+
3351
+ /**
3352
+ * Returns the class name at the specified index.
3353
+ * @param {number} index - The index of the class name.
3354
+ * @returns {string|null} The class name at the index or null if not found.
3355
+ * @throws {TypeError} If the index is not a number.
3356
+ */
3357
+ classItem(index) {
3358
+ return TinyHtml.classItem(this, index);
3359
+ }
3360
+
3361
+ /**
3362
+ * Toggles a class name on the element with an optional force boolean.
3363
+ * @param {TinyElement|TinyElement[]} el - Target element.
3364
+ * @param {string} token - The class name to toggle.
3365
+ * @param {boolean} [force] - If true, adds the class; if false, removes it.
3366
+ * @returns {boolean[]} Whether the class is present after the toggle.
3367
+ * @throws {TypeError} If token is not a string or force is not a boolean.
3368
+ */
3369
+ static toggleClass(el, token, force) {
3370
+ if (typeof token !== 'string') throw new TypeError('The "token" parameter must be a string.');
3371
+ if (typeof force !== 'undefined' && typeof force !== 'boolean')
3372
+ throw new TypeError('The "force" parameter must be a boolean.');
3373
+ /** @type {boolean[]} */
3374
+ const result = [];
3375
+ TinyHtml._preElems(el, 'toggleClass').forEach((elem) =>
3376
+ result.push(elem.classList.toggle(token, force)),
3377
+ );
3378
+ return result;
3379
+ }
3380
+
3381
+ /**
3382
+ * Toggles a class name on the element with an optional force boolean.
3383
+ * @param {string} token - The class name to toggle.
3384
+ * @param {boolean} force - If true, adds the class; if false, removes it.
3385
+ * @returns {boolean} Whether the class is present after the toggle.
3386
+ * @throws {TypeError} If token is not a string or force is not a boolean.
3387
+ */
3388
+ toggleClass(token, force) {
3389
+ return TinyHtml.toggleClass(this, token, force)[0];
3390
+ }
3391
+
3392
+ /**
3393
+ * Checks if the element contains the given class name.
3394
+ * @param {TinyElement} el - Target element.
3395
+ * @param {string} token - The class name to check.
3396
+ * @returns {boolean} True if the class is present, false otherwise.
3397
+ * @throws {TypeError} If token is not a string.
3398
+ */
3399
+ static hasClass(el, token) {
3400
+ const elem = TinyHtml._preElem(el, 'hasClass');
3401
+ if (typeof token !== 'string') throw new TypeError('The "token" parameter must be a string.');
3402
+ return elem.classList.contains(token);
3403
+ }
3404
+
3405
+ /**
3406
+ * Checks if the element contains the given class name.
3407
+ * @param {string} token - The class name to check.
3408
+ * @returns {boolean} True if the class is present, false otherwise.
3409
+ * @throws {TypeError} If token is not a string.
3410
+ */
3411
+ hasClass(token) {
3412
+ return TinyHtml.hasClass(this, token);
3413
+ }
3414
+
3415
+ /**
3416
+ * Returns the number of classes applied to the element.
3417
+ * @param {TinyElement} el - Target element.
3418
+ * @returns {number} The number of classes.
3419
+ */
3420
+ static classLength(el) {
3421
+ const elem = TinyHtml._preElem(el, 'classLength');
3422
+ return elem.classList.length;
3423
+ }
3424
+
3425
+ /**
3426
+ * Returns the number of classes applied to the element.
3427
+ * @returns {number} The number of classes.
3428
+ */
3429
+ classLength() {
3430
+ return TinyHtml.classLength(this);
3431
+ }
3432
+
3433
+ /**
3434
+ * Returns all class names as an array of strings.
3435
+ * @param {TinyElement} el - Target element.
3436
+ * @returns {string[]} An array of class names.
3437
+ */
3438
+ static classList(el) {
3439
+ const elem = TinyHtml._preElem(el, 'classList');
3440
+ return elem.classList.values().toArray();
3441
+ }
3442
+
3443
+ /**
3444
+ * Returns all class names as an array of strings.
3445
+ * @returns {string[]} An array of class names.
3446
+ */
3447
+ classList() {
3448
+ return TinyHtml.classList(this);
3449
+ }
3450
+
3451
+ /////////////////////////////////////////
3452
+
3453
+ /**
3454
+ * Returns the tag name of the element.
3455
+ * @param {TinyElement} el - Target element.
3456
+ * @returns {string} The tag name in uppercase.
3457
+ */
3458
+ static tagName(el) {
3459
+ const elem = TinyHtml._preElem(el, 'tagName');
3460
+ return elem.tagName;
3461
+ }
3462
+
3463
+ /**
3464
+ * Returns the tag name of the element.
3465
+ * @returns {string} The tag name in uppercase.
3466
+ */
3467
+ tagName() {
3468
+ return TinyHtml.tagName(this);
3469
+ }
3470
+
3471
+ /**
3472
+ * Returns the ID of the element.
3473
+ * @param {TinyElement} el - Target element.
3474
+ * @returns {string} The element's ID.
3475
+ */
3476
+ static id(el) {
3477
+ const elem = TinyHtml._preElem(el, 'id');
3478
+ return elem.id;
3479
+ }
3480
+
3481
+ /**
3482
+ * Returns the ID of the element.
3483
+ * @returns {string} The element's ID.
3484
+ */
3485
+ id() {
3486
+ return TinyHtml.id(this);
3487
+ }
3488
+
3489
+ /**
3490
+ * Returns the text content of the element.
3491
+ * @param {TinyElement} el - Target element.
3492
+ * @returns {string|null} The text content or null if none.
3493
+ */
3494
+ static text(el) {
3495
+ const elem = TinyHtml._preElem(el, 'text');
3496
+ return elem.textContent;
3497
+ }
3498
+
3499
+ /**
3500
+ * Returns the text content of the element.
3501
+ * @returns {string|null} The text content or null if none.
3502
+ */
3503
+ text() {
3504
+ return TinyHtml.text(this);
3505
+ }
3506
+
3507
+ /**
3508
+ * Set text content of elements.
3509
+ * @param {TinyElement|TinyElement[]} el
3510
+ * @param {string} value
3511
+ */
3512
+ static setText(el, value) {
3513
+ if (typeof value !== 'string') throw new Error('Value is not a valid string.');
3514
+ TinyHtml._preElems(el, 'setText').forEach((el) => (el.textContent = value));
3515
+ }
3516
+
3517
+ /**
3518
+ * Set text content of the element.
3519
+ * @param {string} value
3520
+ */
3521
+ setText(value) {
3522
+ return TinyHtml.setText(this, value);
3523
+ }
3524
+
3525
+ /**
3526
+ * Remove all child nodes from each element.
3527
+ * @param {TinyElement|TinyElement[]} el
3528
+ */
3529
+ static empty(el) {
3530
+ TinyHtml._preElems(el, 'empty').forEach((el) => (el.textContent = ''));
3531
+ }
3532
+
3533
+ /**
3534
+ * Remove all child nodes of the element.
3535
+ */
3536
+ empty() {
3537
+ return TinyHtml.empty(this);
3538
+ }
3539
+
3540
+ /**
3541
+ * Get the innerHTML of the element.
3542
+ * @param {TinyElement|TinyElement[]} el
3543
+ * @returns {string}
3544
+ */
3545
+ static html(el) {
3546
+ const elem = TinyHtml._preElem(el, 'html');
3547
+ return elem.innerHTML;
3548
+ }
3549
+
3550
+ /**
3551
+ * Get the innerHTML of the element.
3552
+ * @returns {string}
3553
+ */
3554
+ html() {
3555
+ return TinyHtml.html(this);
3556
+ }
3557
+
3558
+ /**
3559
+ * Set the innerHTML of each element.
3560
+ * @param {TinyElement|TinyElement[]} el
3561
+ * @param {string} value
3562
+ */
3563
+ static setHtml(el, value) {
3564
+ if (typeof value !== 'string') throw new Error('Value is not a valid string.');
3565
+ TinyHtml._preElems(el, 'setHtml').forEach((el) => (el.innerHTML = value));
3566
+ }
3567
+
3568
+ /**
3569
+ * Set the innerHTML of the element.
3570
+ * @param {string} value
3571
+ */
3572
+ setHtml(value) {
3573
+ return TinyHtml.setHtml(this, value);
3574
+ }
3575
+
3576
+ /** @readonly */
3577
+ static _valHooks = {
3578
+ option: {
3579
+ /**
3580
+ * @param {HTMLOptionElement} elem
3581
+ * @returns {string|null}
3582
+ */
3583
+ get: (elem) => {
3584
+ const val = elem.getAttribute('value');
3585
+ return val != null ? val : elem.textContent;
3586
+ },
3587
+ },
3588
+
3589
+ select: {
3590
+ /**
3591
+ * @param {HTMLSelectElement} elem
3592
+ * @returns {(string | null)[] | string | null}
3593
+ */
3594
+ get: (elem) => {
3595
+ const options = elem.options;
3596
+ const index = elem.selectedIndex;
3597
+ const isSingle = elem.type === 'select-one';
3598
+ const max = isSingle ? index + 1 : options.length;
3599
+
3600
+ /** @type {(string | null)[] | null} */
3601
+ const values = [];
3602
+ let i = index < 0 ? max : isSingle ? index : 0;
3603
+
3604
+ for (; i < max; i++) {
3605
+ const option = options[i];
3606
+ /** @type {HTMLSelectElement|null} */
3607
+ // @ts-ignore
3608
+ const parentNode = option.parentNode;
3609
+ if (
3610
+ (option.selected || i === index) &&
3611
+ !option.disabled &&
3612
+ (!parentNode || !parentNode.disabled || parentNode.tagName !== 'OPTGROUP')
3613
+ ) {
3614
+ const val = TinyHtml._valHooks.option.get(option);
3615
+ if (isSingle) return val;
3616
+ values.push(val);
3617
+ }
3618
+ }
3619
+
3620
+ return values;
3621
+ },
3622
+
3623
+ /**
3624
+ * @param {HTMLSelectElement} elem
3625
+ * @param {string[]|string} value
3626
+ */
3627
+ set: (elem, value) => {
3628
+ const options = elem.options;
3629
+ const values = Array.isArray(value) ? value.map(String) : [String(value)];
3630
+ let optionSet = false;
3631
+
3632
+ for (let i = 0; i < options.length; i++) {
3633
+ const option = options[i];
3634
+ const optionVal = TinyHtml._valHooks.option.get(option);
3635
+ if (typeof optionVal === 'string' && (option.selected = values.includes(optionVal))) {
3636
+ optionSet = true;
3637
+ }
3638
+ }
3639
+
3640
+ if (!optionSet) {
3641
+ elem.selectedIndex = -1;
3642
+ }
3643
+
3644
+ return values;
3645
+ },
3646
+ },
3647
+
3648
+ radio: {
3649
+ /**
3650
+ * @param {HTMLInputElement} elem
3651
+ * @returns {string}
3652
+ */
3653
+ get(elem) {
3654
+ return elem.checked ? 'on' : 'off';
3655
+ },
3656
+ /**
3657
+ * @param {HTMLInputElement} elem
3658
+ * @param {string[]} value
3659
+ */
3660
+ set(elem, value) {
3661
+ if (typeof value === 'boolean') {
3662
+ const label = elem.closest('label');
3663
+ if (value && label) {
3664
+ const otherRadios = label.querySelectorAll('input[type="radio"]');
3665
+ otherRadios.forEach((otherRadio) => {
3666
+ if (otherRadio instanceof HTMLInputElement && otherRadio !== elem)
3667
+ otherRadio.checked = false;
3668
+ });
3669
+ }
3670
+ elem.checked = value;
3671
+ return value;
3672
+ }
3673
+ },
3674
+ },
3675
+
3676
+ checkbox: {
3677
+ /**
3678
+ * @param {HTMLInputElement} elem
3679
+ * @returns {string}
3680
+ */
3681
+ get(elem) {
3682
+ return elem.checked ? 'on' : 'off';
3683
+ },
3684
+ /**
3685
+ * @param {HTMLInputElement} elem
3686
+ * @param {boolean} value
3687
+ */
3688
+ set(elem, value) {
3689
+ if (typeof value === 'boolean') {
3690
+ elem.checked = value;
3691
+ return value;
3692
+ }
3693
+ },
3694
+ },
3695
+ };
3696
+
3697
+ /**
3698
+ * Sets the value of the current HTML value element (input, select, textarea, etc.).
3699
+ * Accepts strings, numbers, booleans or arrays of these values, or a callback function that computes them.
3700
+ *
3701
+ * @param {TinyInputElement|TinyInputElement[]} el - Target element.
3702
+ * @param {SetValueList|((el: InputElement, val: SetValueList) => SetValueList)} value - The value to assign or a function that returns it.
3703
+ * @throws {Error} If the computed value is not a valid string or boolean.
3704
+ */
3705
+ static setVal(el, value) {
3706
+ TinyHtml._preInputElems(el, 'setVal').forEach((elem) => {
3707
+ /**
3708
+ * @param {SetValueBase[]} array
3709
+ * @param {(v: SetValueBase, i: number) => SetValueBase} callback
3710
+ */
3711
+ const mapArray = (array, callback) => {
3712
+ const result = [];
3713
+ for (let i = 0; i < array.length; i++) {
3714
+ result.push(callback(array[i], i));
3715
+ }
3716
+ return result;
3717
+ };
3718
+
3719
+ if (elem.nodeType !== 1) return;
3720
+ /** @type {SetValueList} */
3721
+ let valToSet = typeof value === 'function' ? value(elem, TinyHtml.val(elem)) : value;
3722
+
3723
+ if (valToSet == null) {
3724
+ valToSet = '';
3725
+ } else if (typeof valToSet === 'number') {
3726
+ valToSet = String(valToSet);
3727
+ } else if (Array.isArray(valToSet)) {
3728
+ valToSet = mapArray(valToSet, (v) => (v == null ? '' : String(v)));
3729
+ }
3730
+
3731
+ // @ts-ignore
3732
+ const hook = TinyHtml._valHooks[elem.type] || TinyHtml._valHooks[elem.nodeName.toLowerCase()];
3733
+ if (
3734
+ !hook ||
3735
+ typeof hook.set !== 'function' ||
3736
+ hook.set(elem, valToSet, 'value') === undefined
3737
+ ) {
3738
+ if (typeof valToSet !== 'string' && typeof valToSet !== 'boolean')
3739
+ throw new Error(`Invalid setValue "${typeof valToSet}" value.`);
3740
+ if (typeof valToSet === 'string') elem.value = valToSet;
3741
+ }
3742
+ });
3743
+ }
3744
+
3745
+ /**
3746
+ * Sets the value of the current HTML value element (input, select, textarea, etc.).
3747
+ * Accepts strings, numbers, booleans or arrays of these values, or a callback function that computes them.
3748
+ *
3749
+ * @param {SetValueList|((el: InputElement, val: SetValueList) => SetValueList)} value - The value to assign or a function that returns it.
3750
+ * @throws {Error} If the computed value is not a valid string or boolean.
3751
+ */
3752
+ setVal(value) {
3753
+ return TinyHtml.setVal(this, value);
3754
+ }
3755
+
3756
+ /**
3757
+ * Maps value types to their corresponding getter functions.
3758
+ * Each function extracts a value of a specific type from a compatible HTMLInputElement.
3759
+ * @readonly
3760
+ */
3761
+ static _valTypes = {
3762
+ /**
3763
+ * Gets the string value from any HTMLInputElement.
3764
+ * @type {(elem: HTMLInputElement) => string}
3765
+ */
3766
+ string: (elem) => elem.value,
3767
+
3768
+ /**
3769
+ * Gets the value as a Date object from supported input types.
3770
+ * Valid only for types: "date", "datetime-local", "month", "time", "week".
3771
+ * Returns `null` if the field is empty or invalid.
3772
+ * @type {(elem: HTMLInputElement & { type: "date" | "datetime-local" | "month" | "time" | "week" }) => Date | null}
3773
+ */
3774
+ date: (elem) => elem.valueAsDate,
3775
+
3776
+ /**
3777
+ * Gets the numeric value from supported input types.
3778
+ * Valid for types: "number", "range", "date", "time".
3779
+ * Returns `NaN` if the value is invalid or empty.
3780
+ * @type {(elem: HTMLInputElement & { type: "number" | "range" | "date" | "time" }) => number}
3781
+ */
3782
+ number: (elem) => elem.valueAsNumber,
3783
+ };
3784
+
3785
+ /**
3786
+ * Gets the value of an input element according to the specified type.
3787
+ *
3788
+ * @param {InputElement} elem - The input element to extract the value from.
3789
+ * @param {GetValueTypes} type - The type of value to retrieve ("string", "date", or "number").
3790
+ * @param {string} where - The context/method name using this validation.
3791
+ * @returns {any} The extracted value, depending on the type.
3792
+ * @throws {Error} If the element is not an HTMLInputElement or if the type handler is invalid.
3793
+ * @readonly
3794
+ */
3795
+ static _getValByType(elem, type, where) {
3796
+ if (typeof type !== 'string') throw new TypeError('The "type" must be a string.');
3797
+ if (typeof where !== 'string') throw new TypeError('The "where" must be a string.');
3798
+ if (!(elem instanceof HTMLInputElement))
3799
+ throw new Error(`Provided element is not an HTMLInputElement in ${where}().`);
3800
+ if (typeof TinyHtml._valTypes[type] !== 'function')
3801
+ throw new Error(`No handler found for type "${type}" in ${where}().`);
3802
+ // @ts-ignore
3803
+ return TinyHtml._valTypes[type](elem);
3804
+ }
3805
+
3806
+ /**
3807
+ * Retrieves the raw value from the HTML input element.
3808
+ * If a custom value hook exists, it will be used first.
3809
+ *
3810
+ * @param {TinyInputElement} el - Target element.
3811
+ * @param {GetValueTypes} type - The type of value to retrieve ("string", "date", or "number").
3812
+ * @param {string} where - The context/method name using this validation.
3813
+ * @returns {any} The raw value retrieved from the element or hook.
3814
+ * @readonly
3815
+ */
3816
+ static _val(el, where, type) {
3817
+ const elem = TinyHtml._preInputElem(el, where);
3818
+ // @ts-ignore
3819
+ const hook = TinyHtml._valHooks[elem.type] || TinyHtml._valHooks[elem.nodeName.toLowerCase()];
3820
+ if (hook && typeof hook.get === 'function') {
3821
+ const ret = hook.get(elem, 'value', type);
3822
+ if (ret !== undefined) return typeof ret === 'string' ? ret.replace(/\r/g, '') : ret;
3823
+ }
3824
+
3825
+ return TinyHtml._getValByType(elem, type, where);
3826
+ }
3827
+
3828
+ /**
3829
+ * Retrieves the raw value from the HTML input element.
3830
+ * If a custom value hook exists, it will be used first.
3831
+ *
3832
+ * @param {GetValueTypes} type - The type of value to retrieve ("string", "date", or "number").
3833
+ * @param {string} where - The context/method name using this validation.
3834
+ * @returns {any} The raw value retrieved from the element or hook.
3835
+ * @readonly
3836
+ */
3837
+ _val(where, type) {
3838
+ return TinyHtml._val(this, where, type);
3839
+ }
3840
+
3841
+ /**
3842
+ * Gets the value of the current HTML value element.
3843
+ *
3844
+ * @param {TinyInputElement} el - Target element.
3845
+ * @returns {SetValueList} The normalized value, with carriage returns removed.
3846
+ */
3847
+ static val(el) {
3848
+ return /** @type {SetValueList} */ (TinyHtml._val(el, 'val', 'string'));
3849
+ }
3850
+
3851
+ /**
3852
+ * Gets the value of the current HTML value element.
3853
+ *
3854
+ * @returns {SetValueList} The normalized value, with carriage returns removed.
3855
+ */
3856
+ val() {
3857
+ return TinyHtml.val(this);
3858
+ }
3859
+
3860
+ /**
3861
+ * Gets the text of the current HTML value element (for text).
3862
+ *
3863
+ * @param {TinyInputElement} el - Target element.
3864
+ * @returns {string} The text value.
3865
+ * @throws {Error} If the element is not a string value.
3866
+ */
3867
+ static valTxt(el) {
3868
+ /** @type {string} */
3869
+ const ret = TinyHtml._val(el, 'valTxt', 'string');
3870
+ if (typeof ret !== 'string' && ret !== null) throw new Error('Value is not a valid string.');
3871
+ return ret == null ? '' : typeof ret === 'string' ? ret.replace(/\r/g, '') : ret;
3872
+ }
3873
+
3874
+ /**
3875
+ * Gets the text of the current HTML value element (for text).
3876
+ *
3877
+ * @returns {string} The text value.
3878
+ * @throws {Error} If the element is not a string value.
3879
+ */
3880
+ valTxt() {
3881
+ return TinyHtml.valTxt(this);
3882
+ }
3883
+
3884
+ /**
3885
+ * Internal helper to get a value from an input expected to return an array.
3886
+ *
3887
+ * @param {TinyInputElement} el - Target element.
3888
+ * @param {string} where - The method name or context using this validation (for error reporting).
3889
+ * @param {GetValueTypes} type - The type of value to retrieve ("string", "date", or "number").
3890
+ * @returns {SetValueBase[]} - The validated value as an array.
3891
+ * @throws {Error} If the returned value is not an array.
3892
+ * @readonly
3893
+ */
3894
+ static _valArr(el, where, type) {
3895
+ /** @type {SetValueBase[]} */
3896
+ const ret = TinyHtml._val(el, where, type);
3897
+ if (!Array.isArray(ret)) throw new Error(`Value expected an array but got ${typeof ret}.`);
3898
+ return ret;
3899
+ }
3900
+
3901
+ /**
3902
+ * Internal helper to get a value from an input expected to return an array.
3903
+ *
3904
+ * @param {string} where - The method name or context using this validation (for error reporting).
3905
+ * @param {GetValueTypes} type - The type of value to retrieve ("string", "date", or "number").
3906
+ * @returns {SetValueBase[]} - The validated value as an array.
3907
+ * @throws {Error} If the returned value is not an array.
3908
+ * @readonly
3909
+ */
3910
+ _valArr(where, type) {
3911
+ return TinyHtml._valArr(this, where, type);
3912
+ }
3913
+
3914
+ /**
3915
+ * Gets the raw value as a generic array of the current HTML value element (for select).
3916
+ *
3917
+ * @param {TinyInputElement} el - Target element.
3918
+ * @returns {SetValueBase[]} - The value cast as a generic array.
3919
+ * @throws {Error} If the value is not a valid array.
3920
+ */
3921
+ static valArr(el) {
3922
+ return TinyHtml._valArr(el, 'valArr', 'string');
3923
+ }
3924
+
3925
+ /**
3926
+ * Gets the raw value as a generic array of the current HTML value element (for select).
3927
+ *
3928
+ * @returns {SetValueBase[]} - The value cast as a generic array.
3929
+ * @throws {Error} If the value is not a valid array.
3930
+ */
3931
+ valArr() {
3932
+ return TinyHtml.valArr(this);
3933
+ }
3934
+
3935
+ /**
3936
+ * Gets the current value parsed as a number (for number/text).
3937
+ *
3938
+ * @param {TinyInputElement} el - Target element.
3939
+ * @returns {number} The numeric value.
3940
+ * @throws {Error} If the element is not a number-compatible input or value is NaN.
3941
+ */
3942
+ static valNb(el) {
3943
+ const elem = TinyHtml._preInputElem(el, 'valNb');
3944
+ if (!(elem instanceof HTMLInputElement)) throw new Error('Element must be an input element.');
3945
+ /** @type {number} */
3946
+ const result = TinyHtml._val(el, 'valNb', 'number');
3947
+ if (Number.isNaN(result)) throw new Error('Value is not a valid number.');
3948
+ return result;
3949
+ }
3950
+
3951
+ /**
3952
+ * Gets the current value parsed as a number (for number/text).
3953
+ *
3954
+ * @returns {number} The numeric value.
3955
+ * @throws {Error} If the element is not a number-compatible input or value is NaN.
3956
+ */
3957
+ valNb() {
3958
+ return TinyHtml.valNb(this);
3959
+ }
3960
+
3961
+ /**
3962
+ * Gets the current value parsed as a Date (for time/date).
3963
+ *
3964
+ * @param {TinyInputElement} el - Target element.
3965
+ * @returns {Date} The date value.
3966
+ * @throws {Error} If the element is not a date-compatible input.
3967
+ */
3968
+ static valDate(el) {
3969
+ const elem = TinyHtml._preInputElem(el, 'valDate');
3970
+ if (!(elem instanceof HTMLInputElement)) throw new Error('Element must be an input element.');
3971
+ /** @type {Date} */
3972
+ const result = TinyHtml._val(el, 'valDate', 'date');
3973
+ if (!(result instanceof Date)) throw new Error('Value is not a valid date.');
3974
+ return result;
3975
+ }
3976
+
3977
+ /**
3978
+ * Gets the current value parsed as a Date (for time/date).
3979
+ *
3980
+ * @returns {Date} The date value.
3981
+ * @throws {Error} If the element is not a date-compatible input.
3982
+ */
3983
+ valDate() {
3984
+ return TinyHtml.valDate(this);
3985
+ }
3986
+
3987
+ /**
3988
+ * Checks if the input element is boolean (for checkboxes/radios).
3989
+ *
3990
+ * @param {TinyInputElement} el - Target element.
3991
+ * @returns {boolean} True if the input is considered checked (value === "on"), false otherwise.
3992
+ * @throws {Error} If the element is not a checkbox/radio input.
3993
+ */
3994
+ static valBool(el) {
3995
+ const elem = TinyHtml._preInputElem(el, 'valBool');
3996
+ if (!(elem instanceof HTMLInputElement)) throw new Error('Element must be an input element.');
3997
+ return TinyHtml.val(elem) === 'on' ? true : false;
3998
+ }
3999
+
4000
+ /**
4001
+ * Checks if the input element is boolean (for checkboxes/radios).
4002
+ *
4003
+ * @returns {boolean} True if the input is considered checked (value === "on"), false otherwise.
4004
+ * @throws {Error} If the element is not a checkbox/radio input.
4005
+ */
4006
+ valBool() {
4007
+ return TinyHtml.valBool(this);
4008
+ }
4009
+
4010
+ ////////////////////////////////////////////
4011
+
4012
+ /**
4013
+ * Registers an event listener on the specified element.
4014
+ *
4015
+ * @param {TinyEventTarget|TinyEventTarget[]} el - The target to listen on.
4016
+ * @param {string} event - The event type (e.g. 'click', 'keydown').
4017
+ * @param {EventRegistryHandle} handler - The callback function to run on event.
4018
+ * @param {EventRegistryOptions} [options] - Optional event listener options.
4019
+ */
4020
+ static on(el, event, handler, options) {
4021
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
4022
+ TinyHtml._preEventTargetElems(el, 'on').forEach((elem) => {
4023
+ elem.addEventListener(event, handler, options);
4024
+
4025
+ if (!__eventRegistry.has(elem)) __eventRegistry.set(elem, {});
4026
+ const events = __eventRegistry.get(elem);
4027
+ if (!events) return;
4028
+ if (!Array.isArray(events[event])) events[event] = [];
4029
+ events[event].push({ handler, options });
4030
+ });
4031
+ }
4032
+
4033
+ /**
4034
+ * Registers an event listener on the specified element.
4035
+ *
4036
+ * @param {string} event - The event type (e.g. 'click', 'keydown').
4037
+ * @param {EventRegistryHandle} handler - The callback function to run on event.
4038
+ * @param {EventRegistryOptions} [options] - Optional event listener options.
4039
+ */
4040
+ on(event, handler, options) {
4041
+ return TinyHtml.on(this, event, handler, options);
4042
+ }
4043
+
4044
+ /**
4045
+ * Registers an event listener that runs only once, then is removed.
4046
+ *
4047
+ * @param {TinyEventTarget|TinyEventTarget[]} el - The target to listen on.
4048
+ * @param {string} event - The event type (e.g. 'click', 'keydown').
4049
+ * @param {EventRegistryHandle} handler - The callback function to run on event.
4050
+ * @param {EventRegistryOptions} [options={}] - Optional event listener options.
4051
+ */
4052
+ static once(el, event, handler, options = {}) {
4053
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
4054
+ TinyHtml._preEventTargetElems(el, 'once').forEach((elem) => {
4055
+ /** @type {EventRegistryHandle} e */
4056
+ const wrapped = (e) => {
4057
+ TinyHtml.off(elem, event, wrapped);
4058
+ handler(e);
4059
+ };
4060
+
4061
+ TinyHtml.on(
4062
+ elem,
4063
+ event,
4064
+ wrapped,
4065
+ typeof options === 'boolean' ? options : { ...options, once: true },
4066
+ );
4067
+ });
4068
+ }
4069
+
4070
+ /**
4071
+ * Registers an event listener that runs only once, then is removed.
4072
+ *
4073
+ * @param {string} event - The event type (e.g. 'click', 'keydown').
4074
+ * @param {EventRegistryHandle} handler - The callback function to run on event.
4075
+ * @param {EventRegistryOptions} [options={}] - Optional event listener options.
4076
+ */
4077
+ once(event, handler, options = {}) {
4078
+ return TinyHtml.once(this, event, handler, options);
4079
+ }
4080
+
4081
+ /**
4082
+ * Removes a specific event listener from an element.
4083
+ *
4084
+ * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
4085
+ * @param {string} event - The event type.
4086
+ * @param {EventRegistryHandle} handler - The function originally bound to the event.
4087
+ * @param {boolean|EventListenerOptions} [options] - Optional listener options.
4088
+ */
4089
+ static off(el, event, handler, options) {
4090
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
4091
+ TinyHtml._preEventTargetElems(el, 'off').forEach((elem) => {
4092
+ elem.removeEventListener(event, handler, options);
4093
+
4094
+ const events = __eventRegistry.get(elem);
4095
+ if (events && events[event]) {
4096
+ events[event] = events[event].filter((entry) => entry.handler !== handler);
4097
+ if (events[event].length === 0) delete events[event];
4098
+ }
4099
+ });
4100
+ }
4101
+
4102
+ /**
4103
+ * Removes a specific event listener from an element.
4104
+ *
4105
+ * @param {string} event - The event type.
4106
+ * @param {EventRegistryHandle} handler - The function originally bound to the event.
4107
+ * @param {boolean|EventListenerOptions} [options] - Optional listener options.
4108
+ */
4109
+ off(event, handler, options) {
4110
+ return TinyHtml.off(this, event, handler, options);
4111
+ }
4112
+
4113
+ /**
4114
+ * Removes all event listeners of a specific type from the element.
4115
+ *
4116
+ * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
4117
+ * @param {string} event - The event type to remove (e.g. 'click').
4118
+ */
4119
+ static offAll(el, event) {
4120
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
4121
+ TinyHtml._preEventTargetElems(el, 'offAll').forEach((elem) => {
4122
+ const events = __eventRegistry.get(elem);
4123
+ if (events && events[event]) {
4124
+ for (const entry of events[event]) {
4125
+ elem.removeEventListener(event, entry.handler, entry.options);
4126
+ }
4127
+ delete events[event];
4128
+ }
4129
+ });
4130
+ }
4131
+
4132
+ /**
4133
+ * Removes all event listeners of a specific type from the element.
4134
+ *
4135
+ * @param {string} event - The event type to remove (e.g. 'click').
4136
+ */
4137
+ offAll(event) {
4138
+ return TinyHtml.offAll(this, event);
4139
+ }
4140
+
4141
+ /**
4142
+ * Removes all event listeners of all types from the element.
4143
+ *
4144
+ * @param {TinyEventTarget|TinyEventTarget[]} el - The target element.
4145
+ * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
4146
+ * Optional filter function to selectively remove specific handlers.
4147
+ */
4148
+ static offAllTypes(el, filterFn = null) {
4149
+ if (filterFn !== null && typeof filterFn !== 'function')
4150
+ throw new TypeError('The "filterFn" must be a function.');
4151
+ TinyHtml._preEventTargetElems(el, 'offAllTypes').forEach((elem) => {
4152
+ const events = __eventRegistry.get(elem);
4153
+ if (!events) return;
4154
+
4155
+ for (const event in events) {
4156
+ for (const entry of events[event]) {
4157
+ if (typeof filterFn !== 'function' || filterFn(entry.handler, event)) {
4158
+ elem.removeEventListener(event, entry.handler, entry.options);
4159
+ }
4160
+ }
4161
+ }
4162
+
4163
+ __eventRegistry.delete(elem);
4164
+ });
4165
+ }
4166
+
4167
+ /**
4168
+ * Removes all event listeners of all types from the element.
4169
+ *
4170
+ * @param {((handler: EventListenerOrEventListenerObject, event: string) => boolean)|null} [filterFn=null] -
4171
+ * Optional filter function to selectively remove specific handlers.
4172
+ */
4173
+ offAllTypes(filterFn = null) {
4174
+ return TinyHtml.offAllTypes(this, filterFn);
4175
+ }
4176
+
4177
+ /**
4178
+ * Triggers all handlers associated with a specific event on the given element.
4179
+ *
4180
+ * @param {TinyEventTarget|TinyEventTarget[]} el - Target element where the event should be triggered.
4181
+ * @param {string} event - Name of the event to trigger.
4182
+ * @param {Event|CustomEvent|CustomEventInit} [payload] - Optional event object or data to pass.
4183
+ */
4184
+ static trigger(el, event, payload = {}) {
4185
+ if (typeof event !== 'string') throw new TypeError('The event name must be a string.');
4186
+ TinyHtml._preEventTargetElems(el, 'trigger').forEach((elem) => {
4187
+ const evt =
4188
+ payload instanceof Event || payload instanceof CustomEvent
4189
+ ? payload
4190
+ : new CustomEvent(event, {
4191
+ bubbles: true,
4192
+ cancelable: true,
4193
+ detail: payload,
4194
+ });
4195
+
4196
+ elem.dispatchEvent(evt);
4197
+ });
4198
+ }
4199
+
4200
+ /**
4201
+ * Triggers all handlers associated with a specific event on the given element.
4202
+ *
4203
+ * @param {string} event - Name of the event to trigger.
4204
+ * @param {Event|CustomEvent|CustomEventInit} [payload] - Optional event object or data to pass.
4205
+ */
4206
+ trigger(event, payload = {}) {
4207
+ return TinyHtml.trigger(this, event, payload);
4208
+ }
4209
+
4210
+ ///////////////////////////////////////////////////////////////
4211
+
4212
+ /**
4213
+ * Property name normalization similar to jQuery's propFix.
4214
+ * @readonly
4215
+ */
4216
+ static _propFix = {
4217
+ for: 'htmlFor',
4218
+ class: 'className',
4219
+ };
4220
+
4221
+ /**
4222
+ * Get an attribute on an element.
4223
+ * @param {TinyElement} el
4224
+ * @param {string} name
4225
+ * @returns {string|null}
4226
+ */
4227
+ static attr(el, name) {
4228
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4229
+ const elem = TinyHtml._preElem(el, 'attr');
4230
+ return elem.getAttribute(name);
4231
+ }
4232
+
4233
+ /**
4234
+ * Get an attribute on an element.
4235
+ * @param {string} name
4236
+ * @returns {string|null}
4237
+ */
4238
+ attr(name) {
4239
+ return TinyHtml.attr(this, name);
4240
+ }
4241
+
4242
+ /**
4243
+ * Set an attribute on an element.
4244
+ * @param {TinyElement|TinyElement[]} el
4245
+ * @param {string} name
4246
+ * @param {string|null} [value=null]
4247
+ */
4248
+ static setAttr(el, name, value = null) {
4249
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4250
+ if (value !== null && typeof value !== 'string')
4251
+ throw new TypeError('The "value" must be a string.');
4252
+ TinyHtml._preElems(el, 'setAttr').forEach((elem) => {
4253
+ if (value === null) elem.removeAttribute(name);
4254
+ else elem.setAttribute(name, value);
4255
+ });
4256
+ }
4257
+
4258
+ /**
4259
+ * Set an attribute on an element.
4260
+ * @param {string} name
4261
+ * @param {string|null} [value=null]
4262
+ */
4263
+ setAttr(name, value) {
4264
+ return TinyHtml.setAttr(this, name, value);
4265
+ }
4266
+
4267
+ /**
4268
+ * Remove attribute(s) from an element.
4269
+ * @param {TinyElement|TinyElement[]} el
4270
+ * @param {string} name Space-separated list of attributes.
4271
+ */
4272
+ static removeAttr(el, name) {
4273
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4274
+ TinyHtml._preElems(el, 'removeAttr').forEach((elem) => elem.removeAttribute(name));
4275
+ }
4276
+
4277
+ /**
4278
+ * Remove attribute(s) from an element.
4279
+ * @param {string} name Space-separated list of attributes.
4280
+ */
4281
+ removeAttr(name) {
4282
+ return TinyHtml.removeAttr(this, name);
4283
+ }
4284
+
4285
+ /**
4286
+ * Check if an attribute exists on an element.
4287
+ * @param {TinyElement} el
4288
+ * @param {string} name
4289
+ * @returns {boolean}
4290
+ */
4291
+ static hasAttr(el, name) {
4292
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4293
+ const elem = TinyHtml._preElem(el, 'hasAttr');
4294
+ return elem.hasAttribute(name);
4295
+ }
4296
+
4297
+ /**
4298
+ * Check if an attribute exists on an element.
4299
+ * @param {string} name
4300
+ * @returns {boolean}
4301
+ */
4302
+ hasAttr(name) {
4303
+ return TinyHtml.hasAttr(this, name);
4304
+ }
4305
+
4306
+ /**
4307
+ * Check if a property exists.
4308
+ * @param {TinyElement} el
4309
+ * @param {string} name
4310
+ * @returns {boolean}
4311
+ */
4312
+ static hasProp(el, name) {
4313
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4314
+ const elem = TinyHtml._preElem(el, 'hasProp');
4315
+ // @ts-ignore
4316
+ const propName = TinyHtml._propFix[name] || name;
4317
+ // @ts-ignore
4318
+ return !!elem[propName];
4319
+ }
4320
+
4321
+ /**
4322
+ * Check if a property exists.
4323
+ * @param {string} name
4324
+ * @returns {boolean}
4325
+ */
4326
+ hasProp(name) {
4327
+ return TinyHtml.hasProp(this, name);
4328
+ }
4329
+
4330
+ /**
4331
+ * Set a property on an element.
4332
+ * @param {TinyElement|TinyElement[]} el
4333
+ * @param {string} name
4334
+ */
4335
+ static addProp(el, name) {
4336
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4337
+ TinyHtml._preElems(el, 'addProp').forEach((elem) => {
4338
+ // @ts-ignore
4339
+ name = TinyHtml._propFix[name] || name;
4340
+ // @ts-ignore
4341
+ elem[name] = true;
4342
+ });
4343
+ }
4344
+
4345
+ /**
4346
+ * Set a property on an element.
4347
+ * @param {string} name
4348
+ */
4349
+ addProp(name) {
4350
+ return TinyHtml.addProp(this, name);
4351
+ }
4352
+
4353
+ /**
4354
+ * Remove a property from an element.
4355
+ * @param {TinyElement|TinyElement[]} el
4356
+ * @param {string} name
4357
+ */
4358
+ static removeProp(el, name) {
4359
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4360
+ TinyHtml._preElems(el, 'removeProp').forEach((elem) => {
4361
+ // @ts-ignore
4362
+ name = TinyHtml._propFix[name] || name;
4363
+ // @ts-ignore
4364
+ elem[name] = false;
4365
+ });
4366
+ }
4367
+
4368
+ /**
4369
+ * Remove a property from an element.
4370
+ * @param {string} name
4371
+ */
4372
+ removeProp(name) {
4373
+ return TinyHtml.removeProp(this, name);
4374
+ }
4375
+
4376
+ /**
4377
+ * Toggle a boolean property.
4378
+ * @param {TinyElement|TinyElement[]} el
4379
+ * @param {string} name
4380
+ * @param {boolean} [force]
4381
+ */
4382
+ static toggleProp(el, name, force) {
4383
+ if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
4384
+ if (typeof force !== 'undefined' && typeof force !== 'boolean')
4385
+ throw new TypeError('The "force" must be a boolean.');
4386
+ TinyHtml._preElems(el, 'toggleProp').forEach((elem) => {
4387
+ // @ts-ignore
4388
+ const propName = TinyHtml._propFix[name] || name;
4389
+ // @ts-ignore
4390
+ const shouldEnable = force === undefined ? !elem[propName] : force;
4391
+ // @ts-ignore
4392
+ if (shouldEnable) TinyHtml.addProp(elem, name);
4393
+ else TinyHtml.removeProp(elem, name);
4394
+ });
4395
+ }
4396
+
4397
+ /**
4398
+ * Toggle a boolean property.
4399
+ * @param {string} name
4400
+ * @param {boolean} [force]
4401
+ */
4402
+ toggleProp(name, force) {
4403
+ return TinyHtml.toggleProp(this, name, force);
4404
+ }
4405
+
4406
+ /////////////////////////////////////////////////////
4407
+
4408
+ /**
4409
+ * Removes an element from the DOM.
4410
+ * @param {TinyElement|TinyElement[]} el - The DOM element or selector to remove.
4411
+ */
4412
+ static remove(el) {
4413
+ TinyHtml._preElems(el, 'remove').forEach((elem) => elem.remove());
4414
+ }
4415
+
4416
+ /**
4417
+ * Removes the element from the DOM.
4418
+ */
4419
+ remove() {
4420
+ return TinyHtml.remove(this);
4421
+ }
4422
+
4423
+ /**
4424
+ * Returns the index of the first element within its parent or relative to a selector/element.
4425
+ *
4426
+ * @param {TinyElement} el - The element target
4427
+ * @param {string|TinyElement|null} [el2] - Optional target to compare index against.
4428
+ * @returns {number}
4429
+ */
4430
+ static index(el, el2 = null) {
4431
+ const elem = TinyHtml._preElem(el, 'index');
4432
+ if (!elem) return -1;
4433
+
4434
+ if (!el2) {
4435
+ return Array.prototype.indexOf.call(elem.parentNode?.children || [], elem);
4436
+ }
4437
+
4438
+ if (el2) {
4439
+ const matchEls =
4440
+ typeof el2 === 'string' ? document.querySelectorAll(el2) : TinyHtml._preElems(el2, 'index');
4441
+ return Array.prototype.indexOf.call(matchEls, elem);
4442
+ }
4443
+
4444
+ return -1;
4445
+ }
4446
+
4447
+ /**
4448
+ * Returns the index of the first element within its parent or relative to a selector/element.
4449
+ *
4450
+ * @param {string|TinyElement|null} [elem] - Optional target to compare index against.
4451
+ * @returns {number}
4452
+ */
4453
+ index(elem) {
4454
+ return TinyHtml.index(this, elem);
4455
+ }
4456
+
4457
+ ////////////////////////////////////////////////////////////////////
4458
+
4459
+ /**
4460
+ * Creates a new DOMRect object by copying the base rect and applying optional additional dimensions.
4461
+ *
4462
+ * @param {DOMRect} rect - The base rectangle to be cloned and extended.
4463
+ * @param {Partial<DOMRect>} extraRect - Additional dimensions to apply to the base rect (e.g., extra padding or offset).
4464
+ * @returns {DOMRect} - A new DOMRect object with the combined dimensions.
4465
+ * @readonly
4466
+ */
4467
+ static _getCustomRect(rect, extraRect) {
4468
+ /** @type {DOMRect} */
4469
+ const result = {
4470
+ height: 0,
4471
+ width: 0,
4472
+ x: 0,
4473
+ y: 0,
4474
+ bottom: 0,
4475
+ left: 0,
4476
+ right: 0,
4477
+ top: 0,
4478
+ toJSON: function () {
4479
+ throw new Error('Function not implemented.');
4480
+ },
4481
+ };
4482
+ for (const name in rect) {
4483
+ // @ts-ignore
4484
+ if (typeof rect[name] === 'number')
4485
+ // @ts-ignore
4486
+ result[name] = rect[name];
4487
+ }
4488
+
4489
+ if (typeof extraRect !== 'object' || extraRect === null || Array.isArray(extraRect))
4490
+ throw new Error('');
4491
+ const { height = 0, width = 0, top = 0, bottom = 0, left = 0, right = 0 } = extraRect;
4492
+ if (typeof height !== 'number') throw new Error('');
4493
+ if (typeof width !== 'number') throw new Error('');
4494
+ if (typeof top !== 'number') throw new Error('');
4495
+ if (typeof bottom !== 'number') throw new Error('');
4496
+ if (typeof left !== 'number') throw new Error('');
4497
+ if (typeof right !== 'number') throw new Error('');
4498
+
4499
+ // @ts-ignore
4500
+ result.height += height;
4501
+ // @ts-ignore
4502
+ result.width += width;
4503
+ // @ts-ignore
4504
+ result.top += top;
4505
+ // @ts-ignore
4506
+ result.bottom += bottom;
4507
+ // @ts-ignore
4508
+ result.left += left;
4509
+ // @ts-ignore
4510
+ result.right += right;
4511
+ return result;
4512
+ }
4513
+
4514
+ /**
4515
+ * Determines if two HTML elements are colliding, using a simple bounding box comparison.
4516
+ *
4517
+ * @param {TinyElement} el1 - The first element to compare.
4518
+ * @param {TinyElement} el2 - The second element to compare.
4519
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4520
+ * @returns {boolean} - `true` if the elements are colliding, `false` otherwise.
4521
+ */
4522
+ static isCollWith(el1, el2, extraRect = {}) {
4523
+ const rect1 = TinyHtml._getCustomRect(
4524
+ TinyHtml._preElem(el1, 'isCollWith').getBoundingClientRect(),
4525
+ extraRect,
4526
+ );
4527
+ const rect2 = TinyHtml._preElem(el2, 'isCollWith').getBoundingClientRect();
4528
+ return TinyHtml_areElsColliding(rect1, rect2);
4529
+ }
4530
+
4531
+ /**
4532
+ * Determines if two HTML elements are colliding, using a simple bounding box comparison.
4533
+ *
4534
+ * @param {TinyElement} el2 - The second element to compare.
4535
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4536
+ * @returns {boolean} - `true` if the elements are colliding, `false` otherwise.
4537
+ */
4538
+ isCollWith(el2, extraRect) {
4539
+ return TinyHtml.isCollWith(this, el2, extraRect);
4540
+ }
4541
+
4542
+ /**
4543
+ * Determines if two HTML elements are colliding using a pixel-perfect collision algorithm.
4544
+ *
4545
+ * @param {TinyElement} el1 - The first element to compare.
4546
+ * @param {TinyElement} el2 - The second element to compare.
4547
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4548
+ * @returns {boolean} - `true` if the elements are colliding with higher precision, `false` otherwise.
4549
+ */
4550
+ static isCollPerfWith(el1, el2, extraRect = {}) {
4551
+ const rect1 = TinyHtml._getCustomRect(
4552
+ TinyHtml._preElem(el1, 'isCollPerfWith').getBoundingClientRect(),
4553
+ extraRect,
4554
+ );
4555
+ const rect2 = TinyHtml._preElem(el2, 'isCollPerfWith').getBoundingClientRect();
4556
+ return TinyHtml_areElsPerfColliding(rect1, rect2);
4557
+ }
4558
+
4559
+ /**
4560
+ * Determines if two HTML elements are colliding using a pixel-perfect collision algorithm.
4561
+ *
4562
+ * @param {TinyElement} el2 - The second element to compare.
4563
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4564
+ * @returns {boolean} - `true` if the elements are colliding with higher precision, `false` otherwise.
4565
+ */
4566
+ isCollPerfWith(el2, extraRect) {
4567
+ return TinyHtml.isCollPerfWith(this, el2, extraRect);
4568
+ }
4569
+
4570
+ /**
4571
+ * Determines whether two elements are colliding with a directional lock mechanism.
4572
+ *
4573
+ * This function tracks the direction from which an element (`elem1`) initially collided with another,
4574
+ * and keeps the collision "locked" until the element exits the collision from the same direction.
4575
+ *
4576
+ * - If `isColliding` is true and no lock is stored yet, it saves the direction of entry.
4577
+ * - If `isColliding` is false but a previous lock exists, it checks if the element has exited
4578
+ * in the same direction it entered to remove the lock.
4579
+ *
4580
+ * @param {boolean} isColliding - Indicates whether `rect1` and `rect2` are currently colliding.
4581
+ * @param {DOMRect} rect1 - The bounding box of the first element.
4582
+ * @param {DOMRect} rect2 - The bounding box of the second element.
4583
+ * @param {Element} elem1 - The element to track collision state for.
4584
+ * @param {CollisionDirLock} lockDirection - The direction from which the collision was first detected.
4585
+ * @returns {boolean} Returns `true` if the element is still considered colliding (locked), otherwise `false`.
4586
+ * @readonly
4587
+ */
4588
+ static _isCollWithLock(isColliding, rect1, rect2, elem1, lockDirection) {
4589
+ const lockMap = __elemCollision[lockDirection];
4590
+
4591
+ if (isColliding) {
4592
+ // Save entry direction
4593
+ if (!lockMap.has(elem1)) {
4594
+ lockMap.set(elem1, true);
4595
+ }
4596
+ return true;
4597
+ }
4598
+
4599
+ // Handle unlock logic
4600
+ if (lockMap.has(elem1)) {
4601
+ let shouldUnlock = false;
4602
+
4603
+ switch (lockDirection) {
4604
+ case 'top':
4605
+ shouldUnlock = TinyHtml_areElsCollTop(rect1, rect2);
4606
+ break;
4607
+ case 'bottom':
4608
+ shouldUnlock = TinyHtml_areElsCollBottom(rect1, rect2);
4609
+ break;
4610
+ case 'left':
4611
+ shouldUnlock = TinyHtml_areElsCollLeft(rect1, rect2);
4612
+ break;
4613
+ case 'right':
4614
+ shouldUnlock = TinyHtml_areElsCollRight(rect1, rect2);
4615
+ break;
4616
+ }
4617
+
4618
+ if (shouldUnlock) lockMap.delete(elem1);
4619
+
4620
+ return lockMap.has(elem1); // still colliding (locked)
4621
+ }
4622
+
4623
+ return false;
4624
+ }
4625
+
4626
+ /**
4627
+ * Checks if two DOM elements are colliding on the screen, and locks the collision
4628
+ * until the element exits through the same side it entered.
4629
+ *
4630
+ * @param {TinyElement} el1 - First DOM element (e.g. draggable or moving element).
4631
+ * @param {TinyElement} el2 - Second DOM element (e.g. a container or boundary element).
4632
+ * @param {CollisionDirLock} lockDirection - Direction that must be respected to unlock the collision.
4633
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4634
+ * @returns {boolean} True if collision is still active.
4635
+ */
4636
+ static isCollWithLock(el1, el2, lockDirection, extraRect = {}) {
4637
+ const elem1 = TinyHtml._preElem(el1, 'isCollWithLock');
4638
+ const elem2 = TinyHtml._preElem(el2, 'isCollWithLock');
4639
+ const rect1 = TinyHtml._getCustomRect(elem1.getBoundingClientRect(), extraRect);
4640
+ const rect2 = elem2.getBoundingClientRect();
4641
+ const isColliding = TinyHtml_areElsColliding(rect1, rect2);
4642
+ return TinyHtml._isCollWithLock(isColliding, rect1, rect2, elem1, lockDirection);
4643
+ }
4644
+
4645
+ /**
4646
+ * Checks if two DOM elements are colliding on the screen, and locks the collision
4647
+ * until the element exits through the same side it entered.
4648
+ *
4649
+ * @param {TinyElement} el2 - Second DOM element (e.g. a container or boundary element).
4650
+ * @param {CollisionDirLock} lockDirection - Direction that must be respected to unlock the collision.
4651
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4652
+ * @returns {boolean} True if collision is still active.
4653
+ */
4654
+ isCollWithLock(el2, lockDirection, extraRect) {
4655
+ return TinyHtml.isCollWithLock(this, el2, lockDirection, extraRect);
4656
+ }
4657
+
4658
+ /**
4659
+ * Checks if two DOM elements are colliding on the screen, and locks the collision
4660
+ * until the element exits through the same side it entered.
4661
+ *
4662
+ * @param {TinyElement} el1 - First DOM element (e.g. draggable or moving element).
4663
+ * @param {TinyElement} el2 - Second DOM element (e.g. a container or boundary element).
4664
+ * @param {CollisionDirLock} lockDirection - Direction that must be respected to unlock the collision.
4665
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4666
+ * @returns {boolean} True if collision is still active.
4667
+ */
4668
+ static isCollPerfWithLock(el1, el2, lockDirection, extraRect = {}) {
4669
+ const elem1 = TinyHtml._preElem(el1, 'isCollPerfWithLock');
4670
+ const elem2 = TinyHtml._preElem(el2, 'isCollPerfWithLock');
4671
+ const rect1 = TinyHtml._getCustomRect(elem1.getBoundingClientRect(), extraRect);
4672
+ const rect2 = elem2.getBoundingClientRect();
4673
+ const isColliding = TinyHtml_areElsPerfColliding(rect1, rect2);
4674
+ return TinyHtml._isCollWithLock(isColliding, rect1, rect2, elem1, lockDirection);
4675
+ }
4676
+
4677
+ /**
4678
+ * Checks if two DOM elements are colliding on the screen, and locks the collision
4679
+ * until the element exits through the same side it entered.
4680
+ *
4681
+ * @param {TinyElement} el2 - Second DOM element (e.g. a container or boundary element).
4682
+ * @param {CollisionDirLock} lockDirection - Direction that must be respected to unlock the collision.
4683
+ * @param {Partial<ObjRect>} [extraRect] - Optional values to expand the size of the first element's rect.
4684
+ * @returns {boolean} True if collision is still active.
4685
+ */
4686
+ isCollPerfWithLock(el2, lockDirection, extraRect) {
4687
+ return TinyHtml.isCollPerfWithLock(this, el2, lockDirection, extraRect);
4688
+ }
4689
+
4690
+ /**
4691
+ * Resets all collision locks for a specific element (for all directions).
4692
+ *
4693
+ * @param {TinyElement} el - The element whose locks should be removed.
4694
+ * @returns {boolean} True if at least one lock was removed.
4695
+ */
4696
+ static resetCollLock(el) {
4697
+ const elem = TinyHtml._preElem(el, 'resetCollLock');
4698
+ let removed = false;
4699
+
4700
+ for (const dir of /** @type {CollisionDirLock[]} */ (['top', 'bottom', 'left', 'right'])) {
4701
+ if (__elemCollision[dir].has(elem)) {
4702
+ __elemCollision[dir].delete(elem);
4703
+ removed = true;
4704
+ }
4705
+ }
4706
+
4707
+ return removed;
4708
+ }
4709
+
4710
+ /**
4711
+ * Resets the collision lock for a specific element.
4712
+ *
4713
+ * This removes any previously stored collision direction for the given element,
4714
+ * effectively unlocking its collision state.
4715
+ *
4716
+ * @returns {boolean} Returns `true` if a lock was removed, `false` if no lock was present.
4717
+ */
4718
+ resetCollLock() {
4719
+ return TinyHtml.resetCollLock(this);
4720
+ }
4721
+
4722
+ /**
4723
+ * Resets the collision lock for a specific element and direction.
4724
+ *
4725
+ * @param {TinyElement} el - The element whose lock should be removed.
4726
+ * @param {CollisionDirLock} direction - The direction to clear the lock from.
4727
+ * @returns {boolean} True if the lock was removed.
4728
+ */
4729
+ static resetCollLockDir(el, direction) {
4730
+ const elem = TinyHtml._preElem(el, 'resetCollLockDir');
4731
+ const lockMap = __elemCollision[direction];
4732
+
4733
+ if (lockMap.has(elem)) {
4734
+ lockMap.delete(elem);
4735
+ return true;
4736
+ }
4737
+
4738
+ return false;
4739
+ }
4740
+
4741
+ /**
4742
+ * Resets the collision lock for a specific element and direction.
4743
+ *
4744
+ * @param {CollisionDirLock} direction - The direction to clear the lock from.
4745
+ * @returns {boolean} True if the lock was removed.
4746
+ */
4747
+ resetCollLockDir(direction) {
4748
+ return TinyHtml.resetCollLockDir(this, direction);
4749
+ }
4750
+
4751
+ //////////////////////////////////////////////////////////////////////////////
4752
+
4753
+ /**
4754
+ * Checks if the given element is at least partially visible in the viewport.
4755
+ *
4756
+ * @param {TinyElement} el - The DOM element to check.
4757
+ * @returns {boolean} True if the element is partially in the viewport, false otherwise.
4758
+ */
4759
+ static isInViewport(el) {
4760
+ const elem = TinyHtml._preElem(el, 'isInViewport');
4761
+ const elementTop = TinyHtml.offset(elem).top;
4762
+ const elementBottom = elementTop + TinyHtml.outerHeight(elem);
4763
+
4764
+ const viewportTop = TinyHtml.scrollTop(window);
4765
+ const viewportBottom = viewportTop + TinyHtml.height(window);
4766
+
4767
+ return elementBottom > viewportTop && elementTop < viewportBottom;
4768
+ }
4769
+
4770
+ /**
4771
+ * Checks if the given element is at least partially visible in the viewport.
4772
+ *
4773
+ * @returns {boolean} True if the element is partially in the viewport, false otherwise.
4774
+ */
4775
+ isInViewport() {
4776
+ return TinyHtml.isInViewport(this);
4777
+ }
4778
+
4779
+ /**
4780
+ * Checks if the given element is fully visible in the viewport (top and bottom).
4781
+ *
4782
+ * @param {TinyElement} el - The DOM element to check.
4783
+ * @returns {boolean} True if the element is fully visible in the viewport, false otherwise.
4784
+ */
4785
+ static isScrolledIntoView(el) {
4786
+ const elem = TinyHtml._preElem(el, 'isScrolledIntoView');
4787
+ const docViewTop = TinyHtml.scrollTop(window);
4788
+ const docViewBottom = docViewTop + TinyHtml.height(window);
4789
+
4790
+ const elemTop = TinyHtml.offset(elem).top;
4791
+ const elemBottom = elemTop + TinyHtml.height(elem);
4792
+
4793
+ return elemBottom <= docViewBottom && elemTop >= docViewTop;
4794
+ }
4795
+
4796
+ /**
4797
+ * Checks if the given element is fully visible in the viewport (top and bottom).
4798
+ *
4799
+ * @returns {boolean} True if the element is fully visible in the viewport, false otherwise.
4800
+ */
4801
+ isScrolledIntoView() {
4802
+ return TinyHtml.isScrolledIntoView(this);
4803
+ }
4804
+ }
4805
+
4806
+ /* harmony default export */ const libs_TinyHtml = (TinyHtml);
4807
+
4808
+ ;// ./src/v1/build/TinyHtml.mjs
4809
+
4810
+
4811
+
4812
+
4813
+ window.TinyHtml = __webpack_exports__.TinyHtml;
4814
+ /******/ })()
4815
+ ;