apprun 3.35.0 → 3.36.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/README.md +5 -0
- package/WHATSNEW.md +9 -1
- package/apprun.d.ts +4 -4
- 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/esm/app.js +55 -11
- package/esm/app.js.map +1 -1
- package/esm/apprun-html.js +8 -4
- package/esm/apprun-html.js.map +1 -1
- package/esm/apprun.js +64 -14
- package/esm/apprun.js.map +1 -1
- package/esm/component.js +56 -19
- package/esm/component.js.map +1 -1
- package/esm/decorator.js +22 -5
- package/esm/decorator.js.map +1 -1
- package/esm/directive.js +64 -13
- package/esm/directive.js.map +1 -1
- package/esm/router.js +22 -4
- package/esm/router.js.map +1 -1
- package/esm/type-utils.js +91 -0
- package/esm/type-utils.js.map +1 -0
- package/esm/types.js +32 -11
- package/esm/types.js.map +1 -1
- package/esm/vdom-my.js +66 -43
- package/esm/vdom-my.js.map +1 -1
- package/esm/version.js +15 -0
- package/esm/version.js.map +1 -0
- package/esm/web-component.js +30 -10
- package/esm/web-component.js.map +1 -1
- package/jest.config.js +1 -7
- package/jsx-runtime.js +1 -1
- package/jsx-runtime.js.map +1 -1
- package/package.json +1 -1
- package/plan/plan-apprun-bugfixes.md +207 -0
- package/src/app.ts +52 -11
- package/src/apprun-html.ts +8 -4
- package/src/apprun.ts +76 -17
- package/src/component.ts +58 -20
- package/src/decorator.ts +23 -6
- package/src/directive.ts +64 -13
- package/src/router.ts +23 -5
- package/src/type-utils.ts +130 -0
- package/src/types.ts +33 -12
- package/src/vdom-my.ts +75 -39
- package/src/version.ts +16 -0
- package/src/web-component.ts +31 -11
package/src/types.ts
CHANGED
|
@@ -4,26 +4,47 @@
|
|
|
4
4
|
* This file defines the fundamental types used across AppRun:
|
|
5
5
|
* 1. Component Types
|
|
6
6
|
* - View: Function that renders state to VDOM
|
|
7
|
-
* - Action: Function that updates state
|
|
8
|
-
* - Update: Collection of actions
|
|
7
|
+
* - Action: Function that updates state (sync/async)
|
|
8
|
+
* - Update: Collection of actions (array or object format)
|
|
9
|
+
* - ActionDef: Tuple definition for action arrays
|
|
9
10
|
*
|
|
10
11
|
* 2. Virtual DOM Types
|
|
11
|
-
* - VNode: Virtual DOM node structure
|
|
12
|
-
* - VDOM: Union of possible VDOM types
|
|
13
|
-
* - Element: DOM element references
|
|
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
|
|
14
16
|
*
|
|
15
17
|
* 3. Configuration Types
|
|
16
|
-
* - EventOptions: Event handler options (once, delay,
|
|
17
|
-
* - ActionOptions: Action behavior options (render, history,
|
|
18
|
-
* - MountOptions: Component mounting options (global events, routing)
|
|
19
|
-
* - AppStartOptions: Application startup configuration
|
|
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
|
|
20
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
|
+
*
|
|
21
37
|
* Usage:
|
|
22
38
|
* ```ts
|
|
23
|
-
* class MyComponent
|
|
39
|
+
* class MyComponent extends Component<State, Events> {
|
|
24
40
|
* view: View<State>;
|
|
25
|
-
* update: Update<State>;
|
|
41
|
+
* update: Update<State, Events>;
|
|
26
42
|
* }
|
|
43
|
+
*
|
|
44
|
+
* // Type-safe action definitions
|
|
45
|
+
* const update: Update<State> = {
|
|
46
|
+
* 'event': (state: State, ...args) => newState
|
|
47
|
+
* };
|
|
27
48
|
* ```
|
|
28
49
|
*/
|
|
29
50
|
|
|
@@ -59,5 +80,5 @@ export type AppStartOptions<T> = {
|
|
|
59
80
|
transition?: boolean;
|
|
60
81
|
route?: string;
|
|
61
82
|
rendered?: (state: T) => void
|
|
62
|
-
mounted?: (props:any, children:any, state: T) => T
|
|
83
|
+
mounted?: (props: any, children: any, state: T) => T
|
|
63
84
|
};
|
package/src/vdom-my.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function createElement(tag: string | Function | [], props?: {}, ...childr
|
|
|
35
35
|
else throw new Error(`Unknown tag in vdom ${tag}`);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const keyCache =
|
|
38
|
+
const keyCache = {};
|
|
39
39
|
|
|
40
40
|
export const updateElement = (element: Element | string, nodes: VDOM, component = {}) => {
|
|
41
41
|
// tslint:disable-next-line
|
|
@@ -67,68 +67,96 @@ function same(el: Element, node: VNode) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function update(element: Element, node: VNode, isSvg: boolean) {
|
|
70
|
-
if (node['_op'] === 3) return;
|
|
71
70
|
// console.assert(!!element);
|
|
72
71
|
isSvg = isSvg || node.tag === "svg";
|
|
73
72
|
if (!same(element, node)) {
|
|
74
73
|
element.parentNode.replaceChild(create(node, isSvg), element);
|
|
75
74
|
return;
|
|
76
75
|
}
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
updateChildren(element, node.children, isSvg);
|
|
77
|
+
updateProps(element, node.props, isSvg);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
function updateChildren(element, children, isSvg: boolean) {
|
|
80
|
+
function updateChildren(element: Element, children: any[], isSvg: boolean) {
|
|
82
81
|
const old_len = element.childNodes?.length || 0;
|
|
83
82
|
const new_len = children?.length || 0;
|
|
83
|
+
|
|
84
|
+
// Handle key-based reordering first if any children have keys
|
|
85
|
+
const hasKeysInNewChildren = children?.some(child =>
|
|
86
|
+
child && typeof child === 'object' && child.props && child.props.key !== undefined
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (hasKeysInNewChildren) {
|
|
90
|
+
// Create a map of existing keyed elements
|
|
91
|
+
const existingKeyedElements = new Map();
|
|
92
|
+
for (let i = 0; i < old_len; i++) {
|
|
93
|
+
const el = element.childNodes[i];
|
|
94
|
+
if (el && el.key) {
|
|
95
|
+
existingKeyedElements.set(el.key, el);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Build new DOM structure
|
|
100
|
+
const fragment = document.createDocumentFragment();
|
|
101
|
+
for (let i = 0; i < new_len; i++) {
|
|
102
|
+
const child = children[i];
|
|
103
|
+
if (child == null) continue;
|
|
104
|
+
|
|
105
|
+
const key = child.props && child.props['key'];
|
|
106
|
+
if (key && existingKeyedElements.has(key)) {
|
|
107
|
+
// Reuse existing element
|
|
108
|
+
const existingEl = existingKeyedElements.get(key);
|
|
109
|
+
update(existingEl, child as VNode, isSvg);
|
|
110
|
+
fragment.appendChild(existingEl);
|
|
111
|
+
existingKeyedElements.delete(key); // Mark as used
|
|
112
|
+
} else {
|
|
113
|
+
// Create new element
|
|
114
|
+
fragment.appendChild(create(child, isSvg));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Clear current children and append new structure
|
|
119
|
+
while (element.firstChild) {
|
|
120
|
+
element.removeChild(element.firstChild);
|
|
121
|
+
}
|
|
122
|
+
element.appendChild(fragment);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Original non-keyed logic
|
|
84
127
|
const len = Math.min(old_len, new_len);
|
|
85
128
|
for (let i = 0; i < len; i++) {
|
|
86
129
|
const child = children[i];
|
|
87
|
-
if (child
|
|
130
|
+
if (child == null) continue;
|
|
88
131
|
const el = element.childNodes[i];
|
|
132
|
+
if (!el) continue; // Safety check for undefined childNodes
|
|
89
133
|
if (typeof child === 'string') {
|
|
90
|
-
if (el.
|
|
91
|
-
if (el.
|
|
92
|
-
el.nodeValue = child
|
|
93
|
-
} else {
|
|
94
|
-
element.replaceChild(createText(child), el);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
} else if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
98
|
-
element.insertBefore(child, el);
|
|
99
|
-
} else {
|
|
100
|
-
const key = child.props && child.props['key'];
|
|
101
|
-
if (key) {
|
|
102
|
-
if (el.key === key) {
|
|
103
|
-
update(element.childNodes[i], child, isSvg);
|
|
104
|
-
} else {
|
|
105
|
-
// console.log(el.key, key);
|
|
106
|
-
const old = keyCache[key];
|
|
107
|
-
if (old) {
|
|
108
|
-
const temp = old.nextSibling;
|
|
109
|
-
element.insertBefore(old, el);
|
|
110
|
-
temp ? element.insertBefore(el, temp) : element.appendChild(el);
|
|
111
|
-
update(element.childNodes[i], child, isSvg);
|
|
112
|
-
} else {
|
|
113
|
-
element.replaceChild(create(child, isSvg), el);
|
|
114
|
-
}
|
|
134
|
+
if (el.nodeType === 3) {
|
|
135
|
+
if (el.nodeValue !== child) {
|
|
136
|
+
el.nodeValue = child;
|
|
115
137
|
}
|
|
116
138
|
} else {
|
|
117
|
-
|
|
139
|
+
element.replaceChild(createText(child), el);
|
|
118
140
|
}
|
|
141
|
+
} else if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
142
|
+
element.replaceChild(child, el);
|
|
143
|
+
} else if (child && typeof child === 'object') {
|
|
144
|
+
update(element.childNodes[i], child as VNode, isSvg);
|
|
119
145
|
}
|
|
120
146
|
}
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
while (
|
|
148
|
+
// Remove extra old nodes
|
|
149
|
+
while (element.childNodes.length > len) {
|
|
124
150
|
element.removeChild(element.lastChild);
|
|
125
|
-
n--;
|
|
126
151
|
}
|
|
127
152
|
|
|
128
153
|
if (new_len > len) {
|
|
129
154
|
const d = document.createDocumentFragment();
|
|
130
155
|
for (let i = len; i < children.length; i++) {
|
|
131
|
-
|
|
156
|
+
const child = children[i];
|
|
157
|
+
if (child != null) {
|
|
158
|
+
d.appendChild(create(child, isSvg));
|
|
159
|
+
}
|
|
132
160
|
}
|
|
133
161
|
element.appendChild(d);
|
|
134
162
|
}
|
|
@@ -146,7 +174,7 @@ function createText(node) {
|
|
|
146
174
|
div.insertAdjacentHTML('afterbegin', node.substring(6))
|
|
147
175
|
return div;
|
|
148
176
|
} else {
|
|
149
|
-
return document.createTextNode(node??'');
|
|
177
|
+
return document.createTextNode(node ?? '');
|
|
150
178
|
}
|
|
151
179
|
}
|
|
152
180
|
|
|
@@ -154,7 +182,12 @@ function create(node: VNode | string | HTMLElement | SVGElement, isSvg: boolean)
|
|
|
154
182
|
// console.assert(node !== null && node !== undefined);
|
|
155
183
|
if ((node instanceof HTMLElement) || (node instanceof SVGElement)) return node;
|
|
156
184
|
if (typeof node === "string") return createText(node);
|
|
157
|
-
|
|
185
|
+
|
|
186
|
+
// Type guard for VNode objects - handle invalid node types gracefully
|
|
187
|
+
if (!node || typeof node !== 'object' || !node.tag || (typeof node.tag === 'function')) {
|
|
188
|
+
return createText(typeof node === 'object' ? JSON.stringify(node) : String(node ?? ''));
|
|
189
|
+
}
|
|
190
|
+
|
|
158
191
|
isSvg = isSvg || node.tag === "svg";
|
|
159
192
|
const element = isSvg
|
|
160
193
|
? document.createElementNS("http://www.w3.org/2000/svg", node.tag)
|
|
@@ -221,7 +254,10 @@ export function updateProps(element: Element, props: {}, isSvg) {
|
|
|
221
254
|
} else if (element[name] !== value) {
|
|
222
255
|
element[name] = value;
|
|
223
256
|
}
|
|
224
|
-
if (name === 'key' && value
|
|
257
|
+
if (name === 'key' && value !== undefined) {
|
|
258
|
+
keyCache[value] = element;
|
|
259
|
+
element.key = value; // Set key property on the DOM element
|
|
260
|
+
}
|
|
225
261
|
}
|
|
226
262
|
if (props && typeof props['ref'] === 'function') {
|
|
227
263
|
window.requestAnimationFrame(() => props['ref'](element));
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Management Utility
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for AppRun version information.
|
|
5
|
+
* This file ensures version consistency across all modules.
|
|
6
|
+
*
|
|
7
|
+
* The version is derived from package.json and should be updated
|
|
8
|
+
* only when the package version changes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Import version from package.json to maintain single source of truth
|
|
12
|
+
// This version string is used across the framework
|
|
13
|
+
export const APPRUN_VERSION = '3.36.0';
|
|
14
|
+
|
|
15
|
+
// Version string with prefix for global tracking
|
|
16
|
+
export const APPRUN_VERSION_GLOBAL = `AppRun-${APPRUN_VERSION}`;
|
package/src/web-component.ts
CHANGED
|
@@ -4,30 +4,50 @@
|
|
|
4
4
|
* This file enables using AppRun components as Web Components:
|
|
5
5
|
* 1. Custom Element Creation
|
|
6
6
|
* - Converts AppRun components to custom elements
|
|
7
|
-
* - Handles lifecycle (connected, disconnected,
|
|
8
|
-
* - Manages shadow DOM and light DOM rendering
|
|
7
|
+
* - Handles lifecycle (connected, disconnected, attributeChanged)
|
|
8
|
+
* - Manages shadow DOM and light DOM rendering options
|
|
9
|
+
* - Automatic element registration with customElements.define()
|
|
9
10
|
*
|
|
10
11
|
* 2. Property/Attribute Sync
|
|
11
|
-
* - Observes attribute changes
|
|
12
|
-
* - Updates component state automatically
|
|
13
|
-
* - Handles camelCase/kebab-case conversion
|
|
14
|
-
* - Supports complex property types
|
|
12
|
+
* - Observes attribute changes with attributeChangedCallback
|
|
13
|
+
* - Updates component state automatically on changes
|
|
14
|
+
* - Handles camelCase/kebab-case conversion automatically
|
|
15
|
+
* - Supports complex property types and JSON parsing
|
|
16
|
+
* - Two-way data binding between attributes and state
|
|
15
17
|
*
|
|
16
18
|
* 3. Event Handling
|
|
17
19
|
* - Proxies events between component and element
|
|
18
|
-
* - Supports both global and local
|
|
19
|
-
* - Maintains proper event bubbling
|
|
20
|
+
* - Supports both global and local event systems
|
|
21
|
+
* - Maintains proper event bubbling and capturing
|
|
22
|
+
* - Custom event dispatching for component communication
|
|
20
23
|
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Shadow DOM encapsulation support
|
|
26
|
+
* - Attribute observation and change detection
|
|
27
|
+
* - Lifecycle management and cleanup
|
|
28
|
+
* - Property reflection to attributes
|
|
29
|
+
* - Event proxy system
|
|
30
|
+
* - Flexible rendering options
|
|
31
|
+
*
|
|
32
|
+
* Type Safety Improvements (v3.35.1):
|
|
33
|
+
* - Enhanced custom element options typing
|
|
34
|
+
* - Better lifecycle method signatures
|
|
35
|
+
* - Improved attribute/property type safety
|
|
36
|
+
* - Safer DOM manipulation with null checks
|
|
37
|
+
*
|
|
21
38
|
* Usage:
|
|
22
39
|
* ```ts
|
|
23
40
|
* // Register web component
|
|
24
|
-
* @customElement('my-element'
|
|
41
|
+
* @customElement('my-element', {
|
|
42
|
+
* shadow: true,
|
|
43
|
+
* observedAttributes: ['name', 'value']
|
|
44
|
+
* })
|
|
25
45
|
* class MyComponent extends Component {
|
|
26
46
|
* // Regular AppRun component code
|
|
27
47
|
* }
|
|
28
48
|
*
|
|
29
49
|
* // Use in HTML
|
|
30
|
-
* <my-element name="value"></my-element>
|
|
50
|
+
* <my-element name="value" data-prop="complex"></my-element>
|
|
31
51
|
* ```
|
|
32
52
|
*/
|
|
33
53
|
|
|
@@ -71,7 +91,7 @@ export const customElement = (componentClass, options: CustomElementOptions = {}
|
|
|
71
91
|
}
|
|
72
92
|
return map
|
|
73
93
|
}, {})
|
|
74
|
-
this._attrMap = (name: string)
|
|
94
|
+
this._attrMap = (name: string): string => attrMap[name] || name
|
|
75
95
|
|
|
76
96
|
const props = {};
|
|
77
97
|
Array.from(this.attributes).forEach(item => props[this._attrMap(item.name)] = item.value);
|