@oscarpalmer/abydon 0.15.0 → 0.17.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 (63) hide show
  1. package/dist/abydon.full.mjs +1443 -0
  2. package/dist/constants.d.mts +20 -0
  3. package/dist/constants.mjs +27 -0
  4. package/dist/fragment-9qTBAvtR.d.mts +82 -0
  5. package/dist/fragment.d.mts +2 -0
  6. package/dist/{fragment.js → fragment.mjs} +41 -14
  7. package/dist/fragments.d.mts +16 -0
  8. package/dist/{fragments.js → fragments.mjs} +10 -4
  9. package/dist/helpers/dom.d.mts +6 -0
  10. package/dist/helpers/{dom.js → dom.mjs} +4 -12
  11. package/dist/helpers/index.d.mts +9 -0
  12. package/dist/helpers/index.mjs +23 -0
  13. package/dist/index.d.mts +21 -0
  14. package/dist/index.mjs +24 -0
  15. package/dist/models.d.mts +2 -0
  16. package/dist/models.mjs +1 -0
  17. package/dist/node/attribute/index.d.mts +6 -0
  18. package/dist/node/attribute/index.mjs +41 -0
  19. package/dist/node/attribute/value.d.mts +6 -0
  20. package/dist/node/attribute/value.mjs +65 -0
  21. package/dist/node/event.d.mts +4 -0
  22. package/dist/node/event.mjs +25 -0
  23. package/dist/node/index.d.mts +6 -0
  24. package/dist/node/{index.js → index.mjs} +10 -8
  25. package/dist/node/value.d.mts +7 -0
  26. package/dist/node/{value.js → value.mjs} +12 -10
  27. package/dist/parse.d.mts +6 -0
  28. package/dist/{parse.js → parse.mjs} +11 -1
  29. package/package.json +42 -42
  30. package/src/constants.ts +30 -10
  31. package/src/fragment.ts +12 -24
  32. package/src/fragments.ts +3 -5
  33. package/src/helpers/dom.ts +1 -20
  34. package/src/helpers/index.ts +18 -15
  35. package/src/index.ts +14 -10
  36. package/src/models.ts +10 -1
  37. package/src/node/attribute/index.ts +22 -21
  38. package/src/node/attribute/value.ts +47 -37
  39. package/src/node/event.ts +26 -33
  40. package/src/node/index.ts +6 -18
  41. package/src/node/value.ts +15 -46
  42. package/src/parse.ts +15 -17
  43. package/dist/abydon.full.js +0 -1274
  44. package/dist/constants.js +0 -10
  45. package/dist/helpers/index.js +0 -14
  46. package/dist/index.js +0 -11
  47. package/dist/models.js +0 -0
  48. package/dist/node/attribute/index.js +0 -39
  49. package/dist/node/attribute/value.js +0 -56
  50. package/dist/node/event.js +0 -30
  51. package/types/constants.d.ts +0 -9
  52. package/types/fragment.d.ts +0 -38
  53. package/types/fragments.d.ts +0 -13
  54. package/types/helpers/dom.d.ts +0 -3
  55. package/types/helpers/index.d.ts +0 -5
  56. package/types/index.d.ts +0 -9
  57. package/types/models.d.ts +0 -32
  58. package/types/node/attribute/index.d.ts +0 -3
  59. package/types/node/attribute/value.d.ts +0 -3
  60. package/types/node/event.d.ts +0 -3
  61. package/types/node/index.d.ts +0 -2
  62. package/types/node/value.d.ts +0 -3
  63. package/types/parse.d.ts +0 -2
@@ -1,11 +1,12 @@
1
- import { compareArrays, isFragment } from "../helpers/index.js";
2
- import { createNodes, replaceNodes } from "../helpers/dom.js";
1
+ import { compareArrays, isFragment } from "../helpers/index.mjs";
2
+ import { createNodes, replaceNodes } from "../helpers/dom.mjs";
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
+ //#region src/node/value.ts
6
7
  function addToArray(identifiers, items, nodes, added) {
7
8
  let position = nodes[0];
8
- const before = added && !identifiers.previous.includes(items.templates[0].identifier);
9
+ const before = added && !identifiers.previous.has(items.templates[0].identifier);
9
10
  const next = items.next.flatMap((fragment) => fragment.get().flatMap((node) => ({
10
11
  identifier: fragment.identifier,
11
12
  value: node
@@ -13,7 +14,7 @@ function addToArray(identifiers, items, nodes, added) {
13
14
  const { length } = next;
14
15
  for (let index = 0; index < length; index += 1) {
15
16
  const node = next[index];
16
- if (!(added && identifiers.previous.includes(node.identifier))) if (index === 0 && before) position.before(node.value);
17
+ if (!(added && identifiers.previous.has(node.identifier))) if (index === 0 && before) position.before(node.value);
17
18
  else position.after(node.value);
18
19
  position = node.value;
19
20
  }
@@ -25,7 +26,7 @@ function handleArray(identifiers, items, nodes) {
25
26
  ...items,
26
27
  next
27
28
  }, nodes, comparison === "added");
28
- const toRemove = items.fragments?.filter((fragment) => !identifiers.next.includes(fragment.identifier)) ?? [];
29
+ const toRemove = items.fragments?.filter((fragment) => !identifiers.next.has(fragment.identifier)) ?? [];
29
30
  const { length } = toRemove;
30
31
  for (let index = 0; index < length; index += 1) toRemove[index].remove();
31
32
  return {
@@ -47,18 +48,18 @@ function replaceText(item, comment, isNullable) {
47
48
  }
48
49
  function setArray(item, comment, value) {
49
50
  if (value.length === 0) return { nodes: setText(item, comment, value) };
50
- let templates = value.filter((item$1) => isFragment(item$1) && item$1.identifier != null);
51
+ let templates = value.filter((item) => isFragment(item) && item.identifier != null);
51
52
  const next = templates.map((fragment) => fragment.identifier);
52
53
  const previous = item.fragments?.map((fragment) => fragment.identifier) ?? [];
53
54
  if (new Set(next).size !== templates.length) templates = [];
54
55
  const noTemplates = templates.length === 0;
55
56
  if (noTemplates || item.nodes == null || previous.some((identifier) => identifier == null)) return {
56
57
  fragments: noTemplates ? void 0 : templates,
57
- nodes: setNodes(item, comment, noTemplates ? value.flatMap((item$1) => createNodes(item$1)) : templates.flatMap((template) => template.get()))
58
+ nodes: setNodes(item, comment, noTemplates ? value.flatMap((item) => createNodes(item)) : templates.flatMap((template) => template.get()))
58
59
  };
59
60
  return handleArray({
60
- next,
61
- previous
61
+ next: new Set(next),
62
+ previous: new Set(previous)
62
63
  }, {
63
64
  templates,
64
65
  fragments: item.fragments ?? []
@@ -73,7 +74,7 @@ function setNodes(item, comment, next) {
73
74
  return next;
74
75
  }
75
76
  function setReactiveValue(data, comment, reactive) {
76
- let item = data.items.find((item$1) => item$1.nodes?.includes(comment));
77
+ let item = data.items.find((item) => item.nodes?.includes(comment));
77
78
  item ??= {};
78
79
  item.text = new Text();
79
80
  data.mora.subscribers.add(reactive.subscribe((value) => {
@@ -110,4 +111,5 @@ function setText(item, comment, value) {
110
111
  removeFragments(item.fragments);
111
112
  if (result) return item.text == null ? [] : [item.text];
112
113
  }
114
+ //#endregion
113
115
  export { setReactiveValue };
@@ -0,0 +1,6 @@
1
+ import { r as FragmentData } from "./fragment-9qTBAvtR.mjs";
2
+
3
+ //#region src/parse.d.ts
4
+ declare function parse(data: FragmentData): string;
5
+ //#endregion
6
+ export { parse };
@@ -1,4 +1,6 @@
1
+ import { EXPRESSION_ABYDON_ATTRIBUTE_FULL } from "./constants.mjs";
1
2
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
3
+ //#region src/parse.ts
2
4
  function handleExpression(data, prefix, expression) {
3
5
  if (Array.isArray(expression)) {
4
6
  const { length } = expression;
@@ -6,7 +8,7 @@ function handleExpression(data, prefix, expression) {
6
8
  for (let index = 0; index < length; index += 1) expressions += handleExpression(data, "", expression[index]);
7
9
  return `${prefix}${expressions}`;
8
10
  }
9
- if (typeof expression === "function" || typeof expression === "object" && expression != null) return `${prefix}<!--abydon.${data.values.push(expression) - 1}-->`;
11
+ if (typeof expression === "function" || typeof expression === "object" && expression != null) return transformExpression(prefix, data.values.push(expression) - 1);
10
12
  return isNullableOrWhitespace(expression) ? prefix : `${prefix}${expression}`;
11
13
  }
12
14
  function parse(data) {
@@ -14,8 +16,16 @@ function parse(data) {
14
16
  const { length } = data.strings;
15
17
  data.template = "";
16
18
  for (let index = 0; index < length; index += 1) data.template += handleExpression(data, data.strings[index], data.expressions[index]);
19
+ data.template = data.template.replaceAll(EXPRESSION_ABYDON_ATTRIBUTE_FULL, transformAttribute);
17
20
  data.expressions = [];
18
21
  data.strings = [];
19
22
  return data.template;
20
23
  }
24
+ function transformAttribute(_, name, index) {
25
+ return `_${name}="@abydon.${index}@"`;
26
+ }
27
+ function transformExpression(prefix, index) {
28
+ return `${prefix}<!--abydon.${index}-->`;
29
+ }
30
+ //#endregion
21
31
  export { parse };
package/package.json CHANGED
@@ -1,36 +1,6 @@
1
1
  {
2
- "author": {
3
- "name": "Oscar Palmér",
4
- "url": "https://oscarpalmer.se"
5
- },
6
- "dependencies": {
7
- "@oscarpalmer/atoms": "^0.114",
8
- "@oscarpalmer/mora": "^0.23",
9
- "@oscarpalmer/toretto": "^0.25"
10
- },
11
- "devDependencies": {
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"
25
- },
26
- "exports": {
27
- "./package.json": "./package.json",
28
- ".": {
29
- "types": "./types/index.d.ts",
30
- "default": "./dist/index.js"
31
- }
32
- },
33
- "files": ["dist", "src", "types"],
2
+ "name": "@oscarpalmer/abydon",
3
+ "version": "0.17.0",
34
4
  "keywords": [
35
5
  "components",
36
6
  "dom",
@@ -42,21 +12,51 @@
42
12
  "vanilla"
43
13
  ],
44
14
  "license": "MIT",
45
- "module": "dist/index.js",
46
- "name": "@oscarpalmer/abydon",
15
+ "author": {
16
+ "name": "Oscar Palmér",
17
+ "url": "https://oscarpalmer.se"
18
+ },
47
19
  "repository": {
48
20
  "type": "git",
49
21
  "url": "git+https://github.com/oscarpalmer/abydon.git"
50
22
  },
23
+ "files": [
24
+ "dist",
25
+ "src"
26
+ ],
27
+ "type": "module",
28
+ "module": "./dist/index.mjs",
29
+ "types": "./dist/index.d.mts",
30
+ "exports": {
31
+ "./package.json": "./package.json",
32
+ ".": {
33
+ "types": "./dist/index.d.mts",
34
+ "default": "./dist/index.mjs"
35
+ }
36
+ },
51
37
  "scripts": {
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",
38
+ "build": "npm run tsdown:build && npx vp pack",
39
+ "tsdown:build": "npx tsdown -c ./tsdown.config.ts",
40
+ "tsdown:watch": "npx tsdown -c ./tsdown.config.ts --watch",
41
+ "test": "npx vp test run --coverage",
42
+ "test:leak": "npx vp test run --detect-async-leaks --coverage",
57
43
  "watch": "npx vite build --watch"
58
44
  },
59
- "type": "module",
60
- "types": "types/index.d.ts",
61
- "version": "0.15.0"
45
+ "dependencies": {
46
+ "@oscarpalmer/atoms": "^0.165",
47
+ "@oscarpalmer/mora": "^0.26",
48
+ "@oscarpalmer/toretto": "^0.41"
49
+ },
50
+ "devDependencies": {
51
+ "@oscarpalmer/oui": "^0.19",
52
+ "@types/node": "^25.5",
53
+ "@vitest/coverage-istanbul": "^4.1",
54
+ "jsdom": "^28.1",
55
+ "tsdown": "^0.21",
56
+ "typescript": "^5.9",
57
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
58
+ "vite-plus": "latest",
59
+ "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
60
+ },
61
+ "packageManager": "npm@11.11.1"
62
62
  }
package/src/constants.ts CHANGED
@@ -1,20 +1,40 @@
1
- export const ABORT_CONTROLLERS: WeakMap<HTMLOrSVGElement, AbortController> =
2
- new WeakMap();
3
-
4
1
  export const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
5
2
 
3
+ export const EVENT_DEFAULTS: Record<string, string> = {
4
+ A: 'click',
5
+ BUTTON: 'click',
6
+ DETAILS: 'toggle',
7
+ FORM: 'submit',
8
+ SELECT: 'change',
9
+ TEXTAREA: 'input',
10
+ };
11
+
12
+ export const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?\w+)="<!--abydon.(\d+)-->"/g;
13
+
14
+ export const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
15
+
16
+ export const EXPRESSION_ABYDON_CONTENT = /^@?abydon\.(\d+)@?$/;
17
+
6
18
  export const EXPRESSION_ATTRIBUTE_CLASS = /^class\./;
7
19
 
8
- export const EXPRESSION_ATTRIBUTE_STYLE_FULL =
9
- /^style\.([\w-]+)(?:\.([\w-]+))?$/;
20
+ export const EXPRESSION_ATTRIBUTE_STYLE_FULL = /^style\.([\w-]+)(?:\.([\w-]+))?$/;
10
21
 
11
22
  export const EXPRESSION_ATTRIBUTE_STYLE_PREFIX = /^style\./;
12
23
 
13
- export const EXPRESSION_COMMENT_FULL = /^<!--abydon\.(\d+)-->$/;
14
-
15
- export const EXPRESSION_COMMENT_CONTENT = /^abydon\.(\d+)$/;
24
+ export const EXPRESSION_EVENT_CHANGE_TYPES = /^(checkbox|radio)$/;
16
25
 
17
26
  export const EXPRESSION_EVENT_NAME = /^@([\w-]+)(?::([a-z:]+))?$/i;
18
27
 
19
- export const REASON_EVENT_REMOVED =
20
- 'Event removed as element was removed from document by Abydon';
28
+ export const EXPRESSION_EVENT_OPTIONS_ACTIVE = /^a(ctive)$/i;
29
+
30
+ export const EXPRESSION_EVENT_OPTIONS_CAPTURE = /^c(apture)$/i;
31
+
32
+ export const EXPRESSION_EVENT_OPTIONS_ONCE = /^o(nce)$/i;
33
+
34
+ export const EXPRESSION_EVENT_PREFIX = /^@/;
35
+
36
+ export const EXPRESSION_PERIOD = /\./;
37
+
38
+ export const NAME_FRAGMENT = '$fragment';
39
+
40
+ export const NAME_FRAGMENTS = '$fragments';
package/src/fragment.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import {html} from '@oscarpalmer/toretto/html';
3
+ import {NAME_FRAGMENT} from './constants';
3
4
  import {handleFragments} from './fragments';
4
5
  import {isFragments} from './helpers';
5
6
  import {removeNodes} from './helpers/dom';
@@ -12,7 +13,7 @@ export class Fragment {
12
13
 
13
14
  readonly #configuration: Required<FragmentConfiguration> = {
14
15
  identifier: undefined,
15
- ignoreCache: false,
16
+ cache: true,
16
17
  };
17
18
 
18
19
  /**
@@ -23,7 +24,7 @@ export class Fragment {
23
24
  }
24
25
 
25
26
  constructor(strings: TemplateStringsArray, expressions: unknown[]) {
26
- Object.defineProperty(this, '$fragment', {
27
+ Object.defineProperty(this, NAME_FRAGMENT, {
27
28
  value: true,
28
29
  });
29
30
 
@@ -55,12 +56,12 @@ export class Fragment {
55
56
  configure(configuration: FragmentConfiguration): Fragment {
56
57
  const actual = isPlainObject(configuration) ? configuration : {};
57
58
 
58
- if (actual.identifier !== undefined) {
59
+ if ('identifier' in actual) {
59
60
  this.#configuration.identifier = actual.identifier;
60
61
  }
61
62
 
62
- if (typeof actual.ignoreCache === 'boolean') {
63
- this.#configuration.ignoreCache = actual.ignoreCache;
63
+ if (typeof actual.cache === 'boolean') {
64
+ this.#configuration.cache = actual.cache;
64
65
  }
65
66
 
66
67
  return this;
@@ -77,8 +78,7 @@ export class Fragment {
77
78
  const parsed = parse(data);
78
79
 
79
80
  const templated = html(parsed, {
80
- ignoreCache: this.#configuration.ignoreCache,
81
- sanitizeBooleanAttributes: false,
81
+ cache: this.#configuration.cache,
82
82
  });
83
83
 
84
84
  data.items.splice(
@@ -92,22 +92,14 @@ export class Fragment {
92
92
  mapNodes(
93
93
  data,
94
94
  data.items.flatMap(
95
- item =>
96
- item.fragments?.flatMap(fragment => fragment.get()) ??
97
- item.nodes ??
98
- [],
95
+ item => item.fragments?.flatMap(fragment => fragment.get()) ?? item.nodes ?? [],
99
96
  ),
100
97
  );
101
98
  }
102
99
 
103
- return [
104
- ...data.items.flatMap(
105
- item =>
106
- item.fragments?.flatMap(fragment => fragment.get()) ??
107
- item.nodes ??
108
- [],
109
- ),
110
- ];
100
+ return data.items.flatMap(
101
+ item => item.fragments?.flatMap(fragment => fragment.get()) ?? item.nodes ?? [],
102
+ );
111
103
  }
112
104
 
113
105
  /**
@@ -141,11 +133,7 @@ function removeFragment(data: FragmentData): void {
141
133
  const {fragments, nodes} = data.items[index];
142
134
  const fragmentsLength = fragments?.length ?? 0;
143
135
 
144
- for (
145
- let fragmentIndex = 0;
146
- fragmentIndex < fragmentsLength;
147
- fragmentIndex += 1
148
- ) {
136
+ for (let fragmentIndex = 0; fragmentIndex < fragmentsLength; fragmentIndex += 1) {
149
137
  fragments?.[fragmentIndex]?.remove();
150
138
  }
151
139
 
package/src/fragments.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
2
  import {array, type ReactiveArray} from '@oscarpalmer/mora';
3
+ import {NAME_FRAGMENTS} from './constants';
3
4
  import type {Fragment} from './fragment';
4
5
  import {isFragment, isFragments} from './helpers';
5
6
  import type {FragmentsState} from './models';
@@ -19,7 +20,7 @@ export class Fragments {
19
20
  identify: (item: unknown) => unknown,
20
21
  fragment: (item: unknown) => Fragment,
21
22
  ) {
22
- Object.defineProperty(this, '$fragments', {
23
+ Object.defineProperty(this, NAME_FRAGMENTS, {
23
24
  value: true,
24
25
  });
25
26
 
@@ -38,10 +39,7 @@ export class Fragments {
38
39
  }
39
40
  }
40
41
 
41
- export function handleFragments(
42
- item: Fragments | FragmentsState,
43
- remove: boolean,
44
- ): void {
42
+ export function handleFragments(item: Fragments | FragmentsState, remove: boolean): void {
45
43
  const state = isFragments(item) ? states.get(item) : item;
46
44
 
47
45
  if (state != null) {
@@ -1,6 +1,5 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
- import {isChildNode, isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
3
- import {removeEvents} from '../node/event';
2
+ import {isChildNode} from '@oscarpalmer/toretto/is';
4
3
  import {isFragment} from './index';
5
4
 
6
5
  export function createNodes(value: unknown): ChildNode[] {
@@ -16,8 +15,6 @@ export function createNodes(value: unknown): ChildNode[] {
16
15
  }
17
16
 
18
17
  export function removeNodes(nodes: ChildNode[]): void {
19
- sanitizeNodes(nodes);
20
-
21
18
  const {length} = nodes;
22
19
 
23
20
  for (let index = 0; index < length; index += 1) {
@@ -34,19 +31,3 @@ export function replaceNodes(from: ChildNode[], to: ChildNode[]): void {
34
31
  from[index].remove();
35
32
  }
36
33
  }
37
-
38
- function sanitizeNodes(nodes: ChildNode[]): void {
39
- const {length} = nodes;
40
-
41
- for (let index = 0; index < length; index += 1) {
42
- const node = nodes[index];
43
-
44
- if (isHTMLOrSVGElement(node)) {
45
- removeEvents(node);
46
- }
47
-
48
- if (node.hasChildNodes()) {
49
- sanitizeNodes([...node.childNodes] as ChildNode[]);
50
- }
51
- }
52
- }
@@ -1,3 +1,5 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import {NAME_FRAGMENT, NAME_FRAGMENTS} from '../constants';
1
3
  import type {Fragment} from '../fragment';
2
4
  import type {Fragments} from '../fragments';
3
5
 
@@ -9,31 +11,32 @@ export function compareArrays(
9
11
  const from = firstIsLarger ? first : second;
10
12
  const to = firstIsLarger ? second : first;
11
13
 
12
- if (
13
- !from
14
- .filter(key => to.includes(key))
15
- .every((key, index) => to[index] === key)
16
- ) {
17
- return 'dissimilar';
14
+ if (!from.filter(key => to.includes(key)).every((key, index) => to[index] === key)) {
15
+ return COMPARISON_DISSIMILAR;
18
16
  }
19
17
 
20
- return firstIsLarger ? 'removed' : 'added';
18
+ return firstIsLarger ? COMPARISON_REMOVED : COMPARISON_ADDED;
21
19
  }
22
20
 
23
21
  export function isFragment(value: unknown): value is Fragment {
24
- return (
25
- typeof value === 'object' &&
26
- value != null &&
27
- '$fragment' in value &&
28
- value.$fragment === true
29
- );
22
+ return isNamed(value, NAME_FRAGMENT);
30
23
  }
31
24
 
32
25
  export function isFragments(value: unknown): value is Fragments {
26
+ return isNamed(value, NAME_FRAGMENTS);
27
+ }
28
+
29
+ function isNamed(value: unknown, name: string): boolean {
33
30
  return (
34
31
  typeof value === 'object' &&
35
32
  value != null &&
36
- '$fragments' in value &&
37
- value.$fragments === true
33
+ name in value &&
34
+ (value as PlainObject)[name] === true
38
35
  );
39
36
  }
37
+
38
+ const COMPARISON_ADDED = 'added';
39
+
40
+ const COMPARISON_DISSIMILAR = 'dissimilar';
41
+
42
+ const COMPARISON_REMOVED = 'removed';
package/src/index.ts CHANGED
@@ -3,23 +3,27 @@ import type {ReactiveArray} from '@oscarpalmer/mora';
3
3
  import {Fragment} from './fragment';
4
4
  import {Fragments} from './fragments';
5
5
 
6
+ /**
7
+ * Create Fragments from a reactive array
8
+ * @param array Reactive array
9
+ * @param identify Function to identify item
10
+ * @param fragment Function to create fragment from item
11
+ * @returns Fragments
12
+ */
6
13
  export function fragments<Item>(
7
14
  array: ReactiveArray<Item>,
8
15
  identify: (item: Item) => unknown,
9
16
  fragment: (item: Item) => Fragment,
10
17
  ): Fragments {
11
- return new Fragments(
12
- array as ReactiveArray<unknown>,
13
- identify as never,
14
- fragment as never,
15
- );
18
+ return new Fragments(array as ReactiveArray<unknown>, identify as never, fragment as never);
16
19
  }
17
20
 
18
- export function html(
19
- strings: TemplateStringsArray,
20
- ...values: unknown[]
21
- ): unknown {
22
- return new Fragment(strings, values);
21
+ /**
22
+ * Create Fragment from a template
23
+ * @returns Fragment
24
+ */
25
+ export function html(template: TemplateStringsArray, ...values: unknown[]): Fragment {
26
+ return new Fragment(template, values);
23
27
  }
24
28
 
25
29
  export * from '@oscarpalmer/mora';
package/src/models.ts CHANGED
@@ -2,8 +2,17 @@ import type {Reactive, ReactiveArray, Unsubscribe} from '@oscarpalmer/mora';
2
2
  import type {Fragment} from './fragment';
3
3
 
4
4
  export type FragmentConfiguration = {
5
+ /**
6
+ * Should the template be cached? _(defaults to `true`)_
7
+ */
8
+ cache?: boolean;
9
+ /**
10
+ * Identifier for the fragment
11
+ *
12
+ * _(An identifier can be used to uniquely identify a fragment,
13
+ * which helps prevent re-rendering in certain scenarios)_
14
+ */
5
15
  identifier?: unknown;
6
- ignoreCache?: boolean;
7
16
  };
8
17
 
9
18
  export type FragmentData = {
@@ -1,43 +1,44 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
2
  import {computed, isReactive} from '@oscarpalmer/mora';
3
- import {isBooleanAttribute, setProperty} from '@oscarpalmer/toretto/attribute';
4
- import type {HTMLOrSVGElement} from '@oscarpalmer/toretto/models';
5
- import {EXPRESSION_COMMENT_FULL} from '../../constants';
3
+ import {
4
+ EXPRESSION_ABYDON_ATTRIBUTE_PREFIX,
5
+ EXPRESSION_ABYDON_CONTENT,
6
+ EXPRESSION_EVENT_PREFIX,
7
+ EXPRESSION_PERIOD,
8
+ } from '../../constants';
6
9
  import type {FragmentData} from '../../models';
7
10
  import {mapEvent} from '../event';
8
11
  import {setAttribute} from './value';
9
12
 
10
13
  function getValue(data: FragmentData, original: string): unknown {
11
- const matches = EXPRESSION_COMMENT_FULL.exec(original ?? '');
14
+ const matches = EXPRESSION_ABYDON_CONTENT.exec(original ?? '');
12
15
 
13
16
  return matches == null ? original : data.values[+matches[1]];
14
17
  }
15
18
 
16
- export function mapAttributes(
17
- data: FragmentData,
18
- element: HTMLOrSVGElement,
19
- ): void {
19
+ export function mapAttributes(data: FragmentData, element: HTMLElement | SVGElement): void {
20
20
  const attributes = [...element.attributes];
21
21
  const {length} = attributes;
22
22
 
23
23
  for (let index = 0; index < length; index += 1) {
24
24
  const {name, value} = attributes[index];
25
25
 
26
- const actual = getValue(data, value);
26
+ const actualName = name.replace(EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, '');
27
+ const actualValue = getValue(data, value) as never;
27
28
 
28
- switch (true) {
29
- case name.startsWith('@'):
30
- mapEvent(element, name, actual);
31
- break;
29
+ if (actualName !== name) {
30
+ element.removeAttribute(name);
31
+ }
32
32
 
33
- case name.includes('.') ||
34
- typeof actual === 'function' ||
35
- isReactive(actual):
36
- mapValue(data, element, name, actual);
33
+ switch (true) {
34
+ case EXPRESSION_EVENT_PREFIX.test(actualName):
35
+ mapEvent(element, actualName, actualValue);
37
36
  break;
38
37
 
39
- case isBooleanAttribute(name):
40
- setProperty(element, name, value);
38
+ case EXPRESSION_PERIOD.test(actualName):
39
+ case typeof actualValue === 'function':
40
+ case isReactive(actualValue):
41
+ mapValue(data, element, actualName, actualValue);
41
42
  break;
42
43
 
43
44
  default:
@@ -48,7 +49,7 @@ export function mapAttributes(
48
49
 
49
50
  function mapValue(
50
51
  data: FragmentData,
51
- element: HTMLOrSVGElement,
52
+ element: HTMLElement | SVGElement,
52
53
  name: string,
53
54
  value: unknown,
54
55
  ): void {
@@ -61,7 +62,7 @@ function mapValue(
61
62
 
62
63
  function setComputedAttribute(
63
64
  data: FragmentData,
64
- element: HTMLOrSVGElement,
65
+ element: HTMLElement | SVGElement,
65
66
  name: string,
66
67
  callback: GenericCallback,
67
68
  ): void {