@oscarpalmer/abydon 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { FragmentData } from "../models.mjs";
2
2
 
3
3
  //#region src/attribute/index.d.ts
4
- declare function mapAttributes(data: FragmentData, element: HTMLElement | SVGElement): void;
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;
5
6
  //#endregion
6
- export { mapAttributes };
7
+ export { mapAttributeValue, mapAttributes };
@@ -1,42 +1,48 @@
1
1
  import { EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_EVENT_PREFIX } from "../constants.mjs";
2
- import { isInputElement } from "../helpers/dom.mjs";
2
+ import { setComputedValue } from "../helpers/index.mjs";
3
3
  import { mapEvent } from "../node/event.mjs";
4
4
  import { setAttribute } from "./value.mjs";
5
- import { computed, isSignal } from "@oscarpalmer/mora";
5
+ import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
6
+ import { isSignal } from "@oscarpalmer/mora";
7
+ import { isInputElement } from "@oscarpalmer/toretto/is";
6
8
  //#region src/attribute/index.ts
9
+ function compareAttributes(first, second) {
10
+ return first.name.localeCompare(second.name);
11
+ }
7
12
  function getValue(data, original) {
8
13
  const matches = EXPRESSION_ABYDON_CONTENT.exec(original);
9
14
  return matches == null ? original : data.values[+matches[1]];
10
15
  }
11
- function mapAttributes(data, element) {
12
- const attributes = [...element.attributes].sort((first, second) => first.name.localeCompare(second.name));
16
+ function mapAttributeValue(data, element, name, value) {
17
+ if (typeof value === "function") setComputedAttribute(data, element, name, value);
18
+ else setAttribute(data, element, name, value);
19
+ if (isInputElement(element) && isSignal(value) && name in element) mapEvent(element, "on", () => {
20
+ value.set(element[name]);
21
+ });
22
+ }
23
+ function mapAttributes(data, element, ignoreValue) {
24
+ const attributes = [...element.attributes].sort(compareAttributes);
13
25
  const { length } = attributes;
14
26
  for (let index = 0; index < length; index += 1) {
15
27
  const { name, value } = attributes[index];
16
28
  const actualName = name.replace(EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, "");
17
- const actualValue = getValue(data, value);
18
29
  if (actualName !== name) element.removeAttribute(name);
30
+ if (isNullableOrWhitespace(value) || actualName === "value" && ignoreValue) continue;
31
+ const actualValue = getValue(data, value);
19
32
  switch (true) {
20
33
  case EXPRESSION_EVENT_PREFIX.test(actualName):
21
34
  mapEvent(element, actualName, actualValue);
22
35
  break;
23
36
  default:
24
- mapValue(data, element, actualName, actualValue);
37
+ mapAttributeValue(data, element, actualName, actualValue);
25
38
  break;
26
39
  }
27
40
  }
28
41
  }
29
- function mapValue(data, element, name, value) {
30
- if (typeof value === "function") setComputedAttribute(data, element, name, value);
31
- else setAttribute(data, element, name, value);
32
- if (isInputElement(element) && isSignal(value) && name in element) mapEvent(element, "on", () => {
33
- value.set(element[name]);
34
- });
35
- }
36
42
  function setComputedAttribute(data, element, name, callback) {
37
- const value = computed(callback);
38
- data.mora.values.add(value);
39
- setAttribute(data, element, name, value);
43
+ setComputedValue(data, callback, (computation) => {
44
+ setAttribute(data, element, name, computation);
45
+ });
40
46
  }
41
47
  //#endregion
42
- export { mapAttributes };
48
+ export { mapAttributeValue, mapAttributes };
@@ -1,17 +1,16 @@
1
- import { EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_ATTRIBUTE_STYLE_PROPERTY } from "../constants.mjs";
1
+ import { EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_ATTRIBUTE_STYLE_VARIABLE } from "../constants.mjs";
2
2
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
3
3
  import { getString } from "@oscarpalmer/atoms/string";
4
4
  import { isReactive } from "@oscarpalmer/mora";
5
- import { camelCase } from "@oscarpalmer/atoms/string/case";
6
5
  import { setAttribute as setAttribute$1 } from "@oscarpalmer/toretto/attribute";
7
6
  import { setStyle } from "@oscarpalmer/toretto/style";
8
7
  //#region src/attribute/value.ts
9
- function getStyleValue(value, unit, isProperty) {
10
- if (removeStyleValue(value, unit, isProperty)) return;
8
+ function getStyleValue(value, unit, isVariable) {
9
+ if (removeStyleValue(value, unit, isVariable)) return;
11
10
  return value === true || value === "true" ? unit : `${getString(value)}${unit ?? ""}`;
12
11
  }
13
- function removeStyleValue(value, unit, isProperty) {
14
- if (value == null || value === false || isProperty && isNullableOrWhitespace(value)) return true;
12
+ function removeStyleValue(value, unit, isVariable) {
13
+ if (value == null || value === false || isVariable && isNullableOrWhitespace(value)) return true;
15
14
  return unit == null && (value === true || value === "true");
16
15
  }
17
16
  function setAttribute(data, element, name, value) {
@@ -28,36 +27,27 @@ function setAttribute(data, element, name, value) {
28
27
  }
29
28
  }
30
29
  function setClassValues(data, element, name, value) {
31
- function update(value) {
30
+ const classes = name.slice(6).split(".");
31
+ updateValue(data, value, (value) => {
32
32
  if (value === true || value === "true") element.classList.add(...classes);
33
33
  else element.classList.remove(...classes);
34
- }
35
- const classes = name.slice(6).split(".");
36
- if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
37
- else update(value);
34
+ });
38
35
  }
39
36
  function setStyleValues(data, element, name, value) {
40
37
  let [, property, unit] = EXPRESSION_ATTRIBUTE_STYLE_FULL.exec(name);
41
- const isProperty = EXPRESSION_ATTRIBUTE_STYLE_PROPERTY.test(name);
42
- property = camelCase(property);
43
- if (isProperty) property = `--${property}`;
44
- function update(value) {
45
- setStyle(element, property, getStyleValue(value, unit, isProperty));
46
- }
47
- if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
48
- else update(value);
38
+ const isVariable = EXPRESSION_ATTRIBUTE_STYLE_VARIABLE.test(name);
39
+ updateValue(data, value, (value) => {
40
+ setStyle(element, property, getStyleValue(value, unit, isVariable));
41
+ });
49
42
  }
50
43
  function setValue(data, element, name, value) {
51
- const isReactiveValue = isReactive(value);
52
- unsetValue(element, name, isReactiveValue ? value.peek() : value);
53
- if (isReactiveValue) data.mora.subscribers.add(value.subscribe((next) => {
54
- setAttribute$1(element, name, next);
55
- }));
56
- else setAttribute$1(element, name, value);
44
+ updateValue(data, value, (value) => {
45
+ setAttribute$1(element, name, value);
46
+ });
57
47
  }
58
- function unsetValue(element, name, value) {
59
- if (name === "checked") element.checked = !(value === true || value === "true");
60
- if (name === "value") element.value = element.value === "" ? "_" : "";
48
+ function updateValue(data, value, updater) {
49
+ if (isReactive(value)) data.mora.subscribers.add(value.subscribe(updater));
50
+ else updater(value);
61
51
  }
62
52
  //#endregion
63
53
  export { setAttribute };
@@ -2,11 +2,11 @@
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
7
  declare const CHANGE_INPUTS: Set<string>;
8
8
  declare const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
9
- declare const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
9
+ declare const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<>'";
10
10
  declare const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
11
11
  declare const EVENT_CHANGE = "change";
12
12
  declare const EVENT_INPUT = "input";
@@ -20,24 +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_ATTRIBUTE_STYLE_PROPERTY: RegExp;
23
+ declare const EXPRESSION_ATTRIBUTE_STYLE_VARIABLE: RegExp;
24
24
  declare const EXPRESSION_EVENT_ATTRIBUTE: RegExp;
25
25
  declare const EXPRESSION_EVENT_NAME: RegExp;
26
26
  declare const EXPRESSION_EVENT_OPTIONS_ACTIVE: RegExp;
27
27
  declare const EXPRESSION_EVENT_OPTIONS_CAPTURE: RegExp;
28
28
  declare const EXPRESSION_EVENT_OPTIONS_ONCE: RegExp;
29
29
  declare const EXPRESSION_EVENT_PREFIX: RegExp;
30
- declare const EXPRESSION_PERIOD: RegExp;
31
30
  declare const EXPRESSION_TEXTAREA_VALUE: RegExp;
32
- declare const INPUT_TYPE_CHECKBOX = "checkbox";
33
- declare const INPUT_TYPE_RADIO = "radio";
34
31
  declare const NAME_FRAGMENT = "$fragment";
35
32
  declare const NAME_FRAGMENTS = "$fragments";
36
- declare const PROPERTY_CHECKED = "checked";
37
33
  declare const PROPERTY_IDENTIFIER = "identifier";
38
34
  declare const PROPERTY_VALUE = "value";
39
35
  declare const TEMPLATE_ITEM = "<>";
40
36
  declare const VALUE_TRUE = "true";
41
37
  declare const WHITESPACE: RegExp;
42
38
  //#endregion
43
- 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_PROPERTY, EXPRESSION_EVENT_ATTRIBUTE, 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, INPUT_TYPE_RADIO, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_CHECKED, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM, VALUE_TRUE, WHITESPACE };
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 };
@@ -6,7 +6,7 @@ const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
6
6
  const ATTRIBUTE_NAME_DELIMITER = ".";
7
7
  const CHANGE_INPUTS = new Set(["checkbox", "radio"]);
8
8
  const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
9
- const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
9
+ const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<>'";
10
10
  const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
11
11
  const EVENT_CHANGE = "change";
12
12
  const EVENT_INPUT = "input";
@@ -27,24 +27,20 @@ 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_ATTRIBUTE_STYLE_PROPERTY = /^style\.--/;
30
+ const EXPRESSION_ATTRIBUTE_STYLE_VARIABLE = /^style\.--/;
31
31
  const EXPRESSION_EVENT_ATTRIBUTE = /^@([\w-]+)(?::([a-z:]+))?="$/i;
32
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;
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;
36
36
  const EXPRESSION_EVENT_PREFIX = /^@/;
37
- const EXPRESSION_PERIOD = /\./;
38
37
  const EXPRESSION_TEXTAREA_VALUE = /(?:<|&lt;)!--abydon\.(\d+)--(?:>|&gt;)/;
39
- const INPUT_TYPE_CHECKBOX = "checkbox";
40
- const INPUT_TYPE_RADIO = "radio";
41
38
  const NAME_FRAGMENT = "$fragment";
42
39
  const NAME_FRAGMENTS = "$fragments";
43
- const PROPERTY_CHECKED = "checked";
44
40
  const PROPERTY_IDENTIFIER = "identifier";
45
41
  const PROPERTY_VALUE = "value";
46
42
  const TEMPLATE_ITEM = "<>";
47
43
  const VALUE_TRUE = "true";
48
44
  const WHITESPACE = /\s+/g;
49
45
  //#endregion
50
- 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_PROPERTY, EXPRESSION_EVENT_ATTRIBUTE, 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, INPUT_TYPE_RADIO, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_CHECKED, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM, VALUE_TRUE, WHITESPACE };
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 };
@@ -6,39 +6,52 @@ declare class Fragment {
6
6
  /**
7
7
  * Is template caching enabled?
8
8
  */
9
- get caching(): boolean;
9
+ get cache(): boolean;
10
10
  /**
11
- * Fragment identifier
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_
12
14
  */
13
15
  get identifier(): unknown;
14
16
  constructor(strings: TemplateStringsArray, expressions: unknown[]);
15
17
  /**
16
- * 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
17
24
  * @param element Element to append to
18
25
  */
19
26
  appendTo(element: Element): void;
20
27
  /**
21
- * 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_
22
36
  * @param configuration Configuration options
23
- * @returns Fragment
37
+ * @returns _Fragment_
24
38
  */
25
39
  configure(configuration: FragmentConfiguration): Fragment;
26
40
  /**
27
- * Get a list of the fragment's nodes
41
+ * Get a list of the _Fragment_'s nodes
28
42
  * @returns List of nodes
29
43
  */
30
44
  get(): ChildNode[];
31
45
  /**
32
- * Set an identifier for the fragment
33
- *
34
- * _An identifier can be used to uniquely identify a fragment,
35
- * which helps prevent re-rendering in certain scenarios._
36
- * @param identifier Identifier
37
- * @returns Fragment
46
+ * Prepend the _Fragment_ to the given element
47
+ * @param element Element to prepend to
38
48
  */
39
- identify(identifier: unknown): Fragment;
49
+ prependTo(element: Element): void;
40
50
  /**
41
- * 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_
42
55
  */
43
56
  remove(): void;
44
57
  }
package/dist/fragment.mjs CHANGED
@@ -16,11 +16,13 @@ var Fragment = class {
16
16
  /**
17
17
  * Is template caching enabled?
18
18
  */
19
- get caching() {
19
+ get cache() {
20
20
  return this.#configuration.cache;
21
21
  }
22
22
  /**
23
- * Fragment identifier
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_
24
26
  */
25
27
  get identifier() {
26
28
  return this.#configuration.identifier;
@@ -39,16 +41,32 @@ var Fragment = class {
39
41
  };
40
42
  }
41
43
  /**
42
- * 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
43
52
  * @param element Element to append to
44
53
  */
45
54
  appendTo(element) {
46
55
  element.append(...this.get());
47
56
  }
48
57
  /**
49
- * 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_
50
68
  * @param configuration Configuration options
51
- * @returns Fragment
69
+ * @returns _Fragment_
52
70
  */
53
71
  configure(configuration) {
54
72
  const actual = isPlainObject(configuration) ? configuration : {};
@@ -57,7 +75,7 @@ var Fragment = class {
57
75
  return this;
58
76
  }
59
77
  /**
60
- * Get a list of the fragment's nodes
78
+ * Get a list of the _Fragment_'s nodes
61
79
  * @returns List of nodes
62
80
  */
63
81
  get() {
@@ -70,19 +88,17 @@ var Fragment = class {
70
88
  return data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes);
71
89
  }
72
90
  /**
73
- * Set an identifier for the fragment
74
- *
75
- * _An identifier can be used to uniquely identify a fragment,
76
- * which helps prevent re-rendering in certain scenarios._
77
- * @param identifier Identifier
78
- * @returns Fragment
91
+ * Prepend the _Fragment_ to the given element
92
+ * @param element Element to prepend to
79
93
  */
80
- identify(identifier) {
81
- this.#configuration.identifier = identifier;
82
- return this;
94
+ prependTo(element) {
95
+ element.prepend(...this.get());
83
96
  }
84
97
  /**
85
- * 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_
86
102
  */
87
103
  remove() {
88
104
  removeFragment(this.#data);
@@ -8,7 +8,6 @@ declare class Fragments {
8
8
  constructor(items: ReactiveArray<unknown>, identify: (item: unknown) => unknown, fragment: (item: unknown) => Fragment);
9
9
  }
10
10
  declare function handleFragments(item: Fragments | FragmentsState, remove: boolean): void;
11
- declare function initializeFragments(state: FragmentsState): void;
12
11
  declare const fragmentsStates: WeakMap<Fragments, FragmentsState>;
13
12
  //#endregion
14
- export { Fragments, fragmentsStates, handleFragments, initializeFragments };
13
+ export { Fragments, fragmentsStates, handleFragments };
@@ -38,7 +38,7 @@ function handleItems(state, items) {
38
38
  instance = state.fragment(item);
39
39
  if (!isFragment(instance)) throw new Error(ERROR_FRAGMENT);
40
40
  }
41
- instance.identify(key);
41
+ instance.configure({ identifier: key });
42
42
  state.instances[key] = instance;
43
43
  keys.add(key);
44
44
  mapped.push(instance);
@@ -71,4 +71,4 @@ function updateFragments(state, active) {
71
71
  }
72
72
  const fragmentsStates = /* @__PURE__ */ new WeakMap();
73
73
  //#endregion
74
- export { Fragments, fragmentsStates, 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,20 +1,24 @@
1
1
  import { Fragment } from "../fragment.mjs";
2
+ import { FragmentData } from "../models.mjs";
2
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;
7
10
  /**
8
- * Is the value a Fragment?
11
+ * Is the value a _Fragment_?
9
12
  * @param value Value to check
10
- * @returns `true` if the value is a Fragment, otherwise `false`
13
+ * @returns `true` if the value is a _Fragment_, otherwise `false`
11
14
  */
12
15
  declare function isFragment(value: unknown): value is Fragment;
13
16
  /**
14
- * Is the value a Fragments?
17
+ * Is the value a _Fragments_ instance?
15
18
  * @param value Value to check
16
- * @returns `true` if the value is a Fragments, otherwise `false`
19
+ * @returns `true` if the value is a _Fragments_ instance, otherwise `false`
17
20
  */
18
21
  declare function isFragments(value: unknown): value is Fragments;
22
+ declare function setComputedValue(data: FragmentData, callback: GenericCallback, after: (computation: Computed<unknown>) => void): void;
19
23
  //#endregion
20
- 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;
@@ -8,17 +9,17 @@ function compareArrays(first, second) {
8
9
  return firstIsLarger ? ARRAY_COMPARISON_REMOVED : ARRAY_COMPARISON_ADDED;
9
10
  }
10
11
  /**
11
- * Is the value a Fragment?
12
+ * Is the value a _Fragment_?
12
13
  * @param value Value to check
13
- * @returns `true` if the value is a Fragment, otherwise `false`
14
+ * @returns `true` if the value is a _Fragment_, otherwise `false`
14
15
  */
15
16
  function isFragment(value) {
16
17
  return isNamed(value, NAME_FRAGMENT);
17
18
  }
18
19
  /**
19
- * Is the value a Fragments?
20
+ * Is the value a _Fragments_ instance?
20
21
  * @param value Value to check
21
- * @returns `true` if the value is a Fragments, otherwise `false`
22
+ * @returns `true` if the value is a _Fragments_ instance, otherwise `false`
22
23
  */
23
24
  function isFragments(value) {
24
25
  return isNamed(value, NAME_FRAGMENTS);
@@ -26,5 +27,10 @@ function isFragments(value) {
26
27
  function isNamed(value, name) {
27
28
  return typeof value === "object" && value != null && name in value && value[name] === true;
28
29
  }
30
+ function setComputedValue(data, callback, after) {
31
+ const computation = computed(callback);
32
+ data.mora.values.add(computation);
33
+ after(computation);
34
+ }
29
35
  //#endregion
30
- export { compareArrays, isFragment, isFragments };
36
+ export { compareArrays, isFragment, isFragments, setComputedValue };
package/dist/index.d.mts CHANGED
@@ -6,16 +6,43 @@ export * from "@oscarpalmer/mora";
6
6
 
7
7
  //#region src/index.d.ts
8
8
  /**
9
- * Create a 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
+ *
10
26
  * @param array Reactive array
11
- * @param identify Function to identify item
12
- * @param fragment Function to create fragment from item
13
- * @returns Fragments
27
+ * @param identify Function to identify item uniquely _(non-nullable)_
28
+ * @param fragment Function to create _Fragment_ from item
29
+ * @returns _Fragments_
14
30
  */
15
31
  declare function fragments<Item>(array: ReactiveArray<Item>, identify: (item: Item) => unknown, fragment: (item: Item) => Fragment): Fragments;
16
32
  /**
17
- * Create a Fragment from a template
18
- * @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_
19
46
  */
20
47
  declare function html(template: TemplateStringsArray, ...values: unknown[]): Fragment;
21
48
  //#endregion
package/dist/index.mjs CHANGED
@@ -5,11 +5,27 @@ import { isArray } from "@oscarpalmer/mora";
5
5
  export * from "@oscarpalmer/mora";
6
6
  //#region src/index.ts
7
7
  /**
8
- * Create a Fragments from a reactive array
8
+ * Create a _Fragments_ instance from a reactive array
9
+ *
10
+ * _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._
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const fruits = array(['Apple', 'Banana', 'Cherry']);
15
+ * const items = fragments(
16
+ * fruits,
17
+ * fruit => fruit, // Identifies a unique item
18
+ * fruit => html`<p>${fruit}</p>`, // Creates a Fragment from an item
19
+ * );
20
+ * html`${items}`.appendTo(document.body) // Renders '<p>Apple</p><p>Banana</p><p>Cherry</p>'
21
+ * fruits.push(['Date', 'Elderberry', 'Fig']); // Appends '<p>Date</p><p>Elderberry</p><p>Fig</p>'
22
+ * // without re-rendering the existing Fragments
23
+ * ```
24
+ *
9
25
  * @param array Reactive array
10
- * @param identify Function to identify item
11
- * @param fragment Function to create fragment from item
12
- * @returns Fragments
26
+ * @param identify Function to identify item uniquely _(non-nullable)_
27
+ * @param fragment Function to create _Fragment_ from item
28
+ * @returns _Fragments_
13
29
  */
14
30
  function fragments(array, identify, fragment) {
15
31
  if (!isArray(array)) throw new TypeError("Fragments array must be a reactive array");
@@ -18,8 +34,19 @@ function fragments(array, identify, fragment) {
18
34
  return new Fragments(array, identify, fragment);
19
35
  }
20
36
  /**
21
- * Create a Fragment from a template
22
- * @returns Fragment
37
+ * Create a _Fragment_ from a template
38
+ *
39
+ * _A Fragment can be used to efficiently render a template that may change over time, only updating the necessary parts of the DOM._
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const name = signal('World');
44
+ * const fragment = html`<p>Hello, ${name}!</p>`;
45
+ * fragment.appendTo(document.body); // Renders '<p>Hello, World!</p>'
46
+ * name.set('Alice'); // Replaces 'World' with 'Alice'
47
+ * ```
48
+ *
49
+ * @returns _Fragment_
23
50
  */
24
51
  function html(template, ...values) {
25
52
  return new Fragment(template, values);
package/dist/models.d.mts CHANGED
@@ -2,16 +2,18 @@ import { Fragment } from "./fragment.mjs";
2
2
  import { Reactive, ReactiveArray, Unsubscribe } from "@oscarpalmer/mora";
3
3
 
4
4
  //#region src/models.d.ts
5
+ /**
6
+ * Configuration for a _Fragment_
7
+ */
5
8
  type FragmentConfiguration = {
6
9
  /**
7
10
  * Should the template be cached? _(defaults to `true`)_
8
11
  */
9
12
  cache?: boolean;
10
13
  /**
11
- * Identifier for the fragment
14
+ * Identifier for the _Fragment_
12
15
  *
13
- * _(An identifier can be used to uniquely identify a fragment,
14
- * which helps prevent re-rendering in certain scenarios)_
16
+ * _An identifier can be used to uniquely identify a Fragment, which helps prevent re-rendering in reactive arrays and Fragments_
15
17
  */
16
18
  identifier?: unknown;
17
19
  };
@@ -19,7 +21,7 @@ type FragmentData = {
19
21
  expressions: unknown[];
20
22
  items: FragmentItem[];
21
23
  mora: MoraData;
22
- strings: TemplateStringsArray;
24
+ strings: TemplateStringsArray | string[];
23
25
  template?: string;
24
26
  values: unknown[];
25
27
  };