eleva 1.0.0-rc.4 → 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.
@@ -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
+ };
@@ -32,3 +32,4 @@
32
32
  // Export plugins with clean names
33
33
  export { AttrPlugin as Attr } from "./Attr.js";
34
34
  export { RouterPlugin as Router } from "./Router.js";
35
+ export { PropsPlugin as Props } from "./Props.js";
@@ -0,0 +1,48 @@
1
+ export namespace PropsPlugin {
2
+ let name: string;
3
+ let version: string;
4
+ let description: string;
5
+ /**
6
+ * Installs the plugin into the Eleva instance
7
+ *
8
+ * @param {Object} eleva - The Eleva instance
9
+ * @param {Object} options - Plugin configuration options
10
+ * @param {boolean} [options.enableAutoParsing=true] - Enable automatic type detection and parsing
11
+ * @param {boolean} [options.enableReactivity=true] - Enable reactive prop updates using Eleva's signal system
12
+ * @param {Function} [options.onError=null] - Error handler function called when parsing fails
13
+ *
14
+ * @example
15
+ * // Basic installation
16
+ * app.use(PropsPlugin);
17
+ *
18
+ * // Installation with custom options
19
+ * app.use(PropsPlugin, {
20
+ * enableAutoParsing: true,
21
+ * enableReactivity: false,
22
+ * onError: (error, value) => {
23
+ * console.error('Props parsing error:', error, value);
24
+ * }
25
+ * });
26
+ */
27
+ function install(eleva: Object, options?: {
28
+ enableAutoParsing?: boolean | undefined;
29
+ enableReactivity?: boolean | undefined;
30
+ onError?: Function | undefined;
31
+ }): void;
32
+ /**
33
+ * Uninstalls the plugin from the Eleva instance
34
+ *
35
+ * @param {Object} eleva - The Eleva instance
36
+ *
37
+ * @description
38
+ * Restores the original Eleva methods and removes all plugin-specific
39
+ * functionality. This method should be called when the plugin is no
40
+ * longer needed.
41
+ *
42
+ * @example
43
+ * // Uninstall the plugin
44
+ * PropsPlugin.uninstall(app);
45
+ */
46
+ function uninstall(eleva: Object): void;
47
+ }
48
+ //# sourceMappingURL=Props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Props.d.ts","sourceRoot":"","sources":["../../src/plugins/Props.js"],"names":[],"mappings":";cAqDY,MAAM;iBAMN,MAAM;qBAMN,MAAM;IAKhB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,wBAnBW,MAAM,YAEd;QAA0B,iBAAiB;QACjB,gBAAgB;QACf,OAAO;KAElC,QA+QF;IAED;;;;;;;;;;;;;OAaG;IACH,0BAXW,MAAM,QA4BhB"}
@@ -1,3 +1,4 @@
1
1
  export { AttrPlugin as Attr } from "./Attr.js";
2
2
  export { RouterPlugin as Router } from "./Router.js";
3
+ export { PropsPlugin as Props } from "./Props.js";
3
4
  //# sourceMappingURL=index.d.ts.map