web-mojo 2.1.1043 → 2.1.1044

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 (91) hide show
  1. package/README.md +19 -16
  2. package/dist/admin.cjs.js +1 -1
  3. package/dist/admin.cjs.js.map +1 -1
  4. package/dist/admin.es.js +12 -10
  5. package/dist/admin.es.js.map +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.cjs.js.map +1 -1
  8. package/dist/auth.css +305 -266
  9. package/dist/auth.es.js +537 -2175
  10. package/dist/auth.es.js.map +1 -1
  11. package/dist/charts.cjs.js +1 -1
  12. package/dist/charts.es.js +3 -2
  13. package/dist/charts.es.js.map +1 -1
  14. package/dist/chunks/ChatView-Bvkdj-lq.js +2 -0
  15. package/dist/chunks/{ChatView-CGBaudUc.js.map → ChatView-Bvkdj-lq.js.map} +1 -1
  16. package/dist/chunks/{ChatView-DguKw-gR.js → ChatView-DBgQzOyI.js} +5 -6
  17. package/dist/chunks/{ChatView-DguKw-gR.js.map → ChatView-DBgQzOyI.js.map} +1 -1
  18. package/dist/chunks/{Collection-YRfGoT73.js → Collection-DaTm-2LH.js} +2 -2
  19. package/dist/chunks/{Collection-YRfGoT73.js.map → Collection-DaTm-2LH.js.map} +1 -1
  20. package/dist/chunks/{ContextMenu-B4_YS0G8.js → ContextMenu-hQH_6Pyi.js} +349 -2
  21. package/dist/chunks/ContextMenu-hQH_6Pyi.js.map +1 -0
  22. package/dist/chunks/ContextMenu-snx9Dd1s.js +3 -0
  23. package/dist/chunks/ContextMenu-snx9Dd1s.js.map +1 -0
  24. package/dist/chunks/{DataView-OUqaLmGB.js → DataView-CWejLV3B.js} +2 -1
  25. package/dist/chunks/DataView-CWejLV3B.js.map +1 -0
  26. package/dist/chunks/DataView-D7j4IWyS.js +2 -0
  27. package/dist/chunks/DataView-D7j4IWyS.js.map +1 -0
  28. package/dist/chunks/Dialog-7T8ENHYD.js +2 -0
  29. package/dist/chunks/Dialog-7T8ENHYD.js.map +1 -0
  30. package/dist/chunks/{Dialog-BiVgKzSK.js → Dialog-BcJG5Vta.js} +1358 -4
  31. package/dist/chunks/Dialog-BcJG5Vta.js.map +1 -0
  32. package/dist/chunks/{ListView-BMNhd5-B.js → ListView-BrsQ26R6.js} +2 -2
  33. package/dist/chunks/{ListView-BMNhd5-B.js.map → ListView-BrsQ26R6.js.map} +1 -1
  34. package/dist/chunks/MetricsMiniChartWidget-CN1HPnWf.js +2 -0
  35. package/dist/chunks/{MetricsMiniChartWidget-Esvv-lFp.js.map → MetricsMiniChartWidget-CN1HPnWf.js.map} +1 -1
  36. package/dist/chunks/{MetricsMiniChartWidget-CCroU6BZ.js → MetricsMiniChartWidget-DALWxrzu.js} +2 -2
  37. package/dist/chunks/{MetricsMiniChartWidget-CCroU6BZ.js.map → MetricsMiniChartWidget-DALWxrzu.js.map} +1 -1
  38. package/dist/chunks/{PDFViewer-NeL91Gon.js → PDFViewer-CgdSGU1n.js} +2 -2
  39. package/dist/chunks/{PDFViewer-NeL91Gon.js.map → PDFViewer-CgdSGU1n.js.map} +1 -1
  40. package/dist/chunks/{PDFViewer-D4uo3oiA.js → PDFViewer-DtJIlPXi.js} +2 -2
  41. package/dist/chunks/{PDFViewer-D4uo3oiA.js.map → PDFViewer-DtJIlPXi.js.map} +1 -1
  42. package/dist/chunks/{TopNav-DC8oGpHp.js → TokenManager-BanwFrq7.js} +368 -5
  43. package/dist/chunks/TokenManager-BanwFrq7.js.map +1 -0
  44. package/dist/chunks/TokenManager-DIEFCQ3B.js +2 -0
  45. package/dist/chunks/TokenManager-DIEFCQ3B.js.map +1 -0
  46. package/dist/chunks/version-BaFu2yii.js +38 -0
  47. package/dist/chunks/version-BaFu2yii.js.map +1 -0
  48. package/dist/chunks/version-WMgX72-y.js +2 -0
  49. package/dist/chunks/version-WMgX72-y.js.map +1 -0
  50. package/dist/css/web-mojo.css +1 -17
  51. package/dist/docit.cjs.js +1 -1
  52. package/dist/docit.cjs.js.map +1 -1
  53. package/dist/docit.es.js +5 -7
  54. package/dist/docit.es.js.map +1 -1
  55. package/dist/index.cjs.js +1 -1
  56. package/dist/index.cjs.js.map +1 -1
  57. package/dist/index.es.js +30 -32
  58. package/dist/index.es.js.map +1 -1
  59. package/dist/lightbox.cjs.js +1 -1
  60. package/dist/lightbox.cjs.js.map +1 -1
  61. package/dist/lightbox.es.js +4 -3
  62. package/dist/lightbox.es.js.map +1 -1
  63. package/dist/map.es.js +1 -1
  64. package/dist/timeline.es.js +2 -2
  65. package/package.json +1 -1
  66. package/dist/chunks/ChatView-CGBaudUc.js +0 -2
  67. package/dist/chunks/ContextMenu-B4_YS0G8.js.map +0 -1
  68. package/dist/chunks/ContextMenu-DcLhcYMp.js +0 -3
  69. package/dist/chunks/ContextMenu-DcLhcYMp.js.map +0 -1
  70. package/dist/chunks/DataView-CdDY9ijM.js +0 -2
  71. package/dist/chunks/DataView-CdDY9ijM.js.map +0 -1
  72. package/dist/chunks/DataView-OUqaLmGB.js.map +0 -1
  73. package/dist/chunks/Dialog-BiVgKzSK.js.map +0 -1
  74. package/dist/chunks/Dialog-DmIPK_Bi.js +0 -2
  75. package/dist/chunks/Dialog-DmIPK_Bi.js.map +0 -1
  76. package/dist/chunks/MetricsMiniChartWidget-Esvv-lFp.js +0 -2
  77. package/dist/chunks/Page-CvbwEoLv.js +0 -2
  78. package/dist/chunks/Page-CvbwEoLv.js.map +0 -1
  79. package/dist/chunks/Page-Deq4y2Kq.js +0 -351
  80. package/dist/chunks/Page-Deq4y2Kq.js.map +0 -1
  81. package/dist/chunks/TokenManager-CAZNcCMs.js +0 -366
  82. package/dist/chunks/TokenManager-CAZNcCMs.js.map +0 -1
  83. package/dist/chunks/TokenManager-CJBYcVqs.js +0 -2
  84. package/dist/chunks/TokenManager-CJBYcVqs.js.map +0 -1
  85. package/dist/chunks/TopNav-23B5R-dl.js +0 -2
  86. package/dist/chunks/TopNav-23B5R-dl.js.map +0 -1
  87. package/dist/chunks/TopNav-DC8oGpHp.js.map +0 -1
  88. package/dist/chunks/WebApp-C1vcdSuu.js +0 -1388
  89. package/dist/chunks/WebApp-C1vcdSuu.js.map +0 -1
  90. package/dist/chunks/WebApp-CpxtmTk0.js +0 -2
  91. package/dist/chunks/WebApp-CpxtmTk0.js.map +0 -1
@@ -1,4 +1,1350 @@
1
- import { V as View } from "./Rest-CS4jRCAs.js";
1
+ import { r as rest, V as View } from "./Rest-CS4jRCAs.js";
2
+ class Router {
3
+ constructor(options = {}) {
4
+ this.defaultRoute = options.defaultRoute || "home";
5
+ this.routes = [];
6
+ this.currentRoute = null;
7
+ this.eventEmitter = options.eventEmitter || null;
8
+ this.boundHandlePopState = this.handlePopState.bind(this);
9
+ }
10
+ start() {
11
+ window.addEventListener("popstate", this.boundHandlePopState);
12
+ this.handleCurrentLocation();
13
+ }
14
+ stop() {
15
+ window.removeEventListener("popstate", this.boundHandlePopState);
16
+ }
17
+ addRoute(pattern, pageName) {
18
+ this.routes.push({
19
+ pattern: this.normalizePattern(pattern),
20
+ regex: this.patternToRegex(pattern),
21
+ pageName,
22
+ paramNames: this.extractParamNames(pattern)
23
+ });
24
+ }
25
+ async navigate(path, options = {}) {
26
+ const { replace = false, state = null, trigger = true } = options;
27
+ const { pageName, queryParams } = this.parseInput(path);
28
+ if (trigger) {
29
+ await this.handleRouteChange(pageName, queryParams);
30
+ }
31
+ }
32
+ // Browser navigation
33
+ back() {
34
+ window.history.back();
35
+ }
36
+ forward() {
37
+ window.history.forward();
38
+ }
39
+ // Get current route info
40
+ getCurrentRoute() {
41
+ return this.currentRoute;
42
+ }
43
+ getCurrentPath() {
44
+ const { pageName, queryParams } = this.parseCurrentUrl();
45
+ return this.buildPublicUrl(pageName, queryParams);
46
+ }
47
+ // Private methods
48
+ handlePopState(_event) {
49
+ if (this.allowPopState) {
50
+ this.handleCurrentLocation();
51
+ } else {
52
+ console.warn("PopStateEvent is not allowed");
53
+ }
54
+ }
55
+ async handleCurrentLocation() {
56
+ const { pageName, queryParams } = this.parseCurrentUrl();
57
+ await this.handleRouteChange(pageName, queryParams);
58
+ }
59
+ async handleRouteChange(pageName, queryParams) {
60
+ const routePath = "/" + pageName;
61
+ const route = this.matchRoute(routePath);
62
+ const publicUrl = this.buildPublicUrl(pageName, queryParams);
63
+ if (!route) {
64
+ console.log("No route matched for page:", pageName);
65
+ if (this.eventEmitter) {
66
+ this.eventEmitter.emit("route:notfound", { path: publicUrl });
67
+ }
68
+ return null;
69
+ }
70
+ this.currentRoute = route;
71
+ if (this.eventEmitter) {
72
+ this.eventEmitter.emit("route:changed", {
73
+ path: publicUrl,
74
+ pageName: route.pageName,
75
+ params: route.params,
76
+ query: queryParams,
77
+ route
78
+ });
79
+ }
80
+ return route;
81
+ }
82
+ matchRoute(path) {
83
+ for (const route of this.routes) {
84
+ const match = path.match(route.regex);
85
+ if (match) {
86
+ const params = {};
87
+ route.paramNames.forEach((name, index) => {
88
+ params[name] = match[index + 1];
89
+ });
90
+ return {
91
+ ...route,
92
+ params,
93
+ path
94
+ };
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ // Parse any input format and extract page name + query params
100
+ parseInput(input) {
101
+ let pageName = this.defaultRoute;
102
+ let queryParams = {};
103
+ if (!input) {
104
+ return { pageName, queryParams };
105
+ }
106
+ try {
107
+ if (input.includes("?")) {
108
+ const [pathPart, queryPart] = input.split("?", 2);
109
+ const urlParams = new URLSearchParams(queryPart);
110
+ if (urlParams.has("page")) {
111
+ pageName = urlParams.get("page") || this.defaultRoute;
112
+ for (const [key, value] of urlParams) {
113
+ if (key !== "page") {
114
+ queryParams[key] = value;
115
+ }
116
+ }
117
+ } else {
118
+ if (pathPart.startsWith("/")) {
119
+ pageName = pathPart.substring(1) || this.defaultRoute;
120
+ } else {
121
+ pageName = pathPart || this.defaultRoute;
122
+ }
123
+ for (const [key, value] of urlParams) {
124
+ queryParams[key] = value;
125
+ }
126
+ }
127
+ } else if (input.startsWith("/")) {
128
+ pageName = input.substring(1) || this.defaultRoute;
129
+ } else {
130
+ pageName = input;
131
+ }
132
+ } catch (error) {
133
+ console.warn("Failed to parse input:", input, error);
134
+ pageName = this.defaultRoute;
135
+ queryParams = {};
136
+ }
137
+ return { pageName, queryParams };
138
+ }
139
+ // Parse current browser URL
140
+ parseCurrentUrl() {
141
+ const urlParams = new URLSearchParams(window.location.search);
142
+ const pageName = urlParams.get("page") || this.defaultRoute;
143
+ const queryParams = {};
144
+ for (const [key, value] of urlParams) {
145
+ if (key !== "page") {
146
+ queryParams[key] = value;
147
+ }
148
+ }
149
+ return { pageName, queryParams };
150
+ }
151
+ // Build public URL format: ?page=admin&group=123
152
+ buildPublicUrl(pageName, queryParams = {}) {
153
+ const urlParams = new URLSearchParams();
154
+ urlParams.set("page", pageName);
155
+ Object.entries(queryParams).forEach(([key, value]) => {
156
+ if (value !== null && value !== void 0 && value !== "") {
157
+ urlParams.set(key, String(value));
158
+ }
159
+ });
160
+ return "?" + urlParams.toString();
161
+ }
162
+ // Update browser URL
163
+ updateBrowserUrl(pageName, queryParams, replace, state) {
164
+ const currentUrl = new URL(window.location.origin + window.location.pathname);
165
+ currentUrl.searchParams.set("page", pageName);
166
+ Object.entries(queryParams).forEach(([key, value]) => {
167
+ if (value !== null && value !== void 0 && value !== "") {
168
+ currentUrl.searchParams.set(key, String(value));
169
+ }
170
+ });
171
+ const url = currentUrl.toString();
172
+ if (replace) {
173
+ window.history.replaceState(state, "", url);
174
+ } else {
175
+ window.history.pushState(state, "", url);
176
+ }
177
+ }
178
+ // Route pattern utilities
179
+ patternToRegex(pattern) {
180
+ let regexPattern = pattern.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&").replace(/\/:([^/?]+)\?/g, "(?:/([^/]+))?").replace(/:([^/]+)/g, "([^/]+)");
181
+ return new RegExp(`^${regexPattern}$`);
182
+ }
183
+ extractParamNames(pattern) {
184
+ const matches = pattern.match(/:([^/?]+)\??/g) || [];
185
+ return matches.map((match) => match.replace(/[:?]/g, ""));
186
+ }
187
+ normalizePattern(pattern) {
188
+ return pattern.startsWith("/") ? pattern : `/${pattern}`;
189
+ }
190
+ /**
191
+ * Update URL parameters without triggering navigation
192
+ * @param {object} params - Parameters to update in URL
193
+ * @param {object} options - Options like { replace: true }
194
+ */
195
+ updateUrl(params = {}, options = {}) {
196
+ const { replace = false } = options;
197
+ const { pageName } = this.parseCurrentUrl();
198
+ this.updateBrowserUrl(pageName, params, replace);
199
+ }
200
+ /**
201
+ * Build URL for given page and query parameters
202
+ */
203
+ buildUrl(page, query = {}) {
204
+ return this.buildPublicUrl(page, query);
205
+ }
206
+ /**
207
+ * Check if two routes match (ignoring query parameters)
208
+ * @param {string} route1 - First route in any format ("/admin", "?page=admin", "admin")
209
+ * @param {string} route2 - Second route in any format
210
+ * @returns {boolean} - True if routes point to the same page
211
+ */
212
+ doRoutesMatch(route1, route2) {
213
+ if (!route1 || !route2) return false;
214
+ const { pageName: pageName1 } = this.parseInput(route1);
215
+ const { pageName: pageName2 } = this.parseInput(route2);
216
+ return pageName1 === pageName2;
217
+ }
218
+ }
219
+ class EventBus {
220
+ constructor() {
221
+ this.listeners = {};
222
+ this.onceListeners = {};
223
+ this.maxListeners = 100;
224
+ this.debugMode = false;
225
+ this.eventStats = {};
226
+ }
227
+ /**
228
+ * Add event listener
229
+ * @param {string|Array<string>} event - Event name or array of event names
230
+ * @param {function} callback - Event callback
231
+ * @returns {EventBus} This instance for chaining
232
+ */
233
+ on(event, callback) {
234
+ if (typeof callback !== "function") {
235
+ throw new Error("Callback must be a function");
236
+ }
237
+ if (Array.isArray(event)) {
238
+ event.forEach((eventName) => this.on(eventName, callback));
239
+ return this;
240
+ }
241
+ if (!this.listeners[event]) {
242
+ this.listeners[event] = [];
243
+ }
244
+ if (this.listeners[event].length >= this.maxListeners) {
245
+ console.warn(`Max listeners (${this.maxListeners}) exceeded for event: ${event}`);
246
+ }
247
+ this.listeners[event].push(callback);
248
+ return this;
249
+ }
250
+ /**
251
+ * Add one-time event listener
252
+ * @param {string|Array<string>} event - Event name or array of event names
253
+ * @param {function} callback - Event callback
254
+ * @returns {EventBus} This instance for chaining
255
+ */
256
+ once(event, callback) {
257
+ if (typeof callback !== "function") {
258
+ throw new Error("Callback must be a function");
259
+ }
260
+ if (Array.isArray(event)) {
261
+ event.forEach((eventName) => this.once(eventName, callback));
262
+ return this;
263
+ }
264
+ if (!this.onceListeners[event]) {
265
+ this.onceListeners[event] = [];
266
+ }
267
+ this.onceListeners[event].push(callback);
268
+ return this;
269
+ }
270
+ /**
271
+ * Remove event listener
272
+ * @param {string|Array<string>} event - Event name or array of event names
273
+ * @param {function} callback - Event callback to remove
274
+ * @returns {EventBus} This instance for chaining
275
+ */
276
+ off(event, callback) {
277
+ if (Array.isArray(event)) {
278
+ event.forEach((eventName) => this.off(eventName, callback));
279
+ return this;
280
+ }
281
+ if (!callback) {
282
+ delete this.listeners[event];
283
+ delete this.onceListeners[event];
284
+ return this;
285
+ }
286
+ if (this.listeners[event]) {
287
+ const index = this.listeners[event].indexOf(callback);
288
+ if (index !== -1) {
289
+ this.listeners[event].splice(index, 1);
290
+ if (this.listeners[event].length === 0) {
291
+ delete this.listeners[event];
292
+ }
293
+ }
294
+ }
295
+ if (this.onceListeners[event]) {
296
+ const index = this.onceListeners[event].indexOf(callback);
297
+ if (index !== -1) {
298
+ this.onceListeners[event].splice(index, 1);
299
+ if (this.onceListeners[event].length === 0) {
300
+ delete this.onceListeners[event];
301
+ }
302
+ }
303
+ }
304
+ return this;
305
+ }
306
+ /**
307
+ * Emit event to all listeners
308
+ * @param {string} event - Event name
309
+ * @param {*} data - Event data
310
+ * @returns {EventBus} This instance for chaining
311
+ */
312
+ emit(event, data) {
313
+ this.updateEventStats(event);
314
+ if (this.debugMode) {
315
+ console.log(`[EventBus] Emitting: ${event}`, data);
316
+ }
317
+ const listeners = [];
318
+ if (this.listeners[event]) {
319
+ listeners.push(...this.listeners[event]);
320
+ }
321
+ if (this.listeners["*"]) {
322
+ listeners.push(...this.listeners["*"]);
323
+ }
324
+ if (this.onceListeners[event]) {
325
+ listeners.push(...this.onceListeners[event]);
326
+ delete this.onceListeners[event];
327
+ }
328
+ if (this.onceListeners["*"]) {
329
+ listeners.push(...this.onceListeners["*"]);
330
+ delete this.onceListeners["*"];
331
+ }
332
+ if (this.debugMode && listeners.length > 0) {
333
+ console.log(`[EventBus] ${listeners.length} listener(s) for '${event}'`);
334
+ }
335
+ listeners.forEach((callback) => {
336
+ try {
337
+ if (callback(data, event)) {
338
+ if (event.stopPropagation) {
339
+ event.stopPropagation();
340
+ }
341
+ if (event.preventDefault) {
342
+ event.preventDefault();
343
+ }
344
+ }
345
+ } catch (error) {
346
+ console.error(`Error in event listener for '${event}':`, error);
347
+ this.emitError(error, event, callback);
348
+ }
349
+ });
350
+ return this;
351
+ }
352
+ /**
353
+ * Emit event asynchronously
354
+ * @param {string} event - Event name
355
+ * @param {*} data - Event data
356
+ * @returns {Promise<EventBus>} Promise that resolves with this instance
357
+ */
358
+ async emitAsync(event, data) {
359
+ const listeners = [];
360
+ if (this.listeners[event]) {
361
+ listeners.push(...this.listeners[event]);
362
+ }
363
+ if (this.listeners["*"]) {
364
+ listeners.push(...this.listeners["*"]);
365
+ }
366
+ if (this.onceListeners[event]) {
367
+ listeners.push(...this.onceListeners[event]);
368
+ delete this.onceListeners[event];
369
+ }
370
+ if (this.onceListeners["*"]) {
371
+ listeners.push(...this.onceListeners["*"]);
372
+ delete this.onceListeners["*"];
373
+ }
374
+ const promises = listeners.map((callback) => {
375
+ return new Promise((resolve) => {
376
+ try {
377
+ const result = callback(data, event);
378
+ resolve(result);
379
+ } catch (error) {
380
+ console.error(`Error in async event listener for '${event}':`, error);
381
+ this.emitError(error, event, callback);
382
+ resolve();
383
+ }
384
+ });
385
+ });
386
+ await Promise.all(promises);
387
+ return this;
388
+ }
389
+ /**
390
+ * Remove all listeners for all events
391
+ * @returns {EventBus} This instance for chaining
392
+ */
393
+ removeAllListeners() {
394
+ this.listeners = {};
395
+ this.onceListeners = {};
396
+ return this;
397
+ }
398
+ /**
399
+ * Get listener count for an event
400
+ * @param {string} event - Event name
401
+ * @returns {number} Number of listeners
402
+ */
403
+ listenerCount(event) {
404
+ const regularCount = this.listeners[event] ? this.listeners[event].length : 0;
405
+ const onceCount = this.onceListeners[event] ? this.onceListeners[event].length : 0;
406
+ return regularCount + onceCount;
407
+ }
408
+ /**
409
+ * Get all event names that have listeners
410
+ * @returns {Array<string>} Array of event names
411
+ */
412
+ eventNames() {
413
+ const regularEvents = Object.keys(this.listeners);
414
+ const onceEvents = Object.keys(this.onceListeners);
415
+ return [.../* @__PURE__ */ new Set([...regularEvents, ...onceEvents])];
416
+ }
417
+ /**
418
+ * Set maximum number of listeners per event
419
+ * @param {number} max - Maximum listeners
420
+ * @returns {EventBus} This instance for chaining
421
+ */
422
+ setMaxListeners(max) {
423
+ if (typeof max !== "number" || max < 0) {
424
+ throw new Error("Max listeners must be a non-negative number");
425
+ }
426
+ this.maxListeners = max;
427
+ return this;
428
+ }
429
+ /**
430
+ * Create a namespaced event bus
431
+ * @param {string} namespace - Namespace prefix
432
+ * @returns {object} Namespaced event bus methods
433
+ */
434
+ namespace(namespace) {
435
+ const prefixEvent = (event) => `${namespace}:${event}`;
436
+ return {
437
+ on: (event, callback) => this.on(prefixEvent(event), callback),
438
+ once: (event, callback) => this.once(prefixEvent(event), callback),
439
+ off: (event, callback) => this.off(prefixEvent(event), callback),
440
+ emit: (event, data) => this.emit(prefixEvent(event), data),
441
+ emitAsync: (event, data) => this.emitAsync(prefixEvent(event), data)
442
+ };
443
+ }
444
+ /**
445
+ * Add middleware to intercept events
446
+ * @param {function} middleware - Middleware function
447
+ * @returns {EventBus} This instance for chaining
448
+ */
449
+ use(middleware) {
450
+ if (typeof middleware !== "function") {
451
+ throw new Error("Middleware must be a function");
452
+ }
453
+ const originalEmit = this.emit;
454
+ this.emit = (event, data) => {
455
+ try {
456
+ const result = middleware(event, data);
457
+ if (result === false) {
458
+ return this;
459
+ }
460
+ const finalData = result !== void 0 ? result : data;
461
+ return originalEmit.call(this, event, finalData);
462
+ } catch (error) {
463
+ console.error("Error in event middleware:", error);
464
+ return originalEmit.call(this, event, data);
465
+ }
466
+ };
467
+ return this;
468
+ }
469
+ /**
470
+ * Emit error event for listener errors
471
+ * @private
472
+ * @param {Error} error - The error that occurred
473
+ * @param {string} originalEvent - The event that caused the error
474
+ * @param {function} callback - The callback that threw the error
475
+ */
476
+ emitError(error, originalEvent, callback) {
477
+ if (originalEvent === "error") {
478
+ return;
479
+ }
480
+ setTimeout(() => {
481
+ this.emit("error", {
482
+ error,
483
+ originalEvent,
484
+ callback: callback.toString()
485
+ });
486
+ }, 0);
487
+ }
488
+ /**
489
+ * Create a promise that resolves when an event is emitted
490
+ * @param {string} event - Event name
491
+ * @param {number} timeout - Optional timeout in milliseconds
492
+ * @returns {Promise} Promise that resolves with event data
493
+ */
494
+ waitFor(event, timeout = null) {
495
+ return new Promise((resolve, reject) => {
496
+ let timeoutId = null;
497
+ const cleanup = () => {
498
+ if (timeoutId) {
499
+ clearTimeout(timeoutId);
500
+ }
501
+ };
502
+ const listener = (data) => {
503
+ cleanup();
504
+ resolve(data);
505
+ };
506
+ this.once(event, listener);
507
+ if (timeout) {
508
+ timeoutId = setTimeout(() => {
509
+ this.off(event, listener);
510
+ reject(new Error(`Timeout waiting for event: ${event}`));
511
+ }, timeout);
512
+ }
513
+ });
514
+ }
515
+ /**
516
+ * Enable/disable debug mode for detailed event logging
517
+ * @param {boolean} enable - Whether to enable debug mode
518
+ * @returns {EventBus} This instance for chaining
519
+ */
520
+ debug(enable = true) {
521
+ this.debugMode = enable;
522
+ if (enable) {
523
+ console.log("[EventBus] Debug mode enabled");
524
+ } else {
525
+ console.log("[EventBus] Debug mode disabled");
526
+ }
527
+ return this;
528
+ }
529
+ /**
530
+ * Get statistics about the event bus
531
+ * @returns {object} Statistics object
532
+ */
533
+ getStats() {
534
+ const eventNames = this.eventNames();
535
+ const stats = {
536
+ totalEvents: eventNames.length,
537
+ totalListeners: 0,
538
+ events: {},
539
+ emissions: { ...this.eventStats }
540
+ };
541
+ eventNames.forEach((event) => {
542
+ const count = this.listenerCount(event);
543
+ stats.events[event] = count;
544
+ stats.totalListeners += count;
545
+ });
546
+ return stats;
547
+ }
548
+ /**
549
+ * Update event emission statistics
550
+ * @private
551
+ * @param {string} event - Event name
552
+ */
553
+ updateEventStats(event) {
554
+ if (!this.eventStats[event]) {
555
+ this.eventStats[event] = {
556
+ count: 0,
557
+ firstEmission: Date.now(),
558
+ lastEmission: null
559
+ };
560
+ }
561
+ this.eventStats[event].count++;
562
+ this.eventStats[event].lastEmission = Date.now();
563
+ }
564
+ /**
565
+ * Get detailed statistics for a specific event
566
+ * @param {string} event - Event name
567
+ * @returns {Object} Event statistics
568
+ */
569
+ getEventStats(event) {
570
+ const stats = this.eventStats[event];
571
+ if (!stats) {
572
+ return null;
573
+ }
574
+ return {
575
+ ...stats,
576
+ listenerCount: this.listenerCount(event),
577
+ avgEmissionsPerMinute: this.calculateEmissionRate(stats)
578
+ };
579
+ }
580
+ /**
581
+ * Calculate emission rate for an event
582
+ * @private
583
+ * @param {Object} stats - Event statistics
584
+ * @returns {number} Emissions per minute
585
+ */
586
+ calculateEmissionRate(stats) {
587
+ if (!stats.firstEmission || !stats.lastEmission) {
588
+ return 0;
589
+ }
590
+ const durationMs = stats.lastEmission - stats.firstEmission;
591
+ if (durationMs === 0) {
592
+ return 0;
593
+ }
594
+ const durationMinutes = durationMs / (1e3 * 60);
595
+ return Math.round(stats.count / durationMinutes * 100) / 100;
596
+ }
597
+ /**
598
+ * Reset event statistics
599
+ * @returns {EventBus} This instance for chaining
600
+ */
601
+ resetStats() {
602
+ this.eventStats = {};
603
+ return this;
604
+ }
605
+ /**
606
+ * Get a summary of the most active events
607
+ * @param {number} limit - Number of events to return (default: 10)
608
+ * @returns {Array} Array of event statistics sorted by emission count
609
+ */
610
+ getTopEvents(limit = 10) {
611
+ return Object.entries(this.eventStats).map(([event, stats]) => ({
612
+ event,
613
+ count: stats.count,
614
+ rate: this.calculateEmissionRate(stats),
615
+ listeners: this.listenerCount(event)
616
+ })).sort((a, b) => b.count - a.count).slice(0, limit);
617
+ }
618
+ /**
619
+ * Log comprehensive debug information
620
+ * @returns {EventBus} This instance for chaining
621
+ */
622
+ debugInfo() {
623
+ console.group("[EventBus] Debug Information");
624
+ console.log("Debug Mode:", this.debugMode);
625
+ console.log("Max Listeners:", this.maxListeners);
626
+ const stats = this.getStats();
627
+ console.log("Total Events:", stats.totalEvents);
628
+ console.log("Total Listeners:", stats.totalListeners);
629
+ if (Object.keys(this.eventStats).length > 0) {
630
+ console.log("Top Events:", this.getTopEvents(5));
631
+ }
632
+ console.groupEnd();
633
+ return this;
634
+ }
635
+ }
636
+ class WebApp {
637
+ constructor(config = {}) {
638
+ this.config = config;
639
+ this.initPluginRegistry();
640
+ this.name = config.name || "MOJO App";
641
+ this.version = config.version || "1.0.0";
642
+ this.debug = config.debug || false;
643
+ this.container = config.container || "#app";
644
+ this.layoutType = config.layout || "portal";
645
+ this.layoutConfig = config.layoutConfig || {};
646
+ if (config.sidebar) {
647
+ this.layoutConfig.sidebarConfig = config.sidebar;
648
+ }
649
+ if (config.topbar) {
650
+ this.layoutConfig.topbarConfig = config.topbar;
651
+ }
652
+ this.layout = null;
653
+ this.layoutConfig.containerId = this.container || this.containerId || "#app";
654
+ this.pageContainer = config.pageContainer || "#page-container";
655
+ this.basePath = config.basePath || "";
656
+ this.routerMode = config.routerMode || config.router?.mode || "param";
657
+ this.basePath = config.basePath || config.router?.base || "";
658
+ this.defaultRoute = config.defaultRoute || "home";
659
+ this.session = config.session || {};
660
+ this.router = null;
661
+ this.navigation = config.navigation || {};
662
+ this.state = {
663
+ currentPage: null,
664
+ previousPage: null,
665
+ loading: false
666
+ };
667
+ this.events = new EventBus();
668
+ this.rest = rest;
669
+ if (config.api) {
670
+ this.rest.configure(config.api);
671
+ }
672
+ this.router = new Router({
673
+ mode: this.routerMode === "param" ? "params" : this.routerMode,
674
+ basePath: this.basePath,
675
+ defaultRoute: this.defaultRoute,
676
+ eventEmitter: this.events
677
+ });
678
+ this.events.on("route:changed", async (routeInfo) => {
679
+ const { pageName, params, query } = routeInfo;
680
+ await this.showPage(pageName, query, params, { fromRouter: true });
681
+ });
682
+ if (typeof window !== "undefined") {
683
+ window.MOJO = window.MOJO || {};
684
+ window.MOJO.router = this.router;
685
+ }
686
+ this.setupFocusTracking();
687
+ this.pageCache = /* @__PURE__ */ new Map();
688
+ this.pageClasses = /* @__PURE__ */ new Map();
689
+ this.componentClasses = /* @__PURE__ */ new Map();
690
+ this.modelClasses = /* @__PURE__ */ new Map();
691
+ this.currentPage = null;
692
+ this.isStarted = false;
693
+ if (window.matchUUID) {
694
+ window[window.matchUUID] = this;
695
+ } else if (window.MOJO) {
696
+ window.MOJO.app = this;
697
+ } else {
698
+ window.__app__ = this;
699
+ }
700
+ }
701
+ /**
702
+ * Start the application
703
+ */
704
+ async start() {
705
+ if (this.isStarted) {
706
+ console.warn("WebApp already started");
707
+ return;
708
+ }
709
+ try {
710
+ this.setupPageContainer();
711
+ this.validateDefaultRoute();
712
+ await this.setupRouter();
713
+ this.isStarted = true;
714
+ this.router.allowPopState = false;
715
+ this.events.emit("app:ready", { app: this });
716
+ } catch (error) {
717
+ console.error(`Failed to start ${this.name}:`, error);
718
+ this.showError("Failed to start application");
719
+ throw error;
720
+ }
721
+ }
722
+ /**
723
+ * Setup router with configuration
724
+ */
725
+ async setupRouter() {
726
+ if (!this.router) {
727
+ console.error("Router not initialized");
728
+ return;
729
+ }
730
+ this.events.on("route:notfound", async (info) => {
731
+ console.warn(`Route not found: ${info.path}`);
732
+ this._show404(info.path);
733
+ });
734
+ this.router.start();
735
+ console.log(`Router started in ${this.routerMode} mode`);
736
+ }
737
+ /**
738
+ * Setup layout based on configuration
739
+ */
740
+ setupPageContainer() {
741
+ const container = typeof this.container === "string" ? document.querySelector(this.container) : this.container;
742
+ if (container && !container.querySelector("#page-container")) {
743
+ container.innerHTML = '<div id="page-container"></div>';
744
+ }
745
+ this.pageContainer = "#page-container";
746
+ }
747
+ /**
748
+ * Register a page with the app
749
+ */
750
+ registerPage(pageName, PageClass, options = {}) {
751
+ if (typeof pageName !== "string" || !pageName) {
752
+ console.error("registerPage: pageName must be a non-empty string");
753
+ return this;
754
+ }
755
+ if (typeof PageClass !== "function") {
756
+ console.error("registerPage: PageClass must be a constructor function");
757
+ return this;
758
+ }
759
+ if (!options.containerId) options.containerId = this.pageContainer;
760
+ this.pageClasses.set(pageName, {
761
+ PageClass,
762
+ constructorOptions: options
763
+ });
764
+ if (this.router) {
765
+ let route = options.route || `/${pageName}`;
766
+ if (!route.startsWith("/")) {
767
+ route = `/${route}`;
768
+ }
769
+ options.route = route;
770
+ this.router.addRoute(route, pageName);
771
+ }
772
+ return this;
773
+ }
774
+ /**
775
+ * Get page instance (cached)
776
+ */
777
+ getPage(pageName) {
778
+ return this.pageCache.get(pageName);
779
+ }
780
+ getPagePermissions(pageName) {
781
+ if (this.pageCache.has(pageName)) {
782
+ return this.pageCache.get(pageName).permissions;
783
+ }
784
+ const pageInfo = this.pageClasses.get(pageName);
785
+ if (!pageInfo) return null;
786
+ const { PageClass, constructorOptions } = pageInfo;
787
+ if (!constructorOptions) return null;
788
+ return constructorOptions.permissions;
789
+ }
790
+ /**
791
+ * Get or create page instance (with caching)
792
+ */
793
+ getOrCreatePage(pageName) {
794
+ if (this.pageCache.has(pageName)) {
795
+ return this.pageCache.get(pageName);
796
+ }
797
+ const pageInfo = this.pageClasses.get(pageName);
798
+ if (!pageInfo) {
799
+ console.error(`Page not registered: ${pageName}`);
800
+ return null;
801
+ }
802
+ const { PageClass, constructorOptions } = pageInfo;
803
+ try {
804
+ const pageOptions = {
805
+ pageName,
806
+ ...constructorOptions,
807
+ app: this
808
+ };
809
+ const page = new PageClass(pageOptions);
810
+ if (constructorOptions.route) {
811
+ page.route = constructorOptions.route;
812
+ }
813
+ this.pageCache.set(pageName, page);
814
+ console.log(`Created page: ${pageName} with route: ${page.route}`);
815
+ return page;
816
+ } catch (error) {
817
+ console.error(`Failed to create page ${pageName}:`, error);
818
+ return null;
819
+ }
820
+ }
821
+ /**
822
+ * Show a page
823
+ */
824
+ /**
825
+ * SIMPLIFIED UNIFIED PAGE SHOWING METHOD
826
+ * @param {string|object} page - Page name or page instance
827
+ * @param {object} query - URL query parameters (URL-safe)
828
+ * @param {object} params - Any data to pass to page (can include objects)
829
+ * @param {object} options - Options { fromRouter, replace, force }
830
+ */
831
+ async showPage(page, query = {}, params = {}, options = {}) {
832
+ const { fromRouter = false, replace = false, force = false } = options;
833
+ try {
834
+ let pageInstance, pageName;
835
+ if (typeof page === "string") {
836
+ pageName = page;
837
+ pageInstance = this.getOrCreatePage(page);
838
+ } else if (page && typeof page === "object") {
839
+ pageInstance = page;
840
+ pageName = page.pageName;
841
+ }
842
+ this.events.emit("page:showing", {
843
+ page: pageInstance,
844
+ pageName: pageInstance.pageName,
845
+ params,
846
+ query,
847
+ fromRouter
848
+ });
849
+ const oldPage = this.currentPage;
850
+ if (!pageInstance) {
851
+ this._show404(pageName, params, query, fromRouter);
852
+ return;
853
+ }
854
+ if (!pageInstance.canEnter()) {
855
+ this._showDeniedPage(pageInstance, params, query, fromRouter);
856
+ return;
857
+ }
858
+ if (oldPage && oldPage !== pageInstance) {
859
+ await this._exitOldPage(oldPage);
860
+ }
861
+ await pageInstance.onParams(params, query);
862
+ if (oldPage !== pageInstance) {
863
+ await pageInstance.onEnter();
864
+ }
865
+ pageInstance.syncUrl();
866
+ this.events.emit("page:show", {
867
+ page: pageInstance,
868
+ pageName: pageInstance.pageName,
869
+ params,
870
+ query,
871
+ fromRouter
872
+ });
873
+ await pageInstance.render();
874
+ this.currentPage = pageInstance;
875
+ console.log(`✅ Showing page: ${pageInstance.pageName}`, { query, params });
876
+ } catch (error) {
877
+ console.error("Error in showPage:", error);
878
+ this.showError(`Failed to load page: ${error.message}`);
879
+ if (page !== "error") {
880
+ await this.showPage("error", {}, { error, originalPage: page }, { fromRouter });
881
+ }
882
+ }
883
+ }
884
+ async _show404(pageName, params, query, fromRouter) {
885
+ const notFoundPage = this.getOrCreatePage("404");
886
+ if (!notFoundPage) return;
887
+ if (notFoundPage.setInfo) {
888
+ notFoundPage.setInfo(pageName);
889
+ }
890
+ await this._exitOldPage(this.currentPage);
891
+ await notFoundPage.render();
892
+ this.currentPage = notFoundPage;
893
+ this.events.emit("page:404", {
894
+ page: null,
895
+ pageName,
896
+ params,
897
+ query,
898
+ fromRouter
899
+ });
900
+ }
901
+ async _showDeniedPage(pageInstance, params, query, fromRouter) {
902
+ const deniedPage = this.getOrCreatePage("denied");
903
+ if (deniedPage.setDeniedPage) {
904
+ deniedPage.setDeniedPage(pageInstance);
905
+ }
906
+ await this._exitOldPage(this.currentPage);
907
+ await deniedPage.render();
908
+ this.currentPage = deniedPage;
909
+ this.events.emit("page:denied", {
910
+ page: pageInstance,
911
+ pageName: pageInstance.pageName,
912
+ params,
913
+ query,
914
+ fromRouter
915
+ });
916
+ }
917
+ async _exitOldPage(oldPage) {
918
+ if (!oldPage) return;
919
+ try {
920
+ await oldPage.onExit();
921
+ await oldPage.unmount();
922
+ this.events.emit("page:hide", { page: oldPage });
923
+ } catch (error) {
924
+ console.error(`Error exiting page ${oldPage.pageName}:`, error);
925
+ }
926
+ }
927
+ /**
928
+ * SIMPLIFIED NAVIGATION - delegates to router
929
+ * @param {string} route - Route like "/users" or "/users/123"
930
+ * @param {object} query - Query string params { filter: 'active' }
931
+ * @param {object} options - Navigation options { replace, force }
932
+ */
933
+ async navigate(route, query = {}, options = {}) {
934
+ if (!this.router) {
935
+ console.error("Router not initialized");
936
+ return;
937
+ }
938
+ let fullPath = route;
939
+ if (Object.keys(query).length > 0) {
940
+ const queryString = new URLSearchParams(query).toString();
941
+ fullPath += (route.includes("?") ? "&" : "?") + queryString;
942
+ }
943
+ return await this.router.navigate(fullPath, options);
944
+ }
945
+ /**
946
+ * Navigate to the default route
947
+ */
948
+ async navigateToDefault(options = {}) {
949
+ return await this.showPage(this.defaultRoute, {}, {}, options);
950
+ }
951
+ /**
952
+ * Navigate back
953
+ */
954
+ back() {
955
+ if (this.router) {
956
+ this.router.back();
957
+ } else {
958
+ console.warn("Router not initialized");
959
+ }
960
+ }
961
+ /**
962
+ * Navigate forward
963
+ */
964
+ forward() {
965
+ if (this.router) {
966
+ this.router.forward();
967
+ } else {
968
+ console.warn("Router not initialized");
969
+ }
970
+ }
971
+ /**
972
+ * Get current page
973
+ */
974
+ getCurrentPage() {
975
+ return this.currentPage;
976
+ }
977
+ /**
978
+ * Get page container element
979
+ */
980
+ getPageContainer() {
981
+ if (this.layout && this.layout.getPageContainer) {
982
+ return this.layout.getPageContainer();
983
+ }
984
+ const container = typeof this.pageContainer === "string" ? document.querySelector(this.pageContainer) : this.pageContainer;
985
+ return container;
986
+ }
987
+ /**
988
+ * Show error notification
989
+ */
990
+ async showError(message) {
991
+ try {
992
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
993
+ await Dialog2.alert(message, "Error", { size: "md", class: "text-danger" });
994
+ } catch (e) {
995
+ this.events.emit("notification", { message, type: "error" });
996
+ if (typeof window !== "undefined" && window?.console) {
997
+ console.error("[WebApp] showError fallback:", e);
998
+ }
999
+ if (typeof window !== "undefined") {
1000
+ alert(`Error: ${message}`);
1001
+ }
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Show success notification
1006
+ */
1007
+ async showSuccess(message) {
1008
+ try {
1009
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1010
+ await Dialog2.alert(message, "Success", { size: "md", class: "text-success" });
1011
+ } catch (e) {
1012
+ this.events.emit("notification", { message, type: "success" });
1013
+ if (typeof window !== "undefined" && window?.console) {
1014
+ console.warn("[WebApp] showSuccess fallback:", e);
1015
+ }
1016
+ if (typeof window !== "undefined") {
1017
+ alert(`Success: ${message}`);
1018
+ }
1019
+ }
1020
+ }
1021
+ /**
1022
+ * Show info notification
1023
+ */
1024
+ async showInfo(message) {
1025
+ try {
1026
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1027
+ await Dialog2.alert(message, "Information", { size: "md", class: "text-info" });
1028
+ } catch (e) {
1029
+ this.events.emit("notification", { message, type: "info" });
1030
+ if (typeof window !== "undefined" && window?.console) {
1031
+ console.info("[WebApp] showInfo fallback:", e);
1032
+ }
1033
+ if (typeof window !== "undefined") {
1034
+ alert(`Info: ${message}`);
1035
+ }
1036
+ }
1037
+ }
1038
+ /**
1039
+ * Show warning notification
1040
+ */
1041
+ async showWarning(message) {
1042
+ try {
1043
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1044
+ await Dialog2.alert(message, "Warning", { size: "md", class: "text-warning" });
1045
+ } catch (e) {
1046
+ this.events.emit("notification", { message, type: "warning" });
1047
+ if (typeof window !== "undefined" && window?.console) {
1048
+ console.warn("[WebApp] showWarning fallback:", e);
1049
+ }
1050
+ if (typeof window !== "undefined") {
1051
+ alert(`Warning: ${message}`);
1052
+ }
1053
+ }
1054
+ }
1055
+ /**
1056
+ * Show generic notification
1057
+ */
1058
+ showNotification(message, type = "info") {
1059
+ this.events.emit("notification", { message, type });
1060
+ }
1061
+ /**
1062
+ * Show loading indicator
1063
+ */
1064
+ async showLoading(opts = {}) {
1065
+ if (typeof opts === "string") {
1066
+ opts = { message: opts };
1067
+ }
1068
+ try {
1069
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1070
+ Dialog2.showBusy(opts);
1071
+ } catch (e) {
1072
+ if (typeof window !== "undefined" && window?.console) {
1073
+ console.warn("[WebApp] showLoading fallback:", e, opts);
1074
+ }
1075
+ this.events.emit("notification", { message: opts.message || "Loading...", type: "info" });
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Hide loading indicator
1080
+ */
1081
+ async hideLoading() {
1082
+ try {
1083
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1084
+ Dialog2.hideBusy();
1085
+ } catch (e) {
1086
+ if (typeof window !== "undefined" && window?.console) {
1087
+ console.warn("[WebApp] hideLoading fallback:", e);
1088
+ }
1089
+ }
1090
+ }
1091
+ async showModelForm(options = {}) {
1092
+ try {
1093
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1094
+ return await Dialog2.showModelForm(options);
1095
+ } catch (e) {
1096
+ if (typeof window !== "undefined" && window?.console) {
1097
+ console.error("[WebApp] showModelForm failed:", e);
1098
+ }
1099
+ throw e;
1100
+ }
1101
+ }
1102
+ async showForm(options = {}) {
1103
+ try {
1104
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1105
+ return await Dialog2.showForm(options);
1106
+ } catch (e) {
1107
+ if (typeof window !== "undefined" && window?.console) {
1108
+ console.error("[WebApp] showForm failed:", e);
1109
+ }
1110
+ throw e;
1111
+ }
1112
+ }
1113
+ async confirm(message, title = "Confirm", options = {}) {
1114
+ const Dialog2 = (await Promise.resolve().then(() => Dialog$1)).default;
1115
+ return await Dialog2.confirm(message, title, options);
1116
+ }
1117
+ /**
1118
+ * Setup browser focus/blur tracking
1119
+ */
1120
+ setupFocusTracking() {
1121
+ if (typeof window === "undefined") return;
1122
+ this.isFocused = !document.hidden;
1123
+ const handleVisibilityChange = () => {
1124
+ const wasFocused = this.isFocused;
1125
+ this.isFocused = !document.hidden;
1126
+ if (wasFocused !== this.isFocused) {
1127
+ if (this.isFocused) {
1128
+ this.events.emit("browser:focus");
1129
+ } else {
1130
+ this.events.emit("browser:blur");
1131
+ }
1132
+ }
1133
+ };
1134
+ const handleFocus = () => {
1135
+ if (!this.isFocused) {
1136
+ this.isFocused = true;
1137
+ this.events.emit("browser:focus");
1138
+ }
1139
+ };
1140
+ const handleBlur = () => {
1141
+ if (this.isFocused) {
1142
+ this.isFocused = false;
1143
+ this.events.emit("browser:blur");
1144
+ }
1145
+ };
1146
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1147
+ window.addEventListener("focus", handleFocus);
1148
+ window.addEventListener("blur", handleBlur);
1149
+ this._focusHandlers = {
1150
+ visibilitychange: handleVisibilityChange,
1151
+ focus: handleFocus,
1152
+ blur: handleBlur
1153
+ };
1154
+ }
1155
+ /**
1156
+ * Setup global error handling
1157
+ */
1158
+ setupErrorHandling() {
1159
+ window.addEventListener("error", (event) => {
1160
+ console.error("Global error:", event.error);
1161
+ if (this.debug) {
1162
+ this.showError(`Error: ${event.error?.message || "Unknown error"}`);
1163
+ }
1164
+ });
1165
+ window.addEventListener("unhandledrejection", (event) => {
1166
+ console.error("Unhandled promise rejection:", event.reason);
1167
+ if (this.debug) {
1168
+ this.showError(`Promise rejected: ${event.reason?.message || "Unknown error"}`);
1169
+ }
1170
+ });
1171
+ }
1172
+ /**
1173
+ * Escape HTML to prevent XSS
1174
+ */
1175
+ escapeHtml(unsafe) {
1176
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1177
+ }
1178
+ /**
1179
+ * Get application state
1180
+ */
1181
+ getState(key) {
1182
+ return key ? this.state[key] : this.state;
1183
+ }
1184
+ /**
1185
+ * Set application state
1186
+ */
1187
+ setState(updates) {
1188
+ const oldState = { ...this.state };
1189
+ Object.assign(this.state, updates);
1190
+ this.events.emit("state:changed", {
1191
+ oldState,
1192
+ newState: this.state,
1193
+ updates
1194
+ });
1195
+ }
1196
+ /**
1197
+ * Register component class
1198
+ */
1199
+ registerComponent(name, ComponentClass) {
1200
+ this.componentClasses.set(name, ComponentClass);
1201
+ }
1202
+ /**
1203
+ * Get component class
1204
+ */
1205
+ getComponent(name) {
1206
+ return this.componentClasses.get(name);
1207
+ }
1208
+ /**
1209
+ * Register model class
1210
+ */
1211
+ registerModel(name, ModelClass) {
1212
+ this.modelClasses.set(name, ModelClass);
1213
+ }
1214
+ /**
1215
+ * Get model class
1216
+ */
1217
+ getModel(name) {
1218
+ return this.modelClasses.get(name);
1219
+ }
1220
+ /**
1221
+ * Setup REST configuration
1222
+ */
1223
+ setupRest() {
1224
+ this.rest = rest;
1225
+ rest.configure(this.api);
1226
+ }
1227
+ /**
1228
+ * Destroy the application
1229
+ */
1230
+ async destroy() {
1231
+ console.log("Destroying WebApp...");
1232
+ if (this.router) {
1233
+ this.router.stop();
1234
+ }
1235
+ if (this._focusHandlers && typeof window !== "undefined") {
1236
+ document.removeEventListener("visibilitychange", this._focusHandlers.visibilitychange);
1237
+ window.removeEventListener("focus", this._focusHandlers.focus);
1238
+ window.removeEventListener("blur", this._focusHandlers.blur);
1239
+ }
1240
+ const pages = Array.from(this.pageCache.values());
1241
+ await Promise.allSettled(
1242
+ pages.map(async (page) => {
1243
+ try {
1244
+ if (page.destroy) {
1245
+ await page.destroy();
1246
+ }
1247
+ } catch (error) {
1248
+ console.error("Error destroying page:", error);
1249
+ }
1250
+ })
1251
+ );
1252
+ if (this.layout && this.layout.destroy) {
1253
+ try {
1254
+ await this.layout.destroy();
1255
+ } catch (error) {
1256
+ console.error("Error destroying layout:", error);
1257
+ }
1258
+ }
1259
+ this.pageCache.clear();
1260
+ this.pageClasses.clear();
1261
+ this.componentClasses.clear();
1262
+ this.modelClasses.clear();
1263
+ if (typeof window !== "undefined" && window.MOJO) {
1264
+ delete window.MOJO.router;
1265
+ }
1266
+ this.isStarted = false;
1267
+ console.log(`✨ ${this.name} destroyed`);
1268
+ }
1269
+ /**
1270
+ * Build page path with params and query
1271
+ */
1272
+ buildPagePath(page, params, query) {
1273
+ let path = page.route || `/${page.pageName.toLowerCase()}`;
1274
+ Object.keys(params).forEach((key) => {
1275
+ if (typeof params[key] === "string" || typeof params[key] === "number") {
1276
+ path = path.replace(`:${key}`, params[key]);
1277
+ }
1278
+ });
1279
+ if (query && Object.keys(query).length > 0) {
1280
+ const queryString = new URLSearchParams(query).toString();
1281
+ path += (path.includes("?") ? "&" : "?") + queryString;
1282
+ }
1283
+ return path;
1284
+ }
1285
+ /**
1286
+ * Static factory method
1287
+ */
1288
+ /**
1289
+ * Validate that default route has a registered page
1290
+ */
1291
+ validateDefaultRoute() {
1292
+ if (!this.pageClasses.has(this.defaultRoute)) {
1293
+ console.warn(`⚠️ Default route '${this.defaultRoute}' is not registered!`);
1294
+ console.warn(` Please register a page: app.registerPage('${this.defaultRoute}', YourPageClass);`);
1295
+ console.warn(` Or change default route: new WebApp({ defaultRoute: 'your-page' });`);
1296
+ } else {
1297
+ console.log(`✅ Default route '${this.defaultRoute}' is registered`);
1298
+ }
1299
+ }
1300
+ /**
1301
+ * Find any registered page to use as emergency fallback
1302
+ */
1303
+ findFallbackPage() {
1304
+ const systemPages = ["404", "error", "denied"];
1305
+ for (const [pageName] of this.pageClasses.entries()) {
1306
+ if (!systemPages.includes(pageName)) {
1307
+ return pageName;
1308
+ }
1309
+ }
1310
+ return null;
1311
+ }
1312
+ static create(config = {}) {
1313
+ return new WebApp(config);
1314
+ }
1315
+ /**
1316
+ * Initialize global plugin registry for extensions
1317
+ * Allows extensions to register components that core can use optionally
1318
+ */
1319
+ initPluginRegistry() {
1320
+ if (typeof window !== "undefined") {
1321
+ if (!window.MOJO) {
1322
+ window.MOJO = {};
1323
+ }
1324
+ if (!window.MOJO.plugins) {
1325
+ window.MOJO.plugins = {};
1326
+ }
1327
+ window.MOJO.app = this;
1328
+ }
1329
+ }
1330
+ /**
1331
+ * Register a plugin component
1332
+ * @param {string} name - Plugin component name (e.g., 'ImageCropView')
1333
+ * @param {*} component - The component/class to register
1334
+ */
1335
+ static registerPlugin(name, component) {
1336
+ if (typeof window !== "undefined") {
1337
+ if (!window.MOJO) {
1338
+ window.MOJO = {};
1339
+ }
1340
+ if (!window.MOJO.plugins) {
1341
+ window.MOJO.plugins = {};
1342
+ }
1343
+ window.MOJO.plugins[name] = component;
1344
+ console.debug(`MOJO Plugin registered: ${name}`);
1345
+ }
1346
+ }
1347
+ }
2
1348
  class Dialog extends View {
3
1349
  static _openDialogs = [];
4
1350
  static _baseZIndex = {
@@ -1370,7 +2716,7 @@ class Dialog extends View {
1370
2716
  closeText = "Close",
1371
2717
  ...dialogOptions
1372
2718
  } = options;
1373
- const DataView = (await import("./DataView-OUqaLmGB.js")).default;
2719
+ const DataView = (await import("./DataView-CWejLV3B.js")).default;
1374
2720
  const dataView = new DataView({
1375
2721
  data,
1376
2722
  model,
@@ -1430,7 +2776,15 @@ class Dialog extends View {
1430
2776
  }
1431
2777
  Dialog.showConfirm = Dialog.confirm;
1432
2778
  Dialog.showError = Dialog.alert;
2779
+ const Dialog$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2780
+ __proto__: null,
2781
+ default: Dialog
2782
+ }, Symbol.toStringTag, { value: "Module" }));
1433
2783
  export {
1434
- Dialog as default
2784
+ Dialog as D,
2785
+ EventBus as E,
2786
+ Router as R,
2787
+ WebApp as W,
2788
+ Dialog$1 as a
1435
2789
  };
1436
- //# sourceMappingURL=Dialog-BiVgKzSK.js.map
2790
+ //# sourceMappingURL=Dialog-BcJG5Vta.js.map