apprun 3.38.0 → 6.0.0-rc.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/CHANGELOG.md +12 -1
- package/README.md +18 -10
- package/apprun.d.ts +67 -23
- package/dist/apprun-code.js +1 -1
- package/dist/apprun-code.js.map +1 -1
- package/dist/apprun-dev-tools.js +1 -1
- 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.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/add-components.js +7 -7
- package/esm/add-components.js.map +1 -1
- package/esm/app.js +81 -32
- package/esm/app.js.map +1 -1
- package/esm/apprun-code.js +8 -2
- package/esm/apprun-code.js.map +1 -1
- package/esm/apprun-html.js +14 -9
- package/esm/apprun-html.js.map +1 -1
- package/esm/apprun-play.js +7 -1
- package/esm/apprun-play.js.map +1 -1
- package/esm/apprun.js +38 -19
- package/esm/apprun.js.map +1 -1
- package/esm/component.js +90 -37
- package/esm/component.js.map +1 -1
- package/esm/directive.js +1 -1
- package/esm/directive.js.map +1 -1
- package/esm/router.js +73 -19
- package/esm/router.js.map +1 -1
- package/esm/types.js +2 -1
- package/esm/types.js.map +1 -1
- package/esm/vdom-my.js +52 -52
- package/esm/vdom-my.js.map +1 -1
- package/esm/vdom-to-html.js +8 -1
- package/esm/vdom-to-html.js.map +1 -1
- package/esm/vdom.js +3 -3
- package/esm/vdom.js.map +1 -1
- package/esm/version.js +1 -1
- package/esm/version.js.map +1 -1
- package/esm/web-component.js +23 -6
- package/esm/web-component.js.map +1 -1
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +62 -2
- package/.clinerules +0 -1
- package/.travis.yml +0 -10
- package/BACKERS.md +0 -8
- package/CNAME +0 -1
- package/WHATSNEW.md +0 -367
- package/ai/apprun-html.prompt.md +0 -290
- package/ai/apprun.prompt.md +0 -602
- package/apprun-book.jpg +0 -0
- package/dist/apprun-dev-tools-tests.js +0 -1
- package/dist/apprun-dev-tools.js.LICENSE.txt +0 -1
- package/dist/apprun-play-html.esm.js +0 -34
- package/dist/apprun-play-html.esm.js.map +0 -1
- package/error.log +0 -0
- package/esm/apprun-play-html.js +0 -210
- package/esm/apprun-play-html.js.map +0 -1
- package/esm/shadow.js +0 -8
- package/esm/shadow.js.map +0 -1
- package/esm/vdom-html.js +0 -22
- package/esm/vdom-html.js.map +0 -1
- package/esm/vdom-my-new.js +0 -327
- package/esm/vdom-my-new.js.map +0 -1
- package/esm/vdom-patch.js +0 -103
- package/esm/vdom-patch.js.map +0 -1
- package/index.html +0 -87
- package/jest.config.js +0 -57
- package/jest.setup.js +0 -73
- package/logo.png +0 -0
- package/react.js +0 -13
- package/rollup.config.js +0 -62
- package/src/add-components.ts +0 -103
- package/src/app.ts +0 -173
- package/src/apprun-code.tsx +0 -177
- package/src/apprun-dev-tools-tests.tsx +0 -101
- package/src/apprun-dev-tools.tsx +0 -307
- package/src/apprun-html.ts +0 -25
- package/src/apprun-play.tsx +0 -249
- package/src/apprun.ts +0 -238
- package/src/component.ts +0 -388
- package/src/createState.ts +0 -11
- package/src/decorator.ts +0 -98
- package/src/directive.ts +0 -303
- package/src/global.d.ts +0 -22
- package/src/router.ts +0 -311
- package/src/shadow.tsx +0 -8
- package/src/tsconfig.json +0 -16
- package/src/type-utils.ts +0 -130
- package/src/types.ts +0 -151
- package/src/vdom-html.ts_ +0 -20
- package/src/vdom-lit-html.ts +0 -67
- package/src/vdom-my-new.ts +0 -311
- package/src/vdom-my-prop-attr.ts +0 -241
- package/src/vdom-my.ts +0 -286
- package/src/vdom-patch.ts +0 -108
- package/src/vdom-to-html.tsx +0 -49
- package/src/vdom.ts +0 -28
- package/src/version.ts +0 -16
- package/src/web-component.ts +0 -154
- package/tsconfig.jest.json +0 -49
- package/tsconfig.json +0 -16
- package/tslint.json +0 -114
- package/typescriptreact.json +0 -87
- package/viewEngine.js +0 -54
- package/webpack.config.cjs +0 -41
package/src/apprun.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
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 with improved null safety
|
|
11
|
-
* - Web component support
|
|
12
|
-
* - Type-safe React integration
|
|
13
|
-
* - Component batch mounting system
|
|
14
|
-
*
|
|
15
|
-
* Key exports:
|
|
16
|
-
* - app: Global event system instance
|
|
17
|
-
* - Component: Base component class
|
|
18
|
-
* - Decorators: @on, @update, @customElement
|
|
19
|
-
* - Router events and configuration
|
|
20
|
-
* - Web component registration
|
|
21
|
-
*
|
|
22
|
-
* Features:
|
|
23
|
-
* - Event-driven architecture with pub/sub pattern
|
|
24
|
-
* - Virtual DOM rendering with multiple renderer support
|
|
25
|
-
* - Component lifecycle management
|
|
26
|
-
* - Client-side routing with hash/path support
|
|
27
|
-
* - Web Components integration
|
|
28
|
-
* - React compatibility layer
|
|
29
|
-
* - TypeScript support with strong typing
|
|
30
|
-
* - Batch component mounting with addComponents(element, components)
|
|
31
|
-
*
|
|
32
|
-
* Type Safety Improvements (v3.35.1):
|
|
33
|
-
* - Added null checks for DOM event targets
|
|
34
|
-
* - Improved global window object assignments with proper typing
|
|
35
|
-
* - Enhanced React integration parameter validation
|
|
36
|
-
* - Better error handling for invalid event handlers
|
|
37
|
-
* - Safer element access with proper type assertions
|
|
38
|
-
*
|
|
39
|
-
* Recent Changes:
|
|
40
|
-
* - Modified addComponents to accept (element, components) where components is a key-value object with routes as keys and components as values
|
|
41
|
-
* - Simplified component mounting API for better usability
|
|
42
|
-
*
|
|
43
|
-
* Usage:
|
|
44
|
-
* ```ts
|
|
45
|
-
* import { app, Component } from 'apprun';
|
|
46
|
-
*
|
|
47
|
-
* // Create components
|
|
48
|
-
* class MyComponent extends Component {
|
|
49
|
-
* state = // Initial state
|
|
50
|
-
* view = state => // Render view
|
|
51
|
-
* update = {
|
|
52
|
-
* 'event': (state, ...args) => // Handle events
|
|
53
|
-
* }
|
|
54
|
-
* }
|
|
55
|
-
*
|
|
56
|
-
* // Mount multiple components
|
|
57
|
-
* app.addComponents(document.body, {
|
|
58
|
-
* '/home': MyComponent,
|
|
59
|
-
* '/about': AnotherComponent
|
|
60
|
-
* });
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
import _app, { App } from './app';
|
|
65
|
-
import { createElement, render, Fragment, safeHTML } from './vdom';
|
|
66
|
-
import { Component } from './component';
|
|
67
|
-
import { IApp, VNode, State, View, Action, Update, EventOptions, ActionOptions, MountOptions, AppStartOptions, CustomElementOptions } from './types';
|
|
68
|
-
import { on, update, customElement } from './decorator';
|
|
69
|
-
import { route, ROUTER_EVENT, ROUTER_404_EVENT } from './router';
|
|
70
|
-
import webComponent from './web-component';
|
|
71
|
-
import addComponents from './add-components';
|
|
72
|
-
import { APPRUN_VERSION } from './version';
|
|
73
|
-
|
|
74
|
-
export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
|
|
75
|
-
type OnDecorator = {
|
|
76
|
-
<T = unknown>(options?: any): (constructor: Function) => void;
|
|
77
|
-
<E = string>(events?: E, options?: any): (target: any, key: string) => void;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const app: IApp = _app as unknown as IApp;
|
|
81
|
-
export default app as IApp;
|
|
82
|
-
|
|
83
|
-
export {
|
|
84
|
-
App,
|
|
85
|
-
app,
|
|
86
|
-
Component,
|
|
87
|
-
View,
|
|
88
|
-
Action,
|
|
89
|
-
Update,
|
|
90
|
-
on,
|
|
91
|
-
update,
|
|
92
|
-
EventOptions,
|
|
93
|
-
ActionOptions,
|
|
94
|
-
MountOptions,
|
|
95
|
-
Fragment,
|
|
96
|
-
safeHTML
|
|
97
|
-
}
|
|
98
|
-
export { update as event };
|
|
99
|
-
export { ROUTER_EVENT, ROUTER_404_EVENT };
|
|
100
|
-
export { customElement, CustomElementOptions, AppStartOptions };
|
|
101
|
-
|
|
102
|
-
if (!app.start) {
|
|
103
|
-
|
|
104
|
-
app.version = APPRUN_VERSION;
|
|
105
|
-
|
|
106
|
-
app.h = app.createElement = createElement;
|
|
107
|
-
app.render = render;
|
|
108
|
-
app.Fragment = Fragment;
|
|
109
|
-
app.webComponent = webComponent;
|
|
110
|
-
app.safeHTML = safeHTML;
|
|
111
|
-
|
|
112
|
-
app.start = <T, E = unknown>(element?: Element | string, state?: State<T>, view?: View<T>, update?: Update<T, E>,
|
|
113
|
-
options?: AppStartOptions<T>): Component<T, E> => {
|
|
114
|
-
const opts = { render: true, global_event: true, ...options };
|
|
115
|
-
const component = new Component<T, E>(state, view, update);
|
|
116
|
-
if (options && options.rendered) component.rendered = options.rendered;
|
|
117
|
-
if (options && options.mounted) component.mounted = options.mounted;
|
|
118
|
-
component.start(element, opts);
|
|
119
|
-
return component;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Deprecated: app.query is deprecated in favor of app.runAsync
|
|
123
|
-
app.query = app.query || app.runAsync;
|
|
124
|
-
|
|
125
|
-
const NOOP = _ => {/* Intentionally empty */ }
|
|
126
|
-
app.on('/', NOOP);
|
|
127
|
-
app.on('debug', _ => NOOP);
|
|
128
|
-
app.on(ROUTER_EVENT, NOOP);
|
|
129
|
-
app.on(ROUTER_404_EVENT, NOOP);
|
|
130
|
-
app.route = route;
|
|
131
|
-
app.on('route', url => app['route'] && app['route'](url));
|
|
132
|
-
|
|
133
|
-
if (typeof document === 'object') {
|
|
134
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
135
|
-
const no_init_route = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
|
|
136
|
-
const use_hash = app.find('#') || app.find('#/') || false;
|
|
137
|
-
|
|
138
|
-
// console.log(`AppRun ${app.version} started with ${use_hash ? 'hash' : 'path'} routing. Initial load: ${init_load ? 'disabled' : 'enabled'}.`);
|
|
139
|
-
window.addEventListener('hashchange', () => route(location.hash));
|
|
140
|
-
window.addEventListener('popstate', () => route(location.pathname));
|
|
141
|
-
|
|
142
|
-
if (use_hash) {
|
|
143
|
-
!no_init_route && route(location.hash);
|
|
144
|
-
} else {
|
|
145
|
-
!no_init_route && (() => {
|
|
146
|
-
const basePath = app.basePath || '';
|
|
147
|
-
let initialPath = location.pathname;
|
|
148
|
-
|
|
149
|
-
// Strip base path if present
|
|
150
|
-
if (basePath && initialPath.startsWith(basePath)) {
|
|
151
|
-
initialPath = initialPath.substring(basePath.length);
|
|
152
|
-
if (!initialPath.startsWith('/')) initialPath = '/' + initialPath;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
route(initialPath);
|
|
156
|
-
})();
|
|
157
|
-
document.body.addEventListener('click', e => {
|
|
158
|
-
const element = e.target as HTMLElement;
|
|
159
|
-
if (!element) return;
|
|
160
|
-
|
|
161
|
-
const menu = (element.tagName === 'A' ? element : element.closest('a')) as HTMLAnchorElement;
|
|
162
|
-
if (menu &&
|
|
163
|
-
menu.origin === location.origin &&
|
|
164
|
-
menu.pathname) {
|
|
165
|
-
e.preventDefault();
|
|
166
|
-
|
|
167
|
-
// Handle base path for navigation
|
|
168
|
-
const basePath = app.basePath || '';
|
|
169
|
-
const fullPath = basePath + menu.pathname;
|
|
170
|
-
|
|
171
|
-
history.pushState(null, '', fullPath);
|
|
172
|
-
route(menu.pathname); // Route with relative path (without base path)
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (typeof window === 'object') {
|
|
180
|
-
const globalWindow = window as any;
|
|
181
|
-
globalWindow['Component'] = Component;
|
|
182
|
-
globalWindow['_React'] = globalWindow['React'];
|
|
183
|
-
globalWindow['React'] = app;
|
|
184
|
-
globalWindow['on'] = on as OnDecorator;
|
|
185
|
-
globalWindow['customElement'] = customElement;
|
|
186
|
-
globalWindow['safeHTML'] = safeHTML;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
app.use_render = (render, mode = 0) => {
|
|
190
|
-
if (mode === 0) {
|
|
191
|
-
app.render = (el, vdom) => render(vdom, el); // react style
|
|
192
|
-
} else {
|
|
193
|
-
app.render = (el, vdom) => render(el, vdom); // apprun style
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
app.use_react = (React, ReactDOM) => {
|
|
198
|
-
if (!React || !ReactDOM) {
|
|
199
|
-
console.error('AppRun use_react: React and ReactDOM parameters are required');
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (typeof React.createElement !== 'function') {
|
|
204
|
-
console.error('AppRun use_react: Invalid React object - createElement method not found');
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!React.Fragment) {
|
|
209
|
-
console.error('AppRun use_react: Invalid React object - Fragment not found');
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
app.h = app.createElement = React.createElement;
|
|
214
|
-
app.Fragment = React.Fragment;
|
|
215
|
-
|
|
216
|
-
// React 18+ uses createRoot API
|
|
217
|
-
if (React.version && React.version.startsWith('18')) {
|
|
218
|
-
if (!ReactDOM.createRoot || typeof ReactDOM.createRoot !== 'function') {
|
|
219
|
-
console.error('AppRun use_react: ReactDOM.createRoot not found in React 18+');
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
app.render = (el, vdom) => {
|
|
223
|
-
if (!el || vdom === undefined) return;
|
|
224
|
-
if (!(el as any)._root) (el as any)._root = ReactDOM.createRoot(el);
|
|
225
|
-
(el as any)._root.render(vdom);
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
// Legacy React versions
|
|
229
|
-
if (!ReactDOM.render || typeof ReactDOM.render !== 'function') {
|
|
230
|
-
console.error('AppRun use_react: ReactDOM.render not found in legacy React');
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
app.render = (el, vdom) => ReactDOM.render(vdom, el);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
app.addComponents = addComponents;
|
|
238
|
-
}
|
package/src/component.ts
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
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 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
|
|
10
|
-
*
|
|
11
|
-
* 2. View Rendering
|
|
12
|
-
* - Renders virtual DOM to real DOM with directives
|
|
13
|
-
* - Handles component lifecycle (mounted, rendered, unload)
|
|
14
|
-
* - Supports shadow DOM and web components
|
|
15
|
-
* - DOM change tracking with MutationObserver
|
|
16
|
-
* - View transition API support
|
|
17
|
-
*
|
|
18
|
-
* 3. Event Handling
|
|
19
|
-
* - Local and global event subscription management
|
|
20
|
-
* - Event handler registration via decorators
|
|
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
|
|
38
|
-
*
|
|
39
|
-
* Usage:
|
|
40
|
-
* ```ts
|
|
41
|
-
* class MyComponent extends Component {
|
|
42
|
-
* state = // Initial state
|
|
43
|
-
* view = state => // Return virtual DOM
|
|
44
|
-
* update = {
|
|
45
|
-
* 'event': (state, ...args) => // Return new state
|
|
46
|
-
* }
|
|
47
|
-
* }
|
|
48
|
-
*
|
|
49
|
-
* // Mount component
|
|
50
|
-
* new MyComponent().mount('element-id');
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
import _app, { App } from './app';
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
import { Reflect } from './decorator'
|
|
58
|
-
import { State, View, Update, ActionDef, ActionOptions, MountOptions, EventOptions, IApp } from './types';
|
|
59
|
-
import directive from './directive';
|
|
60
|
-
import { safeQuerySelector, safeGetElementById } from './type-utils';
|
|
61
|
-
|
|
62
|
-
// const componentCache = new Map();
|
|
63
|
-
// if (!app.find('get-components')) app.on('get-components', o => o.components = componentCache);
|
|
64
|
-
|
|
65
|
-
export const REFRESH = state => state;
|
|
66
|
-
|
|
67
|
-
const app = _app as unknown as IApp;
|
|
68
|
-
|
|
69
|
-
export class Component<T = unknown, E = unknown> {
|
|
70
|
-
static __isAppRunComponent = true;
|
|
71
|
-
private _app = new App();
|
|
72
|
-
private _actions = [];
|
|
73
|
-
private _global_events = [];
|
|
74
|
-
private _state;
|
|
75
|
-
private _history = [];
|
|
76
|
-
private _history_idx = -1;
|
|
77
|
-
private enable_history;
|
|
78
|
-
private global_event;
|
|
79
|
-
public element;
|
|
80
|
-
public rendered;
|
|
81
|
-
public mounted;
|
|
82
|
-
public unload;
|
|
83
|
-
private tracking_id;
|
|
84
|
-
private observer;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
private renderState(state: T, vdom = null) {
|
|
88
|
-
if (!this.view) return;
|
|
89
|
-
let html = vdom || this.view(state);
|
|
90
|
-
app['debug'] && app.run('debug', {
|
|
91
|
-
component: this,
|
|
92
|
-
_: html ? '.' : '-',
|
|
93
|
-
state,
|
|
94
|
-
vdom: html,
|
|
95
|
-
el: this.element
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (typeof document !== 'object') return;
|
|
99
|
-
|
|
100
|
-
const el = (typeof this.element === 'string' && this.element) ?
|
|
101
|
-
safeGetElementById(this.element) || safeQuerySelector(this.element) : this.element;
|
|
102
|
-
|
|
103
|
-
if (!el) {
|
|
104
|
-
console.warn(`Component element not found: ${this.element}`);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const tracking_attr = '_c';
|
|
108
|
-
if (!this.unload) {
|
|
109
|
-
el.removeAttribute && el.removeAttribute(tracking_attr);
|
|
110
|
-
} else if (el['_component'] !== this || el.getAttribute(tracking_attr) !== this.tracking_id) {
|
|
111
|
-
this.tracking_id = new Date().valueOf().toString();
|
|
112
|
-
el.setAttribute(tracking_attr, this.tracking_id);
|
|
113
|
-
if (typeof MutationObserver !== 'undefined') {
|
|
114
|
-
if (!this.observer) this.observer = new MutationObserver(changes => {
|
|
115
|
-
if (changes[0].oldValue === this.tracking_id || !document.body.contains(el)) {
|
|
116
|
-
this.unload(this.state);
|
|
117
|
-
this.observer.disconnect();
|
|
118
|
-
this.observer = null;
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
this.observer.observe(document.body, {
|
|
122
|
-
childList: true, subtree: true,
|
|
123
|
-
attributes: true, attributeOldValue: true, attributeFilter: [tracking_attr]
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
el['_component'] = this;
|
|
128
|
-
|
|
129
|
-
if (!vdom && html) {
|
|
130
|
-
html = directive(html, this);
|
|
131
|
-
if (this.options.transition && document && document['startViewTransition']) {
|
|
132
|
-
document['startViewTransition'](() => app.render(el, html, this));
|
|
133
|
-
} else {
|
|
134
|
-
app.render(el, html, this);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
this.rendered && this.rendered(this.state);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public setState(state: T, options: ActionOptions & EventOptions
|
|
141
|
-
= { render: true, history: false }) {
|
|
142
|
-
|
|
143
|
-
const handleAsyncIterator = async (iterator: AsyncIterator<T>) => {
|
|
144
|
-
try {
|
|
145
|
-
while (true) {
|
|
146
|
-
const { value, done } = await iterator.next();
|
|
147
|
-
if (done) break;
|
|
148
|
-
this.setState(value, options);
|
|
149
|
-
}
|
|
150
|
-
} catch (e) {
|
|
151
|
-
console.error('Error in async iterator:', e);
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const result = state as any;
|
|
156
|
-
if (result?.[Symbol.asyncIterator]) {
|
|
157
|
-
// handleAsyncIterator(result[Symbol.asyncIterator]());
|
|
158
|
-
this.setState(handleAsyncIterator(result[Symbol.asyncIterator]()) as any, options);
|
|
159
|
-
return;
|
|
160
|
-
} else if (result?.[Symbol.iterator] && typeof result.next === "function") {
|
|
161
|
-
for (const value of result) {
|
|
162
|
-
this.setState(value, options);
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
} else if (state && state instanceof Promise) {
|
|
166
|
-
// Promise will not be saved or rendered
|
|
167
|
-
// state will be saved and rendered when promise is resolved
|
|
168
|
-
Promise.resolve(state).then(v => {
|
|
169
|
-
this.setState(v, options);
|
|
170
|
-
this._state = state;
|
|
171
|
-
});
|
|
172
|
-
} else {
|
|
173
|
-
this._state = state;
|
|
174
|
-
if (state == null) return;
|
|
175
|
-
this.state = state;
|
|
176
|
-
if (options.render !== false) {
|
|
177
|
-
// before render state
|
|
178
|
-
if (options.transition && document && document['startViewTransition']) {
|
|
179
|
-
document['startViewTransition'](() => this.renderState(state));
|
|
180
|
-
} else {
|
|
181
|
-
this.renderState(state);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (options.history !== false && this.enable_history) {
|
|
185
|
-
this._history = [...this._history, state];
|
|
186
|
-
this._history_idx = this._history.length - 1;
|
|
187
|
-
}
|
|
188
|
-
if (typeof options.callback === 'function') options.callback(this.state);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private _history_prev = () => {
|
|
193
|
-
this._history_idx--;
|
|
194
|
-
if (this._history_idx >= 0) {
|
|
195
|
-
this.setState(this._history[this._history_idx], { render: true, history: false });
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
this._history_idx = 0;
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
private _history_next = () => {
|
|
203
|
-
this._history_idx++;
|
|
204
|
-
if (this._history_idx < this._history.length) {
|
|
205
|
-
this.setState(this._history[this._history_idx], { render: true, history: false });
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
this._history_idx = this._history.length - 1;
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
constructor(
|
|
213
|
-
protected state?: State<T>,
|
|
214
|
-
protected view?: View<T>,
|
|
215
|
-
protected update?: Update<T, E>,
|
|
216
|
-
protected options?) {
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
start = (element = null, options?: MountOptions): Component<T, E> => {
|
|
220
|
-
this.mount(element, { render: true, ...options });
|
|
221
|
-
if (this.mounted && typeof this.mounted === 'function') {
|
|
222
|
-
const new_state = this.mounted({}, [], this.state);
|
|
223
|
-
(typeof new_state !== 'undefined') && this.setState(new_state);
|
|
224
|
-
}
|
|
225
|
-
return this;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
public mount(element = null, options?: MountOptions): Component<T, E> {
|
|
229
|
-
console.assert(!this.element, 'Component already mounted.')
|
|
230
|
-
this.options = options = { ...this.options, ...options };
|
|
231
|
-
this.element = element;
|
|
232
|
-
this.global_event = options.global_event;
|
|
233
|
-
this.enable_history = !!options.history;
|
|
234
|
-
|
|
235
|
-
if (this.enable_history) {
|
|
236
|
-
this.on(options.history.prev || 'history-prev', this._history_prev);
|
|
237
|
-
this.on(options.history.next || 'history-next', this._history_next);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (options.route) {
|
|
241
|
-
this.update = this.update || {};
|
|
242
|
-
if (!this.update[options.route]) this.update[options.route] = REFRESH;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
this.add_actions();
|
|
246
|
-
this.state = this.state ?? this['model'] ?? {};
|
|
247
|
-
if (typeof this.state === 'function') this.state = (this.state as Function)();
|
|
248
|
-
this.setState(this.state as T, { render: !!options.render, history: true });
|
|
249
|
-
if (app['debug'] && app.find('debug-create-component')?.length) {
|
|
250
|
-
app.run('debug-create-component', this);
|
|
251
|
-
}
|
|
252
|
-
return this;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
is_global_event(name: string): boolean {
|
|
256
|
-
return name && (
|
|
257
|
-
this.global_event ||
|
|
258
|
-
this._global_events.indexOf(name) >= 0 ||
|
|
259
|
-
name.startsWith('#') || name.startsWith('/') || name.startsWith('@'));
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
add_action(name: string, action, options: ActionOptions = {}) {
|
|
263
|
-
if (!action || typeof action !== 'function') {
|
|
264
|
-
console.warn(`Component action for '${name}' is not a valid function:`, action);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
if (options.global) this._global_events.push(name);
|
|
268
|
-
this.on(name as any, (...p) => {
|
|
269
|
-
|
|
270
|
-
app['debug'] && app.run('debug', {
|
|
271
|
-
component: this,
|
|
272
|
-
_: '>',
|
|
273
|
-
event: name, p,
|
|
274
|
-
current_state: this.state,
|
|
275
|
-
options
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
try {
|
|
279
|
-
const newState = action(this.state, ...p);
|
|
280
|
-
|
|
281
|
-
app['debug'] && app.run('debug', {
|
|
282
|
-
component: this,
|
|
283
|
-
_: '<',
|
|
284
|
-
event: name, p,
|
|
285
|
-
newState,
|
|
286
|
-
state: this.state,
|
|
287
|
-
options
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
this.setState(newState, options);
|
|
291
|
-
} catch (error) {
|
|
292
|
-
console.error(`Error in component action '${name}':`, error);
|
|
293
|
-
app['debug'] && app.run('debug', {
|
|
294
|
-
component: this,
|
|
295
|
-
_: '!',
|
|
296
|
-
event: name, p,
|
|
297
|
-
error,
|
|
298
|
-
state: this.state,
|
|
299
|
-
options
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}, options);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
add_actions() {
|
|
306
|
-
const actions = this.update || {};
|
|
307
|
-
Reflect.getMetadataKeys(this).forEach(key => {
|
|
308
|
-
if (key.startsWith('apprun-update:')) {
|
|
309
|
-
const meta = Reflect.getMetadata(key, this)
|
|
310
|
-
actions[meta.name] = [this[meta.key].bind(this), meta.options];
|
|
311
|
-
}
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
const all = {};
|
|
315
|
-
if (Array.isArray(actions)) {
|
|
316
|
-
actions.forEach(act => {
|
|
317
|
-
const [name, action, opts] = act as ActionDef<T, E>;
|
|
318
|
-
const names = name.toString();
|
|
319
|
-
names.split(',').forEach(n => all[n.trim()] = [action, opts])
|
|
320
|
-
})
|
|
321
|
-
} else {
|
|
322
|
-
Object.keys(actions).forEach(name => {
|
|
323
|
-
const action = actions[name];
|
|
324
|
-
if (typeof action === 'function' || Array.isArray(action)) {
|
|
325
|
-
name.split(',').forEach(n => all[n.trim()] = action)
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (!all['.']) all['.'] = REFRESH;
|
|
331
|
-
Object.keys(all).forEach(name => {
|
|
332
|
-
const action = all[name];
|
|
333
|
-
if (typeof action === 'function') {
|
|
334
|
-
this.add_action(name, action);
|
|
335
|
-
} else if (Array.isArray(action)) {
|
|
336
|
-
this.add_action(name, action[0], action[1]);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
public run(event: E, ...args) {
|
|
342
|
-
if (this.state instanceof Promise) {
|
|
343
|
-
return Promise.resolve(this.state).then(state => {
|
|
344
|
-
this.state = state;
|
|
345
|
-
this.run(event, ...args)
|
|
346
|
-
});
|
|
347
|
-
} else {
|
|
348
|
-
const name = event.toString();
|
|
349
|
-
return this.is_global_event(name) ?
|
|
350
|
-
app.run(name, ...args) :
|
|
351
|
-
this._app.run(name, ...args);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
public on(event: E, fn: (...args) => void, options?: any) {
|
|
356
|
-
const name = event.toString();
|
|
357
|
-
this._actions.push({ name, fn });
|
|
358
|
-
return this.is_global_event(name) ?
|
|
359
|
-
app.on(name, fn, options) :
|
|
360
|
-
this._app.on(name, fn, options);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
public runAsync(event: E, ...args) {
|
|
364
|
-
const name = event.toString();
|
|
365
|
-
return this.is_global_event(name) ?
|
|
366
|
-
app.runAsync(name, ...args) :
|
|
367
|
-
this._app.runAsync(name, ...args);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// obsolete
|
|
371
|
-
/**
|
|
372
|
-
* @deprecated Use runAsync() instead. query() will be removed in a future version.
|
|
373
|
-
*/
|
|
374
|
-
public query(event: E, ...args) {
|
|
375
|
-
console.warn('component.query() is deprecated. Use component.runAsync() instead.');
|
|
376
|
-
return this.runAsync(event, ...args);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
public unmount() {
|
|
380
|
-
this.observer?.disconnect();
|
|
381
|
-
this._actions.forEach(action => {
|
|
382
|
-
const { name, fn } = action;
|
|
383
|
-
this.is_global_event(name) ?
|
|
384
|
-
app.off(name, fn) :
|
|
385
|
-
this._app.off(name, fn);
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
package/src/createState.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { produce, Draft } from 'immer';
|
|
2
|
-
|
|
3
|
-
export function createState<T = unknown>(
|
|
4
|
-
fn: (draft: Draft<T>, ...args: any[]) => void
|
|
5
|
-
): (state: T, ...args: any[]) => T {
|
|
6
|
-
return (state: T, ...args: any[]): T => {
|
|
7
|
-
return produce(state, (draft) => {
|
|
8
|
-
fn(draft, ...args);
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
}
|