@oscarpalmer/toretto 0.30.0 → 0.31.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/attribute/index.js +5 -5
- package/dist/event/delegation.js +10 -10
- package/dist/find/index.js +1 -1
- package/dist/find/relative.js +13 -14
- package/dist/html/index.js +11 -8
- package/dist/html/sanitize.js +29 -14
- package/dist/internal/attribute.js +5 -5
- package/dist/is.js +3 -3
- package/dist/style.js +2 -2
- package/dist/toretto.full.js +102 -236
- package/package.json +6 -6
- package/src/attribute/get.ts +4 -12
- package/src/attribute/index.ts +5 -5
- package/src/attribute/set.ts +13 -13
- package/src/data.ts +7 -16
- package/src/event/delegation.ts +10 -10
- package/src/find/index.ts +1 -1
- package/src/find/relative.ts +19 -23
- package/src/html/index.ts +13 -12
- package/src/html/sanitize.ts +58 -31
- package/src/internal/attribute.ts +9 -9
- package/src/internal/element-value.ts +3 -4
- package/src/internal/get-value.ts +5 -8
- package/src/internal/is.ts +1 -3
- package/src/is.ts +4 -4
- package/src/models.ts +0 -5
- package/src/style.ts +12 -15
- package/types/attribute/get.d.ts +3 -3
- package/types/attribute/set.d.ts +9 -9
- package/types/data.d.ts +4 -5
- package/types/internal/attribute.d.ts +7 -7
- package/types/internal/element-value.d.ts +2 -3
- package/types/internal/get-value.d.ts +2 -3
- package/types/internal/is.d.ts +1 -2
- package/types/models.d.ts +0 -4
- package/types/style.d.ts +7 -7
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@types/node": "^25",
|
|
12
12
|
"@vitest/coverage-istanbul": "^4",
|
|
13
13
|
"jsdom": "^27.3",
|
|
14
|
-
"oxfmt": "^0.
|
|
15
|
-
"oxlint": "^1.
|
|
14
|
+
"oxfmt": "^0.19",
|
|
15
|
+
"oxlint": "^1.34",
|
|
16
16
|
"rolldown": "1.0.0-beta.55",
|
|
17
17
|
"tslib": "^2.8",
|
|
18
18
|
"typescript": "^5.9",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"default": "./dist/focusable.js"
|
|
47
47
|
},
|
|
48
48
|
"./html": {
|
|
49
|
-
"types": "./types/html.d.ts",
|
|
50
|
-
"default": "./dist/html.js"
|
|
49
|
+
"types": "./types/html/index.d.ts",
|
|
50
|
+
"default": "./dist/html/index.js"
|
|
51
51
|
},
|
|
52
52
|
"./is": {
|
|
53
53
|
"types": "./types/is.d.ts",
|
|
@@ -87,11 +87,11 @@
|
|
|
87
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
89
|
"rolldown:build": "npx rolldown -c",
|
|
90
|
-
"rolldown:watch": "npx rolldown -c --watch",
|
|
90
|
+
"rolldown:watch": "npx rolldown -c ./rolldown.config.js --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.
|
|
96
|
+
"version": "0.31.0"
|
|
97
97
|
}
|
package/src/attribute/get.ts
CHANGED
|
@@ -8,11 +8,7 @@ import {isHTMLOrSVGElement} from '../internal/is';
|
|
|
8
8
|
* @param parse Parse value? _(defaults to `true`)_
|
|
9
9
|
* @returns Attribute value _(or `undefined`)_
|
|
10
10
|
*/
|
|
11
|
-
export function getAttribute(
|
|
12
|
-
element: HTMLOrSVGElement,
|
|
13
|
-
name: `data-${string}`,
|
|
14
|
-
parse?: boolean,
|
|
15
|
-
): unknown;
|
|
11
|
+
export function getAttribute(element: Element, name: `data-${string}`, parse?: boolean): unknown;
|
|
16
12
|
|
|
17
13
|
/**
|
|
18
14
|
* Get the value of a specific attribute from an element
|
|
@@ -20,13 +16,9 @@ export function getAttribute(
|
|
|
20
16
|
* @param name Attribute name
|
|
21
17
|
* @returns Attribute value _(or `undefined`)_
|
|
22
18
|
*/
|
|
23
|
-
export function getAttribute(element:
|
|
19
|
+
export function getAttribute(element: Element, name: string): unknown;
|
|
24
20
|
|
|
25
|
-
export function getAttribute(
|
|
26
|
-
element: HTMLOrSVGElement,
|
|
27
|
-
name: string,
|
|
28
|
-
parseValues?: boolean,
|
|
29
|
-
): unknown {
|
|
21
|
+
export function getAttribute(element: Element, name: string, parseValues?: boolean): unknown {
|
|
30
22
|
if (isHTMLOrSVGElement(element) && typeof name === 'string') {
|
|
31
23
|
return getAttributeValue(element, name, parseValues !== false);
|
|
32
24
|
}
|
|
@@ -40,7 +32,7 @@ export function getAttribute(
|
|
|
40
32
|
* @returns Object of named attributes
|
|
41
33
|
*/
|
|
42
34
|
export function getAttributes<Key extends string>(
|
|
43
|
-
element:
|
|
35
|
+
element: Element,
|
|
44
36
|
names: Key[],
|
|
45
37
|
parseData?: boolean,
|
|
46
38
|
): Record<Key, unknown> {
|
package/src/attribute/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
_isBadAttribute,
|
|
3
|
+
_isBooleanAttribute,
|
|
4
|
+
_isEmptyNonBooleanAttribute,
|
|
5
|
+
_isInvalidBooleanAttribute,
|
|
6
6
|
} from '../internal/attribute';
|
|
7
7
|
import type {Attribute} from '../models';
|
|
8
8
|
|
|
@@ -22,7 +22,7 @@ export function isBadAttribute(attribute: Attr | Attribute): boolean;
|
|
|
22
22
|
export function isBadAttribute(name: string, value: string): boolean;
|
|
23
23
|
|
|
24
24
|
export function isBadAttribute(first: unknown, second?: unknown): boolean {
|
|
25
|
-
return
|
|
25
|
+
return _isBadAttribute(first, second, true);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
package/src/attribute/set.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {updateValue, updateValues} from '../internal/attribute';
|
|
2
|
-
import type {Attribute,
|
|
2
|
+
import type {Attribute, Property} from '../models';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Set an attribute on an element
|
|
@@ -9,7 +9,7 @@ import type {Attribute, HTMLOrSVGElement, Property} from '../models';
|
|
|
9
9
|
* @param name Attribute name
|
|
10
10
|
* @param value Attribute value
|
|
11
11
|
*/
|
|
12
|
-
export function setAttribute(element:
|
|
12
|
+
export function setAttribute(element: Element, name: string, value?: unknown): void;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Set an attribute on an element
|
|
@@ -18,9 +18,9 @@ export function setAttribute(element: HTMLOrSVGElement, name: string, value?: un
|
|
|
18
18
|
* @param element Element for attribute
|
|
19
19
|
* @param attribute Attribute to set
|
|
20
20
|
*/
|
|
21
|
-
export function setAttribute(element:
|
|
21
|
+
export function setAttribute(element: Element, attribute: Attr | Attribute): void;
|
|
22
22
|
|
|
23
|
-
export function setAttribute(element:
|
|
23
|
+
export function setAttribute(element: Element, first: unknown, second?: unknown): void {
|
|
24
24
|
updateValue(element, first, second);
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ export function setAttribute(element: HTMLOrSVGElement, first: unknown, second?:
|
|
|
31
31
|
* @param element Element for attributes
|
|
32
32
|
* @param attributes Attributes to set
|
|
33
33
|
*/
|
|
34
|
-
export function setAttributes(element:
|
|
34
|
+
export function setAttributes(element: Element, attributes: Array<Attr | Attribute>): void;
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Set one or more attributes on an element
|
|
@@ -40,10 +40,10 @@ export function setAttributes(element: HTMLOrSVGElement, attributes: Array<Attr
|
|
|
40
40
|
* @param element Element for attributes
|
|
41
41
|
* @param attributes Attributes to set
|
|
42
42
|
*/
|
|
43
|
-
export function setAttributes(element:
|
|
43
|
+
export function setAttributes(element: Element, attributes: Record<string, unknown>): void;
|
|
44
44
|
|
|
45
45
|
export function setAttributes(
|
|
46
|
-
element:
|
|
46
|
+
element: Element,
|
|
47
47
|
attributes: Attribute[] | Record<string, unknown>,
|
|
48
48
|
): void {
|
|
49
49
|
updateValues(element, attributes);
|
|
@@ -57,7 +57,7 @@ export function setAttributes(
|
|
|
57
57
|
* @param name Property name
|
|
58
58
|
* @param value Property value
|
|
59
59
|
*/
|
|
60
|
-
export function setProperty(element:
|
|
60
|
+
export function setProperty(element: Element, name: string, value: boolean | string): void;
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Set a property on an element
|
|
@@ -66,9 +66,9 @@ export function setProperty(element: HTMLOrSVGElement, name: string, value: bool
|
|
|
66
66
|
* @param element Element for property
|
|
67
67
|
* @param property Property to set
|
|
68
68
|
*/
|
|
69
|
-
export function setProperty(element:
|
|
69
|
+
export function setProperty(element: Element, property: Property): void;
|
|
70
70
|
|
|
71
|
-
export function setProperty(element:
|
|
71
|
+
export function setProperty(element: Element, first: unknown, second?: unknown): void {
|
|
72
72
|
updateValue(element, first, second);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -79,7 +79,7 @@ export function setProperty(element: HTMLOrSVGElement, first: unknown, second?:
|
|
|
79
79
|
* @param element Element for properties
|
|
80
80
|
* @param properties Properties to set
|
|
81
81
|
*/
|
|
82
|
-
export function setProperties(element:
|
|
82
|
+
export function setProperties(element: Element, properties: Property[]): void;
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Set one or more properties on an element
|
|
@@ -88,10 +88,10 @@ export function setProperties(element: HTMLOrSVGElement, properties: Property[])
|
|
|
88
88
|
* @param element Element for properties
|
|
89
89
|
* @param properties Properties to set
|
|
90
90
|
*/
|
|
91
|
-
export function setProperties(element:
|
|
91
|
+
export function setProperties(element: Element, properties: Record<string, unknown>): void;
|
|
92
92
|
|
|
93
93
|
export function setProperties(
|
|
94
|
-
element:
|
|
94
|
+
element: Element,
|
|
95
95
|
properties: Property[] | Record<string, unknown>,
|
|
96
96
|
): void {
|
|
97
97
|
updateValues(element, properties);
|
package/src/data.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {kebabCase, parse} from '@oscarpalmer/atoms/string';
|
|
|
3
3
|
import {setElementValues, updateElementValue} from './internal/element-value';
|
|
4
4
|
import {EXPRESSION_DATA_PREFIX} from './internal/get-value';
|
|
5
5
|
import {isHTMLOrSVGElement} from './internal/is';
|
|
6
|
-
import type {HTMLOrSVGElement} from './models';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Get a keyed data value from an element
|
|
@@ -12,7 +11,7 @@ import type {HTMLOrSVGElement} from './models';
|
|
|
12
11
|
* @param parse Parse values? _(defaults to `true`)_
|
|
13
12
|
* @returns Data value
|
|
14
13
|
*/
|
|
15
|
-
export function getData(element:
|
|
14
|
+
export function getData(element: Element, key: string, parse?: boolean): unknown;
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Get keyed data values from an element
|
|
@@ -22,16 +21,12 @@ export function getData(element: HTMLOrSVGElement, key: string, parse?: boolean)
|
|
|
22
21
|
* @returns Keyed data values
|
|
23
22
|
*/
|
|
24
23
|
export function getData<Key extends string>(
|
|
25
|
-
element:
|
|
24
|
+
element: Element,
|
|
26
25
|
keys: Key[],
|
|
27
26
|
parse?: boolean,
|
|
28
27
|
): Record<Key, unknown>;
|
|
29
28
|
|
|
30
|
-
export function getData(
|
|
31
|
-
element: HTMLOrSVGElement,
|
|
32
|
-
keys: string | string[],
|
|
33
|
-
parseValues?: boolean,
|
|
34
|
-
): unknown {
|
|
29
|
+
export function getData(element: Element, keys: string | string[], parseValues?: boolean): unknown {
|
|
35
30
|
if (!isHTMLOrSVGElement(element)) {
|
|
36
31
|
return;
|
|
37
32
|
}
|
|
@@ -75,7 +70,7 @@ function getName(original: string): string {
|
|
|
75
70
|
* @param element Element to set data on
|
|
76
71
|
* @param data Data to set
|
|
77
72
|
*/
|
|
78
|
-
export function setData(element:
|
|
73
|
+
export function setData(element: Element, data: PlainObject): void;
|
|
79
74
|
|
|
80
75
|
/**
|
|
81
76
|
* Set a data value on an element
|
|
@@ -83,17 +78,13 @@ export function setData(element: HTMLOrSVGElement, data: PlainObject): void;
|
|
|
83
78
|
* @param key Data key
|
|
84
79
|
* @param value Data value
|
|
85
80
|
*/
|
|
86
|
-
export function setData(element:
|
|
81
|
+
export function setData(element: Element, key: string, value: unknown): void;
|
|
87
82
|
|
|
88
|
-
export function setData(
|
|
89
|
-
element: HTMLOrSVGElement,
|
|
90
|
-
first: PlainObject | string,
|
|
91
|
-
second?: unknown,
|
|
92
|
-
): void {
|
|
83
|
+
export function setData(element: Element, first: PlainObject | string, second?: unknown): void {
|
|
93
84
|
setElementValues(element, first, second, updateDataAttribute);
|
|
94
85
|
}
|
|
95
86
|
|
|
96
|
-
function updateDataAttribute(element:
|
|
87
|
+
function updateDataAttribute(element: Element, key: string, value: unknown): void {
|
|
97
88
|
updateElementValue(
|
|
98
89
|
element,
|
|
99
90
|
getName(key),
|
package/src/event/delegation.ts
CHANGED
|
@@ -14,22 +14,22 @@ export type EventTargetWithListeners = EventTarget &
|
|
|
14
14
|
}>;
|
|
15
15
|
|
|
16
16
|
function addDelegatedHandler(
|
|
17
|
-
|
|
17
|
+
doc: DocumentWithListenerCounts,
|
|
18
18
|
type: string,
|
|
19
19
|
name: string,
|
|
20
20
|
passive: boolean,
|
|
21
21
|
): void {
|
|
22
22
|
const count = `${name}${COUNT_SUFFIX}`;
|
|
23
23
|
|
|
24
|
-
if (
|
|
25
|
-
(
|
|
24
|
+
if (doc[count] != null) {
|
|
25
|
+
(doc[count] as number) += 1;
|
|
26
26
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
doc[count] = 1;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
doc.addEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE, {
|
|
33
33
|
passive,
|
|
34
34
|
});
|
|
35
35
|
}
|
|
@@ -103,19 +103,19 @@ export function getDelegatedName(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function removeDelegatedHandler(
|
|
106
|
-
|
|
106
|
+
doc: DocumentWithListenerCounts,
|
|
107
107
|
type: string,
|
|
108
108
|
name: string,
|
|
109
109
|
passive: boolean,
|
|
110
110
|
): void {
|
|
111
111
|
const count = `${name}${COUNT_SUFFIX}`;
|
|
112
112
|
|
|
113
|
-
(
|
|
113
|
+
(doc[count] as number) -= 1;
|
|
114
114
|
|
|
115
|
-
if ((
|
|
116
|
-
|
|
115
|
+
if ((doc[count] as number) < 1) {
|
|
116
|
+
doc[count] = undefined;
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
doc.removeEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
package/src/find/index.ts
CHANGED
|
@@ -93,7 +93,7 @@ function findElementOrElementsFromNodes(
|
|
|
93
93
|
element != null &&
|
|
94
94
|
(context == null ||
|
|
95
95
|
contexts.length === 0 ||
|
|
96
|
-
contexts.some(
|
|
96
|
+
contexts.some(ctx => ctx === element || ctx.contains(element))) &&
|
|
97
97
|
!result.includes(element)
|
|
98
98
|
) {
|
|
99
99
|
result.push(element);
|
package/src/find/relative.ts
CHANGED
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
* - If the distance cannot be calculated, `-1` is returned
|
|
4
4
|
*/
|
|
5
5
|
function getDistanceBetweenElements(origin: Element, target: Element): number | undefined {
|
|
6
|
-
if (origin === target
|
|
6
|
+
if (origin === target) {
|
|
7
7
|
return 0;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if (origin.parentElement === target) {
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
11
14
|
const children = [...(origin.parentElement?.children ?? [])];
|
|
12
15
|
|
|
13
16
|
if (children.includes(target)) {
|
|
14
17
|
return Math.abs(children.indexOf(origin) - children.indexOf(target));
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
const
|
|
20
|
+
const comparison = origin.compareDocumentPosition(target);
|
|
21
|
+
const beforeOrInside = Boolean(comparison & 2 || comparison & 8);
|
|
18
22
|
|
|
19
|
-
if (beforeOrInside ||
|
|
23
|
+
if (beforeOrInside || Boolean(comparison & 4 || comparison & 16)) {
|
|
20
24
|
return traverse(beforeOrInside ? origin : target, beforeOrInside ? target : origin) ?? -1;
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -86,10 +90,6 @@ export function findRelatives(
|
|
|
86
90
|
return [];
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
if (origin.matches(selector)) {
|
|
90
|
-
return [origin];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
93
|
const elements = [
|
|
94
94
|
...(context instanceof Document || context instanceof Element
|
|
95
95
|
? context
|
|
@@ -99,12 +99,8 @@ export function findRelatives(
|
|
|
99
99
|
|
|
100
100
|
const {length} = elements;
|
|
101
101
|
|
|
102
|
-
if (length
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (length === 1) {
|
|
107
|
-
return [elements[0]];
|
|
102
|
+
if (length < 2) {
|
|
103
|
+
return elements.filter(element => element !== origin);
|
|
108
104
|
}
|
|
109
105
|
|
|
110
106
|
const distances = [];
|
|
@@ -113,9 +109,9 @@ export function findRelatives(
|
|
|
113
109
|
|
|
114
110
|
for (let index = 0; index < length; index += 1) {
|
|
115
111
|
const element = elements[index];
|
|
116
|
-
const distance = getDistanceBetweenElements(origin, element)
|
|
112
|
+
const distance = getDistanceBetweenElements(origin, element);
|
|
117
113
|
|
|
118
|
-
if (distance >
|
|
114
|
+
if (distance != null && distance > 0) {
|
|
119
115
|
if (minimum == null || distance < minimum) {
|
|
120
116
|
minimum = distance;
|
|
121
117
|
}
|
|
@@ -127,16 +123,16 @@ export function findRelatives(
|
|
|
127
123
|
}
|
|
128
124
|
}
|
|
129
125
|
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
return distances
|
|
127
|
+
.filter(found => found.distance === minimum && found.element !== origin)
|
|
128
|
+
.map(found => found.element);
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
function traverse(from: Element, to: Element): number | undefined {
|
|
136
132
|
let index = [...to.children].indexOf(from);
|
|
137
133
|
|
|
138
134
|
if (index > -1) {
|
|
139
|
-
return
|
|
135
|
+
return 1;
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
let current = from;
|
|
@@ -148,7 +144,7 @@ function traverse(from: Element, to: Element): number | undefined {
|
|
|
148
144
|
return distance + 1;
|
|
149
145
|
}
|
|
150
146
|
|
|
151
|
-
const children = [...
|
|
147
|
+
const children = [...parent.children];
|
|
152
148
|
|
|
153
149
|
if (children.includes(to)) {
|
|
154
150
|
return distance + Math.abs(children.indexOf(current) - children.indexOf(to));
|
|
@@ -157,9 +153,9 @@ function traverse(from: Element, to: Element): number | undefined {
|
|
|
157
153
|
index = children.findIndex(child => child.contains(to));
|
|
158
154
|
|
|
159
155
|
if (index > -1) {
|
|
160
|
-
const traversed = traverse(current, children[index])
|
|
156
|
+
const traversed = traverse(current, children[index]);
|
|
161
157
|
|
|
162
|
-
return traversed === -1
|
|
158
|
+
return traversed == null || traversed === -1
|
|
163
159
|
? -1
|
|
164
160
|
: distance + Math.abs(index - children.indexOf(current)) + traversed;
|
|
165
161
|
}
|
package/src/html/index.ts
CHANGED
|
@@ -44,16 +44,13 @@ type Options = Required<HtmlOptions>;
|
|
|
44
44
|
//
|
|
45
45
|
|
|
46
46
|
function createHtml(value: string | HTMLTemplateElement): string {
|
|
47
|
-
const
|
|
48
|
-
typeof value === 'string' ? value : value.innerHTML,
|
|
49
|
-
HTML_PARSE_TYPE,
|
|
50
|
-
);
|
|
47
|
+
const parsed = getParser().parseFromString(getHtml(value), PARSE_TYPE_HTML);
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
parsed.body.normalize();
|
|
53
50
|
|
|
54
|
-
sanitizeNodes([
|
|
51
|
+
sanitizeNodes([parsed.body], 0);
|
|
55
52
|
|
|
56
|
-
return
|
|
53
|
+
return parsed.body.innerHTML;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
function createTemplate(
|
|
@@ -71,6 +68,10 @@ function createTemplate(
|
|
|
71
68
|
return template;
|
|
72
69
|
}
|
|
73
70
|
|
|
71
|
+
function getHtml(value: string | HTMLTemplateElement): string {
|
|
72
|
+
return `${TEMPORARY_ELEMENT}${typeof value === 'string' ? value : value.innerHTML}${TEMPORARY_ELEMENT}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
74
75
|
function getNodes(value: string | HTMLTemplateElement, options: Options): Node[] {
|
|
75
76
|
if (typeof value !== 'string' && !(value instanceof HTMLTemplateElement)) {
|
|
76
77
|
return [];
|
|
@@ -103,7 +104,7 @@ function getTemplate(
|
|
|
103
104
|
return createTemplate(value, options);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
if (
|
|
107
|
+
if (value.trim().length === 0) {
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -115,9 +116,7 @@ function getTemplate(
|
|
|
115
116
|
|
|
116
117
|
const element = EXPRESSION_ID.test(value) ? document.querySelector(`#${value}`) : null;
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return template;
|
|
119
|
+
return createTemplate(element instanceof HTMLTemplateElement ? element : value, options);
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
const html = ((value: string | HTMLTemplateElement, options?: Options): Node[] => {
|
|
@@ -163,10 +162,12 @@ export function sanitize(value: Node | Node[]): Node[] {
|
|
|
163
162
|
|
|
164
163
|
const EXPRESSION_ID = /^[a-z][\w-]*$/i;
|
|
165
164
|
|
|
166
|
-
const
|
|
165
|
+
const PARSE_TYPE_HTML = 'text/html';
|
|
167
166
|
|
|
168
167
|
const TEMPLATE_TAG = 'template';
|
|
169
168
|
|
|
169
|
+
const TEMPORARY_ELEMENT = '<toretto-temporary></toretto-temporary>';
|
|
170
|
+
|
|
170
171
|
let parser: DOMParser;
|
|
171
172
|
|
|
172
173
|
let templates: Record<string, HTMLTemplateElement> = {};
|
package/src/html/sanitize.ts
CHANGED
|
@@ -1,27 +1,20 @@
|
|
|
1
|
+
import {setAttribute} from '../attribute/set';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
_isBadAttribute,
|
|
4
|
+
_isEmptyNonBooleanAttribute,
|
|
5
|
+
_isInvalidBooleanAttribute,
|
|
5
6
|
} from '../internal/attribute';
|
|
6
7
|
|
|
7
|
-
function handleElement(element: Element, depth: number):
|
|
8
|
-
if (isClobbered(element)) {
|
|
9
|
-
element.remove();
|
|
10
|
-
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
8
|
+
function handleElement(element: Element, depth: number): void {
|
|
14
9
|
if (depth === 0) {
|
|
15
|
-
const
|
|
10
|
+
const removable = element.querySelectorAll(REMOVE_SELECTOR);
|
|
16
11
|
|
|
17
|
-
for (const
|
|
18
|
-
|
|
12
|
+
for (const item of removable) {
|
|
13
|
+
item.remove();
|
|
19
14
|
}
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
sanitizeAttributes(element, [...element.attributes]);
|
|
23
|
-
|
|
24
|
-
return false;
|
|
25
18
|
}
|
|
26
19
|
|
|
27
20
|
/**
|
|
@@ -29,47 +22,75 @@ function handleElement(element: Element, depth: number): boolean {
|
|
|
29
22
|
*
|
|
30
23
|
* Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
|
|
31
24
|
*/
|
|
32
|
-
function isClobbered(
|
|
25
|
+
function isClobbered(value: unknown): boolean {
|
|
33
26
|
return (
|
|
34
|
-
|
|
35
|
-
(typeof
|
|
36
|
-
typeof
|
|
37
|
-
typeof
|
|
38
|
-
!(
|
|
39
|
-
typeof
|
|
40
|
-
typeof
|
|
41
|
-
typeof
|
|
42
|
-
typeof
|
|
43
|
-
typeof
|
|
27
|
+
value instanceof HTMLFormElement &&
|
|
28
|
+
(typeof value.nodeName !== 'string' ||
|
|
29
|
+
typeof value.textContent !== 'string' ||
|
|
30
|
+
typeof value.removeChild !== 'function' ||
|
|
31
|
+
!(value.attributes instanceof NamedNodeMap) ||
|
|
32
|
+
typeof value.removeAttribute !== 'function' ||
|
|
33
|
+
typeof value.setAttribute !== 'function' ||
|
|
34
|
+
typeof value.namespaceURI !== 'string' ||
|
|
35
|
+
typeof value.insertBefore !== 'function' ||
|
|
36
|
+
typeof value.hasChildNodes !== 'function')
|
|
44
37
|
);
|
|
45
38
|
}
|
|
46
39
|
|
|
40
|
+
function removeNode(node: Node): void {
|
|
41
|
+
if (typeof (node as ChildNode).remove === 'function') {
|
|
42
|
+
(node as ChildNode).remove();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
47
46
|
export function sanitizeAttributes(element: Element, attributes: Attr[]): void {
|
|
48
47
|
const {length} = attributes;
|
|
49
48
|
|
|
50
49
|
for (let index = 0; index < length; index += 1) {
|
|
51
50
|
const {name, value} = attributes[index];
|
|
52
51
|
|
|
53
|
-
if (
|
|
52
|
+
if (_isBadAttribute(name, value, false) || _isEmptyNonBooleanAttribute(name, value, false)) {
|
|
54
53
|
element.removeAttribute(name);
|
|
55
|
-
} else if (
|
|
56
|
-
|
|
54
|
+
} else if (_isInvalidBooleanAttribute(name, value, false)) {
|
|
55
|
+
setAttribute(element, name, true);
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
export function sanitizeNodes(nodes: Node[], depth: number): Node[] {
|
|
62
61
|
const actual = nodes.filter(node => node instanceof Node);
|
|
62
|
+
|
|
63
63
|
let {length} = nodes;
|
|
64
64
|
|
|
65
65
|
for (let index = 0; index < length; index += 1) {
|
|
66
66
|
const node = actual[index];
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
let remove = isClobbered(node);
|
|
69
|
+
|
|
70
|
+
if (!remove) {
|
|
71
|
+
switch (node.nodeType) {
|
|
72
|
+
case Node.ELEMENT_NODE:
|
|
73
|
+
handleElement(node as Element, depth);
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case Node.COMMENT_NODE:
|
|
77
|
+
remove = COMMENT_HARMFUL.test((node as Comment).data);
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case Node.DOCUMENT_TYPE_NODE:
|
|
81
|
+
case Node.PROCESSING_INSTRUCTION_NODE:
|
|
82
|
+
remove = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (remove) {
|
|
88
|
+
removeNode(node);
|
|
89
|
+
|
|
69
90
|
actual.splice(index, 1);
|
|
70
91
|
|
|
71
|
-
length -= 1;
|
|
72
92
|
index -= 1;
|
|
93
|
+
length -= 1;
|
|
73
94
|
|
|
74
95
|
continue;
|
|
75
96
|
}
|
|
@@ -81,3 +102,9 @@ export function sanitizeNodes(nodes: Node[], depth: number): Node[] {
|
|
|
81
102
|
|
|
82
103
|
return nodes;
|
|
83
104
|
}
|
|
105
|
+
|
|
106
|
+
//
|
|
107
|
+
|
|
108
|
+
const COMMENT_HARMFUL = /<[/\w]/g;
|
|
109
|
+
|
|
110
|
+
const REMOVE_SELECTOR = 'script, toretto-temporary';
|