@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.
@@ -1,11 +1,11 @@
1
1
  import { isEventTarget } from "../internal/is.js";
2
2
  function addDelegatedHandler(document$1, type, name, passive) {
3
- const listeners = `${name}${LISTENERS_SUFFIX}`;
4
- if (document$1[listeners] != null) {
5
- document$1[listeners] += 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[listeners] = 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 (!item.disabled && listeners != null) {
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;
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 listeners = `${name}${LISTENERS_SUFFIX}`;
47
- document$1[listeners] -= 1;
48
- if (document$1[listeners] < 1) {
49
- document$1[listeners] = void 0;
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?.size === 0) target[name] = void 0;
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 { getSanitizeOptions, sanitizeNodes } from "./internal/sanitize.js";
1
+ import { sanitizeNodes } from "./internal/sanitize.js";
2
2
  import { isPlainObject } from "@oscarpalmer/atoms/is";
3
- function createTemplate(html$1, ignore) {
4
- const template = document.createElement("template");
5
- template.innerHTML = html$1;
6
- if (!ignore) templates[html$1] = template;
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 getHtml(value, options) {
15
+ function getNodes(value, options) {
10
16
  if (typeof value !== "string" && !(value instanceof HTMLTemplateElement)) return [];
11
- const template = value instanceof HTMLTemplateElement ? value : getTemplate(value, options.ignoreCache);
12
- if (template == null) return [];
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 getTemplate(value, ignore) {
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, ignore);
35
+ template = element instanceof HTMLTemplateElement ? element : createTemplate(value, options);
31
36
  return template;
32
37
  }
33
38
  var html = ((value, options) => {
34
- return getHtml(value, getOptions(options));
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, options) {
51
- return sanitizeNodes(Array.isArray(value) ? value : [value], getSanitizeOptions(options));
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
- import { isPlainObject } from "@oscarpalmer/atoms/is";
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 (options.sanitizeBooleanAttributes && isInvalidBooleanAttribute(attribute)) element.setAttribute(attribute.name, "");
7
+ else if (isInvalidBooleanAttribute(attribute)) element.setAttribute(attribute.name, "");
14
8
  }
15
9
  }
16
- function sanitizeNodes(nodes, options) {
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], options);
18
+ sanitizeAttributes(node, [...node.attributes]);
25
19
  }
26
- if (node.hasChildNodes()) sanitizeNodes([...node.childNodes], options);
20
+ if (node.hasChildNodes()) sanitizeNodes([...node.childNodes]);
27
21
  }
28
22
  return nodes;
29
23
  }
30
- export { getSanitizeOptions, sanitizeAttributes, sanitizeNodes };
24
+ export { sanitizeAttributes, sanitizeNodes };
@@ -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 (isPlainObject(value) && typeof value.name === 'string');
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 listeners = `${name}${LISTENERS_SUFFIX}`;
453
- if (document[listeners] != null) {
454
- document[listeners] += 1;
443
+ const count = `${name}${COUNT_SUFFIX}`;
444
+ if (document[count] != null) {
445
+ document[count] += 1;
455
446
  return;
456
447
  }
457
- document[listeners] = 1;
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 (!item.disabled && listeners != null) {
482
- Object.defineProperty(event, 'currentTarget', {
483
- configurable: true,
484
- value: item,
485
- });
486
- for (const listener of listeners) {
487
- listener.call(item, event);
488
- if (event.cancelBubble) {
489
- return;
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 listeners = `${name}${LISTENERS_SUFFIX}`;
506
- document[listeners] -= 1;
507
- if (document[listeners] < 1) {
508
- document[listeners] = undefined;
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?.size === 0) {
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 (traverse(beforeOrInside ? origin : target, beforeOrInside ? target : origin) ?? -1);
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 (distance + Math.abs(children.indexOf(current) - children.indexOf(to)));
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 (parent.matches(SELECTOR_FIELDSET_DISABLED) ||
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 (options.sanitizeBooleanAttributes &&
1126
- isInvalidBooleanAttribute(attribute)) {
1085
+ else if (isInvalidBooleanAttribute(attribute)) {
1127
1086
  element.setAttribute(attribute.name, '');
1128
1087
  }
1129
1088
  }
1130
1089
  }
1131
- function sanitizeNodes(nodes, options) {
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], options);
1100
+ sanitizeAttributes(node, [...node.attributes]);
1142
1101
  }
1143
1102
  if (node.hasChildNodes()) {
1144
- sanitizeNodes([...node.childNodes], options);
1103
+ sanitizeNodes([...node.childNodes]);
1145
1104
  }
1146
1105
  }
1147
1106
  return nodes;
1148
1107
  }
1149
1108
 
1150
1109
  //
1151
- function createTemplate(html, ignore) {
1152
- const template = document.createElement('template');
1153
- template.innerHTML = html;
1154
- if (!ignore) {
1155
- templates[html] = template;
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 getHtml(value, options) {
1124
+ function getNodes(value, options) {
1160
1125
  if (typeof value !== 'string' && !(value instanceof HTMLTemplateElement)) {
1161
1126
  return [];
1162
1127
  }
1163
- const template = value instanceof HTMLTemplateElement
1164
- ? value
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 getTemplate(value, ignore) {
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
- ? document.querySelector(`#${value}`)
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 getHtml(value, getOptions(options));
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, options) {
1232
- return sanitizeNodes(Array.isArray(value) ? value : [value], getSanitizeOptions(options));
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.114"
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": "npm:rolldown-vite@latest",
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.27.0"
96
+ "version": "0.29.0"
96
97
  }