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.
Files changed (58) hide show
  1. package/README.md +5 -0
  2. package/WHATSNEW.md +9 -1
  3. package/apprun.d.ts +4 -4
  4. package/dist/apprun-dev-tools.js +1 -1
  5. package/dist/apprun-dev-tools.js.map +1 -1
  6. package/dist/apprun-html.esm.js +7 -7
  7. package/dist/apprun-html.esm.js.map +1 -1
  8. package/dist/apprun-html.js +1 -1
  9. package/dist/apprun-html.js.map +1 -1
  10. package/dist/apprun-play-html.esm.js +7 -7
  11. package/dist/apprun-play-html.esm.js.map +1 -1
  12. package/dist/apprun-play.js +1 -1
  13. package/dist/apprun-play.js.map +1 -1
  14. package/dist/apprun.esm.js +1 -1
  15. package/dist/apprun.esm.js.map +1 -1
  16. package/dist/apprun.js +1 -1
  17. package/dist/apprun.js.map +1 -1
  18. package/esm/app.js +55 -11
  19. package/esm/app.js.map +1 -1
  20. package/esm/apprun-html.js +8 -4
  21. package/esm/apprun-html.js.map +1 -1
  22. package/esm/apprun.js +64 -14
  23. package/esm/apprun.js.map +1 -1
  24. package/esm/component.js +56 -19
  25. package/esm/component.js.map +1 -1
  26. package/esm/decorator.js +22 -5
  27. package/esm/decorator.js.map +1 -1
  28. package/esm/directive.js +64 -13
  29. package/esm/directive.js.map +1 -1
  30. package/esm/router.js +22 -4
  31. package/esm/router.js.map +1 -1
  32. package/esm/type-utils.js +91 -0
  33. package/esm/type-utils.js.map +1 -0
  34. package/esm/types.js +32 -11
  35. package/esm/types.js.map +1 -1
  36. package/esm/vdom-my.js +66 -43
  37. package/esm/vdom-my.js.map +1 -1
  38. package/esm/version.js +15 -0
  39. package/esm/version.js.map +1 -0
  40. package/esm/web-component.js +30 -10
  41. package/esm/web-component.js.map +1 -1
  42. package/jest.config.js +1 -7
  43. package/jsx-runtime.js +1 -1
  44. package/jsx-runtime.js.map +1 -1
  45. package/package.json +1 -1
  46. package/plan/plan-apprun-bugfixes.md +207 -0
  47. package/src/app.ts +52 -11
  48. package/src/apprun-html.ts +8 -4
  49. package/src/apprun.ts +76 -17
  50. package/src/component.ts +58 -20
  51. package/src/decorator.ts +23 -6
  52. package/src/directive.ts +64 -13
  53. package/src/router.ts +23 -5
  54. package/src/type-utils.ts +130 -0
  55. package/src/types.ts +33 -12
  56. package/src/vdom-my.ts +75 -39
  57. package/src/version.ts +16 -0
  58. 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, etc)
17
- * - ActionOptions: Action behavior options (render, history, etc)
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 implements Component<State> {
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 = new WeakMap();
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
- !(node['_op'] & 2) && updateChildren(element, node.children, isSvg);
78
- !(node['_op'] & 1) && updateProps(element, node.props, isSvg);
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['_op'] === 3) continue;
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.textContent !== child) {
91
- if (el.nodeType === 3) {
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
- update(element.childNodes[i], child, isSvg);
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
- let n = element.childNodes?.length || 0;
123
- while (n > len) {
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
- d.appendChild(create(children[i], isSvg));
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
- if (!node.tag || (typeof node.tag === 'function')) return createText(JSON.stringify(node));
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) keyCache[value] = element;
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}`;
@@ -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, etc)
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 events
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) : string => attrMap[name] || name
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);