eleva 1.0.0-rc.3 → 1.0.0-rc.5
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 -2
- package/dist/eleva-plugins.cjs.js +1693 -0
- package/dist/eleva-plugins.cjs.js.map +1 -0
- package/dist/eleva-plugins.esm.js +1689 -0
- package/dist/eleva-plugins.esm.js.map +1 -0
- package/dist/eleva-plugins.umd.js +1699 -0
- package/dist/eleva-plugins.umd.js.map +1 -0
- package/dist/eleva-plugins.umd.min.js +3 -0
- package/dist/eleva-plugins.umd.min.js.map +1 -0
- package/dist/eleva.cjs.js +15 -34
- package/dist/eleva.cjs.js.map +1 -1
- package/dist/eleva.d.ts +0 -1
- package/dist/eleva.esm.js +15 -34
- package/dist/eleva.esm.js.map +1 -1
- package/dist/eleva.umd.js +15 -34
- 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 +231 -0
- package/dist/plugins/attr.umd.js.map +1 -0
- package/dist/plugins/attr.umd.min.js +3 -0
- package/dist/plugins/attr.umd.min.js.map +1 -0
- package/dist/plugins/props.umd.js +373 -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/dist/plugins/router.umd.js +1115 -0
- package/dist/plugins/router.umd.js.map +1 -0
- package/dist/plugins/router.umd.min.js +3 -0
- package/dist/plugins/router.umd.min.js.map +1 -0
- package/package.json +56 -17
- package/src/core/Eleva.js +6 -7
- package/src/modules/Renderer.js +8 -36
- package/src/plugins/Attr.js +252 -0
- package/src/plugins/Props.js +385 -0
- package/src/plugins/Router.js +1217 -0
- package/src/plugins/index.js +35 -0
- package/types/core/Eleva.d.ts +0 -1
- package/types/core/Eleva.d.ts.map +1 -1
- package/types/modules/Renderer.d.ts.map +1 -1
- package/types/plugins/Attr.d.ts +28 -0
- package/types/plugins/Attr.d.ts.map +1 -0
- package/types/plugins/Props.d.ts +48 -0
- package/types/plugins/Props.d.ts.map +1 -0
- package/types/plugins/Router.d.ts +500 -0
- package/types/plugins/Router.d.ts.map +1 -0
- package/types/plugins/index.d.ts +4 -0
- package/types/plugins/index.d.ts.map +1 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A regular expression to match hyphenated lowercase letters.
|
|
5
|
+
* @private
|
|
6
|
+
* @type {RegExp}
|
|
7
|
+
*/
|
|
8
|
+
const CAMEL_RE = /-([a-z])/g;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @class 🎯 AttrPlugin
|
|
12
|
+
* @classdesc A plugin that provides advanced attribute handling for Eleva components.
|
|
13
|
+
* This plugin extends the renderer with sophisticated attribute processing including:
|
|
14
|
+
* - ARIA attribute handling with proper property mapping
|
|
15
|
+
* - Data attribute management
|
|
16
|
+
* - Boolean attribute processing
|
|
17
|
+
* - Dynamic property detection and mapping
|
|
18
|
+
* - Attribute cleanup and removal
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Install the plugin
|
|
22
|
+
* const app = new Eleva("myApp");
|
|
23
|
+
* app.use(AttrPlugin);
|
|
24
|
+
*
|
|
25
|
+
* // Use advanced attributes in components
|
|
26
|
+
* app.component("myComponent", {
|
|
27
|
+
* template: (ctx) => `
|
|
28
|
+
* <button
|
|
29
|
+
* aria-expanded="${ctx.isExpanded.value}"
|
|
30
|
+
* data-user-id="${ctx.userId.value}"
|
|
31
|
+
* disabled="${ctx.isLoading.value}"
|
|
32
|
+
* class="btn ${ctx.variant.value}"
|
|
33
|
+
* >
|
|
34
|
+
* ${ctx.text.value}
|
|
35
|
+
* </button>
|
|
36
|
+
* `
|
|
37
|
+
* });
|
|
38
|
+
*/
|
|
39
|
+
export const AttrPlugin = {
|
|
40
|
+
/**
|
|
41
|
+
* Unique identifier for the plugin
|
|
42
|
+
* @type {string}
|
|
43
|
+
*/
|
|
44
|
+
name: "attr",
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Plugin version
|
|
48
|
+
* @type {string}
|
|
49
|
+
*/
|
|
50
|
+
version: "1.0.0-rc.1",
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Plugin description
|
|
54
|
+
* @type {string}
|
|
55
|
+
*/
|
|
56
|
+
description: "Advanced attribute handling for Eleva components",
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Installs the plugin into the Eleva instance
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} eleva - The Eleva instance
|
|
62
|
+
* @param {Object} options - Plugin configuration options
|
|
63
|
+
* @param {boolean} [options.enableAria=true] - Enable ARIA attribute handling
|
|
64
|
+
* @param {boolean} [options.enableData=true] - Enable data attribute handling
|
|
65
|
+
* @param {boolean} [options.enableBoolean=true] - Enable boolean attribute handling
|
|
66
|
+
* @param {boolean} [options.enableDynamic=true] - Enable dynamic property detection
|
|
67
|
+
*/
|
|
68
|
+
install(eleva, options = {}) {
|
|
69
|
+
const {
|
|
70
|
+
enableAria = true,
|
|
71
|
+
enableData = true,
|
|
72
|
+
enableBoolean = true,
|
|
73
|
+
enableDynamic = true,
|
|
74
|
+
} = options;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Updates the attributes of an element to match a new element's attributes.
|
|
78
|
+
* This method provides sophisticated attribute processing including:
|
|
79
|
+
* - ARIA attribute handling with proper property mapping
|
|
80
|
+
* - Data attribute management
|
|
81
|
+
* - Boolean attribute processing
|
|
82
|
+
* - Dynamic property detection and mapping
|
|
83
|
+
* - Attribute cleanup and removal
|
|
84
|
+
*
|
|
85
|
+
* @param {HTMLElement} oldEl - The original element to update
|
|
86
|
+
* @param {HTMLElement} newEl - The new element to update
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
const updateAttributes = (oldEl, newEl) => {
|
|
90
|
+
const oldAttrs = oldEl.attributes;
|
|
91
|
+
const newAttrs = newEl.attributes;
|
|
92
|
+
|
|
93
|
+
// Process new attributes
|
|
94
|
+
for (let i = 0; i < newAttrs.length; i++) {
|
|
95
|
+
const { name, value } = newAttrs[i];
|
|
96
|
+
|
|
97
|
+
// Skip event attributes (handled by event system)
|
|
98
|
+
if (name.startsWith("@")) continue;
|
|
99
|
+
|
|
100
|
+
// Skip if attribute hasn't changed
|
|
101
|
+
if (oldEl.getAttribute(name) === value) continue;
|
|
102
|
+
|
|
103
|
+
// Handle ARIA attributes
|
|
104
|
+
if (enableAria && name.startsWith("aria-")) {
|
|
105
|
+
const prop =
|
|
106
|
+
"aria" + name.slice(5).replace(CAMEL_RE, (_, l) => l.toUpperCase());
|
|
107
|
+
oldEl[prop] = value;
|
|
108
|
+
oldEl.setAttribute(name, value);
|
|
109
|
+
}
|
|
110
|
+
// Handle data attributes
|
|
111
|
+
else if (enableData && name.startsWith("data-")) {
|
|
112
|
+
oldEl.dataset[name.slice(5)] = value;
|
|
113
|
+
oldEl.setAttribute(name, value);
|
|
114
|
+
}
|
|
115
|
+
// Handle other attributes
|
|
116
|
+
else {
|
|
117
|
+
let prop = name.replace(CAMEL_RE, (_, l) => l.toUpperCase());
|
|
118
|
+
|
|
119
|
+
// Dynamic property detection
|
|
120
|
+
if (
|
|
121
|
+
enableDynamic &&
|
|
122
|
+
!(prop in oldEl) &&
|
|
123
|
+
!Object.getOwnPropertyDescriptor(Object.getPrototypeOf(oldEl), prop)
|
|
124
|
+
) {
|
|
125
|
+
const elementProps = Object.getOwnPropertyNames(
|
|
126
|
+
Object.getPrototypeOf(oldEl)
|
|
127
|
+
);
|
|
128
|
+
const matchingProp = elementProps.find(
|
|
129
|
+
(p) =>
|
|
130
|
+
p.toLowerCase() === name.toLowerCase() ||
|
|
131
|
+
p.toLowerCase().includes(name.toLowerCase()) ||
|
|
132
|
+
name.toLowerCase().includes(p.toLowerCase())
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (matchingProp) {
|
|
136
|
+
prop = matchingProp;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
141
|
+
Object.getPrototypeOf(oldEl),
|
|
142
|
+
prop
|
|
143
|
+
);
|
|
144
|
+
const hasProperty = prop in oldEl || descriptor;
|
|
145
|
+
|
|
146
|
+
if (hasProperty) {
|
|
147
|
+
// Boolean attribute handling
|
|
148
|
+
if (enableBoolean) {
|
|
149
|
+
const isBoolean =
|
|
150
|
+
typeof oldEl[prop] === "boolean" ||
|
|
151
|
+
(descriptor?.get &&
|
|
152
|
+
typeof descriptor.get.call(oldEl) === "boolean");
|
|
153
|
+
|
|
154
|
+
if (isBoolean) {
|
|
155
|
+
const boolValue =
|
|
156
|
+
value !== "false" &&
|
|
157
|
+
(value === "" || value === prop || value === "true");
|
|
158
|
+
oldEl[prop] = boolValue;
|
|
159
|
+
|
|
160
|
+
if (boolValue) {
|
|
161
|
+
oldEl.setAttribute(name, "");
|
|
162
|
+
} else {
|
|
163
|
+
oldEl.removeAttribute(name);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
oldEl[prop] = value;
|
|
167
|
+
oldEl.setAttribute(name, value);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
oldEl[prop] = value;
|
|
171
|
+
oldEl.setAttribute(name, value);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
oldEl.setAttribute(name, value);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Remove old attributes that are no longer present
|
|
180
|
+
for (let i = oldAttrs.length - 1; i >= 0; i--) {
|
|
181
|
+
const name = oldAttrs[i].name;
|
|
182
|
+
if (!newEl.hasAttribute(name)) {
|
|
183
|
+
oldEl.removeAttribute(name);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Extend the renderer with the advanced attribute handler
|
|
189
|
+
if (eleva.renderer) {
|
|
190
|
+
eleva.renderer.updateAttributes = updateAttributes;
|
|
191
|
+
|
|
192
|
+
// Store the original _patchNode method
|
|
193
|
+
const originalPatchNode = eleva.renderer._patchNode;
|
|
194
|
+
eleva.renderer._originalPatchNode = originalPatchNode;
|
|
195
|
+
|
|
196
|
+
// Override the _patchNode method to use our attribute handler
|
|
197
|
+
eleva.renderer._patchNode = function (oldNode, newNode) {
|
|
198
|
+
if (oldNode?._eleva_instance) return;
|
|
199
|
+
|
|
200
|
+
if (!this._isSameNode(oldNode, newNode)) {
|
|
201
|
+
oldNode.replaceWith(newNode.cloneNode(true));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
|
206
|
+
updateAttributes(oldNode, newNode);
|
|
207
|
+
this._diff(oldNode, newNode);
|
|
208
|
+
} else if (
|
|
209
|
+
oldNode.nodeType === Node.TEXT_NODE &&
|
|
210
|
+
oldNode.nodeValue !== newNode.nodeValue
|
|
211
|
+
) {
|
|
212
|
+
oldNode.nodeValue = newNode.nodeValue;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Add plugin metadata to the Eleva instance
|
|
218
|
+
if (!eleva.plugins) {
|
|
219
|
+
eleva.plugins = new Map();
|
|
220
|
+
}
|
|
221
|
+
eleva.plugins.set(this.name, {
|
|
222
|
+
name: this.name,
|
|
223
|
+
version: this.version,
|
|
224
|
+
description: this.description,
|
|
225
|
+
options,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Add utility methods for manual attribute updates
|
|
229
|
+
eleva.updateElementAttributes = updateAttributes;
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Uninstalls the plugin from the Eleva instance
|
|
234
|
+
*
|
|
235
|
+
* @param {Object} eleva - The Eleva instance
|
|
236
|
+
*/
|
|
237
|
+
uninstall(eleva) {
|
|
238
|
+
// Restore original _patchNode method if it exists
|
|
239
|
+
if (eleva.renderer && eleva.renderer._originalPatchNode) {
|
|
240
|
+
eleva.renderer._patchNode = eleva.renderer._originalPatchNode;
|
|
241
|
+
delete eleva.renderer._originalPatchNode;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Remove plugin metadata
|
|
245
|
+
if (eleva.plugins) {
|
|
246
|
+
eleva.plugins.delete(this.name);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Remove utility methods
|
|
250
|
+
delete eleva.updateElementAttributes;
|
|
251
|
+
},
|
|
252
|
+
};
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class 🎯 PropsPlugin
|
|
5
|
+
* @classdesc A plugin that extends Eleva's props data handling to support any type of data structure
|
|
6
|
+
* with automatic type detection, parsing, and reactive prop updates. This plugin enables seamless
|
|
7
|
+
* passing of complex data types from parent to child components without manual parsing.
|
|
8
|
+
*
|
|
9
|
+
* Core Features:
|
|
10
|
+
* - Automatic type detection and parsing (strings, numbers, booleans, objects, arrays, dates, etc.)
|
|
11
|
+
* - Support for complex data structures including nested objects and arrays
|
|
12
|
+
* - Reactive props that automatically update when parent data changes
|
|
13
|
+
* - Comprehensive error handling with custom error callbacks
|
|
14
|
+
* - Simple configuration with minimal setup required
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Install the plugin
|
|
18
|
+
* const app = new Eleva("myApp");
|
|
19
|
+
* app.use(PropsPlugin, {
|
|
20
|
+
* enableAutoParsing: true,
|
|
21
|
+
* enableReactivity: true,
|
|
22
|
+
* onError: (error, value) => {
|
|
23
|
+
* console.error('Props parsing error:', error, value);
|
|
24
|
+
* }
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Use complex props in components
|
|
28
|
+
* app.component("UserCard", {
|
|
29
|
+
* template: (ctx) => `
|
|
30
|
+
* <div class="user-info-container"
|
|
31
|
+
* :user='${JSON.stringify(ctx.user.value)}'
|
|
32
|
+
* :permissions='${JSON.stringify(ctx.permissions.value)}'
|
|
33
|
+
* :settings='${JSON.stringify(ctx.settings.value)}'>
|
|
34
|
+
* </div>
|
|
35
|
+
* `,
|
|
36
|
+
* children: {
|
|
37
|
+
* '.user-info-container': 'UserInfo'
|
|
38
|
+
* }
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* app.component("UserInfo", {
|
|
42
|
+
* setup({ props }) {
|
|
43
|
+
* return {
|
|
44
|
+
* user: props.user, // Automatically parsed object
|
|
45
|
+
* permissions: props.permissions, // Automatically parsed array
|
|
46
|
+
* settings: props.settings // Automatically parsed object
|
|
47
|
+
* };
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
51
|
+
export const PropsPlugin = {
|
|
52
|
+
/**
|
|
53
|
+
* Unique identifier for the plugin
|
|
54
|
+
* @type {string}
|
|
55
|
+
*/
|
|
56
|
+
name: "props",
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Plugin version
|
|
60
|
+
* @type {string}
|
|
61
|
+
*/
|
|
62
|
+
version: "1.0.0-rc.1",
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Plugin description
|
|
66
|
+
* @type {string}
|
|
67
|
+
*/
|
|
68
|
+
description:
|
|
69
|
+
"Advanced props data handling for complex data structures with automatic type detection and reactivity",
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Installs the plugin into the Eleva instance
|
|
73
|
+
*
|
|
74
|
+
* @param {Object} eleva - The Eleva instance
|
|
75
|
+
* @param {Object} options - Plugin configuration options
|
|
76
|
+
* @param {boolean} [options.enableAutoParsing=true] - Enable automatic type detection and parsing
|
|
77
|
+
* @param {boolean} [options.enableReactivity=true] - Enable reactive prop updates using Eleva's signal system
|
|
78
|
+
* @param {Function} [options.onError=null] - Error handler function called when parsing fails
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Basic installation
|
|
82
|
+
* app.use(PropsPlugin);
|
|
83
|
+
*
|
|
84
|
+
* // Installation with custom options
|
|
85
|
+
* app.use(PropsPlugin, {
|
|
86
|
+
* enableAutoParsing: true,
|
|
87
|
+
* enableReactivity: false,
|
|
88
|
+
* onError: (error, value) => {
|
|
89
|
+
* console.error('Props parsing error:', error, value);
|
|
90
|
+
* }
|
|
91
|
+
* });
|
|
92
|
+
*/
|
|
93
|
+
install(eleva, options = {}) {
|
|
94
|
+
const {
|
|
95
|
+
enableAutoParsing = true,
|
|
96
|
+
enableReactivity = true,
|
|
97
|
+
onError = null,
|
|
98
|
+
} = options;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detects the type of a given value
|
|
102
|
+
* @private
|
|
103
|
+
* @param {any} value - The value to detect type for
|
|
104
|
+
* @returns {string} The detected type ('string', 'number', 'boolean', 'object', 'array', 'date', 'map', 'set', 'function', 'null', 'undefined', 'unknown')
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* detectType("hello") // → "string"
|
|
108
|
+
* detectType(42) // → "number"
|
|
109
|
+
* detectType(true) // → "boolean"
|
|
110
|
+
* detectType([1, 2, 3]) // → "array"
|
|
111
|
+
* detectType({}) // → "object"
|
|
112
|
+
* detectType(new Date()) // → "date"
|
|
113
|
+
* detectType(null) // → "null"
|
|
114
|
+
*/
|
|
115
|
+
const detectType = (value) => {
|
|
116
|
+
if (value === null) return "null";
|
|
117
|
+
if (value === undefined) return "undefined";
|
|
118
|
+
if (typeof value === "boolean") return "boolean";
|
|
119
|
+
if (typeof value === "number") return "number";
|
|
120
|
+
if (typeof value === "string") return "string";
|
|
121
|
+
if (typeof value === "function") return "function";
|
|
122
|
+
if (value instanceof Date) return "date";
|
|
123
|
+
if (value instanceof Map) return "map";
|
|
124
|
+
if (value instanceof Set) return "set";
|
|
125
|
+
if (Array.isArray(value)) return "array";
|
|
126
|
+
if (typeof value === "object") return "object";
|
|
127
|
+
return "unknown";
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parses a prop value with automatic type detection
|
|
132
|
+
* @private
|
|
133
|
+
* @param {any} value - The value to parse
|
|
134
|
+
* @returns {any} The parsed value with appropriate type
|
|
135
|
+
*
|
|
136
|
+
* @description
|
|
137
|
+
* This function automatically detects and parses different data types from string values:
|
|
138
|
+
* - Special strings: "true" → true, "false" → false, "null" → null, "undefined" → undefined
|
|
139
|
+
* - JSON objects/arrays: '{"key": "value"}' → {key: "value"}, '[1, 2, 3]' → [1, 2, 3]
|
|
140
|
+
* - Boolean-like strings: "1" → true, "0" → false, "" → true
|
|
141
|
+
* - Numeric strings: "42" → 42, "3.14" → 3.14
|
|
142
|
+
* - Date strings: "2023-01-01T00:00:00.000Z" → Date object
|
|
143
|
+
* - Other strings: returned as-is
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* parsePropValue("true") // → true
|
|
147
|
+
* parsePropValue("42") // → 42
|
|
148
|
+
* parsePropValue('{"key": "val"}') // → {key: "val"}
|
|
149
|
+
* parsePropValue('[1, 2, 3]') // → [1, 2, 3]
|
|
150
|
+
* parsePropValue("hello") // → "hello"
|
|
151
|
+
*/
|
|
152
|
+
const parsePropValue = (value) => {
|
|
153
|
+
try {
|
|
154
|
+
// Handle non-string values - return as-is
|
|
155
|
+
if (typeof value !== "string") {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Handle special string patterns first
|
|
160
|
+
if (value === "true") return true;
|
|
161
|
+
if (value === "false") return false;
|
|
162
|
+
if (value === "null") return null;
|
|
163
|
+
if (value === "undefined") return undefined;
|
|
164
|
+
|
|
165
|
+
// Try to parse as JSON (for objects and arrays)
|
|
166
|
+
// This handles complex data structures like objects and arrays
|
|
167
|
+
if (value.startsWith("{") || value.startsWith("[")) {
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(value);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
// Not valid JSON, throw error to trigger error handler
|
|
172
|
+
throw new Error(`Invalid JSON: ${value}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle boolean-like strings (including "1" and "0")
|
|
177
|
+
// These are common in HTML attributes and should be treated as booleans
|
|
178
|
+
if (value === "1") return true;
|
|
179
|
+
if (value === "0") return false;
|
|
180
|
+
if (value === "") return true; // Empty string is truthy in HTML attributes
|
|
181
|
+
|
|
182
|
+
// Handle numeric strings (after boolean check to avoid conflicts)
|
|
183
|
+
// This ensures "0" is treated as boolean false, not number 0
|
|
184
|
+
if (!isNaN(value) && value !== "" && !isNaN(parseFloat(value))) {
|
|
185
|
+
return Number(value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle date strings (ISO format)
|
|
189
|
+
// Recognizes standard ISO date format and converts to Date object
|
|
190
|
+
if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
|
|
191
|
+
const date = new Date(value);
|
|
192
|
+
if (!isNaN(date.getTime())) {
|
|
193
|
+
return date;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Return as string if no other parsing applies
|
|
198
|
+
// This is the fallback for regular text strings
|
|
199
|
+
return value;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
// Call error handler if provided
|
|
202
|
+
if (onError) {
|
|
203
|
+
onError(error, value);
|
|
204
|
+
}
|
|
205
|
+
// Fallback to original value to prevent breaking the application
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Enhanced props extraction with automatic type detection
|
|
212
|
+
* @private
|
|
213
|
+
* @param {HTMLElement} element - The DOM element to extract props from
|
|
214
|
+
* @returns {Object} Object containing parsed props with appropriate types
|
|
215
|
+
*
|
|
216
|
+
* @description
|
|
217
|
+
* Extracts props from DOM element attributes that start with ":" and automatically
|
|
218
|
+
* parses them to their appropriate types. Removes the attributes from the element
|
|
219
|
+
* after extraction.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // HTML: <div :name="John" :age="30" :active="true" :data='{"key": "value"}'></div>
|
|
223
|
+
* const props = extractProps(element);
|
|
224
|
+
* // Result: { name: "John", age: 30, active: true, data: {key: "value"} }
|
|
225
|
+
*/
|
|
226
|
+
const extractProps = (element) => {
|
|
227
|
+
const props = {};
|
|
228
|
+
const attrs = element.attributes;
|
|
229
|
+
|
|
230
|
+
// Iterate through attributes in reverse order to handle removal correctly
|
|
231
|
+
for (let i = attrs.length - 1; i >= 0; i--) {
|
|
232
|
+
const attr = attrs[i];
|
|
233
|
+
// Only process attributes that start with ":" (prop attributes)
|
|
234
|
+
if (attr.name.startsWith(":")) {
|
|
235
|
+
const propName = attr.name.slice(1); // Remove the ":" prefix
|
|
236
|
+
// Parse the value if auto-parsing is enabled, otherwise use as-is
|
|
237
|
+
const parsedValue = enableAutoParsing
|
|
238
|
+
? parsePropValue(attr.value)
|
|
239
|
+
: attr.value;
|
|
240
|
+
props[propName] = parsedValue;
|
|
241
|
+
// Remove the attribute from the DOM element after extraction
|
|
242
|
+
element.removeAttribute(attr.name);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return props;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Creates reactive props using Eleva's signal system
|
|
251
|
+
* @private
|
|
252
|
+
* @param {Object} props - The props object to make reactive
|
|
253
|
+
* @returns {Object} Object containing reactive props (Eleva signals)
|
|
254
|
+
*
|
|
255
|
+
* @description
|
|
256
|
+
* Converts regular prop values into Eleva signals for reactive updates.
|
|
257
|
+
* If a value is already a signal, it's passed through unchanged.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* const props = { name: "John", age: 30, active: true };
|
|
261
|
+
* const reactiveProps = createReactiveProps(props);
|
|
262
|
+
* // Result: {
|
|
263
|
+
* // name: Signal("John"),
|
|
264
|
+
* // age: Signal(30),
|
|
265
|
+
* // active: Signal(true)
|
|
266
|
+
* // }
|
|
267
|
+
*/
|
|
268
|
+
const createReactiveProps = (props) => {
|
|
269
|
+
const reactiveProps = {};
|
|
270
|
+
|
|
271
|
+
// Convert each prop value to a reactive signal
|
|
272
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
273
|
+
// Check if value is already a signal (has 'value' and 'watch' properties)
|
|
274
|
+
if (
|
|
275
|
+
value &&
|
|
276
|
+
typeof value === "object" &&
|
|
277
|
+
"value" in value &&
|
|
278
|
+
"watch" in value
|
|
279
|
+
) {
|
|
280
|
+
// Value is already a signal, use it as-is
|
|
281
|
+
reactiveProps[key] = value;
|
|
282
|
+
} else {
|
|
283
|
+
// Create new signal for the prop value to make it reactive
|
|
284
|
+
reactiveProps[key] = new eleva.signal(value);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return reactiveProps;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Override Eleva's internal _extractProps method with our enhanced version
|
|
292
|
+
eleva._extractProps = extractProps;
|
|
293
|
+
|
|
294
|
+
// Override Eleva's mount method to apply enhanced prop handling
|
|
295
|
+
const originalMount = eleva.mount;
|
|
296
|
+
eleva.mount = async (container, compName, props = {}) => {
|
|
297
|
+
// Create reactive props if reactivity is enabled
|
|
298
|
+
const enhancedProps = enableReactivity
|
|
299
|
+
? createReactiveProps(props)
|
|
300
|
+
: props;
|
|
301
|
+
|
|
302
|
+
// Call the original mount method with enhanced props
|
|
303
|
+
return await originalMount.call(
|
|
304
|
+
eleva,
|
|
305
|
+
container,
|
|
306
|
+
compName,
|
|
307
|
+
enhancedProps
|
|
308
|
+
);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Expose utility methods on the Eleva instance
|
|
313
|
+
* @namespace eleva.props
|
|
314
|
+
*/
|
|
315
|
+
eleva.props = {
|
|
316
|
+
/**
|
|
317
|
+
* Parse a single value with automatic type detection
|
|
318
|
+
* @param {any} value - The value to parse
|
|
319
|
+
* @returns {any} The parsed value with appropriate type
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* app.props.parse("42") // → 42
|
|
323
|
+
* app.props.parse("true") // → true
|
|
324
|
+
* app.props.parse('{"key": "val"}') // → {key: "val"}
|
|
325
|
+
*/
|
|
326
|
+
parse: (value) => {
|
|
327
|
+
// Return value as-is if auto parsing is disabled
|
|
328
|
+
if (!enableAutoParsing) {
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
// Use our enhanced parsing function
|
|
332
|
+
return parsePropValue(value);
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Detect the type of a value
|
|
337
|
+
* @param {any} value - The value to detect type for
|
|
338
|
+
* @returns {string} The detected type
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* app.props.detectType("hello") // → "string"
|
|
342
|
+
* app.props.detectType(42) // → "number"
|
|
343
|
+
* app.props.detectType([1, 2, 3]) // → "array"
|
|
344
|
+
*/
|
|
345
|
+
detectType,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Store original methods for uninstall
|
|
349
|
+
eleva._originalExtractProps = eleva._extractProps;
|
|
350
|
+
eleva._originalMount = originalMount;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Uninstalls the plugin from the Eleva instance
|
|
355
|
+
*
|
|
356
|
+
* @param {Object} eleva - The Eleva instance
|
|
357
|
+
*
|
|
358
|
+
* @description
|
|
359
|
+
* Restores the original Eleva methods and removes all plugin-specific
|
|
360
|
+
* functionality. This method should be called when the plugin is no
|
|
361
|
+
* longer needed.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* // Uninstall the plugin
|
|
365
|
+
* PropsPlugin.uninstall(app);
|
|
366
|
+
*/
|
|
367
|
+
uninstall(eleva) {
|
|
368
|
+
// Restore original _extractProps method
|
|
369
|
+
if (eleva._originalExtractProps) {
|
|
370
|
+
eleva._extractProps = eleva._originalExtractProps;
|
|
371
|
+
delete eleva._originalExtractProps;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Restore original mount method
|
|
375
|
+
if (eleva._originalMount) {
|
|
376
|
+
eleva.mount = eleva._originalMount;
|
|
377
|
+
delete eleva._originalMount;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Remove plugin utility methods
|
|
381
|
+
if (eleva.props) {
|
|
382
|
+
delete eleva.props;
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
};
|