apprun 3.35.0 → 3.36.0

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 (58) hide show
  1. package/README.md +5 -0
  2. package/WHATSNEW.md +9 -1
  3. package/apprun.d.ts +4 -4
  4. package/dist/apprun-dev-tools.js +1 -1
  5. package/dist/apprun-dev-tools.js.map +1 -1
  6. package/dist/apprun-html.esm.js +7 -7
  7. package/dist/apprun-html.esm.js.map +1 -1
  8. package/dist/apprun-html.js +1 -1
  9. package/dist/apprun-html.js.map +1 -1
  10. package/dist/apprun-play-html.esm.js +7 -7
  11. package/dist/apprun-play-html.esm.js.map +1 -1
  12. package/dist/apprun-play.js +1 -1
  13. package/dist/apprun-play.js.map +1 -1
  14. package/dist/apprun.esm.js +1 -1
  15. package/dist/apprun.esm.js.map +1 -1
  16. package/dist/apprun.js +1 -1
  17. package/dist/apprun.js.map +1 -1
  18. package/esm/app.js +55 -11
  19. package/esm/app.js.map +1 -1
  20. package/esm/apprun-html.js +8 -4
  21. package/esm/apprun-html.js.map +1 -1
  22. package/esm/apprun.js +64 -14
  23. package/esm/apprun.js.map +1 -1
  24. package/esm/component.js +56 -19
  25. package/esm/component.js.map +1 -1
  26. package/esm/decorator.js +22 -5
  27. package/esm/decorator.js.map +1 -1
  28. package/esm/directive.js +64 -13
  29. package/esm/directive.js.map +1 -1
  30. package/esm/router.js +22 -4
  31. package/esm/router.js.map +1 -1
  32. package/esm/type-utils.js +91 -0
  33. package/esm/type-utils.js.map +1 -0
  34. package/esm/types.js +32 -11
  35. package/esm/types.js.map +1 -1
  36. package/esm/vdom-my.js +66 -43
  37. package/esm/vdom-my.js.map +1 -1
  38. package/esm/version.js +15 -0
  39. package/esm/version.js.map +1 -0
  40. package/esm/web-component.js +30 -10
  41. package/esm/web-component.js.map +1 -1
  42. package/jest.config.js +1 -7
  43. package/jsx-runtime.js +1 -1
  44. package/jsx-runtime.js.map +1 -1
  45. package/package.json +1 -1
  46. package/plan/plan-apprun-bugfixes.md +207 -0
  47. package/src/app.ts +52 -11
  48. package/src/apprun-html.ts +8 -4
  49. package/src/apprun.ts +76 -17
  50. package/src/component.ts +58 -20
  51. package/src/decorator.ts +23 -6
  52. package/src/directive.ts +64 -13
  53. package/src/router.ts +23 -5
  54. package/src/type-utils.ts +130 -0
  55. package/src/types.ts +33 -12
  56. package/src/vdom-my.ts +75 -39
  57. package/src/version.ts +16 -0
  58. package/src/web-component.ts +31 -11
package/src/apprun.ts CHANGED
@@ -7,8 +7,9 @@
7
7
  * 3. Initializes global app instance with:
8
8
  * - Virtual DOM rendering
9
9
  * - Component system
10
- * - Router
10
+ * - Router with improved null safety
11
11
  * - Web component support
12
+ * - Type-safe React integration
12
13
  *
13
14
  * Key exports:
14
15
  * - app: Global event system instance
@@ -17,6 +18,22 @@
17
18
  * - Router events and configuration
18
19
  * - Web component registration
19
20
  *
21
+ * Features:
22
+ * - Event-driven architecture with pub/sub pattern
23
+ * - Virtual DOM rendering with multiple renderer support
24
+ * - Component lifecycle management
25
+ * - Client-side routing with hash/path support
26
+ * - Web Components integration
27
+ * - React compatibility layer
28
+ * - TypeScript support with strong typing
29
+ *
30
+ * Type Safety Improvements (v3.35.1):
31
+ * - Added null checks for DOM event targets
32
+ * - Improved global window object assignments with proper typing
33
+ * - Enhanced React integration parameter validation
34
+ * - Better error handling for invalid event handlers
35
+ * - Safer element access with proper type assertions
36
+ *
20
37
  * Usage:
21
38
  * ```ts
22
39
  * import { app, Component } from 'apprun';
@@ -39,6 +56,7 @@ import { VNode, View, Action, Update, EventOptions, ActionOptions, MountOptions,
39
56
  import { on, update, customElement } from './decorator';
40
57
  import webComponent, { CustomElementOptions } from './web-component';
41
58
  import { Route, route, ROUTER_EVENT, ROUTER_404_EVENT } from './router';
59
+ import { APPRUN_VERSION } from './version';
42
60
 
43
61
  export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
44
62
  type OnDecorator = {
@@ -70,9 +88,12 @@ export interface IApp {
70
88
  start<T, E = any>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
71
89
  options?: AppStartOptions<T>): Component<T, E>;
72
90
  on(name: string, fn: (...args: any[]) => void, options?: any): void;
91
+ once(name: string, fn: (...args: any[]) => void, options?: any): void;
73
92
  off(name: string, fn: (...args: any[]) => void): void;
74
93
  run(name: string, ...args: any[]): number;
75
94
  find(name: string): any | any[];
95
+ query(name: string, ...args: any[]): Promise<any[]>;
96
+ runAsync(name: string, ...args: any[]): Promise<any[]>;
76
97
  h(tag: string | Function, props, ...children): VNode | VNode[];
77
98
  createElement(tag: string | Function, props, ...children): VNode | VNode[];
78
99
  render(element: Element | string, node: VNode): void;
@@ -81,12 +102,13 @@ export interface IApp {
81
102
  webComponent(name: string, componentClass, options?: CustomElementOptions): void;
82
103
  safeHTML(html: string): any[];
83
104
  use_render(render, mode?: 0 | 1);
84
- use_react(createRoot);
105
+ use_react(React, ReactDOM);
106
+ version: string;
85
107
  }
86
108
 
87
109
  if (!app.start) {
88
110
 
89
- app.version = '3.35.0';
111
+ app.version = APPRUN_VERSION;
90
112
 
91
113
  app.h = app.createElement = createElement;
92
114
  app.render = render;
@@ -104,6 +126,12 @@ if (!app.start) {
104
126
  return component;
105
127
  };
106
128
 
129
+ app.once = app.once || ((name: string, fn: (...args: any[]) => void, options: any = {}) => {
130
+ app.on(name, fn, { ...options, once: true });
131
+ });
132
+
133
+ app.query = app.query || app.runAsync;
134
+
107
135
  const NOOP = _ => {/* Intentionally empty */ }
108
136
  // app.on('$', NOOP);
109
137
  app.on('debug', _ => NOOP);
@@ -114,7 +142,7 @@ if (!app.start) {
114
142
 
115
143
  if (typeof document === 'object') {
116
144
  document.addEventListener("DOMContentLoaded", () => {
117
- const init_load = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
145
+ const no_init_route = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
118
146
  const use_hash = app.find('#') || app.find('#/') || false;
119
147
 
120
148
  // console.log(`AppRun ${app.version} started with ${use_hash ? 'hash' : 'path'} routing. Initial load: ${init_load ? 'disabled' : 'enabled'}.`);
@@ -122,14 +150,17 @@ if (!app.start) {
122
150
  window.addEventListener('popstate', () => route(location.pathname));
123
151
 
124
152
  if (use_hash) {
125
- init_load && route(location.hash);
153
+ !no_init_route && route(location.hash);
126
154
  } else {
127
- init_load && route(location.pathname);
155
+ !no_init_route && route(location.pathname);
128
156
  document.body.addEventListener('click', e => {
129
157
  const element = e.target as HTMLElement;
158
+ if (!element) return;
159
+
130
160
  const menu = (element.tagName === 'A' ? element : element.closest('a')) as HTMLAnchorElement;
131
161
  if (menu &&
132
- menu.origin === location.origin) {
162
+ menu.origin === location.origin &&
163
+ menu.pathname) {
133
164
  e.preventDefault();
134
165
  history.pushState(null, '', menu.pathname);
135
166
  route(menu.pathname);
@@ -144,12 +175,13 @@ if (!app.start) {
144
175
  };
145
176
 
146
177
  if (typeof window === 'object') {
147
- window['Component'] = Component as ComponentType;
148
- window['_React'] = window['React'];
149
- window['React'] = app;
150
- window['on'] = on as OnDecorator;
151
- window['customElement'] = customElement;
152
- window['safeHTML'] = safeHTML;
178
+ const globalWindow = window as any;
179
+ globalWindow['Component'] = Component as ComponentType;
180
+ globalWindow['_React'] = globalWindow['React'];
181
+ globalWindow['React'] = app;
182
+ globalWindow['on'] = on as OnDecorator;
183
+ globalWindow['customElement'] = customElement;
184
+ globalWindow['safeHTML'] = safeHTML;
153
185
  }
154
186
 
155
187
  app.use_render = (render, mode = 0) => {
@@ -161,15 +193,42 @@ if (!app.start) {
161
193
  };
162
194
 
163
195
  app.use_react = (React, ReactDOM) => {
196
+ if (!React || !ReactDOM) {
197
+ console.error('AppRun use_react: React and ReactDOM parameters are required');
198
+ return;
199
+ }
200
+
201
+ if (typeof React.createElement !== 'function') {
202
+ console.error('AppRun use_react: Invalid React object - createElement method not found');
203
+ return;
204
+ }
205
+
206
+ if (!React.Fragment) {
207
+ console.error('AppRun use_react: Invalid React object - Fragment not found');
208
+ return;
209
+ }
210
+
164
211
  app.h = app.createElement = React.createElement;
165
212
  app.Fragment = React.Fragment;
166
- app.render = (el, vdom) => ReactDOM.render(vdom, el);
213
+
214
+ // React 18+ uses createRoot API
167
215
  if (React.version && React.version.startsWith('18')) {
216
+ if (!ReactDOM.createRoot || typeof ReactDOM.createRoot !== 'function') {
217
+ console.error('AppRun use_react: ReactDOM.createRoot not found in React 18+');
218
+ return;
219
+ }
168
220
  app.render = (el, vdom) => {
169
- if (!el || !vdom) return;
170
- if (!el._root) el._root = ReactDOM.createRoot(el);
171
- el._root.render(vdom);
221
+ if (!el || vdom === undefined) return;
222
+ if (!(el as any)._root) (el as any)._root = ReactDOM.createRoot(el);
223
+ (el as any)._root.render(vdom);
224
+ }
225
+ } else {
226
+ // Legacy React versions
227
+ if (!ReactDOM.render || typeof ReactDOM.render !== 'function') {
228
+ console.error('AppRun use_react: ReactDOM.render not found in legacy React');
229
+ return;
172
230
  }
231
+ app.render = (el, vdom) => ReactDOM.render(vdom, el);
173
232
  }
174
233
  }
175
234
  }
package/src/component.ts CHANGED
@@ -3,19 +3,38 @@
3
3
  *
4
4
  * This file provides the Component class which is the foundation for:
5
5
  * 1. State Management
6
- * - Maintains component state
7
- * - Handles state updates
8
- * - Supports state history
6
+ * - Maintains component state with history support
7
+ * - Handles state updates with async/iterator support
8
+ * - Supports state history navigation (prev/next)
9
+ * - Promise and async iterator state handling
9
10
  *
10
11
  * 2. View Rendering
11
- * - Renders virtual DOM to real DOM
12
+ * - Renders virtual DOM to real DOM with directives
12
13
  * - Handles component lifecycle (mounted, rendered, unload)
13
14
  * - Supports shadow DOM and web components
15
+ * - DOM change tracking with MutationObserver
16
+ * - View transition API support
14
17
  *
15
18
  * 3. Event Handling
16
- * - Local and global event subscription
19
+ * - Local and global event subscription management
17
20
  * - Event handler registration via decorators
18
- * - Action to state updates
21
+ * - Action to state updates with error handling
22
+ * - Support for event options (delay, once, global)
23
+ *
24
+ * Features:
25
+ * - Component caching for debugging
26
+ * - Element tracking for cleanup
27
+ * - History navigation support
28
+ * - Global vs local event routing
29
+ * - Async state handling
30
+ * - Memory leak prevention
31
+ * - Component unmounting with cleanup
32
+ *
33
+ * Type Safety Improvements (v3.35.1):
34
+ * - Enhanced element access with null checks and warnings
35
+ * - Improved action validation and error handling
36
+ * - Better error reporting in component actions
37
+ * - Safer DOM element queries with fallback warnings
19
38
  *
20
39
  * Usage:
21
40
  * ```ts
@@ -36,6 +55,7 @@ import app, { App } from './app';
36
55
  import { Reflect } from './decorator'
37
56
  import { View, Update, ActionDef, ActionOptions, MountOptions, EventOptions } from './types';
38
57
  import directive from './directive';
58
+ import { safeQuerySelector, safeGetElementById } from './type-utils';
39
59
 
40
60
  const componentCache = new Map();
41
61
  if (!app.find('get-components')) app.on('get-components', o => o.components = componentCache);
@@ -74,9 +94,12 @@ export class Component<T = any, E = any> {
74
94
  if (typeof document !== 'object') return;
75
95
 
76
96
  const el = (typeof this.element === 'string' && this.element) ?
77
- document.getElementById(this.element) || document.querySelector(this.element) : this.element;
97
+ safeGetElementById(this.element) || safeQuerySelector(this.element) : this.element;
78
98
 
79
- if (!el) return;
99
+ if (!el) {
100
+ console.warn(`Component element not found: ${this.element}`);
101
+ return;
102
+ }
80
103
  const tracking_attr = '_c';
81
104
  if (!this.unload) {
82
105
  el.removeAttribute && el.removeAttribute(tracking_attr);
@@ -236,7 +259,10 @@ export class Component<T = any, E = any> {
236
259
  }
237
260
 
238
261
  add_action(name: string, action, options: ActionOptions = {}) {
239
- if (!action || typeof action !== 'function') return;
262
+ if (!action || typeof action !== 'function') {
263
+ console.warn(`Component action for '${name}' is not a valid function:`, action);
264
+ return;
265
+ }
240
266
  if (options.global) this._global_events.push(name);
241
267
  this.on(name as any, (...p) => {
242
268
 
@@ -248,18 +274,30 @@ export class Component<T = any, E = any> {
248
274
  options
249
275
  });
250
276
 
251
- const newState = action(this.state, ...p);
252
-
253
- app['debug'] && app.run('debug', {
254
- component: this,
255
- _: '<',
256
- event: name, p,
257
- newState,
258
- state: this.state,
259
- options
260
- });
277
+ try {
278
+ const newState = action(this.state, ...p);
279
+
280
+ app['debug'] && app.run('debug', {
281
+ component: this,
282
+ _: '<',
283
+ event: name, p,
284
+ newState,
285
+ state: this.state,
286
+ options
287
+ });
261
288
 
262
- this.setState(newState, options)
289
+ this.setState(newState, options);
290
+ } catch (error) {
291
+ console.error(`Error in component action '${name}':`, error);
292
+ app['debug'] && app.run('debug', {
293
+ component: this,
294
+ _: '!',
295
+ event: name, p,
296
+ error,
297
+ state: this.state,
298
+ options
299
+ });
300
+ }
263
301
  }, options);
264
302
  }
265
303
 
package/src/decorator.ts CHANGED
@@ -5,30 +5,47 @@ import webComponent, { CustomElementOptions } from './web-component';
5
5
  *
6
6
  * This file provides decorators that enable:
7
7
  * 1. Event Handler Registration
8
- * - @on(): Subscribe to events with options
9
- * - @update(): Define state updates with metadata
8
+ * - @on(): Subscribe to events with options (global, once, delay)
9
+ * - @update(): Define state updates with metadata and options
10
+ * - Supports method and class decoration patterns
10
11
  *
11
12
  * 2. Web Component Integration
12
13
  * - @customElement(): Register as custom element with options
13
14
  * - Handles shadow DOM and attribute observation
15
+ * - Automatic lifecycle management for web components
14
16
  *
15
17
  * 3. Metadata Management
16
18
  * - Custom Reflect implementation for decorator metadata
17
- * - Stores event handler and update metadata
19
+ * - Stores event handler and update metadata for runtime use
18
20
  * - Supports runtime reflection for dynamic behavior
21
+ * - Metadata keys for event bindings and updates
19
22
  *
23
+ * Features:
24
+ * - Event handler decoration with options
25
+ * - State update method decoration
26
+ * - Web component registration
27
+ * - Metadata-driven event binding
28
+ * - Flexible decorator patterns
29
+ * - Runtime metadata access
30
+ *
31
+ * Type Safety Improvements (v3.35.1):
32
+ * - Enhanced decorator typing for better IDE support
33
+ * - Improved metadata key management
34
+ * - Better type inference for decorated methods
35
+ *
20
36
  * Usage:
21
37
  * ```ts
22
38
  * @customElement('my-element')
23
39
  * class MyComponent extends Component {
24
- * @on('event')
40
+ * @on('event', { global: true })
25
41
  * handler(state, ...args) {
26
42
  * // Handle event
27
43
  * }
28
44
  *
29
- * @update('event')
45
+ * @update('event', { render: true })
30
46
  * updater(state, ...args) {
31
47
  * // Update state
48
+ * return newState;
32
49
  * }
33
50
  * }
34
51
  * ```
@@ -55,7 +72,7 @@ export const Reflect = {
55
72
  }
56
73
  }
57
74
 
58
- export function update<E=string>(events?: E, options: any = {}) {
75
+ export function update<E = string>(events?: E, options: any = {}) {
59
76
  return (target: any, key: string, descriptor: any) => {
60
77
  const name = events ? events.toString() : key;
61
78
  Reflect.defineMetadata(`apprun-update:${name}`,
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
@@ -3,23 +3,41 @@
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
+ *
26
+ * Type Safety Improvements (v3.35.1):
27
+ * - Enhanced route type definitions
28
+ * - Better parameter type inference
29
+ * - Improved URL validation and error handling
14
30
  *
15
31
  * Usage:
16
32
  * ```ts
17
33
  * // Handle routes
18
34
  * app.on('#/home', () => // Show home page);
19
35
  * app.on('#/users/:id', (id) => // Show user profile);
36
+ * app.on('/api/*', (path) => // Handle API routes);
20
37
  *
21
- * // Navigate programmatically
38
+ * // Navigate programmatically
22
39
  * app.run('route', '#/home');
40
+ * route('/users/123'); // Direct routing
23
41
  * ```
24
42
  */
25
43
 
@@ -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;