apprun 3.37.2 → 3.38.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apprun",
3
- "version": "3.37.2",
3
+ "version": "3.38.0",
4
4
  "description": "JavaScript library that has Elm inspired architecture, event pub-sub and components",
5
5
  "main": "dist/apprun.js",
6
6
  "module": "esm/apprun.js",
@@ -194,7 +194,7 @@ const setup_editor = (textarea, iframe, code, hide_src) => {
194
194
  }
195
195
  }
196
196
 
197
- class Play extends Component {
197
+ class Play extends Component<any> {
198
198
  view = (state) => {
199
199
  const code_id = state['code-element-id'];
200
200
  const element = this.element;
package/src/apprun.ts CHANGED
@@ -73,7 +73,7 @@ import { APPRUN_VERSION } from './version';
73
73
 
74
74
  export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
75
75
  type OnDecorator = {
76
- <T = any>(options?: any): (constructor: Function) => void;
76
+ <T = unknown>(options?: any): (constructor: Function) => void;
77
77
  <E = string>(events?: E, options?: any): (target: any, key: string) => void;
78
78
  };
79
79
 
@@ -109,7 +109,7 @@ if (!app.start) {
109
109
  app.webComponent = webComponent;
110
110
  app.safeHTML = safeHTML;
111
111
 
112
- app.start = <T, E = any>(element?: Element | string, state?: State<T>, view?: View<T>, update?: Update<T, E>,
112
+ app.start = <T, E = unknown>(element?: Element | string, state?: State<T>, view?: View<T>, update?: Update<T, E>,
113
113
  options?: AppStartOptions<T>): Component<T, E> => {
114
114
  const opts = { render: true, global_event: true, ...options };
115
115
  const component = new Component<T, E>(state, view, update);
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 = any, E = any> {
69
+ export class Component<T = unknown, E = unknown> {
70
70
  static __isAppRunComponent = true;
71
71
  private _app = new App();
72
72
  private _actions = [];
@@ -1,6 +1,6 @@
1
1
  import { produce, Draft } from 'immer';
2
2
 
3
- export function createState<T = any>(
3
+ export function createState<T = unknown>(
4
4
  fn: (draft: Draft<T>, ...args: any[]) => void
5
5
  ): (state: T, ...args: any[]) => T {
6
6
  return (state: T, ...args: any[]): T => {
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
- * // Two-way binding
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
- return (name ? component['state'][name] : component['state']) || '';
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 = any>(options?: any): (constructor: Function) => void;
11
+ <T = unknown>(options?: any): (constructor: Function) => void;
12
12
  };
13
13
  _React: any;
14
14
  React: App;
15
15
  on: {
16
- <T = any>(options?: any): (constructor: Function) => void;
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/types.ts CHANGED
@@ -66,7 +66,7 @@ export type VDOM = false | string | VNode | Array<VNode | string> | TemplateResu
66
66
  export type View<T> = (state: T) => VDOM | void;
67
67
  export type Action<T> = (state: T, ...p: any[]) => T | Promise<T> | void | AsyncGenerator<T> | Generator<T>;
68
68
  export type ActionDef<T, E> = (readonly [E, Action<T>, {}?]);
69
- export type Update<T, E = any> = ActionDef<T, E>[] | { [name: string]: Action<T> | {}[] } | (E | Action<T> | {})[];
69
+ export type Update<T, E = unknown> = ActionDef<T, E>[] | { [name: string]: Action<T> | {}[] } | (E | Action<T> | {})[];
70
70
  export type ActionOptions = {
71
71
  render?: boolean, history?, global?: boolean;
72
72
  callback?: (state: any) => void;
@@ -111,7 +111,7 @@ export interface IApp {
111
111
  /** @deprecated Use runAsync() instead */
112
112
  query(name: string, ...args: any[]): Promise<any[]>;
113
113
 
114
- start<T, E = any>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
114
+ start<T, E = unknown>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
115
115
  options?: AppStartOptions<T>): any;
116
116
 
117
117
  h(tag: string | Function, props?: any, ...children: any[]): VNode | VNode[];
@@ -134,7 +134,7 @@ interface ComponentLike {
134
134
  }
135
135
 
136
136
  // Define component constructor type
137
- type ComponentConstructor<T = any> = new (
137
+ type ComponentConstructor<T = unknown> = new (
138
138
  state?: T,
139
139
  view?: any,
140
140
  update?: any,
@@ -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: 2025-01-14 - Added skip logic for focus-sensitive, scroll, and media properties
6
+ * Updated: 2025-01-14 - Skip logic for focus-sensitive (selection), scroll, and media properties
7
7
  */
8
8
 
9
9
  import { find, html, svg } from 'property-information';
@@ -192,7 +192,7 @@ function setAttributeOrProperty(element: Element, name: string, value: any, isSv
192
192
  // Skip logic for preventing user interaction disruption during VDOM reconciliation
193
193
  function shouldSkipPatch(dom: HTMLElement, prop: string): boolean {
194
194
  if (document.activeElement === dom) {
195
- return ['value', 'selectionStart', 'selectionEnd', 'selectionDirection']
195
+ return ['selectionStart', 'selectionEnd', 'selectionDirection']
196
196
  .includes(prop);
197
197
  }
198
198
  if (prop === 'scrollTop' || prop === 'scrollLeft') {
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.37.2';
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}`;