apprun 3.35.0 → 3.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +134 -16
  3. package/WHATSNEW.md +28 -12
  4. package/apprun-book.jpg +0 -0
  5. package/apprun.d.ts +9 -6
  6. package/cli/app.js +29 -0
  7. package/cli/index.html +13 -0
  8. package/{apprun-cli.js → cli/index.js} +8 -14
  9. package/dist/apprun-dev-tools.js +1 -2
  10. package/dist/apprun-dev-tools.js.map +1 -1
  11. package/dist/apprun-html.esm.js +7 -7
  12. package/dist/apprun-html.esm.js.map +1 -1
  13. package/dist/apprun-html.js +1 -1
  14. package/dist/apprun-html.js.map +1 -1
  15. package/dist/apprun-play-html.esm.js +7 -7
  16. package/dist/apprun-play-html.esm.js.map +1 -1
  17. package/dist/apprun-play.js +1 -1
  18. package/dist/apprun-play.js.map +1 -1
  19. package/dist/apprun.esm.js +1 -1
  20. package/dist/apprun.esm.js.map +1 -1
  21. package/dist/apprun.js +1 -1
  22. package/dist/apprun.js.map +1 -1
  23. package/esm/app.js +60 -12
  24. package/esm/app.js.map +1 -1
  25. package/esm/apprun-dev-tools.js +1 -7
  26. package/esm/apprun-dev-tools.js.map +1 -1
  27. package/esm/apprun-html.js +8 -4
  28. package/esm/apprun-html.js.map +1 -1
  29. package/esm/apprun.js +81 -17
  30. package/esm/apprun.js.map +1 -1
  31. package/esm/component.js +60 -19
  32. package/esm/component.js.map +1 -1
  33. package/esm/decorator.js +22 -5
  34. package/esm/decorator.js.map +1 -1
  35. package/esm/directive.js +64 -13
  36. package/esm/directive.js.map +1 -1
  37. package/esm/router.js +263 -22
  38. package/esm/router.js.map +1 -1
  39. package/esm/type-utils.js +91 -0
  40. package/esm/type-utils.js.map +1 -0
  41. package/esm/types.js +32 -11
  42. package/esm/types.js.map +1 -1
  43. package/esm/vdom-my-new.js +329 -0
  44. package/esm/vdom-my-new.js.map +1 -0
  45. package/esm/vdom-my-prop-attr.js +227 -0
  46. package/esm/vdom-my-prop-attr.js.map +1 -0
  47. package/esm/vdom-my.js +77 -88
  48. package/esm/vdom-my.js.map +1 -1
  49. package/esm/version.js +15 -0
  50. package/esm/version.js.map +1 -0
  51. package/esm/web-component.js +30 -10
  52. package/esm/web-component.js.map +1 -1
  53. package/index.html +1 -1
  54. package/jest.config.js +3 -8
  55. package/jest.setup.js +29 -3
  56. package/jsx-runtime.js +1 -1
  57. package/jsx-runtime.js.map +1 -1
  58. package/package.json +7 -7
  59. package/src/app.ts +58 -12
  60. package/src/apprun-dev-tools.tsx +1 -7
  61. package/src/apprun-html.ts +8 -4
  62. package/src/apprun.ts +97 -20
  63. package/src/component.ts +62 -20
  64. package/src/decorator.ts +23 -6
  65. package/src/directive.ts +64 -13
  66. package/src/router.ts +282 -20
  67. package/src/type-utils.ts +130 -0
  68. package/src/types.ts +33 -12
  69. package/src/vdom-my-new.ts +311 -0
  70. package/src/vdom-my-prop-attr.ts +241 -0
  71. package/src/vdom-my.ts +82 -71
  72. package/src/version.ts +16 -0
  73. package/src/web-component.ts +31 -11
  74. package/cli/export.js +0 -92
  75. package/cli/import.js +0 -68
package/src/directive.ts CHANGED
@@ -2,22 +2,38 @@
2
2
  * Component Directives Implementation
3
3
  *
4
4
  * This file provides built-in directives for components:
5
- * 1. Event Binding
5
+ * 1. Event Binding ($on directives)
6
6
  * - $on: Bind DOM events to component events
7
7
  * - Supports event delegation and parameters
8
8
  * - Handles function, string, and array event handlers
9
+ * - Type-safe event target handling
9
10
  *
10
- * 2. Two-way Data Binding
11
+ * 2. Two-way Data Binding ($bind directive)
11
12
  * - $bind: Sync form elements with component state
12
- * - Handles input types: text, checkbox, radio, select
13
- * - Automatic value type conversion
13
+ * - Handles input types: text, checkbox, radio, number, range
14
+ * - Supports select (single/multiple) and textarea elements
15
+ * - Automatic value type conversion for numbers
14
16
  * - Support for complex property paths
15
17
  *
16
18
  * 3. Custom Directives
17
- * - Extensible directive system
19
+ * - Extensible directive system via '$' events
18
20
  * - Processes virtual DOM during rendering
19
21
  * - Supports custom attribute directives
20
22
  *
23
+ * Features:
24
+ * - Automatic state synchronization
25
+ * - Type conversion for form inputs
26
+ * - Event delegation support
27
+ * - Multiple event handler formats
28
+ * - Nested property binding
29
+ * - Custom directive extensibility
30
+ *
31
+ * Type Safety Improvements (v3.35.1):
32
+ * - Added null checks for event targets before type assertions
33
+ * - Proper typing for different HTML element types
34
+ * - Enhanced error handling for invalid event targets
35
+ * - Safer DOM element property access
36
+ *
21
37
  * Usage:
22
38
  * ```tsx
23
39
  * // Event binding
@@ -27,10 +43,14 @@
27
43
  * // Two-way binding
28
44
  * <input $bind="state.property" />
29
45
  * <select $bind="selected">...</select>
46
+ *
47
+ * // Array handlers
48
+ * <button $onclick={['handler', param1, param2]}>Click</button>
30
49
  * ```
31
50
  */
32
51
 
33
52
  import app from './app';
53
+ import { safeEventTarget } from './type-utils';
34
54
 
35
55
  const getStateValue = (component, name) => {
36
56
  return (name ? component['state'][name] : component['state']) || '';
@@ -72,34 +92,65 @@ const apply_directive = (key: string, props: {}, tag, component) => {
72
92
  switch (type) {
73
93
  case 'checkbox':
74
94
  props['checked'] = getStateValue(component, name);
75
- props['onclick'] = e => setStateValue(component, name || e.target.name, e.target.checked);
95
+ props['onclick'] = e => {
96
+ const target = safeEventTarget<HTMLInputElement>(e);
97
+ if (target) {
98
+ setStateValue(component, name || target.name, target.checked);
99
+ }
100
+ };
76
101
  break;
77
102
  case 'radio':
78
103
  props['checked'] = getStateValue(component, name) === props['value'];
79
- props['onclick'] = e => setStateValue(component, name || e.target.name, e.target.value);
104
+ props['onclick'] = e => {
105
+ const target = safeEventTarget<HTMLInputElement>(e);
106
+ if (target) {
107
+ setStateValue(component, name || target.name, target.value);
108
+ }
109
+ };
80
110
  break;
81
111
  case 'number':
82
112
  case 'range':
83
113
  props['value'] = getStateValue(component, name);
84
- props['oninput'] = e => setStateValue(component, name || e.target.name, Number(e.target.value));
114
+ props['oninput'] = e => {
115
+ const target = safeEventTarget<HTMLInputElement>(e);
116
+ if (target) {
117
+ setStateValue(component, name || target.name, Number(target.value));
118
+ }
119
+ };
85
120
  break;
86
121
  default:
87
122
  props['value'] = getStateValue(component, name);
88
- props['oninput'] = e => setStateValue(component, name || e.target.name, e.target.value);
123
+ props['oninput'] = e => {
124
+ const target = safeEventTarget<HTMLInputElement>(e);
125
+ if (target) {
126
+ setStateValue(component, name || target.name, target.value);
127
+ }
128
+ };
89
129
  }
90
130
  } else if (tag === 'select') {
91
131
  props['value'] = getStateValue(component, name);
92
132
  props['onchange'] = e => {
93
- if (!e.target.multiple) { // multiple selection use $bind on option
94
- setStateValue(component, name || e.target.name, e.target.value);
133
+ const target = safeEventTarget<HTMLSelectElement>(e);
134
+ if (target && !target.multiple) { // multiple selection use $bind on option
135
+ setStateValue(component, name || target.name, target.value);
95
136
  }
96
137
  }
97
138
  } else if (tag === 'option') {
98
139
  props['selected'] = getStateValue(component, name);
99
- props['onclick'] = e => setStateValue(component, name || e.target.name, e.target.selected);
140
+ props['onclick'] = e => {
141
+ const target = safeEventTarget<HTMLOptionElement>(e);
142
+ if (target) {
143
+ setStateValue(component, name || (target as any).name, target.selected);
144
+ }
145
+ };
100
146
  } else if (tag === 'textarea') {
101
147
  props['innerHTML'] = getStateValue(component, name);
102
- props['oninput'] = e => setStateValue(component, name || e.target.name, e.target.value);
148
+ props['oninput'] = e => {
149
+ const target = safeEventTarget<HTMLTextAreaElement>(e);
150
+ if (target) {
151
+ setStateValue(component, name || target.name, target.value);
152
+ }
153
+ };
103
154
  }
104
155
  } else {
105
156
  app.run('$', { key, tag, props, component });
package/src/router.ts CHANGED
@@ -1,30 +1,302 @@
1
1
  /**
2
- * AppRun Router Implementation
2
+ * AppRun Router Implementation with Hierarchical Matching
3
3
  *
4
4
  * This file provides URL routing capabilities:
5
5
  * 1. Hash-based routing (#/path)
6
+ * - Works with SPA mode using hash fragments
7
+ * - Handles hashchange events automatically
8
+ * - No server configuration required
6
9
  * 2. Path-based routing (/path)
10
+ * - Works with browser history API
11
+ * - Requires server configuration for SPA routing
12
+ * - Handles popstate events automatically
7
13
  * 3. Event-based navigation
14
+ * - Routes trigger corresponding component events
15
+ * - Automatic route parameter extraction
16
+ * - Fallback to 404 handling for unknown routes
8
17
  *
9
18
  * Features:
10
- * - Automatic route event firing
11
- * - 404 handling via ROUTER_404_EVENT
12
- * - History API integration
13
- * - Route parameter parsing
19
+ * - Automatic route event firing with ROUTER_EVENT
20
+ * - 404 handling via ROUTER_404_EVENT for unmatched routes
21
+ * - History API integration for seamless navigation
22
+ * - Route parameter parsing and injection into events
23
+ * - URL pattern matching with parameter support
24
+ * - Global and component-level route handling
25
+ * - **NEW: Hierarchical route matching** - progressively tries parent routes
26
+ *
27
+ * Type Safety Improvements (v3.35.1):
28
+ * - Enhanced route type definitions
29
+ * - Better parameter type inference
30
+ * - Improved URL validation and error handling
14
31
  *
15
32
  * Usage:
16
33
  * ```ts
17
- * // Handle routes
34
+ * // Basic routing
18
35
  * app.on('#/home', () => // Show home page);
19
36
  * app.on('#/users/:id', (id) => // Show user profile);
37
+ * app.on('/api/*', (path) => // Handle API routes);
20
38
  *
21
- * // Navigate programmatically
39
+ * // Navigate programmatically
22
40
  * app.run('route', '#/home');
41
+ * route('/users/123'); // Direct routing
42
+ *
43
+ * // Hierarchical matching examples
44
+ * app.on('/api', (operation, id) => // Handle /api/users/123);
45
+ * app.on('#users', (id, action) => // Handle #users/123/edit);
46
+ *
47
+ * // Hierarchical Route Matching (NEW):
48
+ * // For URL: /api/v1/users/123
49
+ * // Router tries: /api/v1/users/123 → /api/v1/users → /api/v1 → /api → 404
50
+ * // If handler found at /api, it receives: ('v1', 'users', '123')
51
+ *
52
+ * // Base Path Support (NEW):
53
+ * app.basePath = '/myapp'; // For sub-directory deployments
54
+ * // Links: <a href="/users/123"> (relative paths)
55
+ * // Navigation: /myapp/users/123 (full path)
56
+ * // Routing: /users/123 (base path stripped)
57
+ *
58
+ * // Empty Path Handling (NEW):
59
+ * // For URL: "" (empty)
60
+ * // Router tries: # → / → #/ → 404 (in priority order)
61
+ *
62
+ * // 404 Behavior (ENHANCED):
63
+ * // Fires only at minimal levels: /a, #a, #/a, a
64
+ * // Never tries root handlers: /, #, #/
23
65
  * ```
24
66
  */
25
67
 
26
68
  import app from './app';
27
69
 
70
+ // Helper functions for hierarchical routing
71
+
72
+ /**
73
+ * Extract clean path segments from URL
74
+ * @param url - The URL to parse
75
+ * @returns Array of path segments
76
+ */
77
+ function parsePathSegments(url: string): string[] {
78
+ if (!url) return [];
79
+
80
+ // Handle different URL types
81
+ if (url.startsWith('#/')) {
82
+ return url.substring(2).split('/');
83
+ } else if (url.startsWith('#')) {
84
+ return url.substring(1).split('/');
85
+ } else if (url.startsWith('/')) {
86
+ return url.substring(1).split('/');
87
+ } else {
88
+ return url.split('/');
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Normalize trailing slash - convert /a/ to /a
94
+ * @param url - The URL to normalize
95
+ * @returns Normalized URL
96
+ */
97
+ function normalizeTrailingSlash(url: string): string {
98
+ if (!url || url === '/' || url === '#' || url === '#/') return url;
99
+ if (url.endsWith('/')) return url.slice(0, -1);
100
+ return url;
101
+ }
102
+
103
+ /**
104
+ * Validate hierarchy depth and warn if too deep
105
+ * @param segments - Path segments to validate
106
+ */
107
+ function validateHierarchyDepth(segments: string[]): void {
108
+ // Count non-empty segments for depth validation
109
+ const nonEmptySegments = segments.filter(Boolean);
110
+ if (nonEmptySegments.length > 11) {
111
+ console.warn(`Deep route hierarchy detected: ${nonEmptySegments.join('/')} (${nonEmptySegments.length} levels)`);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Strip base path from URL
117
+ * @param url - The URL to process
118
+ * @param basePath - The base path to remove
119
+ * @returns URL with base path removed
120
+ */
121
+ function stripBasePath(url: string, basePath: string): string {
122
+ if (!basePath || basePath === '/' || basePath === '') return url;
123
+
124
+ // Normalize base path
125
+ const normalizedBasePath = basePath.startsWith('/') ? basePath : '/' + basePath;
126
+
127
+ if (url.startsWith(normalizedBasePath)) {
128
+ const stripped = url.substring(normalizedBasePath.length);
129
+ return stripped.startsWith('/') ? stripped : '/' + stripped;
130
+ }
131
+
132
+ return url;
133
+ }
134
+
135
+ /**
136
+ * Generate hierarchy of routes to try (stops at minimal level)
137
+ * @param segments - Array of path segments
138
+ * @param routeType - Type of route (path, hash, hash-slash, non-prefixed)
139
+ * @returns Array of route names to try in order
140
+ */
141
+ function generateRouteHierarchy(segments: string[], routeType: 'path' | 'hash' | 'hash-slash' | 'non-prefixed'): string[] {
142
+ const hierarchy: string[] = [];
143
+
144
+ // Build hierarchy from most specific to least specific
145
+ for (let i = segments.length; i > 0; i--) {
146
+ const currentSegments = segments.slice(0, i);
147
+ let routeName = '';
148
+
149
+ switch (routeType) {
150
+ case 'path':
151
+ routeName = '/' + currentSegments.join('/');
152
+ break;
153
+ case 'hash':
154
+ routeName = '#' + currentSegments.join('/');
155
+ break;
156
+ case 'hash-slash':
157
+ routeName = '#/' + currentSegments.join('/');
158
+ break;
159
+ case 'non-prefixed':
160
+ routeName = currentSegments.join('/');
161
+ break;
162
+ }
163
+
164
+ hierarchy.push(routeName);
165
+ }
166
+
167
+ return hierarchy;
168
+ }
169
+
170
+ /**
171
+ * Find handler in hierarchy and return handler info
172
+ * @param hierarchy - Array of route names to try
173
+ * @param originalSegments - Original path segments
174
+ * @returns Handler info if found, null otherwise
175
+ */
176
+ function findHandlerInHierarchy(hierarchy: string[], originalSegments: string[]): { eventName: string; parameters: string[] } | null {
177
+ for (let i = 0; i < hierarchy.length; i++) {
178
+ const routeName = hierarchy[i];
179
+ const subscribers = app.find(routeName);
180
+
181
+ if (subscribers && subscribers.length > 0) {
182
+ // Found handler - calculate remaining parameters
183
+ const handlerDepth = hierarchy.length - i;
184
+ const parameters = originalSegments.slice(handlerDepth);
185
+
186
+ return {
187
+ eventName: routeName,
188
+ parameters: parameters
189
+ };
190
+ }
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Handle empty path with priority order: # → / → #/ → 404
198
+ */
199
+ function handleEmptyPath(): void {
200
+ // Try # first
201
+ const hashSubscribers = app.find('#');
202
+ if (hashSubscribers && hashSubscribers.length > 0) {
203
+ app.run('#');
204
+ app.run(ROUTER_EVENT, '#');
205
+ return;
206
+ }
207
+
208
+ // Try / second
209
+ const pathSubscribers = app.find('/');
210
+ if (pathSubscribers && pathSubscribers.length > 0) {
211
+ app.run('/');
212
+ app.run(ROUTER_EVENT, '/');
213
+ return;
214
+ }
215
+
216
+ // Try #/ third
217
+ const hashSlashSubscribers = app.find('#/');
218
+ if (hashSlashSubscribers && hashSlashSubscribers.length > 0) {
219
+ app.run('#/');
220
+ app.run(ROUTER_EVENT, '#/');
221
+ return;
222
+ }
223
+
224
+ // Fire 404 if no handlers found
225
+ console.warn('No subscribers for event: ');
226
+ app.run(ROUTER_404_EVENT, '');
227
+ app.run(ROUTER_EVENT, '');
228
+ }
229
+
230
+ /**
231
+ * Main hierarchical routing logic
232
+ * @param url - The URL to route
233
+ */
234
+ function routeWithHierarchy(url: string): void {
235
+ // Handle empty path
236
+ if (!url) {
237
+ handleEmptyPath();
238
+ return;
239
+ }
240
+
241
+ // Normalize trailing slash
242
+ url = normalizeTrailingSlash(url);
243
+
244
+ // Strip base path if configured
245
+ const basePath = app['basePath'];
246
+ if (basePath) {
247
+ url = stripBasePath(url, basePath);
248
+ }
249
+
250
+ // Parse segments and validate depth
251
+ const segments = parsePathSegments(url);
252
+ validateHierarchyDepth(segments);
253
+
254
+ // Determine route type
255
+ let routeType: 'path' | 'hash' | 'hash-slash' | 'non-prefixed';
256
+ if (url.startsWith('#/')) {
257
+ routeType = 'hash-slash';
258
+ } else if (url.startsWith('#')) {
259
+ routeType = 'hash';
260
+ } else if (url.startsWith('/')) {
261
+ routeType = 'path';
262
+ } else {
263
+ routeType = 'non-prefixed';
264
+ }
265
+
266
+ // Generate hierarchy
267
+ const hierarchy = generateRouteHierarchy(segments, routeType);
268
+
269
+ // Find handler in hierarchy
270
+ const handlerInfo = findHandlerInHierarchy(hierarchy, segments);
271
+
272
+ if (handlerInfo) {
273
+ // Found handler - publish route with parameters
274
+ publishRoute(handlerInfo.eventName, ...handlerInfo.parameters);
275
+ } else {
276
+ // No handler found - fire 404 with original URL
277
+ if (hierarchy.length > 0) {
278
+ const minimalRoute = hierarchy[hierarchy.length - 1];
279
+ console.warn(`No subscribers for event: ${minimalRoute}`);
280
+ app.run(ROUTER_404_EVENT, url);
281
+ app.run(ROUTER_EVENT, url);
282
+ } else {
283
+ handleEmptyPath();
284
+ }
285
+ }
286
+ }
287
+
288
+ const publishRoute = (name: string, ...args: any[]) => {
289
+ if (!name || name === ROUTER_EVENT || name === ROUTER_404_EVENT) return;
290
+ const subscribers = app.find(name);
291
+ if (!subscribers || subscribers.length === 0) {
292
+ console.warn(`No subscribers for event: ${name}`);
293
+ app.run(ROUTER_404_EVENT, name, ...args);
294
+ } else {
295
+ app.run(name, ...args);
296
+ }
297
+ app.run(ROUTER_EVENT, name, ...args);
298
+ }
299
+
28
300
  export type Route = (url: string, ...args: any[]) => any;
29
301
 
30
302
  export const ROUTER_EVENT: string = '//';
@@ -32,18 +304,8 @@ export const ROUTER_404_EVENT: string = '///';
32
304
  export const route: Route = (url: string) => {
33
305
  if (app['lastUrl'] === url) return; // Prevent duplicate routing
34
306
  app['lastUrl'] = url;
35
- if (!url) url = '#';
36
- if (url.startsWith('#')) {
37
- const [name, ...rest] = url.split('/');
38
- app.run(name, ...rest) || app.run(ROUTER_404_EVENT, name, ...rest);
39
- app.run(ROUTER_EVENT, name, ...rest);
40
- } else if (url.startsWith('/')) {
41
- const [_, name, ...rest] = url.split('/');
42
- app.run('/' + name, ...rest) || app.run(ROUTER_404_EVENT, '/' + name, ...rest);
43
- app.run(ROUTER_EVENT, '/' + name, ...rest);
44
- } else {
45
- app.run(url) || app.run(ROUTER_404_EVENT, url);
46
- app.run(ROUTER_EVENT, url);
47
- }
307
+
308
+ // Use hierarchical routing logic
309
+ routeWithHierarchy(url);
48
310
  }
49
311
  export default route;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Type Safety Utilities for AppRun
3
+ *
4
+ * This file provides utility functions and types to improve type safety
5
+ * across the AppRun framework, including:
6
+ * 1. Safe type assertions with null checks
7
+ * 2. Global object assignment helpers
8
+ * 3. Event target type guards
9
+ * 4. Function validation utilities
10
+ */
11
+
12
+ /**
13
+ * Safely cast event target to specific HTML element type with null check
14
+ */
15
+ export function safeEventTarget<T extends HTMLElement = HTMLElement>(event: Event): T | null {
16
+ return event?.target instanceof HTMLElement ? event.target as T : null;
17
+ }
18
+
19
+ /**
20
+ * Type guard to check if an object is a valid function
21
+ */
22
+ export function isFunction(obj: any): obj is Function {
23
+ return typeof obj === 'function';
24
+ }
25
+
26
+ /**
27
+ * Type guard to check if an object is a valid HTML element
28
+ */
29
+ export function isHTMLElement(obj: any): obj is HTMLElement {
30
+ return obj instanceof HTMLElement;
31
+ }
32
+
33
+ /**
34
+ * Safely assign properties to global object with type checking
35
+ */
36
+ export function safeGlobalAssign<T extends Record<string, any>>(
37
+ globalObj: any,
38
+ assignments: T
39
+ ): void {
40
+ if (typeof globalObj === 'object' && globalObj !== null) {
41
+ Object.keys(assignments).forEach(key => {
42
+ globalObj[key] = assignments[key];
43
+ });
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Safely get property from global object with fallback
49
+ */
50
+ export function safeGlobalGet<T>(
51
+ globalObj: any,
52
+ property: string,
53
+ fallback?: T
54
+ ): T | undefined {
55
+ if (typeof globalObj === 'object' && globalObj !== null) {
56
+ return globalObj[property] ?? fallback;
57
+ }
58
+ return fallback;
59
+ }
60
+
61
+ /**
62
+ * Type-safe wrapper for DOM element queries
63
+ */
64
+ export function safeQuerySelector<T extends Element = Element>(
65
+ selector: string,
66
+ context: Document | Element = document
67
+ ): T | null {
68
+ try {
69
+ return context.querySelector<T>(selector);
70
+ } catch (error) {
71
+ console.warn(`Invalid selector: ${selector}`, error);
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Type-safe wrapper for DOM element by ID
78
+ */
79
+ export function safeGetElementById<T extends HTMLElement = HTMLElement>(
80
+ id: string
81
+ ): T | null {
82
+ try {
83
+ return document.getElementById(id) as T | null;
84
+ } catch (error) {
85
+ console.warn(`Error getting element by id: ${id}`, error);
86
+ return null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Validate and safely execute a function with error handling
92
+ */
93
+ export function safeExecute<T extends any[], R>(
94
+ fn: ((...args: T) => R) | undefined | null,
95
+ args: T,
96
+ context?: any,
97
+ errorMessage?: string
98
+ ): R | undefined {
99
+ if (!isFunction(fn)) {
100
+ if (errorMessage) {
101
+ console.warn(errorMessage, fn);
102
+ }
103
+ return undefined;
104
+ }
105
+
106
+ try {
107
+ return context ? fn.apply(context, args) : fn(...args);
108
+ } catch (error) {
109
+ console.error(errorMessage || 'Error executing function:', error);
110
+ return undefined;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Type definitions for improved global object safety
116
+ */
117
+ export interface SafeGlobalWindow {
118
+ Component?: any;
119
+ _React?: any;
120
+ React?: any;
121
+ on?: any;
122
+ customElement?: any;
123
+ safeHTML?: any;
124
+ html?: any;
125
+ svg?: any;
126
+ run?: any;
127
+ [key: string]: any;
128
+ }
129
+
130
+ export type SafeGlobalContext = SafeGlobalWindow | typeof globalThis | any;
package/src/types.ts CHANGED
@@ -4,26 +4,47 @@
4
4
  * This file defines the fundamental types used across AppRun:
5
5
  * 1. Component Types
6
6
  * - View: Function that renders state to VDOM
7
- * - Action: Function that updates state
8
- * - Update: Collection of actions
7
+ * - Action: Function that updates state (sync/async)
8
+ * - Update: Collection of actions (array or object format)
9
+ * - ActionDef: Tuple definition for action arrays
9
10
  *
10
11
  * 2. Virtual DOM Types
11
- * - VNode: Virtual DOM node structure
12
- * - VDOM: Union of possible VDOM types
13
- * - Element: DOM element references
12
+ * - VNode: Virtual DOM node structure with tag/props/children
13
+ * - VDOM: Union of possible VDOM types (false, string, VNode, array)
14
+ * - Element: DOM element references (HTMLElement or string selector)
15
+ * - TemplateResult: Lit-html template support
14
16
  *
15
17
  * 3. Configuration Types
16
- * - EventOptions: Event handler options (once, delay, etc)
17
- * - ActionOptions: Action behavior options (render, history, etc)
18
- * - MountOptions: Component mounting options (global events, routing)
19
- * - AppStartOptions: Application startup configuration
18
+ * - EventOptions: Event handler options (once, delay, transition)
19
+ * - ActionOptions: Action behavior options (render, history, global, callback)
20
+ * - MountOptions: Component mounting options (global events, routing, transitions)
21
+ * - AppStartOptions: Application startup configuration with lifecycle hooks
20
22
  *
23
+ * Features:
24
+ * - Strong typing for component lifecycle
25
+ * - Flexible action definition formats
26
+ * - Comprehensive event options
27
+ * - Integration with external libraries (lit-html)
28
+ * - Type-safe component mounting
29
+ * - Lifecycle hook typing
30
+ *
31
+ * Type Safety Improvements (v3.35.1):
32
+ * - Enhanced generic constraints for better type inference
33
+ * - Improved union types for VDOM flexibility
34
+ * - Better callback typing in options
35
+ * - Stricter typing for lifecycle methods
36
+ *
21
37
  * Usage:
22
38
  * ```ts
23
- * class MyComponent implements Component<State> {
39
+ * class MyComponent extends Component<State, Events> {
24
40
  * view: View<State>;
25
- * update: Update<State>;
41
+ * update: Update<State, Events>;
26
42
  * }
43
+ *
44
+ * // Type-safe action definitions
45
+ * const update: Update<State> = {
46
+ * 'event': (state: State, ...args) => newState
47
+ * };
27
48
  * ```
28
49
  */
29
50
 
@@ -59,5 +80,5 @@ export type AppStartOptions<T> = {
59
80
  transition?: boolean;
60
81
  route?: string;
61
82
  rendered?: (state: T) => void
62
- mounted?: (props:any, children:any, state: T) => T
83
+ mounted?: (props: any, children: any, state: T) => T
63
84
  };