apprun 3.36.0 → 3.37.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 (93) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +143 -25
  3. package/WHATSNEW.md +29 -12
  4. package/apprun-book.jpg +0 -0
  5. package/apprun.d.ts +136 -46
  6. package/cli/app.js +29 -0
  7. package/cli/index.html +13 -0
  8. package/{apprun-cli.js → cli/index.js} +8 -14
  9. package/dist/apprun-code.js +2 -0
  10. package/dist/apprun-code.js.map +1 -0
  11. package/dist/apprun-dev-tools.js +1 -2
  12. package/dist/apprun-dev-tools.js.map +1 -1
  13. package/dist/apprun-html.esm.js +7 -7
  14. package/dist/apprun-html.esm.js.map +1 -1
  15. package/dist/apprun-html.js +1 -1
  16. package/dist/apprun-html.js.map +1 -1
  17. package/dist/apprun-play-html.esm.js +7 -7
  18. package/dist/apprun-play-html.esm.js.map +1 -1
  19. package/dist/apprun-play.js +1 -1
  20. package/dist/apprun-play.js.map +1 -1
  21. package/dist/apprun.esm.js +1 -1
  22. package/dist/apprun.esm.js.map +1 -1
  23. package/dist/apprun.js +1 -1
  24. package/dist/apprun.js.map +1 -1
  25. package/dist/createState.js +2 -0
  26. package/dist/createState.js.map +1 -0
  27. package/esm/add-components.js +90 -0
  28. package/esm/add-components.js.map +1 -0
  29. package/esm/app.js +15 -8
  30. package/esm/app.js.map +1 -1
  31. package/esm/apprun-code.js +51 -16
  32. package/esm/apprun-code.js.map +1 -1
  33. package/esm/apprun-dev-tools.js +32 -32
  34. package/esm/apprun-dev-tools.js.map +1 -1
  35. package/esm/apprun-play.js +50 -17
  36. package/esm/apprun-play.js.map +1 -1
  37. package/esm/apprun.js +42 -11
  38. package/esm/apprun.js.map +1 -1
  39. package/esm/component.js +17 -20
  40. package/esm/component.js.map +1 -1
  41. package/esm/createState.js +9 -0
  42. package/esm/createState.js.map +1 -0
  43. package/esm/decorator.js.map +1 -1
  44. package/esm/directive.js +1 -1
  45. package/esm/directive.js.map +1 -1
  46. package/esm/router.js +241 -18
  47. package/esm/router.js.map +1 -1
  48. package/esm/shadow.js +1 -1
  49. package/esm/shadow.js.map +1 -1
  50. package/esm/type-utils.js +2 -3
  51. package/esm/type-utils.js.map +1 -1
  52. package/esm/vdom-my-new.js +327 -0
  53. package/esm/vdom-my-new.js.map +1 -0
  54. package/esm/vdom-my-prop-attr.js +227 -0
  55. package/esm/vdom-my-prop-attr.js.map +1 -0
  56. package/esm/vdom-my.js +114 -150
  57. package/esm/vdom-my.js.map +1 -1
  58. package/esm/vdom-patch.js +1 -1
  59. package/esm/vdom-patch.js.map +1 -1
  60. package/esm/version.js +1 -1
  61. package/esm/web-component.js +3 -4
  62. package/esm/web-component.js.map +1 -1
  63. package/index.html +5 -2
  64. package/jest.config.js +2 -2
  65. package/jest.setup.js +29 -3
  66. package/jsx-runtime.js +1 -1
  67. package/jsx-runtime.js.map +1 -1
  68. package/package.json +14 -14
  69. package/src/add-components.ts +103 -0
  70. package/src/app.ts +10 -19
  71. package/src/apprun-code.tsx +48 -10
  72. package/src/apprun-dev-tools.tsx +23 -27
  73. package/src/apprun-play.tsx +46 -9
  74. package/src/apprun.ts +49 -40
  75. package/src/component.ts +15 -8
  76. package/src/createState.ts +11 -0
  77. package/src/decorator.ts +2 -1
  78. package/src/router.ts +261 -17
  79. package/src/shadow.tsx +1 -1
  80. package/src/tsconfig.json +2 -2
  81. package/src/types.ts +62 -2
  82. package/src/vdom-my-new.ts +311 -0
  83. package/src/vdom-my-prop-attr.ts +241 -0
  84. package/src/vdom-my.ts +109 -134
  85. package/src/version.ts +1 -1
  86. package/src/web-component.ts +4 -10
  87. package/tsconfig.jest.json +15 -6
  88. package/tsconfig.json +3 -3
  89. package/webpack.config.cjs +3 -1
  90. package/cli/export.js +0 -92
  91. package/cli/import.js +0 -68
  92. package/plan/plan-apprun-bugfixes.md +0 -207
  93. package/src/types/apprun.d.ts +0 -56
@@ -0,0 +1,103 @@
1
+ import { ComponentRoute, IApp } from './types';
2
+ import app from './app'; // ADD: Global app instance access
3
+
4
+ // Type guard functions using the enhanced type system
5
+ function isComponentInstance(obj: any): boolean {
6
+ return obj && typeof obj === 'object' && typeof obj.mount === 'function';
7
+ }
8
+
9
+ function isComponentConstructor(fn: any): boolean {
10
+ return typeof fn === 'function' &&
11
+ fn.prototype &&
12
+ fn.prototype.constructor === fn &&
13
+ (fn.prototype.mount !== undefined ||
14
+ fn.prototype.state !== undefined ||
15
+ fn.prototype.view !== undefined);
16
+ }
17
+
18
+ function isFactoryFunction(fn: any): boolean {
19
+ return typeof fn === 'function' && !isComponentConstructor(fn);
20
+ }
21
+
22
+ // Recursive function resolution with enhanced type checking
23
+ async function resolveComponent(component: any, maxDepth = 3): Promise<any> {
24
+ let resolved = component;
25
+ let depth = 0;
26
+
27
+ while (isFactoryFunction(resolved) && depth < maxDepth) {
28
+ try {
29
+ const result = await resolved();
30
+ if (result === resolved) break; // Prevent infinite loops
31
+ resolved = result;
32
+ depth++;
33
+ } catch (error) {
34
+ console.error(`Error executing component function: ${error}`);
35
+ break;
36
+ }
37
+ }
38
+
39
+ return resolved;
40
+ }
41
+
42
+ export default async (element: HTMLElement | string, components: ComponentRoute) => {
43
+ for (const [route, component] of Object.entries(components)) {
44
+ if (!component || !route) {
45
+ console.error(`Invalid component configuration: component=${component}, route=${route}`);
46
+ continue;
47
+ }
48
+
49
+ // Check if it's a direct component instance
50
+ if (isComponentInstance(component)) {
51
+ const options = { route };
52
+ (component as any).mount(element, options);
53
+ continue;
54
+ }
55
+
56
+ // Check if it's a component class constructor
57
+ if (isComponentConstructor(component)) {
58
+ const instance = new (component as any)();
59
+ const options = { route };
60
+ instance.mount(element, options);
61
+ continue;
62
+ }
63
+
64
+ // At this point it must be a function - resolve it
65
+ if (isFactoryFunction(component)) {
66
+ // Resolve the function to see what it returns
67
+ let resolved = await resolveComponent(component);
68
+
69
+ // Check if resolved result is a component instance
70
+ if (isComponentInstance(resolved)) {
71
+ const options = { route };
72
+ resolved.mount(element, options);
73
+ continue;
74
+ }
75
+
76
+ // Check if resolved result is a component constructor
77
+ if (isComponentConstructor(resolved)) {
78
+ const instance = new resolved();
79
+ const options = { route };
80
+ instance.mount(element, options);
81
+ continue;
82
+ }
83
+
84
+ // If resolved result is still a function or anything else, treat original as event handler with render wrapper
85
+ app.on(route, (...args: any[]) => {
86
+ const result = (component as any)(...args);
87
+ if (typeof element === 'string') {
88
+ element = document.querySelector(element) as HTMLElement;
89
+ if (!element) {
90
+ console.error(`Element not found: ${element}`);
91
+ return;
92
+ }
93
+ }
94
+ return (app as unknown as IApp).render(element, result);
95
+ });
96
+ continue;
97
+ }
98
+
99
+ // If we get here, it's an invalid component type
100
+ console.error(`Invalid component: component must be a class, instance, or function that returns a class/instance`);
101
+ }
102
+ }
103
+
package/src/app.ts CHANGED
@@ -6,8 +6,8 @@
6
6
  * - on(): Subscribe to events with options (once, delay, global)
7
7
  * - off(): Unsubscribe from events with proper cleanup
8
8
  * - run(): Publish events synchronously with error handling
9
- * - runAsync(): Publish events asynchronously with Promise support
10
- * - query(): Alias for runAsync for backward compatibility
9
+ * - runAsync(): Publish events asynchronously with Promise support, returns handler values
10
+ * - query(): Deprecated alias for runAsync() - use runAsync() instead
11
11
  *
12
12
  * 2. Default app singleton - Global event bus instance
13
13
  * - Created once and reused across the application
@@ -35,12 +35,12 @@
35
35
  * // Handle event
36
36
  * });
37
37
  *
38
- * // Publish events
38
+ * // Publish events (fire-and-forget)
39
39
  * app.run('event-name', ...args);
40
40
  *
41
- * // Async events
42
- * app.runAsync('async-event', data).then(results => {
43
- * // Handle results
41
+ * // Get return values from event handlers
42
+ * app.runAsync('event-name', data).then(results => {
43
+ * // Handle results array
44
44
  * });
45
45
  * ```
46
46
  */
@@ -52,19 +52,6 @@ export class App {
52
52
 
53
53
  _events: { [key: string]: Array<{ fn: (...args: any[]) => any, options: EventOptions }> };
54
54
 
55
- public start: any;
56
- public h: any;
57
- public createElement: any;
58
- public render: any;
59
- public Fragment: any;
60
- public webComponent: any;
61
- public safeHTML: any;
62
- public use_render: any;
63
- public use_react: any;
64
- public route: any;
65
-
66
- public version: string;
67
-
68
55
  constructor() {
69
56
  this._events = {} as { [key: string]: Array<{ fn: (...args: any[]) => any, options: EventOptions }> };
70
57
  }
@@ -143,7 +130,11 @@ export class App {
143
130
  return Promise.all(promises);
144
131
  }
145
132
 
133
+ /**
134
+ * @deprecated Use runAsync() instead. app.query() will be removed in a future version.
135
+ */
146
136
  query(name: string, ...args: any[]): Promise<any[]> {
137
+ console.warn('app.query() is deprecated. Use app.runAsync() instead.');
147
138
  return this.runAsync(name, ...args);
148
139
  }
149
140
 
@@ -11,6 +11,7 @@ const styles = `
11
11
  .apprun-play {
12
12
  height: 100%;
13
13
  display: flex;
14
+ font-size: 1.1rem;
14
15
  }
15
16
 
16
17
  .apprun-play .col {
@@ -23,13 +24,20 @@ const styles = `
23
24
  }
24
25
  `;
25
26
 
27
+ const encodeHTML = code => {
28
+ return code.replace(/&/g, '&amp;')
29
+ .replace(/</g, '&lt;')
30
+ .replace(/>/g, '&gt;')
31
+ .replace(/"/g, '&quot;')
32
+ .replace(/'/g, '&#039;');
33
+ }
34
+
26
35
  const code_html = code => `<!DOCTYPE html>
27
36
  <html lang="en">
28
37
  <head>
29
38
  <meta charset="UTF-8">
30
39
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
31
40
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
32
- <script src="https://cdnjs.cloudflare.com/ajax/libs/custom-elements/1.1.2/custom-elements.min.js"></script>
33
41
  <title>AppRun Playground</title>
34
42
  <style>
35
43
  body {
@@ -37,17 +45,47 @@ const code_html = code => `<!DOCTYPE html>
37
45
  margin: 2em;
38
46
  }
39
47
  </style>
40
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
41
- <script src="https://unpkg.com/apprun/dist/apprun-html.js"></script>
48
+ <script src="https://cdn.jsdelivr.net/npm/typescript@latest"></script>
49
+ <script src="dist/apprun-html.js"></script>
42
50
  </head>
43
51
  <body>
44
- <script>
45
- Babel.registerPlugin("d", [Babel.availablePlugins["proposal-decorators"], {legacy: true}]);
46
- Babel.registerPlugin("c", [Babel.availablePlugins["proposal-class-properties"], {loose: true}]);
47
- Babel.registerPlugin("b", [Babel.availablePlugins["proposal-private-methods"], {loose: true}]);
48
- </script>
49
- <script type="text/babel" data-plugins="d, c, b">
50
- ${code}
52
+ <pre id="code" style="display:none">${encodeHTML(code)}</pre>
53
+ <script type="module">
54
+ const code = document.getElementById('code').innerText;
55
+ const compiled = ts.transpileModule(code, {
56
+ compilerOptions: {
57
+ "jsx": "react",
58
+ "jsxFactory": "app.h",
59
+ "jsxFragmentFactory": "app.Fragment",
60
+ "target": "es2020",
61
+ "module": "esnext",
62
+ },
63
+ reportDiagnostics: true,
64
+ });
65
+
66
+ if (compiled.diagnostics && compiled.diagnostics.length) {
67
+ const pre = document.createElement('pre');
68
+ pre.style = 'font-size: 10px;';
69
+ pre.innerText = compiled.diagnostics.map(d => {
70
+ const start = d.start;
71
+ const end = d.start + d.length;
72
+ const line = code.substring(0, end).split('\\n').length;
73
+ const column = code.substring(0, end).split('\\n').pop().length;
74
+ return \`Line: \${line}, Column: \${column}, \${d.messageText}\`;
75
+ }).join('\\n');
76
+ document.body.appendChild(pre);
77
+ } else {
78
+ window.onerror = function () {
79
+ const pre = document.createElement('pre');
80
+ pre.style = 'font-size: 10px;';
81
+ pre.innerText = compiled.outputText;;
82
+ document.body.appendChild(pre);
83
+ };
84
+ const script = document.createElement('script');
85
+ script.type = 'module';
86
+ script.text = compiled.outputText;
87
+ document.body.appendChild(script);
88
+ }
51
89
  </script>
52
90
  </body>
53
91
  </html>`;
@@ -1,12 +1,6 @@
1
1
  import app from './app';
2
2
  import toHTML from './vdom-to-html';
3
3
  import { _createEventTests, _createStateTests } from './apprun-dev-tools-tests';
4
- import yaml from 'js-yaml';
5
-
6
- function replacer(key, value) {
7
- if (typeof value === 'function') return value.toString(); // value.toString();
8
- return ['', null].includes(value) || (typeof value === 'object' && (value.length === 0 || Object.keys(value).length === 0)) ? undefined : value;
9
- }
10
4
 
11
5
  function createProxy(obj) {
12
6
  const handler = {
@@ -53,7 +47,17 @@ function getVDOM(component) {
53
47
  return view;
54
48
  }
55
49
 
50
+ const componentCache = new Map();
56
51
  app['debug'] = true;
52
+ app.on('debug-create-component', component => {
53
+ const element = component.element;
54
+ if (!element) {
55
+ console.warn('Component created without an element:', component);
56
+ return;
57
+ }
58
+ if (componentCache.get(element)) { componentCache.get(element).push(component) }
59
+ else { componentCache.set(element, [component]) }
60
+ });
57
61
 
58
62
  window['_apprun-help'] = ['', () => {
59
63
  Object.keys(window).forEach(cmd => {
@@ -81,12 +85,6 @@ function newWin(html) {
81
85
  win.document.close();
82
86
  }
83
87
 
84
- const get_components = () => {
85
- const o = { components: {} };
86
- app.run('get-components', o);
87
- const { components } = o;
88
- return components;
89
- }
90
88
  const viewElement = element => <div>
91
89
  {element.tagName.toLowerCase()}{element.id ? '#' + element.id : ''}
92
90
  {' '}
@@ -109,7 +107,7 @@ const viewComponents = state => {
109
107
 
110
108
  return <li>
111
109
  <div>{component.constructor.name}</div>
112
- <div><pre>{htmlEncode(yaml.dump(component_def, { replacer }))}</pre></div>
110
+ <div><pre>{htmlEncode(JSON.stringify(component_def, null, 2))}</pre></div>
113
111
  <br />
114
112
  </li>;
115
113
 
@@ -152,7 +150,7 @@ const viewEvents = state => {
152
150
  const _events = (print?) => {
153
151
  const global_events = app['_events']
154
152
  const events = {};
155
- const cache = get_components();
153
+ const cache = componentCache;
156
154
 
157
155
  const add_component = component => component['_actions'].forEach(event => {
158
156
  events[event.name] = events[event.name] || [];
@@ -163,10 +161,6 @@ const _events = (print?) => {
163
161
  for (let [key, comps] of cache) {
164
162
  comps.forEach(add_component);
165
163
  }
166
- } else {
167
- Object.keys(cache).forEach(el =>
168
- cache[el].forEach(add_component)
169
- );
170
164
  }
171
165
  const data = [];
172
166
  Object.keys(events).forEach(event => {
@@ -189,7 +183,7 @@ const _events = (print?) => {
189
183
  }
190
184
 
191
185
  const _components = (print?) => {
192
- const components = get_components();
186
+ const components = componentCache;
193
187
  const data = [];
194
188
 
195
189
  if (components instanceof Map) {
@@ -197,11 +191,6 @@ const _components = (print?) => {
197
191
  const element = typeof key === 'string' ? document.getElementById(key) || document.querySelector(key) : key;
198
192
  data.push({ element, comps });
199
193
  }
200
- } else {
201
- Object.keys(components).forEach(el => {
202
- const element = typeof el === 'string' ? document.getElementById(el) || document.querySelector(el) : el;
203
- data.push({ element, comps: components[el] });
204
- });
205
194
  }
206
195
  if (print) {
207
196
  const vdom = viewComponents(data);
@@ -217,8 +206,15 @@ app.on('debug', p => {
217
206
  if (debugging & 2 && p.vdom) console.log(p);
218
207
  });
219
208
 
220
- window['_apprun-components'] = ['components [print]', (p) => {
221
- _components(p === 'print');
209
+ window['_apprun-components'] = ['components [print|clear]', (p) => {
210
+ if (p === 'print') {
211
+ _components(true);
212
+ } else if (p === 'clear') {
213
+ componentCache.clear();
214
+ console.log('Component cache cleared');
215
+ } else {
216
+ _components(false);
217
+ }
222
218
  }]
223
219
 
224
220
  window['_apprun-events'] = ['events [print]', (p) => {
@@ -262,7 +258,7 @@ window['_apprun'] = (strings) => {
262
258
  else window['_apprun-help'][1]();
263
259
  }
264
260
 
265
- console.info('AppRun DevTools 2.27: type "_apprun `help`" to list all available commands.');
261
+ console.info('AppRun DevTools 3.36: type "_apprun `help`" to list all available commands.');
266
262
 
267
263
  const reduxExt = window['__REDUX_DEVTOOLS_EXTENSION__'];
268
264
  if (reduxExt) {
@@ -92,13 +92,20 @@ a.button:hover {
92
92
  </div>
93
93
  </div>`;
94
94
 
95
+ const encodeHTML = code => {
96
+ return code.replace(/&/g, '&amp;')
97
+ .replace(/</g, '&lt;')
98
+ .replace(/>/g, '&gt;')
99
+ .replace(/"/g, '&quot;')
100
+ .replace(/'/g, '&#039;');
101
+ }
102
+
95
103
  const code_html = code => `<!DOCTYPE html>
96
104
  <html lang="en">
97
105
  <head>
98
106
  <meta charset="UTF-8">
99
107
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
100
108
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
101
- <script src="https://cdnjs.cloudflare.com/ajax/libs/custom-elements/1.1.2/custom-elements.min.js"></script>
102
109
  <title>AppRun Playground</title>
103
110
  <style>
104
111
  body {
@@ -106,17 +113,47 @@ const code_html = code => `<!DOCTYPE html>
106
113
  margin: 2em;
107
114
  }
108
115
  </style>
109
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
116
+ <script src="https://cdn.jsdelivr.net/npm/typescript@latest"></script>
110
117
  <script src="https://unpkg.com/apprun/dist/apprun-html.js"></script>
111
118
  </head>
112
119
  <body>
113
- <script>
114
- Babel.registerPlugin("d", [Babel.availablePlugins["proposal-decorators"], {legacy: true}]);
115
- Babel.registerPlugin("c", [Babel.availablePlugins["proposal-class-properties"], {loose: true}]);
116
- Babel.registerPlugin("b", [Babel.availablePlugins["proposal-private-methods"], {loose: true}]);
117
- </script>
118
- <script type="text/babel" data-plugins="d, c, b">
119
- ${code}
120
+ <pre id="code" style="display:none">${encodeHTML(code)}</pre>
121
+ <script type="module">
122
+ const code = document.getElementById('code').innerText;
123
+ const compiled = ts.transpileModule(code, {
124
+ compilerOptions: {
125
+ "jsx": "react",
126
+ "jsxFactory": "app.h",
127
+ "jsxFragmentFactory": "app.Fragment",
128
+ "target": "es2020",
129
+ "module": "esnext",
130
+ },
131
+ reportDiagnostics: true,
132
+ });
133
+
134
+ if (compiled.diagnostics && compiled.diagnostics.length) {
135
+ const pre = document.createElement('pre');
136
+ pre.style = 'font-size: 10px;';
137
+ pre.innerText = compiled.diagnostics.map(d => {
138
+ const start = d.start;
139
+ const end = d.start + d.length;
140
+ const line = code.substring(0, end).split('\\n').length;
141
+ const column = code.substring(0, end).split('\\n').pop().length;
142
+ return \`Line: \${line}, Column: \${column}, \${d.messageText}\`;
143
+ }).join('\\n');
144
+ document.body.appendChild(pre);
145
+ } else {
146
+ window.onerror = function () {
147
+ const pre = document.createElement('pre');
148
+ pre.style = 'font-size: 10px;';
149
+ pre.innerText = compiled.outputText;;
150
+ document.body.appendChild(pre);
151
+ };
152
+ const script = document.createElement('script');
153
+ script.type = 'module';
154
+ script.text = compiled.outputText;
155
+ document.body.appendChild(script);
156
+ }
120
157
  </script>
121
158
  </body>
122
159
  </html>`;
package/src/apprun.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  * - Router with improved null safety
11
11
  * - Web component support
12
12
  * - Type-safe React integration
13
+ * - Component batch mounting system
13
14
  *
14
15
  * Key exports:
15
16
  * - app: Global event system instance
@@ -26,6 +27,7 @@
26
27
  * - Web Components integration
27
28
  * - React compatibility layer
28
29
  * - TypeScript support with strong typing
30
+ * - Batch component mounting with addComponents(element, components)
29
31
  *
30
32
  * Type Safety Improvements (v3.35.1):
31
33
  * - Added null checks for DOM event targets
@@ -34,6 +36,10 @@
34
36
  * - Better error handling for invalid event handlers
35
37
  * - Safer element access with proper type assertions
36
38
  *
39
+ * Recent Changes:
40
+ * - Modified addComponents to accept (element, components) where components is a key-value object with routes as keys and components as values
41
+ * - Simplified component mounting API for better usability
42
+ *
37
43
  * Usage:
38
44
  * ```ts
39
45
  * import { app, Component } from 'apprun';
@@ -46,16 +52,23 @@
46
52
  * 'event': (state, ...args) => // Handle events
47
53
  * }
48
54
  * }
55
+ *
56
+ * // Mount multiple components
57
+ * app.addComponents(document.body, {
58
+ * '/home': MyComponent,
59
+ * '/about': AnotherComponent
60
+ * });
49
61
  * ```
50
62
  */
51
63
 
52
- import app, { App } from './app';
64
+ import _app, { App } from './app';
53
65
  import { createElement, render, Fragment, safeHTML } from './vdom';
54
66
  import { Component } from './component';
55
- import { VNode, View, Action, Update, EventOptions, ActionOptions, MountOptions, AppStartOptions } from './types';
67
+ import { IApp, VNode, View, Action, Update, EventOptions, ActionOptions, MountOptions, AppStartOptions, CustomElementOptions } from './types';
56
68
  import { on, update, customElement } from './decorator';
57
- import webComponent, { CustomElementOptions } from './web-component';
58
- import { Route, route, ROUTER_EVENT, ROUTER_404_EVENT } from './router';
69
+ import { route, ROUTER_EVENT, ROUTER_404_EVENT } from './router';
70
+ import webComponent from './web-component';
71
+ import addComponents from './add-components';
59
72
  import { APPRUN_VERSION } from './version';
60
73
 
61
74
  export type StatelessComponent<T = {}> = (args: T) => string | VNode | void;
@@ -64,6 +77,9 @@ type OnDecorator = {
64
77
  <E = string>(events?: E, options?: any): (target: any, key: string) => void;
65
78
  };
66
79
 
80
+ const app: IApp = _app as unknown as IApp;
81
+ export default app as IApp;
82
+
67
83
  export {
68
84
  App,
69
85
  app,
@@ -82,29 +98,6 @@ export {
82
98
  export { update as event };
83
99
  export { ROUTER_EVENT, ROUTER_404_EVENT };
84
100
  export { customElement, CustomElementOptions, AppStartOptions };
85
- export default app as IApp;
86
-
87
- export interface IApp {
88
- start<T, E = any>(element?: Element | string, model?: T, view?: View<T>, update?: Update<T, E>,
89
- options?: AppStartOptions<T>): Component<T, E>;
90
- on(name: string, fn: (...args: any[]) => void, options?: any): void;
91
- once(name: string, fn: (...args: any[]) => void, options?: any): void;
92
- off(name: string, fn: (...args: any[]) => void): void;
93
- run(name: string, ...args: any[]): number;
94
- find(name: string): any | any[];
95
- query(name: string, ...args: any[]): Promise<any[]>;
96
- runAsync(name: string, ...args: any[]): Promise<any[]>;
97
- h(tag: string | Function, props, ...children): VNode | VNode[];
98
- createElement(tag: string | Function, props, ...children): VNode | VNode[];
99
- render(element: Element | string, node: VNode): void;
100
- Fragment(props, ...children): any[];
101
- route?: Route;
102
- webComponent(name: string, componentClass, options?: CustomElementOptions): void;
103
- safeHTML(html: string): any[];
104
- use_render(render, mode?: 0 | 1);
105
- use_react(React, ReactDOM);
106
- version: string;
107
- }
108
101
 
109
102
  if (!app.start) {
110
103
 
@@ -126,14 +119,11 @@ if (!app.start) {
126
119
  return component;
127
120
  };
128
121
 
129
- app.once = app.once || ((name: string, fn: (...args: any[]) => void, options: any = {}) => {
130
- app.on(name, fn, { ...options, once: true });
131
- });
132
-
122
+ // Deprecated: app.query is deprecated in favor of app.runAsync
133
123
  app.query = app.query || app.runAsync;
134
124
 
135
125
  const NOOP = _ => {/* Intentionally empty */ }
136
- // app.on('$', NOOP);
126
+ app.on('/', NOOP);
137
127
  app.on('debug', _ => NOOP);
138
128
  app.on(ROUTER_EVENT, NOOP);
139
129
  app.on(ROUTER_404_EVENT, NOOP);
@@ -141,6 +131,11 @@ if (!app.start) {
141
131
  app.on('route', url => app['route'] && app['route'](url));
142
132
 
143
133
  if (typeof document === 'object') {
134
+ let basePath = location.pathname;
135
+ if (basePath.endsWith('/')) {
136
+ basePath = basePath.slice(0, -1);
137
+ }
138
+ app.basePath = basePath;
144
139
  document.addEventListener("DOMContentLoaded", () => {
145
140
  const no_init_route = document.body.hasAttribute('apprun-no-init') || app['no-init-route'] || false;
146
141
  const use_hash = app.find('#') || app.find('#/') || false;
@@ -152,7 +147,18 @@ if (!app.start) {
152
147
  if (use_hash) {
153
148
  !no_init_route && route(location.hash);
154
149
  } else {
155
- !no_init_route && route(location.pathname);
150
+ !no_init_route && (() => {
151
+ const basePath = app.basePath || '';
152
+ let initialPath = location.pathname;
153
+
154
+ // Strip base path if present
155
+ if (basePath && initialPath.startsWith(basePath)) {
156
+ initialPath = initialPath.substring(basePath.length);
157
+ if (!initialPath.startsWith('/')) initialPath = '/' + initialPath;
158
+ }
159
+
160
+ route(initialPath);
161
+ })();
156
162
  document.body.addEventListener('click', e => {
157
163
  const element = e.target as HTMLElement;
158
164
  if (!element) return;
@@ -162,21 +168,22 @@ if (!app.start) {
162
168
  menu.origin === location.origin &&
163
169
  menu.pathname) {
164
170
  e.preventDefault();
165
- history.pushState(null, '', menu.pathname);
166
- route(menu.pathname);
171
+
172
+ // Handle base path for navigation
173
+ const basePath = app.basePath || '';
174
+ const fullPath = basePath + menu.pathname;
175
+
176
+ history.pushState(null, '', fullPath);
177
+ route(menu.pathname); // Route with relative path (without base path)
167
178
  }
168
179
  });
169
180
  }
170
181
  });
171
182
  }
172
183
 
173
- type ComponentType = typeof Component & {
174
- <T = any>(options?: any): (constructor: Function) => void;
175
- };
176
-
177
184
  if (typeof window === 'object') {
178
185
  const globalWindow = window as any;
179
- globalWindow['Component'] = Component as ComponentType;
186
+ globalWindow['Component'] = Component;
180
187
  globalWindow['_React'] = globalWindow['React'];
181
188
  globalWindow['React'] = app;
182
189
  globalWindow['on'] = on as OnDecorator;
@@ -231,4 +238,6 @@ if (!app.start) {
231
238
  app.render = (el, vdom) => ReactDOM.render(vdom, el);
232
239
  }
233
240
  }
241
+
242
+ app.addComponents = addComponents;
234
243
  }
package/src/component.ts CHANGED
@@ -51,16 +51,20 @@
51
51
  * ```
52
52
  */
53
53
 
54
- import app, { App } from './app';
54
+ import _app, { App } from './app';
55
+
56
+
55
57
  import { Reflect } from './decorator'
56
- import { View, Update, ActionDef, ActionOptions, MountOptions, EventOptions } from './types';
58
+ import { View, Update, ActionDef, ActionOptions, MountOptions, EventOptions, IApp } from './types';
57
59
  import directive from './directive';
58
60
  import { safeQuerySelector, safeGetElementById } from './type-utils';
59
61
 
60
- const componentCache = new Map();
61
- if (!app.find('get-components')) app.on('get-components', o => o.components = componentCache);
62
+ // const componentCache = new Map();
63
+ // if (!app.find('get-components')) app.on('get-components', o => o.components = componentCache);
64
+
65
+ export const REFRESH = state => state;
62
66
 
63
- const REFRESH = state => state;
67
+ const app = _app as unknown as IApp;
64
68
 
65
69
  export class Component<T = any, E = any> {
66
70
  static __isAppRunComponent = true;
@@ -244,9 +248,8 @@ export class Component<T = any, E = any> {
244
248
 
245
249
  this.setState(this.state, { render: !!options.render, history: true });
246
250
 
247
- if (app['debug']) {
248
- if (componentCache.get(element)) { componentCache.get(element).push(this) }
249
- else { componentCache.set(element, [this]) }
251
+ if (app['debug'] && app.find('debug-create-component')?.length) {
252
+ app.run('debug-create-component', this);
250
253
  }
251
254
  return this;
252
255
  }
@@ -367,7 +370,11 @@ export class Component<T = any, E = any> {
367
370
  }
368
371
 
369
372
  // obsolete
373
+ /**
374
+ * @deprecated Use runAsync() instead. query() will be removed in a future version.
375
+ */
370
376
  public query(event: E, ...args) {
377
+ console.warn('component.query() is deprecated. Use component.runAsync() instead.');
371
378
  return this.runAsync(event, ...args);
372
379
  }
373
380
 
@@ -0,0 +1,11 @@
1
+ import { produce, Draft } from 'immer';
2
+
3
+ export function createState<T = any>(
4
+ fn: (draft: Draft<T>, ...args: any[]) => void
5
+ ): (state: T, ...args: any[]) => T {
6
+ return (state: T, ...args: any[]): T => {
7
+ return produce(state, (draft) => {
8
+ fn(draft, ...args);
9
+ });
10
+ };
11
+ }