@oscarpalmer/abydon 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +2 -0
  2. package/dist/abydon.full.mjs +422 -359
  3. package/dist/attribute/index.d.mts +7 -0
  4. package/dist/attribute/index.mjs +47 -0
  5. package/dist/{node/attribute → attribute}/value.d.mts +2 -2
  6. package/dist/attribute/value.mjs +53 -0
  7. package/dist/constants.d.mts +8 -8
  8. package/dist/constants.mjs +15 -15
  9. package/dist/fragment.d.mts +30 -13
  10. package/dist/fragment.mjs +40 -18
  11. package/dist/fragments.d.mts +3 -7
  12. package/dist/fragments.mjs +6 -12
  13. package/dist/helpers/dom.d.mts +2 -3
  14. package/dist/helpers/dom.mjs +2 -4
  15. package/dist/helpers/index.d.mts +16 -2
  16. package/dist/helpers/index.mjs +17 -1
  17. package/dist/index.d.mts +35 -7
  18. package/dist/index.mjs +39 -8
  19. package/dist/models.d.mts +6 -4
  20. package/dist/node/event.mjs +9 -4
  21. package/dist/node/index.mjs +26 -32
  22. package/dist/node/value.mjs +70 -56
  23. package/dist/parse.mjs +6 -4
  24. package/package.json +6 -5
  25. package/src/attribute/index.ts +89 -0
  26. package/src/attribute/value.ts +107 -0
  27. package/src/constants.ts +19 -19
  28. package/src/fragment.ts +43 -21
  29. package/src/fragments.ts +8 -15
  30. package/src/helpers/dom.ts +3 -5
  31. package/src/helpers/index.ts +25 -1
  32. package/src/index.ts +47 -7
  33. package/src/models.ts +6 -4
  34. package/src/node/event.ts +12 -5
  35. package/src/node/index.ts +36 -56
  36. package/src/node/value.ts +121 -104
  37. package/src/parse.ts +15 -5
  38. package/dist/node/attribute/index.d.mts +0 -6
  39. package/dist/node/attribute/index.mjs +0 -51
  40. package/dist/node/attribute/value.mjs +0 -65
  41. package/src/node/attribute/index.ts +0 -100
  42. package/src/node/attribute/value.ts +0 -158
@@ -0,0 +1,7 @@
1
+ import { FragmentData } from "../models.mjs";
2
+
3
+ //#region src/attribute/index.d.ts
4
+ declare function mapAttributeValue(data: FragmentData, element: HTMLElement | SVGElement, name: string, value: unknown): void;
5
+ declare function mapAttributes(data: FragmentData, element: HTMLElement | SVGElement, ignoreValue: boolean): void;
6
+ //#endregion
7
+ export { mapAttributeValue, mapAttributes };
@@ -0,0 +1,47 @@
1
+ import { EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_EVENT_PREFIX } from "../constants.mjs";
2
+ import { setComputedValue } from "../helpers/index.mjs";
3
+ import { mapEvent } from "../node/event.mjs";
4
+ import { setAttribute } from "./value.mjs";
5
+ import { isSignal } from "@oscarpalmer/mora";
6
+ import { isInputElement } from "@oscarpalmer/toretto/is";
7
+ //#region src/attribute/index.ts
8
+ function compareAttributes(first, second) {
9
+ return first.name.localeCompare(second.name);
10
+ }
11
+ function getValue(data, original) {
12
+ const matches = EXPRESSION_ABYDON_CONTENT.exec(original);
13
+ return matches == null ? original : data.values[+matches[1]];
14
+ }
15
+ function mapAttributeValue(data, element, name, value) {
16
+ if (typeof value === "function") setComputedAttribute(data, element, name, value);
17
+ else setAttribute(data, element, name, value);
18
+ if (isInputElement(element) && isSignal(value) && name in element) mapEvent(element, "on", () => {
19
+ value.set(element[name]);
20
+ });
21
+ }
22
+ function mapAttributes(data, element, ignoreValue) {
23
+ const attributes = [...element.attributes].sort(compareAttributes);
24
+ const { length } = attributes;
25
+ for (let index = 0; index < length; index += 1) {
26
+ const { name, value } = attributes[index];
27
+ const actualName = name.replace(EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, "");
28
+ const actualValue = getValue(data, value);
29
+ if (actualName !== name) element.removeAttribute(name);
30
+ if (actualName === "value" && ignoreValue) continue;
31
+ switch (true) {
32
+ case EXPRESSION_EVENT_PREFIX.test(actualName):
33
+ mapEvent(element, actualName, actualValue);
34
+ break;
35
+ default:
36
+ mapAttributeValue(data, element, actualName, actualValue);
37
+ break;
38
+ }
39
+ }
40
+ }
41
+ function setComputedAttribute(data, element, name, callback) {
42
+ setComputedValue(data, callback, (computation) => {
43
+ setAttribute(data, element, name, computation);
44
+ });
45
+ }
46
+ //#endregion
47
+ export { mapAttributeValue, mapAttributes };
@@ -1,6 +1,6 @@
1
- import { FragmentData } from "../../models.mjs";
1
+ import { FragmentData } from "../models.mjs";
2
2
 
3
- //#region src/node/attribute/value.d.ts
3
+ //#region src/attribute/value.d.ts
4
4
  declare function setAttribute(data: FragmentData, element: HTMLElement | SVGElement, name: string, value: unknown): void;
5
5
  //#endregion
6
6
  export { setAttribute };
@@ -0,0 +1,53 @@
1
+ import { EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_ATTRIBUTE_STYLE_VARIABLE } from "../constants.mjs";
2
+ import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
3
+ import { getString } from "@oscarpalmer/atoms/string";
4
+ import { isReactive } from "@oscarpalmer/mora";
5
+ import { setAttribute as setAttribute$1 } from "@oscarpalmer/toretto/attribute";
6
+ import { setStyle } from "@oscarpalmer/toretto/style";
7
+ //#region src/attribute/value.ts
8
+ function getStyleValue(value, unit, isVariable) {
9
+ if (removeStyleValue(value, unit, isVariable)) return;
10
+ return value === true || value === "true" ? unit : `${getString(value)}${unit ?? ""}`;
11
+ }
12
+ function removeStyleValue(value, unit, isVariable) {
13
+ if (value == null || value === false || isVariable && isNullableOrWhitespace(value)) return true;
14
+ return unit == null && (value === true || value === "true");
15
+ }
16
+ function setAttribute(data, element, name, value) {
17
+ switch (true) {
18
+ case EXPRESSION_ATTRIBUTE_CLASS.test(name):
19
+ setClassValues(data, element, name, value);
20
+ return;
21
+ case EXPRESSION_ATTRIBUTE_STYLE_PREFIX.test(name):
22
+ setStyleValues(data, element, name, value);
23
+ return;
24
+ default:
25
+ setValue(data, element, name, value);
26
+ break;
27
+ }
28
+ }
29
+ function setClassValues(data, element, name, value) {
30
+ const classes = name.slice(6).split(".");
31
+ updateValue(data, value, (value) => {
32
+ if (value === true || value === "true") element.classList.add(...classes);
33
+ else element.classList.remove(...classes);
34
+ });
35
+ }
36
+ function setStyleValues(data, element, name, value) {
37
+ let [, property, unit] = EXPRESSION_ATTRIBUTE_STYLE_FULL.exec(name);
38
+ const isVariable = EXPRESSION_ATTRIBUTE_STYLE_VARIABLE.test(name);
39
+ updateValue(data, value, (value) => {
40
+ setStyle(element, property, getStyleValue(value, unit, isVariable));
41
+ });
42
+ }
43
+ function setValue(data, element, name, value) {
44
+ updateValue(data, value, (value) => {
45
+ setAttribute$1(element, name, value);
46
+ });
47
+ }
48
+ function updateValue(data, value, updater) {
49
+ if (isReactive(value)) data.mora.subscribers.add(value.subscribe(updater));
50
+ else updater(value);
51
+ }
52
+ //#endregion
53
+ export { setAttribute };
@@ -2,16 +2,16 @@
2
2
  declare const ARRAY_COMPARISON_ADDED = "added";
3
3
  declare const ARRAY_COMPARISON_DISSIMILAR = "dissimilar";
4
4
  declare const ARRAY_COMPARISON_REMOVED = "removed";
5
- declare const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
5
+ declare const ATTRIBUTE_CLASS_PREFIX_LENGTH: number;
6
6
  declare const ATTRIBUTE_NAME_DELIMITER = ".";
7
+ declare const CHANGE_INPUTS: Set<string>;
7
8
  declare const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
8
- declare const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
9
+ declare const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<>'";
9
10
  declare const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
10
11
  declare const EVENT_CHANGE = "change";
11
12
  declare const EVENT_INPUT = "input";
12
13
  declare const EVENT_SUBMIT = "submit";
13
14
  declare const EVENT_DEFAULTS: Record<string, string>;
14
- declare const EVENT_ON_PREFIXED = "@on";
15
15
  declare const EVENT_ON_VALUE = "on";
16
16
  declare const EVENT_OPTIONS_DELIMITER = ":";
17
17
  declare const EXPRESSION_ABYDON_ATTRIBUTE_FULL: RegExp;
@@ -20,20 +20,20 @@ declare const EXPRESSION_ABYDON_CONTENT: RegExp;
20
20
  declare const EXPRESSION_ATTRIBUTE_CLASS: RegExp;
21
21
  declare const EXPRESSION_ATTRIBUTE_STYLE_FULL: RegExp;
22
22
  declare const EXPRESSION_ATTRIBUTE_STYLE_PREFIX: RegExp;
23
- declare const EXPRESSION_EVENT_CHANGE_TYPES: RegExp;
23
+ declare const EXPRESSION_ATTRIBUTE_STYLE_VARIABLE: RegExp;
24
+ declare const EXPRESSION_EVENT_ATTRIBUTE: RegExp;
24
25
  declare const EXPRESSION_EVENT_NAME: RegExp;
25
26
  declare const EXPRESSION_EVENT_OPTIONS_ACTIVE: RegExp;
26
27
  declare const EXPRESSION_EVENT_OPTIONS_CAPTURE: RegExp;
27
28
  declare const EXPRESSION_EVENT_OPTIONS_ONCE: RegExp;
28
29
  declare const EXPRESSION_EVENT_PREFIX: RegExp;
29
- declare const EXPRESSION_PERIOD: RegExp;
30
30
  declare const EXPRESSION_TEXTAREA_VALUE: RegExp;
31
- declare const INPUT_TYPE_CHECKBOX = "checkbox";
32
31
  declare const NAME_FRAGMENT = "$fragment";
33
32
  declare const NAME_FRAGMENTS = "$fragments";
34
- declare const PROPERTY_CHECKED = "checked";
35
33
  declare const PROPERTY_IDENTIFIER = "identifier";
36
34
  declare const PROPERTY_VALUE = "value";
37
35
  declare const TEMPLATE_ITEM = "<>";
36
+ declare const VALUE_TRUE = "true";
37
+ declare const WHITESPACE: RegExp;
38
38
  //#endregion
39
- export { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, ATTRIBUTE_CLASS_PREFIX_LENGTH, ATTRIBUTE_NAME_DELIMITER, ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_ON_PREFIXED, EVENT_ON_VALUE, EVENT_OPTIONS_DELIMITER, EVENT_SUBMIT, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD, EXPRESSION_TEXTAREA_VALUE, INPUT_TYPE_CHECKBOX, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_CHECKED, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM };
39
+ export { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, ATTRIBUTE_CLASS_PREFIX_LENGTH, ATTRIBUTE_NAME_DELIMITER, CHANGE_INPUTS, ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_ON_VALUE, EVENT_OPTIONS_DELIMITER, EVENT_SUBMIT, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_ATTRIBUTE_STYLE_VARIABLE, EXPRESSION_EVENT_ATTRIBUTE, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_TEXTAREA_VALUE, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM, VALUE_TRUE, WHITESPACE };
@@ -4,8 +4,9 @@ const ARRAY_COMPARISON_DISSIMILAR = "dissimilar";
4
4
  const ARRAY_COMPARISON_REMOVED = "removed";
5
5
  const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
6
6
  const ATTRIBUTE_NAME_DELIMITER = ".";
7
+ const CHANGE_INPUTS = new Set(["checkbox", "radio"]);
7
8
  const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
8
- const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
9
+ const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<>'";
9
10
  const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
10
11
  const EVENT_CHANGE = "change";
11
12
  const EVENT_INPUT = "input";
@@ -18,29 +19,28 @@ const EVENT_DEFAULTS = {
18
19
  SELECT: EVENT_CHANGE,
19
20
  TEXTAREA: EVENT_INPUT
20
21
  };
21
- const EVENT_ON_PREFIXED = "@on";
22
22
  const EVENT_ON_VALUE = "on";
23
23
  const EVENT_OPTIONS_DELIMITER = ":";
24
- const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?[\w-]+)="<!--abydon.(\d+)-->"/g;
25
- const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
26
- const EXPRESSION_ABYDON_CONTENT = /^@?abydon\.(\d+)@?$/;
24
+ const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?[\w-.]+(?::[a-z:]+)?)="<!--abydon.(\d+)-->"/g;
25
+ const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^abydon-/;
26
+ const EXPRESSION_ABYDON_CONTENT = /^abydon\.(\d+)$/;
27
27
  const EXPRESSION_ATTRIBUTE_CLASS = /^class\./;
28
28
  const EXPRESSION_ATTRIBUTE_STYLE_FULL = /^style\.([\w-]+)(?:\.([\w-]+))?$/;
29
29
  const EXPRESSION_ATTRIBUTE_STYLE_PREFIX = /^style\./;
30
- const EXPRESSION_EVENT_CHANGE_TYPES = /^(checkbox|radio)$/;
31
- const EXPRESSION_EVENT_NAME = /^@([\w-]+)(?::([a-z:]+))?$/i;
32
- const EXPRESSION_EVENT_OPTIONS_ACTIVE = /^a(ctive)$/i;
33
- const EXPRESSION_EVENT_OPTIONS_CAPTURE = /^c(apture)$/i;
34
- const EXPRESSION_EVENT_OPTIONS_ONCE = /^o(nce)$/i;
30
+ const EXPRESSION_ATTRIBUTE_STYLE_VARIABLE = /^style\.--/;
31
+ const EXPRESSION_EVENT_ATTRIBUTE = /^@([\w-]+)(?::([a-z:]+))?="$/i;
32
+ const EXPRESSION_EVENT_NAME = /^@?([\w-]+)(?::([a-z:]+))?$/i;
33
+ const EXPRESSION_EVENT_OPTIONS_ACTIVE = /^a(?:ctive)$/i;
34
+ const EXPRESSION_EVENT_OPTIONS_CAPTURE = /^c(?:apture)$/i;
35
+ const EXPRESSION_EVENT_OPTIONS_ONCE = /^o(?:nce)$/i;
35
36
  const EXPRESSION_EVENT_PREFIX = /^@/;
36
- const EXPRESSION_PERIOD = /\./;
37
- const EXPRESSION_TEXTAREA_VALUE = /<!--abydon\.(\d+)-->/;
38
- const INPUT_TYPE_CHECKBOX = "checkbox";
37
+ const EXPRESSION_TEXTAREA_VALUE = /(?:<|&lt;)!--abydon\.(\d+)--(?:>|&gt;)/;
39
38
  const NAME_FRAGMENT = "$fragment";
40
39
  const NAME_FRAGMENTS = "$fragments";
41
- const PROPERTY_CHECKED = "checked";
42
40
  const PROPERTY_IDENTIFIER = "identifier";
43
41
  const PROPERTY_VALUE = "value";
44
42
  const TEMPLATE_ITEM = "<>";
43
+ const VALUE_TRUE = "true";
44
+ const WHITESPACE = /\s+/g;
45
45
  //#endregion
46
- export { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, ATTRIBUTE_CLASS_PREFIX_LENGTH, ATTRIBUTE_NAME_DELIMITER, ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_ON_PREFIXED, EVENT_ON_VALUE, EVENT_OPTIONS_DELIMITER, EVENT_SUBMIT, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD, EXPRESSION_TEXTAREA_VALUE, INPUT_TYPE_CHECKBOX, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_CHECKED, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM };
46
+ export { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, ATTRIBUTE_CLASS_PREFIX_LENGTH, ATTRIBUTE_NAME_DELIMITER, CHANGE_INPUTS, ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_ON_VALUE, EVENT_OPTIONS_DELIMITER, EVENT_SUBMIT, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_ATTRIBUTE_STYLE_VARIABLE, EXPRESSION_EVENT_ATTRIBUTE, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_TEXTAREA_VALUE, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM, VALUE_TRUE, WHITESPACE };
@@ -4,37 +4,54 @@ import { FragmentConfiguration } from "./models.mjs";
4
4
  declare class Fragment {
5
5
  #private;
6
6
  /**
7
- * Fragment identifier
7
+ * Is template caching enabled?
8
+ */
9
+ get cache(): boolean;
10
+ /**
11
+ * Identifier for the _Fragment_
12
+ *
13
+ * _An identifier can be used to uniquely identify a Fragment, which helps prevent re-rendering in reactive arrays and Fragments_
8
14
  */
9
15
  get identifier(): unknown;
10
16
  constructor(strings: TemplateStringsArray, expressions: unknown[]);
11
17
  /**
12
- * Append the fragment to the given element
18
+ * Insert the _Fragment_ after the given element
19
+ * @param element Element to insert after
20
+ */
21
+ after(element: Element): void;
22
+ /**
23
+ * Append the _Fragment_ to the given element
13
24
  * @param element Element to append to
14
25
  */
15
26
  appendTo(element: Element): void;
16
27
  /**
17
- * Configure the fragment
28
+ * Insert the _Fragment_ before the given element
29
+ * @param element Element to insert before
30
+ */
31
+ before(element: Element): void;
32
+ /**
33
+ * Configure the _Fragment_
34
+ *
35
+ * _Returns the Fragment instance for chaining_
18
36
  * @param configuration Configuration options
19
- * @returns Fragment
37
+ * @returns _Fragment_
20
38
  */
21
39
  configure(configuration: FragmentConfiguration): Fragment;
22
40
  /**
23
- * Get a list of the fragment's nodes
41
+ * Get a list of the _Fragment_'s nodes
24
42
  * @returns List of nodes
25
43
  */
26
44
  get(): ChildNode[];
27
45
  /**
28
- * Set an identifier for the fragment
29
- *
30
- * _An identifier can be used to uniquely identify a fragment,
31
- * which helps prevent re-rendering in certain scenarios._
32
- * @param identifier Identifier
33
- * @returns Fragment
46
+ * Prepend the _Fragment_ to the given element
47
+ * @param element Element to prepend to
34
48
  */
35
- identify(identifier: unknown): Fragment;
49
+ prependTo(element: Element): void;
36
50
  /**
37
- * Remove the fragment from the DOM
51
+ * Remove the _Fragment_ _(and all its descendants)_ from the _DOM_
52
+ *
53
+ * - _Any events, reactive values, and Fragments will also be cleaned up and removed_
54
+ * - _After being removed, the Fragment can be re-inserted into the DOM_
38
55
  */
39
56
  remove(): void;
40
57
  }
package/dist/fragment.mjs CHANGED
@@ -14,7 +14,15 @@ var Fragment = class {
14
14
  cache: true
15
15
  };
16
16
  /**
17
- * Fragment identifier
17
+ * Is template caching enabled?
18
+ */
19
+ get cache() {
20
+ return this.#configuration.cache;
21
+ }
22
+ /**
23
+ * Identifier for the _Fragment_
24
+ *
25
+ * _An identifier can be used to uniquely identify a Fragment, which helps prevent re-rendering in reactive arrays and Fragments_
18
26
  */
19
27
  get identifier() {
20
28
  return this.#configuration.identifier;
@@ -33,16 +41,32 @@ var Fragment = class {
33
41
  };
34
42
  }
35
43
  /**
36
- * Append the fragment to the given element
44
+ * Insert the _Fragment_ after the given element
45
+ * @param element Element to insert after
46
+ */
47
+ after(element) {
48
+ element.after(...this.get());
49
+ }
50
+ /**
51
+ * Append the _Fragment_ to the given element
37
52
  * @param element Element to append to
38
53
  */
39
54
  appendTo(element) {
40
55
  element.append(...this.get());
41
56
  }
42
57
  /**
43
- * Configure the fragment
58
+ * Insert the _Fragment_ before the given element
59
+ * @param element Element to insert before
60
+ */
61
+ before(element) {
62
+ element.before(...this.get());
63
+ }
64
+ /**
65
+ * Configure the _Fragment_
66
+ *
67
+ * _Returns the Fragment instance for chaining_
44
68
  * @param configuration Configuration options
45
- * @returns Fragment
69
+ * @returns _Fragment_
46
70
  */
47
71
  configure(configuration) {
48
72
  const actual = isPlainObject(configuration) ? configuration : {};
@@ -51,7 +75,7 @@ var Fragment = class {
51
75
  return this;
52
76
  }
53
77
  /**
54
- * Get a list of the fragment's nodes
78
+ * Get a list of the _Fragment_'s nodes
55
79
  * @returns List of nodes
56
80
  */
57
81
  get() {
@@ -59,24 +83,22 @@ var Fragment = class {
59
83
  if (data.items.length === 0) {
60
84
  const templated = html(parse(data), { cache: this.#configuration.cache });
61
85
  data.items.splice(0, data.items.length, ...templated.map((node) => ({ nodes: [node] })));
62
- mapNodes(data, data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes ?? []));
86
+ mapNodes(data, data.items.flatMap((item) => item.nodes));
63
87
  }
64
- return data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes ?? []);
88
+ return data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes);
65
89
  }
66
90
  /**
67
- * Set an identifier for the fragment
68
- *
69
- * _An identifier can be used to uniquely identify a fragment,
70
- * which helps prevent re-rendering in certain scenarios._
71
- * @param identifier Identifier
72
- * @returns Fragment
91
+ * Prepend the _Fragment_ to the given element
92
+ * @param element Element to prepend to
73
93
  */
74
- identify(identifier) {
75
- this.#configuration.identifier = identifier;
76
- return this;
94
+ prependTo(element) {
95
+ element.prepend(...this.get());
77
96
  }
78
97
  /**
79
- * Remove the fragment from the DOM
98
+ * Remove the _Fragment_ _(and all its descendants)_ from the _DOM_
99
+ *
100
+ * - _Any events, reactive values, and Fragments will also be cleaned up and removed_
101
+ * - _After being removed, the Fragment can be re-inserted into the DOM_
80
102
  */
81
103
  remove() {
82
104
  removeFragment(this.#data);
@@ -89,7 +111,7 @@ function removeFragment(data) {
89
111
  const { fragments, nodes } = data.items[index];
90
112
  const fragmentsLength = fragments?.length ?? 0;
91
113
  for (let fragmentIndex = 0; fragmentIndex < fragmentsLength; fragmentIndex += 1) fragments?.[fragmentIndex]?.remove();
92
- removeNodes(nodes ?? []);
114
+ removeNodes(nodes);
93
115
  }
94
116
  data.items.length = 0;
95
117
  length = data.values.length;
@@ -1,17 +1,13 @@
1
- import { FragmentsState } from "./models.mjs";
2
1
  import { Fragment } from "./fragment.mjs";
2
+ import { FragmentsState } from "./models.mjs";
3
3
  import { ReactiveArray } from "@oscarpalmer/mora";
4
4
 
5
5
  //#region src/fragments.d.ts
6
6
  declare class Fragments {
7
7
  #private;
8
- /**
9
- * Fragment items
10
- */
11
- get items(): ReactiveArray<Fragment>;
12
8
  constructor(items: ReactiveArray<unknown>, identify: (item: unknown) => unknown, fragment: (item: unknown) => Fragment);
13
9
  }
14
10
  declare function handleFragments(item: Fragments | FragmentsState, remove: boolean): void;
15
- declare function initializeFragments(state: FragmentsState): void;
11
+ declare const fragmentsStates: WeakMap<Fragments, FragmentsState>;
16
12
  //#endregion
17
- export { Fragments, handleFragments, initializeFragments };
13
+ export { Fragments, fragmentsStates, handleFragments };
@@ -5,12 +5,6 @@ import { array } from "@oscarpalmer/mora";
5
5
  //#region src/fragments.ts
6
6
  var Fragments = class {
7
7
  #state;
8
- /**
9
- * Fragment items
10
- */
11
- get items() {
12
- return this.#state.mapped;
13
- }
14
8
  constructor(items, identify, fragment) {
15
9
  Object.defineProperty(this, NAME_FRAGMENTS, { value: true });
16
10
  this.#state = {
@@ -21,13 +15,13 @@ var Fragments = class {
21
15
  mapped: array([]),
22
16
  subscriber: void 0
23
17
  };
24
- states.set(this, this.#state);
18
+ fragmentsStates.set(this, this.#state);
25
19
  initializeFragments(this.#state);
26
20
  }
27
21
  };
28
22
  function handleFragments(item, remove) {
29
- const state = isFragments(item) ? states.get(item) : item;
30
- if (state != null) (remove ? removeFragments : initializeFragments)(state);
23
+ const state = isFragments(item) ? fragmentsStates.get(item) : item;
24
+ (remove ? removeFragments : initializeFragments)(state);
31
25
  }
32
26
  function handleItems(state, items) {
33
27
  const keys = /* @__PURE__ */ new Set();
@@ -44,7 +38,7 @@ function handleItems(state, items) {
44
38
  instance = state.fragment(item);
45
39
  if (!isFragment(instance)) throw new Error(ERROR_FRAGMENT);
46
40
  }
47
- instance.identify(key);
41
+ instance.configure({ identifier: key });
48
42
  state.instances[key] = instance;
49
43
  keys.add(key);
50
44
  mapped.push(instance);
@@ -75,6 +69,6 @@ function updateFragments(state, active) {
75
69
  state.instances = next;
76
70
  active?.clear();
77
71
  }
78
- const states = /* @__PURE__ */ new WeakMap();
72
+ const fragmentsStates = /* @__PURE__ */ new WeakMap();
79
73
  //#endregion
80
- export { Fragments, handleFragments, initializeFragments };
74
+ export { Fragments, fragmentsStates, handleFragments };
@@ -1,7 +1,6 @@
1
1
  //#region src/helpers/dom.d.ts
2
2
  declare function createNodes(value: unknown): ChildNode[];
3
- declare function isInputElement(node: Node): node is HTMLInputElement | HTMLSelectElement;
4
3
  declare function removeNodes(nodes: ChildNode[]): void;
5
- declare function replaceNodes(from: ChildNode[], to: ChildNode[]): void;
4
+ declare function replaceNodes(from: ChildNode[], to: ChildNode[]): ChildNode[];
6
5
  //#endregion
7
- export { createNodes, isInputElement, removeNodes, replaceNodes };
6
+ export { createNodes, removeNodes, replaceNodes };
@@ -7,9 +7,6 @@ function createNodes(value) {
7
7
  if (isChildNode(value)) return [value];
8
8
  return [new Text(getString(value))];
9
9
  }
10
- function isInputElement(node) {
11
- return node instanceof HTMLInputElement || node instanceof HTMLSelectElement;
12
- }
13
10
  function removeNodes(nodes) {
14
11
  const { length } = nodes;
15
12
  for (let index = 0; index < length; index += 1) nodes[index].remove();
@@ -18,6 +15,7 @@ function replaceNodes(from, to) {
18
15
  from[0]?.replaceWith(...to);
19
16
  const { length } = from;
20
17
  for (let index = 1; index < length; index += 1) from[index].remove();
18
+ return to;
21
19
  }
22
20
  //#endregion
23
- export { createNodes, isInputElement, removeNodes, replaceNodes };
21
+ export { createNodes, removeNodes, replaceNodes };
@@ -1,10 +1,24 @@
1
- import { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED } from "../constants.mjs";
2
1
  import { Fragment } from "../fragment.mjs";
2
+ import { FragmentData } from "../models.mjs";
3
+ import { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED } from "../constants.mjs";
3
4
  import { Fragments } from "../fragments.mjs";
5
+ import { Computed } from "@oscarpalmer/mora";
6
+ import { GenericCallback } from "@oscarpalmer/atoms/models";
4
7
 
5
8
  //#region src/helpers/index.d.ts
6
9
  declare function compareArrays(first: unknown[], second: unknown[]): typeof ARRAY_COMPARISON_ADDED | typeof ARRAY_COMPARISON_DISSIMILAR | typeof ARRAY_COMPARISON_REMOVED;
10
+ /**
11
+ * Is the value a _Fragment_?
12
+ * @param value Value to check
13
+ * @returns `true` if the value is a _Fragment_, otherwise `false`
14
+ */
7
15
  declare function isFragment(value: unknown): value is Fragment;
16
+ /**
17
+ * Is the value a _Fragments_ instance?
18
+ * @param value Value to check
19
+ * @returns `true` if the value is a _Fragments_ instance, otherwise `false`
20
+ */
8
21
  declare function isFragments(value: unknown): value is Fragments;
22
+ declare function setComputedValue(data: FragmentData, callback: GenericCallback, after: (computation: Computed<unknown>) => void): void;
9
23
  //#endregion
10
- export { compareArrays, isFragment, isFragments };
24
+ export { compareArrays, isFragment, isFragments, setComputedValue };
@@ -1,4 +1,5 @@
1
1
  import { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, NAME_FRAGMENT, NAME_FRAGMENTS } from "../constants.mjs";
2
+ import { computed } from "@oscarpalmer/mora";
2
3
  //#region src/helpers/index.ts
3
4
  function compareArrays(first, second) {
4
5
  const firstIsLarger = first.length > second.length;
@@ -7,14 +8,29 @@ function compareArrays(first, second) {
7
8
  if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return ARRAY_COMPARISON_DISSIMILAR;
8
9
  return firstIsLarger ? ARRAY_COMPARISON_REMOVED : ARRAY_COMPARISON_ADDED;
9
10
  }
11
+ /**
12
+ * Is the value a _Fragment_?
13
+ * @param value Value to check
14
+ * @returns `true` if the value is a _Fragment_, otherwise `false`
15
+ */
10
16
  function isFragment(value) {
11
17
  return isNamed(value, NAME_FRAGMENT);
12
18
  }
19
+ /**
20
+ * Is the value a _Fragments_ instance?
21
+ * @param value Value to check
22
+ * @returns `true` if the value is a _Fragments_ instance, otherwise `false`
23
+ */
13
24
  function isFragments(value) {
14
25
  return isNamed(value, NAME_FRAGMENTS);
15
26
  }
16
27
  function isNamed(value, name) {
17
28
  return typeof value === "object" && value != null && name in value && value[name] === true;
18
29
  }
30
+ function setComputedValue(data, callback, after) {
31
+ const computation = computed(callback);
32
+ data.mora.values.add(computation);
33
+ after(computation);
34
+ }
19
35
  //#endregion
20
- export { compareArrays, isFragment, isFragments };
36
+ export { compareArrays, isFragment, isFragments, setComputedValue };
package/dist/index.d.mts CHANGED
@@ -1,21 +1,49 @@
1
1
  import { Fragment } from "./fragment.mjs";
2
2
  import { Fragments } from "./fragments.mjs";
3
+ import { isFragment, isFragments } from "./helpers/index.mjs";
3
4
  import { ReactiveArray } from "@oscarpalmer/mora";
4
5
  export * from "@oscarpalmer/mora";
5
6
 
6
7
  //#region src/index.d.ts
7
8
  /**
8
- * Create Fragments from a reactive array
9
+ * Create a _Fragments_ instance from a reactive array
10
+ *
11
+ * _A Fragments instance can be used to efficiently render a list of items that may change over time, using unique identifiers to track each item, only adding, removing, or updating each related Fragment._
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const fruits = array(['Apple', 'Banana', 'Cherry']);
16
+ * const items = fragments(
17
+ * fruits,
18
+ * fruit => fruit, // Identifies a unique item
19
+ * fruit => html`<p>${fruit}</p>`, // Creates a Fragment from an item
20
+ * );
21
+ * html`${items}`.appendTo(document.body) // Renders '<p>Apple</p><p>Banana</p><p>Cherry</p>'
22
+ * fruits.push(['Date', 'Elderberry', 'Fig']); // Appends '<p>Date</p><p>Elderberry</p><p>Fig</p>'
23
+ * // without re-rendering the existing Fragments
24
+ * ```
25
+ *
9
26
  * @param array Reactive array
10
- * @param identify Function to identify item
11
- * @param fragment Function to create fragment from item
12
- * @returns Fragments
27
+ * @param identify Function to identify item uniquely _(non-nullable)_
28
+ * @param fragment Function to create _Fragment_ from item
29
+ * @returns _Fragments_
13
30
  */
14
31
  declare function fragments<Item>(array: ReactiveArray<Item>, identify: (item: Item) => unknown, fragment: (item: Item) => Fragment): Fragments;
15
32
  /**
16
- * Create Fragment from a template
17
- * @returns Fragment
33
+ * Create a _Fragment_ from a template
34
+ *
35
+ * _A Fragment can be used to efficiently render a template that may change over time, only updating the necessary parts of the DOM._
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const name = signal('World');
40
+ * const fragment = html`<p>Hello, ${name}!</p>`;
41
+ * fragment.appendTo(document.body); // Renders '<p>Hello, World!</p>'
42
+ * name.set('Alice'); // Replaces 'World' with 'Alice'
43
+ * ```
44
+ *
45
+ * @returns _Fragment_
18
46
  */
19
47
  declare function html(template: TemplateStringsArray, ...values: unknown[]): Fragment;
20
48
  //#endregion
21
- export { type Fragment, type Fragments, fragments, html };
49
+ export { type Fragment, type Fragments, fragments, html, isFragment, isFragments };