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