@oscarpalmer/abydon 0.8.0 → 0.10.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/fragment.mjs +30 -11
- package/dist/helpers/dom.mjs +23 -25
- package/dist/helpers/index.mjs +16 -6
- package/dist/index.js +540 -251
- package/dist/node/attribute/index.mjs +5 -13
- package/dist/node/attribute/value.mjs +42 -59
- package/dist/node/event.mjs +22 -2
- package/dist/node/index.mjs +18 -9
- package/dist/node/value.mjs +105 -22
- package/package.json +6 -5
- package/src/fragment.ts +31 -14
- package/src/helpers/dom.ts +31 -34
- package/src/helpers/index.ts +4 -8
- package/src/models.ts +10 -3
- package/src/node/attribute/index.ts +12 -25
- package/src/node/attribute/value.ts +38 -68
- package/src/node/event.ts +30 -8
- package/src/node/index.ts +29 -12
- package/src/node/value.ts +68 -29
- package/types/fragment.d.ts +2 -1
- package/types/helpers/dom.d.ts +4 -5
- package/types/helpers/index.d.ts +3 -3
- package/types/index.d.cts +2 -1
- package/types/models.d.ts +8 -3
- package/types/node/attribute/index.d.ts +2 -3
- package/types/node/attribute/value.d.ts +2 -2
- package/types/node/event.d.ts +3 -2
- package/types/node/index.d.ts +2 -2
- package/types/node/value.d.ts +2 -1
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
// src/node/attribute/index.ts
|
|
2
|
-
import {isReactive} from "@oscarpalmer/sentinel";
|
|
2
|
+
import {computed, isReactive} from "@oscarpalmer/sentinel";
|
|
3
3
|
import {mapEvent} from "../event";
|
|
4
4
|
import {setAttribute} from "./value";
|
|
5
5
|
function getValue(data, original) {
|
|
6
6
|
const matches = commentExpression.exec(original ?? "");
|
|
7
7
|
return matches == null ? original : data.values[+matches[1]];
|
|
8
8
|
}
|
|
9
|
-
function isBadAttribute(name, value2) {
|
|
10
|
-
return /^on/i.test(name) || /^(href|src|xlink:href)$/i.test(name) && /(data:text\/html|javascript:)/i.test(value2);
|
|
11
|
-
}
|
|
12
9
|
function mapAttributes(data, element) {
|
|
13
10
|
const attributes = [...element.attributes];
|
|
14
11
|
const { length } = attributes;
|
|
15
12
|
for (let index = 0;index < length; index += 1) {
|
|
16
13
|
const { name, value: value2 } = attributes[index];
|
|
17
|
-
if (isBadAttribute(name, value2)) {
|
|
18
|
-
element.removeAttribute(name);
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
14
|
const actual = getValue(data, value2);
|
|
22
15
|
if (name.startsWith("@")) {
|
|
23
16
|
mapEvent(element, name, actual);
|
|
24
|
-
} else if (name.includes(".") || isReactive(actual)) {
|
|
17
|
+
} else if (name.includes(".") || typeof value2 === "function" || isReactive(actual)) {
|
|
25
18
|
mapValue(element, name, actual);
|
|
26
19
|
}
|
|
27
20
|
}
|
|
@@ -29,8 +22,8 @@ function mapAttributes(data, element) {
|
|
|
29
22
|
function mapValue(element, name, value2) {
|
|
30
23
|
switch (true) {
|
|
31
24
|
case typeof value2 === "function":
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
setAttribute(element, name, computed(value2));
|
|
26
|
+
break;
|
|
34
27
|
default:
|
|
35
28
|
setAttribute(element, name, value2);
|
|
36
29
|
break;
|
|
@@ -38,6 +31,5 @@ function mapValue(element, name, value2) {
|
|
|
38
31
|
}
|
|
39
32
|
var commentExpression = /^<!--abydon\.(\d+)-->$/;
|
|
40
33
|
export {
|
|
41
|
-
mapAttributes
|
|
42
|
-
isBadAttribute
|
|
34
|
+
mapAttributes
|
|
43
35
|
};
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
// src/node/attribute/value.ts
|
|
2
|
-
import {closest} from "@oscarpalmer/atoms/element";
|
|
3
2
|
import {getString} from "@oscarpalmer/atoms/string";
|
|
4
3
|
import {effect, isReactive} from "@oscarpalmer/sentinel";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
isBooleanAttribute,
|
|
6
|
+
setAttribute as setAttr
|
|
7
|
+
} from "@oscarpalmer/toretto/attribute";
|
|
8
|
+
function setAttribute(element, name, value) {
|
|
9
|
+
element.removeAttribute(name);
|
|
7
10
|
switch (true) {
|
|
8
|
-
case
|
|
9
|
-
setClasses(
|
|
11
|
+
case classExpression.test(name):
|
|
12
|
+
setClasses(element, name, value);
|
|
10
13
|
return;
|
|
11
|
-
case
|
|
12
|
-
setStyle(
|
|
14
|
+
case stylePrefixExpression.test(name):
|
|
15
|
+
setStyle(element, name, value);
|
|
13
16
|
return;
|
|
14
17
|
default:
|
|
15
|
-
setValue(
|
|
18
|
+
setValue(element, name, value);
|
|
16
19
|
break;
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
|
-
function setClasses(
|
|
22
|
+
function setClasses(element, name, value) {
|
|
20
23
|
function update(value2) {
|
|
21
|
-
if (
|
|
22
|
-
|
|
24
|
+
if (value2 === true) {
|
|
25
|
+
element.classList.add(...classes);
|
|
23
26
|
} else {
|
|
24
|
-
|
|
27
|
+
element.classList.remove(...classes);
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
const classes = name.slice(6).split(".");
|
|
@@ -33,34 +36,33 @@ function setClasses(element2, name, value) {
|
|
|
33
36
|
update(value);
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
|
-
function setStyle(
|
|
39
|
+
function setStyle(element, name, value) {
|
|
37
40
|
function update(value2) {
|
|
38
41
|
if (value2 == null || value2 === false || value2 === true && unit == null) {
|
|
39
|
-
|
|
42
|
+
element.style.removeProperty(property);
|
|
40
43
|
} else {
|
|
41
|
-
|
|
44
|
+
element.style.setProperty(property, value2 === true ? unit : getString(value2));
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
|
-
const [, property, unit] =
|
|
45
|
-
if (property
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
update(value);
|
|
47
|
+
const [, property, unit] = styleFullExpression.exec(name) ?? [];
|
|
48
|
+
if (property != null) {
|
|
49
|
+
if (isReactive(value)) {
|
|
50
|
+
effect(() => {
|
|
51
|
+
update(value.get());
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
update(value);
|
|
55
|
+
}
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
function setValue(
|
|
57
|
-
const callback =
|
|
58
|
+
function setValue(element, name, value) {
|
|
59
|
+
const callback = isBooleanAttribute(name) && name in element ? name === "selected" ? updateSelected : updateProperty : setAttr;
|
|
58
60
|
if (isReactive(value)) {
|
|
59
61
|
effect(() => {
|
|
60
|
-
callback(
|
|
62
|
+
callback(element, name, value.get());
|
|
61
63
|
});
|
|
62
64
|
} else {
|
|
63
|
-
callback(
|
|
65
|
+
callback(element, name, value);
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
function triggerSelectChange(select) {
|
|
@@ -68,39 +70,20 @@ function triggerSelectChange(select) {
|
|
|
68
70
|
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
element2.removeAttribute(name);
|
|
74
|
-
} else {
|
|
75
|
-
element2.setAttribute(name, getString(value));
|
|
76
|
-
}
|
|
73
|
+
function updateProperty(element, name, value) {
|
|
74
|
+
element[name] = value === true;
|
|
77
75
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
function updateSelected(element2) {
|
|
82
|
-
const select = closest(element2, "select")[0];
|
|
76
|
+
function updateSelected(element, name, value) {
|
|
77
|
+
const select = element.closest("select");
|
|
83
78
|
const options = [...select?.options ?? []];
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
updateProperty(element3, name, value);
|
|
89
|
-
};
|
|
79
|
+
if (select != null && options.includes(element)) {
|
|
80
|
+
triggerSelectChange(select);
|
|
81
|
+
}
|
|
82
|
+
updateProperty(element, name, value);
|
|
90
83
|
}
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"hidden",
|
|
95
|
-
"inert",
|
|
96
|
-
"multiple",
|
|
97
|
-
"open",
|
|
98
|
-
"readOnly",
|
|
99
|
-
"required",
|
|
100
|
-
"selected"
|
|
101
|
-
]);
|
|
102
|
-
var classPattern = /^class\./;
|
|
103
|
-
var stylePattern = /^style\./;
|
|
84
|
+
var classExpression = /^class\./;
|
|
85
|
+
var styleFullExpression = /^style\.([\w-]+)(?:\.([\w-]+))?$/;
|
|
86
|
+
var stylePrefixExpression = /^style\./;
|
|
104
87
|
export {
|
|
105
88
|
setAttribute
|
|
106
89
|
};
|
package/dist/node/event.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// src/node/event.ts
|
|
2
|
+
function getController(element) {
|
|
3
|
+
let controller = controllers.get(element);
|
|
4
|
+
if (controller == null) {
|
|
5
|
+
controller = new AbortController;
|
|
6
|
+
controllers.set(element, controller);
|
|
7
|
+
}
|
|
8
|
+
return controller;
|
|
9
|
+
}
|
|
2
10
|
function getOptions(options) {
|
|
3
11
|
const parts = options.split(":");
|
|
4
12
|
return {
|
|
@@ -9,11 +17,23 @@ function getOptions(options) {
|
|
|
9
17
|
}
|
|
10
18
|
function mapEvent(element, name, value) {
|
|
11
19
|
element.removeAttribute(name);
|
|
12
|
-
const [, type, options] =
|
|
20
|
+
const [, type, options] = eventNamePattern.exec(name) ?? [];
|
|
13
21
|
if (typeof value === "function" && type != null) {
|
|
14
|
-
element.addEventListener(type, value,
|
|
22
|
+
element.addEventListener(type, value, {
|
|
23
|
+
...getOptions(options ?? ""),
|
|
24
|
+
signal: getController(element).signal
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function removeEvents(element) {
|
|
29
|
+
if (controllers.has(element)) {
|
|
30
|
+
controllers.get(element)?.abort();
|
|
31
|
+
controllers.delete(element);
|
|
15
32
|
}
|
|
16
33
|
}
|
|
34
|
+
var controllers = new WeakMap;
|
|
35
|
+
var eventNamePattern = /^@([\w-]+)(?::([a-z:]+))?$/i;
|
|
17
36
|
export {
|
|
37
|
+
removeEvents,
|
|
18
38
|
mapEvent
|
|
19
39
|
};
|
package/dist/node/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/node/index.ts
|
|
2
|
-
import {isReactive} from "@oscarpalmer/sentinel";
|
|
3
|
-
import {
|
|
2
|
+
import {computed, isReactive} from "@oscarpalmer/sentinel";
|
|
3
|
+
import {isFragment, isProperElement} from "../helpers";
|
|
4
4
|
import {createNodes} from "../helpers/dom";
|
|
5
5
|
import {mapAttributes} from "./attribute";
|
|
6
6
|
import {setReactiveNode} from "./value";
|
|
@@ -8,7 +8,7 @@ function mapNode(data, comment) {
|
|
|
8
8
|
const matches = commentExpression.exec(comment.textContent ?? "");
|
|
9
9
|
const value2 = matches == null ? null : data.values[+matches[1]];
|
|
10
10
|
if (value2 != null) {
|
|
11
|
-
mapValue(comment, value2);
|
|
11
|
+
mapValue(data, comment, value2);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
function mapNodes(data, nodes) {
|
|
@@ -19,7 +19,7 @@ function mapNodes(data, nodes) {
|
|
|
19
19
|
mapNode(data, node);
|
|
20
20
|
continue;
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
22
|
+
if (isProperElement(node)) {
|
|
23
23
|
mapAttributes(data, node);
|
|
24
24
|
}
|
|
25
25
|
if (node.hasChildNodes()) {
|
|
@@ -27,19 +27,28 @@ function mapNodes(data, nodes) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
function mapValue(comment, value2) {
|
|
30
|
+
function mapValue(data, comment, value2) {
|
|
31
31
|
switch (true) {
|
|
32
32
|
case typeof value2 === "function":
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
setReactiveNode(data, comment, computed(value2));
|
|
34
|
+
break;
|
|
35
35
|
case isReactive(value2):
|
|
36
|
-
setReactiveNode(comment, value2);
|
|
36
|
+
setReactiveNode(data, comment, value2);
|
|
37
37
|
break;
|
|
38
38
|
default:
|
|
39
|
-
comment
|
|
39
|
+
replaceComment(data, comment, value2);
|
|
40
40
|
break;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
function replaceComment(data, comment, value2) {
|
|
44
|
+
const item = data.items.find((item2) => item2.nodes.includes(comment));
|
|
45
|
+
const nodes = createNodes(value2);
|
|
46
|
+
if (item != null) {
|
|
47
|
+
item.fragments = isFragment(value2) ? [value2] : undefined;
|
|
48
|
+
item.nodes = nodes;
|
|
49
|
+
}
|
|
50
|
+
comment.replaceWith(...nodes);
|
|
51
|
+
}
|
|
43
52
|
var commentExpression = /^abydon\.(\d+)$/;
|
|
44
53
|
export {
|
|
45
54
|
mapNodes
|
package/dist/node/value.mjs
CHANGED
|
@@ -2,39 +2,122 @@
|
|
|
2
2
|
import {isNullableOrWhitespace} from "@oscarpalmer/atoms/is";
|
|
3
3
|
import {getString} from "@oscarpalmer/atoms/string";
|
|
4
4
|
import {effect} from "@oscarpalmer/sentinel";
|
|
5
|
-
import {isFragment,
|
|
5
|
+
import {compareArrays, isFragment, isProperNode} from "../helpers";
|
|
6
6
|
import {createNodes, replaceNodes} from "../helpers/dom";
|
|
7
|
-
function
|
|
7
|
+
function removeFragments(fragments) {
|
|
8
|
+
if (fragments != null) {
|
|
9
|
+
const { length } = fragments;
|
|
10
|
+
for (let index = 0;index < length; index += 1) {
|
|
11
|
+
fragments[index].remove();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function setArray(fragments, nodes, comment, text, value) {
|
|
16
|
+
if (value.length === 0) {
|
|
17
|
+
return {
|
|
18
|
+
nodes: setText(fragments, nodes, comment, text, value)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
let templates = value.filter((item) => isFragment(item) && item.identifier != null);
|
|
22
|
+
const identifiers = templates.map((fragment) => fragment.identifier);
|
|
23
|
+
const oldIdentifiers = fragments?.map((fragment) => fragment.identifier) ?? [];
|
|
24
|
+
if (new Set(identifiers).size !== templates.length) {
|
|
25
|
+
templates = [];
|
|
26
|
+
}
|
|
27
|
+
const noTemplates = templates.length === 0;
|
|
28
|
+
if (noTemplates || nodes == null || oldIdentifiers.some((identifier) => identifier == null)) {
|
|
29
|
+
return {
|
|
30
|
+
fragments: noTemplates ? undefined : templates,
|
|
31
|
+
nodes: setNodes(fragments, nodes, comment, text, noTemplates ? value.flatMap((item) => createNodes(item)) : templates.flatMap((template) => template.get()))
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const next = templates.map((template) => fragments?.find((fragment) => fragment.identifier === template.identifier) ?? template);
|
|
35
|
+
const comparison = compareArrays(fragments ?? [], templates);
|
|
36
|
+
if (comparison !== "removed") {
|
|
37
|
+
let position = nodes[0];
|
|
38
|
+
const before = comparison === "added" && !oldIdentifiers.includes(templates[0].identifier);
|
|
39
|
+
const items = next.flatMap((fragment) => fragment.get().flatMap((node) => ({
|
|
40
|
+
identifier: fragment.identifier,
|
|
41
|
+
value: node
|
|
42
|
+
})));
|
|
43
|
+
const { length: length2 } = items;
|
|
44
|
+
for (let index = 0;index < length2; index += 1) {
|
|
45
|
+
const item = items[index];
|
|
46
|
+
if (comparison === "dissimilar" || !oldIdentifiers.includes(item.identifier)) {
|
|
47
|
+
if (index === 0 && before) {
|
|
48
|
+
position.before(item.value);
|
|
49
|
+
} else {
|
|
50
|
+
position.after(item.value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
position = item.value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const toRemove = fragments?.filter((fragment) => !identifiers.includes(fragment.identifier)) ?? [];
|
|
57
|
+
const { length } = toRemove;
|
|
58
|
+
for (let index = 0;index < length; index += 1) {
|
|
59
|
+
toRemove[index].remove();
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
fragments: next,
|
|
63
|
+
nodes: next.flatMap((fragment) => fragment.get())
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function setNodes(fragments, nodes, comment, text, next) {
|
|
67
|
+
if (nodes == null) {
|
|
68
|
+
if (comment.parentNode != null) {
|
|
69
|
+
replaceNodes([comment], next);
|
|
70
|
+
} else if (text.parentNode != null) {
|
|
71
|
+
replaceNodes([text], next);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
replaceNodes(nodes, next);
|
|
75
|
+
}
|
|
76
|
+
removeFragments(fragments);
|
|
77
|
+
return next;
|
|
78
|
+
}
|
|
79
|
+
function setReactiveNode(data, comment, reactive) {
|
|
80
|
+
const item = data.items.find((item2) => item2.nodes.includes(comment));
|
|
8
81
|
const text = new Text;
|
|
82
|
+
let fragments;
|
|
9
83
|
let nodes;
|
|
10
84
|
effect(() => {
|
|
11
85
|
const value = reactive.get();
|
|
12
|
-
if (
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
const result = setArray(fragments, nodes, comment, text, value);
|
|
88
|
+
fragments = typeof result === "boolean" ? undefined : result?.fragments;
|
|
89
|
+
nodes = typeof result === "boolean" ? result ? [text] : undefined : result?.nodes;
|
|
90
|
+
} else {
|
|
91
|
+
const valueIsFragment = isFragment(value);
|
|
92
|
+
fragments = valueIsFragment ? [value] : undefined;
|
|
93
|
+
if (valueIsFragment || isProperNode(value)) {
|
|
94
|
+
nodes = setNodes(fragments, nodes, comment, text, createNodes(value));
|
|
20
95
|
} else {
|
|
21
|
-
|
|
96
|
+
nodes = setText(fragments, nodes, comment, text, value);
|
|
22
97
|
}
|
|
23
|
-
nodes = next;
|
|
24
|
-
return;
|
|
25
98
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
replaceNodes(nodes, [isNullable ? comment : text]);
|
|
30
|
-
} else if (isNullable && comment.parentNode == null) {
|
|
31
|
-
text.replaceWith(comment);
|
|
32
|
-
} else if (!isNullable && text.parentNode == null) {
|
|
33
|
-
comment.replaceWith(text);
|
|
99
|
+
if (item != null) {
|
|
100
|
+
item.fragments = fragments;
|
|
101
|
+
item.nodes = [...nodes ?? [comment]];
|
|
34
102
|
}
|
|
35
|
-
nodes = undefined;
|
|
36
103
|
});
|
|
37
104
|
}
|
|
105
|
+
function setText(fragments, nodes, comment, text, value) {
|
|
106
|
+
const isNullable = isNullableOrWhitespace(value);
|
|
107
|
+
text.textContent = isNullable ? "" : getString(value);
|
|
108
|
+
let result = false;
|
|
109
|
+
if (nodes != null) {
|
|
110
|
+
replaceNodes(nodes, [isNullable ? comment : text]);
|
|
111
|
+
result = !isNullable;
|
|
112
|
+
} else if (isNullable && comment.parentNode == null) {
|
|
113
|
+
text.replaceWith(comment);
|
|
114
|
+
} else if (!isNullable && text.parentNode == null) {
|
|
115
|
+
comment.replaceWith(text);
|
|
116
|
+
result = true;
|
|
117
|
+
}
|
|
118
|
+
removeFragments(fragments);
|
|
119
|
+
return result ? [text] : undefined;
|
|
120
|
+
}
|
|
38
121
|
export {
|
|
39
122
|
setReactiveNode
|
|
40
123
|
};
|
package/package.json
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@oscarpalmer/atoms": "^0.
|
|
8
|
-
"@oscarpalmer/sentinel": "^0.23.0"
|
|
7
|
+
"@oscarpalmer/atoms": "^0.71.0",
|
|
8
|
+
"@oscarpalmer/sentinel": "^0.23.0",
|
|
9
|
+
"@oscarpalmer/toretto": "0.12.1"
|
|
9
10
|
},
|
|
10
11
|
"devDependencies": {
|
|
11
12
|
"@biomejs/biome": "^1.8.3",
|
|
12
|
-
"@types/bun": "^1.1.
|
|
13
|
-
"bun": "^1.1.
|
|
13
|
+
"@types/bun": "^1.1.8",
|
|
14
|
+
"bun": "^1.1.27",
|
|
14
15
|
"dts-bundle-generator": "^9.5.1",
|
|
15
16
|
"typescript": "^5.5.4"
|
|
16
17
|
},
|
|
@@ -46,5 +47,5 @@
|
|
|
46
47
|
},
|
|
47
48
|
"type": "module",
|
|
48
49
|
"types": "types/index.d.ts",
|
|
49
|
-
"version": "0.
|
|
50
|
+
"version": "0.10.0"
|
|
50
51
|
}
|
package/src/fragment.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {html} from '@oscarpalmer/toretto/html';
|
|
2
2
|
import {parse} from './html';
|
|
3
|
-
import type {FragmentData} from './models';
|
|
3
|
+
import type {FragmentData, ProperNode} from './models';
|
|
4
4
|
import {mapNodes} from './node';
|
|
5
|
+
import {removeNodes} from './helpers/dom';
|
|
5
6
|
|
|
6
7
|
export class Fragment {
|
|
7
|
-
private
|
|
8
|
+
private readonly $fragment = true;
|
|
8
9
|
private readonly data: FragmentData;
|
|
9
10
|
|
|
10
11
|
constructor(strings: TemplateStringsArray, expressions: unknown[]) {
|
|
11
12
|
this.data = {
|
|
12
13
|
expressions,
|
|
13
14
|
strings,
|
|
14
|
-
|
|
15
|
+
items: [],
|
|
15
16
|
values: [],
|
|
16
17
|
};
|
|
17
18
|
}
|
|
@@ -26,31 +27,47 @@ export class Fragment {
|
|
|
26
27
|
/**
|
|
27
28
|
* Gets a list of the fragment's nodes
|
|
28
29
|
*/
|
|
29
|
-
get():
|
|
30
|
-
if (this.data.
|
|
30
|
+
get(): ProperNode[] {
|
|
31
|
+
if (this.data.items.length === 0) {
|
|
31
32
|
const parsed = parse(this.data);
|
|
32
|
-
const templated = createTemplate(parsed);
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
const templated = html(parsed, {
|
|
35
|
+
sanitiseBooleanAttributes: false,
|
|
36
|
+
});
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
this.data.items.splice(
|
|
39
|
+
0,
|
|
40
|
+
this.data.items.length,
|
|
41
|
+
...templated.map(node => ({
|
|
42
|
+
nodes: [node as ProperNode],
|
|
43
|
+
})),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
mapNodes(
|
|
47
|
+
this.data,
|
|
48
|
+
this.data.items.flatMap(item => item.fragment?.get() ?? item.nodes),
|
|
49
|
+
);
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
return [
|
|
52
|
+
return [
|
|
53
|
+
...this.data.items.flatMap(item => item.fragment?.get() ?? item.nodes),
|
|
54
|
+
];
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/**
|
|
43
58
|
* Removes the fragment from the DOM
|
|
44
59
|
*/
|
|
45
60
|
remove(): void {
|
|
46
|
-
const {length} = this.data.
|
|
61
|
+
const {length} = this.data.items;
|
|
47
62
|
|
|
48
63
|
for (let index = 0; index < length; index += 1) {
|
|
49
|
-
const
|
|
64
|
+
const {fragment, nodes} = this.data.items[index];
|
|
65
|
+
|
|
66
|
+
fragment?.remove();
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
removeNodes(nodes);
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
this.data.
|
|
71
|
+
this.data.items.splice(0, length);
|
|
55
72
|
}
|
|
56
73
|
}
|
package/src/helpers/dom.ts
CHANGED
|
@@ -1,57 +1,54 @@
|
|
|
1
1
|
import {getString} from '@oscarpalmer/atoms/string';
|
|
2
|
-
import type {
|
|
3
|
-
import {isFragment,
|
|
2
|
+
import type {ProperNode} from '../models';
|
|
3
|
+
import {isFragment, isProperElement, isProperNode} from './index';
|
|
4
|
+
import {removeEvents} from '../node/event';
|
|
4
5
|
|
|
5
|
-
export function createNodes(value: unknown):
|
|
6
|
-
if (
|
|
7
|
-
return [
|
|
6
|
+
export function createNodes(value: unknown): ProperNode[] {
|
|
7
|
+
if (isFragment(value)) {
|
|
8
|
+
return value.get() as ProperNode[];
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
if (
|
|
11
|
-
return value
|
|
11
|
+
if (isProperNode(value)) {
|
|
12
|
+
return [value];
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
return [new Text(getString(value))];
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
template.innerHTML = html;
|
|
21
|
-
|
|
22
|
-
const cloned = template.content.cloneNode(true);
|
|
18
|
+
export function removeNodes(nodes: ProperNode[]): void {
|
|
19
|
+
sanitiseNodes(nodes);
|
|
23
20
|
|
|
24
|
-
const
|
|
25
|
-
...(cloned instanceof Element ? cloned.querySelectorAll('script') : []),
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const {length} = scripts;
|
|
21
|
+
const {length} = nodes;
|
|
29
22
|
|
|
30
23
|
for (let index = 0; index < length; index += 1) {
|
|
31
|
-
|
|
24
|
+
nodes[index].remove();
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
cloned.normalize();
|
|
35
|
-
|
|
36
|
-
return cloned;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getNodes(node: Node): Node[] {
|
|
40
|
-
return /^documentfragment$/i.test(node.constructor.name)
|
|
41
|
-
? [...node.childNodes]
|
|
42
|
-
: [node];
|
|
43
26
|
}
|
|
44
27
|
|
|
45
|
-
export function replaceNodes(from:
|
|
28
|
+
export function replaceNodes(from: ProperNode[], to: ProperNode[]): void {
|
|
46
29
|
const {length} = from;
|
|
47
30
|
|
|
48
31
|
for (let index = 0; index < length; index += 1) {
|
|
49
|
-
const node = from[index];
|
|
50
|
-
|
|
51
32
|
if (index === 0) {
|
|
52
|
-
|
|
33
|
+
from[index].replaceWith(...to);
|
|
53
34
|
} else {
|
|
54
|
-
|
|
35
|
+
from[index].remove();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sanitiseNodes(nodes: ProperNode[]): void {
|
|
41
|
+
const {length} = nodes;
|
|
42
|
+
|
|
43
|
+
for (let index = 0; index < length; index += 1) {
|
|
44
|
+
const node = nodes[index];
|
|
45
|
+
|
|
46
|
+
if (isProperElement(node)) {
|
|
47
|
+
removeEvents(node);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (node.hasChildNodes()) {
|
|
51
|
+
sanitiseNodes([...node.childNodes] as ProperNode[]);
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
54
|
}
|
package/src/helpers/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {Fragment} from '../fragment';
|
|
2
|
-
import type {
|
|
2
|
+
import type {ProperElement, ProperNode} from '../models';
|
|
3
3
|
|
|
4
4
|
export function isFragment(value: unknown): value is Fragment {
|
|
5
5
|
return (
|
|
@@ -10,14 +10,10 @@ export function isFragment(value: unknown): value is Fragment {
|
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
13
|
+
export function isProperElement(value: unknown): value is ProperElement {
|
|
14
14
|
return value instanceof HTMLElement || value instanceof SVGElement;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function
|
|
18
|
-
return
|
|
19
|
-
value instanceof Comment ||
|
|
20
|
-
value instanceof Element ||
|
|
21
|
-
value instanceof Text
|
|
22
|
-
);
|
|
17
|
+
export function isProperNode(value: unknown): value is ProperNode {
|
|
18
|
+
return value instanceof CharacterData || value instanceof Element;
|
|
23
19
|
}
|
package/src/models.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import type {Fragment} from './fragment';
|
|
2
|
+
|
|
1
3
|
export type FragmentData = {
|
|
2
4
|
expressions: unknown[];
|
|
3
|
-
|
|
5
|
+
items: FragmentItem[];
|
|
4
6
|
strings: TemplateStringsArray;
|
|
5
7
|
values: unknown[];
|
|
6
8
|
};
|
|
7
9
|
|
|
8
|
-
export type
|
|
10
|
+
export type FragmentItem = {
|
|
11
|
+
fragment?: Fragment;
|
|
12
|
+
nodes: ProperNode[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ProperElement = HTMLElement | SVGElement;
|
|
9
16
|
|
|
10
|
-
export type
|
|
17
|
+
export type ProperNode = CharacterData | Element;
|