@oscarpalmer/abydon 0.11.0 → 0.13.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/abydon.full.js +1247 -0
- package/dist/fragment.js +60 -0
- package/dist/helpers/dom.js +28 -0
- package/dist/helpers/index.js +11 -0
- package/dist/index.js +6 -885
- package/dist/node/attribute/index.js +34 -0
- package/dist/node/attribute/value.js +58 -0
- package/dist/node/event.js +32 -0
- package/dist/node/index.js +52 -0
- package/dist/node/value.js +93 -0
- package/dist/parse.js +21 -0
- package/package.json +41 -28
- package/src/fragment.ts +36 -15
- package/src/helpers/dom.ts +10 -10
- package/src/helpers/index.ts +0 -9
- package/src/index.ts +11 -1
- package/src/models.ts +7 -4
- package/src/node/attribute/index.ts +26 -7
- package/src/node/attribute/value.ts +31 -20
- package/src/node/event.ts +9 -9
- package/src/node/index.ts +22 -9
- package/src/node/value.ts +38 -37
- package/src/{html.ts → parse.ts} +5 -14
- package/types/fragment.d.cts +27 -0
- package/types/fragment.d.ts +1 -2
- package/types/helpers/dom.d.cts +7 -0
- package/types/helpers/dom.d.ts +3 -4
- package/types/helpers/index.d.cts +29 -0
- package/types/helpers/index.d.ts +0 -3
- package/types/index.d.cts +279 -4
- package/types/index.d.ts +4 -1
- package/types/models.d.cts +107 -0
- package/types/models.d.ts +8 -3
- package/types/node/attribute/index.d.cts +109 -0
- package/types/node/attribute/index.d.ts +3 -2
- package/types/node/attribute/value.d.cts +109 -0
- package/types/node/attribute/value.d.ts +3 -2
- package/types/node/event.d.cts +7 -0
- package/types/node/event.d.ts +3 -3
- package/types/node/index.d.cts +108 -0
- package/types/node/index.d.ts +2 -2
- package/types/node/value.d.cts +108 -0
- package/types/node/value.d.ts +2 -2
- package/types/parse.d.cts +108 -0
- package/types/parse.d.ts +2 -0
- package/dist/fragment.mjs +0 -58
- package/dist/helpers/dom.mjs +0 -44
- package/dist/helpers/index.mjs +0 -25
- package/dist/html.mjs +0 -36
- package/dist/index.mjs +0 -5
- package/dist/node/attribute/index.mjs +0 -35
- package/dist/node/attribute/value.mjs +0 -89
- package/dist/node/event.mjs +0 -39
- package/dist/node/index.mjs +0 -55
- package/dist/node/value.mjs +0 -123
- package/types/html.d.ts +0 -4
- /package/dist/{models.mjs → models.js} +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mapEvent } from "../event.js";
|
|
2
|
+
import { setAttribute } from "./value.js";
|
|
3
|
+
import { computed, isReactive } from "@oscarpalmer/mora";
|
|
4
|
+
var commentExpression = /^<!--abydon\.(\d+)-->$/;
|
|
5
|
+
function getValue(data, original) {
|
|
6
|
+
const matches = commentExpression.exec(original ?? "");
|
|
7
|
+
return matches == null ? original : data.values[+matches[1]];
|
|
8
|
+
}
|
|
9
|
+
function mapAttributes(data, element) {
|
|
10
|
+
const attributes = [...element.attributes];
|
|
11
|
+
const { length } = attributes;
|
|
12
|
+
for (let index = 0; index < length; index += 1) {
|
|
13
|
+
const { name, value } = attributes[index];
|
|
14
|
+
const actual = getValue(data, value);
|
|
15
|
+
if (name.startsWith("@")) mapEvent(element, name, actual);
|
|
16
|
+
else if (name.includes(".") || typeof value === "function" || isReactive(actual)) mapValue(data, element, name, actual);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function mapValue(data, element, name, value) {
|
|
20
|
+
switch (true) {
|
|
21
|
+
case typeof value === "function":
|
|
22
|
+
setComputedAttribute(data, element, name, value);
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
setAttribute(data, element, name, value);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function setComputedAttribute(data, element, name, callback) {
|
|
30
|
+
const value = computed(callback);
|
|
31
|
+
data.mora.values.add(value);
|
|
32
|
+
setAttribute(data, element, name, value);
|
|
33
|
+
}
|
|
34
|
+
export { mapAttributes };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isReactive } from "@oscarpalmer/mora";
|
|
2
|
+
import { getString } from "@oscarpalmer/atoms/string";
|
|
3
|
+
import { isBooleanAttribute, setAttribute as setAttribute$1 } from "@oscarpalmer/toretto/attribute";
|
|
4
|
+
var classExpression = /^class\./;
|
|
5
|
+
var styleFullExpression = /^style\.([\w-]+)(?:\.([\w-]+))?$/;
|
|
6
|
+
var stylePrefixExpression = /^style\./;
|
|
7
|
+
function setAttribute(data, element, name, value) {
|
|
8
|
+
element.removeAttribute(name);
|
|
9
|
+
switch (true) {
|
|
10
|
+
case classExpression.test(name):
|
|
11
|
+
setClasses(data, element, name, value);
|
|
12
|
+
return;
|
|
13
|
+
case stylePrefixExpression.test(name):
|
|
14
|
+
setStyle(data, element, name, value);
|
|
15
|
+
return;
|
|
16
|
+
default:
|
|
17
|
+
setValue(data, element, name, value);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function setClasses(data, element, name, value) {
|
|
22
|
+
function update(value$1) {
|
|
23
|
+
if (value$1 === true) element.classList.add(...classes);
|
|
24
|
+
else element.classList.remove(...classes);
|
|
25
|
+
}
|
|
26
|
+
const classes = name.slice(6).split(".");
|
|
27
|
+
if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
|
|
28
|
+
else update(value);
|
|
29
|
+
}
|
|
30
|
+
function setStyle(data, element, name, value) {
|
|
31
|
+
function update(value$1) {
|
|
32
|
+
if (value$1 == null || value$1 === false || value$1 === true && unit == null) element.style.removeProperty(property);
|
|
33
|
+
else element.style.setProperty(property, value$1 === true ? unit : getString(value$1));
|
|
34
|
+
}
|
|
35
|
+
const [, property, unit] = styleFullExpression.exec(name) ?? [];
|
|
36
|
+
if (property != null) if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
|
|
37
|
+
else update(value);
|
|
38
|
+
}
|
|
39
|
+
function setValue(data, element, name, value) {
|
|
40
|
+
const callback = isBooleanAttribute(name) && name in element ? name === "selected" ? updateSelected : updateProperty : setAttribute$1;
|
|
41
|
+
if (isReactive(value)) data.mora.subscribers.add(value.subscribe((next) => {
|
|
42
|
+
callback(element, name, next);
|
|
43
|
+
}));
|
|
44
|
+
else callback(element, name, value);
|
|
45
|
+
}
|
|
46
|
+
function triggerSelectChange(select) {
|
|
47
|
+
if (select != null) select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
48
|
+
}
|
|
49
|
+
function updateProperty(element, name, value) {
|
|
50
|
+
element[name] = value === true;
|
|
51
|
+
}
|
|
52
|
+
function updateSelected(element, name, value) {
|
|
53
|
+
const select = element.closest("select");
|
|
54
|
+
const options = [...select?.options ?? []];
|
|
55
|
+
if (select != null && options.includes(element)) triggerSelectChange(select);
|
|
56
|
+
updateProperty(element, name, value);
|
|
57
|
+
}
|
|
58
|
+
export { setAttribute };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
var controllers = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
var eventNamePattern = /^@([\w-]+)(?::([a-z:]+))?$/i;
|
|
3
|
+
var reason = "Event removed as element was removed from document by Abydon";
|
|
4
|
+
function getController(element) {
|
|
5
|
+
let controller = controllers.get(element);
|
|
6
|
+
if (controller == null) {
|
|
7
|
+
controller = new AbortController();
|
|
8
|
+
controllers.set(element, controller);
|
|
9
|
+
}
|
|
10
|
+
return controller;
|
|
11
|
+
}
|
|
12
|
+
function getOptions(options) {
|
|
13
|
+
const parts = options.split(":");
|
|
14
|
+
return {
|
|
15
|
+
capture: parts.includes("c") || parts.includes("capture"),
|
|
16
|
+
once: parts.includes("o") || parts.includes("once"),
|
|
17
|
+
passive: !parts.includes("a") && !parts.includes("active")
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function mapEvent(element, name, value) {
|
|
21
|
+
element.removeAttribute(name);
|
|
22
|
+
const [, type, options] = eventNamePattern.exec(name) ?? [];
|
|
23
|
+
if (typeof value === "function" && type != null) element.addEventListener(type, value, {
|
|
24
|
+
...getOptions(options ?? ""),
|
|
25
|
+
signal: getController(element).signal
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function removeEvents(element) {
|
|
29
|
+
controllers.get(element)?.abort(reason);
|
|
30
|
+
controllers.delete(element);
|
|
31
|
+
}
|
|
32
|
+
export { mapEvent, removeEvents };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isFragment } from "../helpers/index.js";
|
|
2
|
+
import { createNodes } from "../helpers/dom.js";
|
|
3
|
+
import { mapAttributes } from "./attribute/index.js";
|
|
4
|
+
import { setReactiveValue } from "./value.js";
|
|
5
|
+
import { computed, isReactive } from "@oscarpalmer/mora";
|
|
6
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
7
|
+
var commentExpression = /^abydon\.(\d+)$/;
|
|
8
|
+
function mapNode(data, comment) {
|
|
9
|
+
const matches = commentExpression.exec(comment.textContent ?? "");
|
|
10
|
+
const value = matches == null ? null : data.values[+matches[1]];
|
|
11
|
+
if (value != null) mapValue(data, comment, value);
|
|
12
|
+
}
|
|
13
|
+
function mapNodes(data, nodes) {
|
|
14
|
+
const { length } = nodes;
|
|
15
|
+
for (let index = 0; index < length; index += 1) {
|
|
16
|
+
const node = nodes[index];
|
|
17
|
+
if (node instanceof Comment) {
|
|
18
|
+
mapNode(data, node);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (isHTMLOrSVGElement(node)) mapAttributes(data, node);
|
|
22
|
+
if (node.hasChildNodes()) mapNodes(data, [...node.childNodes]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function mapValue(data, comment, value) {
|
|
26
|
+
switch (true) {
|
|
27
|
+
case typeof value === "function":
|
|
28
|
+
setComputedValue(data, comment, value);
|
|
29
|
+
break;
|
|
30
|
+
case isReactive(value):
|
|
31
|
+
setReactiveValue(data, comment, value);
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
replaceComment(data, comment, value);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function replaceComment(data, comment, value) {
|
|
39
|
+
const item = data.items.find((item$1) => item$1.nodes.includes(comment));
|
|
40
|
+
const nodes = createNodes(value);
|
|
41
|
+
if (item != null) {
|
|
42
|
+
item.fragments = isFragment(value) ? [value] : void 0;
|
|
43
|
+
item.nodes = nodes;
|
|
44
|
+
}
|
|
45
|
+
comment.replaceWith(...nodes);
|
|
46
|
+
}
|
|
47
|
+
function setComputedValue(data, comment, callback) {
|
|
48
|
+
const value = computed(callback);
|
|
49
|
+
data.mora.values.add(value);
|
|
50
|
+
setReactiveValue(data, comment, value);
|
|
51
|
+
}
|
|
52
|
+
export { mapNodes };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { compareArrays, isFragment } from "../helpers/index.js";
|
|
2
|
+
import { createNodes, replaceNodes } from "../helpers/dom.js";
|
|
3
|
+
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
4
|
+
import { getString } from "@oscarpalmer/atoms/string";
|
|
5
|
+
import { isChildNode } from "@oscarpalmer/toretto/is";
|
|
6
|
+
function removeFragments(fragments) {
|
|
7
|
+
if (fragments != null) {
|
|
8
|
+
const { length } = fragments;
|
|
9
|
+
for (let index = 0; index < length; index += 1) fragments[index].remove();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function setArray(fragments, nodes, comment, text, value) {
|
|
13
|
+
if (value.length === 0) return { nodes: setText(fragments, nodes, comment, text, value) };
|
|
14
|
+
let templates = value.filter((item) => isFragment(item) && item.identifier != null);
|
|
15
|
+
const identifiers = templates.map((fragment) => fragment.identifier);
|
|
16
|
+
const oldIdentifiers = fragments?.map((fragment) => fragment.identifier) ?? [];
|
|
17
|
+
if (new Set(identifiers).size !== templates.length) templates = [];
|
|
18
|
+
const noTemplates = templates.length === 0;
|
|
19
|
+
if (noTemplates || nodes == null || oldIdentifiers.some((identifier) => identifier == null)) return {
|
|
20
|
+
fragments: noTemplates ? void 0 : templates,
|
|
21
|
+
nodes: setNodes(fragments, nodes, comment, text, noTemplates ? value.flatMap((item) => createNodes(item)) : templates.flatMap((template) => template.get()))
|
|
22
|
+
};
|
|
23
|
+
const next = templates.map((template) => fragments?.find((fragment) => fragment.identifier === template.identifier) ?? template);
|
|
24
|
+
const comparison = compareArrays(fragments ?? [], templates);
|
|
25
|
+
if (comparison !== "removed") {
|
|
26
|
+
let position = nodes[0];
|
|
27
|
+
const before = comparison === "added" && !oldIdentifiers.includes(templates[0].identifier);
|
|
28
|
+
const items = next.flatMap((fragment) => fragment.get().flatMap((node) => ({
|
|
29
|
+
identifier: fragment.identifier,
|
|
30
|
+
value: node
|
|
31
|
+
})));
|
|
32
|
+
const { length: length$1 } = items;
|
|
33
|
+
for (let index = 0; index < length$1; index += 1) {
|
|
34
|
+
const item = items[index];
|
|
35
|
+
if (comparison === "dissimilar" || !oldIdentifiers.includes(item.identifier)) if (index === 0 && before) position.before(item.value);
|
|
36
|
+
else position.after(item.value);
|
|
37
|
+
position = item.value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const toRemove = fragments?.filter((fragment) => !identifiers.includes(fragment.identifier)) ?? [];
|
|
41
|
+
const { length } = toRemove;
|
|
42
|
+
for (let index = 0; index < length; index += 1) toRemove[index].remove();
|
|
43
|
+
return {
|
|
44
|
+
fragments: next,
|
|
45
|
+
nodes: next.flatMap((fragment) => fragment.get())
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function setNodes(fragments, nodes, comment, text, next) {
|
|
49
|
+
if (nodes == null) {
|
|
50
|
+
if (comment.parentNode != null) replaceNodes([comment], next);
|
|
51
|
+
else if (text.parentNode != null) replaceNodes([text], next);
|
|
52
|
+
} else replaceNodes(nodes, next);
|
|
53
|
+
removeFragments(fragments);
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
56
|
+
function setReactiveValue(data, comment, reactive) {
|
|
57
|
+
const item = data.items.find((item$1) => item$1.nodes.includes(comment));
|
|
58
|
+
const text = new Text();
|
|
59
|
+
let fragments;
|
|
60
|
+
let nodes;
|
|
61
|
+
data.mora.subscribers.add(reactive.subscribe((value) => {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
const result = setArray(fragments, nodes, comment, text, value);
|
|
64
|
+
fragments = typeof result === "boolean" ? void 0 : result?.fragments;
|
|
65
|
+
nodes = typeof result === "boolean" ? result ? [text] : void 0 : result?.nodes;
|
|
66
|
+
} else {
|
|
67
|
+
const valueIsFragment = isFragment(value);
|
|
68
|
+
fragments = valueIsFragment ? [value] : void 0;
|
|
69
|
+
if (valueIsFragment || isChildNode(value)) nodes = setNodes(fragments, nodes, comment, text, createNodes(value));
|
|
70
|
+
else nodes = setText(fragments, nodes, comment, text, value);
|
|
71
|
+
}
|
|
72
|
+
if (item != null) {
|
|
73
|
+
item.fragments = fragments;
|
|
74
|
+
item.nodes = [...nodes ?? [comment]];
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
function setText(fragments, nodes, comment, text, value) {
|
|
79
|
+
const isNullable = isNullableOrWhitespace(value);
|
|
80
|
+
text.textContent = isNullable ? "" : getString(value);
|
|
81
|
+
let result = false;
|
|
82
|
+
if (nodes != null) {
|
|
83
|
+
replaceNodes(nodes, [isNullable ? comment : text]);
|
|
84
|
+
result = !isNullable;
|
|
85
|
+
} else if (isNullable && comment.parentNode == null) text.replaceWith(comment);
|
|
86
|
+
else if (!isNullable && text.parentNode == null) {
|
|
87
|
+
comment.replaceWith(text);
|
|
88
|
+
result = true;
|
|
89
|
+
}
|
|
90
|
+
removeFragments(fragments);
|
|
91
|
+
return result ? [text] : void 0;
|
|
92
|
+
}
|
|
93
|
+
export { setReactiveValue };
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
2
|
+
function handleExpression(data, prefix, expression) {
|
|
3
|
+
if (Array.isArray(expression)) {
|
|
4
|
+
const { length } = expression;
|
|
5
|
+
let expressions = "";
|
|
6
|
+
for (let index = 0; index < length; index += 1) expressions += handleExpression(data, "", expression[index]);
|
|
7
|
+
return `${prefix}${expressions}`;
|
|
8
|
+
}
|
|
9
|
+
if (typeof expression === "function" || typeof expression === "object" && expression != null) {
|
|
10
|
+
const index = data.values.push(expression) - 1;
|
|
11
|
+
return `${prefix}<!--abydon.${index}-->`;
|
|
12
|
+
}
|
|
13
|
+
return isNullableOrWhitespace(expression) ? prefix : `${prefix}${expression}`;
|
|
14
|
+
}
|
|
15
|
+
function parse(data) {
|
|
16
|
+
const { length } = data.strings;
|
|
17
|
+
let template = "";
|
|
18
|
+
for (let index = 0; index < length; index += 1) template += handleExpression(data, data.strings[index], data.expressions[index]);
|
|
19
|
+
return template;
|
|
20
|
+
}
|
|
21
|
+
export { parse };
|
package/package.json
CHANGED
|
@@ -4,48 +4,61 @@
|
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@oscarpalmer/atoms": "^0.
|
|
8
|
-
"@oscarpalmer/
|
|
9
|
-
"@oscarpalmer/toretto": "0.
|
|
7
|
+
"@oscarpalmer/atoms": "^0.108",
|
|
8
|
+
"@oscarpalmer/mora": "^0.21",
|
|
9
|
+
"@oscarpalmer/toretto": "^0.22"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@biomejs/biome": "^
|
|
13
|
-
"@
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
12
|
+
"@biomejs/biome": "^2.2",
|
|
13
|
+
"@oscarpalmer/oui": "^0.17",
|
|
14
|
+
"@rollup/plugin-node-resolve": "^16",
|
|
15
|
+
"@rollup/plugin-typescript": "^12.1",
|
|
16
|
+
"@types/node": "^24.3",
|
|
17
|
+
"@vitest/coverage-istanbul": "^3.2",
|
|
18
|
+
"dts-bundle-generator": "^9.5",
|
|
19
|
+
"glob": "^11",
|
|
20
|
+
"jsdom": "^26.1",
|
|
21
|
+
"rollup": "^4.50",
|
|
22
|
+
"tslib": "^2.8",
|
|
23
|
+
"typescript": "^5.9",
|
|
24
|
+
"vite": "npm:rolldown-vite@latest",
|
|
25
|
+
"vitest": "^3.2"
|
|
17
26
|
},
|
|
18
27
|
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
19
29
|
".": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"types": "./types/index.d.ts",
|
|
23
|
-
"default": "./dist/index.mjs"
|
|
24
|
-
},
|
|
25
|
-
"require": {
|
|
26
|
-
"types": "./types/index.d.cts",
|
|
27
|
-
"default": "./dist/index.js"
|
|
28
|
-
}
|
|
30
|
+
"types": "./types/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
29
32
|
}
|
|
30
33
|
},
|
|
31
|
-
"files": ["dist
|
|
34
|
+
"files": ["dist", "src", "types"],
|
|
35
|
+
"keywords": [
|
|
36
|
+
"components",
|
|
37
|
+
"dom",
|
|
38
|
+
"library",
|
|
39
|
+
"literals",
|
|
40
|
+
"reactive",
|
|
41
|
+
"signal",
|
|
42
|
+
"template",
|
|
43
|
+
"vanilla"
|
|
44
|
+
],
|
|
32
45
|
"license": "MIT",
|
|
33
|
-
"
|
|
34
|
-
"module": "dist/index.mjs",
|
|
46
|
+
"module": "dist/index.js",
|
|
35
47
|
"name": "@oscarpalmer/abydon",
|
|
36
48
|
"repository": {
|
|
37
49
|
"type": "git",
|
|
38
50
|
"url": "git+https://github.com/oscarpalmer/abydon.git"
|
|
39
51
|
},
|
|
40
52
|
"scripts": {
|
|
41
|
-
"build": "
|
|
42
|
-
"clean": "rm -rf ./dist && rm -rf ./types && rm -
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
53
|
+
"build": "npm run clean && npx vite build && npm run rollup:build && npm run types",
|
|
54
|
+
"clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
|
|
55
|
+
"rollup:build": "npx rollup -c",
|
|
56
|
+
"rollup:watch": "npx rollup -c --watch",
|
|
57
|
+
"test": "npx vitest --coverage",
|
|
58
|
+
"types": "npx tsc && npx dts-bundle-generator --config ./dts.config.cts --silent",
|
|
59
|
+
"watch": "npx vite build --watch"
|
|
47
60
|
},
|
|
48
61
|
"type": "module",
|
|
49
|
-
"types": "types/index.d.
|
|
50
|
-
"version": "0.
|
|
62
|
+
"types": "types/index.d.cts",
|
|
63
|
+
"version": "0.13.0"
|
|
51
64
|
}
|
package/src/fragment.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {Key} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {html} from '@oscarpalmer/toretto/html';
|
|
3
|
-
import {parse} from './html';
|
|
4
|
-
import type {FragmentData, ProperNode} from './models';
|
|
5
|
-
import {mapNodes} from './node';
|
|
6
3
|
import {removeNodes} from './helpers/dom';
|
|
4
|
+
import type {FragmentData} from './models';
|
|
5
|
+
import {mapNodes} from './node';
|
|
6
|
+
import {parse} from './parse';
|
|
7
7
|
|
|
8
8
|
export class Fragment {
|
|
9
9
|
private readonly $fragment = true;
|
|
@@ -18,6 +18,10 @@ export class Fragment {
|
|
|
18
18
|
expressions,
|
|
19
19
|
strings,
|
|
20
20
|
items: [],
|
|
21
|
+
mora: {
|
|
22
|
+
subscribers: new Set(),
|
|
23
|
+
values: new Set(),
|
|
24
|
+
},
|
|
21
25
|
values: [],
|
|
22
26
|
};
|
|
23
27
|
}
|
|
@@ -32,19 +36,19 @@ export class Fragment {
|
|
|
32
36
|
/**
|
|
33
37
|
* Gets a list of the fragment's nodes
|
|
34
38
|
*/
|
|
35
|
-
get():
|
|
39
|
+
get(): ChildNode[] {
|
|
36
40
|
if (this.data.items.length === 0) {
|
|
37
41
|
const parsed = parse(this.data);
|
|
38
42
|
|
|
39
43
|
const templated = html(parsed, {
|
|
40
|
-
|
|
44
|
+
sanitizeBooleanAttributes: false,
|
|
41
45
|
});
|
|
42
46
|
|
|
43
47
|
this.data.items.splice(
|
|
44
48
|
0,
|
|
45
49
|
this.data.items.length,
|
|
46
50
|
...templated.map(node => ({
|
|
47
|
-
nodes: [node as
|
|
51
|
+
nodes: [node as ChildNode],
|
|
48
52
|
})),
|
|
49
53
|
);
|
|
50
54
|
|
|
@@ -75,19 +79,36 @@ export class Fragment {
|
|
|
75
79
|
* Removes the fragment from the DOM
|
|
76
80
|
*/
|
|
77
81
|
remove(): void {
|
|
78
|
-
|
|
82
|
+
removeFragment(this.data);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
function removeFragment(data: FragmentData): void {
|
|
87
|
+
removeMora(data);
|
|
88
|
+
|
|
89
|
+
const {length} = data.items;
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
for (let index = 0; index < length; index += 1) {
|
|
92
|
+
const {fragments, nodes} = data.items[index];
|
|
93
|
+
const {length} = fragments ?? [];
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
for (let index = 0; index < length; index += 1) {
|
|
96
|
+
fragments?.[index]?.remove();
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
|
|
99
|
+
removeNodes(nodes);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
data.items.splice(0, length);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function removeMora(data: FragmentData): void {
|
|
106
|
+
const unsubscribers = [...data.mora.subscribers];
|
|
107
|
+
|
|
108
|
+
data.mora.subscribers.clear();
|
|
109
|
+
data.mora.values.clear();
|
|
110
|
+
|
|
111
|
+
for (const unsubscribe of unsubscribers) {
|
|
112
|
+
unsubscribe();
|
|
92
113
|
}
|
|
93
114
|
}
|
package/src/helpers/dom.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {getString} from '@oscarpalmer/atoms/string';
|
|
2
|
-
import
|
|
3
|
-
import {isFragment, isProperElement, isProperNode} from './index';
|
|
2
|
+
import {isChildNode, isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
4
3
|
import {removeEvents} from '../node/event';
|
|
4
|
+
import {isFragment} from './index';
|
|
5
5
|
|
|
6
|
-
export function createNodes(value: unknown):
|
|
6
|
+
export function createNodes(value: unknown): ChildNode[] {
|
|
7
7
|
if (isFragment(value)) {
|
|
8
|
-
return value.get() as
|
|
8
|
+
return value.get() as ChildNode[];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
if (
|
|
11
|
+
if (isChildNode(value)) {
|
|
12
12
|
return [value];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
return [new Text(getString(value))];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function removeNodes(nodes:
|
|
18
|
+
export function removeNodes(nodes: ChildNode[]): void {
|
|
19
19
|
sanitiseNodes(nodes);
|
|
20
20
|
|
|
21
21
|
const {length} = nodes;
|
|
@@ -25,7 +25,7 @@ export function removeNodes(nodes: ProperNode[]): void {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function replaceNodes(from:
|
|
28
|
+
export function replaceNodes(from: ChildNode[], to: ChildNode[]): void {
|
|
29
29
|
from[0]?.replaceWith(...to);
|
|
30
30
|
|
|
31
31
|
const {length} = from;
|
|
@@ -35,18 +35,18 @@ export function replaceNodes(from: ProperNode[], to: ProperNode[]): void {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function sanitiseNodes(nodes:
|
|
38
|
+
function sanitiseNodes(nodes: ChildNode[]): void {
|
|
39
39
|
const {length} = nodes;
|
|
40
40
|
|
|
41
41
|
for (let index = 0; index < length; index += 1) {
|
|
42
42
|
const node = nodes[index];
|
|
43
43
|
|
|
44
|
-
if (
|
|
44
|
+
if (isHTMLOrSVGElement(node)) {
|
|
45
45
|
removeEvents(node);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (node.hasChildNodes()) {
|
|
49
|
-
sanitiseNodes([...node.childNodes] as
|
|
49
|
+
sanitiseNodes([...node.childNodes] as ChildNode[]);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/helpers/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {Fragment} from '../fragment';
|
|
2
|
-
import type {ProperElement, ProperNode} from '../models';
|
|
3
2
|
|
|
4
3
|
export function compareArrays(
|
|
5
4
|
first: unknown[],
|
|
@@ -28,11 +27,3 @@ export function isFragment(value: unknown): value is Fragment {
|
|
|
28
27
|
value.$fragment === true
|
|
29
28
|
);
|
|
30
29
|
}
|
|
31
|
-
|
|
32
|
-
export function isProperElement(value: unknown): value is ProperElement {
|
|
33
|
-
return value instanceof HTMLElement || value instanceof SVGElement;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function isProperNode(value: unknown): value is ProperNode {
|
|
37
|
-
return value instanceof CharacterData || value instanceof Element;
|
|
38
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
import '@oscarpalmer/mora';
|
|
2
|
+
import {Fragment} from './fragment';
|
|
3
|
+
|
|
4
|
+
export function html(
|
|
5
|
+
strings: TemplateStringsArray,
|
|
6
|
+
...values: unknown[]
|
|
7
|
+
): Fragment {
|
|
8
|
+
return new Fragment(strings, values);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export * from '@oscarpalmer/mora';
|
|
1
12
|
export type {Fragment} from './fragment';
|
|
2
|
-
export {html} from './html';
|
package/src/models.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type {Key} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import type {Reactive} from '@oscarpalmer/mora';
|
|
2
3
|
import type {Fragment} from './fragment';
|
|
3
4
|
|
|
4
5
|
export type FragmentData = {
|
|
5
6
|
expressions: unknown[];
|
|
6
7
|
identifier?: Key;
|
|
7
8
|
items: FragmentItem[];
|
|
9
|
+
mora: MoraData;
|
|
8
10
|
strings: TemplateStringsArray;
|
|
9
11
|
values: unknown[];
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export type FragmentItem = {
|
|
13
15
|
fragments?: Fragment[];
|
|
14
|
-
nodes:
|
|
16
|
+
nodes: ChildNode[];
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
type MoraData = {
|
|
20
|
+
subscribers: Set<() => void>;
|
|
21
|
+
values: Set<Reactive<unknown>>;
|
|
22
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
-
import {computed, isReactive} from '@oscarpalmer/
|
|
3
|
-
import type {
|
|
2
|
+
import {computed, isReactive} from '@oscarpalmer/mora';
|
|
3
|
+
import type {HTMLOrSVGElement} from '@oscarpalmer/toretto/models';
|
|
4
|
+
import type {FragmentData} from '../../models';
|
|
4
5
|
import {mapEvent} from '../event';
|
|
5
6
|
import {setAttribute} from './value';
|
|
6
7
|
|
|
@@ -14,7 +15,7 @@ function getValue(data: FragmentData, original: string): unknown {
|
|
|
14
15
|
|
|
15
16
|
export function mapAttributes(
|
|
16
17
|
data: FragmentData,
|
|
17
|
-
element:
|
|
18
|
+
element: HTMLOrSVGElement,
|
|
18
19
|
): void {
|
|
19
20
|
const attributes = [...element.attributes];
|
|
20
21
|
const {length} = attributes;
|
|
@@ -31,19 +32,37 @@ export function mapAttributes(
|
|
|
31
32
|
typeof value === 'function' ||
|
|
32
33
|
isReactive(actual)
|
|
33
34
|
) {
|
|
34
|
-
mapValue(element, name, actual);
|
|
35
|
+
mapValue(data, element, name, actual);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
function mapValue(
|
|
40
|
+
function mapValue(
|
|
41
|
+
data: FragmentData,
|
|
42
|
+
element: HTMLOrSVGElement,
|
|
43
|
+
name: string,
|
|
44
|
+
value: unknown,
|
|
45
|
+
): void {
|
|
40
46
|
switch (true) {
|
|
41
47
|
case typeof value === 'function':
|
|
42
|
-
|
|
48
|
+
setComputedAttribute(data, element, name, value as GenericCallback);
|
|
43
49
|
break;
|
|
44
50
|
|
|
45
51
|
default:
|
|
46
|
-
setAttribute(element, name, value);
|
|
52
|
+
setAttribute(data, element, name, value);
|
|
47
53
|
break;
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
|
|
57
|
+
function setComputedAttribute(
|
|
58
|
+
data: FragmentData,
|
|
59
|
+
element: HTMLOrSVGElement,
|
|
60
|
+
name: string,
|
|
61
|
+
callback: GenericCallback,
|
|
62
|
+
): void {
|
|
63
|
+
const value = computed(callback);
|
|
64
|
+
|
|
65
|
+
data.mora.values.add(value);
|
|
66
|
+
|
|
67
|
+
setAttribute(data, element, name, value);
|
|
68
|
+
}
|