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/router.ts
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AppRun Router Implementation with Hierarchical Matching
|
|
3
|
-
*
|
|
4
|
-
* This file provides URL routing capabilities:
|
|
5
|
-
* 1. Hash-based routing (#/path)
|
|
6
|
-
* - Works with SPA mode using hash fragments
|
|
7
|
-
* - Handles hashchange events automatically
|
|
8
|
-
* - No server configuration required
|
|
9
|
-
* 2. Path-based routing (/path)
|
|
10
|
-
* - Works with browser history API
|
|
11
|
-
* - Requires server configuration for SPA routing
|
|
12
|
-
* - Handles popstate events automatically
|
|
13
|
-
* 3. Event-based navigation
|
|
14
|
-
* - Routes trigger corresponding component events
|
|
15
|
-
* - Automatic route parameter extraction
|
|
16
|
-
* - Fallback to 404 handling for unknown routes
|
|
17
|
-
*
|
|
18
|
-
* Features:
|
|
19
|
-
* - Automatic route event firing with ROUTER_EVENT
|
|
20
|
-
* - 404 handling via ROUTER_404_EVENT for unmatched routes
|
|
21
|
-
* - History API integration for seamless navigation
|
|
22
|
-
* - Route parameter parsing and injection into events
|
|
23
|
-
* - URL pattern matching with parameter support
|
|
24
|
-
* - Global and component-level route handling
|
|
25
|
-
* - **NEW: Hierarchical route matching** - progressively tries parent routes
|
|
26
|
-
*
|
|
27
|
-
* Type Safety Improvements (v3.35.1):
|
|
28
|
-
* - Enhanced route type definitions
|
|
29
|
-
* - Better parameter type inference
|
|
30
|
-
* - Improved URL validation and error handling
|
|
31
|
-
*
|
|
32
|
-
* Usage:
|
|
33
|
-
* ```ts
|
|
34
|
-
* // Basic routing
|
|
35
|
-
* app.on('#/home', () => // Show home page);
|
|
36
|
-
* app.on('#/users/:id', (id) => // Show user profile);
|
|
37
|
-
* app.on('/api/*', (path) => // Handle API routes);
|
|
38
|
-
*
|
|
39
|
-
* // Navigate programmatically
|
|
40
|
-
* app.run('route', '#/home');
|
|
41
|
-
* route('/users/123'); // Direct routing
|
|
42
|
-
*
|
|
43
|
-
* // Hierarchical matching examples
|
|
44
|
-
* app.on('/api', (operation, id) => // Handle /api/users/123);
|
|
45
|
-
* app.on('#users', (id, action) => // Handle #users/123/edit);
|
|
46
|
-
*
|
|
47
|
-
* // Hierarchical Route Matching (NEW):
|
|
48
|
-
* // For URL: /api/v1/users/123
|
|
49
|
-
* // Router tries: /api/v1/users/123 → /api/v1/users → /api/v1 → /api → 404
|
|
50
|
-
* // If handler found at /api, it receives: ('v1', 'users', '123')
|
|
51
|
-
*
|
|
52
|
-
* // Base Path Support (NEW):
|
|
53
|
-
* app.basePath = '/myapp'; // For sub-directory deployments
|
|
54
|
-
* // Links: <a href="/users/123"> (relative paths)
|
|
55
|
-
* // Navigation: /myapp/users/123 (full path)
|
|
56
|
-
* // Routing: /users/123 (base path stripped)
|
|
57
|
-
*
|
|
58
|
-
* // Empty Path Handling (NEW):
|
|
59
|
-
* // For URL: "" (empty)
|
|
60
|
-
* // Router tries: # → / → #/ → 404 (in priority order)
|
|
61
|
-
*
|
|
62
|
-
* // 404 Behavior (ENHANCED):
|
|
63
|
-
* // Fires only at minimal levels: /a, #a, #/a, a
|
|
64
|
-
* // Never tries root handlers: /, #, #/
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
|
|
68
|
-
import app from './app';
|
|
69
|
-
|
|
70
|
-
// Helper functions for hierarchical routing
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Extract clean path segments from URL
|
|
74
|
-
* @param url - The URL to parse
|
|
75
|
-
* @returns Array of path segments
|
|
76
|
-
*/
|
|
77
|
-
function parsePathSegments(url: string): string[] {
|
|
78
|
-
if (!url) return [];
|
|
79
|
-
|
|
80
|
-
// Handle different URL types
|
|
81
|
-
if (url.startsWith('#/')) {
|
|
82
|
-
return url.substring(2).split('/');
|
|
83
|
-
} else if (url.startsWith('#')) {
|
|
84
|
-
return url.substring(1).split('/');
|
|
85
|
-
} else if (url.startsWith('/')) {
|
|
86
|
-
return url.substring(1).split('/');
|
|
87
|
-
} else {
|
|
88
|
-
return url.split('/');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Normalize trailing slash - convert /a/ to /a
|
|
94
|
-
* @param url - The URL to normalize
|
|
95
|
-
* @returns Normalized URL
|
|
96
|
-
*/
|
|
97
|
-
function normalizeTrailingSlash(url: string): string {
|
|
98
|
-
if (!url || url === '/' || url === '#' || url === '#/') return url;
|
|
99
|
-
if (url.endsWith('/')) return url.slice(0, -1);
|
|
100
|
-
return url;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Validate hierarchy depth and warn if too deep
|
|
105
|
-
* @param segments - Path segments to validate
|
|
106
|
-
*/
|
|
107
|
-
function validateHierarchyDepth(segments: string[]): void {
|
|
108
|
-
// Count non-empty segments for depth validation
|
|
109
|
-
const nonEmptySegments = segments.filter(Boolean);
|
|
110
|
-
if (nonEmptySegments.length > 11) {
|
|
111
|
-
console.warn(`Deep route hierarchy detected: ${nonEmptySegments.join('/')} (${nonEmptySegments.length} levels)`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Strip base path from URL
|
|
117
|
-
* @param url - The URL to process
|
|
118
|
-
* @param basePath - The base path to remove
|
|
119
|
-
* @returns URL with base path removed
|
|
120
|
-
*/
|
|
121
|
-
function stripBasePath(url: string, basePath: string): string {
|
|
122
|
-
if (!basePath || basePath === '/' || basePath === '') return url;
|
|
123
|
-
|
|
124
|
-
// Normalize base path
|
|
125
|
-
const normalizedBasePath = basePath.startsWith('/') ? basePath : '/' + basePath;
|
|
126
|
-
|
|
127
|
-
if (url.startsWith(normalizedBasePath)) {
|
|
128
|
-
const stripped = url.substring(normalizedBasePath.length);
|
|
129
|
-
return stripped.startsWith('/') ? stripped : '/' + stripped;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return url;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Generate hierarchy of routes to try (stops at minimal level)
|
|
137
|
-
* @param segments - Array of path segments
|
|
138
|
-
* @param routeType - Type of route (path, hash, hash-slash, non-prefixed)
|
|
139
|
-
* @returns Array of route names to try in order
|
|
140
|
-
*/
|
|
141
|
-
function generateRouteHierarchy(segments: string[], routeType: 'path' | 'hash' | 'hash-slash' | 'non-prefixed'): string[] {
|
|
142
|
-
const hierarchy: string[] = [];
|
|
143
|
-
|
|
144
|
-
// Build hierarchy from most specific to least specific
|
|
145
|
-
for (let i = segments.length; i > 0; i--) {
|
|
146
|
-
const currentSegments = segments.slice(0, i);
|
|
147
|
-
let routeName = '';
|
|
148
|
-
|
|
149
|
-
switch (routeType) {
|
|
150
|
-
case 'path':
|
|
151
|
-
routeName = '/' + currentSegments.join('/');
|
|
152
|
-
break;
|
|
153
|
-
case 'hash':
|
|
154
|
-
routeName = '#' + currentSegments.join('/');
|
|
155
|
-
break;
|
|
156
|
-
case 'hash-slash':
|
|
157
|
-
routeName = '#/' + currentSegments.join('/');
|
|
158
|
-
break;
|
|
159
|
-
case 'non-prefixed':
|
|
160
|
-
routeName = currentSegments.join('/');
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
hierarchy.push(routeName);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return hierarchy;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Find handler in hierarchy and return handler info
|
|
172
|
-
* @param hierarchy - Array of route names to try
|
|
173
|
-
* @param originalSegments - Original path segments
|
|
174
|
-
* @returns Handler info if found, null otherwise
|
|
175
|
-
*/
|
|
176
|
-
function findHandlerInHierarchy(hierarchy: string[], originalSegments: string[]): { eventName: string; parameters: string[] } | null {
|
|
177
|
-
for (let i = 0; i < hierarchy.length; i++) {
|
|
178
|
-
const routeName = hierarchy[i];
|
|
179
|
-
const subscribers = app.find(routeName);
|
|
180
|
-
|
|
181
|
-
if (subscribers && subscribers.length > 0) {
|
|
182
|
-
// Found handler - calculate remaining parameters
|
|
183
|
-
const handlerDepth = hierarchy.length - i;
|
|
184
|
-
const parameters = originalSegments.slice(handlerDepth);
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
eventName: routeName,
|
|
188
|
-
parameters: parameters
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Handle empty path with priority order: # → / → #/ → 404
|
|
198
|
-
*/
|
|
199
|
-
function handleEmptyPath(): void {
|
|
200
|
-
// Try # first
|
|
201
|
-
const hashSubscribers = app.find('#');
|
|
202
|
-
if (hashSubscribers && hashSubscribers.length > 0) {
|
|
203
|
-
app.run('#');
|
|
204
|
-
app.run(ROUTER_EVENT, '#');
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Try / second
|
|
209
|
-
const pathSubscribers = app.find('/');
|
|
210
|
-
if (pathSubscribers && pathSubscribers.length > 0) {
|
|
211
|
-
app.run('/');
|
|
212
|
-
app.run(ROUTER_EVENT, '/');
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Try #/ third
|
|
217
|
-
const hashSlashSubscribers = app.find('#/');
|
|
218
|
-
if (hashSlashSubscribers && hashSlashSubscribers.length > 0) {
|
|
219
|
-
app.run('#/');
|
|
220
|
-
app.run(ROUTER_EVENT, '#/');
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Fire 404 if no handlers found
|
|
225
|
-
console.warn('No subscribers for event: ');
|
|
226
|
-
app.run(ROUTER_404_EVENT, '');
|
|
227
|
-
app.run(ROUTER_EVENT, '');
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Main hierarchical routing logic
|
|
232
|
-
* @param url - The URL to route
|
|
233
|
-
*/
|
|
234
|
-
function routeWithHierarchy(url: string): void {
|
|
235
|
-
// Handle empty path
|
|
236
|
-
if (!url) {
|
|
237
|
-
handleEmptyPath();
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Normalize trailing slash
|
|
242
|
-
url = normalizeTrailingSlash(url);
|
|
243
|
-
|
|
244
|
-
// Strip base path if configured
|
|
245
|
-
const basePath = app['basePath'];
|
|
246
|
-
if (basePath) {
|
|
247
|
-
url = stripBasePath(url, basePath);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Parse segments and validate depth
|
|
251
|
-
const segments = parsePathSegments(url);
|
|
252
|
-
validateHierarchyDepth(segments);
|
|
253
|
-
|
|
254
|
-
// Determine route type
|
|
255
|
-
let routeType: 'path' | 'hash' | 'hash-slash' | 'non-prefixed';
|
|
256
|
-
if (url.startsWith('#/')) {
|
|
257
|
-
routeType = 'hash-slash';
|
|
258
|
-
} else if (url.startsWith('#')) {
|
|
259
|
-
routeType = 'hash';
|
|
260
|
-
} else if (url.startsWith('/')) {
|
|
261
|
-
routeType = 'path';
|
|
262
|
-
} else {
|
|
263
|
-
routeType = 'non-prefixed';
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Generate hierarchy
|
|
267
|
-
const hierarchy = generateRouteHierarchy(segments, routeType);
|
|
268
|
-
|
|
269
|
-
// Find handler in hierarchy
|
|
270
|
-
const handlerInfo = findHandlerInHierarchy(hierarchy, segments);
|
|
271
|
-
|
|
272
|
-
if (handlerInfo) {
|
|
273
|
-
// Found handler - publish route with parameters
|
|
274
|
-
publishRoute(handlerInfo.eventName, ...handlerInfo.parameters);
|
|
275
|
-
} else {
|
|
276
|
-
// No handler found - fire 404 with original URL
|
|
277
|
-
if (hierarchy.length > 0) {
|
|
278
|
-
const minimalRoute = hierarchy[hierarchy.length - 1];
|
|
279
|
-
console.warn(`No subscribers for event: ${minimalRoute}`);
|
|
280
|
-
app.run(ROUTER_404_EVENT, url);
|
|
281
|
-
app.run(ROUTER_EVENT, url);
|
|
282
|
-
} else {
|
|
283
|
-
handleEmptyPath();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const publishRoute = (name: string, ...args: any[]) => {
|
|
289
|
-
if (!name || name === ROUTER_EVENT || name === ROUTER_404_EVENT) return;
|
|
290
|
-
const subscribers = app.find(name);
|
|
291
|
-
if (!subscribers || subscribers.length === 0) {
|
|
292
|
-
console.warn(`No subscribers for event: ${name}`);
|
|
293
|
-
app.run(ROUTER_404_EVENT, name, ...args);
|
|
294
|
-
} else {
|
|
295
|
-
app.run(name, ...args);
|
|
296
|
-
}
|
|
297
|
-
app.run(ROUTER_EVENT, name, ...args);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
import { Router } from './types';
|
|
301
|
-
|
|
302
|
-
export const ROUTER_EVENT: string = '//';
|
|
303
|
-
export const ROUTER_404_EVENT: string = '///';
|
|
304
|
-
export const route: Router = (url: string) => {
|
|
305
|
-
if (app['lastUrl'] === url) return; // Prevent duplicate routing
|
|
306
|
-
app['lastUrl'] = url;
|
|
307
|
-
|
|
308
|
-
// Use hierarchical routing logic
|
|
309
|
-
routeWithHierarchy(url);
|
|
310
|
-
}
|
|
311
|
-
export default route;
|
package/src/shadow.tsx
DELETED
package/src/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es2020",
|
|
4
|
-
"module": "esnext",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"jsx": "react",
|
|
7
|
-
"jsxFactory": "app.h",
|
|
8
|
-
"jsxFragmentFactory": "app.Fragment",
|
|
9
|
-
"lib": ["esnext","dom"],
|
|
10
|
-
"experimentalDecorators": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"outDir": "../esm",
|
|
13
|
-
"sourceMap": true,
|
|
14
|
-
"skipLibCheck": true
|
|
15
|
-
}
|
|
16
|
-
}
|
package/src/type-utils.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type Safety Utilities for AppRun
|
|
3
|
-
*
|
|
4
|
-
* This file provides utility functions and types to improve type safety
|
|
5
|
-
* across the AppRun framework, including:
|
|
6
|
-
* 1. Safe type assertions with null checks
|
|
7
|
-
* 2. Global object assignment helpers
|
|
8
|
-
* 3. Event target type guards
|
|
9
|
-
* 4. Function validation utilities
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Safely cast event target to specific HTML element type with null check
|
|
14
|
-
*/
|
|
15
|
-
export function safeEventTarget<T extends HTMLElement = HTMLElement>(event: Event): T | null {
|
|
16
|
-
return event?.target instanceof HTMLElement ? event.target as T : null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Type guard to check if an object is a valid function
|
|
21
|
-
*/
|
|
22
|
-
export function isFunction(obj: any): obj is Function {
|
|
23
|
-
return typeof obj === 'function';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Type guard to check if an object is a valid HTML element
|
|
28
|
-
*/
|
|
29
|
-
export function isHTMLElement(obj: any): obj is HTMLElement {
|
|
30
|
-
return obj instanceof HTMLElement;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Safely assign properties to global object with type checking
|
|
35
|
-
*/
|
|
36
|
-
export function safeGlobalAssign<T extends Record<string, any>>(
|
|
37
|
-
globalObj: any,
|
|
38
|
-
assignments: T
|
|
39
|
-
): void {
|
|
40
|
-
if (typeof globalObj === 'object' && globalObj !== null) {
|
|
41
|
-
Object.keys(assignments).forEach(key => {
|
|
42
|
-
globalObj[key] = assignments[key];
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Safely get property from global object with fallback
|
|
49
|
-
*/
|
|
50
|
-
export function safeGlobalGet<T>(
|
|
51
|
-
globalObj: any,
|
|
52
|
-
property: string,
|
|
53
|
-
fallback?: T
|
|
54
|
-
): T | undefined {
|
|
55
|
-
if (typeof globalObj === 'object' && globalObj !== null) {
|
|
56
|
-
return globalObj[property] ?? fallback;
|
|
57
|
-
}
|
|
58
|
-
return fallback;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Type-safe wrapper for DOM element queries
|
|
63
|
-
*/
|
|
64
|
-
export function safeQuerySelector<T extends Element = Element>(
|
|
65
|
-
selector: string,
|
|
66
|
-
context: Document | Element = document
|
|
67
|
-
): T | null {
|
|
68
|
-
try {
|
|
69
|
-
return context.querySelector<T>(selector);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.warn(`Invalid selector: ${selector}`, error);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Type-safe wrapper for DOM element by ID
|
|
78
|
-
*/
|
|
79
|
-
export function safeGetElementById<T extends HTMLElement = HTMLElement>(
|
|
80
|
-
id: string
|
|
81
|
-
): T | null {
|
|
82
|
-
try {
|
|
83
|
-
return document.getElementById(id) as T | null;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.warn(`Error getting element by id: ${id}`, error);
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Validate and safely execute a function with error handling
|
|
92
|
-
*/
|
|
93
|
-
export function safeExecute<T extends any[], R>(
|
|
94
|
-
fn: ((...args: T) => R) | undefined | null,
|
|
95
|
-
args: T,
|
|
96
|
-
context?: any,
|
|
97
|
-
errorMessage?: string
|
|
98
|
-
): R | undefined {
|
|
99
|
-
if (!isFunction(fn)) {
|
|
100
|
-
if (errorMessage) {
|
|
101
|
-
console.warn(errorMessage, fn);
|
|
102
|
-
}
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
return context ? fn.apply(context, args) : fn(...args);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error(errorMessage || 'Error executing function:', error);
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Type definitions for improved global object safety
|
|
116
|
-
*/
|
|
117
|
-
export interface SafeGlobalWindow {
|
|
118
|
-
Component?: any;
|
|
119
|
-
_React?: any;
|
|
120
|
-
React?: any;
|
|
121
|
-
on?: any;
|
|
122
|
-
customElement?: any;
|
|
123
|
-
safeHTML?: any;
|
|
124
|
-
html?: any;
|
|
125
|
-
svg?: any;
|
|
126
|
-
run?: any;
|
|
127
|
-
[key: string]: any;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export type SafeGlobalContext = SafeGlobalWindow | typeof globalThis | any;
|
package/src/types.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
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 (sync/async/generator)
|
|
8
|
-
* - Update: Collection of actions (array or object format)
|
|
9
|
-
* - ActionDef: Tuple definition for action arrays
|
|
10
|
-
*
|
|
11
|
-
* 2. Virtual DOM Types
|
|
12
|
-
* - VNode: Virtual DOM node structure with tag/props/children
|
|
13
|
-
* - VDOM: Union of possible VDOM types (false, string, VNode, array)
|
|
14
|
-
* - Element: DOM element references (HTMLElement or string selector)
|
|
15
|
-
* - TemplateResult: Lit-html template support
|
|
16
|
-
*
|
|
17
|
-
* 3. Configuration Types
|
|
18
|
-
* - EventOptions: Event handler options (once, delay, transition)
|
|
19
|
-
* - ActionOptions: Action behavior options (render, history, global, callback)
|
|
20
|
-
* - MountOptions: Component mounting options (global events, routing, transitions)
|
|
21
|
-
* - AppStartOptions: Application startup configuration with lifecycle hooks
|
|
22
|
-
*
|
|
23
|
-
* Features:
|
|
24
|
-
* - Strong typing for component lifecycle
|
|
25
|
-
* - Flexible action definition formats
|
|
26
|
-
* - Comprehensive event options
|
|
27
|
-
* - Integration with external libraries (lit-html)
|
|
28
|
-
* - Type-safe component mounting
|
|
29
|
-
* - Lifecycle hook typing
|
|
30
|
-
*
|
|
31
|
-
* Type Safety Improvements (v3.35.1):
|
|
32
|
-
* - Enhanced generic constraints for better type inference
|
|
33
|
-
* - Improved union types for VDOM flexibility
|
|
34
|
-
* - Better callback typing in options
|
|
35
|
-
* - Stricter typing for lifecycle methods
|
|
36
|
-
* - Added support for async generator and generator functions in Action types
|
|
37
|
-
*
|
|
38
|
-
* Usage:
|
|
39
|
-
* ```ts
|
|
40
|
-
* class MyComponent extends Component<State, Events> {
|
|
41
|
-
* view: View<State>;
|
|
42
|
-
* update: Update<State, Events>;
|
|
43
|
-
* }
|
|
44
|
-
*
|
|
45
|
-
* // Type-safe action definitions with async generators
|
|
46
|
-
* const update: Update<State> = {
|
|
47
|
-
* 'event': (state: State, ...args) => newState,
|
|
48
|
-
* 'stream-event': async function* (state: State, ...args) {
|
|
49
|
-
* yield { ...state, loading: true };
|
|
50
|
-
* const data = await fetchData();
|
|
51
|
-
* yield { ...state, data, loading: false };
|
|
52
|
-
* }
|
|
53
|
-
* };
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
import { TemplateResult } from 'lit-html';
|
|
58
|
-
export type Element = HTMLElement | string;
|
|
59
|
-
export type State<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
|
|
60
|
-
export type VNode = {
|
|
61
|
-
tag: string | Function,
|
|
62
|
-
props: {},
|
|
63
|
-
children: Array<VNode | string>
|
|
64
|
-
}
|
|
65
|
-
export type VDOM = false | string | VNode | Array<VNode | string> | TemplateResult;
|
|
66
|
-
export type View<T> = (state: T) => VDOM | void;
|
|
67
|
-
export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void | AsyncGenerator<T> | Generator<T>;
|
|
68
|
-
export type ActionDef<T, E> = (readonly [E, Action<T>, {}?]);
|
|
69
|
-
export type Update<T, E = unknown> = ActionDef<T, E>[] | { [name: string]: Action<T> | {}[] } | (E | Action<T> | {})[];
|
|
70
|
-
export type ActionOptions = {
|
|
71
|
-
render?: boolean, history?, global?: boolean;
|
|
72
|
-
callback?: (state: any) => void;
|
|
73
|
-
};
|
|
74
|
-
export type EventOptions = {
|
|
75
|
-
once?: boolean;
|
|
76
|
-
transition?: boolean;
|
|
77
|
-
delay?: number;
|
|
78
|
-
} | any;
|
|
79
|
-
export type MountOptions = {
|
|
80
|
-
render?: boolean, history?, global_event?: boolean, route?: string;
|
|
81
|
-
transition?: boolean;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export type AppStartOptions<T> = {
|
|
85
|
-
render?: boolean;
|
|
86
|
-
history?;
|
|
87
|
-
transition?: boolean;
|
|
88
|
-
route?: string;
|
|
89
|
-
rendered?: (state: T) => void
|
|
90
|
-
mounted?: (props: any, children: any, state: T) => T
|
|
91
|
-
};
|
|
92
|
-
export type Router = (url: string, ...args: any[]) => any;
|
|
93
|
-
|
|
94
|
-
export type CustomElementOptions = {
|
|
95
|
-
render?: boolean;
|
|
96
|
-
shadow?: boolean;
|
|
97
|
-
history?: boolean;
|
|
98
|
-
global_event?: boolean;
|
|
99
|
-
observedAttributes?: string[];
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export interface IApp {
|
|
103
|
-
// Event system methods
|
|
104
|
-
on(name: string, fn: (...args: any[]) => any, options?: EventOptions): void;
|
|
105
|
-
off(name: string, fn: (...args: any[]) => any): void;
|
|
106
|
-
run(name: string, ...args: any[]): number;
|
|
107
|
-
runAsync(name: string, ...args: any[]): Promise<any[]>;
|
|
108
|
-
once(name: string, fn: (...args: any[]) => any, options?: EventOptions): void;
|
|
109
|
-
find(name: string): any;
|
|
110
|
-
|
|
111
|
-
/** @deprecated Use runAsync() instead */
|
|
112
|
-
query(name: string, ...args: any[]): Promise<any[]>;
|
|
113
|
-
|
|
114
|
-
start<T, E = unknown>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
|
|
115
|
-
options?: AppStartOptions<T>): any;
|
|
116
|
-
|
|
117
|
-
h(tag: string | Function, props?: any, ...children: any[]): VNode | VNode[];
|
|
118
|
-
createElement(tag: string | Function, props?: any, ...children: any[]): VNode | VNode[];
|
|
119
|
-
render(element: Element | ShadowRoot, node: VNode, component?: {}): void;
|
|
120
|
-
Fragment(props: any, ...children: any[]): any[];
|
|
121
|
-
route: Router;
|
|
122
|
-
webComponent(name: string, componentClass: any, options?: CustomElementOptions): void;
|
|
123
|
-
safeHTML(html: string): any[];
|
|
124
|
-
use_render(render: any, mode?: 0 | 1): void;
|
|
125
|
-
use_react(React: any, ReactDOM: any): void;
|
|
126
|
-
version: string;
|
|
127
|
-
basePath?: string;
|
|
128
|
-
addComponents: (element: Element | string, components: ComponentRoute) => void;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Define what constitutes a mountable component
|
|
132
|
-
interface ComponentLike {
|
|
133
|
-
mount(element?: Element | string, options?: any): any;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Define component constructor type
|
|
137
|
-
type ComponentConstructor<T = unknown> = new (
|
|
138
|
-
state?: T,
|
|
139
|
-
view?: any,
|
|
140
|
-
update?: any,
|
|
141
|
-
options?: any
|
|
142
|
-
) => ComponentLike;
|
|
143
|
-
|
|
144
|
-
// Enhanced ComponentRoute type with clear distinctions
|
|
145
|
-
export type ComponentRoute = {
|
|
146
|
-
[route: string]:
|
|
147
|
-
| ComponentLike // Component instance
|
|
148
|
-
| ComponentConstructor // Component class constructor
|
|
149
|
-
| (() => ComponentLike | ComponentConstructor | Promise<ComponentLike | ComponentConstructor>) // Factory function
|
|
150
|
-
| ((...args: any[]) => any) // Event handler function
|
|
151
|
-
};
|
package/src/vdom-html.ts_
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { createElement, updateElement, Fragment } from './vdom-my';
|
|
2
|
-
import morphdom from 'morphdom';
|
|
3
|
-
|
|
4
|
-
export function render(element, html, parent?) {
|
|
5
|
-
if (!element) return;
|
|
6
|
-
if (typeof html === 'string') {
|
|
7
|
-
html = html.trim();
|
|
8
|
-
if (element.firstChild) {
|
|
9
|
-
const el = element.cloneNode(false);
|
|
10
|
-
el.innerHTML = html;
|
|
11
|
-
morphdom(element, el);
|
|
12
|
-
} else {
|
|
13
|
-
element.innerHTML = html;
|
|
14
|
-
}
|
|
15
|
-
} else {
|
|
16
|
-
updateElement(element, html, parent);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export { createElement, Fragment };
|
|
20
|
-
|
package/src/vdom-lit-html.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { createElement, updateElement, Fragment } from './vdom-my';
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
import app from './apprun';
|
|
8
|
-
|
|
9
|
-
function _render(element, vdom, parent?) {
|
|
10
|
-
if (!vdom) return;
|
|
11
|
-
if (typeof vdom === 'string') {
|
|
12
|
-
if (!element['_$litPart$']) element.replaceChildren();
|
|
13
|
-
render(html`${unsafeHTML(vdom)}`, element);
|
|
14
|
-
} else if (vdom['_$litType$']) {
|
|
15
|
-
if (!element['_$litPart$']) element.replaceChildren();
|
|
16
|
-
render(vdom, element);
|
|
17
|
-
} else {
|
|
18
|
-
updateElement(element, vdom, parent);
|
|
19
|
-
element['_$litPart$'] = undefined;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class RunDirective extends Directive {
|
|
24
|
-
// State stored in class field
|
|
25
|
-
value: number | undefined;
|
|
26
|
-
constructor(partInfo: PartInfo) {
|
|
27
|
-
super(partInfo);
|
|
28
|
-
// When necessary, validate part in constructor using `part.type`
|
|
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
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// Override update to perform any direct DOM manipulation
|
|
34
|
-
update(part: EventPart, props: Array<any>): DirectiveResult {
|
|
35
|
-
/* Any imperative updates to DOM/parts would go here */
|
|
36
|
-
const element = part.element;
|
|
37
|
-
const name = part.name;
|
|
38
|
-
const [eventOrFn, ...args] = props;
|
|
39
|
-
|
|
40
|
-
const getComponent = () => {
|
|
41
|
-
let el = element;
|
|
42
|
-
let component = el['_component'];
|
|
43
|
-
while (!component && el) {
|
|
44
|
-
el = el.parentElement;
|
|
45
|
-
component = el && el['_component'];
|
|
46
|
-
}
|
|
47
|
-
console.assert(!!component, 'Component not found.');
|
|
48
|
-
return component;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (typeof eventOrFn === 'string') {
|
|
52
|
-
element[`on${name}`] = e => {
|
|
53
|
-
const component = getComponent();
|
|
54
|
-
component ? component.run(eventOrFn, ...args, e) : app.run(eventOrFn, ...args, e)
|
|
55
|
-
}
|
|
56
|
-
} else if (typeof eventOrFn === 'function') {
|
|
57
|
-
element[`on${name}`] = e => getComponent().setState(eventOrFn(getComponent().state, ...args, e));
|
|
58
|
-
}
|
|
59
|
-
return this.render();
|
|
60
|
-
}
|
|
61
|
-
render() {
|
|
62
|
-
return noChange;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const run = directive(RunDirective) as any;
|
|
67
|
-
export { createElement, Fragment, html, svg, _render as render, run };
|