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.
- package/CHANGELOG.md +28 -0
- package/README.md +134 -16
- package/WHATSNEW.md +28 -12
- package/apprun-book.jpg +0 -0
- package/apprun.d.ts +9 -6
- package/cli/app.js +29 -0
- package/cli/index.html +13 -0
- package/{apprun-cli.js → cli/index.js} +8 -14
- package/dist/apprun-dev-tools.js +1 -2
- package/dist/apprun-dev-tools.js.map +1 -1
- package/dist/apprun-html.esm.js +7 -7
- package/dist/apprun-html.esm.js.map +1 -1
- package/dist/apprun-html.js +1 -1
- package/dist/apprun-html.js.map +1 -1
- package/dist/apprun-play-html.esm.js +7 -7
- package/dist/apprun-play-html.esm.js.map +1 -1
- package/dist/apprun-play.js +1 -1
- package/dist/apprun-play.js.map +1 -1
- package/dist/apprun.esm.js +1 -1
- package/dist/apprun.esm.js.map +1 -1
- package/dist/apprun.js +1 -1
- package/dist/apprun.js.map +1 -1
- package/esm/app.js +60 -12
- package/esm/app.js.map +1 -1
- package/esm/apprun-dev-tools.js +1 -7
- package/esm/apprun-dev-tools.js.map +1 -1
- package/esm/apprun-html.js +8 -4
- package/esm/apprun-html.js.map +1 -1
- package/esm/apprun.js +81 -17
- package/esm/apprun.js.map +1 -1
- package/esm/component.js +60 -19
- package/esm/component.js.map +1 -1
- package/esm/decorator.js +22 -5
- package/esm/decorator.js.map +1 -1
- package/esm/directive.js +64 -13
- package/esm/directive.js.map +1 -1
- package/esm/router.js +263 -22
- package/esm/router.js.map +1 -1
- package/esm/type-utils.js +91 -0
- package/esm/type-utils.js.map +1 -0
- package/esm/types.js +32 -11
- package/esm/types.js.map +1 -1
- package/esm/vdom-my-new.js +329 -0
- package/esm/vdom-my-new.js.map +1 -0
- package/esm/vdom-my-prop-attr.js +227 -0
- package/esm/vdom-my-prop-attr.js.map +1 -0
- package/esm/vdom-my.js +77 -88
- package/esm/vdom-my.js.map +1 -1
- package/esm/version.js +15 -0
- package/esm/version.js.map +1 -0
- package/esm/web-component.js +30 -10
- package/esm/web-component.js.map +1 -1
- package/index.html +1 -1
- package/jest.config.js +3 -8
- package/jest.setup.js +29 -3
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +7 -7
- package/src/app.ts +58 -12
- package/src/apprun-dev-tools.tsx +1 -7
- package/src/apprun-html.ts +8 -4
- package/src/apprun.ts +97 -20
- package/src/component.ts +62 -20
- package/src/decorator.ts +23 -6
- package/src/directive.ts +64 -13
- package/src/router.ts +282 -20
- package/src/type-utils.ts +130 -0
- package/src/types.ts +33 -12
- package/src/vdom-my-new.ts +311 -0
- package/src/vdom-my-prop-attr.ts +241 -0
- package/src/vdom-my.ts +82 -71
- package/src/version.ts +16 -0
- package/src/web-component.ts +31 -11
- package/cli/export.js +0 -92
- package/cli/import.js +0 -68
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apprun",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.36.1",
|
|
4
4
|
"description": "JavaScript library that has Elm inspired architecture, event pub-sub and components",
|
|
5
5
|
"main": "dist/apprun.js",
|
|
6
6
|
"module": "esm/apprun.js",
|
|
7
7
|
"types": "apprun.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"apprun": "
|
|
9
|
+
"apprun": "cli/index.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p src && rollup -c && webpack --mode production",
|
|
13
|
-
"test": "jest
|
|
14
|
-
"jest": "jest",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"jest": "jest --watch",
|
|
15
15
|
"jest:debug": "node --inspect node_modules/.bin/jest --runInBand",
|
|
16
16
|
"start": "webpack serve --mode development",
|
|
17
17
|
"build:docs": "marked -i README.md -o demo/home.html & marked -i WHATSNEW.md -o demo/new.html",
|
|
@@ -53,10 +53,10 @@
|
|
|
53
53
|
"typescript": "^5.7.2",
|
|
54
54
|
"webpack": "^5.97.1",
|
|
55
55
|
"webpack-cli": "^6.0.1",
|
|
56
|
-
"webpack-dev-server": "^5.2.0"
|
|
56
|
+
"webpack-dev-server": "^5.2.0",
|
|
57
|
+
"property-information": "^7.1.0"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
|
-
"commander": "^14.0.0"
|
|
60
|
-
"js-yaml": "^4.1.0"
|
|
60
|
+
"commander": "^14.0.0"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/app.ts
CHANGED
|
@@ -3,16 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This file provides:
|
|
5
5
|
* 1. App class - The core event system implementation with pub/sub capabilities
|
|
6
|
-
* - on(): Subscribe to events
|
|
7
|
-
* - off(): Unsubscribe from events
|
|
8
|
-
* - run(): Publish events synchronously
|
|
9
|
-
* - runAsync(): Publish events asynchronously
|
|
10
|
-
* - query():
|
|
6
|
+
* - on(): Subscribe to events with options (once, delay, global)
|
|
7
|
+
* - off(): Unsubscribe from events with proper cleanup
|
|
8
|
+
* - run(): Publish events synchronously with error handling
|
|
9
|
+
* - runAsync(): Publish events asynchronously with Promise support, returns handler values
|
|
10
|
+
* - query(): Deprecated alias for runAsync() - use runAsync() instead
|
|
11
11
|
*
|
|
12
12
|
* 2. Default app singleton - Global event bus instance
|
|
13
13
|
* - Created once and reused across the application
|
|
14
|
-
* - Stored in global scope (window/global)
|
|
15
|
-
* -
|
|
14
|
+
* - Stored in global scope (window/global) with version tracking
|
|
15
|
+
* - Prevents duplicate instances across different versions
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Event wildcards support (events ending with '*')
|
|
19
|
+
* - Delayed event execution with timeout management
|
|
20
|
+
* - Once-only event subscriptions
|
|
21
|
+
* - Async event handling with Promise.all
|
|
22
|
+
* - Global event bus shared across components
|
|
23
|
+
* - Memory leak prevention with proper cleanup
|
|
24
|
+
*
|
|
25
|
+
* Type Safety Improvements (v3.35.1):
|
|
26
|
+
* - Added validation for event handler functions
|
|
27
|
+
* - Enhanced error handling in event execution
|
|
28
|
+
* - Improved null checks in delayed event handling
|
|
29
|
+
* - Better error reporting for invalid handlers
|
|
16
30
|
*
|
|
17
31
|
* Usage:
|
|
18
32
|
* ```ts
|
|
@@ -21,12 +35,18 @@
|
|
|
21
35
|
* // Handle event
|
|
22
36
|
* });
|
|
23
37
|
*
|
|
24
|
-
* // Publish events
|
|
38
|
+
* // Publish events (fire-and-forget)
|
|
25
39
|
* app.run('event-name', ...args);
|
|
40
|
+
*
|
|
41
|
+
* // Get return values from event handlers
|
|
42
|
+
* app.runAsync('event-name', data).then(results => {
|
|
43
|
+
* // Handle results array
|
|
44
|
+
* });
|
|
26
45
|
* ```
|
|
27
46
|
*/
|
|
28
47
|
|
|
29
48
|
import { EventOptions } from './types'
|
|
49
|
+
import { APPRUN_VERSION_GLOBAL } from './version'
|
|
30
50
|
|
|
31
51
|
export class App {
|
|
32
52
|
|
|
@@ -42,6 +62,7 @@ export class App {
|
|
|
42
62
|
public use_render: any;
|
|
43
63
|
public use_react: any;
|
|
44
64
|
public route: any;
|
|
65
|
+
public basePath: string; // Base path for sub-directory deployments
|
|
45
66
|
|
|
46
67
|
public version: string;
|
|
47
68
|
|
|
@@ -69,10 +90,18 @@ export class App {
|
|
|
69
90
|
console.assert(subscribers && subscribers.length > 0, 'No subscriber for event: ' + name);
|
|
70
91
|
subscribers.forEach((sub) => {
|
|
71
92
|
const { fn, options } = sub;
|
|
93
|
+
if (!fn || typeof fn !== 'function') {
|
|
94
|
+
console.error(`AppRun event handler for '${name}' is not a function:`, fn);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
72
97
|
if (options.delay) {
|
|
73
98
|
this.delay(name, fn, args, options);
|
|
74
99
|
} else {
|
|
75
|
-
|
|
100
|
+
try {
|
|
101
|
+
Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(`Error in event handler for '${name}':`, error);
|
|
104
|
+
}
|
|
76
105
|
}
|
|
77
106
|
return !sub.options.once;
|
|
78
107
|
});
|
|
@@ -88,7 +117,11 @@ export class App {
|
|
|
88
117
|
if (options._t) clearTimeout(options._t);
|
|
89
118
|
options._t = setTimeout(() => {
|
|
90
119
|
clearTimeout(options._t);
|
|
91
|
-
|
|
120
|
+
try {
|
|
121
|
+
Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(`Error in delayed event handler for '${name}':`, error);
|
|
124
|
+
}
|
|
92
125
|
}, options.delay);
|
|
93
126
|
}
|
|
94
127
|
|
|
@@ -97,12 +130,25 @@ export class App {
|
|
|
97
130
|
console.assert(subscribers && subscribers.length > 0, 'No subscriber for event: ' + name);
|
|
98
131
|
const promises = subscribers.map(sub => {
|
|
99
132
|
const { fn, options } = sub;
|
|
100
|
-
|
|
133
|
+
if (!fn || typeof fn !== 'function') {
|
|
134
|
+
console.error(`AppRun async event handler for '${name}' is not a function:`, fn);
|
|
135
|
+
return Promise.resolve(null);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return Object.keys(options).length > 0 ? fn.apply(this, [...args, options]) : fn.apply(this, args);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`Error in async event handler for '${name}':`, error);
|
|
141
|
+
return Promise.reject(error);
|
|
142
|
+
}
|
|
101
143
|
});
|
|
102
144
|
return Promise.all(promises);
|
|
103
145
|
}
|
|
104
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @deprecated Use runAsync() instead. app.query() will be removed in a future version.
|
|
149
|
+
*/
|
|
105
150
|
query(name: string, ...args: any[]): Promise<any[]> {
|
|
151
|
+
console.warn('app.query() is deprecated. Use app.runAsync() instead.');
|
|
106
152
|
return this.runAsync(name, ...args);
|
|
107
153
|
}
|
|
108
154
|
|
|
@@ -125,7 +171,7 @@ export class App {
|
|
|
125
171
|
}
|
|
126
172
|
}
|
|
127
173
|
|
|
128
|
-
const AppRunVersions =
|
|
174
|
+
const AppRunVersions = APPRUN_VERSION_GLOBAL;
|
|
129
175
|
let _app: App;
|
|
130
176
|
const root = (typeof window !== 'undefined' ? window :
|
|
131
177
|
typeof global !== 'undefined' ? global :
|
package/src/apprun-dev-tools.tsx
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import app from './app';
|
|
2
2
|
import toHTML from './vdom-to-html';
|
|
3
3
|
import { _createEventTests, _createStateTests } from './apprun-dev-tools-tests';
|
|
4
|
-
import yaml from 'js-yaml';
|
|
5
|
-
|
|
6
|
-
function replacer(key, value) {
|
|
7
|
-
if (typeof value === 'function') return value.toString(); // value.toString();
|
|
8
|
-
return ['', null].includes(value) || (typeof value === 'object' && (value.length === 0 || Object.keys(value).length === 0)) ? undefined : value;
|
|
9
|
-
}
|
|
10
4
|
|
|
11
5
|
function createProxy(obj) {
|
|
12
6
|
const handler = {
|
|
@@ -109,7 +103,7 @@ const viewComponents = state => {
|
|
|
109
103
|
|
|
110
104
|
return <li>
|
|
111
105
|
<div>{component.constructor.name}</div>
|
|
112
|
-
<div><pre>{htmlEncode(
|
|
106
|
+
<div><pre>{htmlEncode(JSON.stringify(component_def, null, 2))}</pre></div>
|
|
113
107
|
<br />
|
|
114
108
|
</li>;
|
|
115
109
|
|
package/src/apprun-html.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import app from './apprun'
|
|
2
|
+
import { safeGlobalAssign, SafeGlobalContext } from './type-utils';
|
|
2
3
|
export {
|
|
3
4
|
app, Component, View, Action, Update, on, update, event, EventOptions,
|
|
4
5
|
customElement, CustomElementOptions,
|
|
@@ -14,8 +15,11 @@ app.Fragment = Fragment;
|
|
|
14
15
|
export default app;
|
|
15
16
|
|
|
16
17
|
if (typeof window === 'object') {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const globalWindow = window as SafeGlobalContext;
|
|
19
|
+
safeGlobalAssign(globalWindow, {
|
|
20
|
+
'React': globalWindow['_React'] || app,
|
|
21
|
+
'html': html,
|
|
22
|
+
'svg': svg,
|
|
23
|
+
'run': run
|
|
24
|
+
});
|
|
21
25
|
}
|
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,13 @@ 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
|
+
/** @deprecated Use runAsync() instead. query() will be removed in a future version. */
|
|
96
|
+
query(name: string, ...args: any[]): Promise<any[]>;
|
|
97
|
+
runAsync(name: string, ...args: any[]): Promise<any[]>;
|
|
76
98
|
h(tag: string | Function, props, ...children): VNode | VNode[];
|
|
77
99
|
createElement(tag: string | Function, props, ...children): VNode | VNode[];
|
|
78
100
|
render(element: Element | string, node: VNode): void;
|
|
@@ -81,12 +103,13 @@ export interface IApp {
|
|
|
81
103
|
webComponent(name: string, componentClass, options?: CustomElementOptions): void;
|
|
82
104
|
safeHTML(html: string): any[];
|
|
83
105
|
use_render(render, mode?: 0 | 1);
|
|
84
|
-
use_react(
|
|
106
|
+
use_react(React, ReactDOM);
|
|
107
|
+
version: string;
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
if (!app.start) {
|
|
88
111
|
|
|
89
|
-
app.version =
|
|
112
|
+
app.version = APPRUN_VERSION;
|
|
90
113
|
|
|
91
114
|
app.h = app.createElement = createElement;
|
|
92
115
|
app.render = render;
|
|
@@ -104,8 +127,15 @@ if (!app.start) {
|
|
|
104
127
|
return component;
|
|
105
128
|
};
|
|
106
129
|
|
|
130
|
+
app.once = app.once || ((name: string, fn: (...args: any[]) => void, options: any = {}) => {
|
|
131
|
+
app.on(name, fn, { ...options, once: true });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Deprecated: app.query is deprecated in favor of app.runAsync
|
|
135
|
+
app.query = app.query || app.runAsync;
|
|
136
|
+
|
|
107
137
|
const NOOP = _ => {/* Intentionally empty */ }
|
|
108
|
-
|
|
138
|
+
app.on('/', NOOP);
|
|
109
139
|
app.on('debug', _ => NOOP);
|
|
110
140
|
app.on(ROUTER_EVENT, NOOP);
|
|
111
141
|
app.on(ROUTER_404_EVENT, NOOP);
|
|
@@ -114,7 +144,7 @@ if (!app.start) {
|
|
|
114
144
|
|
|
115
145
|
if (typeof document === 'object') {
|
|
116
146
|
document.addEventListener("DOMContentLoaded", () => {
|
|
117
|
-
const
|
|
147
|
+
const no_init_route = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
|
|
118
148
|
const use_hash = app.find('#') || app.find('#/') || false;
|
|
119
149
|
|
|
120
150
|
// console.log(`AppRun ${app.version} started with ${use_hash ? 'hash' : 'path'} routing. Initial load: ${init_load ? 'disabled' : 'enabled'}.`);
|
|
@@ -122,17 +152,36 @@ if (!app.start) {
|
|
|
122
152
|
window.addEventListener('popstate', () => route(location.pathname));
|
|
123
153
|
|
|
124
154
|
if (use_hash) {
|
|
125
|
-
|
|
155
|
+
!no_init_route && route(location.hash);
|
|
126
156
|
} else {
|
|
127
|
-
|
|
157
|
+
!no_init_route && (() => {
|
|
158
|
+
const basePath = app.basePath || '';
|
|
159
|
+
let initialPath = location.pathname;
|
|
160
|
+
|
|
161
|
+
// Strip base path if present
|
|
162
|
+
if (basePath && initialPath.startsWith(basePath)) {
|
|
163
|
+
initialPath = initialPath.substring(basePath.length);
|
|
164
|
+
if (!initialPath.startsWith('/')) initialPath = '/' + initialPath;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
route(initialPath);
|
|
168
|
+
})();
|
|
128
169
|
document.body.addEventListener('click', e => {
|
|
129
170
|
const element = e.target as HTMLElement;
|
|
171
|
+
if (!element) return;
|
|
172
|
+
|
|
130
173
|
const menu = (element.tagName === 'A' ? element : element.closest('a')) as HTMLAnchorElement;
|
|
131
174
|
if (menu &&
|
|
132
|
-
menu.origin === location.origin
|
|
175
|
+
menu.origin === location.origin &&
|
|
176
|
+
menu.pathname) {
|
|
133
177
|
e.preventDefault();
|
|
134
|
-
|
|
135
|
-
|
|
178
|
+
|
|
179
|
+
// Handle base path for navigation
|
|
180
|
+
const basePath = app.basePath || '';
|
|
181
|
+
const fullPath = basePath + menu.pathname;
|
|
182
|
+
|
|
183
|
+
history.pushState(null, '', fullPath);
|
|
184
|
+
route(menu.pathname); // Route with relative path (without base path)
|
|
136
185
|
}
|
|
137
186
|
});
|
|
138
187
|
}
|
|
@@ -144,12 +193,13 @@ if (!app.start) {
|
|
|
144
193
|
};
|
|
145
194
|
|
|
146
195
|
if (typeof window === 'object') {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
196
|
+
const globalWindow = window as any;
|
|
197
|
+
globalWindow['Component'] = Component as ComponentType;
|
|
198
|
+
globalWindow['_React'] = globalWindow['React'];
|
|
199
|
+
globalWindow['React'] = app;
|
|
200
|
+
globalWindow['on'] = on as OnDecorator;
|
|
201
|
+
globalWindow['customElement'] = customElement;
|
|
202
|
+
globalWindow['safeHTML'] = safeHTML;
|
|
153
203
|
}
|
|
154
204
|
|
|
155
205
|
app.use_render = (render, mode = 0) => {
|
|
@@ -161,15 +211,42 @@ if (!app.start) {
|
|
|
161
211
|
};
|
|
162
212
|
|
|
163
213
|
app.use_react = (React, ReactDOM) => {
|
|
214
|
+
if (!React || !ReactDOM) {
|
|
215
|
+
console.error('AppRun use_react: React and ReactDOM parameters are required');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (typeof React.createElement !== 'function') {
|
|
220
|
+
console.error('AppRun use_react: Invalid React object - createElement method not found');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!React.Fragment) {
|
|
225
|
+
console.error('AppRun use_react: Invalid React object - Fragment not found');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
164
229
|
app.h = app.createElement = React.createElement;
|
|
165
230
|
app.Fragment = React.Fragment;
|
|
166
|
-
|
|
231
|
+
|
|
232
|
+
// React 18+ uses createRoot API
|
|
167
233
|
if (React.version && React.version.startsWith('18')) {
|
|
234
|
+
if (!ReactDOM.createRoot || typeof ReactDOM.createRoot !== 'function') {
|
|
235
|
+
console.error('AppRun use_react: ReactDOM.createRoot not found in React 18+');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
168
238
|
app.render = (el, vdom) => {
|
|
169
|
-
if (!el ||
|
|
170
|
-
if (!el._root) el._root = ReactDOM.createRoot(el);
|
|
171
|
-
el._root.render(vdom);
|
|
239
|
+
if (!el || vdom === undefined) return;
|
|
240
|
+
if (!(el as any)._root) (el as any)._root = ReactDOM.createRoot(el);
|
|
241
|
+
(el as any)._root.render(vdom);
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
// Legacy React versions
|
|
245
|
+
if (!ReactDOM.render || typeof ReactDOM.render !== 'function') {
|
|
246
|
+
console.error('AppRun use_react: ReactDOM.render not found in legacy React');
|
|
247
|
+
return;
|
|
172
248
|
}
|
|
249
|
+
app.render = (el, vdom) => ReactDOM.render(vdom, el);
|
|
173
250
|
}
|
|
174
251
|
}
|
|
175
252
|
}
|
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
|
-
|
|
97
|
+
safeGetElementById(this.element) || safeQuerySelector(this.element) : this.element;
|
|
78
98
|
|
|
79
|
-
if (!el)
|
|
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')
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -329,7 +367,11 @@ export class Component<T = any, E = any> {
|
|
|
329
367
|
}
|
|
330
368
|
|
|
331
369
|
// obsolete
|
|
370
|
+
/**
|
|
371
|
+
* @deprecated Use runAsync() instead. query() will be removed in a future version.
|
|
372
|
+
*/
|
|
332
373
|
public query(event: E, ...args) {
|
|
374
|
+
console.warn('component.query() is deprecated. Use component.runAsync() instead.');
|
|
333
375
|
return this.runAsync(event, ...args);
|
|
334
376
|
}
|
|
335
377
|
|
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}`,
|