@oscarpalmer/toretto 0.27.0 → 0.29.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/dist/event/delegation.js +18 -19
- package/dist/html.js +27 -19
- package/dist/internal/sanitize.js +6 -12
- package/dist/toretto.full.js +80 -126
- package/package.json +5 -4
- package/src/attribute/set.ts +10 -44
- package/src/data.ts +3 -15
- package/src/event/delegation.ts +29 -35
- package/src/event/index.ts +5 -23
- package/src/find/index.ts +10 -26
- package/src/find/relative.ts +4 -16
- package/src/focusable.ts +15 -51
- package/src/html.ts +48 -60
- package/src/internal/attribute.ts +11 -45
- package/src/internal/element-value.ts +0 -1
- package/src/internal/get-value.ts +2 -6
- package/src/internal/sanitize.ts +6 -44
- package/src/is.ts +1 -1
- package/src/style.ts +4 -18
- package/src/touch.ts +2 -6
- package/types/html.d.ts +2 -3
- package/types/internal/sanitize.d.ts +2 -13
package/dist/event/delegation.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isEventTarget } from "../internal/is.js";
|
|
2
2
|
function addDelegatedHandler(document$1, type, name, passive) {
|
|
3
|
-
const
|
|
4
|
-
if (document$1[
|
|
5
|
-
document$1[
|
|
3
|
+
const count = `${name}${COUNT_SUFFIX}`;
|
|
4
|
+
if (document$1[count] != null) {
|
|
5
|
+
document$1[count] += 1;
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
|
-
document$1[
|
|
8
|
+
document$1[count] = 1;
|
|
9
9
|
document$1.addEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE, { passive });
|
|
10
10
|
}
|
|
11
11
|
function addDelegatedListener(target, type, name, listener, passive) {
|
|
@@ -27,15 +27,14 @@ function delegatedEventHandler(event) {
|
|
|
27
27
|
for (let index = 0; index < length; index += 1) {
|
|
28
28
|
const item = items[index];
|
|
29
29
|
const listeners = item[key];
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
30
|
+
if (item.disabled || listeners == null) continue;
|
|
31
|
+
Object.defineProperty(event, "currentTarget", {
|
|
32
|
+
configurable: true,
|
|
33
|
+
value: item
|
|
34
|
+
});
|
|
35
|
+
for (const listener of listeners) {
|
|
36
|
+
listener.call(item, event);
|
|
37
|
+
if (event.cancelBubble) return;
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -43,10 +42,10 @@ function getDelegatedName(target, type, options) {
|
|
|
43
42
|
if (isEventTarget(target) && EVENT_TYPES.has(type) && !options.capture && !options.once && options.signal == null) return `${EVENT_PREFIX}${type}${options.passive ? EVENT_SUFFIX_PASSIVE : EVENT_SUFFIX_ACTIVE}`;
|
|
44
43
|
}
|
|
45
44
|
function removeDelegatedHandler(document$1, type, name, passive) {
|
|
46
|
-
const
|
|
47
|
-
document$1[
|
|
48
|
-
if (document$1[
|
|
49
|
-
document$1[
|
|
45
|
+
const count = `${name}${COUNT_SUFFIX}`;
|
|
46
|
+
document$1[count] -= 1;
|
|
47
|
+
if (document$1[count] < 1) {
|
|
48
|
+
document$1[count] = void 0;
|
|
50
49
|
document$1.removeEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE);
|
|
51
50
|
}
|
|
52
51
|
}
|
|
@@ -54,10 +53,11 @@ function removeDelegatedListener(target, type, name, listener, passive) {
|
|
|
54
53
|
const handlers = target[name];
|
|
55
54
|
if (handlers == null || !handlers.has(listener)) return false;
|
|
56
55
|
handlers.delete(listener);
|
|
57
|
-
if (handlers
|
|
56
|
+
if (handlers.size === 0) target[name] = void 0;
|
|
58
57
|
removeDelegatedHandler(document, type, name, passive);
|
|
59
58
|
return true;
|
|
60
59
|
}
|
|
60
|
+
var COUNT_SUFFIX = ".count";
|
|
61
61
|
var EVENT_PREFIX = "@";
|
|
62
62
|
var EVENT_SUFFIX_ACTIVE = ":active";
|
|
63
63
|
var EVENT_SUFFIX_PASSIVE = ":passive";
|
|
@@ -87,5 +87,4 @@ var EVENT_TYPES = new Set([
|
|
|
87
87
|
]);
|
|
88
88
|
var HANDLER_ACTIVE = delegatedEventHandler.bind(false);
|
|
89
89
|
var HANDLER_PASSIVE = delegatedEventHandler.bind(true);
|
|
90
|
-
var LISTENERS_SUFFIX = ":listeners";
|
|
91
90
|
export { addDelegatedListener, getDelegatedName, removeDelegatedListener };
|
package/dist/html.js
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sanitizeNodes } from "./internal/sanitize.js";
|
|
2
2
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
3
|
-
function
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
function createHtml(value) {
|
|
4
|
+
const html$1 = getParser().parseFromString(typeof value === "string" ? value : value.innerHTML, HTML_PARSE_TYPE);
|
|
5
|
+
html$1.body.normalize();
|
|
6
|
+
sanitizeNodes([html$1.body]);
|
|
7
|
+
return html$1.body.innerHTML;
|
|
8
|
+
}
|
|
9
|
+
function createTemplate(value, options) {
|
|
10
|
+
const template = document.createElement(TEMPLATE_TAG);
|
|
11
|
+
template.innerHTML = createHtml(value);
|
|
12
|
+
if (typeof value === "string" && !options.ignoreCache) templates[value] = template;
|
|
7
13
|
return template;
|
|
8
14
|
}
|
|
9
|
-
function
|
|
15
|
+
function getNodes(value, options) {
|
|
10
16
|
if (typeof value !== "string" && !(value instanceof HTMLTemplateElement)) return [];
|
|
11
|
-
const template =
|
|
12
|
-
|
|
13
|
-
const cloned = template.content.cloneNode(true);
|
|
14
|
-
const scripts = cloned.querySelectorAll("script");
|
|
15
|
-
for (const script of scripts) script.remove();
|
|
16
|
-
cloned.normalize();
|
|
17
|
-
return sanitizeNodes([...cloned.childNodes], options);
|
|
17
|
+
const template = getTemplate(value, options);
|
|
18
|
+
return template == null ? [] : [...template.content.cloneNode(true).childNodes];
|
|
18
19
|
}
|
|
19
20
|
function getOptions(input) {
|
|
20
21
|
const options = isPlainObject(input) ? input : {};
|
|
21
22
|
options.ignoreCache = typeof options.ignoreCache === "boolean" ? options.ignoreCache : false;
|
|
22
|
-
options.sanitizeBooleanAttributes = typeof options.sanitizeBooleanAttributes === "boolean" ? options.sanitizeBooleanAttributes : true;
|
|
23
23
|
return options;
|
|
24
24
|
}
|
|
25
|
-
function
|
|
25
|
+
function getParser() {
|
|
26
|
+
parser ??= new DOMParser();
|
|
27
|
+
return parser;
|
|
28
|
+
}
|
|
29
|
+
function getTemplate(value, options) {
|
|
30
|
+
if (value instanceof HTMLTemplateElement) return createTemplate(value, options);
|
|
26
31
|
if (typeof value !== "string" || value.trim().length === 0) return;
|
|
27
32
|
let template = templates[value];
|
|
28
33
|
if (template != null) return template;
|
|
29
34
|
const element = EXPRESSION_ID.test(value) ? document.querySelector(`#${value}`) : null;
|
|
30
|
-
template = element instanceof HTMLTemplateElement ? element : createTemplate(value,
|
|
35
|
+
template = element instanceof HTMLTemplateElement ? element : createTemplate(value, options);
|
|
31
36
|
return template;
|
|
32
37
|
}
|
|
33
38
|
var html = ((value, options) => {
|
|
34
|
-
return
|
|
39
|
+
return getNodes(value, getOptions(options));
|
|
35
40
|
});
|
|
36
41
|
html.clear = () => {
|
|
37
42
|
templates = {};
|
|
@@ -47,9 +52,12 @@ html.remove = (template) => {
|
|
|
47
52
|
}
|
|
48
53
|
templates = updated;
|
|
49
54
|
};
|
|
50
|
-
function sanitize(value
|
|
51
|
-
return sanitizeNodes(Array.isArray(value) ? value : [value]
|
|
55
|
+
function sanitize(value) {
|
|
56
|
+
return sanitizeNodes(Array.isArray(value) ? value : [value]);
|
|
52
57
|
}
|
|
53
58
|
var EXPRESSION_ID = /^[a-z][\w-]*$/i;
|
|
59
|
+
var HTML_PARSE_TYPE = "text/html";
|
|
60
|
+
var TEMPLATE_TAG = "template";
|
|
61
|
+
var parser;
|
|
54
62
|
var templates = {};
|
|
55
63
|
export { html, sanitize };
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { isBadAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute } from "./attribute.js";
|
|
2
|
-
|
|
3
|
-
function getSanitizeOptions(input) {
|
|
4
|
-
const options = isPlainObject(input) ? input : {};
|
|
5
|
-
options.sanitizeBooleanAttributes = typeof options.sanitizeBooleanAttributes === "boolean" ? options.sanitizeBooleanAttributes : true;
|
|
6
|
-
return options;
|
|
7
|
-
}
|
|
8
|
-
function sanitizeAttributes(element, attributes, options) {
|
|
2
|
+
function sanitizeAttributes(element, attributes) {
|
|
9
3
|
const { length } = attributes;
|
|
10
4
|
for (let index = 0; index < length; index += 1) {
|
|
11
5
|
const attribute = attributes[index];
|
|
12
6
|
if (isBadAttribute(attribute) || isEmptyNonBooleanAttribute(attribute)) element.removeAttribute(attribute.name);
|
|
13
|
-
else if (
|
|
7
|
+
else if (isInvalidBooleanAttribute(attribute)) element.setAttribute(attribute.name, "");
|
|
14
8
|
}
|
|
15
9
|
}
|
|
16
|
-
function sanitizeNodes(nodes
|
|
10
|
+
function sanitizeNodes(nodes) {
|
|
17
11
|
const actual = nodes.filter((node) => node instanceof Node);
|
|
18
12
|
const { length } = nodes;
|
|
19
13
|
for (let index = 0; index < length; index += 1) {
|
|
@@ -21,10 +15,10 @@ function sanitizeNodes(nodes, options) {
|
|
|
21
15
|
if (node instanceof Element) {
|
|
22
16
|
const scripts = node.querySelectorAll("script");
|
|
23
17
|
for (const script of scripts) script.remove();
|
|
24
|
-
sanitizeAttributes(node, [...node.attributes]
|
|
18
|
+
sanitizeAttributes(node, [...node.attributes]);
|
|
25
19
|
}
|
|
26
|
-
if (node.hasChildNodes()) sanitizeNodes([...node.childNodes]
|
|
20
|
+
if (node.hasChildNodes()) sanitizeNodes([...node.childNodes]);
|
|
27
21
|
}
|
|
28
22
|
return nodes;
|
|
29
23
|
}
|
|
30
|
-
export {
|
|
24
|
+
export { sanitizeAttributes, sanitizeNodes };
|
package/dist/toretto.full.js
CHANGED
|
@@ -12,12 +12,10 @@ function getSupport() {
|
|
|
12
12
|
if ('ontouchstart' in window) {
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
|
-
if (typeof navigator.maxTouchPoints === 'number' &&
|
|
16
|
-
navigator.maxTouchPoints > 0) {
|
|
15
|
+
if (typeof navigator.maxTouchPoints === 'number' && navigator.maxTouchPoints > 0) {
|
|
17
16
|
return true;
|
|
18
17
|
}
|
|
19
|
-
if (typeof navigator.msMaxTouchPoints ===
|
|
20
|
-
'number' &&
|
|
18
|
+
if (typeof navigator.msMaxTouchPoints === 'number' &&
|
|
21
19
|
navigator.msMaxTouchPoints > 0) {
|
|
22
20
|
return true;
|
|
23
21
|
}
|
|
@@ -67,6 +65,7 @@ function compact(array, strict) {
|
|
|
67
65
|
function getString(value) {
|
|
68
66
|
if (typeof value === "string") return value;
|
|
69
67
|
if (value == null) return "";
|
|
68
|
+
if (typeof value === "function") return getString(value());
|
|
70
69
|
if (typeof value !== "object") return String(value);
|
|
71
70
|
const asString = String(value.valueOf?.() ?? value);
|
|
72
71
|
return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
|
|
@@ -161,8 +160,7 @@ function isBadAttribute(first, second) {
|
|
|
161
160
|
EXPRESSION_VALUE_PREFIX.test(String(attribute.value))), first, second);
|
|
162
161
|
}
|
|
163
162
|
function isBooleanAttribute(value) {
|
|
164
|
-
return isValidAttribute(attribute => attribute != null &&
|
|
165
|
-
booleanAttributes.includes(attribute.name.toLowerCase()), value, '');
|
|
163
|
+
return isValidAttribute(attribute => attribute != null && booleanAttributes.includes(attribute.name.toLowerCase()), value, '');
|
|
166
164
|
}
|
|
167
165
|
function isEmptyNonBooleanAttribute(first, second) {
|
|
168
166
|
return isValidAttribute(attribute => attribute != null &&
|
|
@@ -182,7 +180,7 @@ function isInvalidBooleanAttribute(first, second) {
|
|
|
182
180
|
}, first, second);
|
|
183
181
|
}
|
|
184
182
|
function isProperty(value) {
|
|
185
|
-
return
|
|
183
|
+
return isPlainObject(value) && typeof value.name === 'string';
|
|
186
184
|
}
|
|
187
185
|
function isValidAttribute(callback, first, second) {
|
|
188
186
|
let attribute;
|
|
@@ -209,9 +207,7 @@ function updateAttribute(element, name, value) {
|
|
|
209
207
|
function updateProperty(element, name, value) {
|
|
210
208
|
const actual = name.toLowerCase();
|
|
211
209
|
element[actual] =
|
|
212
|
-
value === '' ||
|
|
213
|
-
(typeof value === 'string' && value.toLowerCase() === actual) ||
|
|
214
|
-
value === true;
|
|
210
|
+
value === '' || (typeof value === 'string' && value.toLowerCase() === actual) || value === true;
|
|
215
211
|
}
|
|
216
212
|
function updateValue(element, first, second) {
|
|
217
213
|
if (!isHTMLOrSVGElement(element)) {
|
|
@@ -282,17 +278,13 @@ function getAttributeValue(element, name, parseValue) {
|
|
|
282
278
|
const normalized = kebabCase(name);
|
|
283
279
|
const attribute = element.attributes[normalized];
|
|
284
280
|
const value = attribute instanceof Attr ? attribute.value : undefined;
|
|
285
|
-
return EXPRESSION_DATA_PREFIX.test(normalized) &&
|
|
286
|
-
typeof value === 'string' &&
|
|
287
|
-
parseValue
|
|
281
|
+
return EXPRESSION_DATA_PREFIX.test(normalized) && typeof value === 'string' && parseValue
|
|
288
282
|
? (parse(value) ?? value)
|
|
289
283
|
: value;
|
|
290
284
|
}
|
|
291
285
|
function getStyleValue(element, property, computed) {
|
|
292
286
|
const name = camelCase(property);
|
|
293
|
-
return computed
|
|
294
|
-
? getComputedStyle(element)[name]
|
|
295
|
-
: element.style[name];
|
|
287
|
+
return computed ? getComputedStyle(element)[name] : element.style[name];
|
|
296
288
|
}
|
|
297
289
|
const EXPRESSION_DATA_PREFIX = /^data-/i;
|
|
298
290
|
|
|
@@ -381,7 +373,6 @@ function setElementValues(element, first, second, callback) {
|
|
|
381
373
|
callback(element, first, second);
|
|
382
374
|
}
|
|
383
375
|
}
|
|
384
|
-
// biome-ignore lint/nursery/useMaxParams: Internal function, so it's fine
|
|
385
376
|
function updateElementValue(element, key, value, set, remove, json) {
|
|
386
377
|
if (isNullableOrWhitespace(value)) {
|
|
387
378
|
remove.call(element, key);
|
|
@@ -449,12 +440,12 @@ calculate().then((value) => {
|
|
|
449
440
|
});
|
|
450
441
|
|
|
451
442
|
function addDelegatedHandler(document, type, name, passive) {
|
|
452
|
-
const
|
|
453
|
-
if (document[
|
|
454
|
-
document[
|
|
443
|
+
const count = `${name}${COUNT_SUFFIX}`;
|
|
444
|
+
if (document[count] != null) {
|
|
445
|
+
document[count] += 1;
|
|
455
446
|
return;
|
|
456
447
|
}
|
|
457
|
-
document[
|
|
448
|
+
document[count] = 1;
|
|
458
449
|
document.addEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE, {
|
|
459
450
|
passive,
|
|
460
451
|
});
|
|
@@ -478,16 +469,17 @@ function delegatedEventHandler(event) {
|
|
|
478
469
|
for (let index = 0; index < length; index += 1) {
|
|
479
470
|
const item = items[index];
|
|
480
471
|
const listeners = item[key];
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
472
|
+
if (item.disabled || listeners == null) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
Object.defineProperty(event, 'currentTarget', {
|
|
476
|
+
configurable: true,
|
|
477
|
+
value: item,
|
|
478
|
+
});
|
|
479
|
+
for (const listener of listeners) {
|
|
480
|
+
listener.call(item, event);
|
|
481
|
+
if (event.cancelBubble) {
|
|
482
|
+
return;
|
|
491
483
|
}
|
|
492
484
|
}
|
|
493
485
|
}
|
|
@@ -502,10 +494,10 @@ function getDelegatedName(target, type, options) {
|
|
|
502
494
|
}
|
|
503
495
|
}
|
|
504
496
|
function removeDelegatedHandler(document, type, name, passive) {
|
|
505
|
-
const
|
|
506
|
-
document[
|
|
507
|
-
if (document[
|
|
508
|
-
document[
|
|
497
|
+
const count = `${name}${COUNT_SUFFIX}`;
|
|
498
|
+
document[count] -= 1;
|
|
499
|
+
if (document[count] < 1) {
|
|
500
|
+
document[count] = undefined;
|
|
509
501
|
document.removeEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE);
|
|
510
502
|
}
|
|
511
503
|
}
|
|
@@ -515,13 +507,14 @@ function removeDelegatedListener(target, type, name, listener, passive) {
|
|
|
515
507
|
return false;
|
|
516
508
|
}
|
|
517
509
|
handlers.delete(listener);
|
|
518
|
-
if (handlers
|
|
510
|
+
if (handlers.size === 0) {
|
|
519
511
|
target[name] = undefined;
|
|
520
512
|
}
|
|
521
513
|
removeDelegatedHandler(document, type, name, passive);
|
|
522
514
|
return true;
|
|
523
515
|
}
|
|
524
516
|
//
|
|
517
|
+
const COUNT_SUFFIX = '.count';
|
|
525
518
|
const EVENT_PREFIX = '@';
|
|
526
519
|
const EVENT_SUFFIX_ACTIVE = ':active';
|
|
527
520
|
const EVENT_SUFFIX_PASSIVE = ':passive';
|
|
@@ -551,7 +544,6 @@ const EVENT_TYPES = new Set([
|
|
|
551
544
|
]);
|
|
552
545
|
const HANDLER_ACTIVE = delegatedEventHandler.bind(false);
|
|
553
546
|
const HANDLER_PASSIVE = delegatedEventHandler.bind(true);
|
|
554
|
-
const LISTENERS_SUFFIX = ':listeners';
|
|
555
547
|
|
|
556
548
|
//
|
|
557
549
|
function createDispatchOptions(options) {
|
|
@@ -610,9 +602,7 @@ function getPosition(event) {
|
|
|
610
602
|
* @param options Options for event
|
|
611
603
|
*/
|
|
612
604
|
function off(target, type, listener, options) {
|
|
613
|
-
if (!isEventTarget(target) ||
|
|
614
|
-
typeof type !== 'string' ||
|
|
615
|
-
typeof listener !== 'function') {
|
|
605
|
+
if (!isEventTarget(target) || typeof type !== 'string' || typeof listener !== 'function') {
|
|
616
606
|
return;
|
|
617
607
|
}
|
|
618
608
|
const extended = createEventOptions(options);
|
|
@@ -623,9 +613,7 @@ function off(target, type, listener, options) {
|
|
|
623
613
|
}
|
|
624
614
|
}
|
|
625
615
|
function on(target, type, listener, options) {
|
|
626
|
-
if (!isEventTarget(target) ||
|
|
627
|
-
typeof type !== 'string' ||
|
|
628
|
-
typeof listener !== 'function') {
|
|
616
|
+
if (!isEventTarget(target) || typeof type !== 'string' || typeof listener !== 'function') {
|
|
629
617
|
return noop;
|
|
630
618
|
}
|
|
631
619
|
const extended = createEventOptions(options);
|
|
@@ -659,7 +647,7 @@ function getDistanceBetweenElements(origin, target) {
|
|
|
659
647
|
}
|
|
660
648
|
const beforeOrInside = !!(comparison & 2 || comparison & 8);
|
|
661
649
|
if (beforeOrInside || !!(comparison & 4 || comparison & 16)) {
|
|
662
|
-
return
|
|
650
|
+
return traverse(beforeOrInside ? origin : target, beforeOrInside ? target : origin) ?? -1;
|
|
663
651
|
}
|
|
664
652
|
}
|
|
665
653
|
/**
|
|
@@ -742,9 +730,7 @@ function findRelatives(origin, selector, context) {
|
|
|
742
730
|
}
|
|
743
731
|
return minimum == null
|
|
744
732
|
? []
|
|
745
|
-
: distances
|
|
746
|
-
.filter(found => found.distance === minimum)
|
|
747
|
-
.map(found => found.element);
|
|
733
|
+
: distances.filter(found => found.distance === minimum).map(found => found.element);
|
|
748
734
|
}
|
|
749
735
|
function traverse(from, to) {
|
|
750
736
|
let index = [...to.children].indexOf(from);
|
|
@@ -760,7 +746,7 @@ function traverse(from, to) {
|
|
|
760
746
|
}
|
|
761
747
|
const children = [...(parent.children ?? [])];
|
|
762
748
|
if (children.includes(to)) {
|
|
763
|
-
return
|
|
749
|
+
return distance + Math.abs(children.indexOf(current) - children.indexOf(to));
|
|
764
750
|
}
|
|
765
751
|
index = children.findIndex(child => child.contains(to));
|
|
766
752
|
if (index > -1) {
|
|
@@ -814,9 +800,7 @@ function findElementOrElementsForSelector(selector, contexts, callback, single)
|
|
|
814
800
|
}
|
|
815
801
|
result.push(...Array.from(value));
|
|
816
802
|
}
|
|
817
|
-
return single
|
|
818
|
-
? null
|
|
819
|
-
: result.filter((value, index, array) => array.indexOf(value) === index);
|
|
803
|
+
return single ? null : result.filter((value, index, array) => array.indexOf(value) === index);
|
|
820
804
|
}
|
|
821
805
|
function findElementOrElementsFromNodes(array, context, contexts) {
|
|
822
806
|
const result = [];
|
|
@@ -914,8 +898,7 @@ function getTabbable(parent) {
|
|
|
914
898
|
function getTabIndex(element) {
|
|
915
899
|
const tabIndex = element?.tabIndex ?? TABINDEX_DEFAULT;
|
|
916
900
|
if (tabIndex < TABINDEX_BASE &&
|
|
917
|
-
(EXPRESSION_SPECIAL_TABINDEX.test(element.tagName) ||
|
|
918
|
-
isEditable(element)) &&
|
|
901
|
+
(EXPRESSION_SPECIAL_TABINDEX.test(element.tagName) || isEditable(element)) &&
|
|
919
902
|
!hasTabIndex(element)) {
|
|
920
903
|
return TABINDEX_BASE;
|
|
921
904
|
}
|
|
@@ -946,10 +929,7 @@ function getValidElements(parent, filters, tabbable) {
|
|
|
946
929
|
zeroed.push(item.element);
|
|
947
930
|
}
|
|
948
931
|
else {
|
|
949
|
-
indiced[item.tabIndex] = [
|
|
950
|
-
...(indiced[item.tabIndex] ?? []),
|
|
951
|
-
item.element,
|
|
952
|
-
];
|
|
932
|
+
indiced[item.tabIndex] = [...(indiced[item.tabIndex] ?? []), item.element];
|
|
953
933
|
}
|
|
954
934
|
}
|
|
955
935
|
return [...indiced.flat(), ...zeroed];
|
|
@@ -958,8 +938,7 @@ function hasTabIndex(element) {
|
|
|
958
938
|
return !Number.isNaN(Number.parseInt(element.getAttribute(ATTRIBUTE_TABINDEX), 10));
|
|
959
939
|
}
|
|
960
940
|
function isDisabled(item) {
|
|
961
|
-
if (EXPRESSION_DISABLEABLE.test(item.element.tagName) &&
|
|
962
|
-
isDisabledFromFieldset(item.element)) {
|
|
941
|
+
if (EXPRESSION_DISABLEABLE.test(item.element.tagName) && isDisabledFromFieldset(item.element)) {
|
|
963
942
|
return true;
|
|
964
943
|
}
|
|
965
944
|
return item.element.disabled ?? false;
|
|
@@ -973,8 +952,7 @@ function isDisabledFromFieldset(element) {
|
|
|
973
952
|
for (let index = 0; index < length; index += 1) {
|
|
974
953
|
const child = children[index];
|
|
975
954
|
if (child instanceof HTMLLegendElement) {
|
|
976
|
-
return
|
|
977
|
-
!child.contains(element));
|
|
955
|
+
return parent.matches(SELECTOR_FIELDSET_DISABLED) || !child.contains(element);
|
|
978
956
|
}
|
|
979
957
|
}
|
|
980
958
|
return true;
|
|
@@ -992,20 +970,15 @@ function isEditable(element) {
|
|
|
992
970
|
* @returns `true` if focusable, otherwise `false`
|
|
993
971
|
*/
|
|
994
972
|
function isFocusable(element) {
|
|
995
|
-
return element instanceof Element
|
|
996
|
-
? isValidElement(element, FILTERS_FOCUSABLE, false)
|
|
997
|
-
: false;
|
|
973
|
+
return element instanceof Element ? isValidElement(element, FILTERS_FOCUSABLE, false) : false;
|
|
998
974
|
}
|
|
999
975
|
function isHidden(item) {
|
|
1000
976
|
if ((item.element.hidden ?? false) ||
|
|
1001
|
-
(item.element instanceof HTMLInputElement &&
|
|
1002
|
-
item.element.type === STYLE_HIDDEN)) {
|
|
977
|
+
(item.element instanceof HTMLInputElement && item.element.type === STYLE_HIDDEN)) {
|
|
1003
978
|
return true;
|
|
1004
979
|
}
|
|
1005
980
|
const isDirectSummary = item.element.matches(SELECTOR_SUMMARY_FIRST);
|
|
1006
|
-
const nodeUnderDetails = isDirectSummary
|
|
1007
|
-
? item.element.parentElement
|
|
1008
|
-
: item.element;
|
|
981
|
+
const nodeUnderDetails = isDirectSummary ? item.element.parentElement : item.element;
|
|
1009
982
|
if (nodeUnderDetails?.matches(SELECTOR_DETAILS_CLOSED_CHILDREN) ?? false) {
|
|
1010
983
|
return true;
|
|
1011
984
|
}
|
|
@@ -1033,9 +1006,7 @@ function isNotTabbableRadio(item) {
|
|
|
1033
1006
|
item.element.checked) {
|
|
1034
1007
|
return false;
|
|
1035
1008
|
}
|
|
1036
|
-
const parent = item.element.form ??
|
|
1037
|
-
item.element.getRootNode?.() ??
|
|
1038
|
-
item.element.ownerDocument;
|
|
1009
|
+
const parent = item.element.form ?? item.element.getRootNode?.() ?? item.element.ownerDocument;
|
|
1039
1010
|
const realName = CSS?.escape?.(item.element.name) ?? item.element.name;
|
|
1040
1011
|
const radios = [
|
|
1041
1012
|
...parent.querySelectorAll(`${SELECTOR_RADIO_PREFIX}${realName}${SELECTOR_RADIO_SUFFIX}`),
|
|
@@ -1053,9 +1024,7 @@ function isSummarised(item) {
|
|
|
1053
1024
|
* @returns `true` if tabbable, otherwise `false`
|
|
1054
1025
|
*/
|
|
1055
1026
|
function isTabbable(element) {
|
|
1056
|
-
return element instanceof Element
|
|
1057
|
-
? isValidElement(element, FILTERS_TABBABLE, true)
|
|
1058
|
-
: false;
|
|
1027
|
+
return element instanceof Element ? isValidElement(element, FILTERS_TABBABLE, true) : false;
|
|
1059
1028
|
}
|
|
1060
1029
|
function isValidElement(element, filters, tabbable) {
|
|
1061
1030
|
const item = getItem(element, tabbable);
|
|
@@ -1106,29 +1075,19 @@ const TABINDEX_BASE = 0;
|
|
|
1106
1075
|
const TABINDEX_DEFAULT = -1;
|
|
1107
1076
|
const TYPE_RADIO = 'radio';
|
|
1108
1077
|
|
|
1109
|
-
|
|
1110
|
-
function getSanitizeOptions(input) {
|
|
1111
|
-
const options = isPlainObject(input) ? input : {};
|
|
1112
|
-
options.sanitizeBooleanAttributes =
|
|
1113
|
-
typeof options.sanitizeBooleanAttributes === 'boolean'
|
|
1114
|
-
? options.sanitizeBooleanAttributes
|
|
1115
|
-
: true;
|
|
1116
|
-
return options;
|
|
1117
|
-
}
|
|
1118
|
-
function sanitizeAttributes(element, attributes, options) {
|
|
1078
|
+
function sanitizeAttributes(element, attributes) {
|
|
1119
1079
|
const { length } = attributes;
|
|
1120
1080
|
for (let index = 0; index < length; index += 1) {
|
|
1121
1081
|
const attribute = attributes[index];
|
|
1122
1082
|
if (isBadAttribute(attribute) || isEmptyNonBooleanAttribute(attribute)) {
|
|
1123
1083
|
element.removeAttribute(attribute.name);
|
|
1124
1084
|
}
|
|
1125
|
-
else if (
|
|
1126
|
-
isInvalidBooleanAttribute(attribute)) {
|
|
1085
|
+
else if (isInvalidBooleanAttribute(attribute)) {
|
|
1127
1086
|
element.setAttribute(attribute.name, '');
|
|
1128
1087
|
}
|
|
1129
1088
|
}
|
|
1130
1089
|
}
|
|
1131
|
-
function sanitizeNodes(nodes
|
|
1090
|
+
function sanitizeNodes(nodes) {
|
|
1132
1091
|
const actual = nodes.filter(node => node instanceof Node);
|
|
1133
1092
|
const { length } = nodes;
|
|
1134
1093
|
for (let index = 0; index < length; index += 1) {
|
|
@@ -1138,53 +1097,50 @@ function sanitizeNodes(nodes, options) {
|
|
|
1138
1097
|
for (const script of scripts) {
|
|
1139
1098
|
script.remove();
|
|
1140
1099
|
}
|
|
1141
|
-
sanitizeAttributes(node, [...node.attributes]
|
|
1100
|
+
sanitizeAttributes(node, [...node.attributes]);
|
|
1142
1101
|
}
|
|
1143
1102
|
if (node.hasChildNodes()) {
|
|
1144
|
-
sanitizeNodes([...node.childNodes]
|
|
1103
|
+
sanitizeNodes([...node.childNodes]);
|
|
1145
1104
|
}
|
|
1146
1105
|
}
|
|
1147
1106
|
return nodes;
|
|
1148
1107
|
}
|
|
1149
1108
|
|
|
1150
1109
|
//
|
|
1151
|
-
function
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1110
|
+
function createHtml(value) {
|
|
1111
|
+
const html = getParser().parseFromString(typeof value === 'string' ? value : value.innerHTML, HTML_PARSE_TYPE);
|
|
1112
|
+
html.body.normalize();
|
|
1113
|
+
sanitizeNodes([html.body]);
|
|
1114
|
+
return html.body.innerHTML;
|
|
1115
|
+
}
|
|
1116
|
+
function createTemplate(value, options) {
|
|
1117
|
+
const template = document.createElement(TEMPLATE_TAG);
|
|
1118
|
+
template.innerHTML = createHtml(value);
|
|
1119
|
+
if (typeof value === 'string' && !options.ignoreCache) {
|
|
1120
|
+
templates[value] = template;
|
|
1156
1121
|
}
|
|
1157
1122
|
return template;
|
|
1158
1123
|
}
|
|
1159
|
-
function
|
|
1124
|
+
function getNodes(value, options) {
|
|
1160
1125
|
if (typeof value !== 'string' && !(value instanceof HTMLTemplateElement)) {
|
|
1161
1126
|
return [];
|
|
1162
1127
|
}
|
|
1163
|
-
const template = value
|
|
1164
|
-
|
|
1165
|
-
: getTemplate(value, options.ignoreCache);
|
|
1166
|
-
if (template == null) {
|
|
1167
|
-
return [];
|
|
1168
|
-
}
|
|
1169
|
-
const cloned = template.content.cloneNode(true);
|
|
1170
|
-
const scripts = cloned.querySelectorAll('script');
|
|
1171
|
-
for (const script of scripts) {
|
|
1172
|
-
script.remove();
|
|
1173
|
-
}
|
|
1174
|
-
cloned.normalize();
|
|
1175
|
-
return sanitizeNodes([...cloned.childNodes], options);
|
|
1128
|
+
const template = getTemplate(value, options);
|
|
1129
|
+
return template == null ? [] : [...template.content.cloneNode(true).childNodes];
|
|
1176
1130
|
}
|
|
1177
1131
|
function getOptions(input) {
|
|
1178
1132
|
const options = isPlainObject(input) ? input : {};
|
|
1179
|
-
options.ignoreCache =
|
|
1180
|
-
typeof options.ignoreCache === 'boolean' ? options.ignoreCache : false;
|
|
1181
|
-
options.sanitizeBooleanAttributes =
|
|
1182
|
-
typeof options.sanitizeBooleanAttributes === 'boolean'
|
|
1183
|
-
? options.sanitizeBooleanAttributes
|
|
1184
|
-
: true;
|
|
1133
|
+
options.ignoreCache = typeof options.ignoreCache === 'boolean' ? options.ignoreCache : false;
|
|
1185
1134
|
return options;
|
|
1186
1135
|
}
|
|
1187
|
-
function
|
|
1136
|
+
function getParser() {
|
|
1137
|
+
parser ??= new DOMParser();
|
|
1138
|
+
return parser;
|
|
1139
|
+
}
|
|
1140
|
+
function getTemplate(value, options) {
|
|
1141
|
+
if (value instanceof HTMLTemplateElement) {
|
|
1142
|
+
return createTemplate(value, options);
|
|
1143
|
+
}
|
|
1188
1144
|
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
1189
1145
|
return;
|
|
1190
1146
|
}
|
|
@@ -1192,17 +1148,12 @@ function getTemplate(value, ignore) {
|
|
|
1192
1148
|
if (template != null) {
|
|
1193
1149
|
return template;
|
|
1194
1150
|
}
|
|
1195
|
-
const element = EXPRESSION_ID.test(value)
|
|
1196
|
-
|
|
1197
|
-
: null;
|
|
1198
|
-
template =
|
|
1199
|
-
element instanceof HTMLTemplateElement
|
|
1200
|
-
? element
|
|
1201
|
-
: createTemplate(value, ignore);
|
|
1151
|
+
const element = EXPRESSION_ID.test(value) ? document.querySelector(`#${value}`) : null;
|
|
1152
|
+
template = element instanceof HTMLTemplateElement ? element : createTemplate(value, options);
|
|
1202
1153
|
return template;
|
|
1203
1154
|
}
|
|
1204
1155
|
const html = ((value, options) => {
|
|
1205
|
-
return
|
|
1156
|
+
return getNodes(value, getOptions(options));
|
|
1206
1157
|
});
|
|
1207
1158
|
html.clear = () => {
|
|
1208
1159
|
templates = {};
|
|
@@ -1228,11 +1179,14 @@ html.remove = (template) => {
|
|
|
1228
1179
|
* @param options Sanitization options
|
|
1229
1180
|
* @returns Sanitized nodes
|
|
1230
1181
|
*/
|
|
1231
|
-
function sanitize(value
|
|
1232
|
-
return sanitizeNodes(Array.isArray(value) ? value : [value]
|
|
1182
|
+
function sanitize(value) {
|
|
1183
|
+
return sanitizeNodes(Array.isArray(value) ? value : [value]);
|
|
1233
1184
|
}
|
|
1234
1185
|
//
|
|
1235
1186
|
const EXPRESSION_ID = /^[a-z][\w-]*$/i;
|
|
1187
|
+
const HTML_PARSE_TYPE = 'text/html';
|
|
1188
|
+
const TEMPLATE_TAG = 'template';
|
|
1189
|
+
let parser;
|
|
1236
1190
|
let templates = {};
|
|
1237
1191
|
|
|
1238
1192
|
/**
|
package/package.json
CHANGED
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@oscarpalmer/atoms": "^0.
|
|
7
|
+
"@oscarpalmer/atoms": "^0.115"
|
|
8
8
|
},
|
|
9
9
|
"description": "A collection of badass DOM utilities.",
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@biomejs/biome": "^2.3",
|
|
12
11
|
"@rollup/plugin-node-resolve": "^16",
|
|
13
12
|
"@rollup/plugin-typescript": "^12.3",
|
|
14
13
|
"@types/node": "^24.10",
|
|
15
14
|
"@vitest/coverage-istanbul": "^4",
|
|
16
15
|
"jsdom": "^27.2",
|
|
16
|
+
"oxfmt": "^0.16",
|
|
17
|
+
"oxlint": "^1.31",
|
|
17
18
|
"rollup": "^4.53",
|
|
18
19
|
"tslib": "^2.8",
|
|
19
20
|
"typescript": "^5.9",
|
|
20
|
-
"vite": "
|
|
21
|
+
"vite": "8.0.0-beta.0",
|
|
21
22
|
"vitest": "^4"
|
|
22
23
|
},
|
|
23
24
|
"exports": {
|
|
@@ -92,5 +93,5 @@
|
|
|
92
93
|
},
|
|
93
94
|
"type": "module",
|
|
94
95
|
"types": "types/index.d.ts",
|
|
95
|
-
"version": "0.
|
|
96
|
+
"version": "0.29.0"
|
|
96
97
|
}
|