@oscarpalmer/atoms 0.68.0 → 0.69.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.
@@ -0,0 +1,61 @@
1
+ // src/js/element/attribute.ts
2
+ function isBadAttribute(name, value) {
3
+ return onPrefix.test(name) || sourcePrefix.test(name) && valuePrefix.test(value);
4
+ }
5
+ function isBooleanAttribute(name) {
6
+ return booleanAttributes.includes(name.toLowerCase());
7
+ }
8
+ function isEmptyNonBooleanAttribute(name, value) {
9
+ return !booleanAttributes.includes(name) && value.trim().length === 0;
10
+ }
11
+ function isInvalidBooleanAttribute(name, value) {
12
+ if (!booleanAttributes.includes(name)) {
13
+ return true;
14
+ }
15
+ const normalised = value.toLowerCase().trim();
16
+ return !(normalised.length === 0 || normalised === name || name === "hidden" && normalised === "until-found");
17
+ }
18
+ function setAttribute(element, name, value) {
19
+ if (value == null) {
20
+ element.removeAttribute(name);
21
+ } else {
22
+ element.setAttribute(name, typeof value === "string" ? value : JSON.stringify(value));
23
+ }
24
+ }
25
+ var booleanAttributes = Object.freeze([
26
+ "async",
27
+ "autofocus",
28
+ "autoplay",
29
+ "checked",
30
+ "controls",
31
+ "default",
32
+ "defer",
33
+ "disabled",
34
+ "formnovalidate",
35
+ "hidden",
36
+ "inert",
37
+ "ismap",
38
+ "itemscope",
39
+ "loop",
40
+ "multiple",
41
+ "muted",
42
+ "nomodule",
43
+ "novalidate",
44
+ "open",
45
+ "playsinline",
46
+ "readonly",
47
+ "required",
48
+ "reversed",
49
+ "selected"
50
+ ]);
51
+ var onPrefix = /^on/i;
52
+ var sourcePrefix = /^(href|src|xlink:href)$/i;
53
+ var valuePrefix = /(data:text\/html|javascript:)/i;
54
+ export {
55
+ setAttribute,
56
+ isInvalidBooleanAttribute,
57
+ isEmptyNonBooleanAttribute,
58
+ isBooleanAttribute,
59
+ isBadAttribute,
60
+ booleanAttributes
61
+ };
@@ -17,6 +17,59 @@ function getTextDirection(element) {
17
17
  return getComputedStyle?.(element)?.direction === "rtl" ? "rtl" : "ltr";
18
18
  }
19
19
 
20
+ // src/js/element/attribute.ts
21
+ function isBadAttribute(name, value) {
22
+ return onPrefix.test(name) || sourcePrefix.test(name) && valuePrefix.test(value);
23
+ }
24
+ function isBooleanAttribute(name) {
25
+ return booleanAttributes.includes(name.toLowerCase());
26
+ }
27
+ function isEmptyNonBooleanAttribute(name, value) {
28
+ return !booleanAttributes.includes(name) && value.trim().length === 0;
29
+ }
30
+ function isInvalidBooleanAttribute(name, value) {
31
+ if (!booleanAttributes.includes(name)) {
32
+ return true;
33
+ }
34
+ const normalised = value.toLowerCase().trim();
35
+ return !(normalised.length === 0 || normalised === name || name === "hidden" && normalised === "until-found");
36
+ }
37
+ function setAttribute(element, name, value) {
38
+ if (value == null) {
39
+ element.removeAttribute(name);
40
+ } else {
41
+ element.setAttribute(name, typeof value === "string" ? value : JSON.stringify(value));
42
+ }
43
+ }
44
+ var booleanAttributes = Object.freeze([
45
+ "async",
46
+ "autofocus",
47
+ "autoplay",
48
+ "checked",
49
+ "controls",
50
+ "default",
51
+ "defer",
52
+ "disabled",
53
+ "formnovalidate",
54
+ "hidden",
55
+ "inert",
56
+ "ismap",
57
+ "itemscope",
58
+ "loop",
59
+ "multiple",
60
+ "muted",
61
+ "nomodule",
62
+ "novalidate",
63
+ "open",
64
+ "playsinline",
65
+ "readonly",
66
+ "required",
67
+ "reversed",
68
+ "selected"
69
+ ]);
70
+ var onPrefix = /^on/i;
71
+ var sourcePrefix = /^(href|src|xlink:href)$/i;
72
+ var valuePrefix = /(data:text\/html|javascript:)/i;
20
73
  // src/js/element/closest.ts
21
74
  function calculateDistance(origin, target) {
22
75
  if (origin === target || origin.parentElement === target) {
@@ -236,6 +289,11 @@ function updateStyleProperty(element, key, value2) {
236
289
  export {
237
290
  setStyles,
238
291
  setData,
292
+ setAttribute,
293
+ isInvalidBooleanAttribute,
294
+ isEmptyNonBooleanAttribute,
295
+ isBooleanAttribute,
296
+ isBadAttribute,
239
297
  getTextDirection,
240
298
  getElementUnderPointer,
241
299
  getData,
@@ -243,6 +301,7 @@ export {
243
301
  findElements,
244
302
  findElement,
245
303
  closest,
304
+ booleanAttributes,
246
305
  findElements as $$,
247
306
  findElement as $
248
307
  };
@@ -17,6 +17,7 @@ function getTextDirection(element) {
17
17
  return getComputedStyle?.(element)?.direction === "rtl" ? "rtl" : "ltr";
18
18
  }
19
19
 
20
+ export * from "./attribute";
20
21
  export * from "./closest";
21
22
  export * from "./data";
22
23
  export * from "./find";
@@ -0,0 +1,124 @@
1
+ // src/js/is.ts
2
+ function isPlainObject(value2) {
3
+ if (typeof value2 !== "object" || value2 === null) {
4
+ return false;
5
+ }
6
+ const prototype = Object.getPrototypeOf(value2);
7
+ return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value2) && !(Symbol.iterator in value2);
8
+ }
9
+
10
+ // src/js/element/attribute.ts
11
+ function isBadAttribute(name, value2) {
12
+ return onPrefix.test(name) || sourcePrefix.test(name) && valuePrefix.test(value2);
13
+ }
14
+ function isEmptyNonBooleanAttribute(name, value2) {
15
+ return !booleanAttributes.includes(name) && value2.trim().length === 0;
16
+ }
17
+ function isInvalidBooleanAttribute(name, value2) {
18
+ if (!booleanAttributes.includes(name)) {
19
+ return true;
20
+ }
21
+ const normalised = value2.toLowerCase().trim();
22
+ return !(normalised.length === 0 || normalised === name || name === "hidden" && normalised === "until-found");
23
+ }
24
+ var booleanAttributes = Object.freeze([
25
+ "async",
26
+ "autofocus",
27
+ "autoplay",
28
+ "checked",
29
+ "controls",
30
+ "default",
31
+ "defer",
32
+ "disabled",
33
+ "formnovalidate",
34
+ "hidden",
35
+ "inert",
36
+ "ismap",
37
+ "itemscope",
38
+ "loop",
39
+ "multiple",
40
+ "muted",
41
+ "nomodule",
42
+ "novalidate",
43
+ "open",
44
+ "playsinline",
45
+ "readonly",
46
+ "required",
47
+ "reversed",
48
+ "selected"
49
+ ]);
50
+ var onPrefix = /^on/i;
51
+ var sourcePrefix = /^(href|src|xlink:href)$/i;
52
+ var valuePrefix = /(data:text\/html|javascript:)/i;
53
+ // src/js/html/sanitise.ts
54
+ function sanitise(value2, options) {
55
+ return sanitiseNodes(Array.isArray(value2) ? value2 : [value2], {
56
+ sanitiseBooleanAttributes: options?.sanitiseBooleanAttributes ?? true
57
+ });
58
+ }
59
+ function sanitiseAttributes(element2, attributes, options) {
60
+ const { length } = attributes;
61
+ for (let index = 0;index < length; index += 1) {
62
+ const { name, value: value2 } = attributes[index];
63
+ if (isBadAttribute(name, value2) || isEmptyNonBooleanAttribute(name, value2)) {
64
+ element2.removeAttribute(name);
65
+ } else if (options.sanitiseBooleanAttributes && isInvalidBooleanAttribute(name, value2)) {
66
+ element2.setAttribute(name, "");
67
+ }
68
+ }
69
+ }
70
+ function sanitiseNodes(nodes, options) {
71
+ const { length } = nodes;
72
+ for (let index = 0;index < length; index += 1) {
73
+ const node = nodes[index];
74
+ if (node instanceof Element) {
75
+ sanitiseAttributes(node, [...node.attributes], options);
76
+ }
77
+ sanitiseNodes([...node.childNodes], options);
78
+ }
79
+ return nodes;
80
+ }
81
+
82
+ // src/js/html/index.ts
83
+ function createTemplate(html) {
84
+ const template2 = document.createElement("template");
85
+ template2.innerHTML = html;
86
+ templates[html] = template2;
87
+ return template2;
88
+ }
89
+ function getNodes(node) {
90
+ return /^documentfragment$/i.test(node.constructor.name) ? [...node.childNodes] : [node];
91
+ }
92
+ function getTemplate(value2) {
93
+ if (value2.trim().length === 0) {
94
+ return;
95
+ }
96
+ let template2;
97
+ if (/^[\w-]+$/.test(value2)) {
98
+ template2 = document.querySelector(`#${value2}`);
99
+ }
100
+ if (template2 instanceof HTMLTemplateElement) {
101
+ return template2;
102
+ }
103
+ return templates[value2] ?? createTemplate(value2);
104
+ }
105
+ function html(value2, sanitisation) {
106
+ const options = sanitisation == null || sanitisation === true ? {} : isPlainObject(sanitisation) ? { ...sanitisation } : null;
107
+ const template2 = value2 instanceof HTMLTemplateElement ? value2 : typeof value2 === "string" ? getTemplate(value2) : null;
108
+ if (template2 == null) {
109
+ return [];
110
+ }
111
+ const cloned = template2.content.cloneNode(true);
112
+ const scripts = cloned.querySelectorAll("script");
113
+ const { length } = scripts;
114
+ for (let index = 0;index < length; index += 1) {
115
+ scripts[index].remove();
116
+ }
117
+ cloned.normalize();
118
+ return options != null ? sanitise(getNodes(cloned), options) : getNodes(cloned);
119
+ }
120
+ var templates = {};
121
+ export {
122
+ sanitise,
123
+ html
124
+ };
@@ -0,0 +1,46 @@
1
+ // src/js/html/index.ts
2
+ import {isPlainObject} from "../is";
3
+ import {sanitise as sanitise2} from "./sanitise";
4
+ function createTemplate(html) {
5
+ const template = document.createElement("template");
6
+ template.innerHTML = html;
7
+ templates[html] = template;
8
+ return template;
9
+ }
10
+ function getNodes(node) {
11
+ return /^documentfragment$/i.test(node.constructor.name) ? [...node.childNodes] : [node];
12
+ }
13
+ function getTemplate(value) {
14
+ if (value.trim().length === 0) {
15
+ return;
16
+ }
17
+ let template;
18
+ if (/^[\w-]+$/.test(value)) {
19
+ template = document.querySelector(`#${value}`);
20
+ }
21
+ if (template instanceof HTMLTemplateElement) {
22
+ return template;
23
+ }
24
+ return templates[value] ?? createTemplate(value);
25
+ }
26
+ function html(value, sanitisation) {
27
+ const options = sanitisation == null || sanitisation === true ? {} : isPlainObject(sanitisation) ? { ...sanitisation } : null;
28
+ const template = value instanceof HTMLTemplateElement ? value : typeof value === "string" ? getTemplate(value) : null;
29
+ if (template == null) {
30
+ return [];
31
+ }
32
+ const cloned = template.content.cloneNode(true);
33
+ const scripts = cloned.querySelectorAll("script");
34
+ const { length } = scripts;
35
+ for (let index = 0;index < length; index += 1) {
36
+ scripts[index].remove();
37
+ }
38
+ cloned.normalize();
39
+ return options != null ? sanitise2(getNodes(cloned), options) : getNodes(cloned);
40
+ }
41
+
42
+ export * from "./sanitise";
43
+ var templates = {};
44
+ export {
45
+ html
46
+ };
@@ -0,0 +1,36 @@
1
+ // src/js/html/sanitise.ts
2
+ import {
3
+ isBadAttribute,
4
+ isEmptyNonBooleanAttribute,
5
+ isInvalidBooleanAttribute
6
+ } from "../element";
7
+ function sanitise(value, options) {
8
+ return sanitiseNodes(Array.isArray(value) ? value : [value], {
9
+ sanitiseBooleanAttributes: options?.sanitiseBooleanAttributes ?? true
10
+ });
11
+ }
12
+ function sanitiseAttributes(element2, attributes, options) {
13
+ const { length } = attributes;
14
+ for (let index = 0;index < length; index += 1) {
15
+ const { name, value } = attributes[index];
16
+ if (isBadAttribute(name, value) || isEmptyNonBooleanAttribute(name, value)) {
17
+ element2.removeAttribute(name);
18
+ } else if (options.sanitiseBooleanAttributes && isInvalidBooleanAttribute(name, value)) {
19
+ element2.setAttribute(name, "");
20
+ }
21
+ }
22
+ }
23
+ function sanitiseNodes(nodes, options) {
24
+ const { length } = nodes;
25
+ for (let index = 0;index < length; index += 1) {
26
+ const node = nodes[index];
27
+ if (node instanceof Element) {
28
+ sanitiseAttributes(node, [...node.attributes], options);
29
+ }
30
+ sanitiseNodes([...node.childNodes], options);
31
+ }
32
+ return nodes;
33
+ }
34
+ export {
35
+ sanitise
36
+ };
package/dist/js/index.js CHANGED
@@ -1271,6 +1271,59 @@ function getTextDirection(element) {
1271
1271
  return getComputedStyle?.(element)?.direction === "rtl" ? "rtl" : "ltr";
1272
1272
  }
1273
1273
 
1274
+ // src/js/element/attribute.ts
1275
+ function isBadAttribute(name, value2) {
1276
+ return onPrefix.test(name) || sourcePrefix.test(name) && valuePrefix.test(value2);
1277
+ }
1278
+ function isBooleanAttribute(name) {
1279
+ return booleanAttributes.includes(name.toLowerCase());
1280
+ }
1281
+ function isEmptyNonBooleanAttribute(name, value2) {
1282
+ return !booleanAttributes.includes(name) && value2.trim().length === 0;
1283
+ }
1284
+ function isInvalidBooleanAttribute(name, value2) {
1285
+ if (!booleanAttributes.includes(name)) {
1286
+ return true;
1287
+ }
1288
+ const normalised = value2.toLowerCase().trim();
1289
+ return !(normalised.length === 0 || normalised === name || name === "hidden" && normalised === "until-found");
1290
+ }
1291
+ function setAttribute(element, name, value2) {
1292
+ if (value2 == null) {
1293
+ element.removeAttribute(name);
1294
+ } else {
1295
+ element.setAttribute(name, typeof value2 === "string" ? value2 : JSON.stringify(value2));
1296
+ }
1297
+ }
1298
+ var booleanAttributes = Object.freeze([
1299
+ "async",
1300
+ "autofocus",
1301
+ "autoplay",
1302
+ "checked",
1303
+ "controls",
1304
+ "default",
1305
+ "defer",
1306
+ "disabled",
1307
+ "formnovalidate",
1308
+ "hidden",
1309
+ "inert",
1310
+ "ismap",
1311
+ "itemscope",
1312
+ "loop",
1313
+ "multiple",
1314
+ "muted",
1315
+ "nomodule",
1316
+ "novalidate",
1317
+ "open",
1318
+ "playsinline",
1319
+ "readonly",
1320
+ "required",
1321
+ "reversed",
1322
+ "selected"
1323
+ ]);
1324
+ var onPrefix = /^on/i;
1325
+ var sourcePrefix = /^(href|src|xlink:href)$/i;
1326
+ var valuePrefix = /(data:text\/html|javascript:)/i;
1274
1327
  // src/js/element/closest.ts
1275
1328
  function calculateDistance(origin, target) {
1276
1329
  if (origin === target || origin.parentElement === target) {
@@ -1657,6 +1710,74 @@ function getPosition(event) {
1657
1710
  }
1658
1711
  return typeof x === "number" && typeof y === "number" ? { x, y } : undefined;
1659
1712
  }
1713
+ // src/js/html/sanitise.ts
1714
+ function sanitise(value2, options) {
1715
+ return sanitiseNodes(Array.isArray(value2) ? value2 : [value2], {
1716
+ sanitiseBooleanAttributes: options?.sanitiseBooleanAttributes ?? true
1717
+ });
1718
+ }
1719
+ function sanitiseAttributes(element2, attributes, options) {
1720
+ const { length } = attributes;
1721
+ for (let index = 0;index < length; index += 1) {
1722
+ const { name, value: value2 } = attributes[index];
1723
+ if (isBadAttribute(name, value2) || isEmptyNonBooleanAttribute(name, value2)) {
1724
+ element2.removeAttribute(name);
1725
+ } else if (options.sanitiseBooleanAttributes && isInvalidBooleanAttribute(name, value2)) {
1726
+ element2.setAttribute(name, "");
1727
+ }
1728
+ }
1729
+ }
1730
+ function sanitiseNodes(nodes, options) {
1731
+ const { length } = nodes;
1732
+ for (let index = 0;index < length; index += 1) {
1733
+ const node = nodes[index];
1734
+ if (node instanceof Element) {
1735
+ sanitiseAttributes(node, [...node.attributes], options);
1736
+ }
1737
+ sanitiseNodes([...node.childNodes], options);
1738
+ }
1739
+ return nodes;
1740
+ }
1741
+
1742
+ // src/js/html/index.ts
1743
+ function createTemplate(html) {
1744
+ const template3 = document.createElement("template");
1745
+ template3.innerHTML = html;
1746
+ templates[html] = template3;
1747
+ return template3;
1748
+ }
1749
+ function getNodes(node) {
1750
+ return /^documentfragment$/i.test(node.constructor.name) ? [...node.childNodes] : [node];
1751
+ }
1752
+ function getTemplate(value2) {
1753
+ if (value2.trim().length === 0) {
1754
+ return;
1755
+ }
1756
+ let template3;
1757
+ if (/^[\w-]+$/.test(value2)) {
1758
+ template3 = document.querySelector(`#${value2}`);
1759
+ }
1760
+ if (template3 instanceof HTMLTemplateElement) {
1761
+ return template3;
1762
+ }
1763
+ return templates[value2] ?? createTemplate(value2);
1764
+ }
1765
+ function html(value2, sanitisation) {
1766
+ const options = sanitisation == null || sanitisation === true ? {} : isPlainObject(sanitisation) ? { ...sanitisation } : null;
1767
+ const template3 = value2 instanceof HTMLTemplateElement ? value2 : typeof value2 === "string" ? getTemplate(value2) : null;
1768
+ if (template3 == null) {
1769
+ return [];
1770
+ }
1771
+ const cloned = template3.content.cloneNode(true);
1772
+ const scripts = cloned.querySelectorAll("script");
1773
+ const { length } = scripts;
1774
+ for (let index = 0;index < length; index += 1) {
1775
+ scripts[index].remove();
1776
+ }
1777
+ cloned.normalize();
1778
+ return options != null ? sanitise(getNodes(cloned), options) : getNodes(cloned);
1779
+ }
1780
+ var templates = {};
1660
1781
  // src/js/logger.ts
1661
1782
  if (globalThis._atomic_logging == null) {
1662
1783
  globalThis._atomic_logging = true;
@@ -1998,20 +2119,20 @@ function delay(time, timeout) {
1998
2119
  }
1999
2120
 
2000
2121
  // src/js/timer/is.ts
2001
- function is11(pattern, value3) {
2122
+ function is12(pattern, value3) {
2002
2123
  return pattern.test(value3?.$timer);
2003
2124
  }
2004
2125
  function isRepeated(value3) {
2005
- return is11(/^repeat$/, value3);
2126
+ return is12(/^repeat$/, value3);
2006
2127
  }
2007
2128
  function isTimer(value3) {
2008
- return is11(/^repeat|wait$/, value3);
2129
+ return is12(/^repeat|wait$/, value3);
2009
2130
  }
2010
2131
  function isWaited(value3) {
2011
- return is11(/^wait$/, value3);
2132
+ return is12(/^wait$/, value3);
2012
2133
  }
2013
2134
  function isWhen(value3) {
2014
- return is11(/^when$/, value3) && typeof value3.then === "function";
2135
+ return is12(/^when$/, value3) && typeof value3.then === "function";
2015
2136
  }
2016
2137
  // src/js/timer/when.ts
2017
2138
  function when(condition, options) {
@@ -2138,6 +2259,8 @@ export {
2138
2259
  setValue,
2139
2260
  setStyles,
2140
2261
  setData,
2262
+ setAttribute,
2263
+ sanitise,
2141
2264
  round,
2142
2265
  repeat,
2143
2266
  queue,
@@ -2168,14 +2291,19 @@ export {
2168
2291
  isNullableOrEmpty,
2169
2292
  isNullable,
2170
2293
  isKey,
2294
+ isInvalidBooleanAttribute,
2171
2295
  isHexColour,
2172
2296
  isHSLColour,
2173
2297
  isFocusableElement,
2298
+ isEmptyNonBooleanAttribute,
2174
2299
  isEmpty,
2175
2300
  isColour,
2301
+ isBooleanAttribute,
2302
+ isBadAttribute,
2176
2303
  isArrayOrPlainObject,
2177
2304
  insert,
2178
2305
  indexOf,
2306
+ html,
2179
2307
  groupBy,
2180
2308
  getValue,
2181
2309
  getTextDirection,
@@ -2221,6 +2349,7 @@ export {
2221
2349
  chunk,
2222
2350
  capitalise,
2223
2351
  camelCase,
2352
+ booleanAttributes,
2224
2353
  between,
2225
2354
  average,
2226
2355
  RGBColour,
package/dist/js/index.mjs CHANGED
@@ -6,6 +6,7 @@ export * from "./element/index";
6
6
  export * from "./emitter";
7
7
  export * from "./event";
8
8
  export * from "./function";
9
+ export * from "./html/index";
9
10
  export * from "./is";
10
11
  export * from "./logger";
11
12
  export * from "./math";
package/package.json CHANGED
@@ -82,6 +82,12 @@
82
82
  "import": "./dist/js/function.mjs",
83
83
  "require": "./dist/js/function.js"
84
84
  },
85
+ "./html": {
86
+ "types": "./types/html/index.d.ts",
87
+ "bun": "./src/js/html/index.ts",
88
+ "import": "./dist/js/html/index.mjs",
89
+ "require": "./dist/js/html/index.js"
90
+ },
85
91
  "./is": {
86
92
  "types": "./types/is.d.ts",
87
93
  "bun": "./src/js/is.ts",
@@ -186,5 +192,5 @@
186
192
  },
187
193
  "type": "module",
188
194
  "types": "./types/index.d.cts",
189
- "version": "0.68.0"
195
+ "version": "0.69.0"
190
196
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * List of boolean attributes
3
+ */
4
+ export const booleanAttributes = Object.freeze([
5
+ 'async',
6
+ 'autofocus',
7
+ 'autoplay',
8
+ 'checked',
9
+ 'controls',
10
+ 'default',
11
+ 'defer',
12
+ 'disabled',
13
+ 'formnovalidate',
14
+ 'hidden',
15
+ 'inert',
16
+ 'ismap',
17
+ 'itemscope',
18
+ 'loop',
19
+ 'multiple',
20
+ 'muted',
21
+ 'nomodule',
22
+ 'novalidate',
23
+ 'open',
24
+ 'playsinline',
25
+ 'readonly',
26
+ 'required',
27
+ 'reversed',
28
+ 'selected',
29
+ ]);
30
+
31
+ const onPrefix = /^on/i;
32
+ const sourcePrefix = /^(href|src|xlink:href)$/i;
33
+ const valuePrefix = /(data:text\/html|javascript:)/i;
34
+
35
+ /**
36
+ * Is the attribute considered bad and potentially harmful?
37
+ */
38
+ export function isBadAttribute(name: string, value: string): boolean {
39
+ return (
40
+ onPrefix.test(name) || (sourcePrefix.test(name) && valuePrefix.test(value))
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Is the attribute a boolean attribute?
46
+ */
47
+ export function isBooleanAttribute(name: string): boolean {
48
+ return booleanAttributes.includes(name.toLowerCase());
49
+ }
50
+
51
+ /**
52
+ * Is the attribute empty and not a boolean attribute?
53
+ */
54
+ export function isEmptyNonBooleanAttribute(
55
+ name: string,
56
+ value: string,
57
+ ): boolean {
58
+ return !booleanAttributes.includes(name) && value.trim().length === 0;
59
+ }
60
+
61
+ /**
62
+ * - Is the attribute an invalid boolean attribute?
63
+ * - I.e., its value is not empty or the same as its name?
64
+ */
65
+ export function isInvalidBooleanAttribute(
66
+ name: string,
67
+ value: string,
68
+ ): boolean {
69
+ if (!booleanAttributes.includes(name)) {
70
+ return true;
71
+ }
72
+
73
+ const normalised = value.toLowerCase().trim();
74
+
75
+ return !(
76
+ normalised.length === 0 ||
77
+ normalised === name ||
78
+ (name === 'hidden' && normalised === 'until-found')
79
+ );
80
+ }
81
+
82
+ /**
83
+ * - Sets an attribute for an element
84
+ * - If the value is nullable, the attribute is removed
85
+ */
86
+ export function setAttribute(
87
+ element: Element,
88
+ name: string,
89
+ value: unknown,
90
+ ): void {
91
+ if (value == null) {
92
+ element.removeAttribute(name);
93
+ } else {
94
+ element.setAttribute(
95
+ name,
96
+ typeof value === 'string' ? value : JSON.stringify(value),
97
+ );
98
+ }
99
+ }
@@ -41,6 +41,7 @@ export function getTextDirection(element: Element): TextDirection {
41
41
  ) as TextDirection;
42
42
  }
43
43
 
44
+ export * from './attribute';
44
45
  export * from './closest';
45
46
  export * from './data';
46
47
  export * from './find';