apprun 3.33.9 → 3.35.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.
- package/.clinerules +1 -0
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/WHATSNEW.md +106 -0
- package/apprun.d.ts +7 -1
- package/dist/apprun-dev-tools.js +1 -1
- package/dist/apprun-dev-tools.js.map +1 -1
- package/dist/apprun-html.esm.js +21 -4
- package/dist/apprun-html.esm.js.map +1 -1
- package/dist/apprun-html.js +1 -1
- package/dist/apprun-html.js.LICENSE.txt +6 -0
- package/dist/apprun-html.js.map +1 -1
- package/dist/apprun-play-html.esm.js +21 -4
- 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 +38 -10
- package/esm/app.js.map +1 -1
- package/esm/apprun-code.js.map +1 -1
- package/esm/apprun-dev-tools-tests.js.map +1 -1
- package/esm/apprun-dev-tools.js.map +1 -1
- package/esm/apprun-html.js.map +1 -1
- package/esm/apprun-play.js.map +1 -1
- package/esm/apprun.js +65 -9
- package/esm/apprun.js.map +1 -1
- package/esm/component.js +59 -1
- package/esm/component.js.map +1 -1
- package/esm/decorator.js +33 -0
- package/esm/decorator.js.map +1 -1
- package/esm/directive.js +31 -0
- package/esm/directive.js.map +1 -1
- package/esm/router.js +27 -0
- package/esm/router.js.map +1 -1
- package/esm/types.js +28 -0
- package/esm/types.js.map +1 -1
- package/esm/vdom-lit-html.js +19 -17
- package/esm/vdom-lit-html.js.map +1 -1
- package/esm/vdom-my.js.map +1 -1
- package/esm/vdom-patch.js.map +1 -1
- package/esm/vdom-to-html.js.map +1 -1
- package/esm/vdom.js +24 -0
- package/esm/vdom.js.map +1 -1
- package/esm/web-component.js +32 -0
- package/esm/web-component.js.map +1 -1
- package/index.html +3 -2
- package/jest.config.js +63 -0
- package/jest.setup.js +47 -0
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +16 -43
- package/react.js +0 -15
- package/rollup.config.js +27 -10
- package/src/app.ts +64 -30
- package/src/apprun.ts +94 -13
- package/src/component.ts +57 -1
- package/src/decorator.ts +34 -1
- package/src/directive.ts +33 -1
- package/src/global.d.ts +22 -0
- package/src/router.ts +28 -2
- package/src/types/apprun.d.ts +56 -0
- package/src/types.ts +30 -1
- package/src/vdom-lit-html.ts +21 -19
- package/src/vdom.ts +25 -2
- package/src/web-component.ts +33 -0
- package/tsconfig.jest.json +27 -3
- package/tsconfig.json +3 -3
- package/webpack.config.cjs +9 -2
package/src/app.ts
CHANGED
|
@@ -1,28 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Core AppRun application class and singleton instance
|
|
3
|
+
*
|
|
4
|
+
* This file provides:
|
|
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(): Alias for runAsync
|
|
11
|
+
*
|
|
12
|
+
* 2. Default app singleton - Global event bus instance
|
|
13
|
+
* - Created once and reused across the application
|
|
14
|
+
* - Stored in global scope (window/global)
|
|
15
|
+
* - Version tracked to prevent duplicate instances
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Subscribe to events
|
|
20
|
+
* app.on('event-name', (state, ...args) => {
|
|
21
|
+
* // Handle event
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Publish events
|
|
25
|
+
* app.run('event-name', ...args);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { EventOptions } from './types'
|
|
30
|
+
|
|
2
31
|
export class App {
|
|
3
32
|
|
|
4
|
-
_events:
|
|
33
|
+
_events: { [key: string]: Array<{ fn: (...args: any[]) => any, options: EventOptions }> };
|
|
34
|
+
|
|
35
|
+
public start: any;
|
|
36
|
+
public h: any;
|
|
37
|
+
public createElement: any;
|
|
38
|
+
public render: any;
|
|
39
|
+
public Fragment: any;
|
|
40
|
+
public webComponent: any;
|
|
41
|
+
public safeHTML: any;
|
|
42
|
+
public use_render: any;
|
|
43
|
+
public use_react: any;
|
|
44
|
+
public route: any;
|
|
5
45
|
|
|
6
|
-
public
|
|
7
|
-
public h;
|
|
8
|
-
public createElement;
|
|
9
|
-
public render;
|
|
10
|
-
public Fragment;
|
|
11
|
-
public webComponent;
|
|
12
|
-
public safeHTML;
|
|
13
|
-
public use_render;
|
|
14
|
-
public use_react;
|
|
46
|
+
public version: string;
|
|
15
47
|
|
|
16
48
|
constructor() {
|
|
17
|
-
this._events = {};
|
|
49
|
+
this._events = {} as { [key: string]: Array<{ fn: (...args: any[]) => any, options: EventOptions }> };
|
|
18
50
|
}
|
|
19
51
|
|
|
20
|
-
on(name: string, fn: (...args) =>
|
|
52
|
+
on(name: string, fn: (...args: any[]) => any, options: EventOptions = {}): void {
|
|
21
53
|
this._events[name] = this._events[name] || [];
|
|
22
54
|
this._events[name].push({ fn, options });
|
|
23
55
|
}
|
|
24
56
|
|
|
25
|
-
off(name: string, fn: (...args) =>
|
|
57
|
+
off(name: string, fn: (...args: any[]) => any): void {
|
|
26
58
|
const subscribers = this._events[name] || [];
|
|
27
59
|
|
|
28
60
|
this._events[name] = subscribers.filter((sub) => sub.fn !== fn);
|
|
@@ -32,7 +64,7 @@ export class App {
|
|
|
32
64
|
return this._events[name];
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
run(name: string, ...args): number {
|
|
67
|
+
run(name: string, ...args: any[]): number {
|
|
36
68
|
const subscribers = this.getSubscribers(name, this._events);
|
|
37
69
|
console.assert(subscribers && subscribers.length > 0, 'No subscriber for event: ' + name);
|
|
38
70
|
subscribers.forEach((sub) => {
|
|
@@ -48,11 +80,11 @@ export class App {
|
|
|
48
80
|
return subscribers.length;
|
|
49
81
|
}
|
|
50
82
|
|
|
51
|
-
once(name: string, fn, options: EventOptions = {}): void {
|
|
83
|
+
once(name: string, fn: (...args: any[]) => any, options: EventOptions = {}): void {
|
|
52
84
|
this.on(name, fn, { ...options, once: true });
|
|
53
85
|
}
|
|
54
86
|
|
|
55
|
-
private delay(name, fn, args, options): void {
|
|
87
|
+
private delay(name: string, fn: (...args: any[]) => any, args: any[], options: EventOptions): void {
|
|
56
88
|
if (options._t) clearTimeout(options._t);
|
|
57
89
|
options._t = setTimeout(() => {
|
|
58
90
|
clearTimeout(options._t);
|
|
@@ -60,7 +92,7 @@ export class App {
|
|
|
60
92
|
}, options.delay);
|
|
61
93
|
}
|
|
62
94
|
|
|
63
|
-
runAsync(name: string, ...args): Promise<any[]> {
|
|
95
|
+
runAsync(name: string, ...args: any[]): Promise<any[]> {
|
|
64
96
|
const subscribers = this.getSubscribers(name, this._events);
|
|
65
97
|
console.assert(subscribers && subscribers.length > 0, 'No subscriber for event: ' + name);
|
|
66
98
|
const promises = subscribers.map(sub => {
|
|
@@ -70,11 +102,11 @@ export class App {
|
|
|
70
102
|
return Promise.all(promises);
|
|
71
103
|
}
|
|
72
104
|
|
|
73
|
-
query(name: string, ...args): Promise<any[]> {
|
|
105
|
+
query(name: string, ...args: any[]): Promise<any[]> {
|
|
74
106
|
return this.runAsync(name, ...args);
|
|
75
107
|
}
|
|
76
108
|
|
|
77
|
-
private getSubscribers(name: string, events) {
|
|
109
|
+
private getSubscribers(name: string, events: { [key: string]: Array<{ fn: (...args: any[]) => any, options: EventOptions }> }): Array<{ fn: (...args: any[]) => any, options: EventOptions }> {
|
|
78
110
|
const subscribers = events[name] || [];
|
|
79
111
|
|
|
80
112
|
// Update the list of subscribers by pulling out those which will run once.
|
|
@@ -93,15 +125,17 @@ export class App {
|
|
|
93
125
|
}
|
|
94
126
|
}
|
|
95
127
|
|
|
96
|
-
const AppRunVersions = 'AppRun-3';
|
|
97
|
-
let
|
|
98
|
-
const root = (typeof
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
const AppRunVersions = 'AppRun-3.3.11';
|
|
129
|
+
let _app: App;
|
|
130
|
+
const root = (typeof window !== 'undefined' ? window :
|
|
131
|
+
typeof global !== 'undefined' ? global :
|
|
132
|
+
typeof self !== 'undefined' ? self : {}) as any;
|
|
133
|
+
|
|
134
|
+
if (root.app && root._AppRunVersions) {
|
|
135
|
+
_app = root.app;
|
|
102
136
|
} else {
|
|
103
|
-
|
|
104
|
-
root
|
|
105
|
-
root
|
|
137
|
+
_app = new App();
|
|
138
|
+
root.app = _app;
|
|
139
|
+
root._AppRunVersions = AppRunVersions;
|
|
106
140
|
}
|
|
107
|
-
export default
|
|
141
|
+
export default _app;
|
package/src/apprun.ts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main AppRun framework entry point
|
|
3
|
+
*
|
|
4
|
+
* This file:
|
|
5
|
+
* 1. Assembles core AppRun modules into a complete framework
|
|
6
|
+
* 2. Exports public API and types
|
|
7
|
+
* 3. Initializes global app instance with:
|
|
8
|
+
* - Virtual DOM rendering
|
|
9
|
+
* - Component system
|
|
10
|
+
* - Router
|
|
11
|
+
* - Web component support
|
|
12
|
+
*
|
|
13
|
+
* Key exports:
|
|
14
|
+
* - app: Global event system instance
|
|
15
|
+
* - Component: Base component class
|
|
16
|
+
* - Decorators: @on, @update, @customElement
|
|
17
|
+
* - Router events and configuration
|
|
18
|
+
* - Web component registration
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { app, Component } from 'apprun';
|
|
23
|
+
*
|
|
24
|
+
* // Create components
|
|
25
|
+
* class MyComponent extends Component {
|
|
26
|
+
* state = // Initial state
|
|
27
|
+
* view = state => // Render view
|
|
28
|
+
* update = {
|
|
29
|
+
* 'event': (state, ...args) => // Handle events
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
1
35
|
import app, { App } from './app';
|
|
2
36
|
import { createElement, render, Fragment, safeHTML } from './vdom';
|
|
3
37
|
import { Component } from './component';
|
|
@@ -7,7 +41,26 @@ import webComponent, { CustomElementOptions } from './web-component';
|
|
|
7
41
|
import { Route, route, ROUTER_EVENT, ROUTER_404_EVENT } from './router';
|
|
8
42
|
|
|
9
43
|
export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
|
|
10
|
-
|
|
44
|
+
type OnDecorator = {
|
|
45
|
+
<T = any>(options?: any): (constructor: Function) => void;
|
|
46
|
+
<E = string>(events?: E, options?: any): (target: any, key: string) => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
App,
|
|
51
|
+
app,
|
|
52
|
+
Component,
|
|
53
|
+
View,
|
|
54
|
+
Action,
|
|
55
|
+
Update,
|
|
56
|
+
on,
|
|
57
|
+
update,
|
|
58
|
+
EventOptions,
|
|
59
|
+
ActionOptions,
|
|
60
|
+
MountOptions,
|
|
61
|
+
Fragment,
|
|
62
|
+
safeHTML
|
|
63
|
+
}
|
|
11
64
|
export { update as event };
|
|
12
65
|
export { ROUTER_EVENT, ROUTER_404_EVENT };
|
|
13
66
|
export { customElement, CustomElementOptions, AppStartOptions };
|
|
@@ -32,6 +85,9 @@ export interface IApp {
|
|
|
32
85
|
}
|
|
33
86
|
|
|
34
87
|
if (!app.start) {
|
|
88
|
+
|
|
89
|
+
app.version = '3.35.0';
|
|
90
|
+
|
|
35
91
|
app.h = app.createElement = createElement;
|
|
36
92
|
app.render = render;
|
|
37
93
|
app.Fragment = Fragment;
|
|
@@ -49,35 +105,60 @@ if (!app.start) {
|
|
|
49
105
|
};
|
|
50
106
|
|
|
51
107
|
const NOOP = _ => {/* Intentionally empty */ }
|
|
52
|
-
app.on('$', NOOP);
|
|
108
|
+
// app.on('$', NOOP);
|
|
53
109
|
app.on('debug', _ => NOOP);
|
|
54
110
|
app.on(ROUTER_EVENT, NOOP);
|
|
55
|
-
app.on(
|
|
56
|
-
app
|
|
111
|
+
app.on(ROUTER_404_EVENT, NOOP);
|
|
112
|
+
app.route = route;
|
|
57
113
|
app.on('route', url => app['route'] && app['route'](url));
|
|
58
114
|
|
|
59
115
|
if (typeof document === 'object') {
|
|
60
116
|
document.addEventListener("DOMContentLoaded", () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
117
|
+
const init_load = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
|
|
118
|
+
const use_hash = app.find('#') || app.find('#/') || false;
|
|
119
|
+
|
|
120
|
+
// console.log(`AppRun ${app.version} started with ${use_hash ? 'hash' : 'path'} routing. Initial load: ${init_load ? 'disabled' : 'enabled'}.`);
|
|
121
|
+
window.addEventListener('hashchange', () => route(location.hash));
|
|
122
|
+
window.addEventListener('popstate', () => route(location.pathname));
|
|
123
|
+
|
|
124
|
+
if (use_hash) {
|
|
125
|
+
init_load && route(location.hash);
|
|
126
|
+
} else {
|
|
127
|
+
init_load && route(location.pathname);
|
|
128
|
+
document.body.addEventListener('click', e => {
|
|
129
|
+
const element = e.target as HTMLElement;
|
|
130
|
+
const menu = (element.tagName === 'A' ? element : element.closest('a')) as HTMLAnchorElement;
|
|
131
|
+
if (menu &&
|
|
132
|
+
menu.origin === location.origin) {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
history.pushState(null, '', menu.pathname);
|
|
135
|
+
route(menu.pathname);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
64
138
|
}
|
|
65
139
|
});
|
|
66
140
|
}
|
|
67
141
|
|
|
142
|
+
type ComponentType = typeof Component & {
|
|
143
|
+
<T = any>(options?: any): (constructor: Function) => void;
|
|
144
|
+
};
|
|
145
|
+
|
|
68
146
|
if (typeof window === 'object') {
|
|
69
|
-
window['Component'] = Component;
|
|
147
|
+
window['Component'] = Component as ComponentType;
|
|
70
148
|
window['_React'] = window['React'];
|
|
71
149
|
window['React'] = app;
|
|
72
|
-
window['on'] = on;
|
|
150
|
+
window['on'] = on as OnDecorator;
|
|
73
151
|
window['customElement'] = customElement;
|
|
74
152
|
window['safeHTML'] = safeHTML;
|
|
75
153
|
}
|
|
76
154
|
|
|
77
|
-
app.use_render = (render, mode = 0) =>
|
|
78
|
-
mode === 0
|
|
79
|
-
app.render = (el, vdom) => render(vdom, el)
|
|
80
|
-
|
|
155
|
+
app.use_render = (render, mode = 0) => {
|
|
156
|
+
if (mode === 0) {
|
|
157
|
+
app.render = (el, vdom) => render(vdom, el); // react style
|
|
158
|
+
} else {
|
|
159
|
+
app.render = (el, vdom) => render(el, vdom); // apprun style
|
|
160
|
+
}
|
|
161
|
+
};
|
|
81
162
|
|
|
82
163
|
app.use_react = (React, ReactDOM) => {
|
|
83
164
|
app.h = app.createElement = React.createElement;
|
package/src/component.ts
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppRun Component System Implementation
|
|
3
|
+
*
|
|
4
|
+
* This file provides the Component class which is the foundation for:
|
|
5
|
+
* 1. State Management
|
|
6
|
+
* - Maintains component state
|
|
7
|
+
* - Handles state updates
|
|
8
|
+
* - Supports state history
|
|
9
|
+
*
|
|
10
|
+
* 2. View Rendering
|
|
11
|
+
* - Renders virtual DOM to real DOM
|
|
12
|
+
* - Handles component lifecycle (mounted, rendered, unload)
|
|
13
|
+
* - Supports shadow DOM and web components
|
|
14
|
+
*
|
|
15
|
+
* 3. Event Handling
|
|
16
|
+
* - Local and global event subscription
|
|
17
|
+
* - Event handler registration via decorators
|
|
18
|
+
* - Action to state updates
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ```ts
|
|
22
|
+
* class MyComponent extends Component {
|
|
23
|
+
* state = // Initial state
|
|
24
|
+
* view = state => // Return virtual DOM
|
|
25
|
+
* update = {
|
|
26
|
+
* 'event': (state, ...args) => // Return new state
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // Mount component
|
|
31
|
+
* new MyComponent().mount('element-id');
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
1
34
|
|
|
2
35
|
import app, { App } from './app';
|
|
3
36
|
import { Reflect } from './decorator'
|
|
@@ -79,7 +112,30 @@ export class Component<T = any, E = any> {
|
|
|
79
112
|
|
|
80
113
|
public setState(state: T, options: ActionOptions & EventOptions
|
|
81
114
|
= { render: true, history: false }) {
|
|
82
|
-
|
|
115
|
+
|
|
116
|
+
const handleAsyncIterator = async (iterator: AsyncIterator<T>) => {
|
|
117
|
+
try {
|
|
118
|
+
while (true) {
|
|
119
|
+
const { value, done } = await iterator.next();
|
|
120
|
+
if (done) break;
|
|
121
|
+
this.setState(value, options);
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error('Error in async iterator:', e);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const result = state as any;
|
|
129
|
+
if (result?.[Symbol.asyncIterator]) {
|
|
130
|
+
// handleAsyncIterator(result[Symbol.asyncIterator]());
|
|
131
|
+
this.setState(handleAsyncIterator(result[Symbol.asyncIterator]()) as any, options);
|
|
132
|
+
return;
|
|
133
|
+
} else if (result?.[Symbol.iterator] && typeof result.next === "function") {
|
|
134
|
+
for (const value of result) {
|
|
135
|
+
this.setState(value, options);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
} else if (state && state instanceof Promise) {
|
|
83
139
|
// Promise will not be saved or rendered
|
|
84
140
|
// state will be saved and rendered when promise is resolved
|
|
85
141
|
Promise.resolve(state).then(v => {
|
package/src/decorator.ts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import webComponent, { CustomElementOptions } from './web-component';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* TypeScript Decorators for AppRun Components
|
|
5
|
+
*
|
|
6
|
+
* This file provides decorators that enable:
|
|
7
|
+
* 1. Event Handler Registration
|
|
8
|
+
* - @on(): Subscribe to events with options
|
|
9
|
+
* - @update(): Define state updates with metadata
|
|
10
|
+
*
|
|
11
|
+
* 2. Web Component Integration
|
|
12
|
+
* - @customElement(): Register as custom element with options
|
|
13
|
+
* - Handles shadow DOM and attribute observation
|
|
14
|
+
*
|
|
15
|
+
* 3. Metadata Management
|
|
16
|
+
* - Custom Reflect implementation for decorator metadata
|
|
17
|
+
* - Stores event handler and update metadata
|
|
18
|
+
* - Supports runtime reflection for dynamic behavior
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ```ts
|
|
22
|
+
* @customElement('my-element')
|
|
23
|
+
* class MyComponent extends Component {
|
|
24
|
+
* @on('event')
|
|
25
|
+
* handler(state, ...args) {
|
|
26
|
+
* // Handle event
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* @update('event')
|
|
30
|
+
* updater(state, ...args) {
|
|
31
|
+
* // Update state
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
3
37
|
// tslint:disable:no-invalid-this
|
|
4
38
|
export const Reflect = {
|
|
5
39
|
|
|
@@ -44,4 +78,3 @@ export function customElement(name: string, options?: CustomElementOptions) {
|
|
|
44
78
|
return constructor;
|
|
45
79
|
}
|
|
46
80
|
}
|
|
47
|
-
|
package/src/directive.ts
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Directives Implementation
|
|
3
|
+
*
|
|
4
|
+
* This file provides built-in directives for components:
|
|
5
|
+
* 1. Event Binding
|
|
6
|
+
* - $on: Bind DOM events to component events
|
|
7
|
+
* - Supports event delegation and parameters
|
|
8
|
+
* - Handles function, string, and array event handlers
|
|
9
|
+
*
|
|
10
|
+
* 2. Two-way Data Binding
|
|
11
|
+
* - $bind: Sync form elements with component state
|
|
12
|
+
* - Handles input types: text, checkbox, radio, select
|
|
13
|
+
* - Automatic value type conversion
|
|
14
|
+
* - Support for complex property paths
|
|
15
|
+
*
|
|
16
|
+
* 3. Custom Directives
|
|
17
|
+
* - Extensible directive system
|
|
18
|
+
* - Processes virtual DOM during rendering
|
|
19
|
+
* - Supports custom attribute directives
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // Event binding
|
|
24
|
+
* <button $onclick="event-name">Click</button>
|
|
25
|
+
* <input $oninput={e => setState(e.target.value)} />
|
|
26
|
+
*
|
|
27
|
+
* // Two-way binding
|
|
28
|
+
* <input $bind="state.property" />
|
|
29
|
+
* <select $bind="selected">...</select>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
1
33
|
import app from './app';
|
|
2
34
|
|
|
3
35
|
const getStateValue = (component, name) => {
|
|
@@ -92,4 +124,4 @@ const directive = (vdom, component) => {
|
|
|
92
124
|
}
|
|
93
125
|
}
|
|
94
126
|
|
|
95
|
-
export default directive;
|
|
127
|
+
export default directive;
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { App } from './app';
|
|
2
|
+
import { Component } from './component';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
var app: App;
|
|
6
|
+
var _AppRunVersions: string;
|
|
7
|
+
interface Window {
|
|
8
|
+
app: App;
|
|
9
|
+
_AppRunVersions: string;
|
|
10
|
+
Component: typeof Component & {
|
|
11
|
+
<T = any>(options?: any): (constructor: Function) => void;
|
|
12
|
+
};
|
|
13
|
+
_React: any;
|
|
14
|
+
React: App;
|
|
15
|
+
on: {
|
|
16
|
+
<T = any>(options?: any): (constructor: Function) => void;
|
|
17
|
+
<E = string>(events?: E, options?: any): (target: any, key: string) => void;
|
|
18
|
+
};
|
|
19
|
+
customElement: (name: string) => (constructor: Function) => void;
|
|
20
|
+
safeHTML: (html: string) => any[];
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppRun Router Implementation
|
|
3
|
+
*
|
|
4
|
+
* This file provides URL routing capabilities:
|
|
5
|
+
* 1. Hash-based routing (#/path)
|
|
6
|
+
* 2. Path-based routing (/path)
|
|
7
|
+
* 3. Event-based navigation
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Automatic route event firing
|
|
11
|
+
* - 404 handling via ROUTER_404_EVENT
|
|
12
|
+
* - History API integration
|
|
13
|
+
* - Route parameter parsing
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* ```ts
|
|
17
|
+
* // Handle routes
|
|
18
|
+
* app.on('#/home', () => // Show home page);
|
|
19
|
+
* app.on('#/users/:id', (id) => // Show user profile);
|
|
20
|
+
*
|
|
21
|
+
* // Navigate programmatically
|
|
22
|
+
* app.run('route', '#/home');
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
1
26
|
import app from './app';
|
|
2
27
|
|
|
3
28
|
export type Route = (url: string, ...args: any[]) => any;
|
|
4
29
|
|
|
5
30
|
export const ROUTER_EVENT: string = '//';
|
|
6
31
|
export const ROUTER_404_EVENT: string = '///';
|
|
7
|
-
|
|
8
32
|
export const route: Route = (url: string) => {
|
|
33
|
+
if (app['lastUrl'] === url) return; // Prevent duplicate routing
|
|
34
|
+
app['lastUrl'] = url;
|
|
9
35
|
if (!url) url = '#';
|
|
10
36
|
if (url.startsWith('#')) {
|
|
11
37
|
const [name, ...rest] = url.split('/');
|
|
@@ -20,4 +46,4 @@ export const route: Route = (url: string) => {
|
|
|
20
46
|
app.run(ROUTER_EVENT, url);
|
|
21
47
|
}
|
|
22
48
|
}
|
|
23
|
-
export default route;
|
|
49
|
+
export default route;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
declare type VNode = {
|
|
2
|
+
tag: string;
|
|
3
|
+
props?: Record<string, any>;
|
|
4
|
+
children?: Array<VNode | string | number>;
|
|
5
|
+
} | string | number;
|
|
6
|
+
|
|
7
|
+
declare type VDOM = VNode | VNode[];
|
|
8
|
+
|
|
9
|
+
declare type View<T = any> = (state: T) => VDOM | void;
|
|
10
|
+
declare type Action<T = any> = (state: T, ...p: any[]) => T | void | Promise<T>;
|
|
11
|
+
declare type ActionDef<T = any> = [Action<T>, object?];
|
|
12
|
+
declare type Update<T = any> = { [name: string]: Action<T> | ActionDef<T> | Array<Action<T> | ActionDef<T>> };
|
|
13
|
+
|
|
14
|
+
declare interface IApp {
|
|
15
|
+
start<T>(element?: Element | string | null, model?: T, view?: View<T>, update?: Update<T>, options?: AppStartOptions): Component<T>;
|
|
16
|
+
on(name: string, fn: (...args: any[]) => void, options?: any): void;
|
|
17
|
+
run(name: string, ...args: any[]): void;
|
|
18
|
+
createElement(tag: string | Function, props?: any, ...children: any[]): VNode;
|
|
19
|
+
render(element: Element, node: VNode): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare interface Component<T = any> {
|
|
23
|
+
readonly state: T;
|
|
24
|
+
setState(state: T, options?: any): void;
|
|
25
|
+
mount(element?: Element | string | null, options?: any): Component<T>;
|
|
26
|
+
start(element?: Element | string | null, options?: any): Component<T>;
|
|
27
|
+
run(name: string, ...args: any[]): void;
|
|
28
|
+
rendered?: (state: T) => void;
|
|
29
|
+
view?: View<T>;
|
|
30
|
+
update?: Update<T>;
|
|
31
|
+
element?: Element;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare interface CustomElementOptions {
|
|
35
|
+
render?: boolean;
|
|
36
|
+
shadow?: boolean;
|
|
37
|
+
history?: boolean | { prev: string; next: string };
|
|
38
|
+
global_event?: boolean;
|
|
39
|
+
route?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare interface AppStartOptions extends CustomElementOptions {
|
|
43
|
+
render?: boolean;
|
|
44
|
+
history?: boolean | { prev: string; next: string };
|
|
45
|
+
global_event?: boolean;
|
|
46
|
+
route?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare interface Route {
|
|
50
|
+
(url: string): void;
|
|
51
|
+
push(url: string, notify?: boolean): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare const app: IApp;
|
|
55
|
+
declare function on<T = any>(name?: string, options?: any): (target: any, key: string) => void;
|
|
56
|
+
declare function Component<T = any>(options?: any): (constructor: Function) => void;
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core TypeScript Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* This file defines the fundamental types used across AppRun:
|
|
5
|
+
* 1. Component Types
|
|
6
|
+
* - View: Function that renders state to VDOM
|
|
7
|
+
* - Action: Function that updates state
|
|
8
|
+
* - Update: Collection of actions
|
|
9
|
+
*
|
|
10
|
+
* 2. Virtual DOM Types
|
|
11
|
+
* - VNode: Virtual DOM node structure
|
|
12
|
+
* - VDOM: Union of possible VDOM types
|
|
13
|
+
* - Element: DOM element references
|
|
14
|
+
*
|
|
15
|
+
* 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
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```ts
|
|
23
|
+
* class MyComponent implements Component<State> {
|
|
24
|
+
* view: View<State>;
|
|
25
|
+
* update: Update<State>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
1
30
|
import { TemplateResult } from 'lit-html';
|
|
2
31
|
export type Element = HTMLElement | string;
|
|
3
32
|
export type VNode = {
|
|
@@ -31,4 +60,4 @@ export type AppStartOptions<T> = {
|
|
|
31
60
|
route?: string;
|
|
32
61
|
rendered?: (state: T) => void
|
|
33
62
|
mounted?: (props:any, children:any, state: T) => T
|
|
34
|
-
};
|
|
63
|
+
};
|
package/src/vdom-lit-html.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createElement, updateElement, Fragment } from './vdom-my';
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
import { render, svg, html, noChange, nothing } from 'lit
|
|
5
|
-
import { directive, Directive,
|
|
6
|
-
import { unsafeHTML } from 'lit
|
|
4
|
+
import { render, svg, html, noChange, nothing } from 'lit';
|
|
5
|
+
import { directive, Directive, PartInfo, ElementPart, DirectiveResult, EventPart, PartType } from 'lit/directive.js';
|
|
6
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
7
7
|
import app from './apprun';
|
|
8
8
|
|
|
9
9
|
function _render(element, vdom, parent?) {
|
|
@@ -26,32 +26,35 @@ export class RunDirective extends Directive {
|
|
|
26
26
|
constructor(partInfo: PartInfo) {
|
|
27
27
|
super(partInfo);
|
|
28
28
|
// When necessary, validate part in constructor using `part.type`
|
|
29
|
-
if (partInfo.type !== PartType.EVENT) {
|
|
30
|
-
throw new Error('
|
|
29
|
+
if (partInfo.type !== PartType.EVENT) { // In lit v3, use PartType.EVENT
|
|
30
|
+
throw new Error('run() can only be used in event handlers');
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
//
|
|
34
|
-
update(part:
|
|
33
|
+
// Override update to perform any direct DOM manipulation
|
|
34
|
+
update(part: EventPart, props: Array<any>): DirectiveResult {
|
|
35
35
|
/* Any imperative updates to DOM/parts would go here */
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const element = part.element;
|
|
37
|
+
const name = part.name;
|
|
38
|
+
const [eventOrFn, ...args] = props;
|
|
39
|
+
|
|
38
40
|
const getComponent = () => {
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
let el = element;
|
|
42
|
+
let component = el['_component'];
|
|
43
|
+
while (!component && el) {
|
|
44
|
+
el = el.parentElement;
|
|
45
|
+
component = el && el['_component'];
|
|
43
46
|
}
|
|
44
47
|
console.assert(!!component, 'Component not found.');
|
|
45
48
|
return component;
|
|
46
49
|
}
|
|
47
|
-
|
|
48
|
-
if (typeof
|
|
50
|
+
|
|
51
|
+
if (typeof eventOrFn === 'string') {
|
|
49
52
|
element[`on${name}`] = e => {
|
|
50
53
|
const component = getComponent();
|
|
51
|
-
component ? component.run(
|
|
54
|
+
component ? component.run(eventOrFn, ...args, e) : app.run(eventOrFn, ...args, e)
|
|
52
55
|
}
|
|
53
|
-
} else if (typeof
|
|
54
|
-
element[`on${name}`] = e => getComponent().setState(
|
|
56
|
+
} else if (typeof eventOrFn === 'function') {
|
|
57
|
+
element[`on${name}`] = e => getComponent().setState(eventOrFn(getComponent().state, ...args, e));
|
|
55
58
|
}
|
|
56
59
|
return this.render();
|
|
57
60
|
}
|
|
@@ -62,4 +65,3 @@ export class RunDirective extends Directive {
|
|
|
62
65
|
|
|
63
66
|
const run = directive(RunDirective) as any;
|
|
64
67
|
export { createElement, Fragment, html, svg, _render as render, run };
|
|
65
|
-
|