@oscarpalmer/abydon 0.12.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.
Files changed (48) hide show
  1. package/dist/abydon.full.js +1153 -0
  2. package/dist/constants.js +10 -0
  3. package/dist/fragment.js +60 -0
  4. package/dist/helpers/dom.js +28 -0
  5. package/dist/helpers/index.js +11 -0
  6. package/dist/index.js +6 -911
  7. package/dist/node/attribute/index.js +39 -0
  8. package/dist/node/attribute/value.js +56 -0
  9. package/dist/node/event.js +30 -0
  10. package/dist/node/index.js +52 -0
  11. package/dist/node/value.js +108 -0
  12. package/dist/parse.js +18 -0
  13. package/package.json +38 -27
  14. package/src/constants.ts +20 -0
  15. package/src/fragment.ts +63 -36
  16. package/src/helpers/dom.ts +5 -4
  17. package/src/helpers/index.ts +0 -9
  18. package/src/index.ts +13 -3
  19. package/src/models.ts +14 -11
  20. package/src/node/attribute/index.ts +28 -22
  21. package/src/node/attribute/value.ts +38 -42
  22. package/src/node/event.ts +12 -13
  23. package/src/node/index.ts +7 -7
  24. package/src/node/value.ts +169 -122
  25. package/src/{html.ts → parse.ts} +15 -17
  26. package/types/constants.d.ts +9 -0
  27. package/types/fragment.d.ts +3 -5
  28. package/types/helpers/index.d.ts +0 -3
  29. package/types/index.d.ts +4 -2
  30. package/types/models.d.ts +7 -9
  31. package/types/node/attribute/index.d.ts +2 -1
  32. package/types/node/attribute/value.d.ts +2 -1
  33. package/types/node/event.d.ts +1 -1
  34. package/types/node/value.d.ts +2 -2
  35. package/types/parse.d.ts +2 -0
  36. package/dist/fragment.mjs +0 -75
  37. package/dist/helpers/dom.mjs +0 -44
  38. package/dist/helpers/index.mjs +0 -25
  39. package/dist/html.mjs +0 -36
  40. package/dist/index.mjs +0 -6
  41. package/dist/node/attribute/index.mjs +0 -40
  42. package/dist/node/attribute/value.mjs +0 -89
  43. package/dist/node/event.mjs +0 -38
  44. package/dist/node/index.mjs +0 -60
  45. package/dist/node/value.mjs +0 -123
  46. package/types/html.d.ts +0 -4
  47. package/types/index.d.cts +0 -26
  48. /package/dist/{models.mjs → models.js} +0 -0
@@ -0,0 +1,39 @@
1
+ import { EXPRESSION_COMMENT_FULL } from "../../constants.js";
2
+ import { mapEvent } from "../event.js";
3
+ import { setAttribute as setAttribute$1 } from "./value.js";
4
+ import { computed, isReactive } from "@oscarpalmer/mora";
5
+ import { isBooleanAttribute, setProperty } from "@oscarpalmer/toretto/attribute";
6
+ function getValue(data, original) {
7
+ const matches = EXPRESSION_COMMENT_FULL.exec(original ?? "");
8
+ return matches == null ? original : data.values[+matches[1]];
9
+ }
10
+ function mapAttributes(data, element) {
11
+ const attributes = [...element.attributes];
12
+ const { length } = attributes;
13
+ for (let index = 0; index < length; index += 1) {
14
+ const { name, value } = attributes[index];
15
+ const actual = getValue(data, value);
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
+ }
28
+ }
29
+ }
30
+ function mapValue(data, element, name, value) {
31
+ if (typeof value === "function") setComputedAttribute(data, element, name, value);
32
+ else setAttribute$1(data, element, name, value);
33
+ }
34
+ function setComputedAttribute(data, element, name, callback) {
35
+ const value = computed(callback);
36
+ data.mora.values.add(value);
37
+ setAttribute$1(data, element, name, value);
38
+ }
39
+ export { mapAttributes };
@@ -0,0 +1,56 @@
1
+ import { ATTRIBUTE_CLASS_PREFIX_LENGTH, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX } from "../../constants.js";
2
+ import { isReactive } from "@oscarpalmer/mora";
3
+ import { getString } from "@oscarpalmer/atoms/string";
4
+ import { isBooleanAttribute, setAttribute as setAttribute$1 } from "@oscarpalmer/toretto/attribute";
5
+ function setAttribute(data, element, name, value) {
6
+ element.removeAttribute(name);
7
+ switch (true) {
8
+ case EXPRESSION_ATTRIBUTE_CLASS.test(name):
9
+ setClasses(data, element, name, value);
10
+ return;
11
+ case EXPRESSION_ATTRIBUTE_STYLE_PREFIX.test(name):
12
+ setStyle(data, element, name, value);
13
+ return;
14
+ default:
15
+ setValue(data, element, name, value);
16
+ break;
17
+ }
18
+ }
19
+ function setClasses(data, element, name, value) {
20
+ function update(value$1) {
21
+ if (value$1 === true) element.classList.add(...classes);
22
+ else element.classList.remove(...classes);
23
+ }
24
+ const classes = name.slice(6).split(".");
25
+ if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
26
+ else update(value);
27
+ }
28
+ function setStyle(data, element, name, value) {
29
+ const [, property, unit] = EXPRESSION_ATTRIBUTE_STYLE_FULL.exec(name) ?? [];
30
+ if (property == null) return;
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
+ if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
36
+ else update(value);
37
+ }
38
+ function setValue(data, element, name, value) {
39
+ let callback;
40
+ if (isBooleanAttribute(name) && name in element) callback = name === "selected" ? updateSelected : updateProperty;
41
+ else callback = setAttribute$1;
42
+ if (isReactive(value)) data.mora.subscribers.add(value.subscribe((next) => {
43
+ callback(element, name, next);
44
+ }));
45
+ else callback(element, name, value);
46
+ }
47
+ function updateProperty(element, name, value) {
48
+ element[name] = value === true;
49
+ }
50
+ function updateSelected(element, name, value) {
51
+ const select = element.closest("select");
52
+ const options = [...select?.options ?? []];
53
+ if (select != null && options.includes(element)) select.dispatchEvent(new Event("change", { bubbles: true }));
54
+ updateProperty(element, name, value);
55
+ }
56
+ export { setAttribute };
@@ -0,0 +1,30 @@
1
+ import { ABORT_CONTROLLERS, EXPRESSION_EVENT_NAME, REASON_EVENT_REMOVED } from "../constants.js";
2
+ function getController(element) {
3
+ let controller = ABORT_CONTROLLERS.get(element);
4
+ if (controller == null) {
5
+ controller = new AbortController();
6
+ ABORT_CONTROLLERS.set(element, controller);
7
+ }
8
+ return controller;
9
+ }
10
+ function getOptions(options) {
11
+ const parts = options.split(":");
12
+ return {
13
+ capture: parts.includes("c") || parts.includes("capture"),
14
+ once: parts.includes("o") || parts.includes("once"),
15
+ passive: !(parts.includes("a") || parts.includes("active"))
16
+ };
17
+ }
18
+ function mapEvent(element, name, value) {
19
+ element.removeAttribute(name);
20
+ const [, type, options] = EXPRESSION_EVENT_NAME.exec(name) ?? [];
21
+ if (typeof value === "function" && type != null) element.addEventListener(type, value, {
22
+ ...getOptions(options ?? ""),
23
+ signal: getController(element).signal
24
+ });
25
+ }
26
+ function removeEvents(element) {
27
+ ABORT_CONTROLLERS.get(element)?.abort(REASON_EVENT_REMOVED);
28
+ ABORT_CONTROLLERS.delete(element);
29
+ }
30
+ export { mapEvent, removeEvents };
@@ -0,0 +1,52 @@
1
+ import { EXPRESSION_COMMENT_CONTENT } from "../constants.js";
2
+ import { isFragment } from "../helpers/index.js";
3
+ import { createNodes } from "../helpers/dom.js";
4
+ import { mapAttributes } from "./attribute/index.js";
5
+ import { setReactiveValue } from "./value.js";
6
+ import { computed, isReactive } from "@oscarpalmer/mora";
7
+ import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
8
+ function mapNode(data, comment) {
9
+ const matches = EXPRESSION_COMMENT_CONTENT.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,108 @@
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 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
+ }
36
+ function removeFragments(fragments) {
37
+ if (fragments != null) {
38
+ const { length } = fragments;
39
+ for (let index = 0; index < length; index += 1) fragments[index].remove();
40
+ }
41
+ }
42
+ function setArray(state, comment, value) {
43
+ if (value.length === 0) return { nodes: setText(state, comment, value) };
44
+ let templates = value.filter((item) => isFragment(item) && item.identifier != null);
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 = [];
48
+ const noTemplates = templates.length === 0;
49
+ if (noTemplates || state.nodes == null || previous.some((identifier) => identifier == null)) return {
50
+ fragments: noTemplates ? void 0 : templates,
51
+ nodes: setNodes(state, comment, noTemplates ? value.flatMap((item) => createNodes(item)) : templates.flatMap((template) => template.get()))
52
+ };
53
+ return handleArray({
54
+ next,
55
+ previous
56
+ }, {
57
+ templates,
58
+ fragments: state.fragments ?? []
59
+ }, state.nodes);
60
+ }
61
+ function setNodes(state, comment, next) {
62
+ if (state.nodes == null) {
63
+ if (comment.parentNode != null) replaceNodes([comment], next);
64
+ else if (state.text.parentNode != null) replaceNodes([state.text], next);
65
+ } else replaceNodes(state.nodes, next);
66
+ removeFragments(state.fragments);
67
+ return next;
68
+ }
69
+ function setReactiveValue(data, comment, reactive) {
70
+ const item = data.items.find((item$1) => item$1.nodes.includes(comment));
71
+ const state = { text: new Text() };
72
+ data.mora.subscribers.add(reactive.subscribe((value) => {
73
+ if (Array.isArray(value)) setReactiveValueForArray(state, comment, value);
74
+ else setReactiveValueForSingle(state, comment, value);
75
+ if (item != null) {
76
+ item.fragments = state.fragments;
77
+ item.nodes = [...state.nodes ?? [comment]];
78
+ }
79
+ }));
80
+ }
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) {
94
+ const isNullable = isNullableOrWhitespace(value);
95
+ state.text.textContent = isNullable ? "" : getString(value);
96
+ let result = false;
97
+ if (state.nodes != null) {
98
+ replaceNodes(state.nodes, [isNullable ? comment : state.text]);
99
+ result = !isNullable;
100
+ } else if (isNullable && comment.parentNode == null) state.text.replaceWith(comment);
101
+ else if (!isNullable && state.text.parentNode == null) {
102
+ comment.replaceWith(state.text);
103
+ result = true;
104
+ }
105
+ removeFragments(state.fragments);
106
+ return result ? [state.text] : void 0;
107
+ }
108
+ export { setReactiveValue };
package/dist/parse.js ADDED
@@ -0,0 +1,18 @@
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) return `${prefix}<!--abydon.${data.values.push(expression) - 1}-->`;
10
+ return isNullableOrWhitespace(expression) ? prefix : `${prefix}${expression}`;
11
+ }
12
+ function parse(data) {
13
+ const { length } = data.strings;
14
+ let template = "";
15
+ for (let index = 0; index < length; index += 1) template += handleExpression(data, data.strings[index], data.expressions[index]);
16
+ return template;
17
+ }
18
+ export { parse };
package/package.json CHANGED
@@ -4,48 +4,59 @@
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
6
  "dependencies": {
7
- "@oscarpalmer/atoms": "^0.71.0",
8
- "@oscarpalmer/sentinel": "^0.23.0",
9
- "@oscarpalmer/toretto": "0.12.1"
7
+ "@oscarpalmer/atoms": "^0.114",
8
+ "@oscarpalmer/mora": "^0.23",
9
+ "@oscarpalmer/toretto": "^0.24"
10
10
  },
11
11
  "devDependencies": {
12
- "@biomejs/biome": "^1.8.3",
13
- "@types/bun": "^1.1.8",
14
- "bun": "^1.1.27",
15
- "dts-bundle-generator": "^9.5.1",
16
- "typescript": "^5.5.4"
12
+ "@biomejs/biome": "^2.3",
13
+ "@oscarpalmer/oui": "^0.18",
14
+ "@rollup/plugin-node-resolve": "^16",
15
+ "@rollup/plugin-typescript": "^12.3",
16
+ "@types/node": "^24.9",
17
+ "@vitest/coverage-istanbul": "^4",
18
+ "glob": "^11",
19
+ "jsdom": "^27.1",
20
+ "rollup": "^4.52",
21
+ "tslib": "^2.8",
22
+ "typescript": "^5.9",
23
+ "vite": "npm:rolldown-vite@latest",
24
+ "vitest": "^4"
17
25
  },
18
26
  "exports": {
27
+ "./package.json": "./package.json",
19
28
  ".": {
20
- "bun": "./src/index.ts",
21
- "import": {
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
- }
29
+ "types": "./types/index.d.ts",
30
+ "default": "./dist/index.js"
29
31
  }
30
32
  },
31
- "files": ["dist/index.js", "dist/**/*.mjs", "src", "types"],
33
+ "files": ["dist", "src", "types"],
34
+ "keywords": [
35
+ "components",
36
+ "dom",
37
+ "library",
38
+ "literals",
39
+ "reactive",
40
+ "signal",
41
+ "template",
42
+ "vanilla"
43
+ ],
32
44
  "license": "MIT",
33
- "main": "dist/index.js",
34
- "module": "dist/index.mjs",
45
+ "module": "dist/index.js",
35
46
  "name": "@oscarpalmer/abydon",
36
47
  "repository": {
37
48
  "type": "git",
38
49
  "url": "git+https://github.com/oscarpalmer/abydon.git"
39
50
  },
40
51
  "scripts": {
41
- "build": "bun run clean && bunx bun ./.bun.ts && bunx bun ./.bun.ts --mjs && bun run types",
42
- "clean": "rm -rf ./dist && rm -rf ./types && rm -rf ./tsconfig.tsbuildinfo",
43
- "types": "bun run types:cjs && bun run types:esm",
44
- "types:cjs": "bunx dts-bundle-generator --out-file ./types/index.d.cts --no-check --silent ./src/index.ts",
45
- "types:esm": "bunx tsc -p ./tsconfig.json",
46
- "watch": "bun build ./src/index.ts --outfile ./dist/index.js --watch"
52
+ "build": "npm run clean && npx vite build && npm run rollup:build && npx tsc",
53
+ "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
54
+ "rollup:build": "npx rollup -c",
55
+ "rollup:watch": "npx rollup -c --watch",
56
+ "test": "npx vitest --coverage",
57
+ "watch": "npx vite build --watch"
47
58
  },
48
59
  "type": "module",
49
60
  "types": "types/index.d.ts",
50
- "version": "0.12.0"
61
+ "version": "0.14.0"
51
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,29 +1,37 @@
1
- import type {Key} from '@oscarpalmer/atoms/models';
2
1
  import {html} from '@oscarpalmer/toretto/html';
3
- import {parse} from './html';
4
- import type {FragmentData} from './models';
5
- import {mapNodes} from './node';
6
2
  import {removeNodes} from './helpers/dom';
3
+ import type {FragmentConfiguration, FragmentData} from './models';
4
+ import {mapNodes} from './node';
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: [],
21
- sentinel: {
22
- effects: new Set(),
25
+ mora: {
26
+ subscribers: new Set(),
23
27
  values: new Set(),
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
- sanitiseBooleanAttributes: false,
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,37 +107,36 @@ 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
 
86
114
  function removeFragment(data: FragmentData): void {
87
- removeSentinels(data);
115
+ removeMora(data);
88
116
 
89
117
  const {length} = data.items;
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
- function removeSentinels(data: FragmentData): void {
106
- const sentinels = [...data.sentinel.effects, ...data.sentinel.values];
107
- const {length} = sentinels;
133
+ function removeMora(data: FragmentData): void {
134
+ const unsubscribers = [...data.mora.subscribers];
108
135
 
109
- for (let index = 0; index < length; index += 1) {
110
- sentinels[index].stop();
111
- }
136
+ data.mora.subscribers.clear();
137
+ data.mora.values.clear();
112
138
 
113
- data.sentinel.effects.clear();
114
- data.sentinel.values.clear();
139
+ for (const unsubscribe of unsubscribers) {
140
+ unsubscribe();
141
+ }
115
142
  }
@@ -1,6 +1,7 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
- import {isChildNode, isFragment, isHTMLOrSVGElement} from './index';
2
+ import {isChildNode, isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
3
3
  import {removeEvents} from '../node/event';
4
+ import {isFragment} from './index';
4
5
 
5
6
  export function createNodes(value: unknown): ChildNode[] {
6
7
  if (isFragment(value)) {
@@ -15,7 +16,7 @@ export function createNodes(value: unknown): ChildNode[] {
15
16
  }
16
17
 
17
18
  export function removeNodes(nodes: ChildNode[]): void {
18
- sanitiseNodes(nodes);
19
+ sanitizeNodes(nodes);
19
20
 
20
21
  const {length} = nodes;
21
22
 
@@ -34,7 +35,7 @@ export function replaceNodes(from: ChildNode[], to: ChildNode[]): void {
34
35
  }
35
36
  }
36
37
 
37
- function sanitiseNodes(nodes: ChildNode[]): void {
38
+ function sanitizeNodes(nodes: ChildNode[]): void {
38
39
  const {length} = nodes;
39
40
 
40
41
  for (let index = 0; index < length; index += 1) {
@@ -45,7 +46,7 @@ function sanitiseNodes(nodes: ChildNode[]): void {
45
46
  }
46
47
 
47
48
  if (node.hasChildNodes()) {
48
- sanitiseNodes([...node.childNodes] as ChildNode[]);
49
+ sanitizeNodes([...node.childNodes] as ChildNode[]);
49
50
  }
50
51
  }
51
52
  }
@@ -1,5 +1,4 @@
1
1
  import type {Fragment} from '../fragment';
2
- import type {HTMLOrSVGElement} from '../models';
3
2
 
4
3
  export function compareArrays(
5
4
  first: unknown[],
@@ -20,10 +19,6 @@ export function compareArrays(
20
19
  return firstIsLarger ? 'removed' : 'added';
21
20
  }
22
21
 
23
- export function isChildNode(value: unknown): value is ChildNode {
24
- return value instanceof CharacterData || value instanceof Element;
25
- }
26
-
27
22
  export function isFragment(value: unknown): value is Fragment {
28
23
  return (
29
24
  typeof value === 'object' &&
@@ -32,7 +27,3 @@ export function isFragment(value: unknown): value is Fragment {
32
27
  value.$fragment === true
33
28
  );
34
29
  }
35
-
36
- export function isHTMLOrSVGElement(value: unknown): value is HTMLOrSVGElement {
37
- return value instanceof HTMLElement || value instanceof SVGElement;
38
- }
package/src/index.ts CHANGED
@@ -1,3 +1,13 @@
1
- import '@oscarpalmer/sentinel';
2
- export type {Fragment} from './fragment';
3
- export {html} from './html';
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';
12
+ export type { Fragment } from './fragment';
13
+