@oscarpalmer/toretto 0.31.0 → 0.33.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 +2 -2
- package/dist/attribute/set.js +7 -12
- package/dist/data.js +2 -2
- package/dist/event/delegation.js +19 -26
- package/dist/event/index.js +1 -1
- package/dist/find/index.js +2 -2
- package/dist/find/relative.js +18 -17
- package/dist/index.js +3 -3
- package/dist/internal/attribute.js +29 -36
- package/dist/internal/element-value.js +26 -13
- package/dist/style.js +3 -3
- package/dist/toretto.full.js +376 -172
- package/package.json +7 -7
- package/src/attribute/set.ts +52 -56
- package/src/data.ts +2 -1
- package/src/event/delegation.ts +23 -51
- package/src/event/index.ts +1 -7
- package/src/find/index.ts +1 -1
- package/src/find/relative.ts +35 -37
- package/src/index.ts +1 -1
- package/src/internal/attribute.ts +51 -55
- package/src/internal/element-value.ts +43 -13
- package/src/models.ts +2 -7
- package/src/style.ts +3 -2
- package/types/attribute/set.d.ts +19 -37
- package/types/event/delegation.d.ts +1 -1
- package/types/find/index.d.ts +1 -1
- package/types/find/relative.d.ts +8 -2
- package/types/index.d.ts +1 -1
- package/types/internal/attribute.d.ts +3 -4
- package/types/internal/element-value.d.ts +3 -3
- package/types/models.d.ts +2 -6
package/package.json
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@oscarpalmer/atoms": "^0.
|
|
7
|
+
"@oscarpalmer/atoms": "^0.119"
|
|
8
8
|
},
|
|
9
9
|
"description": "A collection of badass DOM utilities.",
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@types/node": "^25",
|
|
12
12
|
"@vitest/coverage-istanbul": "^4",
|
|
13
|
-
"jsdom": "^27.
|
|
14
|
-
"oxfmt": "^0.
|
|
15
|
-
"oxlint": "^1.
|
|
16
|
-
"rolldown": "1.0.0-beta.
|
|
13
|
+
"jsdom": "^27.4",
|
|
14
|
+
"oxfmt": "^0.21",
|
|
15
|
+
"oxlint": "^1.36",
|
|
16
|
+
"rolldown": "1.0.0-beta.57",
|
|
17
17
|
"tslib": "^2.8",
|
|
18
18
|
"typescript": "^5.9",
|
|
19
|
-
"vite": "8.0.0-beta.
|
|
19
|
+
"vite": "8.0.0-beta.5",
|
|
20
20
|
"vitest": "^4"
|
|
21
21
|
},
|
|
22
22
|
"exports": {
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
},
|
|
94
94
|
"type": "module",
|
|
95
95
|
"types": "types/index.d.ts",
|
|
96
|
-
"version": "0.
|
|
96
|
+
"version": "0.33.0"
|
|
97
97
|
}
|
package/src/attribute/set.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import {updateAttribute} from '../internal/attribute';
|
|
2
|
+
import {setElementValue, setElementValues} from '../internal/element-value';
|
|
3
|
+
import type {Attribute} from '../models';
|
|
4
|
+
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
type DispatchedAttribute = 'checked' | 'open' | 'value';
|
|
8
|
+
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Set an attribute on an element
|
|
13
|
+
*
|
|
14
|
+
* _(Or remove it, if value is `null` or `undefined`)_
|
|
15
|
+
* @param element Element for attribute
|
|
16
|
+
* @param name Attribute name
|
|
17
|
+
* @param value Attribute value
|
|
18
|
+
* @param dispatch Dispatch event for attribute? _(defaults to `true`)_
|
|
19
|
+
*/
|
|
20
|
+
export function setAttribute<Name extends DispatchedAttribute>(
|
|
21
|
+
element: Element,
|
|
22
|
+
name: Name,
|
|
23
|
+
value?: unknown,
|
|
24
|
+
dispatch?: boolean,
|
|
25
|
+
): void;
|
|
3
26
|
|
|
4
27
|
/**
|
|
5
28
|
* Set an attribute on an element
|
|
@@ -17,11 +40,21 @@ export function setAttribute(element: Element, name: string, value?: unknown): v
|
|
|
17
40
|
* _(Or remove it, if value is `null` or `undefined`)_
|
|
18
41
|
* @param element Element for attribute
|
|
19
42
|
* @param attribute Attribute to set
|
|
43
|
+
* @param dispatch Dispatch event for attribute? _(defaults to `true`)_
|
|
20
44
|
*/
|
|
21
|
-
export function setAttribute(
|
|
45
|
+
export function setAttribute(
|
|
46
|
+
element: Element,
|
|
47
|
+
attribute: Attr | Attribute,
|
|
48
|
+
dispatch?: boolean,
|
|
49
|
+
): void;
|
|
22
50
|
|
|
23
|
-
export function setAttribute(
|
|
24
|
-
|
|
51
|
+
export function setAttribute(
|
|
52
|
+
element: Element,
|
|
53
|
+
first: unknown,
|
|
54
|
+
second?: unknown,
|
|
55
|
+
third?: unknown,
|
|
56
|
+
): void {
|
|
57
|
+
setElementValue(element, first, second, third, updateAttribute);
|
|
25
58
|
}
|
|
26
59
|
|
|
27
60
|
/**
|
|
@@ -30,8 +63,13 @@ export function setAttribute(element: Element, first: unknown, second?: unknown)
|
|
|
30
63
|
* _(Or remove them, if their value is `null` or `undefined`)_
|
|
31
64
|
* @param element Element for attributes
|
|
32
65
|
* @param attributes Attributes to set
|
|
66
|
+
* @param dispatch Dispatch events for relevant attributes? _(defaults to `true`)_
|
|
33
67
|
*/
|
|
34
|
-
export function setAttributes(
|
|
68
|
+
export function setAttributes(
|
|
69
|
+
element: Element,
|
|
70
|
+
attributes: Array<Attr | Attribute>,
|
|
71
|
+
dispatch?: boolean,
|
|
72
|
+
): void;
|
|
35
73
|
|
|
36
74
|
/**
|
|
37
75
|
* Set one or more attributes on an element
|
|
@@ -39,60 +77,18 @@ export function setAttributes(element: Element, attributes: Array<Attr | Attribu
|
|
|
39
77
|
* _(Or remove them, if their value is `null` or `undefined`)_
|
|
40
78
|
* @param element Element for attributes
|
|
41
79
|
* @param attributes Attributes to set
|
|
80
|
+
* @param dispatch Dispatch events for relevant attributes? _(defaults to `true`)_
|
|
42
81
|
*/
|
|
43
|
-
export function setAttributes(element: Element, attributes: Record<string, unknown>): void;
|
|
44
|
-
|
|
45
82
|
export function setAttributes(
|
|
46
83
|
element: Element,
|
|
47
|
-
attributes:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Set a property on an element
|
|
54
|
-
*
|
|
55
|
-
* _(Or remove it, if value is not an empty string or does not match the name)_
|
|
56
|
-
* @param element Element for property
|
|
57
|
-
* @param name Property name
|
|
58
|
-
* @param value Property value
|
|
59
|
-
*/
|
|
60
|
-
export function setProperty(element: Element, name: string, value: boolean | string): void;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Set a property on an element
|
|
64
|
-
*
|
|
65
|
-
* _(Or remove it, if value is not an empty string or does not match the name)_
|
|
66
|
-
* @param element Element for property
|
|
67
|
-
* @param property Property to set
|
|
68
|
-
*/
|
|
69
|
-
export function setProperty(element: Element, property: Property): void;
|
|
70
|
-
|
|
71
|
-
export function setProperty(element: Element, first: unknown, second?: unknown): void {
|
|
72
|
-
updateValue(element, first, second);
|
|
73
|
-
}
|
|
84
|
+
attributes: Record<string, unknown>,
|
|
85
|
+
dispatch?: boolean,
|
|
86
|
+
): void;
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
* Set one or more properties on an element
|
|
77
|
-
*
|
|
78
|
-
* _(Or remove them, if their value is not an empty string or does not match the name)_
|
|
79
|
-
* @param element Element for properties
|
|
80
|
-
* @param properties Properties to set
|
|
81
|
-
*/
|
|
82
|
-
export function setProperties(element: Element, properties: Property[]): void;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Set one or more properties on an element
|
|
86
|
-
*
|
|
87
|
-
* _(Or remove them, if their value is not an empty string or does not match the name)_
|
|
88
|
-
* @param element Element for properties
|
|
89
|
-
* @param properties Properties to set
|
|
90
|
-
*/
|
|
91
|
-
export function setProperties(element: Element, properties: Record<string, unknown>): void;
|
|
92
|
-
|
|
93
|
-
export function setProperties(
|
|
88
|
+
export function setAttributes(
|
|
94
89
|
element: Element,
|
|
95
|
-
|
|
90
|
+
attributes: Attribute[] | Record<string, unknown>,
|
|
91
|
+
dispatch?: boolean,
|
|
96
92
|
): void {
|
|
97
|
-
|
|
93
|
+
setElementValues(element, attributes, null, dispatch, updateAttribute);
|
|
98
94
|
}
|
package/src/data.ts
CHANGED
|
@@ -81,7 +81,7 @@ export function setData(element: Element, data: PlainObject): void;
|
|
|
81
81
|
export function setData(element: Element, key: string, value: unknown): void;
|
|
82
82
|
|
|
83
83
|
export function setData(element: Element, first: PlainObject | string, second?: unknown): void {
|
|
84
|
-
setElementValues(element, first, second, updateDataAttribute);
|
|
84
|
+
setElementValues(element, first, second, null, updateDataAttribute);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function updateDataAttribute(element: Element, key: string, value: unknown): void {
|
|
@@ -91,6 +91,7 @@ function updateDataAttribute(element: Element, key: string, value: unknown): voi
|
|
|
91
91
|
value,
|
|
92
92
|
element.setAttribute,
|
|
93
93
|
element.removeAttribute,
|
|
94
|
+
false,
|
|
94
95
|
true,
|
|
95
96
|
);
|
|
96
97
|
}
|
package/src/event/delegation.ts
CHANGED
|
@@ -3,31 +3,17 @@ import type {CustomEventListener, RemovableEventListener} from '../models';
|
|
|
3
3
|
|
|
4
4
|
//
|
|
5
5
|
|
|
6
|
-
type DocumentWithListenerCounts = Document &
|
|
7
|
-
Partial<{
|
|
8
|
-
[key: string]: number;
|
|
9
|
-
}>;
|
|
10
|
-
|
|
11
6
|
export type EventTargetWithListeners = EventTarget &
|
|
12
7
|
Partial<{
|
|
13
8
|
[key: string]: Set<EventListener | CustomEventListener>;
|
|
14
9
|
}>;
|
|
15
10
|
|
|
16
|
-
function addDelegatedHandler(
|
|
17
|
-
|
|
18
|
-
type: string,
|
|
19
|
-
name: string,
|
|
20
|
-
passive: boolean,
|
|
21
|
-
): void {
|
|
22
|
-
const count = `${name}${COUNT_SUFFIX}`;
|
|
23
|
-
|
|
24
|
-
if (doc[count] != null) {
|
|
25
|
-
(doc[count] as number) += 1;
|
|
26
|
-
|
|
11
|
+
function addDelegatedHandler(doc: Document, type: string, name: string, passive: boolean): void {
|
|
12
|
+
if (DELEGATED.has(name)) {
|
|
27
13
|
return;
|
|
28
14
|
}
|
|
29
15
|
|
|
30
|
-
|
|
16
|
+
DELEGATED.add(name);
|
|
31
17
|
|
|
32
18
|
doc.addEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE, {
|
|
33
19
|
passive,
|
|
@@ -43,12 +29,12 @@ export function addDelegatedListener(
|
|
|
43
29
|
): RemovableEventListener {
|
|
44
30
|
target[name] ??= new Set();
|
|
45
31
|
|
|
46
|
-
target[name]
|
|
32
|
+
target[name].add(listener);
|
|
47
33
|
|
|
48
|
-
addDelegatedHandler(document
|
|
34
|
+
addDelegatedHandler(document, type, name, passive);
|
|
49
35
|
|
|
50
36
|
return () => {
|
|
51
|
-
removeDelegatedListener(target,
|
|
37
|
+
removeDelegatedListener(target, name, listener);
|
|
52
38
|
};
|
|
53
39
|
}
|
|
54
40
|
|
|
@@ -58,9 +44,19 @@ function delegatedEventHandler(this: boolean, event: Event): void {
|
|
|
58
44
|
const items = event.composedPath();
|
|
59
45
|
const {length} = items;
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
let target = items[0];
|
|
48
|
+
|
|
49
|
+
Object.defineProperties(event, {
|
|
50
|
+
currentTarget: {
|
|
51
|
+
configurable: true,
|
|
52
|
+
get() {
|
|
53
|
+
return target;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
target: {
|
|
57
|
+
configurable: true,
|
|
58
|
+
value: target,
|
|
59
|
+
},
|
|
64
60
|
});
|
|
65
61
|
|
|
66
62
|
for (let index = 0; index < length; index += 1) {
|
|
@@ -71,10 +67,7 @@ function delegatedEventHandler(this: boolean, event: Event): void {
|
|
|
71
67
|
continue;
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
configurable: true,
|
|
76
|
-
value: item,
|
|
77
|
-
});
|
|
70
|
+
target = item;
|
|
78
71
|
|
|
79
72
|
for (const listener of listeners) {
|
|
80
73
|
(listener as EventListener).call(item, event);
|
|
@@ -102,29 +95,10 @@ export function getDelegatedName(
|
|
|
102
95
|
}
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
function removeDelegatedHandler(
|
|
106
|
-
doc: DocumentWithListenerCounts,
|
|
107
|
-
type: string,
|
|
108
|
-
name: string,
|
|
109
|
-
passive: boolean,
|
|
110
|
-
): void {
|
|
111
|
-
const count = `${name}${COUNT_SUFFIX}`;
|
|
112
|
-
|
|
113
|
-
(doc[count] as number) -= 1;
|
|
114
|
-
|
|
115
|
-
if ((doc[count] as number) < 1) {
|
|
116
|
-
doc[count] = undefined;
|
|
117
|
-
|
|
118
|
-
doc.removeEventListener(type, passive ? HANDLER_PASSIVE : HANDLER_ACTIVE);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
98
|
export function removeDelegatedListener(
|
|
123
99
|
target: EventTargetWithListeners,
|
|
124
|
-
type: string,
|
|
125
100
|
name: string,
|
|
126
101
|
listener: EventListener | CustomEventListener,
|
|
127
|
-
passive: boolean,
|
|
128
102
|
): boolean {
|
|
129
103
|
const handlers = target[name];
|
|
130
104
|
|
|
@@ -138,14 +112,12 @@ export function removeDelegatedListener(
|
|
|
138
112
|
target[name] = undefined;
|
|
139
113
|
}
|
|
140
114
|
|
|
141
|
-
removeDelegatedHandler(document as DocumentWithListenerCounts, type, name, passive);
|
|
142
|
-
|
|
143
115
|
return true;
|
|
144
116
|
}
|
|
145
117
|
|
|
146
118
|
//
|
|
147
119
|
|
|
148
|
-
const
|
|
120
|
+
const DELEGATED = new Set<string>();
|
|
149
121
|
|
|
150
122
|
const EVENT_PREFIX = '@';
|
|
151
123
|
|
|
@@ -178,6 +150,6 @@ const EVENT_TYPES: Set<string> = new Set([
|
|
|
178
150
|
'touchstart',
|
|
179
151
|
]);
|
|
180
152
|
|
|
181
|
-
const HANDLER_ACTIVE
|
|
153
|
+
const HANDLER_ACTIVE = delegatedEventHandler.bind(false);
|
|
182
154
|
|
|
183
|
-
const HANDLER_PASSIVE
|
|
155
|
+
const HANDLER_PASSIVE = delegatedEventHandler.bind(true);
|
package/src/event/index.ts
CHANGED
|
@@ -151,13 +151,7 @@ export function off(
|
|
|
151
151
|
|
|
152
152
|
if (
|
|
153
153
|
delegated == null ||
|
|
154
|
-
!removeDelegatedListener(
|
|
155
|
-
target as EventTargetWithListeners,
|
|
156
|
-
type,
|
|
157
|
-
delegated,
|
|
158
|
-
listener,
|
|
159
|
-
extended.passive,
|
|
160
|
-
)
|
|
154
|
+
!removeDelegatedListener(target as EventTargetWithListeners, delegated, listener)
|
|
161
155
|
) {
|
|
162
156
|
target.removeEventListener(type, listener as EventListener, extended);
|
|
163
157
|
}
|
package/src/find/index.ts
CHANGED
package/src/find/relative.ts
CHANGED
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* - Get the distance between two elements _(i.e., the amount of nodes of between them)_
|
|
3
|
-
* - If the distance cannot be calculated, `-1` is returned
|
|
4
|
-
*/
|
|
5
|
-
function getDistanceBetweenElements(origin: Element, target: Element): number | undefined {
|
|
6
|
-
if (origin === target) {
|
|
7
|
-
return 0;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (origin.parentElement === target) {
|
|
11
|
-
return 1;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const children = [...(origin.parentElement?.children ?? [])];
|
|
15
|
-
|
|
16
|
-
if (children.includes(target)) {
|
|
17
|
-
return Math.abs(children.indexOf(origin) - children.indexOf(target));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const comparison = origin.compareDocumentPosition(target);
|
|
21
|
-
const beforeOrInside = Boolean(comparison & 2 || comparison & 8);
|
|
22
|
-
|
|
23
|
-
if (beforeOrInside || Boolean(comparison & 4 || comparison & 16)) {
|
|
24
|
-
return traverse(beforeOrInside ? origin : target, beforeOrInside ? target : origin) ?? -1;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
1
|
/**
|
|
29
2
|
* Find the closest ancestor element that matches the selector _(string or callback)_
|
|
30
3
|
*
|
|
@@ -74,8 +47,7 @@ export function findAncestor(
|
|
|
74
47
|
/**
|
|
75
48
|
* Finds the closest elements to the origin element that matches the selector
|
|
76
49
|
*
|
|
77
|
-
*
|
|
78
|
-
* - _(If you only want to traverse up, use {@link findAncestor})_
|
|
50
|
+
* Traverses up, down, and sideways in the _DOM_-tree. _(If you only want to traverse up, use {@link findAncestor})_
|
|
79
51
|
* @param origin Element to start from
|
|
80
52
|
* @param selector Selector to match
|
|
81
53
|
* @param context Context to search within
|
|
@@ -109,9 +81,9 @@ export function findRelatives(
|
|
|
109
81
|
|
|
110
82
|
for (let index = 0; index < length; index += 1) {
|
|
111
83
|
const element = elements[index];
|
|
112
|
-
const distance =
|
|
84
|
+
const distance = getDistance(origin, element);
|
|
113
85
|
|
|
114
|
-
if (distance
|
|
86
|
+
if (distance > 0) {
|
|
115
87
|
if (minimum == null || distance < minimum) {
|
|
116
88
|
minimum = distance;
|
|
117
89
|
}
|
|
@@ -124,17 +96,43 @@ export function findRelatives(
|
|
|
124
96
|
}
|
|
125
97
|
|
|
126
98
|
return distances
|
|
127
|
-
.filter(found => found.distance === minimum
|
|
99
|
+
.filter(found => found.distance === minimum)
|
|
128
100
|
.map(found => found.element);
|
|
129
101
|
}
|
|
130
102
|
|
|
131
|
-
|
|
132
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Get the distance between two elements _(i.e., the amount of nodes of between them)_
|
|
105
|
+
* @param origin Origin element
|
|
106
|
+
* @param target Target element
|
|
107
|
+
* @returns Distance between elements, or `-1` if distance cannot be calculated
|
|
108
|
+
*/
|
|
109
|
+
export function getDistance(origin: Element, target: Element): number {
|
|
110
|
+
if (origin === target) {
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
133
113
|
|
|
134
|
-
if (
|
|
114
|
+
if (origin.parentElement === target || target.parentElement === origin) {
|
|
135
115
|
return 1;
|
|
136
116
|
}
|
|
137
117
|
|
|
118
|
+
if (origin.parentElement != null && origin.parentElement === target.parentElement) {
|
|
119
|
+
const children = [...origin.parentElement.children];
|
|
120
|
+
|
|
121
|
+
return Math.abs(children.indexOf(origin) - children.indexOf(target));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const comparison = origin.compareDocumentPosition(target);
|
|
125
|
+
|
|
126
|
+
if (comparison & Node.DOCUMENT_POSITION_DISCONNECTED) {
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const preceding = comparison & Node.DOCUMENT_POSITION_PRECEDING;
|
|
131
|
+
|
|
132
|
+
return traverse(preceding ? origin : target, preceding ? target : origin) ?? -1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function traverse(from: Element, to: Element): number | undefined {
|
|
138
136
|
let current = from;
|
|
139
137
|
let distance = 0;
|
|
140
138
|
let parent: Element | null = from.parentElement;
|
|
@@ -146,11 +144,11 @@ function traverse(from: Element, to: Element): number | undefined {
|
|
|
146
144
|
|
|
147
145
|
const children = [...parent.children];
|
|
148
146
|
|
|
149
|
-
if (
|
|
147
|
+
if (to.parentElement === parent) {
|
|
150
148
|
return distance + Math.abs(children.indexOf(current) - children.indexOf(to));
|
|
151
149
|
}
|
|
152
150
|
|
|
153
|
-
index = children.findIndex(child => child.contains(to));
|
|
151
|
+
const index = children.findIndex(child => child.contains(to));
|
|
154
152
|
|
|
155
153
|
if (index > -1) {
|
|
156
154
|
const traversed = traverse(current, children[index]);
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type {PlainObject} from '@oscarpalmer/atoms';
|
|
2
2
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {isHTMLOrSVGElement} from './is';
|
|
3
|
+
import type {Attribute} from '../models';
|
|
4
|
+
import {updateElementValue} from './element-value';
|
|
6
5
|
|
|
7
6
|
function badAttributeHandler(name?: string, value?: string): boolean {
|
|
8
|
-
if (name
|
|
7
|
+
if (typeof name !== 'string' || name.trim().length === 0 || typeof value !== 'string') {
|
|
9
8
|
return true;
|
|
10
9
|
}
|
|
11
10
|
|
|
@@ -28,17 +27,19 @@ function badAttributeHandler(name?: string, value?: string): boolean {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
function booleanAttributeHandler(name?: string, value?: string): boolean {
|
|
31
|
-
if (name
|
|
30
|
+
if (typeof name !== 'string' || name.trim().length === 0 || typeof value !== 'string') {
|
|
32
31
|
return true;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
const normalizedName = name.toLowerCase();
|
|
35
|
+
|
|
36
|
+
if (!booleanAttributesSet.has(normalizedName)) {
|
|
36
37
|
return false;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const normalized = value.toLowerCase()
|
|
40
|
+
const normalized = value.toLowerCase();
|
|
40
41
|
|
|
41
|
-
return !(normalized.length === 0 || normalized ===
|
|
42
|
+
return !(normalized.length === 0 || normalized === normalizedName);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
function decodeAttribute(value: string): string {
|
|
@@ -73,12 +74,12 @@ function handleAttribute(
|
|
|
73
74
|
return callback(name, value?.replace(EXPRESSION_WHITESPACE, ''));
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
function isAttribute(value: unknown): value is Attr | Attribute {
|
|
77
|
+
export function isAttribute(value: unknown): value is Attr | Attribute {
|
|
77
78
|
return (
|
|
78
79
|
value instanceof Attr ||
|
|
79
80
|
(isPlainObject(value) &&
|
|
80
81
|
typeof (value as PlainObject).name === 'string' &&
|
|
81
|
-
|
|
82
|
+
'value' in (value as PlainObject))
|
|
82
83
|
);
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -117,67 +118,53 @@ export function _isInvalidBooleanAttribute(
|
|
|
117
118
|
return handleAttribute(booleanAttributeHandler, decode, first, second);
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
export function isProperty(value: unknown): value is Property {
|
|
121
|
-
return isPlainObject(value) && typeof (value as PlainObject).name === 'string';
|
|
122
|
-
}
|
|
123
|
-
|
|
124
121
|
function isValidSourceAttribute(name: string, value: string): boolean {
|
|
125
122
|
return EXPRESSION_SOURCE_NAME.test(name) && EXPRESSION_SOURCE_VALUE.test(value);
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
function updateAttribute(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
export function updateAttribute(
|
|
126
|
+
element: Element,
|
|
127
|
+
name: string,
|
|
128
|
+
value: unknown,
|
|
129
|
+
dispatch?: unknown,
|
|
130
|
+
): void {
|
|
131
|
+
const normalizedName = name.toLowerCase();
|
|
134
132
|
|
|
135
|
-
|
|
136
|
-
element.removeAttribute(name);
|
|
137
|
-
} else {
|
|
138
|
-
element.setAttribute(name, isBoolean ? '' : getString(value));
|
|
139
|
-
}
|
|
140
|
-
}
|
|
133
|
+
const isBoolean = booleanAttributesSet.has(normalizedName);
|
|
141
134
|
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
const next = isBoolean
|
|
136
|
+
? value === true ||
|
|
137
|
+
(typeof value === 'string' && (value === '' || value.toLowerCase() === normalizedName))
|
|
138
|
+
: value == null
|
|
139
|
+
? ''
|
|
140
|
+
: value;
|
|
144
141
|
|
|
145
|
-
(
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function updateValue(element: Element, first: unknown, second: unknown): void {
|
|
150
|
-
if (!isHTMLOrSVGElement(element)) {
|
|
151
|
-
return;
|
|
142
|
+
if (name in element) {
|
|
143
|
+
updateProperty(element, normalizedName, next, dispatch);
|
|
152
144
|
}
|
|
153
145
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
updateElementValue(
|
|
147
|
+
element,
|
|
148
|
+
name,
|
|
149
|
+
isBoolean ? (next ? '' : null) : value,
|
|
150
|
+
element.setAttribute,
|
|
151
|
+
element.removeAttribute,
|
|
152
|
+
isBoolean,
|
|
153
|
+
false,
|
|
154
|
+
);
|
|
159
155
|
}
|
|
160
156
|
|
|
161
|
-
|
|
162
|
-
element
|
|
163
|
-
values: Attribute<unknown>[] | Record<string, unknown>,
|
|
164
|
-
): void {
|
|
165
|
-
if (!isHTMLOrSVGElement(element)) {
|
|
157
|
+
function updateProperty(element: Element, name: string, value: unknown, dispatch?: unknown): void {
|
|
158
|
+
if (Object.is((element as unknown as PlainObject)[name], value)) {
|
|
166
159
|
return;
|
|
167
160
|
}
|
|
168
161
|
|
|
169
|
-
|
|
170
|
-
const entries = Object.entries(values);
|
|
171
|
-
const {length} = entries;
|
|
162
|
+
(element as unknown as PlainObject)[name] = value;
|
|
172
163
|
|
|
173
|
-
|
|
174
|
-
const entry = entries[index];
|
|
164
|
+
const event = dispatch !== false && elementEvents[element.tagName]?.[name];
|
|
175
165
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
} else {
|
|
179
|
-
updateAttribute(element, entry[0], entry[1]);
|
|
180
|
-
}
|
|
166
|
+
if (typeof event === 'string') {
|
|
167
|
+
element.dispatchEvent(new Event(event, {bubbles: true}));
|
|
181
168
|
}
|
|
182
169
|
}
|
|
183
170
|
|
|
@@ -201,6 +188,8 @@ const EXPRESSION_URI_VALUE =
|
|
|
201
188
|
// oxlint-disable-next-line no-control-regex
|
|
202
189
|
const EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
203
190
|
|
|
191
|
+
//
|
|
192
|
+
|
|
204
193
|
/**
|
|
205
194
|
* List of boolean attributes
|
|
206
195
|
*/
|
|
@@ -233,6 +222,13 @@ export const booleanAttributes: readonly string[] = Object.freeze([
|
|
|
233
222
|
|
|
234
223
|
const booleanAttributesSet = new Set(booleanAttributes);
|
|
235
224
|
|
|
225
|
+
const elementEvents: Record<string, Record<string, string>> = {
|
|
226
|
+
DETAILS: {open: 'toggle'},
|
|
227
|
+
INPUT: {checked: 'change', value: 'input'},
|
|
228
|
+
SELECT: {value: 'change'},
|
|
229
|
+
TEXTAREA: {value: 'input'},
|
|
230
|
+
};
|
|
231
|
+
|
|
236
232
|
const formElement = document.createElement('form');
|
|
237
233
|
|
|
238
234
|
let textArea: HTMLTextAreaElement;
|