eleva 1.0.0-rc.5 → 1.0.0-rc.6
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/README.md +2 -2
- package/dist/eleva-plugins.cjs.js +232 -2
- package/dist/eleva-plugins.cjs.js.map +1 -1
- package/dist/eleva-plugins.esm.js +232 -2
- package/dist/eleva-plugins.esm.js.map +1 -1
- package/dist/eleva-plugins.umd.js +232 -2
- package/dist/eleva-plugins.umd.js.map +1 -1
- package/dist/eleva-plugins.umd.min.js +2 -2
- package/dist/eleva-plugins.umd.min.js.map +1 -1
- package/dist/eleva.cjs.js +1 -1
- package/dist/eleva.esm.js +1 -1
- package/dist/eleva.umd.js +1 -1
- package/dist/eleva.umd.min.js +1 -1
- package/dist/plugins/props.umd.js +232 -2
- package/dist/plugins/props.umd.js.map +1 -1
- package/dist/plugins/props.umd.min.js +2 -2
- package/dist/plugins/props.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/plugins/Props.js +206 -1
- package/types/plugins/Props.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -40,9 +40,9 @@ Pure JavaScript, Pure Performance, Simply Elegant.
|
|
|
40
40
|
**A minimalist, lightweight, pure vanilla JavaScript frontend runtime framework.**
|
|
41
41
|
_Built with love for native JavaScript and designed with a minimal core that can be extended through a powerful plugin system-because sometimes, less really is more!_ 😊
|
|
42
42
|
|
|
43
|
-
> **Stability Notice**: This is `v1.0.0-rc.
|
|
43
|
+
> **Stability Notice**: This is `v1.0.0-rc.6` - The core functionality is stable. Seeking community feedback before the final v1.0.0 release.
|
|
44
44
|
|
|
45
|
-
**Version:** `1.0.0-rc.
|
|
45
|
+
**Version:** `1.0.0-rc.6`
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Eleva Plugins v1.0.0-rc.
|
|
1
|
+
/*! Eleva Plugins v1.0.0-rc.6 | MIT License | https://elevajs.com */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -1325,6 +1325,68 @@ const RouterPlugin = {
|
|
|
1325
1325
|
}
|
|
1326
1326
|
};
|
|
1327
1327
|
|
|
1328
|
+
/**
|
|
1329
|
+
* @class 🔒 TemplateEngine
|
|
1330
|
+
* @classdesc A secure template engine that handles interpolation and dynamic attribute parsing.
|
|
1331
|
+
* Provides a safe way to evaluate expressions in templates while preventing XSS attacks.
|
|
1332
|
+
* All methods are static and can be called directly on the class.
|
|
1333
|
+
*
|
|
1334
|
+
* @example
|
|
1335
|
+
* const template = "Hello, {{name}}!";
|
|
1336
|
+
* const data = { name: "World" };
|
|
1337
|
+
* const result = TemplateEngine.parse(template, data); // Returns: "Hello, World!"
|
|
1338
|
+
*/
|
|
1339
|
+
class TemplateEngine {
|
|
1340
|
+
/**
|
|
1341
|
+
* @private {RegExp} Regular expression for matching template expressions in the format {{ expression }}
|
|
1342
|
+
* @type {RegExp}
|
|
1343
|
+
*/
|
|
1344
|
+
static expressionPattern = /\{\{\s*(.*?)\s*\}\}/g;
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* Parses a template string, replacing expressions with their evaluated values.
|
|
1348
|
+
* Expressions are evaluated in the provided data context.
|
|
1349
|
+
*
|
|
1350
|
+
* @public
|
|
1351
|
+
* @static
|
|
1352
|
+
* @param {string} template - The template string to parse.
|
|
1353
|
+
* @param {Record<string, unknown>} data - The data context for evaluating expressions.
|
|
1354
|
+
* @returns {string} The parsed template with expressions replaced by their values.
|
|
1355
|
+
* @example
|
|
1356
|
+
* const result = TemplateEngine.parse("{{user.name}} is {{user.age}} years old", {
|
|
1357
|
+
* user: { name: "John", age: 30 }
|
|
1358
|
+
* }); // Returns: "John is 30 years old"
|
|
1359
|
+
*/
|
|
1360
|
+
static parse(template, data) {
|
|
1361
|
+
if (typeof template !== "string") return template;
|
|
1362
|
+
return template.replace(this.expressionPattern, (_, expression) => this.evaluate(expression, data));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Evaluates an expression in the context of the provided data object.
|
|
1367
|
+
* Note: This does not provide a true sandbox and evaluated expressions may access global scope.
|
|
1368
|
+
* The use of the `with` statement is necessary for expression evaluation but has security implications.
|
|
1369
|
+
* Expressions should be carefully validated before evaluation.
|
|
1370
|
+
*
|
|
1371
|
+
* @public
|
|
1372
|
+
* @static
|
|
1373
|
+
* @param {string} expression - The expression to evaluate.
|
|
1374
|
+
* @param {Record<string, unknown>} data - The data context for evaluation.
|
|
1375
|
+
* @returns {unknown} The result of the evaluation, or an empty string if evaluation fails.
|
|
1376
|
+
* @example
|
|
1377
|
+
* const result = TemplateEngine.evaluate("user.name", { user: { name: "John" } }); // Returns: "John"
|
|
1378
|
+
* const age = TemplateEngine.evaluate("user.age", { user: { age: 30 } }); // Returns: 30
|
|
1379
|
+
*/
|
|
1380
|
+
static evaluate(expression, data) {
|
|
1381
|
+
if (typeof expression !== "string") return expression;
|
|
1382
|
+
try {
|
|
1383
|
+
return new Function("data", `with(data) { return ${expression}; }`)(data);
|
|
1384
|
+
} catch {
|
|
1385
|
+
return "";
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1328
1390
|
/**
|
|
1329
1391
|
* @class 🎯 PropsPlugin
|
|
1330
1392
|
* @classdesc A plugin that extends Eleva's props data handling to support any type of data structure
|
|
@@ -1383,7 +1445,7 @@ const PropsPlugin = {
|
|
|
1383
1445
|
* Plugin version
|
|
1384
1446
|
* @type {string}
|
|
1385
1447
|
*/
|
|
1386
|
-
version: "1.0.0-rc.
|
|
1448
|
+
version: "1.0.0-rc.2",
|
|
1387
1449
|
/**
|
|
1388
1450
|
* Plugin description
|
|
1389
1451
|
* @type {string}
|
|
@@ -1613,6 +1675,167 @@ const PropsPlugin = {
|
|
|
1613
1675
|
return await originalMount.call(eleva, container, compName, enhancedProps);
|
|
1614
1676
|
};
|
|
1615
1677
|
|
|
1678
|
+
// Override Eleva's _mountComponents method to enable signal reference passing
|
|
1679
|
+
const originalMountComponents = eleva._mountComponents;
|
|
1680
|
+
|
|
1681
|
+
// Cache to store parent contexts by container element
|
|
1682
|
+
const parentContextCache = new WeakMap();
|
|
1683
|
+
// Store child instances that need signal linking
|
|
1684
|
+
const pendingSignalLinks = new Set();
|
|
1685
|
+
eleva._mountComponents = async (container, children, childInstances) => {
|
|
1686
|
+
for (const [selector, component] of Object.entries(children)) {
|
|
1687
|
+
if (!selector) continue;
|
|
1688
|
+
for (const el of container.querySelectorAll(selector)) {
|
|
1689
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
1690
|
+
|
|
1691
|
+
// Extract props from DOM attributes
|
|
1692
|
+
const extractedProps = eleva._extractProps(el);
|
|
1693
|
+
|
|
1694
|
+
// Get parent context to check for signal references
|
|
1695
|
+
let enhancedProps = extractedProps;
|
|
1696
|
+
|
|
1697
|
+
// Try to find parent context by looking up the DOM tree
|
|
1698
|
+
let parentContext = parentContextCache.get(container);
|
|
1699
|
+
if (!parentContext) {
|
|
1700
|
+
let currentElement = container;
|
|
1701
|
+
while (currentElement && !parentContext) {
|
|
1702
|
+
if (currentElement._eleva_instance && currentElement._eleva_instance.data) {
|
|
1703
|
+
parentContext = currentElement._eleva_instance.data;
|
|
1704
|
+
// Cache the parent context for future use
|
|
1705
|
+
parentContextCache.set(container, parentContext);
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
currentElement = currentElement.parentElement;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
if (enableReactivity && parentContext) {
|
|
1712
|
+
const signalProps = {};
|
|
1713
|
+
|
|
1714
|
+
// Check each extracted prop to see if there's a matching signal in parent context
|
|
1715
|
+
Object.keys(extractedProps).forEach(propName => {
|
|
1716
|
+
if (parentContext[propName] && parentContext[propName] instanceof eleva.signal) {
|
|
1717
|
+
// Found a signal in parent context with the same name as the prop
|
|
1718
|
+
// Pass the signal reference instead of creating a new one
|
|
1719
|
+
signalProps[propName] = parentContext[propName];
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
|
|
1723
|
+
// Merge signal props with regular props (signal props take precedence)
|
|
1724
|
+
enhancedProps = {
|
|
1725
|
+
...extractedProps,
|
|
1726
|
+
...signalProps
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// Create reactive props for non-signal props only
|
|
1731
|
+
let finalProps = enhancedProps;
|
|
1732
|
+
if (enableReactivity) {
|
|
1733
|
+
// Only create reactive props for values that aren't already signals
|
|
1734
|
+
const nonSignalProps = {};
|
|
1735
|
+
Object.entries(enhancedProps).forEach(([key, value]) => {
|
|
1736
|
+
if (!(value && typeof value === "object" && "value" in value && "watch" in value)) {
|
|
1737
|
+
// This is not a signal, create a reactive prop for it
|
|
1738
|
+
nonSignalProps[key] = value;
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
// Create reactive props only for non-signal values
|
|
1743
|
+
const reactiveNonSignalProps = createReactiveProps(nonSignalProps);
|
|
1744
|
+
|
|
1745
|
+
// Merge signal props with reactive non-signal props
|
|
1746
|
+
finalProps = {
|
|
1747
|
+
...reactiveNonSignalProps,
|
|
1748
|
+
...enhancedProps // Signal props take precedence
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/** @type {MountResult} */
|
|
1753
|
+
const instance = await eleva.mount(el, component, finalProps);
|
|
1754
|
+
if (instance && !childInstances.includes(instance)) {
|
|
1755
|
+
childInstances.push(instance);
|
|
1756
|
+
|
|
1757
|
+
// If we have extracted props but no parent context yet, mark for later signal linking
|
|
1758
|
+
if (enableReactivity && Object.keys(extractedProps).length > 0 && !parentContext) {
|
|
1759
|
+
pendingSignalLinks.add({
|
|
1760
|
+
instance,
|
|
1761
|
+
extractedProps,
|
|
1762
|
+
container,
|
|
1763
|
+
component
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// After mounting all children, try to link signals for pending instances
|
|
1771
|
+
if (enableReactivity && pendingSignalLinks.size > 0) {
|
|
1772
|
+
for (const pending of pendingSignalLinks) {
|
|
1773
|
+
const {
|
|
1774
|
+
instance,
|
|
1775
|
+
extractedProps,
|
|
1776
|
+
container,
|
|
1777
|
+
component
|
|
1778
|
+
} = pending;
|
|
1779
|
+
|
|
1780
|
+
// Try to find parent context again
|
|
1781
|
+
let parentContext = parentContextCache.get(container);
|
|
1782
|
+
if (!parentContext) {
|
|
1783
|
+
let currentElement = container;
|
|
1784
|
+
while (currentElement && !parentContext) {
|
|
1785
|
+
if (currentElement._eleva_instance && currentElement._eleva_instance.data) {
|
|
1786
|
+
parentContext = currentElement._eleva_instance.data;
|
|
1787
|
+
parentContextCache.set(container, parentContext);
|
|
1788
|
+
break;
|
|
1789
|
+
}
|
|
1790
|
+
currentElement = currentElement.parentElement;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (parentContext) {
|
|
1794
|
+
const signalProps = {};
|
|
1795
|
+
|
|
1796
|
+
// Check each extracted prop to see if there's a matching signal in parent context
|
|
1797
|
+
Object.keys(extractedProps).forEach(propName => {
|
|
1798
|
+
if (parentContext[propName] && parentContext[propName] instanceof eleva.signal) {
|
|
1799
|
+
signalProps[propName] = parentContext[propName];
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
// Update the child instance's data with signal references
|
|
1804
|
+
if (Object.keys(signalProps).length > 0) {
|
|
1805
|
+
Object.assign(instance.data, signalProps);
|
|
1806
|
+
|
|
1807
|
+
// Set up signal watchers for the newly linked signals
|
|
1808
|
+
Object.keys(signalProps).forEach(propName => {
|
|
1809
|
+
const signal = signalProps[propName];
|
|
1810
|
+
if (signal && typeof signal.watch === "function") {
|
|
1811
|
+
signal.watch(newValue => {
|
|
1812
|
+
// Trigger a re-render of the child component when the signal changes
|
|
1813
|
+
const childComponent = eleva._components.get(component) || component;
|
|
1814
|
+
if (childComponent && childComponent.template) {
|
|
1815
|
+
const templateResult = typeof childComponent.template === "function" ? childComponent.template(instance.data) : childComponent.template;
|
|
1816
|
+
const newHtml = TemplateEngine.parse(templateResult, instance.data);
|
|
1817
|
+
eleva.renderer.patchDOM(instance.container, newHtml);
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
// Initial re-render to show the correct signal values
|
|
1824
|
+
const childComponent = eleva._components.get(component) || component;
|
|
1825
|
+
if (childComponent && childComponent.template) {
|
|
1826
|
+
const templateResult = typeof childComponent.template === "function" ? childComponent.template(instance.data) : childComponent.template;
|
|
1827
|
+
const newHtml = TemplateEngine.parse(templateResult, instance.data);
|
|
1828
|
+
eleva.renderer.patchDOM(instance.container, newHtml);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// Remove from pending list
|
|
1833
|
+
pendingSignalLinks.delete(pending);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1616
1839
|
/**
|
|
1617
1840
|
* Expose utility methods on the Eleva instance
|
|
1618
1841
|
* @namespace eleva.props
|
|
@@ -1652,6 +1875,7 @@ const PropsPlugin = {
|
|
|
1652
1875
|
// Store original methods for uninstall
|
|
1653
1876
|
eleva._originalExtractProps = eleva._extractProps;
|
|
1654
1877
|
eleva._originalMount = originalMount;
|
|
1878
|
+
eleva._originalMountComponents = originalMountComponents;
|
|
1655
1879
|
},
|
|
1656
1880
|
/**
|
|
1657
1881
|
* Uninstalls the plugin from the Eleva instance
|
|
@@ -1680,6 +1904,12 @@ const PropsPlugin = {
|
|
|
1680
1904
|
delete eleva._originalMount;
|
|
1681
1905
|
}
|
|
1682
1906
|
|
|
1907
|
+
// Restore original _mountComponents method
|
|
1908
|
+
if (eleva._originalMountComponents) {
|
|
1909
|
+
eleva._mountComponents = eleva._originalMountComponents;
|
|
1910
|
+
delete eleva._originalMountComponents;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1683
1913
|
// Remove plugin utility methods
|
|
1684
1914
|
if (eleva.props) {
|
|
1685
1915
|
delete eleva.props;
|