eleva 1.0.0-rc.5 → 1.0.0-rc.7
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 +135 -6
- package/dist/eleva-plugins.cjs.js +861 -29
- package/dist/eleva-plugins.cjs.js.map +1 -1
- package/dist/eleva-plugins.esm.js +861 -30
- package/dist/eleva-plugins.esm.js.map +1 -1
- package/dist/eleva-plugins.umd.js +861 -29
- 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 +31 -25
- package/dist/eleva.cjs.js.map +1 -1
- package/dist/eleva.esm.js +31 -25
- package/dist/eleva.esm.js.map +1 -1
- package/dist/eleva.umd.js +31 -25
- package/dist/eleva.umd.js.map +1 -1
- package/dist/eleva.umd.min.js +2 -2
- package/dist/eleva.umd.min.js.map +1 -1
- package/dist/plugins/attr.umd.js +2 -2
- package/dist/plugins/attr.umd.js.map +1 -1
- package/dist/plugins/attr.umd.min.js +1 -1
- package/dist/plugins/attr.umd.min.js.map +1 -1
- package/dist/plugins/props.umd.js +235 -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/dist/plugins/router.umd.js +22 -25
- package/dist/plugins/router.umd.js.map +1 -1
- package/dist/plugins/router.umd.min.js +1 -1
- package/dist/plugins/router.umd.min.js.map +1 -1
- package/dist/plugins/store.umd.js +632 -0
- package/dist/plugins/store.umd.js.map +1 -0
- package/dist/plugins/store.umd.min.js +3 -0
- package/dist/plugins/store.umd.min.js.map +1 -0
- package/package.json +4 -4
- package/src/plugins/Props.js +206 -1
- package/src/plugins/Store.js +741 -0
- package/src/plugins/index.js +6 -1
- package/types/plugins/Props.d.ts.map +1 -1
- package/types/plugins/Store.d.ts +86 -0
- package/types/plugins/Store.d.ts.map +1 -0
- package/types/plugins/index.d.ts +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Eleva Plugins v1.0.0-rc.
|
|
1
|
+
/*! Eleva Plugins v1.0.0-rc.7 | MIT License | https://elevajs.com */
|
|
2
2
|
/**
|
|
3
3
|
* A regular expression to match hyphenated lowercase letters.
|
|
4
4
|
* @private
|
|
@@ -127,7 +127,7 @@ const AttrPlugin = {
|
|
|
127
127
|
if (hasProperty) {
|
|
128
128
|
// Boolean attribute handling
|
|
129
129
|
if (enableBoolean) {
|
|
130
|
-
const isBoolean = typeof oldEl[prop] === "boolean" || descriptor
|
|
130
|
+
const isBoolean = typeof oldEl[prop] === "boolean" || (descriptor == null ? void 0 : descriptor.get) && typeof descriptor.get.call(oldEl) === "boolean";
|
|
131
131
|
if (isBoolean) {
|
|
132
132
|
const boolValue = value !== "false" && (value === "" || value === prop || value === "true");
|
|
133
133
|
oldEl[prop] = boolValue;
|
|
@@ -169,7 +169,7 @@ const AttrPlugin = {
|
|
|
169
169
|
|
|
170
170
|
// Override the _patchNode method to use our attribute handler
|
|
171
171
|
eleva.renderer._patchNode = function (oldNode, newNode) {
|
|
172
|
-
if (oldNode
|
|
172
|
+
if (oldNode != null && oldNode._eleva_instance) return;
|
|
173
173
|
if (!this._isSameNode(oldNode, newNode)) {
|
|
174
174
|
oldNode.replaceWith(newNode.cloneNode(true));
|
|
175
175
|
return;
|
|
@@ -219,18 +219,16 @@ const AttrPlugin = {
|
|
|
219
219
|
}
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
function _extends() {
|
|
223
|
+
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
224
|
+
for (var e = 1; e < arguments.length; e++) {
|
|
225
|
+
var t = arguments[e];
|
|
226
|
+
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
|
|
227
|
+
}
|
|
228
|
+
return n;
|
|
229
|
+
}, _extends.apply(null, arguments);
|
|
230
|
+
}
|
|
227
231
|
|
|
228
|
-
/**
|
|
229
|
-
* Simple error handler for the core router.
|
|
230
|
-
* Can be overridden by error handling plugins.
|
|
231
|
-
* Provides consistent error formatting and logging for router operations.
|
|
232
|
-
* @private
|
|
233
|
-
*/
|
|
234
232
|
const CoreErrorHandler = {
|
|
235
233
|
/**
|
|
236
234
|
* Handles router errors with basic formatting.
|
|
@@ -339,12 +337,11 @@ class Router {
|
|
|
339
337
|
this.eleva = eleva;
|
|
340
338
|
|
|
341
339
|
/** @type {RouterOptions} The merged router options. */
|
|
342
|
-
this.options = {
|
|
340
|
+
this.options = _extends({
|
|
343
341
|
mode: "hash",
|
|
344
342
|
queryParam: "view",
|
|
345
|
-
viewSelector: "root"
|
|
346
|
-
|
|
347
|
-
};
|
|
343
|
+
viewSelector: "root"
|
|
344
|
+
}, options);
|
|
348
345
|
|
|
349
346
|
/** @private @type {RouteDefinition[]} The processed list of route definitions. */
|
|
350
347
|
this.routes = this._processRoutes(options.routes || []);
|
|
@@ -408,10 +405,9 @@ class Router {
|
|
|
408
405
|
const processedRoutes = [];
|
|
409
406
|
for (const route of routes) {
|
|
410
407
|
try {
|
|
411
|
-
processedRoutes.push({
|
|
412
|
-
...route,
|
|
408
|
+
processedRoutes.push(_extends({}, route, {
|
|
413
409
|
segments: this._parsePathIntoSegments(route.path)
|
|
414
|
-
});
|
|
410
|
+
}));
|
|
415
411
|
} catch (error) {
|
|
416
412
|
this.errorHandler.warn(`Invalid path in route definition "${route.path || "undefined"}": ${error.message}`, {
|
|
417
413
|
route,
|
|
@@ -666,13 +662,12 @@ class Router {
|
|
|
666
662
|
return false;
|
|
667
663
|
}
|
|
668
664
|
}
|
|
669
|
-
const to = {
|
|
670
|
-
...toLocation,
|
|
665
|
+
const to = _extends({}, toLocation, {
|
|
671
666
|
params: toMatch.params,
|
|
672
667
|
meta: toMatch.route.meta || {},
|
|
673
668
|
name: toMatch.route.name,
|
|
674
669
|
matched: toMatch.route
|
|
675
|
-
};
|
|
670
|
+
});
|
|
676
671
|
try {
|
|
677
672
|
// 1. Run all *pre-navigation* guards.
|
|
678
673
|
const canNavigate = await this._runGuards(to, from, toMatch.route);
|
|
@@ -903,7 +898,10 @@ class Router {
|
|
|
903
898
|
* @returns {Function} A getter function.
|
|
904
899
|
*/
|
|
905
900
|
_createRouteGetter(property, defaultValue) {
|
|
906
|
-
return () =>
|
|
901
|
+
return () => {
|
|
902
|
+
var _this$currentRoute$va, _this$currentRoute$va2;
|
|
903
|
+
return (_this$currentRoute$va = (_this$currentRoute$va2 = this.currentRoute.value) == null ? void 0 : _this$currentRoute$va2[property]) != null ? _this$currentRoute$va : defaultValue;
|
|
904
|
+
};
|
|
907
905
|
}
|
|
908
906
|
|
|
909
907
|
/**
|
|
@@ -915,8 +913,7 @@ class Router {
|
|
|
915
913
|
_wrapComponent(component) {
|
|
916
914
|
const originalSetup = component.setup;
|
|
917
915
|
const self = this;
|
|
918
|
-
return {
|
|
919
|
-
...component,
|
|
916
|
+
return _extends({}, component, {
|
|
920
917
|
async setup(ctx) {
|
|
921
918
|
ctx.router = {
|
|
922
919
|
navigate: self.navigate.bind(self),
|
|
@@ -941,7 +938,7 @@ class Router {
|
|
|
941
938
|
};
|
|
942
939
|
return originalSetup ? await originalSetup(ctx) : {};
|
|
943
940
|
}
|
|
944
|
-
};
|
|
941
|
+
});
|
|
945
942
|
}
|
|
946
943
|
|
|
947
944
|
/**
|
|
@@ -1323,6 +1320,67 @@ const RouterPlugin = {
|
|
|
1323
1320
|
}
|
|
1324
1321
|
};
|
|
1325
1322
|
|
|
1323
|
+
/**
|
|
1324
|
+
* @class 🔒 TemplateEngine
|
|
1325
|
+
* @classdesc A secure template engine that handles interpolation and dynamic attribute parsing.
|
|
1326
|
+
* Provides a safe way to evaluate expressions in templates while preventing XSS attacks.
|
|
1327
|
+
* All methods are static and can be called directly on the class.
|
|
1328
|
+
*
|
|
1329
|
+
* @example
|
|
1330
|
+
* const template = "Hello, {{name}}!";
|
|
1331
|
+
* const data = { name: "World" };
|
|
1332
|
+
* const result = TemplateEngine.parse(template, data); // Returns: "Hello, World!"
|
|
1333
|
+
*/
|
|
1334
|
+
class TemplateEngine {
|
|
1335
|
+
/**
|
|
1336
|
+
* Parses a template string, replacing expressions with their evaluated values.
|
|
1337
|
+
* Expressions are evaluated in the provided data context.
|
|
1338
|
+
*
|
|
1339
|
+
* @public
|
|
1340
|
+
* @static
|
|
1341
|
+
* @param {string} template - The template string to parse.
|
|
1342
|
+
* @param {Record<string, unknown>} data - The data context for evaluating expressions.
|
|
1343
|
+
* @returns {string} The parsed template with expressions replaced by their values.
|
|
1344
|
+
* @example
|
|
1345
|
+
* const result = TemplateEngine.parse("{{user.name}} is {{user.age}} years old", {
|
|
1346
|
+
* user: { name: "John", age: 30 }
|
|
1347
|
+
* }); // Returns: "John is 30 years old"
|
|
1348
|
+
*/
|
|
1349
|
+
static parse(template, data) {
|
|
1350
|
+
if (typeof template !== "string") return template;
|
|
1351
|
+
return template.replace(this.expressionPattern, (_, expression) => this.evaluate(expression, data));
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* Evaluates an expression in the context of the provided data object.
|
|
1356
|
+
* Note: This does not provide a true sandbox and evaluated expressions may access global scope.
|
|
1357
|
+
* The use of the `with` statement is necessary for expression evaluation but has security implications.
|
|
1358
|
+
* Expressions should be carefully validated before evaluation.
|
|
1359
|
+
*
|
|
1360
|
+
* @public
|
|
1361
|
+
* @static
|
|
1362
|
+
* @param {string} expression - The expression to evaluate.
|
|
1363
|
+
* @param {Record<string, unknown>} data - The data context for evaluation.
|
|
1364
|
+
* @returns {unknown} The result of the evaluation, or an empty string if evaluation fails.
|
|
1365
|
+
* @example
|
|
1366
|
+
* const result = TemplateEngine.evaluate("user.name", { user: { name: "John" } }); // Returns: "John"
|
|
1367
|
+
* const age = TemplateEngine.evaluate("user.age", { user: { age: 30 } }); // Returns: 30
|
|
1368
|
+
*/
|
|
1369
|
+
static evaluate(expression, data) {
|
|
1370
|
+
if (typeof expression !== "string") return expression;
|
|
1371
|
+
try {
|
|
1372
|
+
return new Function("data", `with(data) { return ${expression}; }`)(data);
|
|
1373
|
+
} catch (_unused) {
|
|
1374
|
+
return "";
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* @private {RegExp} Regular expression for matching template expressions in the format {{ expression }}
|
|
1380
|
+
* @type {RegExp}
|
|
1381
|
+
*/
|
|
1382
|
+
TemplateEngine.expressionPattern = /\{\{\s*(.*?)\s*\}\}/g;
|
|
1383
|
+
|
|
1326
1384
|
/**
|
|
1327
1385
|
* @class 🎯 PropsPlugin
|
|
1328
1386
|
* @classdesc A plugin that extends Eleva's props data handling to support any type of data structure
|
|
@@ -1381,7 +1439,7 @@ const PropsPlugin = {
|
|
|
1381
1439
|
* Plugin version
|
|
1382
1440
|
* @type {string}
|
|
1383
1441
|
*/
|
|
1384
|
-
version: "1.0.0-rc.
|
|
1442
|
+
version: "1.0.0-rc.2",
|
|
1385
1443
|
/**
|
|
1386
1444
|
* Plugin description
|
|
1387
1445
|
* @type {string}
|
|
@@ -1611,6 +1669,161 @@ const PropsPlugin = {
|
|
|
1611
1669
|
return await originalMount.call(eleva, container, compName, enhancedProps);
|
|
1612
1670
|
};
|
|
1613
1671
|
|
|
1672
|
+
// Override Eleva's _mountComponents method to enable signal reference passing
|
|
1673
|
+
const originalMountComponents = eleva._mountComponents;
|
|
1674
|
+
|
|
1675
|
+
// Cache to store parent contexts by container element
|
|
1676
|
+
const parentContextCache = new WeakMap();
|
|
1677
|
+
// Store child instances that need signal linking
|
|
1678
|
+
const pendingSignalLinks = new Set();
|
|
1679
|
+
eleva._mountComponents = async (container, children, childInstances) => {
|
|
1680
|
+
for (const [selector, component] of Object.entries(children)) {
|
|
1681
|
+
if (!selector) continue;
|
|
1682
|
+
for (const el of container.querySelectorAll(selector)) {
|
|
1683
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
1684
|
+
|
|
1685
|
+
// Extract props from DOM attributes
|
|
1686
|
+
const extractedProps = eleva._extractProps(el);
|
|
1687
|
+
|
|
1688
|
+
// Get parent context to check for signal references
|
|
1689
|
+
let enhancedProps = extractedProps;
|
|
1690
|
+
|
|
1691
|
+
// Try to find parent context by looking up the DOM tree
|
|
1692
|
+
let parentContext = parentContextCache.get(container);
|
|
1693
|
+
if (!parentContext) {
|
|
1694
|
+
let currentElement = container;
|
|
1695
|
+
while (currentElement && !parentContext) {
|
|
1696
|
+
if (currentElement._eleva_instance && currentElement._eleva_instance.data) {
|
|
1697
|
+
parentContext = currentElement._eleva_instance.data;
|
|
1698
|
+
// Cache the parent context for future use
|
|
1699
|
+
parentContextCache.set(container, parentContext);
|
|
1700
|
+
break;
|
|
1701
|
+
}
|
|
1702
|
+
currentElement = currentElement.parentElement;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
if (enableReactivity && parentContext) {
|
|
1706
|
+
const signalProps = {};
|
|
1707
|
+
|
|
1708
|
+
// Check each extracted prop to see if there's a matching signal in parent context
|
|
1709
|
+
Object.keys(extractedProps).forEach(propName => {
|
|
1710
|
+
if (parentContext[propName] && parentContext[propName] instanceof eleva.signal) {
|
|
1711
|
+
// Found a signal in parent context with the same name as the prop
|
|
1712
|
+
// Pass the signal reference instead of creating a new one
|
|
1713
|
+
signalProps[propName] = parentContext[propName];
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
// Merge signal props with regular props (signal props take precedence)
|
|
1718
|
+
enhancedProps = _extends({}, extractedProps, signalProps);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// Create reactive props for non-signal props only
|
|
1722
|
+
let finalProps = enhancedProps;
|
|
1723
|
+
if (enableReactivity) {
|
|
1724
|
+
// Only create reactive props for values that aren't already signals
|
|
1725
|
+
const nonSignalProps = {};
|
|
1726
|
+
Object.entries(enhancedProps).forEach(([key, value]) => {
|
|
1727
|
+
if (!(value && typeof value === "object" && "value" in value && "watch" in value)) {
|
|
1728
|
+
// This is not a signal, create a reactive prop for it
|
|
1729
|
+
nonSignalProps[key] = value;
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
// Create reactive props only for non-signal values
|
|
1734
|
+
const reactiveNonSignalProps = createReactiveProps(nonSignalProps);
|
|
1735
|
+
|
|
1736
|
+
// Merge signal props with reactive non-signal props
|
|
1737
|
+
finalProps = _extends({}, reactiveNonSignalProps, enhancedProps);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
/** @type {MountResult} */
|
|
1741
|
+
const instance = await eleva.mount(el, component, finalProps);
|
|
1742
|
+
if (instance && !childInstances.includes(instance)) {
|
|
1743
|
+
childInstances.push(instance);
|
|
1744
|
+
|
|
1745
|
+
// If we have extracted props but no parent context yet, mark for later signal linking
|
|
1746
|
+
if (enableReactivity && Object.keys(extractedProps).length > 0 && !parentContext) {
|
|
1747
|
+
pendingSignalLinks.add({
|
|
1748
|
+
instance,
|
|
1749
|
+
extractedProps,
|
|
1750
|
+
container,
|
|
1751
|
+
component
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// After mounting all children, try to link signals for pending instances
|
|
1759
|
+
if (enableReactivity && pendingSignalLinks.size > 0) {
|
|
1760
|
+
for (const pending of pendingSignalLinks) {
|
|
1761
|
+
const {
|
|
1762
|
+
instance,
|
|
1763
|
+
extractedProps,
|
|
1764
|
+
container,
|
|
1765
|
+
component
|
|
1766
|
+
} = pending;
|
|
1767
|
+
|
|
1768
|
+
// Try to find parent context again
|
|
1769
|
+
let parentContext = parentContextCache.get(container);
|
|
1770
|
+
if (!parentContext) {
|
|
1771
|
+
let currentElement = container;
|
|
1772
|
+
while (currentElement && !parentContext) {
|
|
1773
|
+
if (currentElement._eleva_instance && currentElement._eleva_instance.data) {
|
|
1774
|
+
parentContext = currentElement._eleva_instance.data;
|
|
1775
|
+
parentContextCache.set(container, parentContext);
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
currentElement = currentElement.parentElement;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
if (parentContext) {
|
|
1782
|
+
const signalProps = {};
|
|
1783
|
+
|
|
1784
|
+
// Check each extracted prop to see if there's a matching signal in parent context
|
|
1785
|
+
Object.keys(extractedProps).forEach(propName => {
|
|
1786
|
+
if (parentContext[propName] && parentContext[propName] instanceof eleva.signal) {
|
|
1787
|
+
signalProps[propName] = parentContext[propName];
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
|
|
1791
|
+
// Update the child instance's data with signal references
|
|
1792
|
+
if (Object.keys(signalProps).length > 0) {
|
|
1793
|
+
Object.assign(instance.data, signalProps);
|
|
1794
|
+
|
|
1795
|
+
// Set up signal watchers for the newly linked signals
|
|
1796
|
+
Object.keys(signalProps).forEach(propName => {
|
|
1797
|
+
const signal = signalProps[propName];
|
|
1798
|
+
if (signal && typeof signal.watch === "function") {
|
|
1799
|
+
signal.watch(newValue => {
|
|
1800
|
+
// Trigger a re-render of the child component when the signal changes
|
|
1801
|
+
const childComponent = eleva._components.get(component) || component;
|
|
1802
|
+
if (childComponent && childComponent.template) {
|
|
1803
|
+
const templateResult = typeof childComponent.template === "function" ? childComponent.template(instance.data) : childComponent.template;
|
|
1804
|
+
const newHtml = TemplateEngine.parse(templateResult, instance.data);
|
|
1805
|
+
eleva.renderer.patchDOM(instance.container, newHtml);
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
|
|
1811
|
+
// Initial re-render to show the correct signal values
|
|
1812
|
+
const childComponent = eleva._components.get(component) || component;
|
|
1813
|
+
if (childComponent && childComponent.template) {
|
|
1814
|
+
const templateResult = typeof childComponent.template === "function" ? childComponent.template(instance.data) : childComponent.template;
|
|
1815
|
+
const newHtml = TemplateEngine.parse(templateResult, instance.data);
|
|
1816
|
+
eleva.renderer.patchDOM(instance.container, newHtml);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// Remove from pending list
|
|
1821
|
+
pendingSignalLinks.delete(pending);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1614
1827
|
/**
|
|
1615
1828
|
* Expose utility methods on the Eleva instance
|
|
1616
1829
|
* @namespace eleva.props
|
|
@@ -1650,6 +1863,7 @@ const PropsPlugin = {
|
|
|
1650
1863
|
// Store original methods for uninstall
|
|
1651
1864
|
eleva._originalExtractProps = eleva._extractProps;
|
|
1652
1865
|
eleva._originalMount = originalMount;
|
|
1866
|
+
eleva._originalMountComponents = originalMountComponents;
|
|
1653
1867
|
},
|
|
1654
1868
|
/**
|
|
1655
1869
|
* Uninstalls the plugin from the Eleva instance
|
|
@@ -1678,6 +1892,12 @@ const PropsPlugin = {
|
|
|
1678
1892
|
delete eleva._originalMount;
|
|
1679
1893
|
}
|
|
1680
1894
|
|
|
1895
|
+
// Restore original _mountComponents method
|
|
1896
|
+
if (eleva._originalMountComponents) {
|
|
1897
|
+
eleva._mountComponents = eleva._originalMountComponents;
|
|
1898
|
+
delete eleva._originalMountComponents;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1681
1901
|
// Remove plugin utility methods
|
|
1682
1902
|
if (eleva.props) {
|
|
1683
1903
|
delete eleva.props;
|
|
@@ -1685,5 +1905,616 @@ const PropsPlugin = {
|
|
|
1685
1905
|
}
|
|
1686
1906
|
};
|
|
1687
1907
|
|
|
1688
|
-
|
|
1908
|
+
const StorePlugin = {
|
|
1909
|
+
/**
|
|
1910
|
+
* Unique identifier for the plugin
|
|
1911
|
+
* @type {string}
|
|
1912
|
+
*/
|
|
1913
|
+
name: "store",
|
|
1914
|
+
/**
|
|
1915
|
+
* Plugin version
|
|
1916
|
+
* @type {string}
|
|
1917
|
+
*/
|
|
1918
|
+
version: "1.0.0-rc.1",
|
|
1919
|
+
/**
|
|
1920
|
+
* Plugin description
|
|
1921
|
+
* @type {string}
|
|
1922
|
+
*/
|
|
1923
|
+
description: "Reactive state management for sharing data across the entire Eleva application",
|
|
1924
|
+
/**
|
|
1925
|
+
* Installs the plugin into the Eleva instance
|
|
1926
|
+
*
|
|
1927
|
+
* @param {Object} eleva - The Eleva instance
|
|
1928
|
+
* @param {Object} options - Plugin configuration options
|
|
1929
|
+
* @param {Object} [options.state={}] - Initial state object
|
|
1930
|
+
* @param {Object} [options.actions={}] - Action functions for state mutations
|
|
1931
|
+
* @param {Object} [options.namespaces={}] - Namespaced modules for organizing store
|
|
1932
|
+
* @param {Object} [options.persistence] - Persistence configuration
|
|
1933
|
+
* @param {boolean} [options.persistence.enabled=false] - Enable state persistence
|
|
1934
|
+
* @param {string} [options.persistence.key="eleva-store"] - Storage key
|
|
1935
|
+
* @param {"localStorage" | "sessionStorage"} [options.persistence.storage="localStorage"] - Storage type
|
|
1936
|
+
* @param {Array<string>} [options.persistence.include] - State keys to persist (if not provided, all state is persisted)
|
|
1937
|
+
* @param {Array<string>} [options.persistence.exclude] - State keys to exclude from persistence
|
|
1938
|
+
* @param {boolean} [options.devTools=false] - Enable development tools integration
|
|
1939
|
+
* @param {Function} [options.onError=null] - Error handler function
|
|
1940
|
+
*
|
|
1941
|
+
* @example
|
|
1942
|
+
* // Basic installation
|
|
1943
|
+
* app.use(StorePlugin, {
|
|
1944
|
+
* state: { count: 0, user: null },
|
|
1945
|
+
* actions: {
|
|
1946
|
+
* increment: (state) => state.count.value++,
|
|
1947
|
+
* setUser: (state, user) => state.user.value = user
|
|
1948
|
+
* }
|
|
1949
|
+
* });
|
|
1950
|
+
*
|
|
1951
|
+
* // Advanced installation with persistence and namespaces
|
|
1952
|
+
* app.use(StorePlugin, {
|
|
1953
|
+
* state: { theme: "light" },
|
|
1954
|
+
* namespaces: {
|
|
1955
|
+
* auth: {
|
|
1956
|
+
* state: { user: null, token: null },
|
|
1957
|
+
* actions: {
|
|
1958
|
+
* login: (state, { user, token }) => {
|
|
1959
|
+
* state.user.value = user;
|
|
1960
|
+
* state.token.value = token;
|
|
1961
|
+
* },
|
|
1962
|
+
* logout: (state) => {
|
|
1963
|
+
* state.user.value = null;
|
|
1964
|
+
* state.token.value = null;
|
|
1965
|
+
* }
|
|
1966
|
+
* }
|
|
1967
|
+
* }
|
|
1968
|
+
* },
|
|
1969
|
+
* persistence: {
|
|
1970
|
+
* enabled: true,
|
|
1971
|
+
* include: ["theme", "auth.user"]
|
|
1972
|
+
* }
|
|
1973
|
+
* });
|
|
1974
|
+
*/
|
|
1975
|
+
install(eleva, options = {}) {
|
|
1976
|
+
const {
|
|
1977
|
+
state = {},
|
|
1978
|
+
actions = {},
|
|
1979
|
+
namespaces = {},
|
|
1980
|
+
persistence = {},
|
|
1981
|
+
devTools = false,
|
|
1982
|
+
onError = null
|
|
1983
|
+
} = options;
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Store instance that manages all state and provides the API
|
|
1987
|
+
* @private
|
|
1988
|
+
*/
|
|
1989
|
+
class Store {
|
|
1990
|
+
constructor() {
|
|
1991
|
+
this.state = {};
|
|
1992
|
+
this.actions = {};
|
|
1993
|
+
this.subscribers = new Set();
|
|
1994
|
+
this.mutations = [];
|
|
1995
|
+
this.persistence = _extends({
|
|
1996
|
+
enabled: false,
|
|
1997
|
+
key: "eleva-store",
|
|
1998
|
+
storage: "localStorage",
|
|
1999
|
+
include: null,
|
|
2000
|
+
exclude: null
|
|
2001
|
+
}, persistence);
|
|
2002
|
+
this.devTools = devTools;
|
|
2003
|
+
this.onError = onError;
|
|
2004
|
+
this._initializeState(state, actions);
|
|
2005
|
+
this._initializeNamespaces(namespaces);
|
|
2006
|
+
this._loadPersistedState();
|
|
2007
|
+
this._setupDevTools();
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* Initializes the root state and actions
|
|
2012
|
+
* @private
|
|
2013
|
+
*/
|
|
2014
|
+
_initializeState(initialState, initialActions) {
|
|
2015
|
+
// Create reactive signals for each state property
|
|
2016
|
+
Object.entries(initialState).forEach(([key, value]) => {
|
|
2017
|
+
this.state[key] = new eleva.signal(value);
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
// Set up actions
|
|
2021
|
+
this.actions = _extends({}, initialActions);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
/**
|
|
2025
|
+
* Initializes namespaced modules
|
|
2026
|
+
* @private
|
|
2027
|
+
*/
|
|
2028
|
+
_initializeNamespaces(namespaces) {
|
|
2029
|
+
Object.entries(namespaces).forEach(([namespace, module]) => {
|
|
2030
|
+
const {
|
|
2031
|
+
state: moduleState = {},
|
|
2032
|
+
actions: moduleActions = {}
|
|
2033
|
+
} = module;
|
|
2034
|
+
|
|
2035
|
+
// Create namespace object if it doesn't exist
|
|
2036
|
+
if (!this.state[namespace]) {
|
|
2037
|
+
this.state[namespace] = {};
|
|
2038
|
+
}
|
|
2039
|
+
if (!this.actions[namespace]) {
|
|
2040
|
+
this.actions[namespace] = {};
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
// Initialize namespaced state
|
|
2044
|
+
Object.entries(moduleState).forEach(([key, value]) => {
|
|
2045
|
+
this.state[namespace][key] = new eleva.signal(value);
|
|
2046
|
+
});
|
|
2047
|
+
|
|
2048
|
+
// Set up namespaced actions
|
|
2049
|
+
this.actions[namespace] = _extends({}, moduleActions);
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
/**
|
|
2054
|
+
* Loads persisted state from storage
|
|
2055
|
+
* @private
|
|
2056
|
+
*/
|
|
2057
|
+
_loadPersistedState() {
|
|
2058
|
+
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
try {
|
|
2062
|
+
const storage = window[this.persistence.storage];
|
|
2063
|
+
const persistedData = storage.getItem(this.persistence.key);
|
|
2064
|
+
if (persistedData) {
|
|
2065
|
+
const data = JSON.parse(persistedData);
|
|
2066
|
+
this._applyPersistedData(data);
|
|
2067
|
+
}
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
if (this.onError) {
|
|
2070
|
+
this.onError(error, "Failed to load persisted state");
|
|
2071
|
+
} else {
|
|
2072
|
+
console.warn("[StorePlugin] Failed to load persisted state:", error);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
/**
|
|
2078
|
+
* Applies persisted data to the current state
|
|
2079
|
+
* @private
|
|
2080
|
+
*/
|
|
2081
|
+
_applyPersistedData(data, currentState = this.state, path = "") {
|
|
2082
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
2083
|
+
const fullPath = path ? `${path}.${key}` : key;
|
|
2084
|
+
if (this._shouldPersist(fullPath)) {
|
|
2085
|
+
if (currentState[key] && typeof currentState[key] === "object" && "value" in currentState[key]) {
|
|
2086
|
+
// This is a signal, update its value
|
|
2087
|
+
currentState[key].value = value;
|
|
2088
|
+
} else if (typeof value === "object" && value !== null && currentState[key]) {
|
|
2089
|
+
// This is a nested object, recurse
|
|
2090
|
+
this._applyPersistedData(value, currentState[key], fullPath);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
/**
|
|
2097
|
+
* Determines if a state path should be persisted
|
|
2098
|
+
* @private
|
|
2099
|
+
*/
|
|
2100
|
+
_shouldPersist(path) {
|
|
2101
|
+
const {
|
|
2102
|
+
include,
|
|
2103
|
+
exclude
|
|
2104
|
+
} = this.persistence;
|
|
2105
|
+
if (include && include.length > 0) {
|
|
2106
|
+
return include.some(includePath => path.startsWith(includePath));
|
|
2107
|
+
}
|
|
2108
|
+
if (exclude && exclude.length > 0) {
|
|
2109
|
+
return !exclude.some(excludePath => path.startsWith(excludePath));
|
|
2110
|
+
}
|
|
2111
|
+
return true;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Saves current state to storage
|
|
2116
|
+
* @private
|
|
2117
|
+
*/
|
|
2118
|
+
_saveState() {
|
|
2119
|
+
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
const storage = window[this.persistence.storage];
|
|
2124
|
+
const dataToSave = this._extractPersistedData();
|
|
2125
|
+
storage.setItem(this.persistence.key, JSON.stringify(dataToSave));
|
|
2126
|
+
} catch (error) {
|
|
2127
|
+
if (this.onError) {
|
|
2128
|
+
this.onError(error, "Failed to save state");
|
|
2129
|
+
} else {
|
|
2130
|
+
console.warn("[StorePlugin] Failed to save state:", error);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
/**
|
|
2136
|
+
* Extracts data that should be persisted
|
|
2137
|
+
* @private
|
|
2138
|
+
*/
|
|
2139
|
+
_extractPersistedData(currentState = this.state, path = "") {
|
|
2140
|
+
const result = {};
|
|
2141
|
+
Object.entries(currentState).forEach(([key, value]) => {
|
|
2142
|
+
const fullPath = path ? `${path}.${key}` : key;
|
|
2143
|
+
if (this._shouldPersist(fullPath)) {
|
|
2144
|
+
if (value && typeof value === "object" && "value" in value) {
|
|
2145
|
+
// This is a signal, extract its value
|
|
2146
|
+
result[key] = value.value;
|
|
2147
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2148
|
+
// This is a nested object, recurse
|
|
2149
|
+
const nestedData = this._extractPersistedData(value, fullPath);
|
|
2150
|
+
if (Object.keys(nestedData).length > 0) {
|
|
2151
|
+
result[key] = nestedData;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
return result;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
/**
|
|
2160
|
+
* Sets up development tools integration
|
|
2161
|
+
* @private
|
|
2162
|
+
*/
|
|
2163
|
+
_setupDevTools() {
|
|
2164
|
+
if (!this.devTools || typeof window === "undefined" || !window.__ELEVA_DEVTOOLS__) {
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
window.__ELEVA_DEVTOOLS__.registerStore(this);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Dispatches an action to mutate the state
|
|
2172
|
+
* @param {string} actionName - The name of the action to dispatch (supports namespaced actions like "auth.login")
|
|
2173
|
+
* @param {any} payload - The payload to pass to the action
|
|
2174
|
+
* @returns {Promise<any>} The result of the action
|
|
2175
|
+
*/
|
|
2176
|
+
async dispatch(actionName, payload) {
|
|
2177
|
+
try {
|
|
2178
|
+
const action = this._getAction(actionName);
|
|
2179
|
+
if (!action) {
|
|
2180
|
+
const error = new Error(`Action "${actionName}" not found`);
|
|
2181
|
+
if (this.onError) {
|
|
2182
|
+
this.onError(error, actionName);
|
|
2183
|
+
}
|
|
2184
|
+
throw error;
|
|
2185
|
+
}
|
|
2186
|
+
const mutation = {
|
|
2187
|
+
type: actionName,
|
|
2188
|
+
payload,
|
|
2189
|
+
timestamp: Date.now()
|
|
2190
|
+
};
|
|
2191
|
+
|
|
2192
|
+
// Record mutation for devtools
|
|
2193
|
+
this.mutations.push(mutation);
|
|
2194
|
+
if (this.mutations.length > 100) {
|
|
2195
|
+
this.mutations.shift(); // Keep only last 100 mutations
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// Execute the action
|
|
2199
|
+
const result = await action.call(null, this.state, payload);
|
|
2200
|
+
|
|
2201
|
+
// Save state if persistence is enabled
|
|
2202
|
+
this._saveState();
|
|
2203
|
+
|
|
2204
|
+
// Notify subscribers
|
|
2205
|
+
this.subscribers.forEach(callback => {
|
|
2206
|
+
try {
|
|
2207
|
+
callback(mutation, this.state);
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
if (this.onError) {
|
|
2210
|
+
this.onError(error, "Subscriber callback failed");
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
// Notify devtools
|
|
2216
|
+
if (this.devTools && typeof window !== "undefined" && window.__ELEVA_DEVTOOLS__) {
|
|
2217
|
+
window.__ELEVA_DEVTOOLS__.notifyMutation(mutation, this.state);
|
|
2218
|
+
}
|
|
2219
|
+
return result;
|
|
2220
|
+
} catch (error) {
|
|
2221
|
+
if (this.onError) {
|
|
2222
|
+
this.onError(error, `Action dispatch failed: ${actionName}`);
|
|
2223
|
+
}
|
|
2224
|
+
throw error;
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
/**
|
|
2229
|
+
* Gets an action by name (supports namespaced actions)
|
|
2230
|
+
* @private
|
|
2231
|
+
*/
|
|
2232
|
+
_getAction(actionName) {
|
|
2233
|
+
const parts = actionName.split(".");
|
|
2234
|
+
let current = this.actions;
|
|
2235
|
+
for (const part of parts) {
|
|
2236
|
+
if (current[part] === undefined) {
|
|
2237
|
+
return null;
|
|
2238
|
+
}
|
|
2239
|
+
current = current[part];
|
|
2240
|
+
}
|
|
2241
|
+
return typeof current === "function" ? current : null;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* Subscribes to store mutations
|
|
2246
|
+
* @param {Function} callback - Callback function to call on mutations
|
|
2247
|
+
* @returns {Function} Unsubscribe function
|
|
2248
|
+
*/
|
|
2249
|
+
subscribe(callback) {
|
|
2250
|
+
if (typeof callback !== "function") {
|
|
2251
|
+
throw new Error("Subscribe callback must be a function");
|
|
2252
|
+
}
|
|
2253
|
+
this.subscribers.add(callback);
|
|
2254
|
+
|
|
2255
|
+
// Return unsubscribe function
|
|
2256
|
+
return () => {
|
|
2257
|
+
this.subscribers.delete(callback);
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/**
|
|
2262
|
+
* Gets a deep copy of the current state values (not signals)
|
|
2263
|
+
* @returns {Object} The current state values
|
|
2264
|
+
*/
|
|
2265
|
+
getState() {
|
|
2266
|
+
return this._extractPersistedData();
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
/**
|
|
2270
|
+
* Replaces the entire state (useful for testing or state hydration)
|
|
2271
|
+
* @param {Object} newState - The new state object
|
|
2272
|
+
*/
|
|
2273
|
+
replaceState(newState) {
|
|
2274
|
+
this._applyPersistedData(newState);
|
|
2275
|
+
this._saveState();
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
/**
|
|
2279
|
+
* Clears persisted state from storage
|
|
2280
|
+
*/
|
|
2281
|
+
clearPersistedState() {
|
|
2282
|
+
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
try {
|
|
2286
|
+
const storage = window[this.persistence.storage];
|
|
2287
|
+
storage.removeItem(this.persistence.key);
|
|
2288
|
+
} catch (error) {
|
|
2289
|
+
if (this.onError) {
|
|
2290
|
+
this.onError(error, "Failed to clear persisted state");
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
/**
|
|
2296
|
+
* Registers a new namespaced module at runtime
|
|
2297
|
+
* @param {string} namespace - The namespace for the module
|
|
2298
|
+
* @param {Object} module - The module definition
|
|
2299
|
+
* @param {Object} module.state - The module's initial state
|
|
2300
|
+
* @param {Object} module.actions - The module's actions
|
|
2301
|
+
*/
|
|
2302
|
+
registerModule(namespace, module) {
|
|
2303
|
+
if (this.state[namespace] || this.actions[namespace]) {
|
|
2304
|
+
console.warn(`[StorePlugin] Module "${namespace}" already exists`);
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// Initialize the module
|
|
2309
|
+
this.state[namespace] = {};
|
|
2310
|
+
this.actions[namespace] = {};
|
|
2311
|
+
const namespaces = {
|
|
2312
|
+
[namespace]: module
|
|
2313
|
+
};
|
|
2314
|
+
this._initializeNamespaces(namespaces);
|
|
2315
|
+
this._saveState();
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Unregisters a namespaced module
|
|
2320
|
+
* @param {string} namespace - The namespace to unregister
|
|
2321
|
+
*/
|
|
2322
|
+
unregisterModule(namespace) {
|
|
2323
|
+
if (!this.state[namespace] && !this.actions[namespace]) {
|
|
2324
|
+
console.warn(`[StorePlugin] Module "${namespace}" does not exist`);
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
delete this.state[namespace];
|
|
2328
|
+
delete this.actions[namespace];
|
|
2329
|
+
this._saveState();
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
/**
|
|
2333
|
+
* Creates a new reactive state property at runtime
|
|
2334
|
+
* @param {string} key - The state key
|
|
2335
|
+
* @param {*} initialValue - The initial value
|
|
2336
|
+
* @returns {Object} The created signal
|
|
2337
|
+
*/
|
|
2338
|
+
createState(key, initialValue) {
|
|
2339
|
+
if (this.state[key]) {
|
|
2340
|
+
return this.state[key]; // Return existing state
|
|
2341
|
+
}
|
|
2342
|
+
this.state[key] = new eleva.signal(initialValue);
|
|
2343
|
+
this._saveState();
|
|
2344
|
+
return this.state[key];
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* Creates a new action at runtime
|
|
2349
|
+
* @param {string} name - The action name
|
|
2350
|
+
* @param {Function} actionFn - The action function
|
|
2351
|
+
*/
|
|
2352
|
+
createAction(name, actionFn) {
|
|
2353
|
+
if (typeof actionFn !== "function") {
|
|
2354
|
+
throw new Error("Action must be a function");
|
|
2355
|
+
}
|
|
2356
|
+
this.actions[name] = actionFn;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// Create the store instance
|
|
2361
|
+
const store = new Store();
|
|
2362
|
+
|
|
2363
|
+
// Store the original mount method to override it
|
|
2364
|
+
const originalMount = eleva.mount;
|
|
2365
|
+
|
|
2366
|
+
/**
|
|
2367
|
+
* Override the mount method to inject store context into components
|
|
2368
|
+
*/
|
|
2369
|
+
eleva.mount = async (container, compName, props = {}) => {
|
|
2370
|
+
// Get the component definition
|
|
2371
|
+
const componentDef = typeof compName === "string" ? eleva._components.get(compName) || compName : compName;
|
|
2372
|
+
if (!componentDef) {
|
|
2373
|
+
return await originalMount.call(eleva, container, compName, props);
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// Create a wrapped component that injects store into setup
|
|
2377
|
+
const wrappedComponent = _extends({}, componentDef, {
|
|
2378
|
+
async setup(ctx) {
|
|
2379
|
+
// Inject store into the context with enhanced API
|
|
2380
|
+
ctx.store = {
|
|
2381
|
+
// Core store functionality
|
|
2382
|
+
state: store.state,
|
|
2383
|
+
dispatch: store.dispatch.bind(store),
|
|
2384
|
+
subscribe: store.subscribe.bind(store),
|
|
2385
|
+
getState: store.getState.bind(store),
|
|
2386
|
+
// Module management
|
|
2387
|
+
registerModule: store.registerModule.bind(store),
|
|
2388
|
+
unregisterModule: store.unregisterModule.bind(store),
|
|
2389
|
+
// Utilities for dynamic state/action creation
|
|
2390
|
+
createState: store.createState.bind(store),
|
|
2391
|
+
createAction: store.createAction.bind(store),
|
|
2392
|
+
// Access to signal constructor for manual state creation
|
|
2393
|
+
signal: eleva.signal
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
// Call original setup if it exists
|
|
2397
|
+
const originalSetup = componentDef.setup;
|
|
2398
|
+
const result = originalSetup ? await originalSetup(ctx) : {};
|
|
2399
|
+
return result;
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
// Call original mount with wrapped component
|
|
2404
|
+
return await originalMount.call(eleva, container, wrappedComponent, props);
|
|
2405
|
+
};
|
|
2406
|
+
|
|
2407
|
+
// Override _mountComponents to ensure child components also get store context
|
|
2408
|
+
const originalMountComponents = eleva._mountComponents;
|
|
2409
|
+
eleva._mountComponents = async (container, children, childInstances) => {
|
|
2410
|
+
// Create wrapped children with store injection
|
|
2411
|
+
const wrappedChildren = {};
|
|
2412
|
+
for (const [selector, childComponent] of Object.entries(children)) {
|
|
2413
|
+
const componentDef = typeof childComponent === "string" ? eleva._components.get(childComponent) || childComponent : childComponent;
|
|
2414
|
+
if (componentDef && typeof componentDef === "object") {
|
|
2415
|
+
wrappedChildren[selector] = _extends({}, componentDef, {
|
|
2416
|
+
async setup(ctx) {
|
|
2417
|
+
// Inject store into the context with enhanced API
|
|
2418
|
+
ctx.store = {
|
|
2419
|
+
// Core store functionality
|
|
2420
|
+
state: store.state,
|
|
2421
|
+
dispatch: store.dispatch.bind(store),
|
|
2422
|
+
subscribe: store.subscribe.bind(store),
|
|
2423
|
+
getState: store.getState.bind(store),
|
|
2424
|
+
// Module management
|
|
2425
|
+
registerModule: store.registerModule.bind(store),
|
|
2426
|
+
unregisterModule: store.unregisterModule.bind(store),
|
|
2427
|
+
// Utilities for dynamic state/action creation
|
|
2428
|
+
createState: store.createState.bind(store),
|
|
2429
|
+
createAction: store.createAction.bind(store),
|
|
2430
|
+
// Access to signal constructor for manual state creation
|
|
2431
|
+
signal: eleva.signal
|
|
2432
|
+
};
|
|
2433
|
+
|
|
2434
|
+
// Call original setup if it exists
|
|
2435
|
+
const originalSetup = componentDef.setup;
|
|
2436
|
+
const result = originalSetup ? await originalSetup(ctx) : {};
|
|
2437
|
+
return result;
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2440
|
+
} else {
|
|
2441
|
+
wrappedChildren[selector] = childComponent;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// Call original _mountComponents with wrapped children
|
|
2446
|
+
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances);
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
// Expose store instance and utilities on the Eleva instance
|
|
2450
|
+
eleva.store = store;
|
|
2451
|
+
|
|
2452
|
+
/**
|
|
2453
|
+
* Expose utility methods on the Eleva instance
|
|
2454
|
+
* @namespace eleva.store
|
|
2455
|
+
*/
|
|
2456
|
+
eleva.createAction = (name, actionFn) => {
|
|
2457
|
+
store.actions[name] = actionFn;
|
|
2458
|
+
};
|
|
2459
|
+
eleva.dispatch = (actionName, payload) => {
|
|
2460
|
+
return store.dispatch(actionName, payload);
|
|
2461
|
+
};
|
|
2462
|
+
eleva.getState = () => {
|
|
2463
|
+
return store.getState();
|
|
2464
|
+
};
|
|
2465
|
+
eleva.subscribe = callback => {
|
|
2466
|
+
return store.subscribe(callback);
|
|
2467
|
+
};
|
|
2468
|
+
|
|
2469
|
+
// Store original methods for cleanup
|
|
2470
|
+
eleva._originalMount = originalMount;
|
|
2471
|
+
eleva._originalMountComponents = originalMountComponents;
|
|
2472
|
+
},
|
|
2473
|
+
/**
|
|
2474
|
+
* Uninstalls the plugin from the Eleva instance
|
|
2475
|
+
*
|
|
2476
|
+
* @param {Object} eleva - The Eleva instance
|
|
2477
|
+
*
|
|
2478
|
+
* @description
|
|
2479
|
+
* Restores the original Eleva methods and removes all plugin-specific
|
|
2480
|
+
* functionality. This method should be called when the plugin is no
|
|
2481
|
+
* longer needed.
|
|
2482
|
+
*
|
|
2483
|
+
* @example
|
|
2484
|
+
* // Uninstall the plugin
|
|
2485
|
+
* StorePlugin.uninstall(app);
|
|
2486
|
+
*/
|
|
2487
|
+
uninstall(eleva) {
|
|
2488
|
+
// Restore original mount method
|
|
2489
|
+
if (eleva._originalMount) {
|
|
2490
|
+
eleva.mount = eleva._originalMount;
|
|
2491
|
+
delete eleva._originalMount;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// Restore original _mountComponents method
|
|
2495
|
+
if (eleva._originalMountComponents) {
|
|
2496
|
+
eleva._mountComponents = eleva._originalMountComponents;
|
|
2497
|
+
delete eleva._originalMountComponents;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// Remove store instance and utility methods
|
|
2501
|
+
if (eleva.store) {
|
|
2502
|
+
delete eleva.store;
|
|
2503
|
+
}
|
|
2504
|
+
if (eleva.createAction) {
|
|
2505
|
+
delete eleva.createAction;
|
|
2506
|
+
}
|
|
2507
|
+
if (eleva.dispatch) {
|
|
2508
|
+
delete eleva.dispatch;
|
|
2509
|
+
}
|
|
2510
|
+
if (eleva.getState) {
|
|
2511
|
+
delete eleva.getState;
|
|
2512
|
+
}
|
|
2513
|
+
if (eleva.subscribe) {
|
|
2514
|
+
delete eleva.subscribe;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
|
|
2519
|
+
export { AttrPlugin as Attr, PropsPlugin as Props, RouterPlugin as Router, StorePlugin as Store };
|
|
1689
2520
|
//# sourceMappingURL=eleva-plugins.esm.js.map
|