@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.
@@ -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
- mapValue(element, name, value2());
33
- return;
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
- function setAttribute(element2, name, value) {
6
- element2.removeAttribute(name);
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 classPattern.test(name):
9
- setClasses(element2, name, value);
11
+ case classExpression.test(name):
12
+ setClasses(element, name, value);
10
13
  return;
11
- case stylePattern.test(name):
12
- setStyle(element2, name, value);
14
+ case stylePrefixExpression.test(name):
15
+ setStyle(element, name, value);
13
16
  return;
14
17
  default:
15
- setValue(element2, name, value);
18
+ setValue(element, name, value);
16
19
  break;
17
20
  }
18
21
  }
19
- function setClasses(element2, name, value) {
22
+ function setClasses(element, name, value) {
20
23
  function update(value2) {
21
- if (/^(|true)$/.test(getString(value2))) {
22
- element2.classList.add(...classes);
24
+ if (value2 === true) {
25
+ element.classList.add(...classes);
23
26
  } else {
24
- element2.classList.remove(...classes);
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(element2, name, value) {
39
+ function setStyle(element, name, value) {
37
40
  function update(value2) {
38
41
  if (value2 == null || value2 === false || value2 === true && unit == null) {
39
- element2.style.removeProperty(property);
42
+ element.style.removeProperty(property);
40
43
  } else {
41
- element2.style.setProperty(property, value2 === true ? unit : getString(value2));
44
+ element.style.setProperty(property, value2 === true ? unit : getString(value2));
42
45
  }
43
46
  }
44
- const [, property, unit] = /^\w+\.([a-z-]+)(?:\.(\w+))?$/i.exec(name) ?? [];
45
- if (property == null) {
46
- return;
47
- }
48
- if (isReactive(value)) {
49
- effect(() => {
50
- update(value.get());
51
- });
52
- } else {
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(element2, name, value) {
57
- const callback = booleanAttributes.has(name) && name in element2 ? name === "selected" ? updateSelected(element2) : updateProperty : updateAttribute;
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(element2, name, value.get());
62
+ callback(element, name, value.get());
61
63
  });
62
64
  } else {
63
- callback(element2, name, value);
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 updateAttribute(element2, name, value) {
72
- if (value == null) {
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 updateProperty(element2, name, value) {
79
- element2[name] = /^(|true)$/.test(getString(value));
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
- return (element3, name, value) => {
85
- if (select != null && options.includes(element3)) {
86
- triggerSelectChange(select);
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 booleanAttributes = new Set([
92
- "checked",
93
- "disabled",
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
  };
@@ -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] = /^@(\w+)(?::([a-z:]+))?$/.exec(name) ?? [];
20
+ const [, type, options] = eventNamePattern.exec(name) ?? [];
13
21
  if (typeof value === "function" && type != null) {
14
- element.addEventListener(type, value, getOptions(options ?? ""));
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
  };
@@ -1,6 +1,6 @@
1
1
  // src/node/index.ts
2
- import {isReactive} from "@oscarpalmer/sentinel";
3
- import {isFragmentElement} from "../helpers";
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 (isFragmentElement(node)) {
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
- mapValue(comment, value2());
34
- return;
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.replaceWith(...createNodes(value2));
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
@@ -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, isFragmentNode} from "../helpers";
5
+ import {compareArrays, isFragment, isProperNode} from "../helpers";
6
6
  import {createNodes, replaceNodes} from "../helpers/dom";
7
- function setReactiveNode(comment, reactive) {
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 (isFragment(value) || isFragmentNode(value)) {
13
- const next = createNodes(value);
14
- if (nodes == null) {
15
- if (comment.parentNode != null) {
16
- replaceNodes([comment], next);
17
- } else if (text.parentNode != null) {
18
- replaceNodes([text], next);
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
- replaceNodes(nodes, next);
96
+ nodes = setText(fragments, nodes, comment, text, value);
22
97
  }
23
- nodes = next;
24
- return;
25
98
  }
26
- const isNullable = isNullableOrWhitespace(value);
27
- text.textContent = getString(value);
28
- if (nodes != null) {
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.67.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.6",
13
- "bun": "^1.1.21",
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.8.0"
50
+ "version": "0.10.0"
50
51
  }
package/src/fragment.ts CHANGED
@@ -1,17 +1,18 @@
1
- import {createTemplate, getNodes} from './helpers/dom';
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 declare readonly $fragment = true;
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
- nodes: [],
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(): Node[] {
30
- if (this.data.nodes.length === 0) {
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
- this.data.nodes.splice(0, this.data.nodes.length, ...getNodes(templated));
34
+ const templated = html(parsed, {
35
+ sanitiseBooleanAttributes: false,
36
+ });
35
37
 
36
- mapNodes(this.data, this.data.nodes);
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 [...this.data.nodes];
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.nodes;
61
+ const {length} = this.data.items;
47
62
 
48
63
  for (let index = 0; index < length; index += 1) {
49
- const node = this.data.nodes[index];
64
+ const {fragment, nodes} = this.data.items[index];
65
+
66
+ fragment?.remove();
50
67
 
51
- node.parentNode?.removeChild(node);
68
+ removeNodes(nodes);
52
69
  }
53
70
 
54
- this.data.nodes.splice(0, length);
71
+ this.data.items.splice(0, length);
55
72
  }
56
73
  }
@@ -1,57 +1,54 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
- import type {FragmentNode} from '../models';
3
- import {isFragment, isFragmentNode} from './index';
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): FragmentNode[] {
6
- if (isFragmentNode(value)) {
7
- return [value];
6
+ export function createNodes(value: unknown): ProperNode[] {
7
+ if (isFragment(value)) {
8
+ return value.get() as ProperNode[];
8
9
  }
9
10
 
10
- if (isFragment(value)) {
11
- return value.get() as FragmentNode[];
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 createTemplate(html: string): Node {
18
- const template = document.createElement('template');
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 scripts = [
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
- scripts[index].remove();
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: FragmentNode[], to: FragmentNode[]): void {
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
- node.replaceWith(...to);
33
+ from[index].replaceWith(...to);
53
34
  } else {
54
- node.remove();
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
  }
@@ -1,5 +1,5 @@
1
1
  import type {Fragment} from '../fragment';
2
- import type {FragmentElement, FragmentNode} from '../models';
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 isFragmentElement(value: unknown): value is FragmentElement {
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 isFragmentNode(value: unknown): value is FragmentNode {
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
- nodes: Node[];
5
+ items: FragmentItem[];
4
6
  strings: TemplateStringsArray;
5
7
  values: unknown[];
6
8
  };
7
9
 
8
- export type FragmentElement = HTMLElement | SVGElement;
10
+ export type FragmentItem = {
11
+ fragment?: Fragment;
12
+ nodes: ProperNode[];
13
+ };
14
+
15
+ export type ProperElement = HTMLElement | SVGElement;
9
16
 
10
- export type FragmentNode = Comment | Element | Text;
17
+ export type ProperNode = CharacterData | Element;