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.
Files changed (41) hide show
  1. package/README.md +135 -6
  2. package/dist/eleva-plugins.cjs.js +861 -29
  3. package/dist/eleva-plugins.cjs.js.map +1 -1
  4. package/dist/eleva-plugins.esm.js +861 -30
  5. package/dist/eleva-plugins.esm.js.map +1 -1
  6. package/dist/eleva-plugins.umd.js +861 -29
  7. package/dist/eleva-plugins.umd.js.map +1 -1
  8. package/dist/eleva-plugins.umd.min.js +2 -2
  9. package/dist/eleva-plugins.umd.min.js.map +1 -1
  10. package/dist/eleva.cjs.js +31 -25
  11. package/dist/eleva.cjs.js.map +1 -1
  12. package/dist/eleva.esm.js +31 -25
  13. package/dist/eleva.esm.js.map +1 -1
  14. package/dist/eleva.umd.js +31 -25
  15. package/dist/eleva.umd.js.map +1 -1
  16. package/dist/eleva.umd.min.js +2 -2
  17. package/dist/eleva.umd.min.js.map +1 -1
  18. package/dist/plugins/attr.umd.js +2 -2
  19. package/dist/plugins/attr.umd.js.map +1 -1
  20. package/dist/plugins/attr.umd.min.js +1 -1
  21. package/dist/plugins/attr.umd.min.js.map +1 -1
  22. package/dist/plugins/props.umd.js +235 -2
  23. package/dist/plugins/props.umd.js.map +1 -1
  24. package/dist/plugins/props.umd.min.js +2 -2
  25. package/dist/plugins/props.umd.min.js.map +1 -1
  26. package/dist/plugins/router.umd.js +22 -25
  27. package/dist/plugins/router.umd.js.map +1 -1
  28. package/dist/plugins/router.umd.min.js +1 -1
  29. package/dist/plugins/router.umd.min.js.map +1 -1
  30. package/dist/plugins/store.umd.js +632 -0
  31. package/dist/plugins/store.umd.js.map +1 -0
  32. package/dist/plugins/store.umd.min.js +3 -0
  33. package/dist/plugins/store.umd.min.js.map +1 -0
  34. package/package.json +4 -4
  35. package/src/plugins/Props.js +206 -1
  36. package/src/plugins/Store.js +741 -0
  37. package/src/plugins/index.js +6 -1
  38. package/types/plugins/Props.d.ts.map +1 -1
  39. package/types/plugins/Store.d.ts +86 -0
  40. package/types/plugins/Store.d.ts.map +1 -0
  41. package/types/plugins/index.d.ts +1 -0
@@ -1,4 +1,4 @@
1
- /*! Eleva Plugins v1.0.0-rc.5 | MIT License | https://elevajs.com */
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?.get && typeof descriptor.get.call(oldEl) === "boolean";
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?._eleva_instance) return;
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
- * @typedef {import('eleva').Eleva} Eleva
224
- * @typedef {import('eleva').Signal} Signal
225
- * @typedef {import('eleva').ComponentDefinition} ComponentDefinition
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
- ...options
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 () => this.currentRoute.value?.[property] ?? defaultValue;
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.1",
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
- export { AttrPlugin as Attr, PropsPlugin as Props, RouterPlugin as Router };
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