eleva 1.0.0-rc.4 → 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.
@@ -1,4 +1,4 @@
1
- /*! Eleva Plugins v1.0.0-rc.4 | MIT License | https://elevajs.com */
1
+ /*! Eleva Plugins v1.0.0-rc.6 | MIT License | https://elevajs.com */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -1329,7 +1329,600 @@
1329
1329
  }
1330
1330
  };
1331
1331
 
1332
+ /**
1333
+ * @class 🔒 TemplateEngine
1334
+ * @classdesc A secure template engine that handles interpolation and dynamic attribute parsing.
1335
+ * Provides a safe way to evaluate expressions in templates while preventing XSS attacks.
1336
+ * All methods are static and can be called directly on the class.
1337
+ *
1338
+ * @example
1339
+ * const template = "Hello, {{name}}!";
1340
+ * const data = { name: "World" };
1341
+ * const result = TemplateEngine.parse(template, data); // Returns: "Hello, World!"
1342
+ */
1343
+ class TemplateEngine {
1344
+ /**
1345
+ * @private {RegExp} Regular expression for matching template expressions in the format {{ expression }}
1346
+ * @type {RegExp}
1347
+ */
1348
+ static expressionPattern = /\{\{\s*(.*?)\s*\}\}/g;
1349
+
1350
+ /**
1351
+ * Parses a template string, replacing expressions with their evaluated values.
1352
+ * Expressions are evaluated in the provided data context.
1353
+ *
1354
+ * @public
1355
+ * @static
1356
+ * @param {string} template - The template string to parse.
1357
+ * @param {Record<string, unknown>} data - The data context for evaluating expressions.
1358
+ * @returns {string} The parsed template with expressions replaced by their values.
1359
+ * @example
1360
+ * const result = TemplateEngine.parse("{{user.name}} is {{user.age}} years old", {
1361
+ * user: { name: "John", age: 30 }
1362
+ * }); // Returns: "John is 30 years old"
1363
+ */
1364
+ static parse(template, data) {
1365
+ if (typeof template !== "string") return template;
1366
+ return template.replace(this.expressionPattern, (_, expression) => this.evaluate(expression, data));
1367
+ }
1368
+
1369
+ /**
1370
+ * Evaluates an expression in the context of the provided data object.
1371
+ * Note: This does not provide a true sandbox and evaluated expressions may access global scope.
1372
+ * The use of the `with` statement is necessary for expression evaluation but has security implications.
1373
+ * Expressions should be carefully validated before evaluation.
1374
+ *
1375
+ * @public
1376
+ * @static
1377
+ * @param {string} expression - The expression to evaluate.
1378
+ * @param {Record<string, unknown>} data - The data context for evaluation.
1379
+ * @returns {unknown} The result of the evaluation, or an empty string if evaluation fails.
1380
+ * @example
1381
+ * const result = TemplateEngine.evaluate("user.name", { user: { name: "John" } }); // Returns: "John"
1382
+ * const age = TemplateEngine.evaluate("user.age", { user: { age: 30 } }); // Returns: 30
1383
+ */
1384
+ static evaluate(expression, data) {
1385
+ if (typeof expression !== "string") return expression;
1386
+ try {
1387
+ return new Function("data", `with(data) { return ${expression}; }`)(data);
1388
+ } catch {
1389
+ return "";
1390
+ }
1391
+ }
1392
+ }
1393
+
1394
+ /**
1395
+ * @class 🎯 PropsPlugin
1396
+ * @classdesc A plugin that extends Eleva's props data handling to support any type of data structure
1397
+ * with automatic type detection, parsing, and reactive prop updates. This plugin enables seamless
1398
+ * passing of complex data types from parent to child components without manual parsing.
1399
+ *
1400
+ * Core Features:
1401
+ * - Automatic type detection and parsing (strings, numbers, booleans, objects, arrays, dates, etc.)
1402
+ * - Support for complex data structures including nested objects and arrays
1403
+ * - Reactive props that automatically update when parent data changes
1404
+ * - Comprehensive error handling with custom error callbacks
1405
+ * - Simple configuration with minimal setup required
1406
+ *
1407
+ * @example
1408
+ * // Install the plugin
1409
+ * const app = new Eleva("myApp");
1410
+ * app.use(PropsPlugin, {
1411
+ * enableAutoParsing: true,
1412
+ * enableReactivity: true,
1413
+ * onError: (error, value) => {
1414
+ * console.error('Props parsing error:', error, value);
1415
+ * }
1416
+ * });
1417
+ *
1418
+ * // Use complex props in components
1419
+ * app.component("UserCard", {
1420
+ * template: (ctx) => `
1421
+ * <div class="user-info-container"
1422
+ * :user='${JSON.stringify(ctx.user.value)}'
1423
+ * :permissions='${JSON.stringify(ctx.permissions.value)}'
1424
+ * :settings='${JSON.stringify(ctx.settings.value)}'>
1425
+ * </div>
1426
+ * `,
1427
+ * children: {
1428
+ * '.user-info-container': 'UserInfo'
1429
+ * }
1430
+ * });
1431
+ *
1432
+ * app.component("UserInfo", {
1433
+ * setup({ props }) {
1434
+ * return {
1435
+ * user: props.user, // Automatically parsed object
1436
+ * permissions: props.permissions, // Automatically parsed array
1437
+ * settings: props.settings // Automatically parsed object
1438
+ * };
1439
+ * }
1440
+ * });
1441
+ */
1442
+ const PropsPlugin = {
1443
+ /**
1444
+ * Unique identifier for the plugin
1445
+ * @type {string}
1446
+ */
1447
+ name: "props",
1448
+ /**
1449
+ * Plugin version
1450
+ * @type {string}
1451
+ */
1452
+ version: "1.0.0-rc.2",
1453
+ /**
1454
+ * Plugin description
1455
+ * @type {string}
1456
+ */
1457
+ description: "Advanced props data handling for complex data structures with automatic type detection and reactivity",
1458
+ /**
1459
+ * Installs the plugin into the Eleva instance
1460
+ *
1461
+ * @param {Object} eleva - The Eleva instance
1462
+ * @param {Object} options - Plugin configuration options
1463
+ * @param {boolean} [options.enableAutoParsing=true] - Enable automatic type detection and parsing
1464
+ * @param {boolean} [options.enableReactivity=true] - Enable reactive prop updates using Eleva's signal system
1465
+ * @param {Function} [options.onError=null] - Error handler function called when parsing fails
1466
+ *
1467
+ * @example
1468
+ * // Basic installation
1469
+ * app.use(PropsPlugin);
1470
+ *
1471
+ * // Installation with custom options
1472
+ * app.use(PropsPlugin, {
1473
+ * enableAutoParsing: true,
1474
+ * enableReactivity: false,
1475
+ * onError: (error, value) => {
1476
+ * console.error('Props parsing error:', error, value);
1477
+ * }
1478
+ * });
1479
+ */
1480
+ install(eleva, options = {}) {
1481
+ const {
1482
+ enableAutoParsing = true,
1483
+ enableReactivity = true,
1484
+ onError = null
1485
+ } = options;
1486
+
1487
+ /**
1488
+ * Detects the type of a given value
1489
+ * @private
1490
+ * @param {any} value - The value to detect type for
1491
+ * @returns {string} The detected type ('string', 'number', 'boolean', 'object', 'array', 'date', 'map', 'set', 'function', 'null', 'undefined', 'unknown')
1492
+ *
1493
+ * @example
1494
+ * detectType("hello") // → "string"
1495
+ * detectType(42) // → "number"
1496
+ * detectType(true) // → "boolean"
1497
+ * detectType([1, 2, 3]) // → "array"
1498
+ * detectType({}) // → "object"
1499
+ * detectType(new Date()) // → "date"
1500
+ * detectType(null) // → "null"
1501
+ */
1502
+ const detectType = value => {
1503
+ if (value === null) return "null";
1504
+ if (value === undefined) return "undefined";
1505
+ if (typeof value === "boolean") return "boolean";
1506
+ if (typeof value === "number") return "number";
1507
+ if (typeof value === "string") return "string";
1508
+ if (typeof value === "function") return "function";
1509
+ if (value instanceof Date) return "date";
1510
+ if (value instanceof Map) return "map";
1511
+ if (value instanceof Set) return "set";
1512
+ if (Array.isArray(value)) return "array";
1513
+ if (typeof value === "object") return "object";
1514
+ return "unknown";
1515
+ };
1516
+
1517
+ /**
1518
+ * Parses a prop value with automatic type detection
1519
+ * @private
1520
+ * @param {any} value - The value to parse
1521
+ * @returns {any} The parsed value with appropriate type
1522
+ *
1523
+ * @description
1524
+ * This function automatically detects and parses different data types from string values:
1525
+ * - Special strings: "true" → true, "false" → false, "null" → null, "undefined" → undefined
1526
+ * - JSON objects/arrays: '{"key": "value"}' → {key: "value"}, '[1, 2, 3]' → [1, 2, 3]
1527
+ * - Boolean-like strings: "1" → true, "0" → false, "" → true
1528
+ * - Numeric strings: "42" → 42, "3.14" → 3.14
1529
+ * - Date strings: "2023-01-01T00:00:00.000Z" → Date object
1530
+ * - Other strings: returned as-is
1531
+ *
1532
+ * @example
1533
+ * parsePropValue("true") // → true
1534
+ * parsePropValue("42") // → 42
1535
+ * parsePropValue('{"key": "val"}') // → {key: "val"}
1536
+ * parsePropValue('[1, 2, 3]') // → [1, 2, 3]
1537
+ * parsePropValue("hello") // → "hello"
1538
+ */
1539
+ const parsePropValue = value => {
1540
+ try {
1541
+ // Handle non-string values - return as-is
1542
+ if (typeof value !== "string") {
1543
+ return value;
1544
+ }
1545
+
1546
+ // Handle special string patterns first
1547
+ if (value === "true") return true;
1548
+ if (value === "false") return false;
1549
+ if (value === "null") return null;
1550
+ if (value === "undefined") return undefined;
1551
+
1552
+ // Try to parse as JSON (for objects and arrays)
1553
+ // This handles complex data structures like objects and arrays
1554
+ if (value.startsWith("{") || value.startsWith("[")) {
1555
+ try {
1556
+ return JSON.parse(value);
1557
+ } catch (e) {
1558
+ // Not valid JSON, throw error to trigger error handler
1559
+ throw new Error(`Invalid JSON: ${value}`);
1560
+ }
1561
+ }
1562
+
1563
+ // Handle boolean-like strings (including "1" and "0")
1564
+ // These are common in HTML attributes and should be treated as booleans
1565
+ if (value === "1") return true;
1566
+ if (value === "0") return false;
1567
+ if (value === "") return true; // Empty string is truthy in HTML attributes
1568
+
1569
+ // Handle numeric strings (after boolean check to avoid conflicts)
1570
+ // This ensures "0" is treated as boolean false, not number 0
1571
+ if (!isNaN(value) && value !== "" && !isNaN(parseFloat(value))) {
1572
+ return Number(value);
1573
+ }
1574
+
1575
+ // Handle date strings (ISO format)
1576
+ // Recognizes standard ISO date format and converts to Date object
1577
+ if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
1578
+ const date = new Date(value);
1579
+ if (!isNaN(date.getTime())) {
1580
+ return date;
1581
+ }
1582
+ }
1583
+
1584
+ // Return as string if no other parsing applies
1585
+ // This is the fallback for regular text strings
1586
+ return value;
1587
+ } catch (error) {
1588
+ // Call error handler if provided
1589
+ if (onError) {
1590
+ onError(error, value);
1591
+ }
1592
+ // Fallback to original value to prevent breaking the application
1593
+ return value;
1594
+ }
1595
+ };
1596
+
1597
+ /**
1598
+ * Enhanced props extraction with automatic type detection
1599
+ * @private
1600
+ * @param {HTMLElement} element - The DOM element to extract props from
1601
+ * @returns {Object} Object containing parsed props with appropriate types
1602
+ *
1603
+ * @description
1604
+ * Extracts props from DOM element attributes that start with ":" and automatically
1605
+ * parses them to their appropriate types. Removes the attributes from the element
1606
+ * after extraction.
1607
+ *
1608
+ * @example
1609
+ * // HTML: <div :name="John" :age="30" :active="true" :data='{"key": "value"}'></div>
1610
+ * const props = extractProps(element);
1611
+ * // Result: { name: "John", age: 30, active: true, data: {key: "value"} }
1612
+ */
1613
+ const extractProps = element => {
1614
+ const props = {};
1615
+ const attrs = element.attributes;
1616
+
1617
+ // Iterate through attributes in reverse order to handle removal correctly
1618
+ for (let i = attrs.length - 1; i >= 0; i--) {
1619
+ const attr = attrs[i];
1620
+ // Only process attributes that start with ":" (prop attributes)
1621
+ if (attr.name.startsWith(":")) {
1622
+ const propName = attr.name.slice(1); // Remove the ":" prefix
1623
+ // Parse the value if auto-parsing is enabled, otherwise use as-is
1624
+ const parsedValue = enableAutoParsing ? parsePropValue(attr.value) : attr.value;
1625
+ props[propName] = parsedValue;
1626
+ // Remove the attribute from the DOM element after extraction
1627
+ element.removeAttribute(attr.name);
1628
+ }
1629
+ }
1630
+ return props;
1631
+ };
1632
+
1633
+ /**
1634
+ * Creates reactive props using Eleva's signal system
1635
+ * @private
1636
+ * @param {Object} props - The props object to make reactive
1637
+ * @returns {Object} Object containing reactive props (Eleva signals)
1638
+ *
1639
+ * @description
1640
+ * Converts regular prop values into Eleva signals for reactive updates.
1641
+ * If a value is already a signal, it's passed through unchanged.
1642
+ *
1643
+ * @example
1644
+ * const props = { name: "John", age: 30, active: true };
1645
+ * const reactiveProps = createReactiveProps(props);
1646
+ * // Result: {
1647
+ * // name: Signal("John"),
1648
+ * // age: Signal(30),
1649
+ * // active: Signal(true)
1650
+ * // }
1651
+ */
1652
+ const createReactiveProps = props => {
1653
+ const reactiveProps = {};
1654
+
1655
+ // Convert each prop value to a reactive signal
1656
+ Object.entries(props).forEach(([key, value]) => {
1657
+ // Check if value is already a signal (has 'value' and 'watch' properties)
1658
+ if (value && typeof value === "object" && "value" in value && "watch" in value) {
1659
+ // Value is already a signal, use it as-is
1660
+ reactiveProps[key] = value;
1661
+ } else {
1662
+ // Create new signal for the prop value to make it reactive
1663
+ reactiveProps[key] = new eleva.signal(value);
1664
+ }
1665
+ });
1666
+ return reactiveProps;
1667
+ };
1668
+
1669
+ // Override Eleva's internal _extractProps method with our enhanced version
1670
+ eleva._extractProps = extractProps;
1671
+
1672
+ // Override Eleva's mount method to apply enhanced prop handling
1673
+ const originalMount = eleva.mount;
1674
+ eleva.mount = async (container, compName, props = {}) => {
1675
+ // Create reactive props if reactivity is enabled
1676
+ const enhancedProps = enableReactivity ? createReactiveProps(props) : props;
1677
+
1678
+ // Call the original mount method with enhanced props
1679
+ return await originalMount.call(eleva, container, compName, enhancedProps);
1680
+ };
1681
+
1682
+ // Override Eleva's _mountComponents method to enable signal reference passing
1683
+ const originalMountComponents = eleva._mountComponents;
1684
+
1685
+ // Cache to store parent contexts by container element
1686
+ const parentContextCache = new WeakMap();
1687
+ // Store child instances that need signal linking
1688
+ const pendingSignalLinks = new Set();
1689
+ eleva._mountComponents = async (container, children, childInstances) => {
1690
+ for (const [selector, component] of Object.entries(children)) {
1691
+ if (!selector) continue;
1692
+ for (const el of container.querySelectorAll(selector)) {
1693
+ if (!(el instanceof HTMLElement)) continue;
1694
+
1695
+ // Extract props from DOM attributes
1696
+ const extractedProps = eleva._extractProps(el);
1697
+
1698
+ // Get parent context to check for signal references
1699
+ let enhancedProps = extractedProps;
1700
+
1701
+ // Try to find parent context by looking up the DOM tree
1702
+ let parentContext = parentContextCache.get(container);
1703
+ if (!parentContext) {
1704
+ let currentElement = container;
1705
+ while (currentElement && !parentContext) {
1706
+ if (currentElement._eleva_instance && currentElement._eleva_instance.data) {
1707
+ parentContext = currentElement._eleva_instance.data;
1708
+ // Cache the parent context for future use
1709
+ parentContextCache.set(container, parentContext);
1710
+ break;
1711
+ }
1712
+ currentElement = currentElement.parentElement;
1713
+ }
1714
+ }
1715
+ if (enableReactivity && parentContext) {
1716
+ const signalProps = {};
1717
+
1718
+ // Check each extracted prop to see if there's a matching signal in parent context
1719
+ Object.keys(extractedProps).forEach(propName => {
1720
+ if (parentContext[propName] && parentContext[propName] instanceof eleva.signal) {
1721
+ // Found a signal in parent context with the same name as the prop
1722
+ // Pass the signal reference instead of creating a new one
1723
+ signalProps[propName] = parentContext[propName];
1724
+ }
1725
+ });
1726
+
1727
+ // Merge signal props with regular props (signal props take precedence)
1728
+ enhancedProps = {
1729
+ ...extractedProps,
1730
+ ...signalProps
1731
+ };
1732
+ }
1733
+
1734
+ // Create reactive props for non-signal props only
1735
+ let finalProps = enhancedProps;
1736
+ if (enableReactivity) {
1737
+ // Only create reactive props for values that aren't already signals
1738
+ const nonSignalProps = {};
1739
+ Object.entries(enhancedProps).forEach(([key, value]) => {
1740
+ if (!(value && typeof value === "object" && "value" in value && "watch" in value)) {
1741
+ // This is not a signal, create a reactive prop for it
1742
+ nonSignalProps[key] = value;
1743
+ }
1744
+ });
1745
+
1746
+ // Create reactive props only for non-signal values
1747
+ const reactiveNonSignalProps = createReactiveProps(nonSignalProps);
1748
+
1749
+ // Merge signal props with reactive non-signal props
1750
+ finalProps = {
1751
+ ...reactiveNonSignalProps,
1752
+ ...enhancedProps // Signal props take precedence
1753
+ };
1754
+ }
1755
+
1756
+ /** @type {MountResult} */
1757
+ const instance = await eleva.mount(el, component, finalProps);
1758
+ if (instance && !childInstances.includes(instance)) {
1759
+ childInstances.push(instance);
1760
+
1761
+ // If we have extracted props but no parent context yet, mark for later signal linking
1762
+ if (enableReactivity && Object.keys(extractedProps).length > 0 && !parentContext) {
1763
+ pendingSignalLinks.add({
1764
+ instance,
1765
+ extractedProps,
1766
+ container,
1767
+ component
1768
+ });
1769
+ }
1770
+ }
1771
+ }
1772
+ }
1773
+
1774
+ // After mounting all children, try to link signals for pending instances
1775
+ if (enableReactivity && pendingSignalLinks.size > 0) {
1776
+ for (const pending of pendingSignalLinks) {
1777
+ const {
1778
+ instance,
1779
+ extractedProps,
1780
+ container,
1781
+ component
1782
+ } = pending;
1783
+
1784
+ // Try to find parent context again
1785
+ let parentContext = parentContextCache.get(container);
1786
+ if (!parentContext) {
1787
+ let currentElement = container;
1788
+ while (currentElement && !parentContext) {
1789
+ if (currentElement._eleva_instance && currentElement._eleva_instance.data) {
1790
+ parentContext = currentElement._eleva_instance.data;
1791
+ parentContextCache.set(container, parentContext);
1792
+ break;
1793
+ }
1794
+ currentElement = currentElement.parentElement;
1795
+ }
1796
+ }
1797
+ if (parentContext) {
1798
+ const signalProps = {};
1799
+
1800
+ // Check each extracted prop to see if there's a matching signal in parent context
1801
+ Object.keys(extractedProps).forEach(propName => {
1802
+ if (parentContext[propName] && parentContext[propName] instanceof eleva.signal) {
1803
+ signalProps[propName] = parentContext[propName];
1804
+ }
1805
+ });
1806
+
1807
+ // Update the child instance's data with signal references
1808
+ if (Object.keys(signalProps).length > 0) {
1809
+ Object.assign(instance.data, signalProps);
1810
+
1811
+ // Set up signal watchers for the newly linked signals
1812
+ Object.keys(signalProps).forEach(propName => {
1813
+ const signal = signalProps[propName];
1814
+ if (signal && typeof signal.watch === "function") {
1815
+ signal.watch(newValue => {
1816
+ // Trigger a re-render of the child component when the signal changes
1817
+ const childComponent = eleva._components.get(component) || component;
1818
+ if (childComponent && childComponent.template) {
1819
+ const templateResult = typeof childComponent.template === "function" ? childComponent.template(instance.data) : childComponent.template;
1820
+ const newHtml = TemplateEngine.parse(templateResult, instance.data);
1821
+ eleva.renderer.patchDOM(instance.container, newHtml);
1822
+ }
1823
+ });
1824
+ }
1825
+ });
1826
+
1827
+ // Initial re-render to show the correct signal values
1828
+ const childComponent = eleva._components.get(component) || component;
1829
+ if (childComponent && childComponent.template) {
1830
+ const templateResult = typeof childComponent.template === "function" ? childComponent.template(instance.data) : childComponent.template;
1831
+ const newHtml = TemplateEngine.parse(templateResult, instance.data);
1832
+ eleva.renderer.patchDOM(instance.container, newHtml);
1833
+ }
1834
+ }
1835
+
1836
+ // Remove from pending list
1837
+ pendingSignalLinks.delete(pending);
1838
+ }
1839
+ }
1840
+ }
1841
+ };
1842
+
1843
+ /**
1844
+ * Expose utility methods on the Eleva instance
1845
+ * @namespace eleva.props
1846
+ */
1847
+ eleva.props = {
1848
+ /**
1849
+ * Parse a single value with automatic type detection
1850
+ * @param {any} value - The value to parse
1851
+ * @returns {any} The parsed value with appropriate type
1852
+ *
1853
+ * @example
1854
+ * app.props.parse("42") // → 42
1855
+ * app.props.parse("true") // → true
1856
+ * app.props.parse('{"key": "val"}') // → {key: "val"}
1857
+ */
1858
+ parse: value => {
1859
+ // Return value as-is if auto parsing is disabled
1860
+ if (!enableAutoParsing) {
1861
+ return value;
1862
+ }
1863
+ // Use our enhanced parsing function
1864
+ return parsePropValue(value);
1865
+ },
1866
+ /**
1867
+ * Detect the type of a value
1868
+ * @param {any} value - The value to detect type for
1869
+ * @returns {string} The detected type
1870
+ *
1871
+ * @example
1872
+ * app.props.detectType("hello") // → "string"
1873
+ * app.props.detectType(42) // → "number"
1874
+ * app.props.detectType([1, 2, 3]) // → "array"
1875
+ */
1876
+ detectType
1877
+ };
1878
+
1879
+ // Store original methods for uninstall
1880
+ eleva._originalExtractProps = eleva._extractProps;
1881
+ eleva._originalMount = originalMount;
1882
+ eleva._originalMountComponents = originalMountComponents;
1883
+ },
1884
+ /**
1885
+ * Uninstalls the plugin from the Eleva instance
1886
+ *
1887
+ * @param {Object} eleva - The Eleva instance
1888
+ *
1889
+ * @description
1890
+ * Restores the original Eleva methods and removes all plugin-specific
1891
+ * functionality. This method should be called when the plugin is no
1892
+ * longer needed.
1893
+ *
1894
+ * @example
1895
+ * // Uninstall the plugin
1896
+ * PropsPlugin.uninstall(app);
1897
+ */
1898
+ uninstall(eleva) {
1899
+ // Restore original _extractProps method
1900
+ if (eleva._originalExtractProps) {
1901
+ eleva._extractProps = eleva._originalExtractProps;
1902
+ delete eleva._originalExtractProps;
1903
+ }
1904
+
1905
+ // Restore original mount method
1906
+ if (eleva._originalMount) {
1907
+ eleva.mount = eleva._originalMount;
1908
+ delete eleva._originalMount;
1909
+ }
1910
+
1911
+ // Restore original _mountComponents method
1912
+ if (eleva._originalMountComponents) {
1913
+ eleva._mountComponents = eleva._originalMountComponents;
1914
+ delete eleva._originalMountComponents;
1915
+ }
1916
+
1917
+ // Remove plugin utility methods
1918
+ if (eleva.props) {
1919
+ delete eleva.props;
1920
+ }
1921
+ }
1922
+ };
1923
+
1332
1924
  exports.Attr = AttrPlugin;
1925
+ exports.Props = PropsPlugin;
1333
1926
  exports.Router = RouterPlugin;
1334
1927
 
1335
1928
  }));