@oscarpalmer/abydon 0.15.0 → 0.16.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.
package/dist/constants.js CHANGED
@@ -1,10 +1,25 @@
1
- const ABORT_CONTROLLERS = /* @__PURE__ */ new WeakMap();
2
1
  const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
2
+ const EVENT_DEFAULTS = {
3
+ A: "click",
4
+ BUTTON: "click",
5
+ DETAILS: "toggle",
6
+ FORM: "submit",
7
+ SELECT: "change",
8
+ TEXTAREA: "input"
9
+ };
10
+ const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?\w+)="<!--abydon.(\d+)-->"/g;
11
+ const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
12
+ const EXPRESSION_ABYDON_CONTENT = /^@?abydon\.(\d+)@?$/;
3
13
  const EXPRESSION_ATTRIBUTE_CLASS = /^class\./;
4
14
  const EXPRESSION_ATTRIBUTE_STYLE_FULL = /^style\.([\w-]+)(?:\.([\w-]+))?$/;
5
15
  const EXPRESSION_ATTRIBUTE_STYLE_PREFIX = /^style\./;
6
- const EXPRESSION_COMMENT_FULL = /^<!--abydon\.(\d+)-->$/;
7
- const EXPRESSION_COMMENT_CONTENT = /^abydon\.(\d+)$/;
16
+ const EXPRESSION_EVENT_CHANGE_TYPES = /^(checkbox|radio)$/;
8
17
  const EXPRESSION_EVENT_NAME = /^@([\w-]+)(?::([a-z:]+))?$/i;
9
- const REASON_EVENT_REMOVED = "Event removed as element was removed from document by Abydon";
10
- export { ABORT_CONTROLLERS, ATTRIBUTE_CLASS_PREFIX_LENGTH, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_COMMENT_CONTENT, EXPRESSION_COMMENT_FULL, EXPRESSION_EVENT_NAME, REASON_EVENT_REMOVED };
18
+ const EXPRESSION_EVENT_OPTIONS_ACTIVE = /^a(ctive)$/i;
19
+ const EXPRESSION_EVENT_OPTIONS_CAPTURE = /^c(apture)$/i;
20
+ const EXPRESSION_EVENT_OPTIONS_ONCE = /^o(nce)$/i;
21
+ const EXPRESSION_EVENT_PREFIX = /^@/;
22
+ const EXPRESSION_PERIOD = /\./;
23
+ const NAME_FRAGMENT = "$fragment";
24
+ const NAME_FRAGMENTS = "$fragments";
25
+ export { ATTRIBUTE_CLASS_PREFIX_LENGTH, EVENT_DEFAULTS, 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, NAME_FRAGMENT, NAME_FRAGMENTS };
package/dist/fragment.js CHANGED
@@ -1,21 +1,22 @@
1
- import { parse } from "./parse.js";
1
+ import { NAME_FRAGMENT } from "./constants.js";
2
2
  import { isFragments } from "./helpers/index.js";
3
3
  import { handleFragments } from "./fragments.js";
4
4
  import { removeNodes } from "./helpers/dom.js";
5
5
  import { mapNodes } from "./node/index.js";
6
+ import { parse } from "./parse.js";
6
7
  import { isPlainObject } from "@oscarpalmer/atoms/is";
7
8
  import { html } from "@oscarpalmer/toretto/html";
8
9
  var Fragment = class {
9
10
  #data;
10
11
  #configuration = {
11
12
  identifier: void 0,
12
- ignoreCache: false
13
+ cache: true
13
14
  };
14
15
  get identifier() {
15
16
  return this.#configuration.identifier;
16
17
  }
17
18
  constructor(strings, expressions) {
18
- Object.defineProperty(this, "$fragment", { value: true });
19
+ Object.defineProperty(this, NAME_FRAGMENT, { value: true });
19
20
  this.#data = {
20
21
  expressions,
21
22
  strings,
@@ -32,21 +33,18 @@ var Fragment = class {
32
33
  }
33
34
  configure(configuration) {
34
35
  const actual = isPlainObject(configuration) ? configuration : {};
35
- if (actual.identifier !== void 0) this.#configuration.identifier = actual.identifier;
36
- if (typeof actual.ignoreCache === "boolean") this.#configuration.ignoreCache = actual.ignoreCache;
36
+ if ("identifier" in actual) this.#configuration.identifier = actual.identifier;
37
+ if (typeof actual.cache === "boolean") this.#configuration.cache = actual.cache;
37
38
  return this;
38
39
  }
39
40
  get() {
40
41
  const data = this.#data;
41
42
  if (data.items.length === 0) {
42
- const templated = html(parse(data), {
43
- ignoreCache: this.#configuration.ignoreCache,
44
- sanitizeBooleanAttributes: false
45
- });
43
+ const templated = html(parse(data), { cache: this.#configuration.cache });
46
44
  data.items.splice(0, data.items.length, ...templated.map((node) => ({ nodes: [node] })));
47
45
  mapNodes(data, data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes ?? []));
48
46
  }
49
- return [...data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes ?? [])];
47
+ return data.items.flatMap((item) => item.fragments?.flatMap((fragment) => fragment.get()) ?? item.nodes ?? []);
50
48
  }
51
49
  identify(identifier) {
52
50
  this.#configuration.identifier = identifier;
package/dist/fragments.js CHANGED
@@ -1,13 +1,14 @@
1
+ import { NAME_FRAGMENTS } from "./constants.js";
1
2
  import { isFragment, isFragments } from "./helpers/index.js";
2
- import { array } from "@oscarpalmer/mora";
3
3
  import { getString } from "@oscarpalmer/atoms/string";
4
+ import { array } from "@oscarpalmer/mora";
4
5
  var Fragments = class {
5
6
  #state;
6
7
  get items() {
7
8
  return this.#state.mapped;
8
9
  }
9
10
  constructor(items, identify, fragment) {
10
- Object.defineProperty(this, "$fragments", { value: true });
11
+ Object.defineProperty(this, NAME_FRAGMENTS, { value: true });
11
12
  this.#state = {
12
13
  fragment,
13
14
  identify,
@@ -1,14 +1,12 @@
1
1
  import { isFragment } from "./index.js";
2
- import { removeEvents } from "../node/event.js";
3
2
  import { getString } from "@oscarpalmer/atoms/string";
4
- import { isChildNode, isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
3
+ import { isChildNode } from "@oscarpalmer/toretto/is";
5
4
  function createNodes(value) {
6
5
  if (isFragment(value)) return value.get();
7
6
  if (isChildNode(value)) return [value];
8
7
  return [new Text(getString(value))];
9
8
  }
10
9
  function removeNodes(nodes) {
11
- sanitizeNodes(nodes);
12
10
  const { length } = nodes;
13
11
  for (let index = 0; index < length; index += 1) nodes[index].remove();
14
12
  }
@@ -17,12 +15,4 @@ function replaceNodes(from, to) {
17
15
  const { length } = from;
18
16
  for (let index = 1; index < length; index += 1) from[index].remove();
19
17
  }
20
- function sanitizeNodes(nodes) {
21
- const { length } = nodes;
22
- for (let index = 0; index < length; index += 1) {
23
- const node = nodes[index];
24
- if (isHTMLOrSVGElement(node)) removeEvents(node);
25
- if (node.hasChildNodes()) sanitizeNodes([...node.childNodes]);
26
- }
27
- }
28
18
  export { createNodes, removeNodes, replaceNodes };
@@ -1,14 +1,21 @@
1
+ import { NAME_FRAGMENT, NAME_FRAGMENTS } from "../constants.js";
1
2
  function compareArrays(first, second) {
2
3
  const firstIsLarger = first.length > second.length;
3
4
  const from = firstIsLarger ? first : second;
4
5
  const to = firstIsLarger ? second : first;
5
- if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return "dissimilar";
6
- return firstIsLarger ? "removed" : "added";
6
+ if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return COMPARISON_DISSIMILAR;
7
+ return firstIsLarger ? COMPARISON_REMOVED : COMPARISON_ADDED;
7
8
  }
8
9
  function isFragment(value) {
9
- return typeof value === "object" && value != null && "$fragment" in value && value.$fragment === true;
10
+ return isNamed(value, NAME_FRAGMENT);
10
11
  }
11
12
  function isFragments(value) {
12
- return typeof value === "object" && value != null && "$fragments" in value && value.$fragments === true;
13
+ return isNamed(value, NAME_FRAGMENTS);
13
14
  }
15
+ function isNamed(value, name) {
16
+ return typeof value === "object" && value != null && name in value && value[name] === true;
17
+ }
18
+ var COMPARISON_ADDED = "added";
19
+ var COMPARISON_DISSIMILAR = "dissimilar";
20
+ var COMPARISON_REMOVED = "removed";
14
21
  export { compareArrays, isFragment, isFragments };
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export * from "@oscarpalmer/mora";
5
5
  function fragments(array$1, identify, fragment) {
6
6
  return new Fragments(array$1, identify, fragment);
7
7
  }
8
- function html(strings, ...values) {
9
- return new Fragment(strings, values);
8
+ function html(template, ...values) {
9
+ return new Fragment(template, values);
10
10
  }
11
11
  export { fragments, html };
@@ -1,10 +1,9 @@
1
- import { EXPRESSION_COMMENT_FULL } from "../../constants.js";
1
+ import { EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD } from "../../constants.js";
2
2
  import { mapEvent } from "../event.js";
3
- import { setAttribute as setAttribute$1 } from "./value.js";
3
+ import { setAttribute } from "./value.js";
4
4
  import { computed, isReactive } from "@oscarpalmer/mora";
5
- import { isBooleanAttribute, setProperty } from "@oscarpalmer/toretto/attribute";
6
5
  function getValue(data, original) {
7
- const matches = EXPRESSION_COMMENT_FULL.exec(original ?? "");
6
+ const matches = EXPRESSION_ABYDON_CONTENT.exec(original ?? "");
8
7
  return matches == null ? original : data.values[+matches[1]];
9
8
  }
10
9
  function mapAttributes(data, element) {
@@ -12,16 +11,17 @@ function mapAttributes(data, element) {
12
11
  const { length } = attributes;
13
12
  for (let index = 0; index < length; index += 1) {
14
13
  const { name, value } = attributes[index];
15
- const actual = getValue(data, value);
14
+ const actualName = name.replace(EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, "");
15
+ const actualValue = getValue(data, value);
16
+ if (actualName !== name) element.removeAttribute(name);
16
17
  switch (true) {
17
- case name.startsWith("@"):
18
- mapEvent(element, name, actual);
18
+ case EXPRESSION_EVENT_PREFIX.test(actualName):
19
+ mapEvent(element, actualName, actualValue);
19
20
  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);
21
+ case EXPRESSION_PERIOD.test(actualName):
22
+ case typeof actualValue === "function":
23
+ case isReactive(actualValue):
24
+ mapValue(data, element, actualName, actualValue);
25
25
  break;
26
26
  default: break;
27
27
  }
@@ -29,11 +29,11 @@ function mapAttributes(data, element) {
29
29
  }
30
30
  function mapValue(data, element, name, value) {
31
31
  if (typeof value === "function") setComputedAttribute(data, element, name, value);
32
- else setAttribute$1(data, element, name, value);
32
+ else setAttribute(data, element, name, value);
33
33
  }
34
34
  function setComputedAttribute(data, element, name, callback) {
35
35
  const value = computed(callback);
36
36
  data.mora.values.add(value);
37
- setAttribute$1(data, element, name, value);
37
+ setAttribute(data, element, name, value);
38
38
  }
39
39
  export { mapAttributes };
@@ -1,9 +1,11 @@
1
1
  import { ATTRIBUTE_CLASS_PREFIX_LENGTH, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX } from "../../constants.js";
2
2
  import { isReactive } from "@oscarpalmer/mora";
3
- import { getString } from "@oscarpalmer/atoms/string";
4
- import { isBooleanAttribute, setAttribute as setAttribute$1 } from "@oscarpalmer/toretto/attribute";
3
+ import { isBooleanAttribute, setAttribute as setAttribute$1, setProperty } from "@oscarpalmer/toretto/attribute";
4
+ function getCallback(element, name) {
5
+ if (isBooleanAttribute(name) && name in element) return name === "checked" ? updateChecked : updateProperty;
6
+ return name === "value" ? updateValue : setAttribute$1;
7
+ }
5
8
  function setAttribute(data, element, name, value) {
6
- element.removeAttribute(name);
7
9
  switch (true) {
8
10
  case EXPRESSION_ATTRIBUTE_CLASS.test(name):
9
11
  setClasses(data, element, name, value);
@@ -30,27 +32,31 @@ function setStyle(data, element, name, value) {
30
32
  if (property == null) return;
31
33
  function update(value$1) {
32
34
  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));
35
+ else element.style.setProperty(property, value$1 === true ? unit : String(value$1));
34
36
  }
35
37
  if (isReactive(value)) data.mora.subscribers.add(value.subscribe(update));
36
38
  else update(value);
37
39
  }
38
40
  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;
41
+ const callback = getCallback(element, name);
42
42
  if (isReactive(value)) data.mora.subscribers.add(value.subscribe((next) => {
43
43
  callback(element, name, next);
44
44
  }));
45
45
  else callback(element, name, value);
46
46
  }
47
+ function updateChecked(element, name, value) {
48
+ updateElement("change", "checked", element, name, value, value === true);
49
+ }
50
+ function updateElement(event, property, element, name, value, next) {
51
+ if (!(property in element) || element[property] === next) return;
52
+ setAttribute$1(element, name, value);
53
+ element[property] = next;
54
+ element.dispatchEvent(new Event(event, { bubbles: true }));
55
+ }
47
56
  function updateProperty(element, name, value) {
48
- element[name] = value === true;
57
+ setProperty(element, name, value === true);
49
58
  }
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);
59
+ function updateValue(element, name, value) {
60
+ updateElement(element instanceof HTMLSelectElement ? "change" : "input", "value", element, name, value, String(value));
55
61
  }
56
62
  export { setAttribute };
@@ -1,30 +1,23 @@
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
- }
1
+ import { EVENT_DEFAULTS, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE } from "../constants.js";
2
+ import { on } from "@oscarpalmer/toretto/event";
10
3
  function getOptions(options) {
11
4
  const parts = options.split(":");
12
5
  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"))
6
+ capture: parts.some((part) => EXPRESSION_EVENT_OPTIONS_CAPTURE.test(part)),
7
+ once: parts.some((part) => EXPRESSION_EVENT_OPTIONS_ONCE.test(part)),
8
+ passive: !parts.some((part) => EXPRESSION_EVENT_OPTIONS_ACTIVE.test(part))
16
9
  };
17
10
  }
11
+ function getType(element, type) {
12
+ if (type !== "on") return type;
13
+ if (element instanceof HTMLInputElement) {
14
+ if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) return "change";
15
+ return element.type === "submit" ? "submit" : "input";
16
+ }
17
+ return EVENT_DEFAULTS[element.tagName] ?? type;
18
+ }
18
19
  function mapEvent(element, name, value) {
19
- element.removeAttribute(name);
20
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);
21
+ if (type != null && typeof value === "function") on(element, getType(element, type), value, getOptions(options ?? ""));
29
22
  }
30
- export { mapEvent, removeEvents };
23
+ export { mapEvent };
@@ -1,13 +1,13 @@
1
+ import { EXPRESSION_ABYDON_CONTENT } from "../constants.js";
1
2
  import { isFragment, isFragments } from "../helpers/index.js";
2
3
  import { handleFragments } from "../fragments.js";
3
- import { EXPRESSION_COMMENT_CONTENT } from "../constants.js";
4
4
  import { createNodes } from "../helpers/dom.js";
5
5
  import { mapAttributes } from "./attribute/index.js";
6
6
  import { setReactiveValue } from "./value.js";
7
7
  import { computed, isReactive } from "@oscarpalmer/mora";
8
8
  import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
9
9
  function mapNode(data, comment) {
10
- const matches = EXPRESSION_COMMENT_CONTENT.exec(comment.textContent ?? "");
10
+ const matches = EXPRESSION_ABYDON_CONTENT.exec(comment.textContent ?? "");
11
11
  const value = matches == null ? null : data.values[+matches[1]];
12
12
  if (value != null) mapValue(data, comment, value);
13
13
  }
@@ -5,7 +5,7 @@ import { getString } from "@oscarpalmer/atoms/string";
5
5
  import { isChildNode } from "@oscarpalmer/toretto/is";
6
6
  function addToArray(identifiers, items, nodes, added) {
7
7
  let position = nodes[0];
8
- const before = added && !identifiers.previous.includes(items.templates[0].identifier);
8
+ const before = added && !identifiers.previous.has(items.templates[0].identifier);
9
9
  const next = items.next.flatMap((fragment) => fragment.get().flatMap((node) => ({
10
10
  identifier: fragment.identifier,
11
11
  value: node
@@ -13,7 +13,7 @@ function addToArray(identifiers, items, nodes, added) {
13
13
  const { length } = next;
14
14
  for (let index = 0; index < length; index += 1) {
15
15
  const node = next[index];
16
- if (!(added && identifiers.previous.includes(node.identifier))) if (index === 0 && before) position.before(node.value);
16
+ if (!(added && identifiers.previous.has(node.identifier))) if (index === 0 && before) position.before(node.value);
17
17
  else position.after(node.value);
18
18
  position = node.value;
19
19
  }
@@ -25,7 +25,7 @@ function handleArray(identifiers, items, nodes) {
25
25
  ...items,
26
26
  next
27
27
  }, nodes, comparison === "added");
28
- const toRemove = items.fragments?.filter((fragment) => !identifiers.next.includes(fragment.identifier)) ?? [];
28
+ const toRemove = items.fragments?.filter((fragment) => !identifiers.next.has(fragment.identifier)) ?? [];
29
29
  const { length } = toRemove;
30
30
  for (let index = 0; index < length; index += 1) toRemove[index].remove();
31
31
  return {
@@ -57,8 +57,8 @@ function setArray(item, comment, value) {
57
57
  nodes: setNodes(item, comment, noTemplates ? value.flatMap((item$1) => createNodes(item$1)) : templates.flatMap((template) => template.get()))
58
58
  };
59
59
  return handleArray({
60
- next,
61
- previous
60
+ next: new Set(next),
61
+ previous: new Set(previous)
62
62
  }, {
63
63
  templates,
64
64
  fragments: item.fragments ?? []
package/dist/parse.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { EXPRESSION_ABYDON_ATTRIBUTE_FULL } from "./constants.js";
1
2
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
2
3
  function handleExpression(data, prefix, expression) {
3
4
  if (Array.isArray(expression)) {
@@ -6,7 +7,7 @@ function handleExpression(data, prefix, expression) {
6
7
  for (let index = 0; index < length; index += 1) expressions += handleExpression(data, "", expression[index]);
7
8
  return `${prefix}${expressions}`;
8
9
  }
9
- if (typeof expression === "function" || typeof expression === "object" && expression != null) return `${prefix}<!--abydon.${data.values.push(expression) - 1}-->`;
10
+ if (typeof expression === "function" || typeof expression === "object" && expression != null) return transformExpression(prefix, data.values.push(expression) - 1);
10
11
  return isNullableOrWhitespace(expression) ? prefix : `${prefix}${expression}`;
11
12
  }
12
13
  function parse(data) {
@@ -14,8 +15,15 @@ function parse(data) {
14
15
  const { length } = data.strings;
15
16
  data.template = "";
16
17
  for (let index = 0; index < length; index += 1) data.template += handleExpression(data, data.strings[index], data.expressions[index]);
18
+ data.template = data.template.replaceAll(EXPRESSION_ABYDON_ATTRIBUTE_FULL, transformAttribute);
17
19
  data.expressions = [];
18
20
  data.strings = [];
19
21
  return data.template;
20
22
  }
23
+ function transformAttribute(_, name, index) {
24
+ return `_${name}="@abydon.${index}@"`;
25
+ }
26
+ function transformExpression(prefix, index) {
27
+ return `${prefix}<!--abydon.${index}-->`;
28
+ }
21
29
  export { parse };
package/package.json CHANGED
@@ -4,23 +4,21 @@
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
6
  "dependencies": {
7
- "@oscarpalmer/atoms": "^0.114",
8
- "@oscarpalmer/mora": "^0.23",
9
- "@oscarpalmer/toretto": "^0.25"
7
+ "@oscarpalmer/atoms": "^0.118",
8
+ "@oscarpalmer/mora": "^0.25",
9
+ "@oscarpalmer/toretto": "^0.32"
10
10
  },
11
11
  "devDependencies": {
12
- "@biomejs/biome": "^2.3",
13
12
  "@oscarpalmer/oui": "^0.18",
14
- "@rollup/plugin-node-resolve": "^16",
15
- "@rollup/plugin-typescript": "^12.3",
16
- "@types/node": "^24.9",
13
+ "@types/node": "^25",
17
14
  "@vitest/coverage-istanbul": "^4",
18
- "glob": "^11",
19
- "jsdom": "^27.1",
20
- "rollup": "^4.52",
15
+ "jsdom": "^27.3",
16
+ "oxfmt": "^0.19",
17
+ "oxlint": "^1.34",
18
+ "rolldown": "1.0.0-beta.56",
21
19
  "tslib": "^2.8",
22
20
  "typescript": "^5.9",
23
- "vite": "npm:rolldown-vite@latest",
21
+ "vite": "8.0.0-beta.2",
24
22
  "vitest": "^4"
25
23
  },
26
24
  "exports": {
@@ -49,14 +47,14 @@
49
47
  "url": "git+https://github.com/oscarpalmer/abydon.git"
50
48
  },
51
49
  "scripts": {
52
- "build": "npm run clean && npx vite build && npm run rollup:build && npx tsc",
50
+ "build": "npm run clean && npx vite build && npm run rolldown:build && npx tsc",
53
51
  "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
54
- "rollup:build": "npx rollup -c",
55
- "rollup:watch": "npx rollup -c --watch",
52
+ "rolldown:build": "npx rolldown -c",
53
+ "rolldown:watch": "npx rolldown -c ./rolldown.config.js --watch",
56
54
  "test": "npx vitest --coverage",
57
55
  "watch": "npx vite build --watch"
58
56
  },
59
57
  "type": "module",
60
58
  "types": "types/index.d.ts",
61
- "version": "0.15.0"
59
+ "version": "0.16.0"
62
60
  }
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) {