@oscarpalmer/toretto 0.28.0 → 0.30.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/package.json CHANGED
@@ -8,17 +8,15 @@
8
8
  },
9
9
  "description": "A collection of badass DOM utilities.",
10
10
  "devDependencies": {
11
- "@rollup/plugin-node-resolve": "^16",
12
- "@rollup/plugin-typescript": "^12.3",
13
- "@types/node": "^24.10",
11
+ "@types/node": "^25",
14
12
  "@vitest/coverage-istanbul": "^4",
15
- "jsdom": "^27.2",
16
- "oxfmt": "^0.16",
17
- "oxlint": "^1.31",
18
- "rollup": "^4.53",
13
+ "jsdom": "^27.3",
14
+ "oxfmt": "^0.18",
15
+ "oxlint": "^1.33",
16
+ "rolldown": "1.0.0-beta.55",
19
17
  "tslib": "^2.8",
20
18
  "typescript": "^5.9",
21
- "vite": "8.0.0-beta.0",
19
+ "vite": "8.0.0-beta.2",
22
20
  "vitest": "^4"
23
21
  },
24
22
  "exports": {
@@ -74,6 +72,8 @@
74
72
  ],
75
73
  "keywords": [
76
74
  "dom",
75
+ "html",
76
+ "sanitization",
77
77
  "utility"
78
78
  ],
79
79
  "license": "MIT",
@@ -84,14 +84,14 @@
84
84
  "url": "git+https://github.com/oscarpalmer/toretto.git"
85
85
  },
86
86
  "scripts": {
87
- "build": "npm run clean && npx vite build && npm run rollup:build && npx tsc",
87
+ "build": "npm run clean && npx vite build && npm run rolldown:build && npx tsc",
88
88
  "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
89
- "rollup:build": "npx rollup -c",
90
- "rollup:watch": "npx rollup -c --watch",
89
+ "rolldown:build": "npx rolldown -c",
90
+ "rolldown:watch": "npx rolldown -c --watch",
91
91
  "test": "npx vitest --coverage",
92
92
  "watch": "npx vite build --watch"
93
93
  },
94
94
  "type": "module",
95
95
  "types": "types/index.d.ts",
96
- "version": "0.28.0"
96
+ "version": "0.30.0"
97
97
  }
@@ -1,9 +1,90 @@
1
- export {
2
- booleanAttributes,
3
- isBadAttribute,
4
- isBooleanAttribute,
5
- isEmptyNonBooleanAttribute,
6
- isInvalidBooleanAttribute,
1
+ import {
2
+ isBadAttribute as internalIsBadAttribute,
3
+ isBooleanAttribute as _isBooleanAttribute,
4
+ isEmptyNonBooleanAttribute as _isEmptyNonBooleanAttribute,
5
+ isInvalidBooleanAttribute as _isInvalidBooleanAttribute,
7
6
  } from '../internal/attribute';
7
+ import type {Attribute} from '../models';
8
+
9
+ /**
10
+ * Is the attribute considered bad and potentially harmful?
11
+ * @param attribute Attribute to check
12
+ * @returns `true` if attribute is considered bad
13
+ */
14
+ export function isBadAttribute(attribute: Attr | Attribute): boolean;
15
+
16
+ /**
17
+ * Is the attribute considered bad and potentially harmful?
18
+ * @param name Attribute name
19
+ * @param value Attribute value
20
+ * @returns `true` if attribute is considered bad
21
+ */
22
+ export function isBadAttribute(name: string, value: string): boolean;
23
+
24
+ export function isBadAttribute(first: unknown, second?: unknown): boolean {
25
+ return internalIsBadAttribute(first, second, true);
26
+ }
27
+
28
+ /**
29
+ * Is the attribute a boolean attribute?
30
+ * @param name Attribute to check
31
+ * @returns `true` if attribute is a boolean attribute
32
+ */
33
+ export function isBooleanAttribute(attribute: Attr | Attribute): boolean;
34
+
35
+ /**
36
+ * Is the attribute a boolean attribute?
37
+ * @param name Attribute name
38
+ * @returns `true` if attribute is a boolean attribute
39
+ */
40
+ export function isBooleanAttribute(name: string): boolean;
41
+
42
+ export function isBooleanAttribute(first: unknown): boolean {
43
+ return _isBooleanAttribute(first, true);
44
+ }
45
+
46
+ /**
47
+ * Is the attribute empty and not a boolean attribute?
48
+ * @param attribute Attribute to check
49
+ * @returns `true` if attribute is empty and not a boolean attribute
50
+ */
51
+ export function isEmptyNonBooleanAttribute(attribute: Attr | Attribute): boolean;
52
+
53
+ /**
54
+ * Is the attribute empty and not a boolean attribute?
55
+ * @param name Attribute name
56
+ * @param value Attribute value
57
+ * @returns `true` if attribute is empty and not a boolean attribute
58
+ */
59
+ export function isEmptyNonBooleanAttribute(name: string, value: string): boolean;
60
+
61
+ export function isEmptyNonBooleanAttribute(first: unknown, second?: unknown): boolean {
62
+ return _isEmptyNonBooleanAttribute(first, second, true);
63
+ }
64
+
65
+ /**
66
+ * Is the attribute an invalid boolean attribute?
67
+ *
68
+ * _(I.e., its value is not empty or the same as its name)_
69
+ * @param attribute Attribute to check
70
+ * @returns `true` if attribute is an invalid boolean attribute
71
+ */
72
+ export function isInvalidBooleanAttribute(attribute: Attr | Attribute): boolean;
73
+
74
+ /**
75
+ * Is the attribute an invalid boolean attribute?
76
+ *
77
+ * _(I.e., its value is not empty or the same as its name)_
78
+ * @param name Attribute name
79
+ * @param value Attribute value
80
+ * @returns `true` if attribute is an invalid boolean attribute
81
+ */
82
+ export function isInvalidBooleanAttribute(name: string, value: string): boolean;
83
+
84
+ export function isInvalidBooleanAttribute(first: unknown, second?: unknown): boolean {
85
+ return _isInvalidBooleanAttribute(first, second, true);
86
+ }
87
+
88
+ export {booleanAttributes} from '../internal/attribute';
8
89
  export * from './get';
9
90
  export * from './set';
@@ -1,5 +1,5 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
- import {getSanitizeOptions, type SanitizeOptions, sanitizeNodes} from './internal/sanitize';
2
+ import {sanitizeNodes} from './sanitize';
3
3
 
4
4
  //
5
5
 
@@ -34,66 +34,75 @@ type Html = {
34
34
 
35
35
  type HtmlOptions = {
36
36
  /**
37
- * Ignore caching the template element for the HTML string? _(defaults to `false`)_
37
+ * Cache template element for the HTML string? _(defaults to `true`)_
38
38
  */
39
- ignoreCache?: boolean;
40
- } & SanitizeOptions;
39
+ cache?: boolean;
40
+ };
41
41
 
42
42
  type Options = Required<HtmlOptions>;
43
43
 
44
44
  //
45
45
 
46
- function createTemplate(html: string, ignore: boolean): HTMLTemplateElement {
47
- const template = document.createElement('template');
46
+ function createHtml(value: string | HTMLTemplateElement): string {
47
+ const html = getParser().parseFromString(
48
+ typeof value === 'string' ? value : value.innerHTML,
49
+ HTML_PARSE_TYPE,
50
+ );
48
51
 
49
- template.innerHTML = html;
52
+ html.body.normalize();
50
53
 
51
- if (!ignore) {
52
- templates[html] = template;
53
- }
54
+ sanitizeNodes([html.body], 0);
54
55
 
55
- return template;
56
+ return html.body.innerHTML;
56
57
  }
57
58
 
58
- function getHtml(value: string | HTMLTemplateElement, options: Options): Node[] {
59
- if (typeof value !== 'string' && !(value instanceof HTMLTemplateElement)) {
60
- return [];
61
- }
59
+ function createTemplate(
60
+ value: string | HTMLTemplateElement,
61
+ options: Options,
62
+ ): HTMLTemplateElement {
63
+ const template = document.createElement(TEMPLATE_TAG);
62
64
 
63
- const template =
64
- value instanceof HTMLTemplateElement ? value : getTemplate(value, options.ignoreCache);
65
+ template.innerHTML = createHtml(value);
65
66
 
66
- if (template == null) {
67
- return [];
67
+ if (typeof value === 'string' && options.cache) {
68
+ templates[value] = template;
68
69
  }
69
70
 
70
- const cloned = template.content.cloneNode(true) as DocumentFragment;
71
-
72
- const scripts = cloned.querySelectorAll('script');
71
+ return template;
72
+ }
73
73
 
74
- for (const script of scripts) {
75
- script.remove();
74
+ function getNodes(value: string | HTMLTemplateElement, options: Options): Node[] {
75
+ if (typeof value !== 'string' && !(value instanceof HTMLTemplateElement)) {
76
+ return [];
76
77
  }
77
78
 
78
- cloned.normalize();
79
+ const template = getTemplate(value, options);
79
80
 
80
- return sanitizeNodes([...cloned.childNodes], options);
81
+ return template == null ? [] : [...template.content.cloneNode(true).childNodes];
81
82
  }
82
83
 
83
84
  function getOptions(input?: HtmlOptions): Options {
84
85
  const options = isPlainObject(input) ? input : {};
85
86
 
86
- options.ignoreCache = typeof options.ignoreCache === 'boolean' ? options.ignoreCache : false;
87
-
88
- options.sanitizeBooleanAttributes =
89
- typeof options.sanitizeBooleanAttributes === 'boolean'
90
- ? options.sanitizeBooleanAttributes
91
- : true;
87
+ options.cache = typeof options.cache === 'boolean' ? options.cache : true;
92
88
 
93
89
  return options as Options;
94
90
  }
95
91
 
96
- function getTemplate(value: string, ignore: boolean): HTMLTemplateElement | undefined {
92
+ function getParser(): DOMParser {
93
+ parser ??= new DOMParser();
94
+
95
+ return parser;
96
+ }
97
+
98
+ function getTemplate(
99
+ value: string | HTMLTemplateElement,
100
+ options: Options,
101
+ ): HTMLTemplateElement | undefined {
102
+ if (value instanceof HTMLTemplateElement) {
103
+ return createTemplate(value, options);
104
+ }
105
+
97
106
  if (typeof value !== 'string' || value.trim().length === 0) {
98
107
  return;
99
108
  }
@@ -106,13 +115,13 @@ function getTemplate(value: string, ignore: boolean): HTMLTemplateElement | unde
106
115
 
107
116
  const element = EXPRESSION_ID.test(value) ? document.querySelector(`#${value}`) : null;
108
117
 
109
- template = element instanceof HTMLTemplateElement ? element : createTemplate(value, ignore);
118
+ template = element instanceof HTMLTemplateElement ? element : createTemplate(value, options);
110
119
 
111
120
  return template;
112
121
  }
113
122
 
114
123
  const html = ((value: string | HTMLTemplateElement, options?: Options): Node[] => {
115
- return getHtml(value, getOptions(options));
124
+ return getNodes(value, getOptions(options));
116
125
  }) as Html;
117
126
 
118
127
  html.clear = (): void => {
@@ -146,14 +155,20 @@ html.remove = (template: string): void => {
146
155
  * @param options Sanitization options
147
156
  * @returns Sanitized nodes
148
157
  */
149
- export function sanitize(value: Node | Node[], options?: SanitizeOptions): Node[] {
150
- return sanitizeNodes(Array.isArray(value) ? value : [value], getSanitizeOptions(options));
158
+ export function sanitize(value: Node | Node[]): Node[] {
159
+ return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
151
160
  }
152
161
 
153
162
  //
154
163
 
155
164
  const EXPRESSION_ID = /^[a-z][\w-]*$/i;
156
165
 
166
+ const HTML_PARSE_TYPE = 'text/html';
167
+
168
+ const TEMPLATE_TAG = 'template';
169
+
170
+ let parser: DOMParser;
171
+
157
172
  let templates: Record<string, HTMLTemplateElement> = {};
158
173
 
159
174
  //
@@ -0,0 +1,83 @@
1
+ import {
2
+ isBadAttribute,
3
+ isEmptyNonBooleanAttribute,
4
+ isInvalidBooleanAttribute,
5
+ } from '../internal/attribute';
6
+
7
+ function handleElement(element: Element, depth: number): boolean {
8
+ if (isClobbered(element)) {
9
+ element.remove();
10
+
11
+ return true;
12
+ }
13
+
14
+ if (depth === 0) {
15
+ const scripts = element.querySelectorAll('script');
16
+
17
+ for (const script of scripts) {
18
+ script.remove();
19
+ }
20
+ }
21
+
22
+ sanitizeAttributes(element, [...element.attributes]);
23
+
24
+ return false;
25
+ }
26
+
27
+ /**
28
+ * Is the element clobbered?
29
+ *
30
+ * Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
31
+ */
32
+ function isClobbered(element: Element): boolean {
33
+ return (
34
+ element instanceof HTMLFormElement &&
35
+ (typeof element.nodeName !== 'string' ||
36
+ typeof element.textContent !== 'string' ||
37
+ typeof element.removeChild !== 'function' ||
38
+ !(element.attributes instanceof NamedNodeMap) ||
39
+ typeof element.removeAttribute !== 'function' ||
40
+ typeof element.setAttribute !== 'function' ||
41
+ typeof element.namespaceURI !== 'string' ||
42
+ typeof element.insertBefore !== 'function' ||
43
+ typeof element.hasChildNodes !== 'function')
44
+ );
45
+ }
46
+
47
+ export function sanitizeAttributes(element: Element, attributes: Attr[]): void {
48
+ const {length} = attributes;
49
+
50
+ for (let index = 0; index < length; index += 1) {
51
+ const {name, value} = attributes[index];
52
+
53
+ if (isBadAttribute(name, value, false) || isEmptyNonBooleanAttribute(name, value, false)) {
54
+ element.removeAttribute(name);
55
+ } else if (isInvalidBooleanAttribute(name, value, false)) {
56
+ element.setAttribute(name, '');
57
+ }
58
+ }
59
+ }
60
+
61
+ export function sanitizeNodes(nodes: Node[], depth: number): Node[] {
62
+ const actual = nodes.filter(node => node instanceof Node);
63
+ let {length} = nodes;
64
+
65
+ for (let index = 0; index < length; index += 1) {
66
+ const node = actual[index];
67
+
68
+ if (node instanceof Element && handleElement(node, depth)) {
69
+ actual.splice(index, 1);
70
+
71
+ length -= 1;
72
+ index -= 1;
73
+
74
+ continue;
75
+ }
76
+
77
+ if (node.hasChildNodes()) {
78
+ sanitizeNodes([...node.childNodes], depth + 1);
79
+ }
80
+ }
81
+
82
+ return nodes;
83
+ }
package/src/index.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import supportsTouch from './touch';
2
2
 
3
- export * from './attribute';
3
+ export {
4
+ isBadAttribute,
5
+ isBooleanAttribute,
6
+ isEmptyNonBooleanAttribute,
7
+ isInvalidBooleanAttribute,
8
+ } from './attribute';
4
9
  export * from './data';
5
10
  export * from './event/index';
6
11
  export * from './find/index';
7
12
  export * from './focusable';
8
- export * from './html';
13
+ export * from './html/index';
9
14
  export * from './is';
10
15
  export * from './models';
11
16
  export * from './style';
@@ -4,6 +4,75 @@ import {getString} from '@oscarpalmer/atoms/string';
4
4
  import type {Attribute, HTMLOrSVGElement, Property} from '../models';
5
5
  import {isHTMLOrSVGElement} from './is';
6
6
 
7
+ function badAttributeHandler(name?: string, value?: string): boolean {
8
+ if (name == null || value == null) {
9
+ return true;
10
+ }
11
+
12
+ if (
13
+ (EXPRESSION_CLOBBERED_NAME.test(name) && (value in document || value in formElement)) ||
14
+ EXPRESSION_EVENT_NAME.test(name)
15
+ ) {
16
+ return true;
17
+ }
18
+
19
+ if (
20
+ EXPRESSION_SKIP_NAME.test(name) ||
21
+ EXPRESSION_URI_VALUE.test(value) ||
22
+ isValidSourceAttribute(name, value)
23
+ ) {
24
+ return false;
25
+ }
26
+
27
+ return EXPRESSION_DATA_OR_SCRIPT.test(value);
28
+ }
29
+
30
+ function booleanAttributeHandler(name?: string, value?: string): boolean {
31
+ if (name == null || value == null) {
32
+ return true;
33
+ }
34
+
35
+ if (!booleanAttributesSet.has(name)) {
36
+ return false;
37
+ }
38
+
39
+ const normalized = value.toLowerCase().trim();
40
+
41
+ return !(normalized.length === 0 || normalized === name);
42
+ }
43
+
44
+ function decodeAttribute(value: string): string {
45
+ textArea ??= document.createElement('textarea');
46
+
47
+ textArea.innerHTML = value;
48
+
49
+ return decodeURIComponent(textArea.value);
50
+ }
51
+
52
+ function handleAttribute(
53
+ callback: (name?: string, value?: string) => boolean,
54
+ decode: boolean,
55
+ first: unknown,
56
+ second?: unknown,
57
+ ): boolean {
58
+ let name: string | undefined;
59
+ let value: string | undefined;
60
+
61
+ if (isAttribute(first)) {
62
+ name = first.name;
63
+ value = String(first.value);
64
+ } else if (typeof first === 'string' && typeof second === 'string') {
65
+ name = first;
66
+ value = second;
67
+ }
68
+
69
+ if (decode && value != null) {
70
+ value = decodeAttribute(value);
71
+ }
72
+
73
+ return callback(name, value?.replace(EXPRESSION_WHITESPACE, ''));
74
+ }
75
+
7
76
  function isAttribute(value: unknown): value is Attr | Attribute {
8
77
  return (
9
78
  value instanceof Attr ||
@@ -13,148 +82,51 @@ function isAttribute(value: unknown): value is Attr | Attribute {
13
82
  );
14
83
  }
15
84
 
16
- /**
17
- * Is the attribute considered bad and potentially harmful?
18
- * @param attribute Attribute to check
19
- * @returns `true` if attribute is considered bad
20
- */
21
- export function isBadAttribute(attribute: Attr | Attribute): boolean;
22
-
23
- /**
24
- * Is the attribute considered bad and potentially harmful?
25
- * @param name Attribute name
26
- * @param value Attribute value
27
- * @returns `true` if attribute is considered bad
28
- */
29
- export function isBadAttribute(name: string, value: string): boolean;
30
-
31
- export function isBadAttribute(first: string | Attr | Attribute, second?: string): boolean {
32
- return isValidAttribute(
33
- attribute =>
34
- attribute == null ||
35
- EXPRESSION_ON_PREFIX.test(attribute.name) ||
36
- (EXPRESSION_SOURCE_PREFIX.test(attribute.name) &&
37
- EXPRESSION_VALUE_PREFIX.test(String(attribute.value))),
38
- first,
39
- second,
40
- );
85
+ export function isBadAttribute(first: unknown, second: unknown, decode: boolean): boolean {
86
+ return handleAttribute(badAttributeHandler, decode, first, second);
41
87
  }
42
88
 
43
- /**
44
- * Is the attribute a boolean attribute?
45
- * @param name Attribute to check
46
- * @returns `true` if attribute is a boolean attribute
47
- */
48
- export function isBooleanAttribute(attribute: Attr | Attribute): boolean;
49
-
50
- /**
51
- * Is the attribute a boolean attribute?
52
- * @param name Attribute name
53
- * @returns `true` if attribute is a boolean attribute
54
- */
55
- export function isBooleanAttribute(name: string): boolean;
56
-
57
- export function isBooleanAttribute(value: string | Attr | Attribute): boolean {
58
- return isValidAttribute(
59
- attribute => attribute != null && booleanAttributes.includes(attribute.name.toLowerCase()),
60
- value,
89
+ export function isBooleanAttribute(first: unknown, decode: boolean): boolean {
90
+ return handleAttribute(
91
+ name => booleanAttributesSet.has(name?.toLowerCase() as string),
92
+ decode,
93
+ first,
61
94
  '',
62
95
  );
63
96
  }
64
97
 
65
- /**
66
- * Is the attribute empty and not a boolean attribute?
67
- * @param attribute Attribute to check
68
- * @returns `true` if attribute is empty and not a boolean attribute
69
- */
70
- export function isEmptyNonBooleanAttribute(attribute: Attr | Attribute): boolean;
71
-
72
- /**
73
- * Is the attribute empty and not a boolean attribute?
74
- * @param name Attribute name
75
- * @param value Attribute value
76
- * @returns `true` if attribute is empty and not a boolean attribute
77
- */
78
- export function isEmptyNonBooleanAttribute(name: string, value: string): boolean;
79
-
80
98
  export function isEmptyNonBooleanAttribute(
81
- first: string | Attr | Attribute,
82
- second?: string,
99
+ first: unknown,
100
+ second: unknown,
101
+ decode: boolean,
83
102
  ): boolean {
84
- return isValidAttribute(
85
- attribute =>
86
- attribute != null &&
87
- !booleanAttributes.includes(attribute.name) &&
88
- String(attribute.value).trim().length === 0,
103
+ return handleAttribute(
104
+ (name, value) =>
105
+ name != null && value != null && !booleanAttributesSet.has(name) && value.trim().length === 0,
106
+ decode,
89
107
  first,
90
108
  second,
91
109
  );
92
110
  }
93
111
 
94
- /**
95
- * Is the attribute an invalid boolean attribute?
96
- *
97
- * _(I.e., its value is not empty or the same as its name)_
98
- * @param attribute Attribute to check
99
- * @returns `true` if attribute is an invalid boolean attribute
100
- */
101
- export function isInvalidBooleanAttribute(attribute: Attr | Attribute): boolean;
102
-
103
- /**
104
- * Is the attribute an invalid boolean attribute?
105
- *
106
- * _(I.e., its value is not empty or the same as its name)_
107
- * @param name Attribute name
108
- * @param value Attribute value
109
- * @returns `true` if attribute is an invalid boolean attribute
110
- */
111
- export function isInvalidBooleanAttribute(name: string, value: string): boolean;
112
-
113
112
  export function isInvalidBooleanAttribute(
114
- first: string | Attr | Attribute,
115
- second?: string,
113
+ first: unknown,
114
+ second: unknown,
115
+ decode: boolean,
116
116
  ): boolean {
117
- return isValidAttribute(
118
- attribute => {
119
- if (attribute == null) {
120
- return true;
121
- }
122
-
123
- if (!booleanAttributes.includes(attribute.name)) {
124
- return false;
125
- }
126
-
127
- const normalized = String(attribute.value).toLowerCase().trim();
128
-
129
- return !(normalized.length === 0 || normalized === attribute.name);
130
- },
131
- first,
132
- second,
133
- );
117
+ return handleAttribute(booleanAttributeHandler, decode, first, second);
134
118
  }
135
119
 
136
120
  export function isProperty(value: unknown): value is Property {
137
121
  return isPlainObject(value) && typeof (value as PlainObject).name === 'string';
138
122
  }
139
123
 
140
- function isValidAttribute(
141
- callback: (attribute: Attr | Attribute | undefined) => boolean,
142
- first: string | Attr | Attribute,
143
- second?: string,
144
- ): boolean {
145
- let attribute: Attribute | undefined;
146
-
147
- if (isAttribute(first)) {
148
- attribute = first;
149
- } else if (typeof first === 'string' && typeof second === 'string') {
150
- attribute = {name: first, value: second};
151
- }
152
-
153
- return callback(attribute);
124
+ function isValidSourceAttribute(name: string, value: string): boolean {
125
+ return EXPRESSION_SOURCE_NAME.test(name) && EXPRESSION_SOURCE_VALUE.test(value);
154
126
  }
155
127
 
156
128
  function updateAttribute(element: HTMLOrSVGElement, name: string, value: unknown): void {
157
- const isBoolean = booleanAttributes.includes(name.toLowerCase());
129
+ const isBoolean = booleanAttributesSet.has(name.toLowerCase());
158
130
 
159
131
  if (isBoolean) {
160
132
  updateProperty(element, name, value);
@@ -211,11 +183,23 @@ export function updateValues(
211
183
 
212
184
  //
213
185
 
214
- const EXPRESSION_ON_PREFIX = /^on/i;
186
+ const EXPRESSION_CLOBBERED_NAME = /^(id|name)$/i;
187
+
188
+ const EXPRESSION_DATA_OR_SCRIPT = /^(?:data|\w+script):/i;
189
+
190
+ const EXPRESSION_EVENT_NAME = /^on/i;
191
+
192
+ const EXPRESSION_SKIP_NAME = /^(aria-[-\w]+|data-[-\w.\u00B7-\uFFFF]+)$/i;
193
+
194
+ const EXPRESSION_SOURCE_NAME = /^src$/i;
215
195
 
216
- const EXPRESSION_SOURCE_PREFIX = /^(href|src|xlink:href)$/i;
196
+ const EXPRESSION_SOURCE_VALUE = /^data:/i;
217
197
 
218
- const EXPRESSION_VALUE_PREFIX = /(data:text\/html|javascript:)/i;
198
+ const EXPRESSION_URI_VALUE =
199
+ /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
200
+
201
+ // oxlint-disable-next-line no-control-regex
202
+ const EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
219
203
 
220
204
  /**
221
205
  * List of boolean attributes
@@ -246,3 +230,9 @@ export const booleanAttributes: readonly string[] = Object.freeze([
246
230
  'reversed',
247
231
  'selected',
248
232
  ]);
233
+
234
+ const booleanAttributesSet = new Set(booleanAttributes);
235
+
236
+ const formElement = document.createElement('form');
237
+
238
+ let textArea: HTMLTextAreaElement;