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