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