tiny-essentials 1.21.9 → 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.
- package/.vscode/extensions.json +30 -0
- package/.vscode/settings.json +53 -0
- package/LICENSE +160 -669
- package/dist/v1/TinyBasicsEs.min.js +1 -1
- package/dist/v1/TinyDragger.min.js +1 -1
- package/dist/v1/TinyElementObserver.min.js +1 -0
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyHtml.min.js +1 -1
- package/dist/v1/TinySmartScroller.min.js +1 -1
- package/dist/v1/TinyUploadClicker.min.js +1 -1
- package/dist/v1/basics/array.cjs +12 -0
- package/dist/v1/basics/array.d.mts +9 -0
- package/dist/v1/basics/array.mjs +10 -0
- package/dist/v1/basics/index.cjs +2 -0
- package/dist/v1/basics/index.d.mts +3 -1
- package/dist/v1/basics/index.mjs +3 -3
- package/dist/v1/basics/text.cjs +44 -9
- package/dist/v1/basics/text.d.mts +14 -2
- package/dist/v1/basics/text.mjs +38 -9
- package/dist/v1/build/TinyElementObserver.cjs +7 -0
- package/dist/v1/build/TinyElementObserver.d.mts +3 -0
- package/dist/v1/build/TinyElementObserver.mjs +2 -0
- package/dist/v1/index.cjs +4 -0
- package/dist/v1/index.d.mts +4 -1
- package/dist/v1/index.mjs +4 -3
- package/dist/v1/libs/TinyElementObserver.cjs +292 -0
- package/dist/v1/libs/TinyElementObserver.d.mts +154 -0
- package/dist/v1/libs/TinyElementObserver.mjs +266 -0
- package/dist/v1/libs/TinyGamepad.d.mts +1 -1
- package/dist/v1/libs/TinyHtml.cjs +1489 -173
- package/dist/v1/libs/TinyHtml.d.mts +543 -42
- package/dist/v1/libs/TinyHtml.mjs +1200 -61
- package/dist/v1/libs/TinyInventory.d.mts +1 -1
- package/dist/v1/libs/UltraRandomMsgGen.d.mts +7 -7
- package/docs/v1/README.md +9 -0
- package/docs/v1/Regex-Helpers.md +72 -0
- package/docs/v1/basics/array.md +20 -0
- package/docs/v1/basics/text.md +38 -7
- package/docs/v1/libs/TinyElementObserver.md +107 -0
- package/docs/v1/libs/TinyHtml.md +803 -8
- package/package.json +2 -2
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var array = require('../basics/array.cjs');
|
|
4
|
+
var text = require('../basics/text.cjs');
|
|
3
5
|
var collision = require('../basics/collision.cjs');
|
|
6
|
+
var TinyElementObserver = require('./TinyElementObserver.cjs');
|
|
4
7
|
|
|
8
|
+
// TITLE: Introduction
|
|
5
9
|
const {
|
|
6
10
|
areElsColliding,
|
|
7
11
|
areElsPerfColliding,
|
|
@@ -16,6 +20,55 @@ const {
|
|
|
16
20
|
* @typedef {TinyHtml<ConstructorElValues>} TinyHtmlAny
|
|
17
21
|
*/
|
|
18
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Callback function used for hover events.
|
|
25
|
+
* @callback HoverEventCallback
|
|
26
|
+
* @param {MouseEvent} ev - The mouse event triggered on enter or leave.
|
|
27
|
+
* @returns {void} Returns nothing.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Represents a collection of active style-based animations.
|
|
32
|
+
*
|
|
33
|
+
* Each HTMLElement is associated with an array of its currently running
|
|
34
|
+
* `Animation` objects (from the Web Animations API).
|
|
35
|
+
*
|
|
36
|
+
* @typedef {Map<HTMLElement, Animation|null>} StyleFxResult
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Represents a collection of animation keyframe data mapped by CSS property.
|
|
41
|
+
*
|
|
42
|
+
* - The **key** is the CSS property name (e.g. `"height"`, `"opacity"`).
|
|
43
|
+
* - The **value** is an array of values representing the start and end
|
|
44
|
+
* states of the property during the animation.
|
|
45
|
+
*
|
|
46
|
+
* @typedef {Record<string, (string|number)[]>} AnimationSfxData
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Function signature for style effects repeat detectors.
|
|
51
|
+
*
|
|
52
|
+
* @typedef {(effects: AnimationSfxData) => boolean} StyleEffectsRdFn
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Function signature for style effect property handlers.
|
|
57
|
+
*
|
|
58
|
+
* @typedef {(
|
|
59
|
+
* el: HTMLElement,
|
|
60
|
+
* keyframes: AnimationSfxData,
|
|
61
|
+
* prop: string,
|
|
62
|
+
* style: CSSStyleDeclaration
|
|
63
|
+
* ) => void} StyleEffectsFn
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A collection of style effect property handlers.
|
|
68
|
+
*
|
|
69
|
+
* @typedef {Record<string, StyleEffectsFn>} StyleEffectsProps
|
|
70
|
+
*/
|
|
71
|
+
|
|
19
72
|
/**
|
|
20
73
|
* Callback invoked on each animation frame with the current scroll position,
|
|
21
74
|
* normalized animation time (`0` to `1`), and a completion flag.
|
|
@@ -176,6 +229,34 @@ const __eventRegistry = new WeakMap();
|
|
|
176
229
|
*/
|
|
177
230
|
const __elementDataMap = new WeakMap();
|
|
178
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Internal storage for animation-related data, associated with elements.
|
|
234
|
+
* Used to remember original dimensions (height/width) and other properties
|
|
235
|
+
* so that animations like `slideUp` and `slideDown` can restore or continue
|
|
236
|
+
* smoothly, mimicking jQuery's behavior.
|
|
237
|
+
*
|
|
238
|
+
* Each element is mapped to a plain object with keys such as `origHeight`,
|
|
239
|
+
* `origWidth`, etc.
|
|
240
|
+
*
|
|
241
|
+
* @type {WeakMap<HTMLElement, Record<string, string|number>>}
|
|
242
|
+
*/
|
|
243
|
+
const __elementAnimateData = new WeakMap();
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Stores the currently active animation for each element,
|
|
247
|
+
* allowing cancellation or replacement of ongoing animations.
|
|
248
|
+
*
|
|
249
|
+
* @type {WeakMap<HTMLElement, { animation: Animation, id: string }>}
|
|
250
|
+
*/
|
|
251
|
+
const __elementCurrentAnimation = new WeakMap();
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Mapping of animation shortcuts to their effect definitions.
|
|
255
|
+
* Similar to jQuery's predefined effects (slideDown, fadeIn, etc.).
|
|
256
|
+
*
|
|
257
|
+
* @typedef {Record<string, string|(string|number)[]>} StyleEffects
|
|
258
|
+
*/
|
|
259
|
+
|
|
179
260
|
/**
|
|
180
261
|
* Stores directional collision locks separately.
|
|
181
262
|
* Each direction has its own WeakMap to allow independent locking.
|
|
@@ -240,6 +321,8 @@ const __elemCollision = {
|
|
|
240
321
|
* ]} HtmlParsed
|
|
241
322
|
*/
|
|
242
323
|
|
|
324
|
+
// TITLE: Class Intro
|
|
325
|
+
|
|
243
326
|
/**
|
|
244
327
|
* TinyHtml is a utility class that provides static and instance-level methods
|
|
245
328
|
* for precise dimension and position computations on HTML elements.
|
|
@@ -256,6 +339,132 @@ class TinyHtml {
|
|
|
256
339
|
|
|
257
340
|
static Utils = { ...collision };
|
|
258
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Parse inline styles into an object.
|
|
344
|
+
* @param {string} styleText
|
|
345
|
+
* @returns {Record<string,string>}
|
|
346
|
+
*/
|
|
347
|
+
static parseStyle(styleText) {
|
|
348
|
+
/** @type {Record<string,string>}} */
|
|
349
|
+
const styles = {};
|
|
350
|
+
styleText.split(';').forEach((rule) => {
|
|
351
|
+
const [prop, value] = rule.split(':').map((s) => s && s.trim());
|
|
352
|
+
if (prop && value) {
|
|
353
|
+
styles[prop] = value;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
return styles;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/////////////////////////////////////////////////////////////////////
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Flag to determine if element observer should start automatically.
|
|
363
|
+
* @type {boolean}
|
|
364
|
+
*/
|
|
365
|
+
static #autoStartElemObserver = true;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Get the auto-start flag for the observer.
|
|
369
|
+
* @returns {boolean}
|
|
370
|
+
*/
|
|
371
|
+
static get autoStartElemObserver() {
|
|
372
|
+
return TinyHtml.#autoStartElemObserver;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Set the auto-start flag for the observer.
|
|
377
|
+
* @param {boolean} value
|
|
378
|
+
*/
|
|
379
|
+
static set autoStartElemObserver(value) {
|
|
380
|
+
if (typeof value !== 'boolean') throw new TypeError('autoStartElemObserver must be a boolean.');
|
|
381
|
+
TinyHtml.#autoStartElemObserver = value;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** @type {TinyElementObserver} */
|
|
385
|
+
static #tinyObserver = new TinyElementObserver({
|
|
386
|
+
el:
|
|
387
|
+
typeof window !== 'undefined' && typeof window.document !== 'undefined'
|
|
388
|
+
? window.document.documentElement
|
|
389
|
+
: undefined,
|
|
390
|
+
initDetectors: [
|
|
391
|
+
// Style Detector
|
|
392
|
+
[
|
|
393
|
+
'tinyStyleEvent',
|
|
394
|
+
(mutation) => {
|
|
395
|
+
if (
|
|
396
|
+
mutation.type !== 'attributes' ||
|
|
397
|
+
mutation.attributeName !== 'style' ||
|
|
398
|
+
!(mutation.target instanceof HTMLElement)
|
|
399
|
+
)
|
|
400
|
+
return;
|
|
401
|
+
const oldVal = mutation.oldValue || '';
|
|
402
|
+
const newVal = mutation.target.getAttribute('style') || '';
|
|
403
|
+
|
|
404
|
+
const oldStyles = TinyHtml.parseStyle(oldVal);
|
|
405
|
+
const newStyles = TinyHtml.parseStyle(newVal);
|
|
406
|
+
|
|
407
|
+
const changes = text.diffStrings(oldStyles, newStyles);
|
|
408
|
+
|
|
409
|
+
if (
|
|
410
|
+
Object.keys(changes.added).length ||
|
|
411
|
+
Object.keys(changes.removed).length ||
|
|
412
|
+
Object.keys(changes.modified).length
|
|
413
|
+
) {
|
|
414
|
+
mutation.target.dispatchEvent(
|
|
415
|
+
new CustomEvent('tinyhtml.stylechanged', {
|
|
416
|
+
detail: changes,
|
|
417
|
+
}),
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
|
|
423
|
+
// Class Detector
|
|
424
|
+
[
|
|
425
|
+
'tinyClassEvent',
|
|
426
|
+
(mutation) => {
|
|
427
|
+
if (
|
|
428
|
+
mutation.type !== 'attributes' ||
|
|
429
|
+
mutation.attributeName !== 'class' ||
|
|
430
|
+
!(mutation.target instanceof HTMLElement)
|
|
431
|
+
)
|
|
432
|
+
return;
|
|
433
|
+
const oldVal = mutation.oldValue || '';
|
|
434
|
+
const newVal = mutation.target.className || '';
|
|
435
|
+
|
|
436
|
+
const oldClasses = oldVal.split(/\s+/).filter(Boolean);
|
|
437
|
+
const newClasses = newVal.split(/\s+/).filter(Boolean);
|
|
438
|
+
|
|
439
|
+
const changes = array.diffArrayList(oldClasses, newClasses);
|
|
440
|
+
|
|
441
|
+
if (changes.added.length || changes.removed.length) {
|
|
442
|
+
mutation.target.dispatchEvent(
|
|
443
|
+
new CustomEvent('tinyhtml.classchanged', {
|
|
444
|
+
detail: changes,
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
],
|
|
451
|
+
initCfg: {
|
|
452
|
+
attributeOldValue: true,
|
|
453
|
+
attributes: true,
|
|
454
|
+
subtree: true,
|
|
455
|
+
attributeFilter: ['style', 'class'],
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
/** @returns {TinyElementObserver} */
|
|
460
|
+
static get tinyObserver() {
|
|
461
|
+
return TinyHtml.#tinyObserver;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
///////////////////////////////////////////////////////////////////
|
|
465
|
+
|
|
466
|
+
// TITLE: Fetch Html List
|
|
467
|
+
|
|
259
468
|
/**
|
|
260
469
|
* Fetches an HTML file from the given URL, parses it to JSON.
|
|
261
470
|
*
|
|
@@ -302,6 +511,8 @@ class TinyHtml {
|
|
|
302
511
|
return TinyHtml.toTinyElm(nodes);
|
|
303
512
|
}
|
|
304
513
|
|
|
514
|
+
// TITLE: Fetch Template List
|
|
515
|
+
|
|
305
516
|
/**
|
|
306
517
|
* Converts the content of a <template> to an array of HtmlParsed.
|
|
307
518
|
*
|
|
@@ -347,6 +558,8 @@ class TinyHtml {
|
|
|
347
558
|
return TinyHtml.toTinyElm(TinyHtml.templateToNodes(nodes));
|
|
348
559
|
}
|
|
349
560
|
|
|
561
|
+
// TITLE: Fetch Json List
|
|
562
|
+
|
|
350
563
|
/**
|
|
351
564
|
* Parses a full HTML string into a JSON-like structure.
|
|
352
565
|
*
|
|
@@ -438,6 +651,8 @@ class TinyHtml {
|
|
|
438
651
|
return TinyHtml.toTinyElm(TinyHtml.jsonToNodes(jsonArray));
|
|
439
652
|
}
|
|
440
653
|
|
|
654
|
+
// TITLE: Element Creator
|
|
655
|
+
|
|
441
656
|
/**
|
|
442
657
|
* Creates a new TinyHtml element from a tag name and optional attributes.
|
|
443
658
|
*
|
|
@@ -521,6 +736,9 @@ class TinyHtml {
|
|
|
521
736
|
return new TinyHtml(template.content.firstChild);
|
|
522
737
|
}
|
|
523
738
|
|
|
739
|
+
///////////////////////////////////////////////////
|
|
740
|
+
// TITLE: Query Script
|
|
741
|
+
|
|
524
742
|
/**
|
|
525
743
|
* Queries the document for the first element matching the CSS selector and wraps it in a TinyHtml instance.
|
|
526
744
|
*
|
|
@@ -639,6 +857,8 @@ class TinyHtml {
|
|
|
639
857
|
|
|
640
858
|
//////////////////////////////////////////////////////////////////
|
|
641
859
|
|
|
860
|
+
// TITLE: Element getter
|
|
861
|
+
|
|
642
862
|
/**
|
|
643
863
|
* Iterates over all elements, executing the provided callback on each.
|
|
644
864
|
* @param {(element: TinyHtmlAny, index: number, items: TinyHtmlAny[]) => void} callback - Function invoked for each element.
|
|
@@ -695,6 +915,10 @@ class TinyHtml {
|
|
|
695
915
|
return [...this.#el];
|
|
696
916
|
}
|
|
697
917
|
|
|
918
|
+
////////////////////////////////////////////////
|
|
919
|
+
|
|
920
|
+
// TITLE: Element Getter (Private) (Pt1)
|
|
921
|
+
|
|
698
922
|
/**
|
|
699
923
|
* Returns the current Element held by this instance.
|
|
700
924
|
*
|
|
@@ -737,6 +961,8 @@ class TinyHtml {
|
|
|
737
961
|
|
|
738
962
|
//////////////////////////////////////////////////////
|
|
739
963
|
|
|
964
|
+
// TITLE: Element Getter (Private) (Pt2)
|
|
965
|
+
|
|
740
966
|
/**
|
|
741
967
|
* Prepares and validates multiple elements against allowed types.
|
|
742
968
|
*
|
|
@@ -1069,6 +1295,10 @@ class TinyHtml {
|
|
|
1069
1295
|
return TinyHtml._preElemTemplate(elems, where, [Element, Document], ['Element', 'Document']);
|
|
1070
1296
|
}
|
|
1071
1297
|
|
|
1298
|
+
//////////////////////////////////////////////////////
|
|
1299
|
+
|
|
1300
|
+
// TITLE: Converter DOM <--> TinyHtml
|
|
1301
|
+
|
|
1072
1302
|
/**
|
|
1073
1303
|
* Normalizes and converts one or more DOM elements (or TinyHtml instances)
|
|
1074
1304
|
* into an array of `TinyHtml` instances.
|
|
@@ -1122,6 +1352,10 @@ class TinyHtml {
|
|
|
1122
1352
|
return checkElement(elems);
|
|
1123
1353
|
}
|
|
1124
1354
|
|
|
1355
|
+
//////////////////////////////////////////////////
|
|
1356
|
+
|
|
1357
|
+
// TITLE: DOM Getter
|
|
1358
|
+
|
|
1125
1359
|
/**
|
|
1126
1360
|
* Filters an array of elements based on a selector, function, element, or array of elements.
|
|
1127
1361
|
*
|
|
@@ -1348,6 +1582,8 @@ class TinyHtml {
|
|
|
1348
1582
|
|
|
1349
1583
|
//////////////////////////////////////////////////////////////////
|
|
1350
1584
|
|
|
1585
|
+
// TITLE: Data Manager
|
|
1586
|
+
|
|
1351
1587
|
/**
|
|
1352
1588
|
* Internal data storage for element information.
|
|
1353
1589
|
* @type {ElementDataStore}
|
|
@@ -1464,6 +1700,8 @@ class TinyHtml {
|
|
|
1464
1700
|
|
|
1465
1701
|
//////////////////////////////////////////////////////
|
|
1466
1702
|
|
|
1703
|
+
// TITLE: DOM Getter 2
|
|
1704
|
+
|
|
1467
1705
|
/**
|
|
1468
1706
|
* Get the sibling element in a given direction.
|
|
1469
1707
|
*
|
|
@@ -1763,6 +2001,10 @@ class TinyHtml {
|
|
|
1763
2001
|
return TinyHtml.contents(this);
|
|
1764
2002
|
}
|
|
1765
2003
|
|
|
2004
|
+
////////////////////////////////////////////
|
|
2005
|
+
|
|
2006
|
+
// TITLE: Clone Dom
|
|
2007
|
+
|
|
1766
2008
|
/**
|
|
1767
2009
|
* Clone each element.
|
|
1768
2010
|
* @param {TinyNode|TinyNode[]} el
|
|
@@ -1783,6 +2025,10 @@ class TinyHtml {
|
|
|
1783
2025
|
return TinyHtml.clone(this, deep)[0];
|
|
1784
2026
|
}
|
|
1785
2027
|
|
|
2028
|
+
//////////////////////////////////////////////////
|
|
2029
|
+
|
|
2030
|
+
// TITLE: Append Content
|
|
2031
|
+
|
|
1786
2032
|
/**
|
|
1787
2033
|
* Normalize and validate nodes before DOM insertion.
|
|
1788
2034
|
* Converts TinyNode-like structures or strings into DOM-compatible nodes.
|
|
@@ -2073,6 +2319,8 @@ class TinyHtml {
|
|
|
2073
2319
|
|
|
2074
2320
|
//////////////////////////////////////////////////////
|
|
2075
2321
|
|
|
2322
|
+
// TITLE: Constructor Base
|
|
2323
|
+
|
|
2076
2324
|
/**
|
|
2077
2325
|
* The target HTML element for instance-level operations.
|
|
2078
2326
|
* @type {ConstructorElValues[]}
|
|
@@ -2141,6 +2389,8 @@ class TinyHtml {
|
|
|
2141
2389
|
|
|
2142
2390
|
/////////////////////////////////////////////////////
|
|
2143
2391
|
|
|
2392
|
+
// TITLE: CSS Stuff
|
|
2393
|
+
|
|
2144
2394
|
/**
|
|
2145
2395
|
* Returns the full computed CSS styles for the given element.
|
|
2146
2396
|
*
|
|
@@ -2274,6 +2524,8 @@ class TinyHtml {
|
|
|
2274
2524
|
|
|
2275
2525
|
//////////////////////////////////////////////////////////////////////
|
|
2276
2526
|
|
|
2527
|
+
// TITLE: Style Stuff
|
|
2528
|
+
|
|
2277
2529
|
/**
|
|
2278
2530
|
* Stores camelCase to kebab-case CSS property aliases.
|
|
2279
2531
|
*
|
|
@@ -2789,6 +3041,8 @@ class TinyHtml {
|
|
|
2789
3041
|
|
|
2790
3042
|
//////////////////////////////////////////////////////////////////////
|
|
2791
3043
|
|
|
3044
|
+
// TITLE: Focus/Blur
|
|
3045
|
+
|
|
2792
3046
|
/**
|
|
2793
3047
|
* Focus the element.
|
|
2794
3048
|
*
|
|
@@ -2831,6 +3085,10 @@ class TinyHtml {
|
|
|
2831
3085
|
return TinyHtml.blur(this);
|
|
2832
3086
|
}
|
|
2833
3087
|
|
|
3088
|
+
/////////////////////////////////////////////////////////
|
|
3089
|
+
|
|
3090
|
+
// TITLE: Select
|
|
3091
|
+
|
|
2834
3092
|
/**
|
|
2835
3093
|
* Select the text content of an input or textarea element.
|
|
2836
3094
|
*
|
|
@@ -2853,6 +3111,8 @@ class TinyHtml {
|
|
|
2853
3111
|
return TinyHtml.select(this);
|
|
2854
3112
|
}
|
|
2855
3113
|
|
|
3114
|
+
// TITLE: Bool Checker
|
|
3115
|
+
|
|
2856
3116
|
/**
|
|
2857
3117
|
* Interprets a value as a boolean `true` if it matches a common truthy representation.
|
|
2858
3118
|
*
|
|
@@ -2879,6 +3139,8 @@ class TinyHtml {
|
|
|
2879
3139
|
|
|
2880
3140
|
//////////////////////////////////////////////////////////////////////
|
|
2881
3141
|
|
|
3142
|
+
// TITLE: Window Getter/Setter
|
|
3143
|
+
|
|
2882
3144
|
/**
|
|
2883
3145
|
* Sets the vertical scroll position of the window.
|
|
2884
3146
|
* @param {number} value - Sets the scroll position.
|
|
@@ -3213,194 +3475,1143 @@ class TinyHtml {
|
|
|
3213
3475
|
|
|
3214
3476
|
//////////////////////////////////////////////////
|
|
3215
3477
|
|
|
3478
|
+
// TITLE: Animate DOM (Data)
|
|
3479
|
+
|
|
3216
3480
|
/**
|
|
3217
|
-
*
|
|
3481
|
+
* Retrieves stored animation data for a given element and key.
|
|
3482
|
+
* If no data exists yet, initializes storage for that element.
|
|
3218
3483
|
*
|
|
3219
|
-
* @
|
|
3220
|
-
* @param {
|
|
3221
|
-
* @
|
|
3222
|
-
* @param {number | KeyframeAnimationOptions} [ops] - Timing or configuration options for the animation.
|
|
3223
|
-
* @returns {T}
|
|
3484
|
+
* @param {HTMLElement} el - The element whose data should be retrieved.
|
|
3485
|
+
* @param {string} where - The key to read (e.g., "origHeight").
|
|
3486
|
+
* @returns {string|number|undefined} - The stored value, or undefined.
|
|
3224
3487
|
*/
|
|
3225
|
-
static
|
|
3226
|
-
|
|
3227
|
-
|
|
3488
|
+
static getAnimateData(el, where) {
|
|
3489
|
+
let dataset = __elementAnimateData.get(el);
|
|
3490
|
+
if (!dataset) {
|
|
3491
|
+
dataset = {};
|
|
3492
|
+
__elementAnimateData.set(el, dataset);
|
|
3493
|
+
}
|
|
3494
|
+
return dataset[where];
|
|
3228
3495
|
}
|
|
3229
3496
|
|
|
3230
3497
|
/**
|
|
3231
|
-
*
|
|
3498
|
+
* Stores animation data for a given element and key.
|
|
3499
|
+
* Used to cache original size/values for animations.
|
|
3232
3500
|
*
|
|
3233
|
-
* @param {
|
|
3234
|
-
* @param {
|
|
3235
|
-
* @
|
|
3501
|
+
* @param {HTMLElement} el - The element whose data should be set.
|
|
3502
|
+
* @param {string} where - The key to store under (e.g., "origHeight").
|
|
3503
|
+
* @param {string|number} value - The value to store.
|
|
3236
3504
|
*/
|
|
3237
|
-
|
|
3238
|
-
|
|
3505
|
+
static setAnimateData(el, where, value) {
|
|
3506
|
+
if (!(el instanceof HTMLElement))
|
|
3507
|
+
throw new TypeError('setAnimateData: el must be an HTMLElement.');
|
|
3508
|
+
if (typeof where !== 'string') throw new TypeError('setAnimateData: where must be a string.');
|
|
3509
|
+
if (!(typeof value === 'string' || typeof value === 'number'))
|
|
3510
|
+
throw new TypeError('setAnimateData: value must be a string or number.');
|
|
3511
|
+
|
|
3512
|
+
let dataset = __elementAnimateData.get(el);
|
|
3513
|
+
if (!dataset) {
|
|
3514
|
+
dataset = {};
|
|
3515
|
+
__elementAnimateData.set(el, dataset);
|
|
3516
|
+
}
|
|
3517
|
+
dataset[where] = value;
|
|
3239
3518
|
}
|
|
3240
3519
|
|
|
3520
|
+
// TITLE: Animate DOM (cancelOldStyleFx)
|
|
3521
|
+
|
|
3241
3522
|
/**
|
|
3242
|
-
*
|
|
3243
|
-
*
|
|
3244
|
-
*
|
|
3523
|
+
* Global configuration flag controlling whether old style-based animations
|
|
3524
|
+
* are cancelled before a new one starts. Defaults to true.
|
|
3525
|
+
*
|
|
3526
|
+
* @type {boolean}
|
|
3245
3527
|
*/
|
|
3246
|
-
static
|
|
3247
|
-
const elem = TinyHtml._preElem(el, 'offset');
|
|
3248
|
-
const rect = elem.getBoundingClientRect();
|
|
3249
|
-
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
3250
|
-
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
|
|
3251
|
-
|
|
3252
|
-
return {
|
|
3253
|
-
top: rect.top + scrollTop,
|
|
3254
|
-
left: rect.left + scrollLeft,
|
|
3255
|
-
};
|
|
3256
|
-
}
|
|
3528
|
+
static #cancelOldStyleFx = true;
|
|
3257
3529
|
|
|
3258
3530
|
/**
|
|
3259
|
-
*
|
|
3260
|
-
*
|
|
3531
|
+
* Returns the current global setting for cancelling old style-based animations.
|
|
3532
|
+
*
|
|
3533
|
+
* @returns {boolean} True if old animations are cancelled by default, false otherwise.
|
|
3261
3534
|
*/
|
|
3262
|
-
|
|
3263
|
-
return TinyHtml
|
|
3535
|
+
static get cancelOldStyleFx() {
|
|
3536
|
+
return TinyHtml.#cancelOldStyleFx;
|
|
3264
3537
|
}
|
|
3265
3538
|
|
|
3266
3539
|
/**
|
|
3267
|
-
*
|
|
3268
|
-
*
|
|
3269
|
-
*
|
|
3540
|
+
* Updates the global setting that determines whether old style-based animations
|
|
3541
|
+
* are cancelled before new ones start.
|
|
3542
|
+
*
|
|
3543
|
+
* @param {boolean} value - True to cancel old animations by default, false to keep them running.
|
|
3544
|
+
* @throws {TypeError} If the value is not a boolean.
|
|
3270
3545
|
*/
|
|
3271
|
-
static
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
let offset;
|
|
3276
|
-
let parentOffset = { top: 0, left: 0 };
|
|
3546
|
+
static set cancelOldStyleFx(value) {
|
|
3547
|
+
if (typeof value !== 'boolean') throw new TypeError('Expected a boolean value.');
|
|
3548
|
+
TinyHtml.#cancelOldStyleFx = value;
|
|
3549
|
+
}
|
|
3277
3550
|
|
|
3278
|
-
|
|
3551
|
+
// TITLE: Animate DOM (styleFxSpeeds)
|
|
3279
3552
|
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3553
|
+
/**
|
|
3554
|
+
* Predefined animation speed options, inspired by jQuery.fx.speeds.
|
|
3555
|
+
* Each entry can be either a number (duration in ms) or a KeyframeAnimationOptions object.
|
|
3556
|
+
*
|
|
3557
|
+
* Usage example:
|
|
3558
|
+
* TinyHtml.animate(el, keyframes, TinyHtml.#fxSpeeds.fast);
|
|
3559
|
+
* TinyHtml.slideDown(el, TinyHtml.#fxSpeeds.slow);
|
|
3560
|
+
*
|
|
3561
|
+
* @type {Record<string, number | KeyframeAnimationOptions>}
|
|
3562
|
+
*/
|
|
3563
|
+
static #styleFxSpeeds = {
|
|
3564
|
+
slow: { duration: 600, easing: 'ease' },
|
|
3565
|
+
fast: { duration: 200, easing: 'ease-out' },
|
|
3566
|
+
_default: { duration: 400, easing: 'linear' },
|
|
3567
|
+
};
|
|
3284
3568
|
|
|
3285
|
-
|
|
3286
|
-
|
|
3569
|
+
/**
|
|
3570
|
+
* Get a cloned copy of predefined animation speeds.
|
|
3571
|
+
* @returns {Record<string, number | KeyframeAnimationOptions>}
|
|
3572
|
+
*/
|
|
3573
|
+
static get styleFxSpeeds() {
|
|
3574
|
+
/** @type {Record<string, number | KeyframeAnimationOptions>} */
|
|
3575
|
+
const data = {};
|
|
3576
|
+
for (const name in TinyHtml.#styleFxSpeeds) {
|
|
3577
|
+
const info = TinyHtml.#styleFxSpeeds[name];
|
|
3578
|
+
data[name] = typeof info === 'object' ? { ...info } : info;
|
|
3579
|
+
}
|
|
3580
|
+
return data;
|
|
3581
|
+
}
|
|
3287
3582
|
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3583
|
+
/**
|
|
3584
|
+
* Replace the predefined animation speeds.
|
|
3585
|
+
* @param {Record<string, number | KeyframeAnimationOptions>} speeds
|
|
3586
|
+
* @throws {TypeError} If input is not a valid object of speed definitions.
|
|
3587
|
+
*/
|
|
3588
|
+
static set styleFxSpeeds(speeds) {
|
|
3589
|
+
if (typeof speeds !== 'object' || speeds === null || Array.isArray(speeds))
|
|
3590
|
+
throw new TypeError('styleFxSpeeds must be an object.');
|
|
3295
3591
|
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
offsetParent.nodeType === 1
|
|
3300
|
-
) {
|
|
3301
|
-
const { borderTopWidth, borderLeftWidth } = TinyHtml.cssFloats(offsetParent, [
|
|
3302
|
-
'borderTopWidth',
|
|
3303
|
-
'borderLeftWidth',
|
|
3304
|
-
]);
|
|
3305
|
-
parentOffset = TinyHtml.offset(offsetParent);
|
|
3306
|
-
parentOffset.top += borderTopWidth;
|
|
3307
|
-
parentOffset.left += borderLeftWidth;
|
|
3308
|
-
}
|
|
3592
|
+
for (const [k, v] of Object.entries(speeds)) {
|
|
3593
|
+
if (!(typeof v === 'number' || typeof v === 'object'))
|
|
3594
|
+
throw new TypeError(`styleFxSpeeds["${k}"] must be a number or KeyframeAnimationOptions.`);
|
|
3309
3595
|
}
|
|
3310
3596
|
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
left: offset.left - parentOffset.left - TinyHtml.cssFloat(elem, 'marginLeft'),
|
|
3314
|
-
};
|
|
3597
|
+
TinyHtml.#styleFxSpeeds = {};
|
|
3598
|
+
for (const [k, v] of Object.entries(speeds)) TinyHtml.setStyleFxSpeed(k, v);
|
|
3315
3599
|
}
|
|
3316
3600
|
|
|
3317
3601
|
/**
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3602
|
+
* Get a predefined animation speed by name.
|
|
3603
|
+
*
|
|
3604
|
+
* @param {string} name - The name of the speed entry.
|
|
3605
|
+
* @returns {number | KeyframeAnimationOptions | undefined} A cloned value of the speed entry, or undefined if not found.
|
|
3320
3606
|
*/
|
|
3321
|
-
|
|
3322
|
-
|
|
3607
|
+
static getStyleFxSpeed(name) {
|
|
3608
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3609
|
+
const spd = TinyHtml.#styleFxSpeeds[name];
|
|
3610
|
+
return typeof spd === 'object' ? { ...spd } : spd;
|
|
3323
3611
|
}
|
|
3324
3612
|
|
|
3325
3613
|
/**
|
|
3326
|
-
*
|
|
3327
|
-
*
|
|
3328
|
-
* @
|
|
3614
|
+
* Set or overwrite a predefined animation speed.
|
|
3615
|
+
*
|
|
3616
|
+
* @param {string} name - The name of the speed entry.
|
|
3617
|
+
* @param {number | KeyframeAnimationOptions} value - The value to store.
|
|
3618
|
+
* @throws {TypeError} If value is not a number or KeyframeAnimationOptions object.
|
|
3329
3619
|
*/
|
|
3330
|
-
static
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
offsetParent instanceof HTMLElement &&
|
|
3336
|
-
window.getComputedStyle(offsetParent).position === 'static'
|
|
3337
|
-
) {
|
|
3338
|
-
offsetParent = offsetParent.offsetParent;
|
|
3339
|
-
}
|
|
3340
|
-
|
|
3341
|
-
// Fallback to document.documentElement
|
|
3342
|
-
return offsetParent instanceof HTMLElement ? offsetParent : document.documentElement;
|
|
3620
|
+
static setStyleFxSpeed(name, value) {
|
|
3621
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3622
|
+
if (!(typeof value === 'number' || typeof value === 'object'))
|
|
3623
|
+
throw new TypeError('styleFxSpeed must be a number or KeyframeAnimationOptions');
|
|
3624
|
+
TinyHtml.#styleFxSpeeds[name] = typeof value === 'object' ? { ...value } : value;
|
|
3343
3625
|
}
|
|
3344
3626
|
|
|
3345
3627
|
/**
|
|
3346
|
-
*
|
|
3347
|
-
*
|
|
3628
|
+
* Delete a predefined animation speed by name.
|
|
3629
|
+
*
|
|
3630
|
+
* @param {string} name - The name of the speed entry to delete.
|
|
3631
|
+
* @returns {boolean} True if the property was deleted, false otherwise.
|
|
3348
3632
|
*/
|
|
3349
|
-
|
|
3350
|
-
|
|
3633
|
+
static deleteStyleFxSpeed(name) {
|
|
3634
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3635
|
+
return delete TinyHtml.#styleFxSpeeds[name];
|
|
3351
3636
|
}
|
|
3352
3637
|
|
|
3353
3638
|
/**
|
|
3354
|
-
*
|
|
3355
|
-
*
|
|
3356
|
-
* @
|
|
3639
|
+
* Check if a predefined animation speed exists.
|
|
3640
|
+
*
|
|
3641
|
+
* @param {string} name - The name of the speed entry to check.
|
|
3642
|
+
* @returns {boolean} True if the speed entry exists, false otherwise.
|
|
3357
3643
|
*/
|
|
3358
|
-
static
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
// @ts-ignore
|
|
3362
|
-
if (elem.nodeType === 9) return elem.defaultView.pageYOffset;
|
|
3363
|
-
return elem.scrollTop;
|
|
3644
|
+
static hasStyleFxSpeed(name) {
|
|
3645
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3646
|
+
return Object.prototype.hasOwnProperty.call(TinyHtml.#styleFxSpeeds, name);
|
|
3364
3647
|
}
|
|
3365
3648
|
|
|
3649
|
+
// TITLE: Animate DOM (cssExpand)
|
|
3650
|
+
|
|
3366
3651
|
/**
|
|
3367
|
-
*
|
|
3368
|
-
* @
|
|
3652
|
+
* CSS expansion shorthand used by genStyleFx to include margin/padding values.
|
|
3653
|
+
* @typedef {['Top', 'Right', 'Bottom', 'Left']}
|
|
3369
3654
|
*/
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3655
|
+
static #cssExpand = ['Top', 'Right', 'Bottom', 'Left'];
|
|
3656
|
+
|
|
3657
|
+
// TITLE: Animate DOM (styleEffects)
|
|
3373
3658
|
|
|
3374
3659
|
/**
|
|
3375
|
-
*
|
|
3376
|
-
* @
|
|
3377
|
-
* @returns {number}
|
|
3660
|
+
* Generate shortcuts
|
|
3661
|
+
* @type {Record<string, StyleEffects>}
|
|
3378
3662
|
*/
|
|
3379
|
-
static
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
}
|
|
3663
|
+
static #styleEffects = {
|
|
3664
|
+
slideDown: TinyHtml.genStyleFx('show'),
|
|
3665
|
+
slideUp: TinyHtml.genStyleFx('hide'),
|
|
3666
|
+
fadeIn: { opacity: 'show' },
|
|
3667
|
+
fadeOut: { opacity: 'hide' },
|
|
3668
|
+
};
|
|
3386
3669
|
|
|
3387
3670
|
/**
|
|
3388
|
-
*
|
|
3389
|
-
*
|
|
3671
|
+
* Returns a deep-cloned copy of the registered style effects.
|
|
3672
|
+
*
|
|
3673
|
+
* @returns {Record<string, StyleEffects>}
|
|
3390
3674
|
*/
|
|
3391
|
-
|
|
3392
|
-
return
|
|
3675
|
+
static get styleEffects() {
|
|
3676
|
+
return JSON.parse(JSON.stringify(TinyHtml.#styleEffects));
|
|
3393
3677
|
}
|
|
3394
3678
|
|
|
3395
3679
|
/**
|
|
3396
|
-
*
|
|
3397
|
-
* Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
|
|
3680
|
+
* Replace the entire styleEffects map with a new one.
|
|
3398
3681
|
*
|
|
3399
|
-
* @
|
|
3682
|
+
* @param {Record<string, StyleEffects>} value
|
|
3400
3683
|
*/
|
|
3401
|
-
static
|
|
3402
|
-
|
|
3403
|
-
|
|
3684
|
+
static set styleEffects(value) {
|
|
3685
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
3686
|
+
throw new TypeError('styleEffects must be an object');
|
|
3687
|
+
for (const name in value) {
|
|
3688
|
+
for (const [prop, mode] of Object.entries(value[name])) {
|
|
3689
|
+
if (
|
|
3690
|
+
typeof mode !== 'string' &&
|
|
3691
|
+
(!Array.isArray(mode) ||
|
|
3692
|
+
!mode.every((v) => typeof v === 'string' || typeof v === 'number'))
|
|
3693
|
+
)
|
|
3694
|
+
throw new TypeError(`Invalid styleEffect["${prop}"]`);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
TinyHtml.#styleEffects = {};
|
|
3699
|
+
for (const name in value) TinyHtml.setStyleEffect(name, value[name]);
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
/**
|
|
3703
|
+
* Get a deep-cloned style effect by name.
|
|
3704
|
+
* @param {string} name
|
|
3705
|
+
* @returns {StyleEffects|undefined}
|
|
3706
|
+
*/
|
|
3707
|
+
static getStyleEffect(name) {
|
|
3708
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3709
|
+
const eff = TinyHtml.#styleEffects[name];
|
|
3710
|
+
return eff ? JSON.parse(JSON.stringify(eff)) : undefined;
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
/**
|
|
3714
|
+
* Set or replace a style effect.
|
|
3715
|
+
* @param {string} name
|
|
3716
|
+
* @param {StyleEffects} value
|
|
3717
|
+
*/
|
|
3718
|
+
static setStyleEffect(name, value) {
|
|
3719
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3720
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
3721
|
+
throw new TypeError('styleEffect must be an object');
|
|
3722
|
+
}
|
|
3723
|
+
/** @type {StyleEffects} */
|
|
3724
|
+
const copy = {};
|
|
3725
|
+
for (const [prop, mode] of Object.entries(value)) {
|
|
3726
|
+
if (typeof mode === 'string') {
|
|
3727
|
+
copy[prop] = mode;
|
|
3728
|
+
} else if (
|
|
3729
|
+
Array.isArray(mode) &&
|
|
3730
|
+
mode.every((v) => typeof v === 'string' || typeof v === 'number')
|
|
3731
|
+
) {
|
|
3732
|
+
copy[prop] = [...mode];
|
|
3733
|
+
} else {
|
|
3734
|
+
throw new TypeError(`Invalid styleEffect["${prop}"]`);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
TinyHtml.#styleEffects[name] = copy;
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
/**
|
|
3741
|
+
* Delete a style effect.
|
|
3742
|
+
* @param {string} name
|
|
3743
|
+
* @returns {boolean} True if deleted.
|
|
3744
|
+
*/
|
|
3745
|
+
static deleteStyleEffect(name) {
|
|
3746
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3747
|
+
return delete TinyHtml.#styleEffects[name];
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
/**
|
|
3751
|
+
* Check if a style effect exists.
|
|
3752
|
+
* @param {string} name
|
|
3753
|
+
* @returns {boolean}
|
|
3754
|
+
*/
|
|
3755
|
+
static hasStyleEffect(name) {
|
|
3756
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3757
|
+
return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffects, name);
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
// TITLE: Animate DOM (styleEffectInverse)
|
|
3761
|
+
|
|
3762
|
+
/**
|
|
3763
|
+
* Style Effect Inverse Values
|
|
3764
|
+
* @type {Record<string, string>}
|
|
3765
|
+
*/
|
|
3766
|
+
static #styleEffectInverse = {
|
|
3767
|
+
slideDown: 'slideUp',
|
|
3768
|
+
slideUp: 'slideDown',
|
|
3769
|
+
fadeIn: 'fadeOut',
|
|
3770
|
+
fadeOut: 'fadeIn',
|
|
3771
|
+
};
|
|
3772
|
+
|
|
3773
|
+
/**
|
|
3774
|
+
* Get a cloned copy of style inverse values.
|
|
3775
|
+
* @returns {Record<string, string>}
|
|
3776
|
+
*/
|
|
3777
|
+
static get styleEffectInverse() {
|
|
3778
|
+
return { ...TinyHtml.#styleEffectInverse };
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* Replace the style inverse values.
|
|
3783
|
+
* @param {Record<string, string>} values
|
|
3784
|
+
* @throws {TypeError} If not a valid object with functions as values.
|
|
3785
|
+
*/
|
|
3786
|
+
static set styleEffectInverse(values) {
|
|
3787
|
+
if (typeof values !== 'object' || values === null)
|
|
3788
|
+
throw new TypeError('styleEffectInverse must be an object.');
|
|
3789
|
+
|
|
3790
|
+
for (const [k, v] of Object.entries(values)) {
|
|
3791
|
+
if (typeof v !== 'string')
|
|
3792
|
+
throw new TypeError(`styleEffectInverse["${k}"] must be a string.`);
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
TinyHtml.#styleEffectInverse = {};
|
|
3796
|
+
for (const [k, v] of Object.entries(values)) TinyHtml.setStyleEffectInverse(k, v);
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
/**
|
|
3800
|
+
* Get a registered inverse values.
|
|
3801
|
+
*
|
|
3802
|
+
* @param {string} name - The detector name.
|
|
3803
|
+
* @returns {string | null} The value if found, otherwise undefined.
|
|
3804
|
+
*/
|
|
3805
|
+
static getStyleEffectInverse(name) {
|
|
3806
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3807
|
+
return TinyHtml.#styleEffectInverse[name] || null;
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
/**
|
|
3811
|
+
* Register or overwrite a inverse value.
|
|
3812
|
+
*
|
|
3813
|
+
* @param {string} name - The detector name.
|
|
3814
|
+
* @param {string} value - The inverse value.
|
|
3815
|
+
* @throws {TypeError} If fn is not a string.
|
|
3816
|
+
*/
|
|
3817
|
+
static setStyleEffectInverse(name, value) {
|
|
3818
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3819
|
+
if (typeof value !== 'string')
|
|
3820
|
+
throw new TypeError(`styleEffectInverse["${name}"] must be a string.`);
|
|
3821
|
+
TinyHtml.#styleEffectInverse[name] = value;
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
/**
|
|
3825
|
+
* Delete a inverse value by name.
|
|
3826
|
+
*
|
|
3827
|
+
* @param {string} name - The inverse value name to delete.
|
|
3828
|
+
* @returns {boolean} True if deleted, false otherwise.
|
|
3829
|
+
*/
|
|
3830
|
+
static deleteStyleEffectInverse(name) {
|
|
3831
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3832
|
+
return delete TinyHtml.#styleEffectInverse[name];
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
/**
|
|
3836
|
+
* Check if a inverse value is registered.
|
|
3837
|
+
*
|
|
3838
|
+
* @param {string} name - The inverse value name to check.
|
|
3839
|
+
* @returns {boolean} True if registered, false otherwise.
|
|
3840
|
+
*/
|
|
3841
|
+
static hasStyleEffectInverse(name) {
|
|
3842
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3843
|
+
return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffectInverse, name);
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
// TITLE: Animate DOM (styleEffectsRd)
|
|
3847
|
+
|
|
3848
|
+
/**
|
|
3849
|
+
* Style Effect Repeat Detector
|
|
3850
|
+
* @type {Record<string, StyleEffectsRdFn>}
|
|
3851
|
+
*/
|
|
3852
|
+
static #styleEffectsRd = {
|
|
3853
|
+
slideDown: (effects) =>
|
|
3854
|
+
effects.height[0] === effects.height[1] &&
|
|
3855
|
+
(!effects.width || effects.width[0] === effects.width[1]),
|
|
3856
|
+
slideUp: (effects) =>
|
|
3857
|
+
effects.height[0] === effects.height[1] &&
|
|
3858
|
+
(!effects.width || effects.width[0] === effects.width[1]),
|
|
3859
|
+
fadeIn: (effects) => Number(effects.opacity[0]) === Number(effects.opacity[1]),
|
|
3860
|
+
fadeOut: (effects) => Number(effects.opacity[0]) === Number(effects.opacity[1]),
|
|
3861
|
+
};
|
|
3862
|
+
|
|
3863
|
+
/**
|
|
3864
|
+
* Get a cloned copy of style effects repeat detectors.
|
|
3865
|
+
* @returns {Record<string, StyleEffectsRdFn>}
|
|
3866
|
+
*/
|
|
3867
|
+
static get styleEffectsRd() {
|
|
3868
|
+
return { ...TinyHtml.#styleEffectsRd };
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
/**
|
|
3872
|
+
* Replace the style effects repeat detectors.
|
|
3873
|
+
* @param {Record<string, StyleEffectsRdFn>} detectors
|
|
3874
|
+
* @throws {TypeError} If not a valid object with functions as values.
|
|
3875
|
+
*/
|
|
3876
|
+
static set styleEffectsRd(detectors) {
|
|
3877
|
+
if (typeof detectors !== 'object' || detectors === null)
|
|
3878
|
+
throw new TypeError('styleEffectsRd must be an object.');
|
|
3879
|
+
|
|
3880
|
+
for (const [k, v] of Object.entries(detectors)) {
|
|
3881
|
+
if (typeof v !== 'function')
|
|
3882
|
+
throw new TypeError(`styleEffectsRd["${k}"] must be a function.`);
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
TinyHtml.#styleEffectsRd = {};
|
|
3886
|
+
for (const [k, v] of Object.entries(detectors)) TinyHtml.setStyleEffectRd(k, v);
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
/**
|
|
3890
|
+
* Get a registered repeat detector function by name.
|
|
3891
|
+
*
|
|
3892
|
+
* @param {string} name - The detector name.
|
|
3893
|
+
* @returns {StyleEffectsRdFn | null} The function if found, otherwise undefined.
|
|
3894
|
+
*/
|
|
3895
|
+
static getStyleEffectRd(name) {
|
|
3896
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3897
|
+
return TinyHtml.#styleEffectsRd[name] || null;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
/**
|
|
3901
|
+
* Register or overwrite a repeat detector function.
|
|
3902
|
+
*
|
|
3903
|
+
* @param {string} name - The detector name.
|
|
3904
|
+
* @param {StyleEffectsRdFn} fn - The detector function.
|
|
3905
|
+
* @throws {TypeError} If fn is not a function.
|
|
3906
|
+
*/
|
|
3907
|
+
static setStyleEffectRd(name, fn) {
|
|
3908
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3909
|
+
if (typeof fn !== 'function')
|
|
3910
|
+
throw new TypeError(`styleEffectsRd["${name}"] must be a function.`);
|
|
3911
|
+
TinyHtml.#styleEffectsRd[name] = fn;
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
/**
|
|
3915
|
+
* Delete a repeat detector function by name.
|
|
3916
|
+
*
|
|
3917
|
+
* @param {string} name - The detector name to delete.
|
|
3918
|
+
* @returns {boolean} True if deleted, false otherwise.
|
|
3919
|
+
*/
|
|
3920
|
+
static deleteStyleEffectRd(name) {
|
|
3921
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3922
|
+
return delete TinyHtml.#styleEffectsRd[name];
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
/**
|
|
3926
|
+
* Check if a repeat detector function is registered.
|
|
3927
|
+
*
|
|
3928
|
+
* @param {string} name - The detector name to check.
|
|
3929
|
+
* @returns {boolean} True if registered, false otherwise.
|
|
3930
|
+
*/
|
|
3931
|
+
static hasStyleEffectRd(name) {
|
|
3932
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
3933
|
+
return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffectsRd, name);
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
// TITLE: Animate DOM (styleEffectsPromps)
|
|
3937
|
+
|
|
3938
|
+
/**
|
|
3939
|
+
* Effect property handlers for show, hide, and toggle.
|
|
3940
|
+
* Each function builds keyframes depending on the property being animated.
|
|
3941
|
+
*
|
|
3942
|
+
* @type {StyleEffectsProps}
|
|
3943
|
+
*/
|
|
3944
|
+
static #styleEffectsProps = {
|
|
3945
|
+
show: (el, keyframes, prop, style) => {
|
|
3946
|
+
if (prop === 'height' || prop === 'width') {
|
|
3947
|
+
const targetSize = TinyHtml.getAnimateData(el, `orig${prop}`) || el.scrollHeight + 'px';
|
|
3948
|
+
TinyHtml.setAnimateData(el, `orig${prop}`, targetSize);
|
|
3949
|
+
|
|
3950
|
+
const current = style[prop]; // pega valor atual
|
|
3951
|
+
keyframes[prop] = [current, targetSize];
|
|
3952
|
+
} else if (prop.startsWith('margin') || prop.startsWith('padding')) {
|
|
3953
|
+
// @ts-ignore
|
|
3954
|
+
const orig = TinyHtml.getAnimateData(el, prop) || style[prop];
|
|
3955
|
+
TinyHtml.setAnimateData(el, prop, orig);
|
|
3956
|
+
keyframes[prop] = ['0px', orig];
|
|
3957
|
+
} else if (prop === 'opacity') {
|
|
3958
|
+
const current = style.opacity;
|
|
3959
|
+
keyframes[prop] = [current, 1];
|
|
3960
|
+
} else {
|
|
3961
|
+
// @ts-ignore
|
|
3962
|
+
const current = TinyHtml.getAnimateData(el, prop) || style[prop];
|
|
3963
|
+
TinyHtml.setAnimateData(el, prop, current);
|
|
3964
|
+
// @ts-ignore
|
|
3965
|
+
keyframes[prop] = [current, style[prop] || 1];
|
|
3966
|
+
}
|
|
3967
|
+
},
|
|
3968
|
+
hide: (el, keyframes, prop, style) => {
|
|
3969
|
+
if (prop === 'height' || prop === 'width') {
|
|
3970
|
+
const targetSize = TinyHtml.getAnimateData(el, `orig${prop}`) || style[prop];
|
|
3971
|
+
TinyHtml.setAnimateData(el, `orig${prop}`, targetSize);
|
|
3972
|
+
|
|
3973
|
+
const current = style[prop]; // pega valor atual
|
|
3974
|
+
keyframes[prop] = [current, 0];
|
|
3975
|
+
} else if (prop.startsWith('margin') || prop.startsWith('padding')) {
|
|
3976
|
+
// @ts-ignore
|
|
3977
|
+
const orig = TinyHtml.getAnimateData(el, prop) || style[prop];
|
|
3978
|
+
TinyHtml.setAnimateData(el, prop, orig);
|
|
3979
|
+
|
|
3980
|
+
// @ts-ignore
|
|
3981
|
+
keyframes[prop] = [style[prop], '0px'];
|
|
3982
|
+
} else if (prop === 'opacity') {
|
|
3983
|
+
const current = style.opacity;
|
|
3984
|
+
keyframes[prop] = [current, 0];
|
|
3985
|
+
} else {
|
|
3986
|
+
// @ts-ignore
|
|
3987
|
+
const current = TinyHtml.getAnimateData(el, prop) || style[prop];
|
|
3988
|
+
TinyHtml.setAnimateData(el, prop, current);
|
|
3989
|
+
keyframes[prop] = [current, 0];
|
|
3990
|
+
}
|
|
3991
|
+
},
|
|
3992
|
+
};
|
|
3993
|
+
|
|
3994
|
+
/**
|
|
3995
|
+
* Returns a shallow-cloned copy of the property effect handlers.
|
|
3996
|
+
*
|
|
3997
|
+
* @returns {StyleEffectsProps}
|
|
3998
|
+
*/
|
|
3999
|
+
static get styleEffectsProps() {
|
|
4000
|
+
return { ...TinyHtml.#styleEffectsProps };
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
/**
|
|
4004
|
+
* Replace the entire styleEffectsProps map with a new one.
|
|
4005
|
+
*
|
|
4006
|
+
* @param {StyleEffectsProps} value
|
|
4007
|
+
*/
|
|
4008
|
+
static set styleEffectsProps(value) {
|
|
4009
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
4010
|
+
throw new TypeError('styleEffectsProps must be an object');
|
|
4011
|
+
|
|
4012
|
+
for (const [k, fn] of Object.entries(value)) {
|
|
4013
|
+
if (typeof fn !== 'function')
|
|
4014
|
+
throw new TypeError(`styleEffectsProps["${k}"] must be a function`);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
TinyHtml.#styleEffectsProps = {};
|
|
4018
|
+
for (const [k, fn] of Object.entries(value)) TinyHtml.setStyleEffectProp(k, fn);
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
/**
|
|
4022
|
+
* Get a style effect property handler by name.
|
|
4023
|
+
*
|
|
4024
|
+
* @param {string} name - The property handler name.
|
|
4025
|
+
* @returns {StyleEffectsFn | null} The handler function, or undefined if not found.
|
|
4026
|
+
*/
|
|
4027
|
+
static getStyleEffectProp(name) {
|
|
4028
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
4029
|
+
return TinyHtml.#styleEffectsProps[name] || null;
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
/**
|
|
4033
|
+
* Register or overwrite a style effect property handler.
|
|
4034
|
+
*
|
|
4035
|
+
* @param {string} name - The property handler name.
|
|
4036
|
+
* @param {StyleEffectsFn} fn - The handler function.
|
|
4037
|
+
* @throws {TypeError} If fn is not a function.
|
|
4038
|
+
*/
|
|
4039
|
+
static setStyleEffectProp(name, fn) {
|
|
4040
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
4041
|
+
if (typeof fn !== 'function')
|
|
4042
|
+
throw new TypeError(`styleEffectsProps["${name}"] must be a function`);
|
|
4043
|
+
TinyHtml.#styleEffectsProps[name] = fn;
|
|
4044
|
+
}
|
|
4045
|
+
|
|
4046
|
+
/**
|
|
4047
|
+
* Delete a style effect property handler by name.
|
|
4048
|
+
*
|
|
4049
|
+
* @param {string} name - The property handler name to delete.
|
|
4050
|
+
* @returns {boolean} True if deleted, false otherwise.
|
|
4051
|
+
*/
|
|
4052
|
+
static deleteStyleEffectProp(name) {
|
|
4053
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
4054
|
+
return delete TinyHtml.#styleEffectsProps[name];
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
/**
|
|
4058
|
+
* Check if a style effect property handler exists.
|
|
4059
|
+
*
|
|
4060
|
+
* @param {string} name - The property handler name to check.
|
|
4061
|
+
* @returns {boolean} True if the handler exists, false otherwise.
|
|
4062
|
+
*/
|
|
4063
|
+
static hasStyleEffectProp(name) {
|
|
4064
|
+
if (typeof name !== 'string') throw new TypeError('The "name" parameter must be a string.');
|
|
4065
|
+
return Object.prototype.hasOwnProperty.call(TinyHtml.#styleEffectsProps, name);
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
// TITLE: Gen Style FX Manager
|
|
4069
|
+
|
|
4070
|
+
/**
|
|
4071
|
+
* Generates effect parameters to create standard animations.
|
|
4072
|
+
*
|
|
4073
|
+
* @param {string} type - The effect type.
|
|
4074
|
+
* @param {boolean} [includeWidth=false] - Whether width (and opacity) should be included.
|
|
4075
|
+
* @returns {Record<string, string>} - A mapping of CSS properties to effect type.
|
|
4076
|
+
*/
|
|
4077
|
+
static genStyleFx(type, includeWidth = false) {
|
|
4078
|
+
if (typeof type !== 'string') throw new TypeError('genStyleFx: type must be a string.');
|
|
4079
|
+
if (typeof includeWidth !== 'boolean')
|
|
4080
|
+
throw new TypeError('genStyleFx: includeWidth must be a boolean.');
|
|
4081
|
+
|
|
4082
|
+
/** @type {string} */
|
|
4083
|
+
let which;
|
|
4084
|
+
/** @type {number} */
|
|
4085
|
+
let i = 0;
|
|
4086
|
+
/** @type {Record<string, string>} */
|
|
4087
|
+
const attrs = { height: type };
|
|
4088
|
+
|
|
4089
|
+
const includeWidthNb = includeWidth ? 1 : 0;
|
|
4090
|
+
for (; i < 4; i += 2 - includeWidthNb) {
|
|
4091
|
+
which = TinyHtml.#cssExpand[i];
|
|
4092
|
+
attrs['margin' + which] = type;
|
|
4093
|
+
attrs['padding' + which] = type;
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
if (includeWidth) {
|
|
4097
|
+
attrs.opacity = type;
|
|
4098
|
+
attrs.width = type;
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
return attrs;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
/**
|
|
4105
|
+
* Applies style-based effects (slide, fade) to one or more elements.
|
|
4106
|
+
* Converts abstract effect definitions (e.g., `{ height: "show" }`)
|
|
4107
|
+
* into concrete Web Animations API keyframes.
|
|
4108
|
+
*
|
|
4109
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el - A single element or an array of elements.
|
|
4110
|
+
* @param {string} id - The style effect id.
|
|
4111
|
+
* @param {StyleEffects} props - The style effect definition.
|
|
4112
|
+
* @param {number | KeyframeAnimationOptions | string} [ops] - Timing options.
|
|
4113
|
+
* @returns {StyleFxResult}
|
|
4114
|
+
*/
|
|
4115
|
+
static applyStyleFx(el, id, props, ops) {
|
|
4116
|
+
if (typeof id !== 'string') throw new TypeError('applyStyleFx: id must be a string.');
|
|
4117
|
+
if (typeof props !== 'object' || props === null)
|
|
4118
|
+
throw new TypeError('applyStyleFx: props must be a non-null object.');
|
|
4119
|
+
if (
|
|
4120
|
+
ops !== undefined &&
|
|
4121
|
+
!(
|
|
4122
|
+
typeof ops === 'number' ||
|
|
4123
|
+
typeof ops === 'string' ||
|
|
4124
|
+
(typeof ops === 'object' && ops !== null)
|
|
4125
|
+
)
|
|
4126
|
+
)
|
|
4127
|
+
throw new TypeError(
|
|
4128
|
+
'applyStyleFx: ops must be a number, string, KeyframeAnimationOptions, or undefined.',
|
|
4129
|
+
);
|
|
4130
|
+
|
|
4131
|
+
/** @type {StyleFxResult} */
|
|
4132
|
+
const results = new Map();
|
|
4133
|
+
TinyHtml._preHtmlElems(el, 'applyStyleFx').forEach((first) => {
|
|
4134
|
+
/**
|
|
4135
|
+
* Generate keyframes based on props
|
|
4136
|
+
* @type {AnimationSfxData}
|
|
4137
|
+
*/
|
|
4138
|
+
const keyframes = {};
|
|
4139
|
+
for (const [prop, action] of Object.entries(props)) {
|
|
4140
|
+
if (typeof action === 'string' && this.#styleEffectsProps[action]) {
|
|
4141
|
+
const style = getComputedStyle(first);
|
|
4142
|
+
this.#styleEffectsProps[action](first, keyframes, prop, style);
|
|
4143
|
+
} else if (typeof action === 'object') {
|
|
4144
|
+
keyframes[prop] = action;
|
|
4145
|
+
} else {
|
|
4146
|
+
throw new TypeError(
|
|
4147
|
+
`applyStyleFx: invalid action for prop "${prop}". Must be string or array.`,
|
|
4148
|
+
);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
const isRepeat =
|
|
4153
|
+
typeof TinyHtml.#styleEffectsRd[id] === 'function'
|
|
4154
|
+
? TinyHtml.#styleEffectsRd[id](keyframes)
|
|
4155
|
+
: false;
|
|
4156
|
+
if (isRepeat) {
|
|
4157
|
+
results.set(first, null);
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
/**
|
|
4162
|
+
* Build Web Animations keyframe objects
|
|
4163
|
+
* @type {Keyframe[]}
|
|
4164
|
+
*/
|
|
4165
|
+
const kf = [];
|
|
4166
|
+
const numFrames = keyframes[Object.keys(keyframes)[0]].length; // assume que todos têm o mesmo tamanho
|
|
4167
|
+
for (let i = 0; i < numFrames; i++) {
|
|
4168
|
+
/** @type {Keyframe} */
|
|
4169
|
+
const frame = {};
|
|
4170
|
+
for (const prop in keyframes) {
|
|
4171
|
+
frame[prop] = keyframes[prop][i];
|
|
4172
|
+
}
|
|
4173
|
+
kf.push(frame);
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
results.set(first, TinyHtml.animate(el, kf, ops, id)[0]);
|
|
4177
|
+
});
|
|
4178
|
+
return results;
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
/**
|
|
4182
|
+
* Applies style-based effects (slide, fade) to one or more elements.
|
|
4183
|
+
* Converts abstract effect definitions (e.g., `{ height: "show" }`)
|
|
4184
|
+
* into concrete Web Animations API keyframes.
|
|
4185
|
+
*
|
|
4186
|
+
* @param {string} id - The style effect id.
|
|
4187
|
+
* @param {StyleEffects} props - The style effect definition.
|
|
4188
|
+
* @param {number | KeyframeAnimationOptions} [ops] - Timing options.
|
|
4189
|
+
* @returns {StyleFxResult}
|
|
4190
|
+
*/
|
|
4191
|
+
applyStyleFx(id, props, ops) {
|
|
4192
|
+
return TinyHtml.applyStyleFx(this, id, props, ops);
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
// TITLE: Animate Stuff
|
|
4196
|
+
|
|
4197
|
+
/**
|
|
4198
|
+
* Get the current animation entry for a given element.
|
|
4199
|
+
* @param {HTMLElement} el - The target element.
|
|
4200
|
+
* @returns {string|null|undefined} Returns string or null to animation.
|
|
4201
|
+
*/
|
|
4202
|
+
static getCurrentAnimationId(el) {
|
|
4203
|
+
if (!(el instanceof HTMLElement)) throw new TypeError('Expected an HTMLElement.');
|
|
4204
|
+
return __elementCurrentAnimation.get(el)?.id ?? undefined;
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
/**
|
|
4208
|
+
* Applies an animation to one or multiple TinyElement instances.
|
|
4209
|
+
*
|
|
4210
|
+
* If `cancelOldAnim` is true, any currently running animation on the same element
|
|
4211
|
+
* will be cancelled before the new one starts.
|
|
4212
|
+
*
|
|
4213
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el - A single TinyElement or an array of TinyElements to animate.
|
|
4214
|
+
* @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - Keyframes that define the animation.
|
|
4215
|
+
* @param {number | KeyframeAnimationOptions | string} [ops] - Timing or configuration options for the animation.
|
|
4216
|
+
* @param {string|null} [id] - The style effect id.
|
|
4217
|
+
* @param {boolean} [cancelOldAnim=TinyHtml.cancelOldStyleFx] - Whether to cancel previous animations on the same element.
|
|
4218
|
+
* @returns {Animation[]}
|
|
4219
|
+
*/
|
|
4220
|
+
static animate(
|
|
4221
|
+
el,
|
|
4222
|
+
keyframes,
|
|
4223
|
+
ops = TinyHtml.#styleFxSpeeds._default,
|
|
4224
|
+
id = null,
|
|
4225
|
+
cancelOldAnim = TinyHtml.#cancelOldStyleFx,
|
|
4226
|
+
) {
|
|
4227
|
+
/** @type {number | KeyframeAnimationOptions} */
|
|
4228
|
+
const fxSpeed =
|
|
4229
|
+
typeof ops === 'string'
|
|
4230
|
+
? (TinyHtml.#styleFxSpeeds[ops] ?? undefined)
|
|
4231
|
+
: typeof ops !== 'number'
|
|
4232
|
+
? { ...ops }
|
|
4233
|
+
: ops;
|
|
4234
|
+
|
|
4235
|
+
if (typeof fxSpeed === 'object' && typeof fxSpeed.fill === 'undefined') {
|
|
4236
|
+
fxSpeed.fill = 'forwards';
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
/** @type {Animation[]} */
|
|
4240
|
+
const results = [];
|
|
4241
|
+
TinyHtml._preHtmlElems(el, 'animate').forEach((elem) => {
|
|
4242
|
+
if (cancelOldAnim) {
|
|
4243
|
+
const prevAnim = __elementCurrentAnimation.get(elem);
|
|
4244
|
+
if (prevAnim) prevAnim.animation.cancel();
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
const anim = elem.animate(keyframes, fxSpeed);
|
|
4248
|
+
results.push(anim);
|
|
4249
|
+
__elementCurrentAnimation.set(elem, { animation: anim, id: id ?? '' });
|
|
4250
|
+
anim.addEventListener('finish', () => {
|
|
4251
|
+
if (__elementCurrentAnimation.get(elem)?.animation === anim)
|
|
4252
|
+
__elementCurrentAnimation.delete(elem);
|
|
4253
|
+
});
|
|
4254
|
+
});
|
|
4255
|
+
|
|
4256
|
+
return results;
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
/**
|
|
4260
|
+
* Applies an animation to one or multiple TinyElement instances.
|
|
4261
|
+
*
|
|
4262
|
+
* If `cancelOldAnim` is true, any currently running animation on the same element
|
|
4263
|
+
* will be cancelled before the new one starts.
|
|
4264
|
+
*
|
|
4265
|
+
* @param {Keyframe[] | PropertyIndexedKeyframes | null} keyframes - Keyframes that define the animation.
|
|
4266
|
+
* @param {number | KeyframeAnimationOptions | string} [ops] - Timing or configuration options for the animation.
|
|
4267
|
+
* @param {string|null} [id] - The style effect id.
|
|
4268
|
+
* @param {boolean} [cancelOldAnim=TinyHtml.cancelOldStyleFx] - Whether to cancel previous animations on the same element.
|
|
4269
|
+
* @returns {Animation[]}
|
|
4270
|
+
*/
|
|
4271
|
+
animate(keyframes, ops, id, cancelOldAnim) {
|
|
4272
|
+
return TinyHtml.animate(this, keyframes, ops, id, cancelOldAnim);
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
/**
|
|
4276
|
+
* Stops the current animation(s) on one or multiple TinyElement instances.
|
|
4277
|
+
*
|
|
4278
|
+
* If an animation is running on the element(s), it will be cancelled immediately.
|
|
4279
|
+
*
|
|
4280
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el - A single TinyElement or an array of TinyElements.
|
|
4281
|
+
* @returns {boolean[]} The same element(s) provided as input, for chaining.
|
|
4282
|
+
*/
|
|
4283
|
+
static stop(el) {
|
|
4284
|
+
/** @type {boolean[]} */
|
|
4285
|
+
const results = [];
|
|
4286
|
+
TinyHtml._preHtmlElems(el, 'stop').forEach((elem) => {
|
|
4287
|
+
const anim = __elementCurrentAnimation.get(elem);
|
|
4288
|
+
if (anim) {
|
|
4289
|
+
anim.animation.cancel();
|
|
4290
|
+
__elementCurrentAnimation.delete(elem);
|
|
4291
|
+
results.push(true);
|
|
4292
|
+
} else results.push(false);
|
|
4293
|
+
});
|
|
4294
|
+
return results;
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
/**
|
|
4298
|
+
* Stops the current animation(s) on this TinyElement instance.
|
|
4299
|
+
*
|
|
4300
|
+
* @returns {boolean[]}
|
|
4301
|
+
*/
|
|
4302
|
+
stop() {
|
|
4303
|
+
return TinyHtml.stop(this);
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
// TITLE: Animate FXs
|
|
4307
|
+
|
|
4308
|
+
/**
|
|
4309
|
+
* Show animation (slideDown).
|
|
4310
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el
|
|
4311
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4312
|
+
* @returns {StyleFxResult}
|
|
4313
|
+
*/
|
|
4314
|
+
static slideDown(el, ops) {
|
|
4315
|
+
return TinyHtml.applyStyleFx(
|
|
4316
|
+
el,
|
|
4317
|
+
'slideDown',
|
|
4318
|
+
this.#styleEffects['slideDown'],
|
|
4319
|
+
ops ?? { duration: 500, easing: 'ease-out' },
|
|
4320
|
+
);
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4323
|
+
/**
|
|
4324
|
+
* Show animation (slideDown).
|
|
4325
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4326
|
+
* @returns {StyleFxResult}
|
|
4327
|
+
*/
|
|
4328
|
+
slideDown(ops) {
|
|
4329
|
+
return TinyHtml.slideDown(this, ops);
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
/**
|
|
4333
|
+
* Hide animation (slideUp).
|
|
4334
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el
|
|
4335
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4336
|
+
* @returns {StyleFxResult}
|
|
4337
|
+
*/
|
|
4338
|
+
static slideUp(el, ops) {
|
|
4339
|
+
return TinyHtml.applyStyleFx(
|
|
4340
|
+
el,
|
|
4341
|
+
'slideUp',
|
|
4342
|
+
this.#styleEffects['slideUp'],
|
|
4343
|
+
ops ?? { duration: 500, easing: 'ease-out' },
|
|
4344
|
+
);
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
/**
|
|
4348
|
+
* Hide animation (slideUp).
|
|
4349
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4350
|
+
* @returns {StyleFxResult}
|
|
4351
|
+
*/
|
|
4352
|
+
slideUp(ops) {
|
|
4353
|
+
return TinyHtml.slideUp(this, ops);
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
/**
|
|
4357
|
+
* Toggle slide animation.
|
|
4358
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el
|
|
4359
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4360
|
+
* @returns {StyleFxResult}
|
|
4361
|
+
*/
|
|
4362
|
+
static slideToggle(el, ops) {
|
|
4363
|
+
const first = TinyHtml._preHtmlElem(el, 'slideToggle');
|
|
4364
|
+
const style = getComputedStyle(first);
|
|
4365
|
+
const id = TinyHtml.getCurrentAnimationId(first);
|
|
4366
|
+
const isHidden = style.display === 'none' || first.offsetHeight === 0;
|
|
4367
|
+
return (typeof id === 'string' ? id === 'slideUp' : isHidden)
|
|
4368
|
+
? TinyHtml.slideDown(el, ops)
|
|
4369
|
+
: TinyHtml.slideUp(el, ops);
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
/**
|
|
4373
|
+
* Toggle slide animation.
|
|
4374
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4375
|
+
* @returns {StyleFxResult}
|
|
4376
|
+
*/
|
|
4377
|
+
slideToggle(ops) {
|
|
4378
|
+
return TinyHtml.slideToggle(this, ops);
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
/**
|
|
4382
|
+
* Fade in animation.
|
|
4383
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el
|
|
4384
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4385
|
+
* @returns {StyleFxResult}
|
|
4386
|
+
*/
|
|
4387
|
+
static fadeIn(el, ops) {
|
|
4388
|
+
return TinyHtml.applyStyleFx(el, 'fadeIn', this.#styleEffects['fadeIn'], ops);
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
/**
|
|
4392
|
+
* Fade in animation.
|
|
4393
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4394
|
+
* @returns {StyleFxResult}
|
|
4395
|
+
*/
|
|
4396
|
+
fadeIn(ops) {
|
|
4397
|
+
return TinyHtml.fadeIn(this, ops);
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
/**
|
|
4401
|
+
* Fade out animation.
|
|
4402
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el
|
|
4403
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4404
|
+
* @returns {StyleFxResult}
|
|
4405
|
+
*/
|
|
4406
|
+
static fadeOut(el, ops) {
|
|
4407
|
+
return TinyHtml.applyStyleFx(el, 'fadeOut', this.#styleEffects['fadeOut'], ops);
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
/**
|
|
4411
|
+
* Fade out animation.
|
|
4412
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4413
|
+
* @returns {StyleFxResult}
|
|
4414
|
+
*/
|
|
4415
|
+
fadeOut(ops) {
|
|
4416
|
+
return TinyHtml.fadeOut(this, ops);
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
/**
|
|
4420
|
+
* Fade toggle animation.
|
|
4421
|
+
* @param {TinyHtmlElement|TinyHtmlElement[]} el
|
|
4422
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4423
|
+
* @returns {StyleFxResult}
|
|
4424
|
+
*/
|
|
4425
|
+
static fadeToggle(el, ops) {
|
|
4426
|
+
const first = TinyHtml._preHtmlElem(el, 'fadeToggle');
|
|
4427
|
+
const style = getComputedStyle(first);
|
|
4428
|
+
const id = TinyHtml.getCurrentAnimationId(first);
|
|
4429
|
+
const isHidden = style.opacity === '0' || first.offsetParent === null;
|
|
4430
|
+
return (typeof id === 'string' ? id === 'fadeOut' : isHidden)
|
|
4431
|
+
? TinyHtml.fadeIn(el, ops)
|
|
4432
|
+
: TinyHtml.fadeOut(el, ops);
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
/**
|
|
4436
|
+
* Fade toggle animation.
|
|
4437
|
+
* @param {number | KeyframeAnimationOptions | string} [ops]
|
|
4438
|
+
* @returns {StyleFxResult}
|
|
4439
|
+
*/
|
|
4440
|
+
fadeToggle(ops) {
|
|
4441
|
+
return TinyHtml.fadeToggle(this, ops);
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
///////////////////////////////////////////////////////////////
|
|
4445
|
+
|
|
4446
|
+
// TITLE: DOM Positions
|
|
4447
|
+
|
|
4448
|
+
/**
|
|
4449
|
+
* Gets the offset of the element relative to the document.
|
|
4450
|
+
* @param {TinyElement} el - Target element.
|
|
4451
|
+
* @returns {{top: number, left: number}}
|
|
4452
|
+
*/
|
|
4453
|
+
static offset(el) {
|
|
4454
|
+
const elem = TinyHtml._preElem(el, 'offset');
|
|
4455
|
+
const rect = elem.getBoundingClientRect();
|
|
4456
|
+
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
4457
|
+
const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
|
|
4458
|
+
|
|
4459
|
+
return {
|
|
4460
|
+
top: rect.top + scrollTop,
|
|
4461
|
+
left: rect.left + scrollLeft,
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
/**
|
|
4466
|
+
* Gets the offset of the element relative to the document.
|
|
4467
|
+
* @returns {{top: number, left: number}}
|
|
4468
|
+
*/
|
|
4469
|
+
offset() {
|
|
4470
|
+
return TinyHtml.offset(this);
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
/**
|
|
4474
|
+
* Gets the position of the element relative to its offset parent.
|
|
4475
|
+
* @param {TinyHtmlElement} el - Target element.
|
|
4476
|
+
* @returns {{top: number, left: number}}
|
|
4477
|
+
*/
|
|
4478
|
+
static position(el) {
|
|
4479
|
+
const elem = TinyHtml._preHtmlElem(el, 'position');
|
|
4480
|
+
|
|
4481
|
+
let offsetParent;
|
|
4482
|
+
let offset;
|
|
4483
|
+
let parentOffset = { top: 0, left: 0 };
|
|
4484
|
+
|
|
4485
|
+
const computedStyle = window.getComputedStyle(elem);
|
|
4486
|
+
|
|
4487
|
+
if (computedStyle.position === 'fixed') {
|
|
4488
|
+
offset = elem.getBoundingClientRect();
|
|
4489
|
+
} else {
|
|
4490
|
+
offset = TinyHtml.offset(elem);
|
|
4491
|
+
|
|
4492
|
+
offsetParent = elem.offsetParent || document.documentElement;
|
|
4493
|
+
const { position } = window.getComputedStyle(offsetParent);
|
|
4494
|
+
|
|
4495
|
+
while (
|
|
4496
|
+
offsetParent instanceof HTMLElement &&
|
|
4497
|
+
(offsetParent === document.body || offsetParent === document.documentElement) &&
|
|
4498
|
+
position === 'static'
|
|
4499
|
+
) {
|
|
4500
|
+
offsetParent = offsetParent.parentNode;
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4503
|
+
if (
|
|
4504
|
+
offsetParent instanceof HTMLElement &&
|
|
4505
|
+
offsetParent !== elem &&
|
|
4506
|
+
offsetParent.nodeType === 1
|
|
4507
|
+
) {
|
|
4508
|
+
const { borderTopWidth, borderLeftWidth } = TinyHtml.cssFloats(offsetParent, [
|
|
4509
|
+
'borderTopWidth',
|
|
4510
|
+
'borderLeftWidth',
|
|
4511
|
+
]);
|
|
4512
|
+
parentOffset = TinyHtml.offset(offsetParent);
|
|
4513
|
+
parentOffset.top += borderTopWidth;
|
|
4514
|
+
parentOffset.left += borderLeftWidth;
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
return {
|
|
4519
|
+
top: offset.top - parentOffset.top - TinyHtml.cssFloat(elem, 'marginTop'),
|
|
4520
|
+
left: offset.left - parentOffset.left - TinyHtml.cssFloat(elem, 'marginLeft'),
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
/**
|
|
4525
|
+
* Gets the position of the element relative to its offset parent.
|
|
4526
|
+
* @returns {{top: number, left: number}}
|
|
4527
|
+
*/
|
|
4528
|
+
position() {
|
|
4529
|
+
return TinyHtml.position(this);
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
/**
|
|
4533
|
+
* Gets the closest positioned ancestor element.
|
|
4534
|
+
* @param {TinyHtmlElement} el - Target element.
|
|
4535
|
+
* @returns {HTMLElement} - Offset parent element.
|
|
4536
|
+
*/
|
|
4537
|
+
static offsetParent(el) {
|
|
4538
|
+
const elem = TinyHtml._preHtmlElem(el, 'offsetParent');
|
|
4539
|
+
let offsetParent = elem.offsetParent;
|
|
4540
|
+
|
|
4541
|
+
while (
|
|
4542
|
+
offsetParent instanceof HTMLElement &&
|
|
4543
|
+
window.getComputedStyle(offsetParent).position === 'static'
|
|
4544
|
+
) {
|
|
4545
|
+
offsetParent = offsetParent.offsetParent;
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
// Fallback to document.documentElement
|
|
4549
|
+
return offsetParent instanceof HTMLElement ? offsetParent : document.documentElement;
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4552
|
+
/**
|
|
4553
|
+
* Gets the closest positioned ancestor element.
|
|
4554
|
+
* @returns {HTMLElement} - Offset parent element.
|
|
4555
|
+
*/
|
|
4556
|
+
offsetParent() {
|
|
4557
|
+
return TinyHtml.offsetParent(this);
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4560
|
+
/////////////////////////////////////////////////
|
|
4561
|
+
|
|
4562
|
+
// TITLE: Scroll Stuff
|
|
4563
|
+
|
|
4564
|
+
/**
|
|
4565
|
+
* Gets the vertical scroll position.
|
|
4566
|
+
* @param {TinyElementAndWindow} el - Element or window.
|
|
4567
|
+
* @returns {number}
|
|
4568
|
+
*/
|
|
4569
|
+
static scrollTop(el) {
|
|
4570
|
+
const elem = TinyHtml._preElemAndWindow(el, 'scrollTop');
|
|
4571
|
+
if (TinyHtml.isWindow(elem)) return elem.pageYOffset;
|
|
4572
|
+
// @ts-ignore
|
|
4573
|
+
if (elem.nodeType === 9) return elem.defaultView.pageYOffset;
|
|
4574
|
+
return elem.scrollTop;
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
/**
|
|
4578
|
+
* Gets the vertical scroll position.
|
|
4579
|
+
* @returns {number}
|
|
4580
|
+
*/
|
|
4581
|
+
scrollTop() {
|
|
4582
|
+
return TinyHtml.scrollTop(this);
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
/**
|
|
4586
|
+
* Gets the horizontal scroll position.
|
|
4587
|
+
* @param {TinyElementAndWindow} el - Element or window.
|
|
4588
|
+
* @returns {number}
|
|
4589
|
+
*/
|
|
4590
|
+
static scrollLeft(el) {
|
|
4591
|
+
const elem = TinyHtml._preElemAndWindow(el, 'scrollLeft');
|
|
4592
|
+
if (TinyHtml.isWindow(elem)) return elem.pageXOffset;
|
|
4593
|
+
// @ts-ignore
|
|
4594
|
+
if (elem.nodeType === 9) return elem.defaultView.pageXOffset;
|
|
4595
|
+
return elem.scrollLeft;
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
/**
|
|
4599
|
+
* Gets the horizontal scroll position.
|
|
4600
|
+
* @returns {number}
|
|
4601
|
+
*/
|
|
4602
|
+
scrollLeft() {
|
|
4603
|
+
return TinyHtml.scrollLeft(this);
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
/**
|
|
4607
|
+
* Collection of easing functions used for scroll and animation calculations.
|
|
4608
|
+
* Each function receives a normalized time value (`t` from 0 to 1) and returns the eased progress.
|
|
4609
|
+
*
|
|
4610
|
+
* @type {Record<string, (t: number) => number>}
|
|
4611
|
+
*/
|
|
4612
|
+
static easings = {
|
|
4613
|
+
linear: (t) => t,
|
|
4614
|
+
easeInQuad: (t) => t * t,
|
|
3404
4615
|
easeOutQuad: (t) => t * (2 - t),
|
|
3405
4616
|
easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
|
|
3406
4617
|
easeInCubic: (t) => t * t * t,
|
|
@@ -3566,6 +4777,10 @@ class TinyHtml {
|
|
|
3566
4777
|
return TinyHtml.setScrollLeft(this, value);
|
|
3567
4778
|
}
|
|
3568
4779
|
|
|
4780
|
+
///////////////////////////////////////////
|
|
4781
|
+
|
|
4782
|
+
// TITLE: Border Stuff
|
|
4783
|
+
|
|
3569
4784
|
/**
|
|
3570
4785
|
* Returns the total border width and individual sides from `border{Side}Width` CSS properties.
|
|
3571
4786
|
*
|
|
@@ -3689,6 +4904,8 @@ class TinyHtml {
|
|
|
3689
4904
|
|
|
3690
4905
|
/////////////////////////////////////////////
|
|
3691
4906
|
|
|
4907
|
+
// TITLE: Class Stuff
|
|
4908
|
+
|
|
3692
4909
|
/**
|
|
3693
4910
|
* Adds one or more CSS class names to the element.
|
|
3694
4911
|
* @template {TinyElement|TinyElement[]} T
|
|
@@ -3877,6 +5094,8 @@ class TinyHtml {
|
|
|
3877
5094
|
|
|
3878
5095
|
/////////////////////////////////////////
|
|
3879
5096
|
|
|
5097
|
+
// TITLE: Tag Stuff
|
|
5098
|
+
|
|
3880
5099
|
/**
|
|
3881
5100
|
* Returns the tag name of the element.
|
|
3882
5101
|
* @param {TinyElement} el - Target element.
|
|
@@ -3913,6 +5132,10 @@ class TinyHtml {
|
|
|
3913
5132
|
return TinyHtml.id(this);
|
|
3914
5133
|
}
|
|
3915
5134
|
|
|
5135
|
+
//////////////////////////////////////////////////////////////
|
|
5136
|
+
|
|
5137
|
+
// TITLE: DOM Content Stuff
|
|
5138
|
+
|
|
3916
5139
|
/**
|
|
3917
5140
|
* Returns the BigInt content of the element.
|
|
3918
5141
|
* @param {TinyElement} el - Target element.
|
|
@@ -4220,6 +5443,10 @@ class TinyHtml {
|
|
|
4220
5443
|
return TinyHtml.setText(this, value);
|
|
4221
5444
|
}
|
|
4222
5445
|
|
|
5446
|
+
//////////////////////////////////////////////////////
|
|
5447
|
+
|
|
5448
|
+
// TITLE: DOM Content Stuff (Part 2)
|
|
5449
|
+
|
|
4223
5450
|
/**
|
|
4224
5451
|
* Remove all child nodes from each element.
|
|
4225
5452
|
* @template {TinyElement|TinyElement[]} T
|
|
@@ -4281,6 +5508,10 @@ class TinyHtml {
|
|
|
4281
5508
|
return TinyHtml.setHtml(this, value);
|
|
4282
5509
|
}
|
|
4283
5510
|
|
|
5511
|
+
///////////////////////////////////////////////
|
|
5512
|
+
|
|
5513
|
+
// TITLE: Value Stuff
|
|
5514
|
+
|
|
4284
5515
|
/** @readonly */
|
|
4285
5516
|
static _valHooks = {
|
|
4286
5517
|
option: {
|
|
@@ -4721,6 +5952,8 @@ class TinyHtml {
|
|
|
4721
5952
|
|
|
4722
5953
|
////////////////////////////////////////////
|
|
4723
5954
|
|
|
5955
|
+
// TITLE: Listen for Paste
|
|
5956
|
+
|
|
4724
5957
|
/**
|
|
4725
5958
|
* Registers a listener for the "paste" event to extract files and text from the clipboard (e.g., when the user presses Ctrl+V).
|
|
4726
5959
|
*
|
|
@@ -4777,6 +6010,10 @@ class TinyHtml {
|
|
|
4777
6010
|
return TinyHtml.listenForPaste(this, { onFilePaste, onTextPaste });
|
|
4778
6011
|
}
|
|
4779
6012
|
|
|
6013
|
+
/////////////////////////////////////////////////////
|
|
6014
|
+
|
|
6015
|
+
// TITLE: Events Stuff
|
|
6016
|
+
|
|
4780
6017
|
/**
|
|
4781
6018
|
* Checks if the element has a listener for a specific event.
|
|
4782
6019
|
*
|
|
@@ -4829,6 +6066,42 @@ class TinyHtml {
|
|
|
4829
6066
|
return TinyHtml.hasExactEventListener(this, event, handler);
|
|
4830
6067
|
}
|
|
4831
6068
|
|
|
6069
|
+
/**
|
|
6070
|
+
* Hover event shortcut.
|
|
6071
|
+
*
|
|
6072
|
+
* Adds `mouseenter` and `mouseleave` event listeners to the target.
|
|
6073
|
+
*
|
|
6074
|
+
* @template {TinyEventTarget|TinyEventTarget[]} T
|
|
6075
|
+
* @param {T} el - The target element(s) to attach the hover handlers.
|
|
6076
|
+
* @param {HoverEventCallback} fnOver - Callback executed when the mouse enters the target.
|
|
6077
|
+
* @param {HoverEventCallback|null} [fnOut] - Optional callback executed when the mouse leaves
|
|
6078
|
+
* the target. If not provided, `fnOver` will be used for both events.
|
|
6079
|
+
* @param {EventRegistryOptions} [options] - Optional event listener options.
|
|
6080
|
+
* @returns {T}
|
|
6081
|
+
*/
|
|
6082
|
+
static hover(el, fnOver, fnOut, options) {
|
|
6083
|
+
// @ts-ignore
|
|
6084
|
+
TinyHtml.on(el, 'mouseenter', fnOver, options);
|
|
6085
|
+
// @ts-ignore
|
|
6086
|
+
TinyHtml.on(el, 'mouseleave', fnOut || fnOver, options);
|
|
6087
|
+
return el;
|
|
6088
|
+
}
|
|
6089
|
+
|
|
6090
|
+
/**
|
|
6091
|
+
* Hover event shortcut.
|
|
6092
|
+
*
|
|
6093
|
+
* Adds `mouseenter` and `mouseleave` event listeners to the target.
|
|
6094
|
+
*
|
|
6095
|
+
* @param {HoverEventCallback} fnOver - Callback executed when the mouse enters the target.
|
|
6096
|
+
* @param {HoverEventCallback|null} [fnOut] - Optional callback executed when the mouse leaves
|
|
6097
|
+
* the target. If not provided, `fnOver` will be used for both events.
|
|
6098
|
+
* @param {EventRegistryOptions} [options] - Optional event listener options.
|
|
6099
|
+
* @returns {this}
|
|
6100
|
+
*/
|
|
6101
|
+
hover(fnOver, fnOut, options) {
|
|
6102
|
+
return TinyHtml.hover(this, fnOver, fnOut, options);
|
|
6103
|
+
}
|
|
6104
|
+
|
|
4832
6105
|
/**
|
|
4833
6106
|
* Registers an event listener on the specified element.
|
|
4834
6107
|
*
|
|
@@ -5088,6 +6361,8 @@ class TinyHtml {
|
|
|
5088
6361
|
|
|
5089
6362
|
///////////////////////////////////////////////////////////////
|
|
5090
6363
|
|
|
6364
|
+
// TITLE: ProFix Manager
|
|
6365
|
+
|
|
5091
6366
|
/**
|
|
5092
6367
|
* Internal property name normalization map (similar to jQuery's `propFix`).
|
|
5093
6368
|
* Maps attribute-like names to their JavaScript DOM property equivalents.
|
|
@@ -5182,28 +6457,46 @@ class TinyHtml {
|
|
|
5182
6457
|
}
|
|
5183
6458
|
|
|
5184
6459
|
/**
|
|
5185
|
-
* Set
|
|
6460
|
+
* Set one or multiple attributes on an element.
|
|
5186
6461
|
* @template {TinyElement|TinyElement[]} T
|
|
5187
|
-
* @param {T} el
|
|
5188
|
-
* @param {string} name
|
|
5189
|
-
* @param {string|null} [value=null]
|
|
6462
|
+
* @param {T} el - Target element(s).
|
|
6463
|
+
* @param {string|Record<string, string|null>} name - Attribute name or an object of attributes.
|
|
6464
|
+
* @param {string|null} [value=null] - Attribute value (ignored if "name" is an object).
|
|
5190
6465
|
* @returns {T}
|
|
5191
6466
|
*/
|
|
5192
6467
|
static setAttr(el, name, value = null) {
|
|
6468
|
+
const elems = TinyHtml._preElems(el, 'setAttr');
|
|
6469
|
+
|
|
6470
|
+
// Multiple attributes at once
|
|
6471
|
+
if (typeof name === 'object' && name !== null) {
|
|
6472
|
+
Object.entries(name).forEach(([attr, val]) => {
|
|
6473
|
+
if (val !== null && typeof val !== 'string')
|
|
6474
|
+
throw new TypeError(`The value for "${attr}" must be a string or null.`);
|
|
6475
|
+
elems.forEach((elem) => {
|
|
6476
|
+
if (val === null) elem.removeAttribute(TinyHtml.getAttrName(attr));
|
|
6477
|
+
else elem.setAttribute(TinyHtml.getAttrName(attr), val);
|
|
6478
|
+
});
|
|
6479
|
+
});
|
|
6480
|
+
return el;
|
|
6481
|
+
}
|
|
6482
|
+
|
|
6483
|
+
// Single attribute
|
|
5193
6484
|
if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
|
|
5194
6485
|
if (value !== null && typeof value !== 'string')
|
|
5195
6486
|
throw new TypeError('The "value" must be a string.');
|
|
5196
|
-
|
|
6487
|
+
|
|
6488
|
+
elems.forEach((elem) => {
|
|
5197
6489
|
if (value === null) elem.removeAttribute(TinyHtml.getAttrName(name));
|
|
5198
6490
|
else elem.setAttribute(TinyHtml.getAttrName(name), value);
|
|
5199
6491
|
});
|
|
6492
|
+
|
|
5200
6493
|
return el;
|
|
5201
6494
|
}
|
|
5202
6495
|
|
|
5203
6496
|
/**
|
|
5204
|
-
* Set
|
|
5205
|
-
* @param {string} name
|
|
5206
|
-
* @param {string|null} [value=null]
|
|
6497
|
+
* Set one or multiple attributes on an element.
|
|
6498
|
+
* @param {string|Record<string, string|null>} name - Attribute name or an object of attributes.
|
|
6499
|
+
* @param {string|null} [value=null] - Attribute value (ignored if "name" is an object).
|
|
5207
6500
|
* @returns {this}
|
|
5208
6501
|
*/
|
|
5209
6502
|
setAttr(name, value) {
|
|
@@ -5278,81 +6571,92 @@ class TinyHtml {
|
|
|
5278
6571
|
}
|
|
5279
6572
|
|
|
5280
6573
|
/**
|
|
5281
|
-
* Set
|
|
6574
|
+
* Set one or multiple properties on an element.
|
|
5282
6575
|
* @template {TinyElement|TinyElement[]} T
|
|
5283
|
-
* @param {T} el
|
|
5284
|
-
* @param {string} name
|
|
6576
|
+
* @param {T} el - Target element(s).
|
|
6577
|
+
* @param {...string} names - Property name(s).
|
|
5285
6578
|
* @returns {T}
|
|
5286
6579
|
*/
|
|
5287
|
-
static addProp(el,
|
|
5288
|
-
if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
|
|
6580
|
+
static addProp(el, ...names) {
|
|
5289
6581
|
TinyHtml._preElems(el, 'addProp').forEach((elem) => {
|
|
5290
|
-
|
|
5291
|
-
|
|
6582
|
+
names.forEach((name) => {
|
|
6583
|
+
if (typeof name !== 'string') throw new TypeError('Each "name" must be a string.');
|
|
6584
|
+
// @ts-ignore
|
|
6585
|
+
elem[TinyHtml.getPropName(name)] = true;
|
|
6586
|
+
});
|
|
5292
6587
|
});
|
|
5293
6588
|
return el;
|
|
5294
6589
|
}
|
|
5295
6590
|
|
|
5296
6591
|
/**
|
|
5297
6592
|
* Set a property on an element.
|
|
5298
|
-
* @param {string} name
|
|
6593
|
+
* @param {...string} names - Property name(s).
|
|
5299
6594
|
* @returns {this}
|
|
5300
6595
|
*/
|
|
5301
|
-
addProp(
|
|
5302
|
-
return TinyHtml.addProp(this,
|
|
6596
|
+
addProp(...names) {
|
|
6597
|
+
return TinyHtml.addProp(this, ...names);
|
|
5303
6598
|
}
|
|
5304
6599
|
|
|
5305
6600
|
/**
|
|
5306
|
-
* Remove
|
|
6601
|
+
* Remove one or multiple properties from an element.
|
|
5307
6602
|
* @template {TinyElement|TinyElement[]} T
|
|
5308
|
-
* @param {T} el
|
|
5309
|
-
* @param {string} name
|
|
6603
|
+
* @param {T} el - Target element(s).
|
|
6604
|
+
* @param {...string} names - Property name(s).
|
|
5310
6605
|
* @returns {T}
|
|
5311
6606
|
*/
|
|
5312
|
-
static removeProp(el,
|
|
5313
|
-
if (typeof name !== 'string') throw new TypeError('The "name" must be a string.');
|
|
6607
|
+
static removeProp(el, ...names) {
|
|
5314
6608
|
TinyHtml._preElems(el, 'removeProp').forEach((elem) => {
|
|
5315
|
-
|
|
5316
|
-
|
|
6609
|
+
names.forEach((name) => {
|
|
6610
|
+
if (typeof name !== 'string') throw new TypeError('Each "name" must be a string.');
|
|
6611
|
+
// @ts-ignore
|
|
6612
|
+
elem[TinyHtml.getPropName(name)] = false;
|
|
6613
|
+
});
|
|
5317
6614
|
});
|
|
5318
6615
|
return el;
|
|
5319
6616
|
}
|
|
5320
6617
|
|
|
5321
6618
|
/**
|
|
5322
6619
|
* Remove a property from an element.
|
|
5323
|
-
* @param {string} name
|
|
6620
|
+
* @param {...string} names - Property name(s).
|
|
5324
6621
|
* @returns {this}
|
|
5325
6622
|
*/
|
|
5326
|
-
removeProp(
|
|
5327
|
-
return TinyHtml.removeProp(this,
|
|
6623
|
+
removeProp(...names) {
|
|
6624
|
+
return TinyHtml.removeProp(this, ...names);
|
|
5328
6625
|
}
|
|
5329
6626
|
|
|
5330
6627
|
/**
|
|
5331
|
-
* Toggle
|
|
6628
|
+
* Toggle one or multiple boolean properties.
|
|
5332
6629
|
* @template {TinyElement|TinyElement[]} T
|
|
5333
|
-
* @param {T} el
|
|
5334
|
-
* @param {string} name
|
|
5335
|
-
* @param {boolean} [force]
|
|
6630
|
+
* @param {T} el - Target element(s).
|
|
6631
|
+
* @param {string|string[]} name - Property name or a list of property names.
|
|
6632
|
+
* @param {boolean} [force] - Force true/false instead of toggling.
|
|
5336
6633
|
* @returns {T}
|
|
5337
6634
|
*/
|
|
5338
6635
|
static toggleProp(el, name, force) {
|
|
5339
|
-
|
|
6636
|
+
const elems = TinyHtml._preElems(el, 'toggleProp');
|
|
5340
6637
|
if (typeof force !== 'undefined' && typeof force !== 'boolean')
|
|
5341
6638
|
throw new TypeError('The "force" must be a boolean.');
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
6639
|
+
|
|
6640
|
+
// Normalize list of properties
|
|
6641
|
+
const props = Array.isArray(name) ? name : [name];
|
|
6642
|
+
|
|
6643
|
+
props.forEach((prop) => {
|
|
6644
|
+
if (typeof prop !== 'string') throw new TypeError('Each property name must be a string.');
|
|
6645
|
+
elems.forEach((elem) => {
|
|
6646
|
+
// @ts-ignore
|
|
6647
|
+
const shouldEnable = force === undefined ? !elem[TinyHtml.getPropName(prop)] : force;
|
|
6648
|
+
if (shouldEnable) TinyHtml.addProp(elem, prop);
|
|
6649
|
+
else TinyHtml.removeProp(elem, prop);
|
|
6650
|
+
});
|
|
5348
6651
|
});
|
|
6652
|
+
|
|
5349
6653
|
return el;
|
|
5350
6654
|
}
|
|
5351
6655
|
|
|
5352
6656
|
/**
|
|
5353
|
-
* Toggle
|
|
5354
|
-
* @param {string} name
|
|
5355
|
-
* @param {boolean} [force]
|
|
6657
|
+
* Toggle one or multiple boolean properties.
|
|
6658
|
+
* @param {string|string[]} name - Property name or a list of property names.
|
|
6659
|
+
* @param {boolean} [force] - Force true/false instead of toggling.
|
|
5356
6660
|
* @returns {this}
|
|
5357
6661
|
*/
|
|
5358
6662
|
toggleProp(name, force) {
|
|
@@ -5361,6 +6665,8 @@ class TinyHtml {
|
|
|
5361
6665
|
|
|
5362
6666
|
/////////////////////////////////////////////////////
|
|
5363
6667
|
|
|
6668
|
+
// TITLE: Remove Element
|
|
6669
|
+
|
|
5364
6670
|
/**
|
|
5365
6671
|
* Removes an element from the DOM.
|
|
5366
6672
|
* @template {TinyElement|TinyElement[]} T
|
|
@@ -5380,6 +6686,10 @@ class TinyHtml {
|
|
|
5380
6686
|
return TinyHtml.remove(this);
|
|
5381
6687
|
}
|
|
5382
6688
|
|
|
6689
|
+
///////////////////////////////////////////////////
|
|
6690
|
+
|
|
6691
|
+
// TITLE: Index Manager
|
|
6692
|
+
|
|
5383
6693
|
/**
|
|
5384
6694
|
* Returns the index of the first element within its parent or relative to a selector/element.
|
|
5385
6695
|
*
|
|
@@ -5416,6 +6726,8 @@ class TinyHtml {
|
|
|
5416
6726
|
|
|
5417
6727
|
////////////////////////////////////////////////////////////////////
|
|
5418
6728
|
|
|
6729
|
+
// TITLE: Collision Stuff
|
|
6730
|
+
|
|
5419
6731
|
/**
|
|
5420
6732
|
* Creates a new DOMRect object by copying the base rect and applying optional additional dimensions.
|
|
5421
6733
|
*
|
|
@@ -5710,6 +7022,8 @@ class TinyHtml {
|
|
|
5710
7022
|
|
|
5711
7023
|
//////////////////////////////////////////////////////////////////////////////
|
|
5712
7024
|
|
|
7025
|
+
// TITLE: Viewport Stuff
|
|
7026
|
+
|
|
5713
7027
|
/**
|
|
5714
7028
|
* Checks if the given element is at least partially visible in the viewport.
|
|
5715
7029
|
*
|
|
@@ -5860,6 +7174,8 @@ class TinyHtml {
|
|
|
5860
7174
|
return TinyHtml.isFullyInContainer(this, cont);
|
|
5861
7175
|
}
|
|
5862
7176
|
|
|
7177
|
+
// TITLE: Has Scroll
|
|
7178
|
+
|
|
5863
7179
|
/**
|
|
5864
7180
|
* Checks if an element has scrollable content.
|
|
5865
7181
|
*
|