eleva 1.2.13-alpha → 1.2.15-beta

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,29 +1,104 @@
1
1
  {
2
2
  "name": "eleva",
3
- "version": "1.2.13-alpha",
3
+ "version": "1.2.15-beta",
4
4
  "description": "A minimalist and lightweight, pure vanilla JavaScript frontend runtime framework.",
5
5
  "type": "module",
6
- "main": "dist/eleva.cjs.js",
7
- "module": "dist/eleva.esm.js",
8
- "umd": "dist/eleva.umd.js",
9
- "browser": "dist/eleva.min.js",
10
- "unpkg": "dist/eleva.min.js",
11
- "types": "dist/eleva.d.ts",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "author": {
9
+ "name": "Tarek Raafat",
10
+ "email": "tarek.m.raafat@gmail.com",
11
+ "url": "https://www.tarekraafat.com"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/TarekRaafat/eleva.git"
16
+ },
17
+ "homepage": "https://elevajs.com",
18
+ "bugs": {
19
+ "url": "https://github.com/TarekRaafat/eleva/issues",
20
+ "email": "tarek.m.raafat@gmail.com"
21
+ },
22
+ "readme": "https://github.com/TarekRaafat/eleva#readme",
23
+ "main": "./dist/eleva.cjs.js",
24
+ "module": "./dist/eleva.esm.js",
25
+ "browser": "./dist/eleva.umd.min.js",
26
+ "types": "./dist/eleva.d.ts",
27
+ "unpkg": "./dist/eleva.umd.min.js",
28
+ "jsdelivr": "./dist/eleva.umd.min.js",
12
29
  "exports": {
13
30
  ".": {
14
31
  "types": "./dist/eleva.d.ts",
15
- "node": "./dist/eleva.cjs.js",
32
+ "import": "./dist/eleva.esm.js",
16
33
  "require": "./dist/eleva.cjs.js",
34
+ "node": "./dist/eleva.cjs.js",
35
+ "browser": "./dist/eleva.umd.min.js",
36
+ "default": "./dist/eleva.umd.min.js"
37
+ },
38
+ "./esm": {
39
+ "types": "./dist/eleva.d.ts",
17
40
  "import": "./dist/eleva.esm.js",
18
- "browser": "./dist/eleva.min.js",
19
- "umd": "./dist/eleva.umd.js",
20
- "default": "./dist/eleva.esm.js"
21
- }
41
+ "require": "./dist/eleva.esm.js",
42
+ "browser": "./dist/eleva.umd.min.js"
43
+ },
44
+ "./cjs": {
45
+ "types": "./dist/eleva.d.ts",
46
+ "require": "./dist/eleva.cjs.js"
47
+ },
48
+ "./umd": {
49
+ "types": "./dist/eleva.d.ts",
50
+ "import": "./dist/eleva.umd.js",
51
+ "browser": "./dist/eleva.umd.js"
52
+ },
53
+ "./browser": {
54
+ "types": "./dist/eleva.d.ts",
55
+ "import": "./dist/eleva.umd.min.js",
56
+ "browser": "./dist/eleva.umd.min.js"
57
+ },
58
+ "./package.json": "./package.json"
59
+ },
60
+ "files": [
61
+ "dist",
62
+ "types",
63
+ "src"
64
+ ],
65
+ "directories": {
66
+ "doc": "docs",
67
+ "example": "examples",
68
+ "test": "test",
69
+ "dist": "dist",
70
+ "src": "src",
71
+ "types": "types"
72
+ },
73
+ "sideEffects": false,
74
+ "devDependencies": {
75
+ "@babel/core": "^7.26.10",
76
+ "@babel/preset-env": "^7.26.9",
77
+ "@codecov/rollup-plugin": "^1.9.0",
78
+ "@rollup/plugin-babel": "^6.0.3",
79
+ "@rollup/plugin-node-resolve": "^16.0.0",
80
+ "@rollup/plugin-terser": "^0.4.4",
81
+ "babel-jest": "^29.7.0",
82
+ "eslint": "^9.0.0",
83
+ "eslint-plugin-prettier": "^5.2.5",
84
+ "husky": "^9.1.7",
85
+ "jest": "^29.6.1",
86
+ "jest-environment-jsdom": "^29.7.0",
87
+ "jest-html-reporters": "^3.1.7",
88
+ "jest-watch-typeahead": "^2.2.2",
89
+ "jsdoc": "^4.0.4",
90
+ "lint-staged": "^15.5.0",
91
+ "prettier": "^3.5.3",
92
+ "rimraf": "^6.0.1",
93
+ "rollup": "^4.34.8",
94
+ "rollup-plugin-dts": "^6.1.1",
95
+ "typescript": "^5.1.6",
96
+ "vitepress": "^1.6.3"
22
97
  },
23
98
  "scripts": {
24
99
  "dev": "rollup -c -w",
25
100
  "build": "rollup -c",
26
- "build:types": "tsc --emitDeclarationOnly",
101
+ "build:types": "tsc",
27
102
  "build:types:bundle": "rollup -c rollup.dts.config.js",
28
103
  "build:all": "npm run clean && npm run build && npm run build:types && npm run build:types:bundle",
29
104
  "docs:dev": "vitepress dev docs",
@@ -48,7 +123,6 @@
48
123
  "codecov": "npx codecov",
49
124
  "lint": "eslint src/**/*.js",
50
125
  "format": "npx prettier \"src/**/*\" \"types/**/*\" --write",
51
- "size": "size-limit",
52
126
  "clean": "rimraf dist types",
53
127
  "prepublishOnly": "npm run format && npm run lint && npm run test:source:coverage && npm run build:all",
54
128
  "prepare": "npm run build:all"
@@ -56,31 +130,16 @@
56
130
  "engines": {
57
131
  "node": ">=16.0.0"
58
132
  },
59
- "devDependencies": {
60
- "@babel/core": "^7.26.10",
61
- "@babel/preset-env": "^7.26.9",
62
- "@codecov/rollup-plugin": "^1.9.0",
63
- "@rollup/plugin-babel": "^6.0.3",
64
- "@rollup/plugin-commonjs": "^28.0.2",
65
- "@rollup/plugin-node-resolve": "^16.0.0",
66
- "@rollup/plugin-terser": "^0.4.4",
67
- "@size-limit/preset-app": "^11.2.0",
68
- "babel-jest": "^29.7.0",
69
- "eslint": "^9.0.0",
70
- "eslint-plugin-prettier": "^5.2.5",
71
- "husky": "^9.1.7",
72
- "jest": "^29.6.1",
73
- "jest-environment-jsdom": "^29.7.0",
74
- "jest-html-reporters": "^3.1.7",
75
- "jest-watch-typeahead": "^2.2.2",
76
- "jsdoc": "^4.0.4",
77
- "lint-staged": "^15.5.0",
78
- "prettier": "^3.5.3",
79
- "rimraf": "^6.0.1",
80
- "rollup": "^4.34.8",
81
- "rollup-plugin-dts": "^6.1.1",
82
- "typescript": "^5.1.6",
83
- "vitepress": "^1.6.3"
133
+ "browserslist": [
134
+ "> 0.25%",
135
+ "last 2 versions",
136
+ "not dead",
137
+ "not op_mini all",
138
+ "not ie 11"
139
+ ],
140
+ "publishConfig": {
141
+ "access": "public",
142
+ "registry": "https://registry.npmjs.org/"
84
143
  },
85
144
  "lint-staged": {
86
145
  "src/**/*.{js,ts}": [
@@ -140,35 +199,6 @@
140
199
  "web development tools",
141
200
  "micro frontends"
142
201
  ],
143
- "author": {
144
- "name": "Tarek Raafat",
145
- "email": "tarek.m.raafat@gmail.com",
146
- "url": "https://www.tarekraafat.com"
147
- },
148
- "license": "MIT",
149
- "homepage": "https://elevajs.com",
150
- "repository": {
151
- "type": "git",
152
- "url": "git+https://github.com/TarekRaafat/eleva.git"
153
- },
154
- "bugs": {
155
- "url": "https://github.com/TarekRaafat/eleva/issues",
156
- "email": "tarek.m.raafat@gmail.com"
157
- },
158
- "directories": {
159
- "doc": "docs",
160
- "example": "examples",
161
- "test": "test",
162
- "dist": "dist",
163
- "src": "src",
164
- "types": "types"
165
- },
166
- "files": [
167
- "dist",
168
- "types",
169
- "src"
170
- ],
171
- "sideEffects": false,
172
202
  "funding": [
173
203
  {
174
204
  "type": "github",
@@ -178,12 +208,5 @@
178
208
  "type": "buymeacoffee",
179
209
  "url": "https://www.buymeacoffee.com/tarekraafat"
180
210
  }
181
- ],
182
- "browserslist": [
183
- "> 0.25%",
184
- "last 2 versions",
185
- "not dead",
186
- "not op_mini all",
187
- "not ie 11"
188
211
  ]
189
212
  }
package/src/core/Eleva.js CHANGED
@@ -177,7 +177,7 @@ export class Eleva {
177
177
  const context = {
178
178
  props,
179
179
  emitter: this.emitter,
180
- /** @type {(v: any) => Signal} */
180
+ /** @type {(v: any) => Signal<any>} */
181
181
  signal: (v) => new this.signal(v),
182
182
  ...this._prepareLifecycleHooks(),
183
183
  };
@@ -191,7 +191,7 @@ export class Eleva {
191
191
  * 4. Managing component lifecycle
192
192
  *
193
193
  * @param {Object<string, any>} data - Data returned from the component's setup function
194
- * @returns {MountResult} An object containing:
194
+ * @returns {Promise<MountResult>} An object containing:
195
195
  * - container: The mounted component's container element
196
196
  * - data: The component's reactive state and context
197
197
  * - unmount: Function to clean up and unmount the component
@@ -301,14 +301,17 @@ export class Eleva {
301
301
  const attrs = el.attributes;
302
302
  for (let i = 0; i < attrs.length; i++) {
303
303
  const attr = attrs[i];
304
- if (attr.name.startsWith("@")) {
305
- const event = attr.name.slice(1);
306
- const handler = TemplateEngine.evaluate(attr.value, context);
307
- if (typeof handler === "function") {
308
- el.addEventListener(event, handler);
309
- el.removeAttribute(attr.name);
310
- cleanupListeners.push(() => el.removeEventListener(event, handler));
311
- }
304
+
305
+ if (!attr.name.startsWith("@")) continue;
306
+
307
+ const event = attr.name.slice(1);
308
+ const handlerName = attr.value;
309
+ const handler =
310
+ context[handlerName] || TemplateEngine.evaluate(handlerName, context);
311
+ if (typeof handler === "function") {
312
+ el.addEventListener(event, handler);
313
+ el.removeAttribute(attr.name);
314
+ cleanupListeners.push(() => el.removeEventListener(event, handler));
312
315
  }
313
316
  }
314
317
  }
@@ -340,62 +343,29 @@ export class Eleva {
340
343
  }
341
344
 
342
345
  /**
343
- * Extracts props from an element's attributes that start with 'eleva-prop-'.
346
+ * Extracts props from an element's attributes that start with the specified prefix.
344
347
  * This method is used to collect component properties from DOM elements.
345
348
  *
346
349
  * @private
347
350
  * @param {HTMLElement} element - The DOM element to extract props from
351
+ * @param {string} prefix - The prefix to look for in attributes
348
352
  * @returns {Object<string, any>} An object containing the extracted props
349
353
  * @example
350
354
  * // For an element with attributes:
351
- * // <div eleva-prop-name="John" eleva-prop-age="25">
355
+ * // <div :name="John" :age="25">
352
356
  * // Returns: { name: "John", age: "25" }
353
357
  */
354
- _extractProps(element) {
358
+ _extractProps(element, prefix) {
359
+ /** @type {Record<string, string>} */
355
360
  const props = {};
356
361
  for (const { name, value } of element.attributes) {
357
- if (name.startsWith("eleva-prop-")) {
358
- props[name.replace("eleva-prop-", "")] = value;
362
+ if (name.startsWith(prefix)) {
363
+ props[name.replace(prefix, "")] = value;
359
364
  }
360
365
  }
361
366
  return props;
362
367
  }
363
368
 
364
- /**
365
- * Mounts a single component instance to a container element.
366
- * This method handles the actual mounting of a component with its props.
367
- *
368
- * @private
369
- * @param {HTMLElement} container - The container element to mount the component to
370
- * @param {string|ComponentDefinition} component - The component to mount, either as a name or definition
371
- * @param {Object<string, any>} props - The props to pass to the component
372
- * @returns {Promise<MountResult>} A promise that resolves to the mounted component instance
373
- * @throws {Error} If the container is not a valid HTMLElement
374
- */
375
- async _mountComponentInstance(container, component, props) {
376
- if (!(container instanceof HTMLElement)) return null;
377
- return await this.mount(container, component, props);
378
- }
379
-
380
- /**
381
- * Mounts components found by a selector in the container.
382
- * This method handles mounting multiple instances of the same component type.
383
- *
384
- * @private
385
- * @param {HTMLElement} container - The container to search for components
386
- * @param {string} selector - The CSS selector to find components
387
- * @param {string|ComponentDefinition} component - The component to mount
388
- * @param {Array<MountResult>} instances - Array to store the mounted component instances
389
- * @returns {Promise<void>}
390
- */
391
- async _mountComponentsBySelector(container, selector, component, instances) {
392
- for (const el of container.querySelectorAll(selector)) {
393
- const props = this._extractProps(el);
394
- const instance = await this._mountComponentInstance(el, component, props);
395
- if (instance) instances.push(instance);
396
- }
397
- }
398
-
399
369
  /**
400
370
  * Mounts all components within the parent component's container.
401
371
  * This method handles mounting of explicitly defined children components.
@@ -413,25 +383,25 @@ export class Eleva {
413
383
  * @example
414
384
  * // Explicit children mounting:
415
385
  * const children = {
416
- * '.user-profile': UserProfileComponent,
417
- * '.settings-panel': SettingsComponent
386
+ * 'UserProfile': UserProfileComponent,
387
+ * '#settings-panel': "settings-panel"
418
388
  * };
419
389
  */
420
390
  async _mountComponents(container, children, childInstances) {
391
+ if (!children) return;
392
+
421
393
  // Clean up existing instances
422
394
  for (const child of childInstances) child.unmount();
423
395
  childInstances.length = 0;
424
396
 
425
397
  // Mount explicitly defined children components
426
- if (children) {
427
- for (const [selector, component] of Object.entries(children)) {
428
- if (!selector) continue;
429
- await this._mountComponentsBySelector(
430
- container,
431
- selector,
432
- component,
433
- childInstances
434
- );
398
+ for (const [selector, component] of Object.entries(children)) {
399
+ if (!selector) continue;
400
+ for (const el of container.querySelectorAll(selector)) {
401
+ if (!(el instanceof HTMLElement)) continue;
402
+ const props = this._extractProps(el, ":");
403
+ const instance = await this.mount(el, component, props);
404
+ if (instance) childInstances.push(instance);
435
405
  }
436
406
  }
437
407
  }
@@ -29,7 +29,7 @@ export class Emitter {
29
29
  * @public
30
30
  * @param {string} event - The name of the event to listen for.
31
31
  * @param {function(any): void} handler - The callback function to invoke when the event occurs.
32
- * @returns {function(): boolean} A function to unsubscribe the event handler.
32
+ * @returns {function(): void} A function to unsubscribe the event handler.
33
33
  * @example
34
34
  * const unsubscribe = emitter.on('user:login', (user) => console.log(user));
35
35
  * // Later...
@@ -13,16 +13,25 @@
13
13
  * renderer.patchDOM(container, newHtml);
14
14
  */
15
15
  export class Renderer {
16
+ /**
17
+ * Creates a new Renderer instance with a reusable temporary container for parsing HTML.
18
+ * @public
19
+ */
20
+ constructor() {
21
+ /** @private {HTMLElement} Reusable temporary container for parsing new HTML */
22
+ this._tempContainer = document.createElement("div");
23
+ }
24
+
16
25
  /**
17
26
  * Patches the DOM of a container element with new HTML content.
18
- * This method efficiently updates the DOM by comparing the new content with the existing
19
- * content and applying only the necessary changes.
27
+ * Efficiently updates the DOM by parsing new HTML into a reusable container
28
+ * and applying only the necessary changes.
20
29
  *
21
30
  * @public
22
31
  * @param {HTMLElement} container - The container element to patch.
23
32
  * @param {string} newHtml - The new HTML content to apply.
24
33
  * @returns {void}
25
- * @throws {Error} If container is not an HTMLElement or newHtml is not a string.
34
+ * @throws {Error} If container is not an HTMLElement, newHtml is not a string, or patching fails.
26
35
  */
27
36
  patchDOM(container, newHtml) {
28
37
  if (!(container instanceof HTMLElement)) {
@@ -32,10 +41,14 @@ export class Renderer {
32
41
  throw new Error("newHtml must be a string");
33
42
  }
34
43
 
35
- const temp = document.createElement("div");
36
- temp.innerHTML = newHtml;
37
- this._diff(container, temp);
38
- temp.innerHTML = "";
44
+ try {
45
+ // Directly set new HTML, replacing any existing content
46
+ this._tempContainer.innerHTML = newHtml;
47
+
48
+ this._diff(container, this._tempContainer);
49
+ } catch {
50
+ throw new Error("Failed to patch DOM");
51
+ }
39
52
  }
40
53
 
41
54
  /**
@@ -47,16 +60,8 @@ export class Renderer {
47
60
  * @param {HTMLElement} oldParent - The original DOM element.
48
61
  * @param {HTMLElement} newParent - The new DOM element.
49
62
  * @returns {void}
50
- * @throws {Error} If either parent is not an HTMLElement.
51
63
  */
52
64
  _diff(oldParent, newParent) {
53
- if (
54
- !(oldParent instanceof HTMLElement) ||
55
- !(newParent instanceof HTMLElement)
56
- ) {
57
- throw new Error("Both parents must be HTMLElements");
58
- }
59
-
60
65
  if (oldParent.isEqualNode(newParent)) return;
61
66
 
62
67
  const oldChildren = oldParent.childNodes;
@@ -67,8 +72,6 @@ export class Renderer {
67
72
  const oldNode = oldChildren[i];
68
73
  const newNode = newChildren[i];
69
74
 
70
- if (!oldNode && !newNode) continue;
71
-
72
75
  if (!oldNode && newNode) {
73
76
  oldParent.appendChild(newNode.cloneNode(true));
74
77
  continue;
@@ -115,13 +118,8 @@ export class Renderer {
115
118
  * @param {HTMLElement} oldEl - The element to update.
116
119
  * @param {HTMLElement} newEl - The element providing the updated attributes.
117
120
  * @returns {void}
118
- * @throws {Error} If either element is not an HTMLElement.
119
121
  */
120
122
  _updateAttributes(oldEl, newEl) {
121
- if (!(oldEl instanceof HTMLElement) || !(newEl instanceof HTMLElement)) {
122
- throw new Error("Both elements must be HTMLElements");
123
- }
124
-
125
123
  const oldAttrs = oldEl.attributes;
126
124
  const newAttrs = newEl.attributes;
127
125
 
@@ -10,6 +10,7 @@
10
10
  * const count = new Signal(0);
11
11
  * count.watch((value) => console.log(`Count changed to: ${value}`));
12
12
  * count.value = 1; // Logs: "Count changed to: 1"
13
+ * @template T
13
14
  */
14
15
  export class Signal {
15
16
  /**
@@ -157,42 +157,19 @@ export class Eleva {
157
157
  */
158
158
  private _injectStyles;
159
159
  /**
160
- * Extracts props from an element's attributes that start with 'eleva-prop-'.
160
+ * Extracts props from an element's attributes that start with the specified prefix.
161
161
  * This method is used to collect component properties from DOM elements.
162
162
  *
163
163
  * @private
164
164
  * @param {HTMLElement} element - The DOM element to extract props from
165
+ * @param {string} prefix - The prefix to look for in attributes
165
166
  * @returns {Object<string, any>} An object containing the extracted props
166
167
  * @example
167
168
  * // For an element with attributes:
168
- * // <div eleva-prop-name="John" eleva-prop-age="25">
169
+ * // <div :name="John" :age="25">
169
170
  * // Returns: { name: "John", age: "25" }
170
171
  */
171
172
  private _extractProps;
172
- /**
173
- * Mounts a single component instance to a container element.
174
- * This method handles the actual mounting of a component with its props.
175
- *
176
- * @private
177
- * @param {HTMLElement} container - The container element to mount the component to
178
- * @param {string|ComponentDefinition} component - The component to mount, either as a name or definition
179
- * @param {Object<string, any>} props - The props to pass to the component
180
- * @returns {Promise<MountResult>} A promise that resolves to the mounted component instance
181
- * @throws {Error} If the container is not a valid HTMLElement
182
- */
183
- private _mountComponentInstance;
184
- /**
185
- * Mounts components found by a selector in the container.
186
- * This method handles mounting multiple instances of the same component type.
187
- *
188
- * @private
189
- * @param {HTMLElement} container - The container to search for components
190
- * @param {string} selector - The CSS selector to find components
191
- * @param {string|ComponentDefinition} component - The component to mount
192
- * @param {Array<MountResult>} instances - Array to store the mounted component instances
193
- * @returns {Promise<void>}
194
- */
195
- private _mountComponentsBySelector;
196
173
  /**
197
174
  * Mounts all components within the parent component's container.
198
175
  * This method handles mounting of explicitly defined children components.
@@ -210,8 +187,8 @@ export class Eleva {
210
187
  * @example
211
188
  * // Explicit children mounting:
212
189
  * const children = {
213
- * '.user-profile': UserProfileComponent,
214
- * '.settings-panel': SettingsComponent
190
+ * 'UserProfile': UserProfileComponent,
191
+ * '#settings-panel': "settings-panel"
215
192
  * };
216
193
  */
217
194
  private _mountComponents;
@@ -1 +1 @@
1
- {"version":3,"file":"Eleva.d.ts","sourceRoot":"","sources":["../../src/core/Eleva.js"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AAEH;;;;;;GAMG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;GAaG;AACH;IACE;;;;;;;OAOG;IACH,kBAJW,MAAM;;OA8BhB;IAzBC,0EAA0E;IAC1E,oBAAgB;IAChB,yFAAyF;IACzF;;MAAoB;IACpB,oFAAoF;IACpF,wBAA4B;IAC5B,+FAA+F;IAC/F,6BAAoB;IACpB,wFAAwF;IACxF,0BAA8B;IAE9B,gGAAgG;IAChG,oBAA4B;IAC5B,2FAA2F;IAC3F,iBAAyB;IACzB,gFAAgF;IAChF,wBAMC;IACD,oFAAoF;IACpF,mBAAuB;IAGzB;;;;;;;;;;;OAWG;IACH,mBANW,WAAW;;QAET,KAAK,CASjB;IAED;;;;;;;;;;;;;;OAcG;IACH,uBAVW,MAAM,cACN,mBAAmB,GACjB,KAAK,CAYjB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAdW,WAAW,YACX,MAAM,GAAC,mBAAmB;;QAExB,OAAO,CAAC,WAAW,CAAC,CAoIhC;IAED;;;;;;;OAOG;IACH,+BAOC;IAED;;;;;;;;;OASG;IACH,uBAiBC;IAED;;;;;;;;;;OAUG;IACH,sBAYC;IAED;;;;;;;;;;;OAWG;IACH,sBAQC;IAED;;;;;;;;;;OAUG;IACH,gCAGC;IAED;;;;;;;;;;OAUG;IACH,mCAMC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,yBAiBC;CACF;;;;;;;UA5a4C,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,GAAC,OAAO,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,CAAC,CAAC;;;;cAEjF,CAAS,IAAmB,EAAnB;YAAO,MAAM,GAAE,GAAG;KAAC,KAAG,MAAM;;;;;;UAEN,MAAM;;;;;;;;;;;;aAQrC,CAAS,IAAK,EAAL,KAAK,EAAE,IAAmB,EAAnB;YAAO,MAAM,GAAE,GAAG;KAAC,KAAG,IAAI;;;;UAE1C,MAAM;;;;;;eAMN,WAAW;;;;;;;;;;aAIX,MAAY,IAAI;;wBA7BN,uBAAuB;uBADxB,sBAAsB;yBAEpB,wBAAwB"}
1
+ {"version":3,"file":"Eleva.d.ts","sourceRoot":"","sources":["../../src/core/Eleva.js"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AAEH;;;;;;GAMG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;GAaG;AACH;IACE;;;;;;;OAOG;IACH,kBAJW,MAAM;;OA8BhB;IAzBC,0EAA0E;IAC1E,oBAAgB;IAChB,yFAAyF;IACzF;;MAAoB;IACpB,oFAAoF;IACpF,wBAA4B;IAC5B,+FAA+F;IAC/F,6BAAoB;IACpB,wFAAwF;IACxF,0BAA8B;IAE9B,gGAAgG;IAChG,oBAA4B;IAC5B,2FAA2F;IAC3F,iBAAyB;IACzB,gFAAgF;IAChF,wBAMC;IACD,oFAAoF;IACpF,mBAAuB;IAGzB;;;;;;;;;;;OAWG;IACH,mBANW,WAAW;;QAET,KAAK,CASjB;IAED;;;;;;;;;;;;;;OAcG;IACH,uBAVW,MAAM,cACN,mBAAmB,GACjB,KAAK,CAYjB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAdW,WAAW,YACX,MAAM,GAAC,mBAAmB;;QAExB,OAAO,CAAC,WAAW,CAAC,CAoIhC;IAED;;;;;;;OAOG;IACH,+BAOC;IAED;;;;;;;;;OASG;IACH,uBAoBC;IAED;;;;;;;;;;OAUG;IACH,sBAYC;IAED;;;;;;;;;;;;OAYG;IACH,sBASC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,yBAiBC;CACF;;;;;;;UA9Y4C,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,GAAC,OAAO,CAAC;YAAO,MAAM,GAAE,GAAG;KAAC,CAAC,CAAC;;;;cAEjF,CAAS,IAAmB,EAAnB;YAAO,MAAM,GAAE,GAAG;KAAC,KAAG,MAAM;;;;;;UAEN,MAAM;;;;;;;;;;;;aAQrC,CAAS,IAAK,EAAL,KAAK,EAAE,IAAmB,EAAnB;YAAO,MAAM,GAAE,GAAG;KAAC,KAAG,IAAI;;;;UAE1C,MAAM;;;;;;eAMN,WAAW;;;;;;;;;;aAIX,MAAY,IAAI;;wBA7BN,uBAAuB;uBADxB,sBAAsB;yBAEpB,wBAAwB"}
@@ -19,13 +19,13 @@ export class Emitter {
19
19
  * @public
20
20
  * @param {string} event - The name of the event to listen for.
21
21
  * @param {function(any): void} handler - The callback function to invoke when the event occurs.
22
- * @returns {function(): boolean} A function to unsubscribe the event handler.
22
+ * @returns {function(): void} A function to unsubscribe the event handler.
23
23
  * @example
24
24
  * const unsubscribe = emitter.on('user:login', (user) => console.log(user));
25
25
  * // Later...
26
26
  * unsubscribe(); // Stops listening for the event
27
27
  */
28
- public on(event: string, handler: (arg0: any) => void): () => boolean;
28
+ public on(event: string, handler: (arg0: any) => void): () => void;
29
29
  /**
30
30
  * Removes an event handler for the specified event name.
31
31
  * If no handler is provided, all handlers for the event are removed.
@@ -1 +1 @@
1
- {"version":3,"file":"Emitter.d.ts","sourceRoot":"","sources":["../../src/modules/Emitter.js"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH;IAOI,gHAAgH;IAChH,gBAAwB;IAG1B;;;;;;;;;;;;OAYG;IACH,iBARW,MAAM,WACN,CAAS,IAAG,EAAH,GAAG,KAAG,IAAI,GACjB,MAAY,OAAO,CAW/B;IAED;;;;;;;;OAQG;IACH,kBAJW,MAAM,YACN,CAAS,IAAG,EAAH,GAAG,KAAG,IAAI,GACjB,IAAI,CAYhB;IAED;;;;;;;;OAQG;IACH,mBAJW,MAAM,WACH,GAAG,EAAA,GACJ,IAAI,CAKhB;CACF"}
1
+ {"version":3,"file":"Emitter.d.ts","sourceRoot":"","sources":["../../src/modules/Emitter.js"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH;IAOI,gHAAgH;IAChH,gBAAwB;IAG1B;;;;;;;;;;;;OAYG;IACH,iBARW,MAAM,WACN,CAAS,IAAG,EAAH,GAAG,KAAG,IAAI,GACjB,MAAY,IAAI,CAW5B;IAED;;;;;;;;OAQG;IACH,kBAJW,MAAM,YACN,CAAS,IAAG,EAAH,GAAG,KAAG,IAAI,GACjB,IAAI,CAYhB;IAED;;;;;;;;OAQG;IACH,mBAJW,MAAM,WACH,GAAG,EAAA,GACJ,IAAI,CAKhB;CACF"}
@@ -11,16 +11,18 @@
11
11
  * renderer.patchDOM(container, newHtml);
12
12
  */
13
13
  export class Renderer {
14
+ /** @private {HTMLElement} Reusable temporary container for parsing new HTML */
15
+ private _tempContainer;
14
16
  /**
15
17
  * Patches the DOM of a container element with new HTML content.
16
- * This method efficiently updates the DOM by comparing the new content with the existing
17
- * content and applying only the necessary changes.
18
+ * Efficiently updates the DOM by parsing new HTML into a reusable container
19
+ * and applying only the necessary changes.
18
20
  *
19
21
  * @public
20
22
  * @param {HTMLElement} container - The container element to patch.
21
23
  * @param {string} newHtml - The new HTML content to apply.
22
24
  * @returns {void}
23
- * @throws {Error} If container is not an HTMLElement or newHtml is not a string.
25
+ * @throws {Error} If container is not an HTMLElement, newHtml is not a string, or patching fails.
24
26
  */
25
27
  public patchDOM(container: HTMLElement, newHtml: string): void;
26
28
  /**
@@ -32,7 +34,6 @@ export class Renderer {
32
34
  * @param {HTMLElement} oldParent - The original DOM element.
33
35
  * @param {HTMLElement} newParent - The new DOM element.
34
36
  * @returns {void}
35
- * @throws {Error} If either parent is not an HTMLElement.
36
37
  */
37
38
  private _diff;
38
39
  /**
@@ -43,7 +44,6 @@ export class Renderer {
43
44
  * @param {HTMLElement} oldEl - The element to update.
44
45
  * @param {HTMLElement} newEl - The element providing the updated attributes.
45
46
  * @returns {void}
46
- * @throws {Error} If either element is not an HTMLElement.
47
47
  */
48
48
  private _updateAttributes;
49
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../src/modules/Renderer.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH;IACE;;;;;;;;;;OAUG;IACH,2BALW,WAAW,WACX,MAAM,GACJ,IAAI,CAehB;IAED;;;;;;;;;;OAUG;IACH,cAwDC;IAED;;;;;;;;;OASG;IACH,0BAqDC;CACF"}
1
+ {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../src/modules/Renderer.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH;IAMI,+EAA+E;IAC/E,uBAAmD;IAGrD;;;;;;;;;;OAUG;IACH,2BALW,WAAW,WACX,MAAM,GACJ,IAAI,CAmBhB;IAED;;;;;;;;;OASG;IACH,cA+CC;IAED;;;;;;;;OAQG;IACH,0BAiDC;CACF"}
@@ -8,8 +8,9 @@
8
8
  * const count = new Signal(0);
9
9
  * count.watch((value) => console.log(`Count changed to: ${value}`));
10
10
  * count.value = 1; // Logs: "Count changed to: 1"
11
+ * @template T
11
12
  */
12
- export class Signal {
13
+ export class Signal<T> {
13
14
  /**
14
15
  * Creates a new Signal instance with the specified initial value.
15
16
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../../src/modules/Signal.js"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH;IACE;;;;;OAKG;IACH,mBAFW,GAAC,EASX;IANC,6GAA6G;IAC7G,eAAmB;IACnB,sIAAsI;IACtI,kBAA0B;IAC1B,sHAAsH;IACtH,iBAAqB;IAavB;;;;;;;OAOG;IACH,yBAHW,CAAC,EAQX;IAvBD;;;;;OAKG;IACH,oBAFa,CAAC,CAIb;IAiBD;;;;;;;;;;;OAWG;IACH,iBAPW,CAAS,IAAC,EAAD,CAAC,KAAG,IAAI,GACf,MAAY,OAAO,CAS/B;IAED;;;;;;;OAOG;IACH,gBAQC;CACF"}
1
+ {"version":3,"file":"Signal.d.ts","sourceRoot":"","sources":["../../src/modules/Signal.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,oBAFa,CAAC;IAGZ;;;;;OAKG;IACH,mBAFW,GAAC,EASX;IANC,6GAA6G;IAC7G,eAAmB;IACnB,sIAAsI;IACtI,kBAA0B;IAC1B,sHAAsH;IACtH,iBAAqB;IAavB;;;;;;;OAOG;IACH,yBAHW,CAAC,EAQX;IAvBD;;;;;OAKG;IACH,oBAFa,CAAC,CAIb;IAiBD;;;;;;;;;;;OAWG;IACH,iBAPW,CAAS,IAAC,EAAD,CAAC,KAAG,IAAI,GACf,MAAY,OAAO,CAS/B;IAED;;;;;;;OAOG;IACH,gBAQC;CACF"}
package/dist/eleva.min.js DELETED
@@ -1,3 +0,0 @@
1
- /*! Eleva v1.2.13-alpha | MIT License | https://elevajs.com */
2
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Eleva=t()}(this,(function(){"use strict";class e{static expressionPattern=/\{\{\s*(.*?)\s*\}\}/g;static parse(e,t){return"string"!=typeof e?e:e.replace(this.expressionPattern,((e,n)=>this.evaluate(n,t)))}static evaluate(e,t){if("string"!=typeof e)return e;try{return new Function("data",`with(data) { return ${e}; }`)(t)}catch{return""}}}class t{constructor(e){this._value=e,this._watchers=new Set,this._pending=!1}get value(){return this._value}set value(e){this._value!==e&&(this._value=e,this._notify())}watch(e){return this._watchers.add(e),()=>this._watchers.delete(e)}_notify(){this._pending||(this._pending=!0,queueMicrotask((()=>{this._watchers.forEach((e=>e(this._value))),this._pending=!1})))}}class n{constructor(){this._events=new Map}on(e,t){return this._events.has(e)||this._events.set(e,new Set),this._events.get(e).add(t),()=>this.off(e,t)}off(e,t){if(this._events.has(e))if(t){const n=this._events.get(e);n.delete(t),0===n.size&&this._events.delete(e)}else this._events.delete(e)}emit(e,...t){this._events.has(e)&&this._events.get(e).forEach((e=>e(...t)))}}class o{patchDOM(e,t){if(!(e instanceof HTMLElement))throw new Error("Container must be an HTMLElement");if("string"!=typeof t)throw new Error("newHtml must be a string");const n=document.createElement("div");n.innerHTML=t,this._diff(e,n),n.innerHTML=""}_diff(e,t){if(!(e instanceof HTMLElement&&t instanceof HTMLElement))throw new Error("Both parents must be HTMLElements");if(e.isEqualNode(t))return;const n=e.childNodes,o=t.childNodes,s=Math.max(n.length,o.length);for(let t=0;t<s;t++){const s=n[t],i=o[t];if(!s&&!i)continue;if(!s&&i){e.appendChild(i.cloneNode(!0));continue}if(s&&!i){e.removeChild(s);continue}if(s.nodeType===i.nodeType&&s.nodeName===i.nodeName)if(s.nodeType===Node.ELEMENT_NODE){const t=s.getAttribute("key"),n=i.getAttribute("key");if(t!==n&&(t||n)){e.replaceChild(i.cloneNode(!0),s);continue}this._updateAttributes(s,i),this._diff(s,i)}else s.nodeType===Node.TEXT_NODE&&s.nodeValue!==i.nodeValue&&(s.nodeValue=i.nodeValue);else e.replaceChild(i.cloneNode(!0),s)}}_updateAttributes(e,t){if(!(e instanceof HTMLElement&&t instanceof HTMLElement))throw new Error("Both elements must be HTMLElements");const n=e.attributes,o=t.attributes;for(const{name:o}of n)t.hasAttribute(o)||e.removeAttribute(o);for(const t of o){const{name:n,value:o}=t;if(!n.startsWith("@")&&e.getAttribute(n)!==o)if(e.setAttribute(n,o),n.startsWith("aria-")){e["aria"+n.slice(5).replace(/-([a-z])/g,((e,t)=>t.toUpperCase()))]=o}else if(n.startsWith("data-"))e.dataset[n.slice(5)]=o;else{const t=n.replace(/-([a-z])/g,((e,t)=>t.toUpperCase()));if(t in e){const n=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(e),t),s="boolean"==typeof e[t]||n?.get&&"boolean"==typeof n.get.call(e);e[t]=s?"false"!==o&&(""===o||o===t||"true"===o):o}}}}}return class{constructor(e,s={}){this.name=e,this.config=s,this.emitter=new n,this.signal=t,this.renderer=new o,this._components=new Map,this._plugins=new Map,this._lifecycleHooks=["onBeforeMount","onMount","onBeforeUpdate","onUpdate","onUnmount"],this._isMounted=!1}use(e,t={}){return e.install(this,t),this._plugins.set(e.name,e),this}component(e,t){return this._components.set(e,t),this}async mount(n,o,s={}){if(!n)throw new Error(`Container not found: ${n}`);const i="string"==typeof o?this._components.get(o):o;if(!i)throw new Error(`Component "${o}" not registered.`);if("function"!=typeof i.template)throw new Error("Component template must be a function");const{setup:r,template:a,style:c,children:l}=i,u={props:s,emitter:this.emitter,signal:e=>new this.signal(e),...this._prepareLifecycleHooks()},f="function"==typeof r?await r(u):{};return await(async s=>{const i={...u,...s},r=[],f=[],h=[];this._isMounted?i.onBeforeUpdate&&i.onBeforeUpdate():i.onBeforeMount&&i.onBeforeMount();const p=async()=>{const t=e.parse(a(i),i);this.renderer.patchDOM(n,t),this._processEvents(n,i,h),this._injectStyles(n,o,c,i),await this._mountComponents(n,l,f),this._isMounted?i.onUpdate&&i.onUpdate():(i.onMount&&i.onMount(),this._isMounted=!0)};for(const e of Object.values(s))e instanceof t&&r.push(e.watch(p));return await p(),{container:n,data:i,unmount:()=>{for(const e of r)e();for(const e of h)e();for(const e of f)e.unmount();i.onUnmount&&i.onUnmount(),n.innerHTML=""}}})(f)}_prepareLifecycleHooks(){const e={};for(const t of this._lifecycleHooks)e[t]=()=>{};return e}_processEvents(t,n,o){const s=t.querySelectorAll("*");for(const t of s){const s=t.attributes;for(let i=0;i<s.length;i++){const r=s[i];if(r.name.startsWith("@")){const s=r.name.slice(1),i=e.evaluate(r.value,n);"function"==typeof i&&(t.addEventListener(s,i),t.removeAttribute(r.name),o.push((()=>t.removeEventListener(s,i))))}}}}_injectStyles(t,n,o,s){if(!o)return;let i=t.querySelector(`style[data-eleva-style="${n}"]`);i||(i=document.createElement("style"),i.setAttribute("data-eleva-style",n),t.appendChild(i)),i.textContent=e.parse(o(s),s)}_extractProps(e){const t={};for(const{name:n,value:o}of e.attributes)n.startsWith("eleva-prop-")&&(t[n.replace("eleva-prop-","")]=o);return t}async _mountComponentInstance(e,t,n){return e instanceof HTMLElement?await this.mount(e,t,n):null}async _mountComponentsBySelector(e,t,n,o){for(const s of e.querySelectorAll(t)){const e=this._extractProps(s),t=await this._mountComponentInstance(s,n,e);t&&o.push(t)}}async _mountComponents(e,t,n){for(const e of n)e.unmount();if(n.length=0,t)for(const[o,s]of Object.entries(t))o&&await this._mountComponentsBySelector(e,o,s,n)}}}));
3
- //# sourceMappingURL=eleva.min.js.map