tiny-essentials 1.21.10 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.vscode/extensions.json +30 -0
  2. package/.vscode/settings.json +53 -0
  3. package/LICENSE +160 -669
  4. package/dist/v1/TinyBasicsEs.min.js +1 -1
  5. package/dist/v1/TinyDragger.min.js +1 -1
  6. package/dist/v1/TinyElementObserver.min.js +1 -0
  7. package/dist/v1/TinyEssentials.min.js +1 -1
  8. package/dist/v1/TinyHtml.min.js +1 -1
  9. package/dist/v1/TinySmartScroller.min.js +1 -1
  10. package/dist/v1/TinyUploadClicker.min.js +1 -1
  11. package/dist/v1/basics/array.cjs +12 -0
  12. package/dist/v1/basics/array.d.mts +9 -0
  13. package/dist/v1/basics/array.mjs +10 -0
  14. package/dist/v1/basics/index.cjs +2 -0
  15. package/dist/v1/basics/index.d.mts +3 -1
  16. package/dist/v1/basics/index.mjs +3 -3
  17. package/dist/v1/basics/text.cjs +44 -9
  18. package/dist/v1/basics/text.d.mts +14 -2
  19. package/dist/v1/basics/text.mjs +38 -9
  20. package/dist/v1/build/TinyElementObserver.cjs +7 -0
  21. package/dist/v1/build/TinyElementObserver.d.mts +3 -0
  22. package/dist/v1/build/TinyElementObserver.mjs +2 -0
  23. package/dist/v1/index.cjs +4 -0
  24. package/dist/v1/index.d.mts +4 -1
  25. package/dist/v1/index.mjs +4 -3
  26. package/dist/v1/libs/TinyElementObserver.cjs +292 -0
  27. package/dist/v1/libs/TinyElementObserver.d.mts +154 -0
  28. package/dist/v1/libs/TinyElementObserver.mjs +266 -0
  29. package/dist/v1/libs/TinyGamepad.d.mts +1 -1
  30. package/dist/v1/libs/TinyHtml.cjs +1418 -131
  31. package/dist/v1/libs/TinyHtml.d.mts +513 -12
  32. package/dist/v1/libs/TinyHtml.mjs +1129 -14
  33. package/dist/v1/libs/TinyInventory.d.mts +1 -1
  34. package/dist/v1/libs/UltraRandomMsgGen.d.mts +7 -7
  35. package/docs/v1/README.md +1 -0
  36. package/docs/v1/basics/array.md +20 -0
  37. package/docs/v1/basics/text.md +38 -7
  38. package/docs/v1/libs/TinyElementObserver.md +107 -0
  39. package/docs/v1/libs/TinyHtml.md +797 -3
  40. package/package.json +2 -2
@@ -1,9 +1,56 @@
1
+ import { diffArrayList } from '../basics/array.mjs';
2
+ import { diffStrings } from '../basics/text.mjs';
1
3
  import * as TinyCollision from '../basics/collision.mjs';
4
+ import TinyElementObserver from './TinyElementObserver.mjs';
5
+ // TITLE: Introduction
2
6
  const { areElsColliding, areElsPerfColliding, areElsCollTop, areElsCollBottom, areElsCollLeft, areElsCollRight, } = TinyCollision;
3
7
  /**
4
8
  * Represents a TinyHtml instance with any constructor element values.
5
9
  * @typedef {TinyHtml<ConstructorElValues>} TinyHtmlAny
6
10
  */
11
+ /**
12
+ * Callback function used for hover events.
13
+ * @callback HoverEventCallback
14
+ * @param {MouseEvent} ev - The mouse event triggered on enter or leave.
15
+ * @returns {void} Returns nothing.
16
+ */
17
+ /**
18
+ * Represents a collection of active style-based animations.
19
+ *
20
+ * Each HTMLElement is associated with an array of its currently running
21
+ * `Animation` objects (from the Web Animations API).
22
+ *
23
+ * @typedef {Map<HTMLElement, Animation|null>} StyleFxResult
24
+ */
25
+ /**
26
+ * Represents a collection of animation keyframe data mapped by CSS property.
27
+ *
28
+ * - The **key** is the CSS property name (e.g. `"height"`, `"opacity"`).
29
+ * - The **value** is an array of values representing the start and end
30
+ * states of the property during the animation.
31
+ *
32
+ * @typedef {Record<string, (string|number)[]>} AnimationSfxData
33
+ */
34
+ /**
35
+ * Function signature for style effects repeat detectors.
36
+ *
37
+ * @typedef {(effects: AnimationSfxData) => boolean} StyleEffectsRdFn
38
+ */
39
+ /**
40
+ * Function signature for style effect property handlers.
41
+ *
42
+ * @typedef {(
43
+ * el: HTMLElement,
44
+ * keyframes: AnimationSfxData,
45
+ * prop: string,
46
+ * style: CSSStyleDeclaration
47
+ * ) => void} StyleEffectsFn
48
+ */
49
+ /**
50
+ * A collection of style effect property handlers.
51
+ *
52
+ * @typedef {Record<string, StyleEffectsFn>} StyleEffectsProps
53
+ */
7
54
  /**
8
55
  * Callback invoked on each animation frame with the current scroll position,
9
56
  * normalized animation time (`0` to `1`), and a completion flag.
@@ -143,6 +190,31 @@ const __eventRegistry = new WeakMap();
143
190
  * @type {WeakMap<ConstructorElValues, ElementDataStore>}
144
191
  */
145
192
  const __elementDataMap = new WeakMap();
193
+ /**
194
+ * Internal storage for animation-related data, associated with elements.
195
+ * Used to remember original dimensions (height/width) and other properties
196
+ * so that animations like `slideUp` and `slideDown` can restore or continue
197
+ * smoothly, mimicking jQuery's behavior.
198
+ *
199
+ * Each element is mapped to a plain object with keys such as `origHeight`,
200
+ * `origWidth`, etc.
201
+ *
202
+ * @type {WeakMap<HTMLElement, Record<string, string|number>>}
203
+ */
204
+ const __elementAnimateData = new WeakMap();
205
+ /**
206
+ * Stores the currently active animation for each element,
207
+ * allowing cancellation or replacement of ongoing animations.
208
+ *
209
+ * @type {WeakMap<HTMLElement, { animation: Animation, id: string }>}
210
+ */
211
+ const __elementCurrentAnimation = new WeakMap();
212
+ /**
213
+ * Mapping of animation shortcuts to their effect definitions.
214
+ * Similar to jQuery's predefined effects (slideDown, fadeIn, etc.).
215
+ *
216
+ * @typedef {Record<string, string|(string|number)[]>} StyleEffects
217
+ */
146
218
  /**
147
219
  * Stores directional collision locks separately.
148
220
  * Each direction has its own WeakMap to allow independent locking.
@@ -199,6 +271,7 @@ const __elemCollision = {
199
271
  * ...children: (string | HtmlParsed)[] // Text or nested elements
200
272
  * ]} HtmlParsed
201
273
  */
274
+ // TITLE: Class Intro
202
275
  /**
203
276
  * TinyHtml is a utility class that provides static and instance-level methods
204
277
  * for precise dimension and position computations on HTML elements.
@@ -213,6 +286,106 @@ const __elemCollision = {
213
286
  class TinyHtml {
214
287
  /** @typedef {import('../basics/collision.mjs').ObjRect} ObjRect */
215
288
  static Utils = { ...TinyCollision };
289
+ /**
290
+ * Parse inline styles into an object.
291
+ * @param {string} styleText
292
+ * @returns {Record<string,string>}
293
+ */
294
+ static parseStyle(styleText) {
295
+ /** @type {Record<string,string>}} */
296
+ const styles = {};
297
+ styleText.split(';').forEach((rule) => {
298
+ const [prop, value] = rule.split(':').map((s) => s && s.trim());
299
+ if (prop && value) {
300
+ styles[prop] = value;
301
+ }
302
+ });
303
+ return styles;
304
+ }
305
+ /////////////////////////////////////////////////////////////////////
306
+ /**
307
+ * Flag to determine if element observer should start automatically.
308
+ * @type {boolean}
309
+ */
310
+ static #autoStartElemObserver = true;
311
+ /**
312
+ * Get the auto-start flag for the observer.
313
+ * @returns {boolean}
314
+ */
315
+ static get autoStartElemObserver() {
316
+ return TinyHtml.#autoStartElemObserver;
317
+ }
318
+ /**
319
+ * Set the auto-start flag for the observer.
320
+ * @param {boolean} value
321
+ */
322
+ static set autoStartElemObserver(value) {
323
+ if (typeof value !== 'boolean')
324
+ throw new TypeError('autoStartElemObserver must be a boolean.');
325
+ TinyHtml.#autoStartElemObserver = value;
326
+ }
327
+ /** @type {TinyElementObserver} */
328
+ static #tinyObserver = new TinyElementObserver({
329
+ el: typeof window !== 'undefined' && typeof window.document !== 'undefined'
330
+ ? window.document.documentElement
331
+ : undefined,
332
+ initDetectors: [
333
+ // Style Detector
334
+ [
335
+ 'tinyStyleEvent',
336
+ (mutation) => {
337
+ if (mutation.type !== 'attributes' ||
338
+ mutation.attributeName !== 'style' ||
339
+ !(mutation.target instanceof HTMLElement))
340
+ return;
341
+ const oldVal = mutation.oldValue || '';
342
+ const newVal = mutation.target.getAttribute('style') || '';
343
+ const oldStyles = TinyHtml.parseStyle(oldVal);
344
+ const newStyles = TinyHtml.parseStyle(newVal);
345
+ const changes = diffStrings(oldStyles, newStyles);
346
+ if (Object.keys(changes.added).length ||
347
+ Object.keys(changes.removed).length ||
348
+ Object.keys(changes.modified).length) {
349
+ mutation.target.dispatchEvent(new CustomEvent('tinyhtml.stylechanged', {
350
+ detail: changes,
351
+ }));
352
+ }
353
+ },
354
+ ],
355
+ // Class Detector
356
+ [
357
+ 'tinyClassEvent',
358
+ (mutation) => {
359
+ if (mutation.type !== 'attributes' ||
360
+ mutation.attributeName !== 'class' ||
361
+ !(mutation.target instanceof HTMLElement))
362
+ return;
363
+ const oldVal = mutation.oldValue || '';
364
+ const newVal = mutation.target.className || '';
365
+ const oldClasses = oldVal.split(/\s+/).filter(Boolean);
366
+ const newClasses = newVal.split(/\s+/).filter(Boolean);
367
+ const changes = diffArrayList(oldClasses, newClasses);
368
+ if (changes.added.length || changes.removed.length) {
369
+ mutation.target.dispatchEvent(new CustomEvent('tinyhtml.classchanged', {
370
+ detail: changes,
371
+ }));
372
+ }
373
+ },
374
+ ],
375
+ ],
376
+ initCfg: {
377
+ attributeOldValue: true,
378
+ attributes: true,
379
+ subtree: true,
380
+ attributeFilter: ['style', 'class'],
381
+ },
382
+ });
383
+ /** @returns {TinyElementObserver} */
384
+ static get tinyObserver() {
385
+ return TinyHtml.#tinyObserver;
386
+ }
387
+ ///////////////////////////////////////////////////////////////////
388
+ // TITLE: Fetch Html List
216
389
  /**
217
390
  * Fetches an HTML file from the given URL, parses it to JSON.
218
391
  *
@@ -254,6 +427,7 @@ class TinyHtml {
254
427
  const nodes = await TinyHtml.fetchHtmlNodes(url, ops);
255
428
  return TinyHtml.toTinyElm(nodes);
256
429
  }
430
+ // TITLE: Fetch Template List
257
431
  /**
258
432
  * Converts the content of a <template> to an array of HtmlParsed.
259
433
  *
@@ -291,6 +465,7 @@ class TinyHtml {
291
465
  static templateToTinyElems(nodes) {
292
466
  return TinyHtml.toTinyElm(TinyHtml.templateToNodes(nodes));
293
467
  }
468
+ // TITLE: Fetch Json List
294
469
  /**
295
470
  * Parses a full HTML string into a JSON-like structure.
296
471
  *
@@ -372,6 +547,7 @@ class TinyHtml {
372
547
  static jsonToTinyElems(jsonArray) {
373
548
  return TinyHtml.toTinyElm(TinyHtml.jsonToNodes(jsonArray));
374
549
  }
550
+ // TITLE: Element Creator
375
551
  /**
376
552
  * Creates a new TinyHtml element from a tag name and optional attributes.
377
553
  *
@@ -450,6 +626,8 @@ class TinyHtml {
450
626
  throw new Error('The HTML string must contain a valid HTML element.');
451
627
  return new TinyHtml(template.content.firstChild);
452
628
  }
629
+ ///////////////////////////////////////////////////
630
+ // TITLE: Query Script
453
631
  /**
454
632
  * Queries the document for the first element matching the CSS selector and wraps it in a TinyHtml instance.
455
633
  *
@@ -555,6 +733,7 @@ class TinyHtml {
555
733
  return TinyHtml.getByTagNameNS(localName, namespaceURI, TinyHtml._preElem(this, 'getByTagNameNS'));
556
734
  }
557
735
  //////////////////////////////////////////////////////////////////
736
+ // TITLE: Element getter
558
737
  /**
559
738
  * Iterates over all elements, executing the provided callback on each.
560
739
  * @param {(element: TinyHtmlAny, index: number, items: TinyHtmlAny[]) => void} callback - Function invoked for each element.
@@ -613,6 +792,8 @@ class TinyHtml {
613
792
  getAll() {
614
793
  return [...this.#el];
615
794
  }
795
+ ////////////////////////////////////////////////
796
+ // TITLE: Element Getter (Private) (Pt1)
616
797
  /**
617
798
  * Returns the current Element held by this instance.
618
799
  *
@@ -645,6 +826,7 @@ class TinyHtml {
645
826
  return [...this.#el];
646
827
  }
647
828
  //////////////////////////////////////////////////////
829
+ // TITLE: Element Getter (Private) (Pt2)
648
830
  /**
649
831
  * Prepares and validates multiple elements against allowed types.
650
832
  *
@@ -928,6 +1110,8 @@ class TinyHtml {
928
1110
  static _preElemWithDoc(elems, where) {
929
1111
  return TinyHtml._preElemTemplate(elems, where, [Element, Document], ['Element', 'Document']);
930
1112
  }
1113
+ //////////////////////////////////////////////////////
1114
+ // TITLE: Converter DOM <--> TinyHtml
931
1115
  /**
932
1116
  * Normalizes and converts one or more DOM elements (or TinyHtml instances)
933
1117
  * into an array of `TinyHtml` instances.
@@ -977,6 +1161,8 @@ class TinyHtml {
977
1161
  return checkElement([elems]);
978
1162
  return checkElement(elems);
979
1163
  }
1164
+ //////////////////////////////////////////////////
1165
+ // TITLE: DOM Getter
980
1166
  /**
981
1167
  * Filters an array of elements based on a selector, function, element, or array of elements.
982
1168
  *
@@ -1169,6 +1355,7 @@ class TinyHtml {
1169
1355
  return TinyHtml.isSameDom(this, elem);
1170
1356
  }
1171
1357
  //////////////////////////////////////////////////////////////////
1358
+ // TITLE: Data Manager
1172
1359
  /**
1173
1360
  * Internal data storage for element information.
1174
1361
  * @type {ElementDataStore}
@@ -1277,6 +1464,7 @@ class TinyHtml {
1277
1464
  return TinyHtml.setData(this, key, value, isPrivate);
1278
1465
  }
1279
1466
  //////////////////////////////////////////////////////
1467
+ // TITLE: DOM Getter 2
1280
1468
  /**
1281
1469
  * Get the sibling element in a given direction.
1282
1470
  *
@@ -1549,6 +1737,8 @@ class TinyHtml {
1549
1737
  contents() {
1550
1738
  return TinyHtml.contents(this);
1551
1739
  }
1740
+ ////////////////////////////////////////////
1741
+ // TITLE: Clone Dom
1552
1742
  /**
1553
1743
  * Clone each element.
1554
1744
  * @param {TinyNode|TinyNode[]} el
@@ -1568,6 +1758,8 @@ class TinyHtml {
1568
1758
  clone(deep) {
1569
1759
  return TinyHtml.clone(this, deep)[0];
1570
1760
  }
1761
+ //////////////////////////////////////////////////
1762
+ // TITLE: Append Content
1571
1763
  /**
1572
1764
  * Normalize and validate nodes before DOM insertion.
1573
1765
  * Converts TinyNode-like structures or strings into DOM-compatible nodes.
@@ -1837,6 +2029,7 @@ class TinyHtml {
1837
2029
  return TinyHtml.replaceAll(this, targets);
1838
2030
  }
1839
2031
  //////////////////////////////////////////////////////
2032
+ // TITLE: Constructor Base
1840
2033
  /**
1841
2034
  * The target HTML element for instance-level operations.
1842
2035
  * @type {ConstructorElValues[]}
@@ -1893,6 +2086,7 @@ class TinyHtml {
1893
2086
  return obj != null && obj === obj.window;
1894
2087
  }
1895
2088
  /////////////////////////////////////////////////////
2089
+ // TITLE: CSS Stuff
1896
2090
  /**
1897
2091
  * Returns the full computed CSS styles for the given element.
1898
2092
  *
@@ -2016,6 +2210,7 @@ class TinyHtml {
2016
2210
  return TinyHtml.cssFloats(this, prop);
2017
2211
  }
2018
2212
  //////////////////////////////////////////////////////////////////////
2213
+ // TITLE: Style Stuff
2019
2214
  /**
2020
2215
  * Stores camelCase to kebab-case CSS property aliases.
2021
2216
  *
@@ -2512,6 +2707,7 @@ class TinyHtml {
2512
2707
  return TinyHtml.clearStyle(this);
2513
2708
  }
2514
2709
  //////////////////////////////////////////////////////////////////////
2710
+ // TITLE: Focus/Blur
2515
2711
  /**
2516
2712
  * Focus the element.
2517
2713
  *
@@ -2550,6 +2746,8 @@ class TinyHtml {
2550
2746
  blur() {
2551
2747
  return TinyHtml.blur(this);
2552
2748
  }
2749
+ /////////////////////////////////////////////////////////
2750
+ // TITLE: Select
2553
2751
  /**
2554
2752
  * Select the text content of an input or textarea element.
2555
2753
  *
@@ -2572,6 +2770,7 @@ class TinyHtml {
2572
2770
  select() {
2573
2771
  return TinyHtml.select(this);
2574
2772
  }
2773
+ // TITLE: Bool Checker
2575
2774
  /**
2576
2775
  * Interprets a value as a boolean `true` if it matches a common truthy representation.
2577
2776
  *
@@ -2595,6 +2794,7 @@ class TinyHtml {
2595
2794
  }
2596
2795
  }
2597
2796
  //////////////////////////////////////////////////////////////////////
2797
+ // TITLE: Window Getter/Setter
2598
2798
  /**
2599
2799
  * Sets the vertical scroll position of the window.
2600
2800
  * @param {number} value - Sets the scroll position.
@@ -2889,29 +3089,888 @@ class TinyHtml {
2889
3089
  return TinyHtml.outerWidth(this, includeMargin);
2890
3090
  }
2891
3091
  //////////////////////////////////////////////////
3092
+ // TITLE: Animate DOM (Data)
2892
3093
  /**
2893
- * Applies an animation to one or multiple TinyElement instances.
3094
+ * Retrieves stored animation data for a given element and key.
3095
+ * If no data exists yet, initializes storage for that element.
2894
3096
  *
2895
- * @template {TinyElement|TinyElement[]} T
2896
- * @param {T} el - A single TinyElement or an array of TinyElements to animate.
2897
- * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2898
- * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2899
- * @returns {T}
3097
+ * @param {HTMLElement} el - The element whose data should be retrieved.
3098
+ * @param {string} where - The key to read (e.g., "origHeight").
3099
+ * @returns {string|number|undefined} - The stored value, or undefined.
2900
3100
  */
2901
- static animate(el, keyframes, ops) {
2902
- TinyHtml._preElems(el, 'animate').forEach((elem) => elem.animate(keyframes, ops));
2903
- return el;
3101
+ static getAnimateData(el, where) {
3102
+ let dataset = __elementAnimateData.get(el);
3103
+ if (!dataset) {
3104
+ dataset = {};
3105
+ __elementAnimateData.set(el, dataset);
3106
+ }
3107
+ return dataset[where];
3108
+ }
3109
+ /**
3110
+ * Stores animation data for a given element and key.
3111
+ * Used to cache original size/values for animations.
3112
+ *
3113
+ * @param {HTMLElement} el - The element whose data should be set.
3114
+ * @param {string} where - The key to store under (e.g., "origHeight").
3115
+ * @param {string|number} value - The value to store.
3116
+ */
3117
+ static setAnimateData(el, where, value) {
3118
+ if (!(el instanceof HTMLElement))
3119
+ throw new TypeError('setAnimateData: el must be an HTMLElement.');
3120
+ if (typeof where !== 'string')
3121
+ throw new TypeError('setAnimateData: where must be a string.');
3122
+ if (!(typeof value === 'string' || typeof value === 'number'))
3123
+ throw new TypeError('setAnimateData: value must be a string or number.');
3124
+ let dataset = __elementAnimateData.get(el);
3125
+ if (!dataset) {
3126
+ dataset = {};
3127
+ __elementAnimateData.set(el, dataset);
3128
+ }
3129
+ dataset[where] = value;
3130
+ }
3131
+ // TITLE: Animate DOM (cancelOldStyleFx)
3132
+ /**
3133
+ * Global configuration flag controlling whether old style-based animations
3134
+ * are cancelled before a new one starts. Defaults to true.
3135
+ *
3136
+ * @type {boolean}
3137
+ */
3138
+ static #cancelOldStyleFx = true;
3139
+ /**
3140
+ * Returns the current global setting for cancelling old style-based animations.
3141
+ *
3142
+ * @returns {boolean} True if old animations are cancelled by default, false otherwise.
3143
+ */
3144
+ static get cancelOldStyleFx() {
3145
+ return TinyHtml.#cancelOldStyleFx;
3146
+ }
3147
+ /**
3148
+ * Updates the global setting that determines whether old style-based animations
3149
+ * are cancelled before new ones start.
3150
+ *
3151
+ * @param {boolean} value - True to cancel old animations by default, false to keep them running.
3152
+ * @throws {TypeError} If the value is not a boolean.
3153
+ */
3154
+ static set cancelOldStyleFx(value) {
3155
+ if (typeof value !== 'boolean')
3156
+ throw new TypeError('Expected a boolean value.');
3157
+ TinyHtml.#cancelOldStyleFx = value;
3158
+ }
3159
+ // TITLE: Animate DOM (styleFxSpeeds)
3160
+ /**
3161
+ * Predefined animation speed options, inspired by jQuery.fx.speeds.
3162
+ * Each entry can be either a number (duration in ms) or a KeyframeAnimationOptions object.
3163
+ *
3164
+ * Usage example:
3165
+ * TinyHtml.animate(el, keyframes, TinyHtml.#fxSpeeds.fast);
3166
+ * TinyHtml.slideDown(el, TinyHtml.#fxSpeeds.slow);
3167
+ *
3168
+ * @type {Record<string, number | KeyframeAnimationOptions>}
3169
+ */
3170
+ static #styleFxSpeeds = {
3171
+ slow: { duration: 600, easing: 'ease' },
3172
+ fast: { duration: 200, easing: 'ease-out' },
3173
+ _default: { duration: 400, easing: 'linear' },
3174
+ };
3175
+ /**
3176
+ * Get a cloned copy of predefined animation speeds.
3177
+ * @returns {Record<string, number | KeyframeAnimationOptions>}
3178
+ */
3179
+ static get styleFxSpeeds() {
3180
+ /** @type {Record<string, number | KeyframeAnimationOptions>} */
3181
+ const data = {};
3182
+ for (const name in TinyHtml.#styleFxSpeeds) {
3183
+ const info = TinyHtml.#styleFxSpeeds[name];
3184
+ data[name] = typeof info === 'object' ? { ...info } : info;
3185
+ }
3186
+ return data;
3187
+ }
3188
+ /**
3189
+ * Replace the predefined animation speeds.
3190
+ * @param {Record<string, number | KeyframeAnimationOptions>} speeds
3191
+ * @throws {TypeError} If input is not a valid object of speed definitions.
3192
+ */
3193
+ static set styleFxSpeeds(speeds) {
3194
+ if (typeof speeds !== 'object' || speeds === null || Array.isArray(speeds))
3195
+ throw new TypeError('styleFxSpeeds must be an object.');
3196
+ for (const [k, v] of Object.entries(speeds)) {
3197
+ if (!(typeof v === 'number' || typeof v === 'object'))
3198
+ throw new TypeError(`styleFxSpeeds["${k}"] must be a number or KeyframeAnimationOptions.`);
3199
+ }
3200
+ TinyHtml.#styleFxSpeeds = {};
3201
+ for (const [k, v] of Object.entries(speeds))
3202
+ TinyHtml.setStyleFxSpeed(k, v);
3203
+ }
3204
+ /**
3205
+ * Get a predefined animation speed by name.
3206
+ *
3207
+ * @param {string} name - The name of the speed entry.
3208
+ * @returns {number | KeyframeAnimationOptions | undefined} A cloned value of the speed entry, or undefined if not found.
3209
+ */
3210
+ static getStyleFxSpeed(name) {
3211
+ if (typeof name !== 'string')
3212
+ throw new TypeError('The "name" parameter must be a string.');
3213
+ const spd = TinyHtml.#styleFxSpeeds[name];
3214
+ return typeof spd === 'object' ? { ...spd } : spd;
3215
+ }
3216
+ /**
3217
+ * Set or overwrite a predefined animation speed.
3218
+ *
3219
+ * @param {string} name - The name of the speed entry.
3220
+ * @param {number | KeyframeAnimationOptions} value - The value to store.
3221
+ * @throws {TypeError} If value is not a number or KeyframeAnimationOptions object.
3222
+ */
3223
+ static setStyleFxSpeed(name, value) {
3224
+ if (typeof name !== 'string')
3225
+ throw new TypeError('The "name" parameter must be a string.');
3226
+ if (!(typeof value === 'number' || typeof value === 'object'))
3227
+ throw new TypeError('styleFxSpeed must be a number or KeyframeAnimationOptions');
3228
+ TinyHtml.#styleFxSpeeds[name] = typeof value === 'object' ? { ...value } : value;
3229
+ }
3230
+ /**
3231
+ * Delete a predefined animation speed by name.
3232
+ *
3233
+ * @param {string} name - The name of the speed entry to delete.
3234
+ * @returns {boolean} True if the property was deleted, false otherwise.
3235
+ */
3236
+ static deleteStyleFxSpeed(name) {
3237
+ if (typeof name !== 'string')
3238
+ throw new TypeError('The "name" parameter must be a string.');
3239
+ return delete TinyHtml.#styleFxSpeeds[name];
3240
+ }
3241
+ /**
3242
+ * Check if a predefined animation speed exists.
3243
+ *
3244
+ * @param {string} name - The name of the speed entry to check.
3245
+ * @returns {boolean} True if the speed entry exists, false otherwise.
3246
+ */
3247
+ static hasStyleFxSpeed(name) {
3248
+ if (typeof name !== 'string')
3249
+ throw new TypeError('The "name" parameter must be a string.');
3250
+ return Object.prototype.hasOwnProperty.call(TinyHtml.#styleFxSpeeds, name);
3251
+ }
3252
+ // TITLE: Animate DOM (cssExpand)
3253
+ /**
3254
+ * CSS expansion shorthand used by genStyleFx to include margin/padding values.
3255
+ * @typedef {['Top', 'Right', 'Bottom', 'Left']}
3256
+ */
3257
+ static #cssExpand = ['Top', 'Right', 'Bottom', 'Left'];
3258
+ // TITLE: Animate DOM (styleEffects)
3259
+ /**
3260
+ * Generate shortcuts
3261
+ * @type {Record<string, StyleEffects>}
3262
+ */
3263
+ static #styleEffects = {
3264
+ slideDown: TinyHtml.genStyleFx('show'),
3265
+ slideUp: TinyHtml.genStyleFx('hide'),
3266
+ fadeIn: { opacity: 'show' },
3267
+ fadeOut: { opacity: 'hide' },
3268
+ };
3269
+ /**
3270
+ * Returns a deep-cloned copy of the registered style effects.
3271
+ *
3272
+ * @returns {Record<string, StyleEffects>}
3273
+ */
3274
+ static get styleEffects() {
3275
+ return JSON.parse(JSON.stringify(TinyHtml.#styleEffects));
3276
+ }
3277
+ /**
3278
+ * Replace the entire styleEffects map with a new one.
3279
+ *
3280
+ * @param {Record<string, StyleEffects>} value
3281
+ */
3282
+ static set styleEffects(value) {
3283
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
3284
+ throw new TypeError('styleEffects must be an object');
3285
+ for (const name in value) {
3286
+ for (const [prop, mode] of Object.entries(value[name])) {
3287
+ if (typeof mode !== 'string' &&
3288
+ (!Array.isArray(mode) ||
3289
+ !mode.every((v) => typeof v === 'string' || typeof v === 'number')))
3290
+ throw new TypeError(`Invalid styleEffect["${prop}"]`);
3291
+ }
3292
+ }
3293
+ TinyHtml.#styleEffects = {};
3294
+ for (const name in value)
3295
+ TinyHtml.setStyleEffect(name, value[name]);
3296
+ }
3297
+ /**
3298
+ * Get a deep-cloned style effect by name.
3299
+ * @param {string} name
3300
+ * @returns {StyleEffects|undefined}
3301
+ */
3302
+ static getStyleEffect(name) {
3303
+ if (typeof name !== 'string')
3304
+ throw new TypeError('The "name" parameter must be a string.');
3305
+ const eff = TinyHtml.#styleEffects[name];
3306
+ return eff ? JSON.parse(JSON.stringify(eff)) : undefined;
3307
+ }
3308
+ /**
3309
+ * Set or replace a style effect.
3310
+ * @param {string} name
3311
+ * @param {StyleEffects} value
3312
+ */
3313
+ static setStyleEffect(name, value) {
3314
+ if (typeof name !== 'string')
3315
+ throw new TypeError('The "name" parameter must be a string.');
3316
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
3317
+ throw new TypeError('styleEffect must be an object');
3318
+ }
3319
+ /** @type {StyleEffects} */
3320
+ const copy = {};
3321
+ for (const [prop, mode] of Object.entries(value)) {
3322
+ if (typeof mode === 'string') {
3323
+ copy[prop] = mode;
3324
+ }
3325
+ else if (Array.isArray(mode) &&
3326
+ mode.every((v) => typeof v === 'string' || typeof v === 'number')) {
3327
+ copy[prop] = [...mode];
3328
+ }
3329
+ else {
3330
+ throw new TypeError(`Invalid styleEffect["${prop}"]`);
3331
+ }
3332
+ }
3333
+ TinyHtml.#styleEffects[name] = copy;
3334
+ }
3335
+ /**
3336
+ * Delete a style effect.
3337
+ * @param {string} name
3338
+ * @returns {boolean} True if deleted.
3339
+ */
3340
+ static deleteStyleEffect(name) {
3341
+ if (typeof name !== 'string')
3342
+ throw new TypeError('The "name" parameter must be a string.');
3343
+ return delete TinyHtml.#styleEffects[name];
3344
+ }
3345
+ /**
3346
+ * Check if a style effect exists.
3347
+ * @param {string} name
3348
+ * @returns {boolean}
3349
+ */
3350
+ static hasStyleEffect(name) {
3351
+ if (typeof name !== 'string')
3352
+ throw new TypeError('The "name" parameter must be a string.');
3353
+ return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffects, name);
3354
+ }
3355
+ // TITLE: Animate DOM (styleEffectInverse)
3356
+ /**
3357
+ * Style Effect Inverse Values
3358
+ * @type {Record<string, string>}
3359
+ */
3360
+ static #styleEffectInverse = {
3361
+ slideDown: 'slideUp',
3362
+ slideUp: 'slideDown',
3363
+ fadeIn: 'fadeOut',
3364
+ fadeOut: 'fadeIn',
3365
+ };
3366
+ /**
3367
+ * Get a cloned copy of style inverse values.
3368
+ * @returns {Record<string, string>}
3369
+ */
3370
+ static get styleEffectInverse() {
3371
+ return { ...TinyHtml.#styleEffectInverse };
3372
+ }
3373
+ /**
3374
+ * Replace the style inverse values.
3375
+ * @param {Record<string, string>} values
3376
+ * @throws {TypeError} If not a valid object with functions as values.
3377
+ */
3378
+ static set styleEffectInverse(values) {
3379
+ if (typeof values !== 'object' || values === null)
3380
+ throw new TypeError('styleEffectInverse must be an object.');
3381
+ for (const [k, v] of Object.entries(values)) {
3382
+ if (typeof v !== 'string')
3383
+ throw new TypeError(`styleEffectInverse["${k}"] must be a string.`);
3384
+ }
3385
+ TinyHtml.#styleEffectInverse = {};
3386
+ for (const [k, v] of Object.entries(values))
3387
+ TinyHtml.setStyleEffectInverse(k, v);
3388
+ }
3389
+ /**
3390
+ * Get a registered inverse values.
3391
+ *
3392
+ * @param {string} name - The detector name.
3393
+ * @returns {string | null} The value if found, otherwise undefined.
3394
+ */
3395
+ static getStyleEffectInverse(name) {
3396
+ if (typeof name !== 'string')
3397
+ throw new TypeError('The "name" parameter must be a string.');
3398
+ return TinyHtml.#styleEffectInverse[name] || null;
3399
+ }
3400
+ /**
3401
+ * Register or overwrite a inverse value.
3402
+ *
3403
+ * @param {string} name - The detector name.
3404
+ * @param {string} value - The inverse value.
3405
+ * @throws {TypeError} If fn is not a string.
3406
+ */
3407
+ static setStyleEffectInverse(name, value) {
3408
+ if (typeof name !== 'string')
3409
+ throw new TypeError('The "name" parameter must be a string.');
3410
+ if (typeof value !== 'string')
3411
+ throw new TypeError(`styleEffectInverse["${name}"] must be a string.`);
3412
+ TinyHtml.#styleEffectInverse[name] = value;
3413
+ }
3414
+ /**
3415
+ * Delete a inverse value by name.
3416
+ *
3417
+ * @param {string} name - The inverse value name to delete.
3418
+ * @returns {boolean} True if deleted, false otherwise.
3419
+ */
3420
+ static deleteStyleEffectInverse(name) {
3421
+ if (typeof name !== 'string')
3422
+ throw new TypeError('The "name" parameter must be a string.');
3423
+ return delete TinyHtml.#styleEffectInverse[name];
3424
+ }
3425
+ /**
3426
+ * Check if a inverse value is registered.
3427
+ *
3428
+ * @param {string} name - The inverse value name to check.
3429
+ * @returns {boolean} True if registered, false otherwise.
3430
+ */
3431
+ static hasStyleEffectInverse(name) {
3432
+ if (typeof name !== 'string')
3433
+ throw new TypeError('The "name" parameter must be a string.');
3434
+ return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffectInverse, name);
3435
+ }
3436
+ // TITLE: Animate DOM (styleEffectsRd)
3437
+ /**
3438
+ * Style Effect Repeat Detector
3439
+ * @type {Record<string, StyleEffectsRdFn>}
3440
+ */
3441
+ static #styleEffectsRd = {
3442
+ slideDown: (effects) => effects.height[0] === effects.height[1] &&
3443
+ (!effects.width || effects.width[0] === effects.width[1]),
3444
+ slideUp: (effects) => effects.height[0] === effects.height[1] &&
3445
+ (!effects.width || effects.width[0] === effects.width[1]),
3446
+ fadeIn: (effects) => Number(effects.opacity[0]) === Number(effects.opacity[1]),
3447
+ fadeOut: (effects) => Number(effects.opacity[0]) === Number(effects.opacity[1]),
3448
+ };
3449
+ /**
3450
+ * Get a cloned copy of style effects repeat detectors.
3451
+ * @returns {Record<string, StyleEffectsRdFn>}
3452
+ */
3453
+ static get styleEffectsRd() {
3454
+ return { ...TinyHtml.#styleEffectsRd };
3455
+ }
3456
+ /**
3457
+ * Replace the style effects repeat detectors.
3458
+ * @param {Record<string, StyleEffectsRdFn>} detectors
3459
+ * @throws {TypeError} If not a valid object with functions as values.
3460
+ */
3461
+ static set styleEffectsRd(detectors) {
3462
+ if (typeof detectors !== 'object' || detectors === null)
3463
+ throw new TypeError('styleEffectsRd must be an object.');
3464
+ for (const [k, v] of Object.entries(detectors)) {
3465
+ if (typeof v !== 'function')
3466
+ throw new TypeError(`styleEffectsRd["${k}"] must be a function.`);
3467
+ }
3468
+ TinyHtml.#styleEffectsRd = {};
3469
+ for (const [k, v] of Object.entries(detectors))
3470
+ TinyHtml.setStyleEffectRd(k, v);
3471
+ }
3472
+ /**
3473
+ * Get a registered repeat detector function by name.
3474
+ *
3475
+ * @param {string} name - The detector name.
3476
+ * @returns {StyleEffectsRdFn | null} The function if found, otherwise undefined.
3477
+ */
3478
+ static getStyleEffectRd(name) {
3479
+ if (typeof name !== 'string')
3480
+ throw new TypeError('The "name" parameter must be a string.');
3481
+ return TinyHtml.#styleEffectsRd[name] || null;
3482
+ }
3483
+ /**
3484
+ * Register or overwrite a repeat detector function.
3485
+ *
3486
+ * @param {string} name - The detector name.
3487
+ * @param {StyleEffectsRdFn} fn - The detector function.
3488
+ * @throws {TypeError} If fn is not a function.
3489
+ */
3490
+ static setStyleEffectRd(name, fn) {
3491
+ if (typeof name !== 'string')
3492
+ throw new TypeError('The "name" parameter must be a string.');
3493
+ if (typeof fn !== 'function')
3494
+ throw new TypeError(`styleEffectsRd["${name}"] must be a function.`);
3495
+ TinyHtml.#styleEffectsRd[name] = fn;
3496
+ }
3497
+ /**
3498
+ * Delete a repeat detector function by name.
3499
+ *
3500
+ * @param {string} name - The detector name to delete.
3501
+ * @returns {boolean} True if deleted, false otherwise.
3502
+ */
3503
+ static deleteStyleEffectRd(name) {
3504
+ if (typeof name !== 'string')
3505
+ throw new TypeError('The "name" parameter must be a string.');
3506
+ return delete TinyHtml.#styleEffectsRd[name];
3507
+ }
3508
+ /**
3509
+ * Check if a repeat detector function is registered.
3510
+ *
3511
+ * @param {string} name - The detector name to check.
3512
+ * @returns {boolean} True if registered, false otherwise.
3513
+ */
3514
+ static hasStyleEffectRd(name) {
3515
+ if (typeof name !== 'string')
3516
+ throw new TypeError('The "name" parameter must be a string.');
3517
+ return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffectsRd, name);
3518
+ }
3519
+ // TITLE: Animate DOM (styleEffectsPromps)
3520
+ /**
3521
+ * Effect property handlers for show, hide, and toggle.
3522
+ * Each function builds keyframes depending on the property being animated.
3523
+ *
3524
+ * @type {StyleEffectsProps}
3525
+ */
3526
+ static #styleEffectsProps = {
3527
+ show: (el, keyframes, prop, style) => {
3528
+ if (prop === 'height' || prop === 'width') {
3529
+ const targetSize = TinyHtml.getAnimateData(el, `orig${prop}`) || el.scrollHeight + 'px';
3530
+ TinyHtml.setAnimateData(el, `orig${prop}`, targetSize);
3531
+ const current = style[prop]; // pega valor atual
3532
+ keyframes[prop] = [current, targetSize];
3533
+ }
3534
+ else if (prop.startsWith('margin') || prop.startsWith('padding')) {
3535
+ // @ts-ignore
3536
+ const orig = TinyHtml.getAnimateData(el, prop) || style[prop];
3537
+ TinyHtml.setAnimateData(el, prop, orig);
3538
+ keyframes[prop] = ['0px', orig];
3539
+ }
3540
+ else if (prop === 'opacity') {
3541
+ const current = style.opacity;
3542
+ keyframes[prop] = [current, 1];
3543
+ }
3544
+ else {
3545
+ // @ts-ignore
3546
+ const current = TinyHtml.getAnimateData(el, prop) || style[prop];
3547
+ TinyHtml.setAnimateData(el, prop, current);
3548
+ // @ts-ignore
3549
+ keyframes[prop] = [current, style[prop] || 1];
3550
+ }
3551
+ },
3552
+ hide: (el, keyframes, prop, style) => {
3553
+ if (prop === 'height' || prop === 'width') {
3554
+ const targetSize = TinyHtml.getAnimateData(el, `orig${prop}`) || style[prop];
3555
+ TinyHtml.setAnimateData(el, `orig${prop}`, targetSize);
3556
+ const current = style[prop]; // pega valor atual
3557
+ keyframes[prop] = [current, 0];
3558
+ }
3559
+ else if (prop.startsWith('margin') || prop.startsWith('padding')) {
3560
+ // @ts-ignore
3561
+ const orig = TinyHtml.getAnimateData(el, prop) || style[prop];
3562
+ TinyHtml.setAnimateData(el, prop, orig);
3563
+ // @ts-ignore
3564
+ keyframes[prop] = [style[prop], '0px'];
3565
+ }
3566
+ else if (prop === 'opacity') {
3567
+ const current = style.opacity;
3568
+ keyframes[prop] = [current, 0];
3569
+ }
3570
+ else {
3571
+ // @ts-ignore
3572
+ const current = TinyHtml.getAnimateData(el, prop) || style[prop];
3573
+ TinyHtml.setAnimateData(el, prop, current);
3574
+ keyframes[prop] = [current, 0];
3575
+ }
3576
+ },
3577
+ };
3578
+ /**
3579
+ * Returns a shallow-cloned copy of the property effect handlers.
3580
+ *
3581
+ * @returns {StyleEffectsProps}
3582
+ */
3583
+ static get styleEffectsProps() {
3584
+ return { ...TinyHtml.#styleEffectsProps };
3585
+ }
3586
+ /**
3587
+ * Replace the entire styleEffectsProps map with a new one.
3588
+ *
3589
+ * @param {StyleEffectsProps} value
3590
+ */
3591
+ static set styleEffectsProps(value) {
3592
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
3593
+ throw new TypeError('styleEffectsProps must be an object');
3594
+ for (const [k, fn] of Object.entries(value)) {
3595
+ if (typeof fn !== 'function')
3596
+ throw new TypeError(`styleEffectsProps["${k}"] must be a function`);
3597
+ }
3598
+ TinyHtml.#styleEffectsProps = {};
3599
+ for (const [k, fn] of Object.entries(value))
3600
+ TinyHtml.setStyleEffectProp(k, fn);
3601
+ }
3602
+ /**
3603
+ * Get a style effect property handler by name.
3604
+ *
3605
+ * @param {string} name - The property handler name.
3606
+ * @returns {StyleEffectsFn | null} The handler function, or undefined if not found.
3607
+ */
3608
+ static getStyleEffectProp(name) {
3609
+ if (typeof name !== 'string')
3610
+ throw new TypeError('The "name" parameter must be a string.');
3611
+ return TinyHtml.#styleEffectsProps[name] || null;
3612
+ }
3613
+ /**
3614
+ * Register or overwrite a style effect property handler.
3615
+ *
3616
+ * @param {string} name - The property handler name.
3617
+ * @param {StyleEffectsFn} fn - The handler function.
3618
+ * @throws {TypeError} If fn is not a function.
3619
+ */
3620
+ static setStyleEffectProp(name, fn) {
3621
+ if (typeof name !== 'string')
3622
+ throw new TypeError('The "name" parameter must be a string.');
3623
+ if (typeof fn !== 'function')
3624
+ throw new TypeError(`styleEffectsProps["${name}"] must be a function`);
3625
+ TinyHtml.#styleEffectsProps[name] = fn;
3626
+ }
3627
+ /**
3628
+ * Delete a style effect property handler by name.
3629
+ *
3630
+ * @param {string} name - The property handler name to delete.
3631
+ * @returns {boolean} True if deleted, false otherwise.
3632
+ */
3633
+ static deleteStyleEffectProp(name) {
3634
+ if (typeof name !== 'string')
3635
+ throw new TypeError('The "name" parameter must be a string.');
3636
+ return delete TinyHtml.#styleEffectsProps[name];
3637
+ }
3638
+ /**
3639
+ * Check if a style effect property handler exists.
3640
+ *
3641
+ * @param {string} name - The property handler name to check.
3642
+ * @returns {boolean} True if the handler exists, false otherwise.
3643
+ */
3644
+ static hasStyleEffectProp(name) {
3645
+ if (typeof name !== 'string')
3646
+ throw new TypeError('The "name" parameter must be a string.');
3647
+ return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffectsProps, name);
3648
+ }
3649
+ // TITLE: Gen Style FX Manager
3650
+ /**
3651
+ * Generates effect parameters to create standard animations.
3652
+ *
3653
+ * @param {string} type - The effect type.
3654
+ * @param {boolean} [includeWidth=false] - Whether width (and opacity) should be included.
3655
+ * @returns {Record<string, string>} - A mapping of CSS properties to effect type.
3656
+ */
3657
+ static genStyleFx(type, includeWidth = false) {
3658
+ if (typeof type !== 'string')
3659
+ throw new TypeError('genStyleFx: type must be a string.');
3660
+ if (typeof includeWidth !== 'boolean')
3661
+ throw new TypeError('genStyleFx: includeWidth must be a boolean.');
3662
+ /** @type {string} */
3663
+ let which;
3664
+ /** @type {number} */
3665
+ let i = 0;
3666
+ /** @type {Record<string, string>} */
3667
+ const attrs = { height: type };
3668
+ const includeWidthNb = includeWidth ? 1 : 0;
3669
+ for (; i < 4; i += 2 - includeWidthNb) {
3670
+ which = TinyHtml.#cssExpand[i];
3671
+ attrs['margin' + which] = type;
3672
+ attrs['padding' + which] = type;
3673
+ }
3674
+ if (includeWidth) {
3675
+ attrs.opacity = type;
3676
+ attrs.width = type;
3677
+ }
3678
+ return attrs;
3679
+ }
3680
+ /**
3681
+ * Applies style-based effects (slide, fade) to one or more elements.
3682
+ * Converts abstract effect definitions (e.g., `{ height: "show" }`)
3683
+ * into concrete Web Animations API keyframes.
3684
+ *
3685
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element or an array of elements.
3686
+ * @param {string} id - The style effect id.
3687
+ * @param {StyleEffects} props - The style effect definition.
3688
+ * @param {number | KeyframeAnimationOptions | string} [ops] - Timing options.
3689
+ * @returns {StyleFxResult}
3690
+ */
3691
+ static applyStyleFx(el, id, props, ops) {
3692
+ if (typeof id !== 'string')
3693
+ throw new TypeError('applyStyleFx: id must be a string.');
3694
+ if (typeof props !== 'object' || props === null)
3695
+ throw new TypeError('applyStyleFx: props must be a non-null object.');
3696
+ if (ops !== undefined &&
3697
+ !(typeof ops === 'number' ||
3698
+ typeof ops === 'string' ||
3699
+ (typeof ops === 'object' && ops !== null)))
3700
+ throw new TypeError('applyStyleFx: ops must be a number, string, KeyframeAnimationOptions, or undefined.');
3701
+ /** @type {StyleFxResult} */
3702
+ const results = new Map();
3703
+ TinyHtml._preHtmlElems(el, 'applyStyleFx').forEach((first) => {
3704
+ /**
3705
+ * Generate keyframes based on props
3706
+ * @type {AnimationSfxData}
3707
+ */
3708
+ const keyframes = {};
3709
+ for (const [prop, action] of Object.entries(props)) {
3710
+ if (typeof action === 'string' && this.#styleEffectsProps[action]) {
3711
+ const style = getComputedStyle(first);
3712
+ this.#styleEffectsProps[action](first, keyframes, prop, style);
3713
+ }
3714
+ else if (typeof action === 'object') {
3715
+ keyframes[prop] = action;
3716
+ }
3717
+ else {
3718
+ throw new TypeError(`applyStyleFx: invalid action for prop "${prop}". Must be string or array.`);
3719
+ }
3720
+ }
3721
+ const isRepeat = typeof TinyHtml.#styleEffectsRd[id] === 'function'
3722
+ ? TinyHtml.#styleEffectsRd[id](keyframes)
3723
+ : false;
3724
+ if (isRepeat) {
3725
+ results.set(first, null);
3726
+ return;
3727
+ }
3728
+ /**
3729
+ * Build Web Animations keyframe objects
3730
+ * @type {Keyframe[]}
3731
+ */
3732
+ const kf = [];
3733
+ const numFrames = keyframes[Object.keys(keyframes)[0]].length; // assume que todos têm o mesmo tamanho
3734
+ for (let i = 0; i < numFrames; i++) {
3735
+ /** @type {Keyframe} */
3736
+ const frame = {};
3737
+ for (const prop in keyframes) {
3738
+ frame[prop] = keyframes[prop][i];
3739
+ }
3740
+ kf.push(frame);
3741
+ }
3742
+ results.set(first, TinyHtml.animate(el, kf, ops, id)[0]);
3743
+ });
3744
+ return results;
3745
+ }
3746
+ /**
3747
+ * Applies style-based effects (slide, fade) to one or more elements.
3748
+ * Converts abstract effect definitions (e.g., `{ height: "show" }`)
3749
+ * into concrete Web Animations API keyframes.
3750
+ *
3751
+ * @param {string} id - The style effect id.
3752
+ * @param {StyleEffects} props - The style effect definition.
3753
+ * @param {number | KeyframeAnimationOptions} [ops] - Timing options.
3754
+ * @returns {StyleFxResult}
3755
+ */
3756
+ applyStyleFx(id, props, ops) {
3757
+ return TinyHtml.applyStyleFx(this, id, props, ops);
3758
+ }
3759
+ // TITLE: Animate Stuff
3760
+ /**
3761
+ * Get the current animation entry for a given element.
3762
+ * @param {HTMLElement} el - The target element.
3763
+ * @returns {string|null|undefined} Returns string or null to animation.
3764
+ */
3765
+ static getCurrentAnimationId(el) {
3766
+ if (!(el instanceof HTMLElement))
3767
+ throw new TypeError('Expected an HTMLElement.');
3768
+ return __elementCurrentAnimation.get(el)?.id ?? undefined;
2904
3769
  }
2905
3770
  /**
2906
3771
  * Applies an animation to one or multiple TinyElement instances.
2907
3772
  *
2908
- * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - The keyframes used to define the animation.
2909
- * @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
2910
- * @returns {this}
3773
+ * If `cancelOldAnim` is true, any currently running animation on the same element
3774
+ * will be cancelled before the new one starts.
3775
+ *
3776
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single TinyElement or an array of TinyElements to animate.
3777
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - Keyframes that define the animation.
3778
+ * @param {number | KeyframeAnimationOptions | string} [ops] - Timing or configuration options for the animation.
3779
+ * @param {string|null} [id] - The style effect id.
3780
+ * @param {boolean} [cancelOldAnim=TinyHtml.cancelOldStyleFx] - Whether to cancel previous animations on the same element.
3781
+ * @returns {Animation[]}
3782
+ */
3783
+ static animate(el, keyframes, ops = TinyHtml.#styleFxSpeeds._default, id = null, cancelOldAnim = TinyHtml.#cancelOldStyleFx) {
3784
+ /** @type {number | KeyframeAnimationOptions} */
3785
+ const fxSpeed = typeof ops === 'string'
3786
+ ? (TinyHtml.#styleFxSpeeds[ops] ?? undefined)
3787
+ : typeof ops !== 'number'
3788
+ ? { ...ops }
3789
+ : ops;
3790
+ if (typeof fxSpeed === 'object' && typeof fxSpeed.fill === 'undefined') {
3791
+ fxSpeed.fill = 'forwards';
3792
+ }
3793
+ /** @type {Animation[]} */
3794
+ const results = [];
3795
+ TinyHtml._preHtmlElems(el, 'animate').forEach((elem) => {
3796
+ if (cancelOldAnim) {
3797
+ const prevAnim = __elementCurrentAnimation.get(elem);
3798
+ if (prevAnim)
3799
+ prevAnim.animation.cancel();
3800
+ }
3801
+ const anim = elem.animate(keyframes, fxSpeed);
3802
+ results.push(anim);
3803
+ __elementCurrentAnimation.set(elem, { animation: anim, id: id ?? '' });
3804
+ anim.addEventListener('finish', () => {
3805
+ if (__elementCurrentAnimation.get(elem)?.animation === anim)
3806
+ __elementCurrentAnimation.delete(elem);
3807
+ });
3808
+ });
3809
+ return results;
3810
+ }
3811
+ /**
3812
+ * Applies an animation to one or multiple TinyElement instances.
3813
+ *
3814
+ * If `cancelOldAnim` is true, any currently running animation on the same element
3815
+ * will be cancelled before the new one starts.
3816
+ *
3817
+ * @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - Keyframes that define the animation.
3818
+ * @param {number | KeyframeAnimationOptions | string} [ops] - Timing or configuration options for the animation.
3819
+ * @param {string|null} [id] - The style effect id.
3820
+ * @param {boolean} [cancelOldAnim=TinyHtml.cancelOldStyleFx] - Whether to cancel previous animations on the same element.
3821
+ * @returns {Animation[]}
3822
+ */
3823
+ animate(keyframes, ops, id, cancelOldAnim) {
3824
+ return TinyHtml.animate(this, keyframes, ops, id, cancelOldAnim);
3825
+ }
3826
+ /**
3827
+ * Stops the current animation(s) on one or multiple TinyElement instances.
3828
+ *
3829
+ * If an animation is running on the element(s), it will be cancelled immediately.
3830
+ *
3831
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el - A single TinyElement or an array of TinyElements.
3832
+ * @returns {boolean[]} The same element(s) provided as input, for chaining.
3833
+ */
3834
+ static stop(el) {
3835
+ /** @type {boolean[]} */
3836
+ const results = [];
3837
+ TinyHtml._preHtmlElems(el, 'stop').forEach((elem) => {
3838
+ const anim = __elementCurrentAnimation.get(elem);
3839
+ if (anim) {
3840
+ anim.animation.cancel();
3841
+ __elementCurrentAnimation.delete(elem);
3842
+ results.push(true);
3843
+ }
3844
+ else
3845
+ results.push(false);
3846
+ });
3847
+ return results;
3848
+ }
3849
+ /**
3850
+ * Stops the current animation(s) on this TinyElement instance.
3851
+ *
3852
+ * @returns {boolean[]}
3853
+ */
3854
+ stop() {
3855
+ return TinyHtml.stop(this);
3856
+ }
3857
+ // TITLE: Animate FXs
3858
+ /**
3859
+ * Show animation (slideDown).
3860
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el
3861
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3862
+ * @returns {StyleFxResult}
3863
+ */
3864
+ static slideDown(el, ops) {
3865
+ return TinyHtml.applyStyleFx(el, 'slideDown', this.#styleEffects['slideDown'], ops ?? { duration: 500, easing: 'ease-out' });
3866
+ }
3867
+ /**
3868
+ * Show animation (slideDown).
3869
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3870
+ * @returns {StyleFxResult}
3871
+ */
3872
+ slideDown(ops) {
3873
+ return TinyHtml.slideDown(this, ops);
3874
+ }
3875
+ /**
3876
+ * Hide animation (slideUp).
3877
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el
3878
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3879
+ * @returns {StyleFxResult}
3880
+ */
3881
+ static slideUp(el, ops) {
3882
+ return TinyHtml.applyStyleFx(el, 'slideUp', this.#styleEffects['slideUp'], ops ?? { duration: 500, easing: 'ease-out' });
3883
+ }
3884
+ /**
3885
+ * Hide animation (slideUp).
3886
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3887
+ * @returns {StyleFxResult}
3888
+ */
3889
+ slideUp(ops) {
3890
+ return TinyHtml.slideUp(this, ops);
3891
+ }
3892
+ /**
3893
+ * Toggle slide animation.
3894
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el
3895
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3896
+ * @returns {StyleFxResult}
3897
+ */
3898
+ static slideToggle(el, ops) {
3899
+ const first = TinyHtml._preHtmlElem(el, 'slideToggle');
3900
+ const style = getComputedStyle(first);
3901
+ const id = TinyHtml.getCurrentAnimationId(first);
3902
+ const isHidden = style.display === 'none' || first.offsetHeight === 0;
3903
+ return (typeof id === 'string' ? id === 'slideUp' : isHidden)
3904
+ ? TinyHtml.slideDown(el, ops)
3905
+ : TinyHtml.slideUp(el, ops);
3906
+ }
3907
+ /**
3908
+ * Toggle slide animation.
3909
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3910
+ * @returns {StyleFxResult}
3911
+ */
3912
+ slideToggle(ops) {
3913
+ return TinyHtml.slideToggle(this, ops);
3914
+ }
3915
+ /**
3916
+ * Fade in animation.
3917
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el
3918
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3919
+ * @returns {StyleFxResult}
3920
+ */
3921
+ static fadeIn(el, ops) {
3922
+ return TinyHtml.applyStyleFx(el, 'fadeIn', this.#styleEffects['fadeIn'], ops);
3923
+ }
3924
+ /**
3925
+ * Fade in animation.
3926
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3927
+ * @returns {StyleFxResult}
3928
+ */
3929
+ fadeIn(ops) {
3930
+ return TinyHtml.fadeIn(this, ops);
3931
+ }
3932
+ /**
3933
+ * Fade out animation.
3934
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el
3935
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3936
+ * @returns {StyleFxResult}
3937
+ */
3938
+ static fadeOut(el, ops) {
3939
+ return TinyHtml.applyStyleFx(el, 'fadeOut', this.#styleEffects['fadeOut'], ops);
3940
+ }
3941
+ /**
3942
+ * Fade out animation.
3943
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3944
+ * @returns {StyleFxResult}
3945
+ */
3946
+ fadeOut(ops) {
3947
+ return TinyHtml.fadeOut(this, ops);
3948
+ }
3949
+ /**
3950
+ * Fade toggle animation.
3951
+ * @param {TinyHtmlElement|TinyHtmlElement[]} el
3952
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3953
+ * @returns {StyleFxResult}
2911
3954
  */
2912
- animate(keyframes, ops) {
2913
- return TinyHtml.animate(this, keyframes, ops);
3955
+ static fadeToggle(el, ops) {
3956
+ const first = TinyHtml._preHtmlElem(el, 'fadeToggle');
3957
+ const style = getComputedStyle(first);
3958
+ const id = TinyHtml.getCurrentAnimationId(first);
3959
+ const isHidden = style.opacity === '0' || first.offsetParent === null;
3960
+ return (typeof id === 'string' ? id === 'fadeOut' : isHidden)
3961
+ ? TinyHtml.fadeIn(el, ops)
3962
+ : TinyHtml.fadeOut(el, ops);
2914
3963
  }
3964
+ /**
3965
+ * Fade toggle animation.
3966
+ * @param {number | KeyframeAnimationOptions | string} [ops]
3967
+ * @returns {StyleFxResult}
3968
+ */
3969
+ fadeToggle(ops) {
3970
+ return TinyHtml.fadeToggle(this, ops);
3971
+ }
3972
+ ///////////////////////////////////////////////////////////////
3973
+ // TITLE: DOM Positions
2915
3974
  /**
2916
3975
  * Gets the offset of the element relative to the document.
2917
3976
  * @param {TinyElement} el - Target element.
@@ -3003,6 +4062,8 @@ class TinyHtml {
3003
4062
  offsetParent() {
3004
4063
  return TinyHtml.offsetParent(this);
3005
4064
  }
4065
+ /////////////////////////////////////////////////
4066
+ // TITLE: Scroll Stuff
3006
4067
  /**
3007
4068
  * Gets the vertical scroll position.
3008
4069
  * @param {TinyElementAndWindow} el - Element or window.
@@ -3212,6 +4273,8 @@ class TinyHtml {
3212
4273
  setScrollLeft(value) {
3213
4274
  return TinyHtml.setScrollLeft(this, value);
3214
4275
  }
4276
+ ///////////////////////////////////////////
4277
+ // TITLE: Border Stuff
3215
4278
  /**
3216
4279
  * Returns the total border width and individual sides from `border{Side}Width` CSS properties.
3217
4280
  *
@@ -3302,6 +4365,7 @@ class TinyHtml {
3302
4365
  return TinyHtml.padding(this);
3303
4366
  }
3304
4367
  /////////////////////////////////////////////
4368
+ // TITLE: Class Stuff
3305
4369
  /**
3306
4370
  * Adds one or more CSS class names to the element.
3307
4371
  * @template {TinyElement|TinyElement[]} T
@@ -3473,6 +4537,7 @@ class TinyHtml {
3473
4537
  return TinyHtml.classList(this);
3474
4538
  }
3475
4539
  /////////////////////////////////////////
4540
+ // TITLE: Tag Stuff
3476
4541
  /**
3477
4542
  * Returns the tag name of the element.
3478
4543
  * @param {TinyElement} el - Target element.
@@ -3505,6 +4570,8 @@ class TinyHtml {
3505
4570
  id() {
3506
4571
  return TinyHtml.id(this);
3507
4572
  }
4573
+ //////////////////////////////////////////////////////////////
4574
+ // TITLE: DOM Content Stuff
3508
4575
  /**
3509
4576
  * Returns the BigInt content of the element.
3510
4577
  * @param {TinyElement} el - Target element.
@@ -3793,6 +4860,8 @@ class TinyHtml {
3793
4860
  setText(value) {
3794
4861
  return TinyHtml.setText(this, value);
3795
4862
  }
4863
+ //////////////////////////////////////////////////////
4864
+ // TITLE: DOM Content Stuff (Part 2)
3796
4865
  /**
3797
4866
  * Remove all child nodes from each element.
3798
4867
  * @template {TinyElement|TinyElement[]} T
@@ -3849,6 +4918,8 @@ class TinyHtml {
3849
4918
  setHtml(value) {
3850
4919
  return TinyHtml.setHtml(this, value);
3851
4920
  }
4921
+ ///////////////////////////////////////////////
4922
+ // TITLE: Value Stuff
3852
4923
  /** @readonly */
3853
4924
  static _valHooks = {
3854
4925
  option: {
@@ -4262,6 +5333,7 @@ class TinyHtml {
4262
5333
  return TinyHtml.valBool(this);
4263
5334
  }
4264
5335
  ////////////////////////////////////////////
5336
+ // TITLE: Listen for Paste
4265
5337
  /**
4266
5338
  * Registers a listener for the "paste" event to extract files and text from the clipboard (e.g., when the user presses Ctrl+V).
4267
5339
  *
@@ -4315,6 +5387,8 @@ class TinyHtml {
4315
5387
  listenForPaste({ onFilePaste, onTextPaste } = {}) {
4316
5388
  return TinyHtml.listenForPaste(this, { onFilePaste, onTextPaste });
4317
5389
  }
5390
+ /////////////////////////////////////////////////////
5391
+ // TITLE: Events Stuff
4318
5392
  /**
4319
5393
  * Checks if the element has a listener for a specific event.
4320
5394
  *
@@ -4367,6 +5441,40 @@ class TinyHtml {
4367
5441
  hasExactEventListener(event, handler) {
4368
5442
  return TinyHtml.hasExactEventListener(this, event, handler);
4369
5443
  }
5444
+ /**
5445
+ * Hover event shortcut.
5446
+ *
5447
+ * Adds `mouseenter` and `mouseleave` event listeners to the target.
5448
+ *
5449
+ * @template {TinyEventTarget|TinyEventTarget[]} T
5450
+ * @param {T} el - The target element(s) to attach the hover handlers.
5451
+ * @param {HoverEventCallback} fnOver - Callback executed when the mouse enters the target.
5452
+ * @param {HoverEventCallback|null} [fnOut] - Optional callback executed when the mouse leaves
5453
+ * the target. If not provided, `fnOver` will be used for both events.
5454
+ * @param {EventRegistryOptions} [options] - Optional event listener options.
5455
+ * @returns {T}
5456
+ */
5457
+ static hover(el, fnOver, fnOut, options) {
5458
+ // @ts-ignore
5459
+ TinyHtml.on(el, 'mouseenter', fnOver, options);
5460
+ // @ts-ignore
5461
+ TinyHtml.on(el, 'mouseleave', fnOut || fnOver, options);
5462
+ return el;
5463
+ }
5464
+ /**
5465
+ * Hover event shortcut.
5466
+ *
5467
+ * Adds `mouseenter` and `mouseleave` event listeners to the target.
5468
+ *
5469
+ * @param {HoverEventCallback} fnOver - Callback executed when the mouse enters the target.
5470
+ * @param {HoverEventCallback|null} [fnOut] - Optional callback executed when the mouse leaves
5471
+ * the target. If not provided, `fnOver` will be used for both events.
5472
+ * @param {EventRegistryOptions} [options] - Optional event listener options.
5473
+ * @returns {this}
5474
+ */
5475
+ hover(fnOver, fnOut, options) {
5476
+ return TinyHtml.hover(this, fnOver, fnOut, options);
5477
+ }
4370
5478
  /**
4371
5479
  * Registers an event listener on the specified element.
4372
5480
  *
@@ -4602,6 +5710,7 @@ class TinyHtml {
4602
5710
  return TinyHtml.trigger(this, events, payload);
4603
5711
  }
4604
5712
  ///////////////////////////////////////////////////////////////
5713
+ // TITLE: ProFix Manager
4605
5714
  /**
4606
5715
  * Internal property name normalization map (similar to jQuery's `propFix`).
4607
5716
  * Maps attribute-like names to their JavaScript DOM property equivalents.
@@ -4885,6 +5994,7 @@ class TinyHtml {
4885
5994
  return TinyHtml.toggleProp(this, name, force);
4886
5995
  }
4887
5996
  /////////////////////////////////////////////////////
5997
+ // TITLE: Remove Element
4888
5998
  /**
4889
5999
  * Removes an element from the DOM.
4890
6000
  * @template {TinyElement|TinyElement[]} T
@@ -4902,6 +6012,8 @@ class TinyHtml {
4902
6012
  remove() {
4903
6013
  return TinyHtml.remove(this);
4904
6014
  }
6015
+ ///////////////////////////////////////////////////
6016
+ // TITLE: Index Manager
4905
6017
  /**
4906
6018
  * Returns the index of the first element within its parent or relative to a selector/element.
4907
6019
  *
@@ -4932,6 +6044,7 @@ class TinyHtml {
4932
6044
  return TinyHtml.index(this, elem);
4933
6045
  }
4934
6046
  ////////////////////////////////////////////////////////////////////
6047
+ // TITLE: Collision Stuff
4935
6048
  /**
4936
6049
  * Creates a new DOMRect object by copying the base rect and applying optional additional dimensions.
4937
6050
  *
@@ -5200,6 +6313,7 @@ class TinyHtml {
5200
6313
  return TinyHtml.resetCollLockDir(this, direction);
5201
6314
  }
5202
6315
  //////////////////////////////////////////////////////////////////////////////
6316
+ // TITLE: Viewport Stuff
5203
6317
  /**
5204
6318
  * Checks if the given element is at least partially visible in the viewport.
5205
6319
  *
@@ -5320,6 +6434,7 @@ class TinyHtml {
5320
6434
  isFullyInContainer(cont) {
5321
6435
  return TinyHtml.isFullyInContainer(this, cont);
5322
6436
  }
6437
+ // TITLE: Has Scroll
5323
6438
  /**
5324
6439
  * Checks if an element has scrollable content.
5325
6440
  *