@oscarpalmer/abydon 0.21.0 → 0.23.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/README.md +2 -0
- package/dist/abydon.full.mjs +422 -359
- package/dist/attribute/index.d.mts +7 -0
- package/dist/attribute/index.mjs +47 -0
- package/dist/{node/attribute → attribute}/value.d.mts +2 -2
- package/dist/attribute/value.mjs +53 -0
- package/dist/constants.d.mts +8 -8
- package/dist/constants.mjs +15 -15
- package/dist/fragment.d.mts +30 -13
- package/dist/fragment.mjs +40 -18
- package/dist/fragments.d.mts +3 -7
- package/dist/fragments.mjs +6 -12
- package/dist/helpers/dom.d.mts +2 -3
- package/dist/helpers/dom.mjs +2 -4
- package/dist/helpers/index.d.mts +16 -2
- package/dist/helpers/index.mjs +17 -1
- package/dist/index.d.mts +35 -7
- package/dist/index.mjs +39 -8
- package/dist/models.d.mts +6 -4
- package/dist/node/event.mjs +9 -4
- package/dist/node/index.mjs +26 -32
- package/dist/node/value.mjs +70 -56
- package/dist/parse.mjs +6 -4
- package/package.json +6 -5
- package/src/attribute/index.ts +89 -0
- package/src/attribute/value.ts +107 -0
- package/src/constants.ts +19 -19
- package/src/fragment.ts +43 -21
- package/src/fragments.ts +8 -15
- package/src/helpers/dom.ts +3 -5
- package/src/helpers/index.ts +25 -1
- package/src/index.ts +47 -7
- package/src/models.ts +6 -4
- package/src/node/event.ts +12 -5
- package/src/node/index.ts +36 -56
- package/src/node/value.ts +121 -104
- package/src/parse.ts +15 -5
- package/dist/node/attribute/index.d.mts +0 -6
- package/dist/node/attribute/index.mjs +0 -51
- package/dist/node/attribute/value.mjs +0 -65
- package/src/node/attribute/index.ts +0 -100
- package/src/node/attribute/value.ts +0 -158
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,55 @@
|
|
|
1
|
+
import { isFragment, isFragments } from "./helpers/index.mjs";
|
|
1
2
|
import { Fragments } from "./fragments.mjs";
|
|
2
3
|
import { Fragment } from "./fragment.mjs";
|
|
3
|
-
import "@oscarpalmer/mora";
|
|
4
|
+
import { isArray } from "@oscarpalmer/mora";
|
|
4
5
|
export * from "@oscarpalmer/mora";
|
|
5
6
|
//#region src/index.ts
|
|
6
7
|
/**
|
|
7
|
-
* Create
|
|
8
|
+
* Create a _Fragments_ instance from a reactive array
|
|
9
|
+
*
|
|
10
|
+
* _A Fragments instance can be used to efficiently render a list of items that may change over time, using unique identifiers to track each item, only adding, removing, or updating each related Fragment._
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const fruits = array(['Apple', 'Banana', 'Cherry']);
|
|
15
|
+
* const items = fragments(
|
|
16
|
+
* fruits,
|
|
17
|
+
* fruit => fruit, // Identifies a unique item
|
|
18
|
+
* fruit => html`<p>${fruit}</p>`, // Creates a Fragment from an item
|
|
19
|
+
* );
|
|
20
|
+
* html`${items}`.appendTo(document.body) // Renders '<p>Apple</p><p>Banana</p><p>Cherry</p>'
|
|
21
|
+
* fruits.push(['Date', 'Elderberry', 'Fig']); // Appends '<p>Date</p><p>Elderberry</p><p>Fig</p>'
|
|
22
|
+
* // without re-rendering the existing Fragments
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
8
25
|
* @param array Reactive array
|
|
9
|
-
* @param identify Function to identify item
|
|
10
|
-
* @param fragment Function to create
|
|
11
|
-
* @returns
|
|
26
|
+
* @param identify Function to identify item uniquely _(non-nullable)_
|
|
27
|
+
* @param fragment Function to create _Fragment_ from item
|
|
28
|
+
* @returns _Fragments_
|
|
12
29
|
*/
|
|
13
30
|
function fragments(array, identify, fragment) {
|
|
31
|
+
if (!isArray(array)) throw new TypeError("Fragments array must be a reactive array");
|
|
32
|
+
if (typeof identify !== "function") throw new TypeError("Fragments identify must be a function");
|
|
33
|
+
if (typeof fragment !== "function") throw new TypeError("Fragments fragment must be a function");
|
|
14
34
|
return new Fragments(array, identify, fragment);
|
|
15
35
|
}
|
|
16
36
|
/**
|
|
17
|
-
* Create
|
|
18
|
-
*
|
|
37
|
+
* Create a _Fragment_ from a template
|
|
38
|
+
*
|
|
39
|
+
* _A Fragment can be used to efficiently render a template that may change over time, only updating the necessary parts of the DOM._
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const name = signal('World');
|
|
44
|
+
* const fragment = html`<p>Hello, ${name}!</p>`;
|
|
45
|
+
* fragment.appendTo(document.body); // Renders '<p>Hello, World!</p>'
|
|
46
|
+
* name.set('Alice'); // Replaces 'World' with 'Alice'
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @returns _Fragment_
|
|
19
50
|
*/
|
|
20
51
|
function html(template, ...values) {
|
|
21
52
|
return new Fragment(template, values);
|
|
22
53
|
}
|
|
23
54
|
//#endregion
|
|
24
|
-
export { fragments, html };
|
|
55
|
+
export { fragments, html, isFragment, isFragments };
|
package/dist/models.d.mts
CHANGED
|
@@ -2,16 +2,18 @@ import { Fragment } from "./fragment.mjs";
|
|
|
2
2
|
import { Reactive, ReactiveArray, Unsubscribe } from "@oscarpalmer/mora";
|
|
3
3
|
|
|
4
4
|
//#region src/models.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for a _Fragment_
|
|
7
|
+
*/
|
|
5
8
|
type FragmentConfiguration = {
|
|
6
9
|
/**
|
|
7
10
|
* Should the template be cached? _(defaults to `true`)_
|
|
8
11
|
*/
|
|
9
12
|
cache?: boolean;
|
|
10
13
|
/**
|
|
11
|
-
* Identifier for the
|
|
14
|
+
* Identifier for the _Fragment_
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
* which helps prevent re-rendering in certain scenarios)_
|
|
16
|
+
* _An identifier can be used to uniquely identify a Fragment, which helps prevent re-rendering in reactive arrays and Fragments_
|
|
15
17
|
*/
|
|
16
18
|
identifier?: unknown;
|
|
17
19
|
};
|
|
@@ -19,7 +21,7 @@ type FragmentData = {
|
|
|
19
21
|
expressions: unknown[];
|
|
20
22
|
items: FragmentItem[];
|
|
21
23
|
mora: MoraData;
|
|
22
|
-
strings: TemplateStringsArray;
|
|
24
|
+
strings: TemplateStringsArray | string[];
|
|
23
25
|
template?: string;
|
|
24
26
|
values: unknown[];
|
|
25
27
|
};
|
package/dist/node/event.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_SUBMIT,
|
|
1
|
+
import { CHANGE_INPUTS, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_SUBMIT, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE } from "../constants.mjs";
|
|
2
2
|
import { on } from "@oscarpalmer/toretto/event";
|
|
3
3
|
//#region src/node/event.ts
|
|
4
4
|
function getOptions(options) {
|
|
@@ -12,14 +12,19 @@ function getOptions(options) {
|
|
|
12
12
|
function getType(element, type) {
|
|
13
13
|
if (type !== "on") return type;
|
|
14
14
|
if (element instanceof HTMLInputElement) {
|
|
15
|
-
if (
|
|
15
|
+
if (CHANGE_INPUTS.has(element.type)) return EVENT_CHANGE;
|
|
16
16
|
return element.type === "submit" ? EVENT_SUBMIT : EVENT_INPUT;
|
|
17
17
|
}
|
|
18
18
|
return EVENT_DEFAULTS[element.tagName] ?? type;
|
|
19
19
|
}
|
|
20
20
|
function mapEvent(element, name, value) {
|
|
21
|
-
const [, type, options] = EXPRESSION_EVENT_NAME.exec(name)
|
|
22
|
-
|
|
21
|
+
const [, type, options] = EXPRESSION_EVENT_NAME.exec(name);
|
|
22
|
+
const values = Array.isArray(value) ? value : [value];
|
|
23
|
+
const { length } = values;
|
|
24
|
+
for (let index = 0; index < length; index += 1) {
|
|
25
|
+
const item = values[index];
|
|
26
|
+
if (typeof item === "function") on(element, getType(element, type), item, getOptions(options ?? ""));
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
//#endregion
|
|
25
30
|
export { mapEvent };
|
package/dist/node/index.mjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { EXPRESSION_ABYDON_CONTENT, EXPRESSION_TEXTAREA_VALUE } from "../constants.mjs";
|
|
2
|
-
import { isFragment, isFragments } from "../helpers/index.mjs";
|
|
3
|
-
import { handleFragments } from "../fragments.mjs";
|
|
2
|
+
import { isFragment, isFragments, setComputedValue } from "../helpers/index.mjs";
|
|
3
|
+
import { fragmentsStates, handleFragments } from "../fragments.mjs";
|
|
4
4
|
import { createNodes } from "../helpers/dom.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { mapAttributes } from "./attribute/index.mjs";
|
|
5
|
+
import { mapAttributeValue, mapAttributes } from "../attribute/index.mjs";
|
|
7
6
|
import { setReactiveValue } from "./value.mjs";
|
|
8
|
-
import {
|
|
7
|
+
import { isReactive } from "@oscarpalmer/mora";
|
|
9
8
|
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
10
9
|
//#region src/node/index.ts
|
|
11
10
|
function mapNode(data, comment) {
|
|
12
|
-
const matches = EXPRESSION_ABYDON_CONTENT.exec(comment.textContent
|
|
11
|
+
const matches = EXPRESSION_ABYDON_CONTENT.exec(comment.textContent);
|
|
13
12
|
const value = matches == null ? null : data.values[+matches[1]];
|
|
14
13
|
if (value != null) mapValue(data, comment, value);
|
|
15
14
|
}
|
|
@@ -21,39 +20,29 @@ function mapNodes(data, nodes) {
|
|
|
21
20
|
mapNode(data, node);
|
|
22
21
|
continue;
|
|
23
22
|
}
|
|
24
|
-
if (node
|
|
25
|
-
|
|
23
|
+
if (isHTMLOrSVGElement(node)) {
|
|
24
|
+
let ignoreValueAttribute = false;
|
|
25
|
+
if (node instanceof HTMLTextAreaElement) ignoreValueAttribute = mapTextarea(data, node);
|
|
26
|
+
mapAttributes(data, node, ignoreValueAttribute);
|
|
27
|
+
}
|
|
26
28
|
if (node.hasChildNodes()) mapNodes(data, [...node.childNodes]);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
function mapTextarea(data, element) {
|
|
30
|
-
const [, index] = EXPRESSION_TEXTAREA_VALUE.exec(element.value) ?? [];
|
|
31
|
-
if (index == null) return;
|
|
32
|
+
const [, index] = EXPRESSION_TEXTAREA_VALUE.exec(element.textContent) ?? EXPRESSION_TEXTAREA_VALUE.exec(element.value) ?? [];
|
|
33
|
+
if (index == null) return false;
|
|
34
|
+
element.textContent = "";
|
|
32
35
|
element.value = "";
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
element.value = String(value.peek());
|
|
36
|
-
mapEvent(element, "@on", () => {
|
|
37
|
-
value.set(element.value);
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
let reactive;
|
|
42
|
-
if (typeof value === "function") reactive = computed(value);
|
|
43
|
-
else if (isComputed(value)) reactive = value;
|
|
44
|
-
if (reactive == null) element.value = String(value);
|
|
45
|
-
else data.mora.subscribers.add(reactive.subscribe((value) => {
|
|
46
|
-
element.value = String(value);
|
|
47
|
-
}));
|
|
36
|
+
mapAttributeValue(data, element, "value", data.values[Number.parseInt(index, 10)]);
|
|
37
|
+
return true;
|
|
48
38
|
}
|
|
49
39
|
function mapValue(data, comment, value) {
|
|
50
40
|
switch (true) {
|
|
51
41
|
case typeof value === "function":
|
|
52
|
-
|
|
42
|
+
setComputedNode(data, comment, value);
|
|
53
43
|
break;
|
|
54
44
|
case isFragments(value):
|
|
55
|
-
|
|
56
|
-
setReactiveValue(data, comment, value.items);
|
|
45
|
+
setFragmentsNode(data, comment, value);
|
|
57
46
|
break;
|
|
58
47
|
case isReactive(value):
|
|
59
48
|
setReactiveValue(data, comment, value);
|
|
@@ -72,10 +61,15 @@ function replaceComment(data, comment, value) {
|
|
|
72
61
|
}
|
|
73
62
|
comment.replaceWith(...nodes);
|
|
74
63
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
function setComputedNode(data, comment, callback) {
|
|
65
|
+
setComputedValue(data, callback, (computation) => {
|
|
66
|
+
setReactiveValue(data, comment, computation);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function setFragmentsNode(data, comment, fragments) {
|
|
70
|
+
const state = fragmentsStates.get(fragments);
|
|
71
|
+
handleFragments(state, false);
|
|
72
|
+
setReactiveValue(data, comment, state.mapped);
|
|
79
73
|
}
|
|
80
74
|
//#endregion
|
|
81
75
|
export { mapNodes };
|
package/dist/node/value.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { isChildNode } from "@oscarpalmer/toretto/is";
|
|
|
7
7
|
//#region src/node/value.ts
|
|
8
8
|
function addToArray(identifiers, items, nodes, added) {
|
|
9
9
|
let position = nodes[0];
|
|
10
|
-
const before = added && !identifiers.previous.has(items.templates[0].identifier);
|
|
10
|
+
const before = added && !identifiers.previous.set.has(items.templates[0].identifier);
|
|
11
11
|
const next = items.next.flatMap((fragment) => fragment.get().flatMap((node) => ({
|
|
12
12
|
identifier: fragment.identifier,
|
|
13
13
|
value: node
|
|
@@ -15,19 +15,48 @@ function addToArray(identifiers, items, nodes, added) {
|
|
|
15
15
|
const { length } = next;
|
|
16
16
|
for (let index = 0; index < length; index += 1) {
|
|
17
17
|
const node = next[index];
|
|
18
|
-
if (!(added && identifiers.previous.has(node.identifier))) if (index === 0 && before) position.before(node.value);
|
|
18
|
+
if (!(added && identifiers.previous.set.has(node.identifier))) if (index === 0 && before) position.before(node.value);
|
|
19
19
|
else position.after(node.value);
|
|
20
20
|
position = node.value;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
function getArrayData(item, values) {
|
|
24
|
+
const { length } = values;
|
|
25
|
+
const next = [];
|
|
26
|
+
let templates = [];
|
|
27
|
+
for (let index = 0; index < length; index += 1) {
|
|
28
|
+
const value = values[index];
|
|
29
|
+
if (isFragment(value) && value.identifier != null) {
|
|
30
|
+
next.push(value.identifier);
|
|
31
|
+
templates.push(value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const previous = item.fragments?.map((fragment) => fragment.identifier) ?? [];
|
|
35
|
+
const nextSet = new Set(next);
|
|
36
|
+
if (nextSet.size !== templates.length) templates = [];
|
|
37
|
+
return {
|
|
38
|
+
next: {
|
|
39
|
+
array: next,
|
|
40
|
+
set: nextSet
|
|
41
|
+
},
|
|
42
|
+
previous: {
|
|
43
|
+
array: previous,
|
|
44
|
+
set: new Set(previous)
|
|
45
|
+
},
|
|
46
|
+
template: {
|
|
47
|
+
empty: templates.length === 0,
|
|
48
|
+
items: templates
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
23
52
|
function handleArray(identifiers, items, nodes) {
|
|
24
|
-
const next = items.templates.map((template) => items.fragments
|
|
25
|
-
const comparison = compareArrays(
|
|
53
|
+
const next = items.templates.map((template) => items.fragments.find((fragment) => fragment.identifier === template.identifier) ?? template);
|
|
54
|
+
const comparison = compareArrays(identifiers.previous.array, identifiers.next.array);
|
|
26
55
|
if (comparison !== "removed") addToArray(identifiers, {
|
|
27
56
|
...items,
|
|
28
57
|
next
|
|
29
58
|
}, nodes, comparison === ARRAY_COMPARISON_ADDED);
|
|
30
|
-
const toRemove = items.fragments
|
|
59
|
+
const toRemove = items.fragments.filter((fragment) => !identifiers.next.set.has(fragment.identifier));
|
|
31
60
|
const { length } = toRemove;
|
|
32
61
|
for (let index = 0; index < length; index += 1) toRemove[index].remove();
|
|
33
62
|
return {
|
|
@@ -35,82 +64,67 @@ function handleArray(identifiers, items, nodes) {
|
|
|
35
64
|
nodes: next.flatMap((fragment) => fragment.get())
|
|
36
65
|
};
|
|
37
66
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
function removeArray(item, comment) {
|
|
68
|
+
const fragments = (item.fragments ?? []).slice();
|
|
69
|
+
const result = { nodes: setText(item, comment) };
|
|
70
|
+
removeFragmentsItems(fragments);
|
|
71
|
+
return result;
|
|
43
72
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else to = item.text == null ? [] : [item.text];
|
|
48
|
-
replaceNodes(item.nodes ?? [], to);
|
|
73
|
+
function removeFragmentsItems(fragments) {
|
|
74
|
+
const { length } = fragments;
|
|
75
|
+
for (let index = 0; index < length; index += 1) fragments[index].remove();
|
|
49
76
|
}
|
|
50
77
|
function setArray(item, comment, value) {
|
|
51
|
-
if (value.length === 0) return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
if (value.length === 0) return removeArray(item, comment);
|
|
79
|
+
const { next, previous, template } = getArrayData(item, value);
|
|
80
|
+
if (template.empty || item.nodes == null || previous.array.some((identifier) => identifier == null)) {
|
|
81
|
+
const fragments = (item.fragments ?? []).slice();
|
|
82
|
+
const result = {
|
|
83
|
+
fragments: template.empty ? void 0 : template.items,
|
|
84
|
+
nodes: replaceNodes(item.nodes ?? [comment], template.empty ? value.flatMap((item) => createNodes(item)) : template.items.flatMap((template) => template.get()))
|
|
85
|
+
};
|
|
86
|
+
removeFragmentsItems(fragments);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
61
89
|
return handleArray({
|
|
62
|
-
next
|
|
63
|
-
previous
|
|
90
|
+
next,
|
|
91
|
+
previous
|
|
64
92
|
}, {
|
|
65
|
-
|
|
66
|
-
|
|
93
|
+
fragments: item.fragments ?? [],
|
|
94
|
+
templates: template.items
|
|
67
95
|
}, item.nodes);
|
|
68
96
|
}
|
|
69
97
|
function setNodes(item, comment, next) {
|
|
70
|
-
|
|
71
|
-
if (comment.parentNode != null) replaceNodes([comment], next);
|
|
72
|
-
else if (item.text?.parentNode != null) replaceNodes([item.text], next);
|
|
73
|
-
} else replaceNodes(item.nodes, next);
|
|
74
|
-
removeFragments(item.fragments);
|
|
75
|
-
return next;
|
|
98
|
+
return replaceNodes(item.nodes ?? [comment], next);
|
|
76
99
|
}
|
|
77
100
|
function setReactiveValue(data, comment, reactive) {
|
|
78
101
|
let item = data.items.find((item) => item.nodes?.includes(comment));
|
|
79
102
|
item ??= {};
|
|
80
|
-
item.text = new Text();
|
|
81
103
|
data.mora.subscribers.add(reactive.subscribe((value) => {
|
|
82
104
|
if (Array.isArray(value)) setReactiveValueForArray(item, comment, value);
|
|
83
105
|
else setReactiveValueForSingle(item, comment, value);
|
|
84
|
-
item.nodes = [...item.nodes ?? [comment]];
|
|
85
106
|
}));
|
|
86
107
|
}
|
|
87
108
|
function setReactiveValueForArray(item, comment, value) {
|
|
88
|
-
const
|
|
89
|
-
item.fragments =
|
|
90
|
-
|
|
91
|
-
else item.nodes = void 0;
|
|
92
|
-
else item.nodes = result?.nodes;
|
|
109
|
+
const { fragments, nodes } = setArray(item, comment, value);
|
|
110
|
+
item.fragments = fragments;
|
|
111
|
+
item.nodes = nodes;
|
|
93
112
|
}
|
|
94
113
|
function setReactiveValueForSingle(item, comment, value) {
|
|
114
|
+
const fragments = (item.fragments ?? []).slice();
|
|
95
115
|
const valueIsFragment = isFragment(value);
|
|
96
|
-
item.fragments = valueIsFragment ? [value] : void 0;
|
|
97
116
|
if (valueIsFragment || isChildNode(value)) item.nodes = setNodes(item, comment, createNodes(value));
|
|
98
117
|
else item.nodes = setText(item, comment, value);
|
|
118
|
+
item.fragments = valueIsFragment ? [value] : void 0;
|
|
119
|
+
removeFragmentsItems(fragments);
|
|
99
120
|
}
|
|
100
121
|
function setText(item, comment, value) {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} else if (isNullable && comment.parentNode == null) item.text?.replaceWith(comment);
|
|
108
|
-
else if (!isNullable && item?.text?.parentNode == null) {
|
|
109
|
-
if (item.text != null) comment.replaceWith(item.text);
|
|
110
|
-
result = true;
|
|
111
|
-
}
|
|
112
|
-
removeFragments(item.fragments);
|
|
113
|
-
if (result) return item.text == null ? [] : [item.text];
|
|
122
|
+
const valueIsNullable = isNullableOrWhitespace(value);
|
|
123
|
+
item.text ??= new Text();
|
|
124
|
+
item.text.textContent = valueIsNullable ? "" : getString(value);
|
|
125
|
+
const result = valueIsNullable ? [comment] : [item.text];
|
|
126
|
+
replaceNodes(item.nodes ?? [comment], result);
|
|
127
|
+
return result;
|
|
114
128
|
}
|
|
115
129
|
//#endregion
|
|
116
130
|
export { setReactiveValue };
|
package/dist/parse.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { EXPRESSION_ABYDON_ATTRIBUTE_FULL } from "./constants.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_EVENT_ATTRIBUTE, WHITESPACE } from "./constants.mjs";
|
|
2
|
+
import { getString } from "@oscarpalmer/atoms/string";
|
|
3
3
|
//#region src/parse.ts
|
|
4
4
|
function handleExpression(data, prefix, expression) {
|
|
5
5
|
if (Array.isArray(expression)) {
|
|
6
|
+
if (EXPRESSION_EVENT_ATTRIBUTE.test(prefix.split(WHITESPACE).at(-1))) return transformExpression(prefix, data.values.push(expression) - 1);
|
|
6
7
|
const { length } = expression;
|
|
7
8
|
let expressions = "";
|
|
8
9
|
for (let index = 0; index < length; index += 1) expressions += handleExpression(data, "", expression[index]);
|
|
9
10
|
return `${prefix}${expressions}`;
|
|
10
11
|
}
|
|
11
12
|
if (typeof expression === "function" || typeof expression === "object" && expression != null) return transformExpression(prefix, data.values.push(expression) - 1);
|
|
12
|
-
|
|
13
|
+
const asString = getString(expression);
|
|
14
|
+
return asString.trim().length === 0 ? prefix : `${prefix}${asString}`;
|
|
13
15
|
}
|
|
14
16
|
function parse(data) {
|
|
15
17
|
if (data.template != null) return data.template;
|
|
@@ -22,7 +24,7 @@ function parse(data) {
|
|
|
22
24
|
return data.template;
|
|
23
25
|
}
|
|
24
26
|
function transformAttribute(_, name, index) {
|
|
25
|
-
return `
|
|
27
|
+
return `abydon-${name}="abydon.${index}"`;
|
|
26
28
|
}
|
|
27
29
|
function transformExpression(prefix, index) {
|
|
28
30
|
return `${prefix}<!--abydon.${index}-->`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oscarpalmer/abydon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"components",
|
|
6
6
|
"dom",
|
|
@@ -42,15 +42,16 @@
|
|
|
42
42
|
"test:leak": "npx vp test run --detect-async-leaks --coverage"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@oscarpalmer/atoms": "^0.
|
|
46
|
-
"@oscarpalmer/mora": "^0.
|
|
47
|
-
"@oscarpalmer/toretto": "^0.
|
|
45
|
+
"@oscarpalmer/atoms": "^0.185",
|
|
46
|
+
"@oscarpalmer/mora": "^0.29",
|
|
47
|
+
"@oscarpalmer/toretto": "^0.45"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@oscarpalmer/oui": "^0.20",
|
|
51
|
+
"@oxlint/plugins": "^1.62",
|
|
51
52
|
"@types/node": "^25.6",
|
|
52
53
|
"@vitest/coverage-istanbul": "^4.1",
|
|
53
|
-
"jsdom": "^29",
|
|
54
|
+
"jsdom": "^29.1",
|
|
54
55
|
"tsdown": "^0.21",
|
|
55
56
|
"typescript": "^5.9",
|
|
56
57
|
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {isSignal} from '@oscarpalmer/mora';
|
|
3
|
+
import {isInputElement} from '@oscarpalmer/toretto/is';
|
|
4
|
+
import {
|
|
5
|
+
EVENT_ON_VALUE,
|
|
6
|
+
EXPRESSION_ABYDON_ATTRIBUTE_PREFIX,
|
|
7
|
+
EXPRESSION_ABYDON_CONTENT,
|
|
8
|
+
EXPRESSION_EVENT_PREFIX,
|
|
9
|
+
PROPERTY_VALUE,
|
|
10
|
+
} from '../constants';
|
|
11
|
+
import {setComputedValue} from '../helpers';
|
|
12
|
+
import type {FragmentData} from '../models';
|
|
13
|
+
import {mapEvent} from '../node/event';
|
|
14
|
+
import {setAttribute} from './value';
|
|
15
|
+
|
|
16
|
+
function compareAttributes(first: Attr, second: Attr): number {
|
|
17
|
+
return first.name.localeCompare(second.name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getValue(data: FragmentData, original: string): unknown {
|
|
21
|
+
const matches = EXPRESSION_ABYDON_CONTENT.exec(original);
|
|
22
|
+
|
|
23
|
+
return matches == null ? original : data.values[+matches[1]];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function mapAttributeValue(
|
|
27
|
+
data: FragmentData,
|
|
28
|
+
element: HTMLElement | SVGElement,
|
|
29
|
+
name: string,
|
|
30
|
+
value: unknown,
|
|
31
|
+
): void {
|
|
32
|
+
if (typeof value === 'function') {
|
|
33
|
+
setComputedAttribute(data, element, name, value as GenericCallback);
|
|
34
|
+
} else {
|
|
35
|
+
setAttribute(data, element, name, value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isInputElement(element) && isSignal(value) && name in element) {
|
|
39
|
+
mapEvent(element, EVENT_ON_VALUE, () => {
|
|
40
|
+
value.set(element[name as keyof typeof element]);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function mapAttributes(
|
|
46
|
+
data: FragmentData,
|
|
47
|
+
element: HTMLElement | SVGElement,
|
|
48
|
+
ignoreValue: boolean,
|
|
49
|
+
): void {
|
|
50
|
+
const attributes = [...element.attributes].sort(compareAttributes);
|
|
51
|
+
|
|
52
|
+
const {length} = attributes;
|
|
53
|
+
|
|
54
|
+
for (let index = 0; index < length; index += 1) {
|
|
55
|
+
const {name, value} = attributes[index];
|
|
56
|
+
|
|
57
|
+
const actualName = name.replace(EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, '');
|
|
58
|
+
const actualValue = getValue(data, value);
|
|
59
|
+
|
|
60
|
+
if (actualName !== name) {
|
|
61
|
+
element.removeAttribute(name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (actualName === PROPERTY_VALUE && ignoreValue) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (true) {
|
|
69
|
+
case EXPRESSION_EVENT_PREFIX.test(actualName):
|
|
70
|
+
mapEvent(element, actualName, actualValue);
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
default:
|
|
74
|
+
mapAttributeValue(data, element, actualName, actualValue);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function setComputedAttribute(
|
|
81
|
+
data: FragmentData,
|
|
82
|
+
element: HTMLElement | SVGElement,
|
|
83
|
+
name: string,
|
|
84
|
+
callback: GenericCallback,
|
|
85
|
+
): void {
|
|
86
|
+
setComputedValue(data, callback, computation => {
|
|
87
|
+
setAttribute(data, element, name, computation);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {getString} from '@oscarpalmer/atoms/string';
|
|
3
|
+
import {isReactive} from '@oscarpalmer/mora';
|
|
4
|
+
import {setAttribute as setTorettoAttribute} from '@oscarpalmer/toretto/attribute';
|
|
5
|
+
import {setStyle} from '@oscarpalmer/toretto/style';
|
|
6
|
+
import {
|
|
7
|
+
ATTRIBUTE_CLASS_PREFIX_LENGTH,
|
|
8
|
+
ATTRIBUTE_NAME_DELIMITER,
|
|
9
|
+
EXPRESSION_ATTRIBUTE_CLASS,
|
|
10
|
+
EXPRESSION_ATTRIBUTE_STYLE_FULL,
|
|
11
|
+
EXPRESSION_ATTRIBUTE_STYLE_PREFIX,
|
|
12
|
+
EXPRESSION_ATTRIBUTE_STYLE_VARIABLE,
|
|
13
|
+
VALUE_TRUE,
|
|
14
|
+
} from '../constants';
|
|
15
|
+
import type {FragmentData} from '../models';
|
|
16
|
+
|
|
17
|
+
function getStyleValue(value: unknown, unit: string, isVariable: boolean): string | undefined {
|
|
18
|
+
if (removeStyleValue(value, unit, isVariable)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return value === true || value === VALUE_TRUE ? unit : `${getString(value)}${unit ?? ''}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function removeStyleValue(value: unknown, unit: unknown, isVariable: boolean): boolean {
|
|
26
|
+
if (value == null || value === false || (isVariable && isNullableOrWhitespace(value))) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return unit == null && (value === true || value === VALUE_TRUE);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function setAttribute(
|
|
34
|
+
data: FragmentData,
|
|
35
|
+
element: HTMLElement | SVGElement,
|
|
36
|
+
name: string,
|
|
37
|
+
value: unknown,
|
|
38
|
+
): void {
|
|
39
|
+
switch (true) {
|
|
40
|
+
case EXPRESSION_ATTRIBUTE_CLASS.test(name):
|
|
41
|
+
setClassValues(data, element, name, value);
|
|
42
|
+
return;
|
|
43
|
+
|
|
44
|
+
case EXPRESSION_ATTRIBUTE_STYLE_PREFIX.test(name):
|
|
45
|
+
setStyleValues(data, element, name, value);
|
|
46
|
+
return;
|
|
47
|
+
|
|
48
|
+
default:
|
|
49
|
+
setValue(data, element, name, value);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setClassValues(
|
|
55
|
+
data: FragmentData,
|
|
56
|
+
element: HTMLElement | SVGElement,
|
|
57
|
+
name: string,
|
|
58
|
+
value: unknown,
|
|
59
|
+
): void {
|
|
60
|
+
const classes = name.slice(ATTRIBUTE_CLASS_PREFIX_LENGTH).split(ATTRIBUTE_NAME_DELIMITER);
|
|
61
|
+
|
|
62
|
+
updateValue(data, value, value => {
|
|
63
|
+
if (value === true || value === VALUE_TRUE) {
|
|
64
|
+
element.classList.add(...classes);
|
|
65
|
+
} else {
|
|
66
|
+
element.classList.remove(...classes);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function setStyleValues(
|
|
72
|
+
data: FragmentData,
|
|
73
|
+
element: HTMLElement | SVGElement,
|
|
74
|
+
name: string,
|
|
75
|
+
value: unknown,
|
|
76
|
+
): void {
|
|
77
|
+
let [, property, unit] = EXPRESSION_ATTRIBUTE_STYLE_FULL.exec(name)!;
|
|
78
|
+
|
|
79
|
+
const isVariable = EXPRESSION_ATTRIBUTE_STYLE_VARIABLE.test(name);
|
|
80
|
+
|
|
81
|
+
updateValue(data, value, value => {
|
|
82
|
+
setStyle(
|
|
83
|
+
element,
|
|
84
|
+
property as keyof CSSStyleDeclaration,
|
|
85
|
+
getStyleValue(value, unit, isVariable),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function setValue(
|
|
91
|
+
data: FragmentData,
|
|
92
|
+
element: HTMLElement | SVGElement,
|
|
93
|
+
name: string,
|
|
94
|
+
value: unknown,
|
|
95
|
+
): void {
|
|
96
|
+
updateValue(data, value, value => {
|
|
97
|
+
setTorettoAttribute(element, name, value);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function updateValue(data: FragmentData, value: unknown, updater: (value: unknown) => void): void {
|
|
102
|
+
if (isReactive(value)) {
|
|
103
|
+
data.mora.subscribers.add(value.subscribe(updater));
|
|
104
|
+
} else {
|
|
105
|
+
updater(value);
|
|
106
|
+
}
|
|
107
|
+
}
|