apprun 3.37.3 → 3.38.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/.claude/settings.local.json +18 -0
- package/.docs/done/2026/07/13/explicit-pretty-links.md +32 -0
- package/.docs/done/2026/07/13/first-render-dom-attributes.md +18 -0
- package/.docs/plans/2026/07/13/plan-explicit-pretty-links.md +119 -0
- package/.docs/plans/2026/07/13/plan-first-render-dom-attributes.md +66 -0
- package/.docs/plans/2026/07/13/plan-play-typescript-runtime.md +77 -0
- package/.docs/plans/2026/07/13/plan-release-3-38-1-docs.md +74 -0
- package/.docs/reqs/2026/07/13/req-explicit-pretty-links.md +46 -0
- package/.docs/reqs/2026/07/13/req-first-render-dom-attributes.md +31 -0
- package/.docs/reqs/2026/07/13/req-play-typescript-runtime.md +29 -0
- package/.docs/reqs/2026/07/13/req-release-3-38-1-docs.md +34 -0
- package/.playwright-mcp/console-2026-06-24T18-37-47-881Z.log +17 -0
- package/CHANGELOG.md +14 -1
- package/README.md +3 -2
- package/WHATSNEW.md +32 -6
- package/ai/apprun.prompt.md +545 -109
- package/apprun.d.ts +21 -6
- package/cli/app.js +7 -0
- 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-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/dist/createState.js.map +1 -1
- package/esm/apprun-code.js +15 -3
- package/esm/apprun-code.js.map +1 -1
- package/esm/apprun-play.js +8 -2
- package/esm/apprun-play.js.map +1 -1
- package/esm/apprun.js +16 -8
- package/esm/apprun.js.map +1 -1
- package/esm/directive.js +129 -8
- package/esm/directive.js.map +1 -1
- package/esm/router.js +3 -2
- package/esm/router.js.map +1 -1
- package/esm/types.js +1 -0
- package/esm/types.js.map +1 -1
- package/esm/vdom-my-prop-attr.js +5 -11
- package/esm/vdom-my-prop-attr.js.map +1 -1
- package/esm/version.js +1 -1
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +1 -1
- package/src/apprun-code.tsx +15 -3
- package/src/apprun-play.tsx +9 -3
- package/src/apprun.ts +18 -10
- package/src/component.ts +1 -1
- package/src/createState.ts +1 -1
- package/src/directive.ts +132 -7
- package/src/global.d.ts +2 -2
- package/src/router.ts +3 -2
- package/src/types.ts +6 -3
- package/src/vdom-my-prop-attr.ts +5 -11
- package/src/version.ts +1 -1
- package/error.log +0 -0
package/package.json
CHANGED
package/src/apprun-code.tsx
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppRun playground custom element.
|
|
3
|
+
*
|
|
4
|
+
* Generates an isolated preview document, compiles JSX/TypeScript in the
|
|
5
|
+
* browser, and renders the result with AppRun. The TypeScript CDN dependency
|
|
6
|
+
* uses an explicit version and browser bundle path so the `ts` global is
|
|
7
|
+
* available before preview compilation begins.
|
|
8
|
+
*
|
|
9
|
+
* Recent Changes:
|
|
10
|
+
* - Executes compiled script-style examples synchronously before the preview
|
|
11
|
+
* document finishes DOMContentLoaded, preserving startup configuration.
|
|
12
|
+
*/
|
|
1
13
|
import { app, Component } from './apprun';
|
|
2
14
|
|
|
3
15
|
declare var CodeMirror;
|
|
@@ -45,7 +57,7 @@ const code_html = code => `<!DOCTYPE html>
|
|
|
45
57
|
margin: 2em;
|
|
46
58
|
}
|
|
47
59
|
</style>
|
|
48
|
-
<script src="https://cdn.jsdelivr.net/npm/typescript@
|
|
60
|
+
<script src="https://cdn.jsdelivr.net/npm/typescript@5.8.3/lib/typescript.js"></script>
|
|
49
61
|
<script src="dist/apprun-html.js"></script>
|
|
50
62
|
</head>
|
|
51
63
|
<body>
|
|
@@ -58,7 +70,7 @@ const compiled = ts.transpileModule(code, {
|
|
|
58
70
|
"jsxFactory": "app.h",
|
|
59
71
|
"jsxFragmentFactory": "app.Fragment",
|
|
60
72
|
"target": "es2020",
|
|
61
|
-
"module": "
|
|
73
|
+
"module": "none",
|
|
62
74
|
},
|
|
63
75
|
reportDiagnostics: true,
|
|
64
76
|
});
|
|
@@ -82,7 +94,7 @@ if (compiled.diagnostics && compiled.diagnostics.length) {
|
|
|
82
94
|
document.body.appendChild(pre);
|
|
83
95
|
};
|
|
84
96
|
const script = document.createElement('script');
|
|
85
|
-
script.type = '
|
|
97
|
+
script.type = 'text/javascript';
|
|
86
98
|
script.text = compiled.outputText;
|
|
87
99
|
document.body.appendChild(script);
|
|
88
100
|
}
|
package/src/apprun-play.tsx
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppRun documentation playground custom element.
|
|
3
|
+
*
|
|
4
|
+
* Compiles browser-global TypeScript/JSX examples into synchronous scripts so
|
|
5
|
+
* startup configuration runs before the preview document's DOMContentLoaded.
|
|
6
|
+
*/
|
|
1
7
|
import { app, Component } from './apprun';
|
|
2
8
|
|
|
3
9
|
const popup_div = `<div id="play-popup" class="overlay">
|
|
@@ -126,7 +132,7 @@ const compiled = ts.transpileModule(code, {
|
|
|
126
132
|
"jsxFactory": "app.h",
|
|
127
133
|
"jsxFragmentFactory": "app.Fragment",
|
|
128
134
|
"target": "es2020",
|
|
129
|
-
"module": "
|
|
135
|
+
"module": "none",
|
|
130
136
|
},
|
|
131
137
|
reportDiagnostics: true,
|
|
132
138
|
});
|
|
@@ -150,7 +156,7 @@ if (compiled.diagnostics && compiled.diagnostics.length) {
|
|
|
150
156
|
document.body.appendChild(pre);
|
|
151
157
|
};
|
|
152
158
|
const script = document.createElement('script');
|
|
153
|
-
script.type = '
|
|
159
|
+
script.type = 'text/javascript';
|
|
154
160
|
script.text = compiled.outputText;
|
|
155
161
|
document.body.appendChild(script);
|
|
156
162
|
}
|
|
@@ -194,7 +200,7 @@ const setup_editor = (textarea, iframe, code, hide_src) => {
|
|
|
194
200
|
}
|
|
195
201
|
}
|
|
196
202
|
|
|
197
|
-
class Play extends Component {
|
|
203
|
+
class Play extends Component<any> {
|
|
198
204
|
view = (state) => {
|
|
199
205
|
const code_id = state['code-element-id'];
|
|
200
206
|
const element = this.element;
|
package/src/apprun.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* - Virtual DOM rendering with multiple renderer support
|
|
25
25
|
* - Component lifecycle management
|
|
26
26
|
* - Client-side routing with hash/path support
|
|
27
|
+
* - Explicit pretty-link path routing through app.use_prettyLink()
|
|
27
28
|
* - Web Components integration
|
|
28
29
|
* - React compatibility layer
|
|
29
30
|
* - TypeScript support with strong typing
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
* - Safer element access with proper type assertions
|
|
38
39
|
*
|
|
39
40
|
* Recent Changes:
|
|
41
|
+
* - Restored hash routing and normal browser navigation as the default; pretty-link interception is opt-in
|
|
40
42
|
* - Modified addComponents to accept (element, components) where components is a key-value object with routes as keys and components as values
|
|
41
43
|
* - Simplified component mounting API for better usability
|
|
42
44
|
*
|
|
@@ -54,6 +56,8 @@
|
|
|
54
56
|
* }
|
|
55
57
|
*
|
|
56
58
|
* // Mount multiple components
|
|
59
|
+
* // Required only when browser /path links should use SPA navigation.
|
|
60
|
+
* app.use_prettyLink();
|
|
57
61
|
* app.addComponents(document.body, {
|
|
58
62
|
* '/home': MyComponent,
|
|
59
63
|
* '/about': AnotherComponent
|
|
@@ -73,7 +77,7 @@ import { APPRUN_VERSION } from './version';
|
|
|
73
77
|
|
|
74
78
|
export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
|
|
75
79
|
type OnDecorator = {
|
|
76
|
-
<T =
|
|
80
|
+
<T = unknown>(options?: any): (constructor: Function) => void;
|
|
77
81
|
<E = string>(events?: E, options?: any): (target: any, key: string) => void;
|
|
78
82
|
};
|
|
79
83
|
|
|
@@ -109,7 +113,7 @@ if (!app.start) {
|
|
|
109
113
|
app.webComponent = webComponent;
|
|
110
114
|
app.safeHTML = safeHTML;
|
|
111
115
|
|
|
112
|
-
app.start = <T, E =
|
|
116
|
+
app.start = <T, E = unknown>(element?: Element | string, state?: State<T>, view?: View<T>, update?: Update<T, E>,
|
|
113
117
|
options?: AppStartOptions<T>): Component<T, E> => {
|
|
114
118
|
const opts = { render: true, global_event: true, ...options };
|
|
115
119
|
const component = new Component<T, E>(state, view, update);
|
|
@@ -124,24 +128,25 @@ if (!app.start) {
|
|
|
124
128
|
|
|
125
129
|
const NOOP = _ => {/* Intentionally empty */ }
|
|
126
130
|
app.on('/', NOOP);
|
|
131
|
+
app.on('#', NOOP);
|
|
127
132
|
app.on('debug', _ => NOOP);
|
|
128
133
|
app.on(ROUTER_EVENT, NOOP);
|
|
129
134
|
app.on(ROUTER_404_EVENT, NOOP);
|
|
130
135
|
app.route = route;
|
|
131
136
|
app.on('route', url => app['route'] && app['route'](url));
|
|
132
137
|
|
|
138
|
+
let prettyLinks = false;
|
|
139
|
+
/** Select SPA path-link routing before DOMContentLoaded; hash routing is the default. */
|
|
140
|
+
app.use_prettyLink = (enabled = true) => {
|
|
141
|
+
prettyLinks = enabled;
|
|
142
|
+
};
|
|
143
|
+
|
|
133
144
|
if (typeof document === 'object') {
|
|
134
145
|
document.addEventListener("DOMContentLoaded", () => {
|
|
135
146
|
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
147
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
window.addEventListener('popstate', () => route(location.pathname));
|
|
141
|
-
|
|
142
|
-
if (use_hash) {
|
|
143
|
-
!no_init_route && route(location.hash);
|
|
144
|
-
} else {
|
|
148
|
+
if (prettyLinks) {
|
|
149
|
+
window.addEventListener('popstate', () => route(location.pathname));
|
|
145
150
|
!no_init_route && (() => {
|
|
146
151
|
const basePath = app.basePath || '';
|
|
147
152
|
let initialPath = location.pathname;
|
|
@@ -172,6 +177,9 @@ if (!app.start) {
|
|
|
172
177
|
route(menu.pathname); // Route with relative path (without base path)
|
|
173
178
|
}
|
|
174
179
|
});
|
|
180
|
+
} else {
|
|
181
|
+
window.addEventListener('hashchange', () => route(location.hash));
|
|
182
|
+
!no_init_route && route(location.hash);
|
|
175
183
|
}
|
|
176
184
|
});
|
|
177
185
|
}
|
package/src/component.ts
CHANGED
|
@@ -66,7 +66,7 @@ export const REFRESH = state => state;
|
|
|
66
66
|
|
|
67
67
|
const app = _app as unknown as IApp;
|
|
68
68
|
|
|
69
|
-
export class Component<T =
|
|
69
|
+
export class Component<T = unknown, E = unknown> {
|
|
70
70
|
static __isAppRunComponent = true;
|
|
71
71
|
private _app = new App();
|
|
72
72
|
private _actions = [];
|
package/src/createState.ts
CHANGED
package/src/directive.ts
CHANGED
|
@@ -34,16 +34,36 @@
|
|
|
34
34
|
* - Enhanced error handling for invalid event targets
|
|
35
35
|
* - Safer DOM element property access
|
|
36
36
|
*
|
|
37
|
+
* Nested State Binding Support (v3.37.4):
|
|
38
|
+
* - Enhanced $bind directive to support nested object and array paths
|
|
39
|
+
* - Supports dot notation: 'user.name', 'user.profile.settings.theme'
|
|
40
|
+
* - Supports bracket notation: 'items[0]', 'users[1].name'
|
|
41
|
+
* - Supports mixed notation: 'users[0].settings.theme', 'data["key"].value'
|
|
42
|
+
* - Safe traversal with automatic intermediate object/array creation
|
|
43
|
+
* - Maintains backward compatibility with simple property binding
|
|
44
|
+
*
|
|
37
45
|
* Usage:
|
|
38
46
|
* ```tsx
|
|
39
47
|
* // Event binding
|
|
40
48
|
* <button $onclick="event-name">Click</button>
|
|
41
49
|
* <input $oninput={e => setState(e.target.value)} />
|
|
42
50
|
*
|
|
43
|
-
* //
|
|
51
|
+
* // Simple two-way binding
|
|
44
52
|
* <input $bind="state.property" />
|
|
45
53
|
* <select $bind="selected">...</select>
|
|
46
54
|
*
|
|
55
|
+
* // Nested object binding
|
|
56
|
+
* <input $bind="user.profile.name" />
|
|
57
|
+
* <input $bind="user.settings.theme" />
|
|
58
|
+
*
|
|
59
|
+
* // Array element binding
|
|
60
|
+
* <input $bind="items[0]" />
|
|
61
|
+
* <input $bind="todos[1].title" />
|
|
62
|
+
*
|
|
63
|
+
* // Mixed nested binding
|
|
64
|
+
* <input $bind="users[0].profile.settings.notifications.email" />
|
|
65
|
+
* <select $bind="config.display.mode">...</select>
|
|
66
|
+
*
|
|
47
67
|
* // Array handlers
|
|
48
68
|
* <button $onclick={['handler', param1, param2]}>Click</button>
|
|
49
69
|
* ```
|
|
@@ -52,18 +72,123 @@
|
|
|
52
72
|
import app from './app';
|
|
53
73
|
import { safeEventTarget } from './type-utils';
|
|
54
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Parse a path string into an array of keys and indices
|
|
77
|
+
* Supports paths like: 'a.b', 'a[0]', 'a[0].b', 'a["key"]'
|
|
78
|
+
*/
|
|
79
|
+
const parsePath = (path: string): Array<string | number> => {
|
|
80
|
+
if (!path) return [];
|
|
81
|
+
|
|
82
|
+
const keys: Array<string | number> = [];
|
|
83
|
+
let current = '';
|
|
84
|
+
let inBracket = false;
|
|
85
|
+
let quoteChar = '';
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < path.length; i++) {
|
|
88
|
+
const char = path[i];
|
|
89
|
+
|
|
90
|
+
if (char === '[' && !inBracket) {
|
|
91
|
+
if (current) {
|
|
92
|
+
keys.push(current);
|
|
93
|
+
current = '';
|
|
94
|
+
}
|
|
95
|
+
inBracket = true;
|
|
96
|
+
} else if (char === ']' && inBracket) {
|
|
97
|
+
if (quoteChar) {
|
|
98
|
+
// Remove quotes from string keys
|
|
99
|
+
current = current.slice(1, -1);
|
|
100
|
+
} else if (/^\d+$/.test(current)) {
|
|
101
|
+
// Convert numeric strings to numbers
|
|
102
|
+
current = parseInt(current, 10) as any;
|
|
103
|
+
}
|
|
104
|
+
keys.push(current);
|
|
105
|
+
current = '';
|
|
106
|
+
inBracket = false;
|
|
107
|
+
quoteChar = '';
|
|
108
|
+
} else if ((char === '"' || char === "'") && inBracket) {
|
|
109
|
+
if (!quoteChar) {
|
|
110
|
+
quoteChar = char;
|
|
111
|
+
} else if (char === quoteChar) {
|
|
112
|
+
quoteChar = '';
|
|
113
|
+
}
|
|
114
|
+
current += char;
|
|
115
|
+
} else if (char === '.' && !inBracket) {
|
|
116
|
+
if (current) {
|
|
117
|
+
keys.push(current);
|
|
118
|
+
current = '';
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
current += char;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (current) {
|
|
126
|
+
keys.push(current);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return keys;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Safely get a nested value from an object using a path
|
|
134
|
+
*/
|
|
135
|
+
const getNestedValue = (obj: any, path: Array<string | number>): any => {
|
|
136
|
+
let current = obj;
|
|
137
|
+
for (const key of path) {
|
|
138
|
+
if (current == null) return undefined;
|
|
139
|
+
current = current[key];
|
|
140
|
+
}
|
|
141
|
+
return current;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Safely set a nested value in an object using a path
|
|
146
|
+
* Creates intermediate objects/arrays as needed
|
|
147
|
+
*/
|
|
148
|
+
const setNestedValue = (obj: any, path: Array<string | number>, value: any): any => {
|
|
149
|
+
if (path.length === 0) return value;
|
|
150
|
+
|
|
151
|
+
const result = { ...obj };
|
|
152
|
+
let current = result;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
155
|
+
const key = path[i];
|
|
156
|
+
const nextKey = path[i + 1];
|
|
157
|
+
|
|
158
|
+
if (current[key] == null) {
|
|
159
|
+
// Create array if next key is numeric, object otherwise
|
|
160
|
+
current[key] = typeof nextKey === 'number' ? [] : {};
|
|
161
|
+
} else if (Array.isArray(current[key])) {
|
|
162
|
+
current[key] = [...current[key]];
|
|
163
|
+
} else if (typeof current[key] === 'object') {
|
|
164
|
+
current[key] = { ...current[key] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
current = current[key];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
current[path[path.length - 1]] = value;
|
|
171
|
+
return result;
|
|
172
|
+
};
|
|
173
|
+
|
|
55
174
|
const getStateValue = (component, name) => {
|
|
56
|
-
|
|
175
|
+
if (!name) return component['state'] || '';
|
|
176
|
+
|
|
177
|
+
const path = parsePath(name);
|
|
178
|
+
const value = getNestedValue(component['state'], path);
|
|
179
|
+
return value !== undefined ? value : '';
|
|
57
180
|
}
|
|
58
181
|
|
|
59
182
|
const setStateValue = (component, name, value) => {
|
|
60
|
-
if (name) {
|
|
61
|
-
const state = component['state'] || {};
|
|
62
|
-
state[name] = value;
|
|
63
|
-
component.setState(state);
|
|
64
|
-
} else {
|
|
183
|
+
if (!name) {
|
|
65
184
|
component.setState(value);
|
|
185
|
+
return;
|
|
66
186
|
}
|
|
187
|
+
|
|
188
|
+
const path = parsePath(name);
|
|
189
|
+
const currentState = component['state'] || {};
|
|
190
|
+
const newState = setNestedValue(currentState, path, value);
|
|
191
|
+
component.setState(newState);
|
|
67
192
|
}
|
|
68
193
|
|
|
69
194
|
const apply_directive = (key: string, props: {}, tag, component) => {
|
package/src/global.d.ts
CHANGED
|
@@ -8,12 +8,12 @@ declare global {
|
|
|
8
8
|
app: App;
|
|
9
9
|
_AppRunVersions: string;
|
|
10
10
|
Component: typeof Component & {
|
|
11
|
-
<T =
|
|
11
|
+
<T = unknown>(options?: any): (constructor: Function) => void;
|
|
12
12
|
};
|
|
13
13
|
_React: any;
|
|
14
14
|
React: App;
|
|
15
15
|
on: {
|
|
16
|
-
<T =
|
|
16
|
+
<T = unknown>(options?: any): (constructor: Function) => void;
|
|
17
17
|
<E = string>(events?: E, options?: any): (target: any, key: string) => void;
|
|
18
18
|
};
|
|
19
19
|
customElement: (name: string) => (constructor: Function) => void;
|
package/src/router.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 2. Path-based routing (/path)
|
|
10
10
|
* - Works with browser history API
|
|
11
11
|
* - Requires server configuration for SPA routing
|
|
12
|
-
* - Handles popstate
|
|
12
|
+
* - Handles browser links and popstate after app.use_prettyLink() opts in before initialization
|
|
13
13
|
* 3. Event-based navigation
|
|
14
14
|
* - Routes trigger corresponding component events
|
|
15
15
|
* - Automatic route parameter extraction
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
*
|
|
39
39
|
* // Navigate programmatically
|
|
40
40
|
* app.run('route', '#/home');
|
|
41
|
-
* route('/users/123'); // Direct routing
|
|
41
|
+
* route('/users/123'); // Direct routing does not require pretty-link mode
|
|
42
42
|
*
|
|
43
43
|
* // Hierarchical matching examples
|
|
44
44
|
* app.on('/api', (operation, id) => // Handle /api/users/123);
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* // If handler found at /api, it receives: ('v1', 'users', '123')
|
|
51
51
|
*
|
|
52
52
|
* // Base Path Support (NEW):
|
|
53
|
+
* app.use_prettyLink(); // Enable automatic browser path navigation before initialization
|
|
53
54
|
* app.basePath = '/myapp'; // For sub-directory deployments
|
|
54
55
|
* // Links: <a href="/users/123"> (relative paths)
|
|
55
56
|
* // Navigation: /myapp/users/123 (full path)
|
package/src/types.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - ActionOptions: Action behavior options (render, history, global, callback)
|
|
20
20
|
* - MountOptions: Component mounting options (global events, routing, transitions)
|
|
21
21
|
* - AppStartOptions: Application startup configuration with lifecycle hooks
|
|
22
|
+
* - IApp.use_prettyLink: Explicit startup selection for path-link routing
|
|
22
23
|
*
|
|
23
24
|
* Features:
|
|
24
25
|
* - Strong typing for component lifecycle
|
|
@@ -66,7 +67,7 @@ export type VDOM = false | string | VNode | Array<VNode | string> | TemplateResu
|
|
|
66
67
|
export type View<T> = (state: T) => VDOM | void;
|
|
67
68
|
export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void | AsyncGenerator<T> | Generator<T>;
|
|
68
69
|
export type ActionDef<T, E> = (readonly [E, Action<T>, {}?]);
|
|
69
|
-
export type Update<T, E =
|
|
70
|
+
export type Update<T, E = unknown> = ActionDef<T, E>[] | { [name: string]: Action<T> | {}[] } | (E | Action<T> | {})[];
|
|
70
71
|
export type ActionOptions = {
|
|
71
72
|
render?: boolean, history?, global?: boolean;
|
|
72
73
|
callback?: (state: any) => void;
|
|
@@ -111,7 +112,7 @@ export interface IApp {
|
|
|
111
112
|
/** @deprecated Use runAsync() instead */
|
|
112
113
|
query(name: string, ...args: any[]): Promise<any[]>;
|
|
113
114
|
|
|
114
|
-
start<T, E =
|
|
115
|
+
start<T, E = unknown>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
|
|
115
116
|
options?: AppStartOptions<T>): any;
|
|
116
117
|
|
|
117
118
|
h(tag: string | Function, props?: any, ...children: any[]): VNode | VNode[];
|
|
@@ -123,6 +124,8 @@ export interface IApp {
|
|
|
123
124
|
safeHTML(html: string): any[];
|
|
124
125
|
use_render(render: any, mode?: 0 | 1): void;
|
|
125
126
|
use_react(React: any, ReactDOM: any): void;
|
|
127
|
+
/** Select SPA path-link routing before DOMContentLoaded; hash routing is the default. */
|
|
128
|
+
use_prettyLink(enabled?: boolean): void;
|
|
126
129
|
version: string;
|
|
127
130
|
basePath?: string;
|
|
128
131
|
addComponents: (element: Element | string, components: ComponentRoute) => void;
|
|
@@ -134,7 +137,7 @@ interface ComponentLike {
|
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
// Define component constructor type
|
|
137
|
-
type ComponentConstructor<T =
|
|
140
|
+
type ComponentConstructor<T = unknown> = new (
|
|
138
141
|
state?: T,
|
|
139
142
|
view?: any,
|
|
140
143
|
update?: any,
|
package/src/vdom-my-prop-attr.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Standards-compliant property/attribute handling with caching and special element support
|
|
4
4
|
* Features: Skip logic for preserving user interactions during VDOM reconciliation
|
|
5
5
|
* Exports: updateProps - Main function for DOM element property updates
|
|
6
|
-
* Updated:
|
|
6
|
+
* Updated: 2026-07-13 - Restore stale class cleanup when first-render VDOM props are null
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { find, html, svg } from 'property-information';
|
|
@@ -13,17 +13,11 @@ const propertyInfoCache = new Map<string, any>();
|
|
|
13
13
|
|
|
14
14
|
// Merge old and new props, handling className -> class conversion and null cleanup
|
|
15
15
|
function mergeProps(oldProps: { [key: string]: any }, newProps: { [key: string]: any }): { [key: string]: any } {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
newProps = newProps || {};
|
|
17
|
+
newProps['class'] = newProps['class'] || newProps['className'];
|
|
18
|
+
delete newProps['className'];
|
|
20
19
|
|
|
21
|
-
if (!oldProps || Object.keys(oldProps).length === 0) return newProps
|
|
22
|
-
if (!newProps || Object.keys(newProps).length === 0) {
|
|
23
|
-
const props: { [key: string]: any } = {};
|
|
24
|
-
Object.keys(oldProps).forEach(p => props[p] = null);
|
|
25
|
-
return props;
|
|
26
|
-
}
|
|
20
|
+
if (!oldProps || Object.keys(oldProps).length === 0) return newProps;
|
|
27
21
|
|
|
28
22
|
const props: { [key: string]: any } = {};
|
|
29
23
|
Object.keys(oldProps).forEach(p => {
|
package/src/version.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
// Import version from package.json to maintain single source of truth
|
|
12
12
|
// This version string is used across the framework
|
|
13
|
-
export const APPRUN_VERSION = '3.
|
|
13
|
+
export const APPRUN_VERSION = '3.38.0';
|
|
14
14
|
|
|
15
15
|
// Version string with prefix for global tracking
|
|
16
16
|
export const APPRUN_VERSION_GLOBAL = `AppRun-${APPRUN_VERSION}`;
|
package/error.log
DELETED
|
File without changes
|