eleva 1.2.2-alpha → 1.2.3-alpha

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eleva",
3
- "version": "1.2.2-alpha",
3
+ "version": "1.2.3-alpha",
4
4
  "description": "A minimalist and lightweight, pure vanilla JavaScript frontend runtime framework.",
5
5
  "type": "module",
6
6
  "main": "dist/eleva.js",
@@ -10,7 +10,8 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "require": "./dist/eleva.js",
13
- "import": "./dist/eleva.esm.js"
13
+ "import": "./dist/eleva.esm.js",
14
+ "types": "./dist/eleva.d.ts"
14
15
  }
15
16
  },
16
17
  "scripts": {
@@ -18,45 +19,129 @@
18
19
  "build": "rollup -c",
19
20
  "build:types": "tsc --emitDeclarationOnly",
20
21
  "build:types:bundle": "rollup -c rollup.dts.config.js",
21
- "build:all": "npm run build && npm run build:types && npm run build:types:bundle",
22
- "test": "jest",
22
+ "build:all": "npm run clean && npm run build && npm run build:types && npm run build:types:bundle",
23
+ "docs:dev": "vitepress dev docs",
24
+ "docs:build": "vitepress build docs",
25
+ "docs:preview": "vitepress preview docs",
26
+ "docs:api": "jsdoc -c jsdoc.json",
27
+ "docs:generate": "npm run docs:api && npm run docs:build",
28
+ "test": "npm run test:all",
29
+ "test:source": "jest -c jest.source.config.js",
30
+ "test:source:unit": "jest -c jest.source.config.js --testPathPattern=source/unit",
31
+ "test:source:performance": "jest -c jest.source.config.js --testPathPattern=source/performance",
32
+ "test:source:coverage": "jest -c jest.source.config.js --testPathPattern=source/unit --coverage",
33
+ "test:source:watch": "jest -c jest.source.config.js --watch",
34
+ "test:source:debug": "jest -c jest.source.config.js --runInBand --no-cache --watchAll",
35
+ "test:build": "jest -c jest.build.config.js",
36
+ "test:build:unit": "jest -c jest.build.config.js --testPathPattern=build/unit",
37
+ "test:build:performance": "jest -c jest.build.config.js --testPathPattern=build/performance",
38
+ "test:build:coverage": "jest -c jest.build.config.js --coverage",
39
+ "test:build:watch": "jest -c jest.build.config.js --watch",
40
+ "test:build:debug": "jest -c jest.build.config.js --runInBand --no-cache --watchAll",
41
+ "test:all": "npm run test:source && npm run test:build",
42
+ "codecov": "npx codecov",
23
43
  "lint": "eslint src/**/*.js",
44
+ "format": "npx prettier \"src/**/*\" \"types/**/*\" --write",
24
45
  "size": "size-limit",
25
- "prepublishOnly": "npm run build && npm run build:types && npm run build:types:bundle"
46
+ "clean": "rimraf dist types",
47
+ "prepublishOnly": "npm run format && npm run lint && npm run test:source:coverage && npm run build:all",
48
+ "prepare": "npm run build:all"
49
+ },
50
+ "engines": {
51
+ "node": ">=16.0.0"
26
52
  },
27
53
  "devDependencies": {
28
- "@babel/core": "^7.22.1",
29
- "@babel/preset-env": "^7.22.4",
54
+ "@babel/core": "^7.26.10",
55
+ "@babel/preset-env": "^7.26.9",
56
+ "@codecov/rollup-plugin": "^1.9.0",
30
57
  "@rollup/plugin-babel": "^6.0.3",
31
58
  "@rollup/plugin-commonjs": "^28.0.2",
32
59
  "@rollup/plugin-node-resolve": "^16.0.0",
33
60
  "@rollup/plugin-terser": "^0.4.4",
34
61
  "@size-limit/preset-app": "^11.2.0",
62
+ "babel-jest": "^29.7.0",
35
63
  "eslint": "^9.0.0",
64
+ "eslint-plugin-prettier": "^5.2.5",
65
+ "husky": "^9.1.7",
36
66
  "jest": "^29.6.1",
67
+ "jest-environment-jsdom": "^29.7.0",
68
+ "jest-html-reporters": "^3.1.7",
69
+ "jest-watch-typeahead": "^2.2.2",
70
+ "jsdoc": "^4.0.4",
71
+ "lint-staged": "^15.5.0",
72
+ "prettier": "^3.5.3",
73
+ "rimraf": "^6.0.1",
37
74
  "rollup": "^4.34.8",
38
75
  "rollup-plugin-dts": "^6.1.1",
39
76
  "size-limit": "^11.2.0",
40
- "typescript": "^5.1.6"
77
+ "typescript": "^5.1.6",
78
+ "vitepress": "^1.6.3"
79
+ },
80
+ "lint-staged": {
81
+ "src/**/*.{js,ts}": [
82
+ "eslint --fix",
83
+ "prettier --write"
84
+ ]
85
+ },
86
+ "husky": {
87
+ "hooks": {
88
+ "pre-commit": "lint-staged"
89
+ }
41
90
  },
42
91
  "size-limit": [
43
92
  {
44
93
  "limit": "5 kB",
45
94
  "path": "dist/eleva.min.js",
46
95
  "name": "eleva",
47
- "brotli": false
96
+ "brotli": true
48
97
  }
49
98
  ],
50
99
  "keywords": [
51
- "modern",
52
- "frontend",
53
- "framework",
54
- "lightweight",
55
- "fast",
56
- "ui",
57
- "components",
58
- "speed",
59
- "performance"
100
+ "modern javascript framework",
101
+ "lightweight JavaScript framework",
102
+ "lightweight JS framework",
103
+ "frontend performance optimization",
104
+ "frontend performance",
105
+ "performance optimization",
106
+ "tiny footprint",
107
+ "minimal framework",
108
+ "fast framework",
109
+ "highly performant",
110
+ "lightweight framework",
111
+ "frontend optimization",
112
+ "small bundle size",
113
+ "tree-shakable",
114
+ "ui components",
115
+ "vanilla JavaScript",
116
+ "vanilla-js",
117
+ "vanilla framework",
118
+ "vanilla JavaScript framework",
119
+ "signal-based reactivity",
120
+ "reactivity",
121
+ "frontend runtime",
122
+ "runtime framework",
123
+ "web components",
124
+ "javascript framework",
125
+ "frontend framework",
126
+ "direct DOM",
127
+ "high performance",
128
+ "open source",
129
+ "modular",
130
+ "developer friendly",
131
+ "efficient",
132
+ "scalable",
133
+ "pure JavaScript",
134
+ "minimalist framework",
135
+ "high-speed rendering",
136
+ "unopinionated",
137
+ "typescript",
138
+ "typescript support",
139
+ "single-page applications",
140
+ "UI library",
141
+ "web development",
142
+ "web development framework",
143
+ "web development tools",
144
+ "micro frontends"
60
145
  ],
61
146
  "author": {
62
147
  "name": "Tarek Raafat",
@@ -64,7 +149,7 @@
64
149
  "url": "https://www.tarekraafat.com"
65
150
  },
66
151
  "license": "MIT",
67
- "homepage": "https://tarekraafat.github.io/eleva",
152
+ "homepage": "https://elevajs.com",
68
153
  "repository": {
69
154
  "type": "git",
70
155
  "url": "git+https://github.com/TarekRaafat/eleva.git"
@@ -76,12 +161,32 @@
76
161
  "directories": {
77
162
  "doc": "docs",
78
163
  "example": "examples",
79
- "test": "test"
164
+ "test": "test",
165
+ "dist": "dist",
166
+ "src": "src",
167
+ "types": "types"
80
168
  },
81
169
  "files": [
82
170
  "dist",
83
171
  "types",
84
172
  "src"
85
173
  ],
86
- "sideEffects": false
174
+ "sideEffects": false,
175
+ "funding": [
176
+ {
177
+ "type": "github",
178
+ "url": "https://github.com/sponsors/TarekRaafat"
179
+ },
180
+ {
181
+ "type": "buymeacoffee",
182
+ "url": "https://www.buymeacoffee.com/tarekraafat"
183
+ }
184
+ ],
185
+ "browserslist": [
186
+ "> 0.25%",
187
+ "last 2 versions",
188
+ "not dead",
189
+ "not op_mini all",
190
+ "not ie 11"
191
+ ]
87
192
  }
package/src/core/Eleva.js CHANGED
@@ -151,6 +151,7 @@ export class Eleva {
151
151
  const mergedContext = { ...context, ...data };
152
152
  const watcherUnsubscribers = [];
153
153
  const childInstances = [];
154
+ const cleanupListeners = [];
154
155
 
155
156
  if (!this._isMounted) {
156
157
  mergedContext.onBeforeMount && mergedContext.onBeforeMount();
@@ -168,7 +169,7 @@ export class Eleva {
168
169
  mergedContext
169
170
  );
170
171
  this.renderer.patchDOM(container, newHtml);
171
- this._processEvents(container, mergedContext);
172
+ this._processEvents(container, mergedContext, cleanupListeners);
172
173
  this._injectStyles(container, compName, style, mergedContext);
173
174
  this._mountChildren(container, children, childInstances);
174
175
  if (!this._isMounted) {
@@ -194,12 +195,13 @@ export class Eleva {
194
195
  container,
195
196
  data: mergedContext,
196
197
  /**
197
- * Unmounts the component, cleaning up watchers, child components, and clearing the container.
198
+ * Unmounts the component, cleaning up watchers and listeners, child components, and clearing the container.
198
199
  *
199
200
  * @returns {void}
200
201
  */
201
202
  unmount: () => {
202
203
  watcherUnsubscribers.forEach((fn) => fn());
204
+ cleanupListeners.forEach((fn) => fn());
203
205
  childInstances.forEach((child) => child.unmount());
204
206
  mergedContext.onUnmount && mergedContext.onUnmount();
205
207
  container.innerHTML = "";
@@ -228,12 +230,14 @@ export class Eleva {
228
230
 
229
231
  /**
230
232
  * Processes DOM elements for event binding based on attributes starting with "@".
233
+ * Tracks listeners for cleanup during unmount.
231
234
  *
232
235
  * @param {HTMLElement} container - The container element in which to search for events.
233
236
  * @param {Object<string, any>} context - The current context containing event handler definitions.
237
+ * @param {Array<Function>} cleanupListeners - Array to collect cleanup functions for each event listener.
234
238
  * @private
235
239
  */
236
- _processEvents(container, context) {
240
+ _processEvents(container, context, cleanupListeners) {
237
241
  container.querySelectorAll("*").forEach((el) => {
238
242
  [...el.attributes].forEach(({ name, value }) => {
239
243
  if (name.startsWith("@")) {
@@ -242,6 +246,7 @@ export class Eleva {
242
246
  if (typeof handler === "function") {
243
247
  el.addEventListener(event, handler);
244
248
  el.removeAttribute(name);
249
+ cleanupListeners.push(() => el.removeEventListener(event, handler));
245
250
  }
246
251
  }
247
252
  });
@@ -288,7 +293,7 @@ export class Eleva {
288
293
  const props = {};
289
294
  [...childEl.attributes].forEach(({ name, value }) => {
290
295
  if (name.startsWith("eleva-prop-")) {
291
- props[name.slice("eleva-prop-".length)] = value;
296
+ props[name.replace("eleva-prop-", "")] = value;
292
297
  }
293
298
  });
294
299
  const instance = this.mount(childEl, children[childSelector], props);
@@ -12,11 +12,25 @@ export class Renderer {
12
12
  *
13
13
  * @param {HTMLElement} container - The container element to patch.
14
14
  * @param {string} newHtml - The new HTML content to apply.
15
+ * @throws {Error} If container is not an HTMLElement or newHtml is not a string
15
16
  */
16
17
  patchDOM(container, newHtml) {
18
+ if (!(container instanceof HTMLElement)) {
19
+ throw new Error("Container must be an HTMLElement");
20
+ }
21
+ if (typeof newHtml !== "string") {
22
+ throw new Error("newHtml must be a string");
23
+ }
24
+
17
25
  const tempContainer = document.createElement("div");
18
- tempContainer.innerHTML = newHtml;
19
- this.diff(container, tempContainer);
26
+ try {
27
+ tempContainer.innerHTML = newHtml;
28
+ this.diff(container, tempContainer);
29
+ } catch (error) {
30
+ throw new Error(`Failed to patch DOM: ${error.message}`);
31
+ } finally {
32
+ tempContainer.innerHTML = "";
33
+ }
20
34
  }
21
35
 
22
36
  /**
@@ -24,36 +38,53 @@ export class Renderer {
24
38
  *
25
39
  * @param {HTMLElement} oldParent - The original DOM element.
26
40
  * @param {HTMLElement} newParent - The new DOM element.
41
+ * @throws {Error} If either parent is not an HTMLElement
27
42
  */
28
43
  diff(oldParent, newParent) {
44
+ if (
45
+ !(oldParent instanceof HTMLElement) ||
46
+ !(newParent instanceof HTMLElement)
47
+ ) {
48
+ throw new Error("Both parents must be HTMLElements");
49
+ }
50
+
51
+ // Fast path for identical nodes
52
+ if (oldParent.isEqualNode(newParent)) return;
53
+
29
54
  const oldNodes = Array.from(oldParent.childNodes);
30
55
  const newNodes = Array.from(newParent.childNodes);
31
56
  const max = Math.max(oldNodes.length, newNodes.length);
57
+
58
+ // Batch DOM operations for better performance
59
+ const operations = [];
60
+
32
61
  for (let i = 0; i < max; i++) {
33
62
  const oldNode = oldNodes[i];
34
63
  const newNode = newNodes[i];
35
64
 
36
65
  // Case 1: Append new nodes that don't exist in the old tree.
37
66
  if (!oldNode && newNode) {
38
- oldParent.appendChild(newNode.cloneNode(true));
67
+ operations.push(() => oldParent.appendChild(newNode.cloneNode(true)));
39
68
  continue;
40
69
  }
41
70
  // Case 2: Remove old nodes not present in the new tree.
42
71
  if (oldNode && !newNode) {
43
- oldParent.removeChild(oldNode);
72
+ operations.push(() => oldParent.removeChild(oldNode));
44
73
  continue;
45
74
  }
46
75
 
47
76
  // Case 3: For element nodes, compare keys if available.
48
77
  if (
49
- oldNode.nodeType === Node.ELEMENT_NODE &&
50
- newNode.nodeType === Node.ELEMENT_NODE
78
+ oldNode?.nodeType === Node.ELEMENT_NODE &&
79
+ newNode?.nodeType === Node.ELEMENT_NODE
51
80
  ) {
52
81
  const oldKey = oldNode.getAttribute("key");
53
82
  const newKey = newNode.getAttribute("key");
54
83
  if (oldKey || newKey) {
55
84
  if (oldKey !== newKey) {
56
- oldParent.replaceChild(newNode.cloneNode(true), oldNode);
85
+ operations.push(() =>
86
+ oldParent.replaceChild(newNode.cloneNode(true), oldNode)
87
+ );
57
88
  continue;
58
89
  }
59
90
  }
@@ -61,25 +92,32 @@ export class Renderer {
61
92
 
62
93
  // Case 4: Replace nodes if types or tag names differ.
63
94
  if (
64
- oldNode.nodeType !== newNode.nodeType ||
65
- oldNode.nodeName !== newNode.nodeName
95
+ oldNode?.nodeType !== newNode?.nodeType ||
96
+ oldNode?.nodeName !== newNode?.nodeName
66
97
  ) {
67
- oldParent.replaceChild(newNode.cloneNode(true), oldNode);
98
+ operations.push(() =>
99
+ oldParent.replaceChild(newNode.cloneNode(true), oldNode)
100
+ );
68
101
  continue;
69
102
  }
103
+
70
104
  // Case 5: For text nodes, update content if different.
71
- if (oldNode.nodeType === Node.TEXT_NODE) {
105
+ if (oldNode?.nodeType === Node.TEXT_NODE) {
72
106
  if (oldNode.nodeValue !== newNode.nodeValue) {
73
107
  oldNode.nodeValue = newNode.nodeValue;
74
108
  }
75
109
  continue;
76
110
  }
111
+
77
112
  // Case 6: For element nodes, update attributes and then diff children.
78
- if (oldNode.nodeType === Node.ELEMENT_NODE) {
113
+ if (oldNode?.nodeType === Node.ELEMENT_NODE) {
79
114
  this.updateAttributes(oldNode, newNode);
80
115
  this.diff(oldNode, newNode);
81
116
  }
82
117
  }
118
+
119
+ // Execute batched operations
120
+ operations.forEach((op) => op());
83
121
  }
84
122
 
85
123
  /**
@@ -87,33 +125,74 @@ export class Renderer {
87
125
  *
88
126
  * @param {HTMLElement} oldEl - The element to update.
89
127
  * @param {HTMLElement} newEl - The element providing the updated attributes.
128
+ * @throws {Error} If either element is not an HTMLElement
90
129
  */
91
130
  updateAttributes(oldEl, newEl) {
92
- const attributeToPropertyMap = {
93
- value: "value",
94
- checked: "checked",
95
- selected: "selected",
96
- disabled: "disabled",
131
+ if (!(oldEl instanceof HTMLElement) || !(newEl instanceof HTMLElement)) {
132
+ throw new Error("Both elements must be HTMLElements");
133
+ }
134
+
135
+ // Special cases for properties that don't map directly to attributes
136
+ const specialProperties = {
137
+ value: true,
138
+ checked: true,
139
+ selected: true,
140
+ disabled: true,
141
+ readOnly: true,
142
+ multiple: true,
97
143
  };
98
144
 
99
- // Remove old attributes that no longer exist.
145
+ // Batch attribute operations for better performance
146
+ const operations = [];
147
+
148
+ // Remove old attributes that no longer exist
100
149
  Array.from(oldEl.attributes).forEach((attr) => {
101
150
  if (attr.name.startsWith("@")) return;
102
151
  if (!newEl.hasAttribute(attr.name)) {
103
- oldEl.removeAttribute(attr.name);
152
+ operations.push(() => oldEl.removeAttribute(attr.name));
104
153
  }
105
154
  });
106
- // Add or update attributes from newEl.
155
+
156
+ // Add or update attributes from newEl
107
157
  Array.from(newEl.attributes).forEach((attr) => {
108
158
  if (attr.name.startsWith("@")) return;
109
159
  if (oldEl.getAttribute(attr.name) !== attr.value) {
110
- oldEl.setAttribute(attr.name, attr.value);
111
- if (attributeToPropertyMap[attr.name]) {
112
- oldEl[attributeToPropertyMap[attr.name]] = attr.value;
113
- } else if (attr.name in oldEl) {
114
- oldEl[attr.name] = attr.value;
115
- }
160
+ operations.push(() => {
161
+ oldEl.setAttribute(attr.name, attr.value);
162
+
163
+ // Convert kebab-case to camelCase for property names
164
+ const propName = attr.name.replace(/-([a-z])/g, (_, letter) =>
165
+ letter.toUpperCase()
166
+ );
167
+
168
+ // Handle special cases first
169
+ if (specialProperties[propName]) {
170
+ oldEl[propName] = attr.value === "" ? true : attr.value;
171
+ }
172
+ // Handle ARIA attributes
173
+ else if (attr.name.startsWith("aria-")) {
174
+ const ariaName =
175
+ "aria" +
176
+ attr.name
177
+ .slice(5)
178
+ .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
179
+ oldEl[ariaName] = attr.value;
180
+ }
181
+ // Handle data attributes
182
+ else if (attr.name.startsWith("data-")) {
183
+ // dataset handles the camelCase conversion automatically
184
+ const dataName = attr.name.slice(5);
185
+ oldEl.dataset[dataName] = attr.value;
186
+ }
187
+ // Handle standard properties
188
+ else if (propName in oldEl) {
189
+ oldEl[propName] = attr.value;
190
+ }
191
+ });
116
192
  }
117
193
  });
194
+
195
+ // Execute batched operations
196
+ operations.forEach((op) => op());
118
197
  }
119
198
  }
@@ -17,6 +17,8 @@ export class Signal {
17
17
  this._value = value;
18
18
  /** @private {Set<function>} Collection of callback functions to be notified when value changes */
19
19
  this._watchers = new Set();
20
+ /** @private {boolean} Flag to prevent multiple synchronous watcher notifications and batch updates into microtasks */
21
+ this._pending = false;
20
22
  }
21
23
 
22
24
  /**
@@ -36,7 +38,7 @@ export class Signal {
36
38
  set value(newVal) {
37
39
  if (newVal !== this._value) {
38
40
  this._value = newVal;
39
- this._watchers.forEach((fn) => fn(newVal));
41
+ this._notifyWatchers();
40
42
  }
41
43
  }
42
44
 
@@ -50,4 +52,22 @@ export class Signal {
50
52
  this._watchers.add(fn);
51
53
  return () => this._watchers.delete(fn);
52
54
  }
55
+
56
+ /**
57
+ * Notifies all registered watchers of a value change using microtask scheduling.
58
+ * Uses a pending flag to batch multiple synchronous updates into a single notification.
59
+ * All watcher callbacks receive the current value when executed.
60
+ *
61
+ * @private
62
+ * @returns {void}
63
+ */
64
+ _notifyWatchers() {
65
+ if (!this._pending) {
66
+ this._pending = true;
67
+ queueMicrotask(() => {
68
+ this._pending = false;
69
+ this._watchers.forEach((fn) => fn(this._value));
70
+ });
71
+ }
72
+ }
53
73
  }
@@ -15,9 +15,10 @@ export class TemplateEngine {
15
15
  * @returns {string} The resulting string with evaluated values.
16
16
  */
17
17
  static parse(template, data) {
18
+ if (!template || typeof template !== "string") return template;
19
+
18
20
  return template.replace(/\{\{\s*(.*?)\s*\}\}/g, (_, expr) => {
19
- const value = this.evaluate(expr, data);
20
- return value === undefined ? "" : value;
21
+ return this.evaluate(expr, data);
21
22
  });
22
23
  }
23
24
 
@@ -29,11 +30,11 @@ export class TemplateEngine {
29
30
  * @returns {any} The result of the evaluated expression, or an empty string if undefined or on error.
30
31
  */
31
32
  static evaluate(expr, data) {
33
+ if (!expr || typeof expr !== "string") return expr;
34
+
32
35
  try {
33
- const keys = Object.keys(data);
34
- const values = Object.values(data);
35
- const result = new Function(...keys, `return ${expr}`)(...values);
36
- return result === undefined ? "" : result;
36
+ const compiledFn = new Function("data", `with(data) { return ${expr} }`);
37
+ return compiledFn(data);
37
38
  } catch (error) {
38
39
  console.error(`Template evaluation error:`, {
39
40
  expression: expr,
@@ -95,9 +95,11 @@ export class Eleva {
95
95
  private _prepareLifecycleHooks;
96
96
  /**
97
97
  * Processes DOM elements for event binding based on attributes starting with "@".
98
+ * Tracks listeners for cleanup during unmount.
98
99
  *
99
100
  * @param {HTMLElement} container - The container element in which to search for events.
100
101
  * @param {Object<string, any>} context - The current context containing event handler definitions.
102
+ * @param {Array<Function>} cleanupListeners - Array to collect cleanup functions for each event listener.
101
103
  * @private
102
104
  */
103
105
  private _processEvents;
@@ -1 +1 @@
1
- {"version":3,"file":"Eleva.d.ts","sourceRoot":"","sources":["../../src/core/Eleva.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH;IACE;;;;;OAKG;IACH,kBAHW,MAAM;;OA0BhB;IAtBC,wEAAwE;IACxE,MADW,MAAM,CACD;IAChB,uFAAuF;IACvF;;MAAoB;IACpB,0GAA0G;IAC1G;;MAAqB;IACrB,wEAAwE;IACxE,iBAAkB;IAClB,mFAAmF;IACnF,wBAMC;IACD,2EAA2E;IAC3E,mBAAuB;IACvB,qFAAqF;IACrF,gBAA4B;IAC5B,yFAAyF;IACzF,iBAA8B;IAGhC;;;;;;OAMG;IACH,YAJW,MAAM;;QAEJ,KAAK,CAQjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,cACN,mBAAmB,GACjB,KAAK,CAKjB;IAED;;;;;;;;OAQG;IACH,iBANW,WAAW,YACX,MAAM,GAAC,mBAAmB;;QAExB,MAAM,GAAC,OAAO,CAAC,MAAM,CAAC,CAgHlC;IAED;;;;;OAKG;IACH,+BAKC;IAED;;;;;;OAMG;IACH,uBAaC;IAED;;;;;;;;OAQG;IACH,sBAYC;IAED;;;;;;;OAOG;IACH,uBAgBC;CACF;;;;;;;;;;;;UAhS4C,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,GAAC,OAAO,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,CAAC,CAAC;;;;;;cAKjF,CAAS,IAAmB,EAAnB;YAAO,MAAM,GAAE,GAAG;KAAC,KAAG,MAAM;;;;;;;;UAKN,MAAM"}
1
+ {"version":3,"file":"Eleva.d.ts","sourceRoot":"","sources":["../../src/core/Eleva.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH;IACE;;;;;OAKG;IACH,kBAHW,MAAM;;OA0BhB;IAtBC,wEAAwE;IACxE,MADW,MAAM,CACD;IAChB,uFAAuF;IACvF;;MAAoB;IACpB,0GAA0G;IAC1G;;MAAqB;IACrB,wEAAwE;IACxE,iBAAkB;IAClB,mFAAmF;IACnF,wBAMC;IACD,2EAA2E;IAC3E,mBAAuB;IACvB,qFAAqF;IACrF,gBAA4B;IAC5B,yFAAyF;IACzF,iBAA8B;IAGhC;;;;;;OAMG;IACH,YAJW,MAAM;;QAEJ,KAAK,CAQjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,cACN,mBAAmB,GACjB,KAAK,CAKjB;IAED;;;;;;;;OAQG;IACH,iBANW,WAAW,YACX,MAAM,GAAC,mBAAmB;;QAExB,MAAM,GAAC,OAAO,CAAC,MAAM,CAAC,CAkHlC;IAED;;;;;OAKG;IACH,+BAKC;IAED;;;;;;;;OAQG;IACH,uBAcC;IAED;;;;;;;;OAQG;IACH,sBAYC;IAED;;;;;;;OAOG;IACH,uBAgBC;CACF;;;;;;;;;;;;UArS4C,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,GAAC,OAAO,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,CAAC,CAAC;;;;;;cAKjF,CAAS,IAAmB,EAAnB;YAAO,MAAM,GAAE,GAAG;KAAC,KAAG,MAAM;;;;;;;;UAKN,MAAM"}
@@ -10,6 +10,7 @@ export class Renderer {
10
10
  *
11
11
  * @param {HTMLElement} container - The container element to patch.
12
12
  * @param {string} newHtml - The new HTML content to apply.
13
+ * @throws {Error} If container is not an HTMLElement or newHtml is not a string
13
14
  */
14
15
  patchDOM(container: HTMLElement, newHtml: string): void;
15
16
  /**
@@ -17,6 +18,7 @@ export class Renderer {
17
18
  *
18
19
  * @param {HTMLElement} oldParent - The original DOM element.
19
20
  * @param {HTMLElement} newParent - The new DOM element.
21
+ * @throws {Error} If either parent is not an HTMLElement
20
22
  */
21
23
  diff(oldParent: HTMLElement, newParent: HTMLElement): void;
22
24
  /**
@@ -24,6 +26,7 @@ export class Renderer {
24
26
  *
25
27
  * @param {HTMLElement} oldEl - The element to update.
26
28
  * @param {HTMLElement} newEl - The element providing the updated attributes.
29
+ * @throws {Error} If either element is not an HTMLElement
27
30
  */
28
31
  updateAttributes(oldEl: HTMLElement, newEl: HTMLElement): void;
29
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../src/modules/Renderer.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;OAKG;IACH,oBAHW,WAAW,WACX,MAAM,QAMhB;IAED;;;;;OAKG;IACH,gBAHW,WAAW,aACX,WAAW,QAyDrB;IAED;;;;;OAKG;IACH,wBAHW,WAAW,SACX,WAAW,QA6BrB;CACF"}
1
+ {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../src/modules/Renderer.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH,oBAJW,WAAW,WACX,MAAM,QAoBhB;IAED;;;;;;OAMG;IACH,gBAJW,WAAW,aACX,WAAW,QAiFrB;IAED;;;;;;OAMG;IACH,wBAJW,WAAW,SACX,WAAW,QAsErB;CACF"}
@@ -15,6 +15,8 @@ export class Signal {
15
15
  private _value;
16
16
  /** @private {Set<function>} Collection of callback functions to be notified when value changes */
17
17
  private _watchers;
18
+ /** @private {boolean} Flag to prevent multiple synchronous watcher notifications and batch updates into microtasks */
19
+ private _pending;
18
20
  /**
19
21
  * Sets a new value for the signal and notifies all registered watchers if the value has changed.
20
22
  *
@@ -34,5 +36,14 @@ export class Signal {
34
36
  * @returns {function(): boolean} A function to unsubscribe the watcher.
35
37
  */
36
38
  watch(fn: (arg0: any) => void): () => boolean;
39
+ /**
40
+ * Notifies all registered watchers of a value change using microtask scheduling.
41
+ * Uses a pending flag to batch multiple synchronous updates into a single notification.
42
+ * All watcher callbacks receive the current value when executed.
43
+ *
44
+ * @private
45
+ * @returns {void}
46
+ */
47
+ private _notifyWatchers;
37
48
  }
38
49
  //# sourceMappingURL=Signal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../../src/modules/Signal.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;OAIG;IACH,mBAFW,GAAC,EAOX;IAJC,mEAAmE;IACnE,eAAmB;IACnB,kGAAkG;IAClG,kBAA0B;IAY5B;;;;OAIG;IACH,kBAFW,GAAC,EAOX;IAnBD;;;;OAIG;IACH,aAFa,GAAC,CAIb;IAcD;;;;;OAKG;IACH,UAHW,CAAS,IAAG,EAAH,GAAG,KAAG,IAAI,GACjB,MAAY,OAAO,CAK/B;CACF"}
1
+ {"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../../src/modules/Signal.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;OAIG;IACH,mBAFW,GAAC,EASX;IANC,mEAAmE;IACnE,eAAmB;IACnB,kGAAkG;IAClG,kBAA0B;IAC1B,sHAAsH;IACtH,iBAAqB;IAYvB;;;;OAIG;IACH,kBAFW,GAAC,EAOX;IAnBD;;;;OAIG;IACH,aAFa,GAAC,CAIb;IAcD;;;;;OAKG;IACH,UAHW,CAAS,IAAG,EAAH,GAAG,KAAG,IAAI,GACjB,MAAY,OAAO,CAK/B;IAED;;;;;;;OAOG;IACH,wBAQC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateEngine.d.ts","sourceRoot":"","sources":["../../src/modules/TemplateEngine.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH,uBAJW,MAAM;;QAEJ,MAAM,CAOlB;IAED;;;;;;OAMG;IACH,sBAJW,MAAM;;QAEJ,GAAG,CAgBf;CACF"}
1
+ {"version":3,"file":"TemplateEngine.d.ts","sourceRoot":"","sources":["../../src/modules/TemplateEngine.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH,uBAJW,MAAM;;QAEJ,MAAM,CAQlB;IAED;;;;;;OAMG;IACH,sBAJW,MAAM;;QAEJ,GAAG,CAgBf;CACF"}