web-mojo 2.2.57 → 2.2.59

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 (119) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +1 -10105
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.es.js +1 -588
  7. package/dist/auth.es.js.map +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -571
  10. package/dist/charts.es.js.map +1 -1
  11. package/dist/chunks/ChatView-D4A9rIX3.js +2 -0
  12. package/dist/chunks/ChatView-D4A9rIX3.js.map +1 -0
  13. package/dist/chunks/ChatView-nxaq8aIo.js +2 -0
  14. package/dist/chunks/ChatView-nxaq8aIo.js.map +1 -0
  15. package/dist/chunks/Collection-1sPoIFvQ.js +2 -0
  16. package/dist/chunks/{Collection-DaiL0uGl.js.map → Collection-1sPoIFvQ.js.map} +1 -1
  17. package/dist/chunks/{Collection-CxbNKOas.js → Collection-DSBRXpwK.js} +2 -2
  18. package/dist/chunks/{Collection-CxbNKOas.js.map → Collection-DSBRXpwK.js.map} +1 -1
  19. package/dist/chunks/{ContextMenu-ClwHEbbD.js → ContextMenu-BWy7WqF4.js} +2 -2
  20. package/dist/chunks/{ContextMenu-ClwHEbbD.js.map → ContextMenu-BWy7WqF4.js.map} +1 -1
  21. package/dist/chunks/ContextMenu-BvniQz-N.js +3 -0
  22. package/dist/chunks/{ContextMenu-sgvgSACY.js.map → ContextMenu-BvniQz-N.js.map} +1 -1
  23. package/dist/chunks/DataView--nUWtq6r.js +2 -0
  24. package/dist/chunks/{DataView-Dzo0jbs2.js.map → DataView--nUWtq6r.js.map} +1 -1
  25. package/dist/chunks/{DataView-1xh3GFeC.js → DataView-CK3Z0TJH.js} +2 -2
  26. package/dist/chunks/{DataView-1xh3GFeC.js.map → DataView-CK3Z0TJH.js.map} +1 -1
  27. package/dist/chunks/Dialog-BcgSR01Z.js +2 -0
  28. package/dist/chunks/{Dialog-DOGDalUq.js.map → Dialog-BcgSR01Z.js.map} +1 -1
  29. package/dist/chunks/{Dialog-CQlTDhZS.js → Dialog-DwCTFV6O.js} +2 -2
  30. package/dist/chunks/{Dialog-CQlTDhZS.js.map → Dialog-DwCTFV6O.js.map} +1 -1
  31. package/dist/chunks/FormPlugins-DvQ-G5J5.js +2 -0
  32. package/dist/chunks/{FormPlugins-DY6e88YT.js.map → FormPlugins-DvQ-G5J5.js.map} +1 -1
  33. package/dist/chunks/{FormView-DaKA4Sys.js → FormView-CRmEReTC.js} +3 -3
  34. package/dist/chunks/{FormView-DaKA4Sys.js.map → FormView-CRmEReTC.js.map} +1 -1
  35. package/dist/chunks/FormView-OLA7t-yv.js +3 -0
  36. package/dist/chunks/{FormView-Dz3mYasQ.js.map → FormView-OLA7t-yv.js.map} +1 -1
  37. package/dist/chunks/ListView-6JQ6tRXs.js +2 -0
  38. package/dist/chunks/{ListView-X5w5jf51.js.map → ListView-6JQ6tRXs.js.map} +1 -1
  39. package/dist/chunks/{ListView-CDzKIpd8.js → ListView-DVStKiMi.js} +2 -2
  40. package/dist/chunks/{ListView-CDzKIpd8.js.map → ListView-DVStKiMi.js.map} +1 -1
  41. package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js → MetricsCountryMapView-CnAEbUw_.js} +2 -2
  42. package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js.map → MetricsCountryMapView-CnAEbUw_.js.map} +1 -1
  43. package/dist/chunks/MetricsCountryMapView-J067qrrt.js +2 -0
  44. package/dist/chunks/{MetricsCountryMapView-B2xz6zUw.js.map → MetricsCountryMapView-J067qrrt.js.map} +1 -1
  45. package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js → MetricsMiniChartWidget-BeD1slGs.js} +2 -2
  46. package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js.map → MetricsMiniChartWidget-BeD1slGs.js.map} +1 -1
  47. package/dist/chunks/MetricsMiniChartWidget-x2gFjHOU.js +2 -0
  48. package/dist/chunks/{MetricsMiniChartWidget-DvKd7Qrk.js.map → MetricsMiniChartWidget-x2gFjHOU.js.map} +1 -1
  49. package/dist/chunks/PDFViewer-CsyKn-gh.js +2 -0
  50. package/dist/chunks/{PDFViewer-EJ9cOfPF.js.map → PDFViewer-CsyKn-gh.js.map} +1 -1
  51. package/dist/chunks/{PDFViewer-ofMGdSaj.js → PDFViewer-DSa4BZCm.js} +2 -2
  52. package/dist/chunks/{PDFViewer-ofMGdSaj.js.map → PDFViewer-DSa4BZCm.js.map} +1 -1
  53. package/dist/chunks/Rest-DHbszkuP.js +2 -0
  54. package/dist/chunks/Rest-DHbszkuP.js.map +1 -0
  55. package/dist/chunks/Rest-Ds9e8tN8.js +2 -0
  56. package/dist/chunks/Rest-Ds9e8tN8.js.map +1 -0
  57. package/dist/chunks/TokenManager-D6SjKgPZ.js +2 -0
  58. package/dist/chunks/{TokenManager-DoN9e6q6.js.map → TokenManager-D6SjKgPZ.js.map} +1 -1
  59. package/dist/chunks/{TokenManager-Gqvj7SDX.js → TokenManager-REbha1Le.js} +2 -2
  60. package/dist/chunks/{TokenManager-Gqvj7SDX.js.map → TokenManager-REbha1Le.js.map} +1 -1
  61. package/dist/chunks/WebApp-CULZpO_0.js +2 -0
  62. package/dist/chunks/{WebApp-6qvqmOts.js.map → WebApp-CULZpO_0.js.map} +1 -1
  63. package/dist/chunks/{WebApp-_dgpwtFw.js → WebApp-DovLtA60.js} +2 -2
  64. package/dist/chunks/{WebApp-_dgpwtFw.js.map → WebApp-DovLtA60.js.map} +1 -1
  65. package/dist/chunks/WebSocketClient-B-wc3mez.js +2 -0
  66. package/dist/chunks/{WebSocketClient-DG2olXpH.js.map → WebSocketClient-B-wc3mez.js.map} +1 -1
  67. package/dist/chunks/{WebSocketClient-MFkFlSue.js → WebSocketClient-BdZ9QYll.js} +2 -2
  68. package/dist/chunks/{WebSocketClient-MFkFlSue.js.map → WebSocketClient-BdZ9QYll.js.map} +1 -1
  69. package/dist/chunks/version-C3dnl1bg.js +2 -0
  70. package/dist/chunks/version-C3dnl1bg.js.map +1 -0
  71. package/dist/chunks/{version-BVADfTA5.js → version-ioN546cp.js} +2 -2
  72. package/dist/chunks/{version-BVADfTA5.js.map → version-ioN546cp.js.map} +1 -1
  73. package/dist/css/web-mojo.css +1 -1
  74. package/dist/docit.cjs.js +1 -1
  75. package/dist/docit.es.js +1 -957
  76. package/dist/docit.es.js.map +1 -1
  77. package/dist/index.cjs.js +1 -1
  78. package/dist/index.es.js +1 -3252
  79. package/dist/index.es.js.map +1 -1
  80. package/dist/lightbox.cjs.js +1 -1
  81. package/dist/lightbox.es.js +1 -3737
  82. package/dist/lightbox.es.js.map +1 -1
  83. package/dist/loader.umd.js +2 -2
  84. package/dist/map.cjs.js +1 -1
  85. package/dist/map.es.js +1 -1032
  86. package/dist/map.es.js.map +1 -1
  87. package/dist/mojo-auth.es.js +338 -0
  88. package/dist/mojo-auth.umd.js +1 -0
  89. package/dist/timeline.cjs.js +1 -1
  90. package/dist/timeline.es.js +1 -224
  91. package/dist/timeline.es.js.map +1 -1
  92. package/dist/web-mojo.lite.iife.js +14 -3
  93. package/dist/web-mojo.lite.iife.js.map +1 -1
  94. package/dist/web-mojo.lite.iife.min.js +6 -6
  95. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  96. package/package.json +2 -2
  97. package/dist/chunks/ChatView-9k6xBWXk.js +0 -7632
  98. package/dist/chunks/ChatView-9k6xBWXk.js.map +0 -1
  99. package/dist/chunks/ChatView-CdtuCDYm.js +0 -2
  100. package/dist/chunks/ChatView-CdtuCDYm.js.map +0 -1
  101. package/dist/chunks/Collection-DaiL0uGl.js +0 -1014
  102. package/dist/chunks/ContextMenu-sgvgSACY.js +0 -1535
  103. package/dist/chunks/DataView-Dzo0jbs2.js +0 -862
  104. package/dist/chunks/Dialog-DOGDalUq.js +0 -1579
  105. package/dist/chunks/FormPlugins-DY6e88YT.js +0 -124
  106. package/dist/chunks/FormView-Dz3mYasQ.js +0 -8636
  107. package/dist/chunks/ListView-X5w5jf51.js +0 -495
  108. package/dist/chunks/MetricsCountryMapView-B2xz6zUw.js +0 -1054
  109. package/dist/chunks/MetricsMiniChartWidget-DvKd7Qrk.js +0 -3283
  110. package/dist/chunks/PDFViewer-EJ9cOfPF.js +0 -946
  111. package/dist/chunks/Rest-CgSjfMaU.js +0 -2
  112. package/dist/chunks/Rest-CgSjfMaU.js.map +0 -1
  113. package/dist/chunks/Rest-W-sPfGh9.js +0 -4375
  114. package/dist/chunks/Rest-W-sPfGh9.js.map +0 -1
  115. package/dist/chunks/TokenManager-DoN9e6q6.js +0 -1423
  116. package/dist/chunks/WebApp-6qvqmOts.js +0 -1386
  117. package/dist/chunks/WebSocketClient-DG2olXpH.js +0 -209
  118. package/dist/chunks/version-OyPGnx30.js +0 -38
  119. package/dist/chunks/version-OyPGnx30.js.map +0 -1
@@ -1,1579 +0,0 @@
1
- import { V as View } from "./Rest-W-sPfGh9.js";
2
- class Dialog extends View {
3
- static _openDialogs = [];
4
- static _baseZIndex = {
5
- backdrop: 1050,
6
- modal: 1055
7
- };
8
- /**
9
- * Check if there's a fullscreen table and return appropriate z-index
10
- */
11
- static getFullscreenAwareZIndex() {
12
- const fullscreenTable = document.querySelector(".table-fullscreen");
13
- if (fullscreenTable) {
14
- return {
15
- backdrop: 10040,
16
- modal: 10050
17
- };
18
- }
19
- return this._baseZIndex;
20
- }
21
- static _busyIndicator = null;
22
- static _busyCounter = 0;
23
- static _busyTimeout = null;
24
- /**
25
- * Fix all backdrop stacking - ensures proper layering of all open modals
26
- */
27
- static fixAllBackdropStacking() {
28
- const backdrops = document.querySelectorAll(".modal-backdrop");
29
- const openDialogs = Dialog._openDialogs;
30
- if (backdrops.length === 0 || openDialogs.length === 0) return;
31
- const sortedDialogs = [...openDialogs].sort(
32
- (a, b) => (a._dialogZIndex || 0) - (b._dialogZIndex || 0)
33
- );
34
- backdrops.forEach((backdrop, index) => {
35
- if (index < sortedDialogs.length) {
36
- const dialog = sortedDialogs[index];
37
- const backdropZIndex = dialog._dialogZIndex - 5;
38
- backdrop.style.zIndex = backdropZIndex;
39
- const fullscreenElement = document.querySelector(".table-fullscreen");
40
- const targetContainer = fullscreenElement || document.body;
41
- if (backdrop.parentNode !== targetContainer) {
42
- targetContainer.appendChild(backdrop);
43
- }
44
- }
45
- });
46
- }
47
- /**
48
- * Update backdrop stacking for all open dialogs
49
- */
50
- static updateAllBackdropStacking() {
51
- Dialog.fixAllBackdropStacking();
52
- }
53
- /**
54
- * Shows a full-screen busy indicator.
55
- * Manages a counter for nested calls, only showing one indicator.
56
- * @param {object} options - Options { timeout, message }
57
- */
58
- static showBusy(options = {}) {
59
- const { timeout = 3e4, message = "Loading..." } = options;
60
- this._busyCounter++;
61
- if (this._busyCounter === 1) {
62
- if (this._busyTimeout) {
63
- clearTimeout(this._busyTimeout);
64
- }
65
- if (!this._busyIndicator) {
66
- const zIndexBase = this.getFullscreenAwareZIndex();
67
- const busyZIndex = zIndexBase.modal + 1e3;
68
- this._busyIndicator = document.createElement("div");
69
- this._busyIndicator.className = "mojo-busy-indicator";
70
- this._busyIndicator.innerHTML = `
71
- <div class="mojo-busy-spinner">
72
- <div class="spinner-border text-light" role="status">
73
- <span class="visually-hidden">Loading...</span>
74
- </div>
75
- <p class="mojo-busy-message mt-3 text-light">${message}</p>
76
- </div>
77
- <style>
78
- .mojo-busy-indicator {
79
- position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
80
- background-color: rgba(0, 0, 0, 0.5); z-index: ${busyZIndex};
81
- display: flex; align-items: center; justify-content: center;
82
- opacity: 0; transition: opacity 0.15s linear;
83
- }
84
- .mojo-busy-indicator.show { opacity: 1; }
85
- .mojo-busy-spinner .spinner-border { width: 3rem; height: 3rem; }
86
- </style>
87
- `;
88
- document.body.appendChild(this._busyIndicator);
89
- }
90
- const msgElement = this._busyIndicator.querySelector(".mojo-busy-message");
91
- if (msgElement) msgElement.textContent = message;
92
- setTimeout(() => this._busyIndicator.classList.add("show"), 10);
93
- this._busyTimeout = setTimeout(() => {
94
- console.error("Busy indicator timed out.");
95
- this.hideBusy(true);
96
- this.alert({
97
- title: "Operation Timed Out",
98
- message: "The operation took too long. Please check your connection and try again.",
99
- type: "danger"
100
- });
101
- }, timeout);
102
- }
103
- }
104
- /**
105
- * Hides the full-screen busy indicator.
106
- * Decrements the counter and only hides when the counter reaches zero.
107
- * @param {boolean} force - If true, forces the indicator to hide immediately.
108
- */
109
- static hideBusy(force = false) {
110
- if (force) {
111
- this._busyCounter = 0;
112
- } else {
113
- this._busyCounter--;
114
- }
115
- if (this._busyCounter <= 0) {
116
- this._busyCounter = 0;
117
- if (this._busyTimeout) {
118
- clearTimeout(this._busyTimeout);
119
- this._busyTimeout = null;
120
- }
121
- if (this._busyIndicator) {
122
- this._busyIndicator.classList.remove("show");
123
- setTimeout(() => {
124
- if (this._busyIndicator && this._busyCounter === 0) {
125
- this._busyIndicator.remove();
126
- this._busyIndicator = null;
127
- }
128
- }, 150);
129
- }
130
- }
131
- }
132
- constructor(options = {}) {
133
- const modalId = options.id || `modal-${Date.now()}`;
134
- super({
135
- ...options,
136
- id: modalId,
137
- // Pass the ID to parent constructor
138
- tagName: "div",
139
- className: `modal ${options.fade !== false ? "fade" : ""} ${options.className || ""}`,
140
- attributes: {
141
- tabindex: "-1",
142
- "aria-hidden": "true",
143
- "aria-labelledby": options.labelledBy || `${modalId}-label`,
144
- "aria-describedby": options.describedBy || null,
145
- ...options.attributes
146
- }
147
- });
148
- this.modalId = modalId;
149
- this.title = options.title || "";
150
- this.titleId = `${this.modalId}-label`;
151
- this.size = options.size || "";
152
- this.centered = options.centered !== void 0 ? options.centered : false;
153
- this.scrollable = options.scrollable !== void 0 ? options.scrollable : false;
154
- this.autoSize = options.autoSize || options.size === "auto";
155
- this.backdrop = options.backdrop !== void 0 ? options.backdrop : true;
156
- this.keyboard = options.keyboard !== void 0 ? options.keyboard : true;
157
- this.focus = options.focus !== void 0 ? options.focus : true;
158
- this.header = options.header !== void 0 ? options.header : true;
159
- this.headerContent = options.headerContent || null;
160
- this.headerView = null;
161
- this.closeButton = options.closeButton !== void 0 ? options.closeButton : true;
162
- this.contextMenu = options.contextMenu || null;
163
- this._processHeaderContent(this.headerContent);
164
- this.body = options.body ?? options.view ?? options.message ?? options.content ?? "";
165
- this.bodyView = null;
166
- this.bodyClass = options.bodyClass || "";
167
- this.noBodyPadding = options.noBodyPadding || false;
168
- this.minWidth = options.minWidth || 300;
169
- this.minHeight = options.minHeight || 200;
170
- if (options.maxHeight) this.maxHeight = options.maxHeight;
171
- this.maxWidthPercent = options.maxWidthPercent || 0.9;
172
- this.maxHeightPercent = options.maxHeightPercent || 0.8;
173
- this._processBodyContent(this.body);
174
- this.footer = options.footer || null;
175
- this.footerView = null;
176
- this.footerClass = options.footerClass || "";
177
- this._processFooterContent(this.footer);
178
- this.buttons = options.buttons || null;
179
- this.onShow = options.onShow || null;
180
- this.onShown = options.onShown || null;
181
- this.onHide = options.onHide || null;
182
- this.onHidden = options.onHidden || null;
183
- this.onHidePrevented = options.onHidePrevented || null;
184
- this.autoShow = options.autoShow !== void 0 ? options.autoShow : false;
185
- this.modal = null;
186
- this.relatedTarget = options.relatedTarget || null;
187
- }
188
- /**
189
- * Process body content to detect and handle View instances
190
- */
191
- _processBodyContent(body) {
192
- if (body instanceof View || body && typeof body === "object" && typeof body.render === "function") {
193
- this.bodyView = body;
194
- this.body = "";
195
- this.addChild(this.bodyView);
196
- } else if (typeof body === "function") {
197
- try {
198
- const result = body();
199
- if (result instanceof View) {
200
- this.bodyView = result;
201
- this.body = "";
202
- this.addChild(this.bodyView);
203
- } else if (result instanceof Promise) {
204
- this.bodyPromise = result;
205
- this.body = '<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>';
206
- } else {
207
- this.body = result;
208
- }
209
- } catch (error) {
210
- console.error("Error processing body function:", error);
211
- this.body = body;
212
- }
213
- } else {
214
- this.body = body;
215
- }
216
- }
217
- /**
218
- * Process header content to detect and handle View instances
219
- */
220
- _processHeaderContent(headerContent) {
221
- if (headerContent instanceof View) {
222
- this.headerView = headerContent;
223
- this.headerContent = null;
224
- this.addChild(this.headerView);
225
- } else if (typeof headerContent === "function") {
226
- try {
227
- const result = headerContent();
228
- if (result instanceof View) {
229
- this.headerView = result;
230
- this.headerContent = null;
231
- this.addChild(this.headerView);
232
- } else if (result instanceof Promise) {
233
- this.headerPromise = result;
234
- this.headerContent = '<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>';
235
- } else {
236
- this.headerContent = result;
237
- }
238
- } catch (error) {
239
- console.error("Error processing headerContent function:", error);
240
- this.headerContent = headerContent;
241
- }
242
- } else {
243
- this.headerContent = headerContent;
244
- }
245
- }
246
- /**
247
- * Process footer content to detect and handle View instances
248
- */
249
- _processFooterContent(footer) {
250
- if (footer instanceof View) {
251
- this.footerView = footer;
252
- this.footer = null;
253
- this.addChild(this.footerView);
254
- } else if (typeof footer === "function") {
255
- try {
256
- const result = footer();
257
- if (result instanceof View) {
258
- this.footerView = result;
259
- this.footer = null;
260
- this.addChild(this.footerView);
261
- } else if (result instanceof Promise) {
262
- this.footerPromise = result;
263
- this.footer = '<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>';
264
- } else {
265
- this.footer = result;
266
- }
267
- } catch (error) {
268
- console.error("Error processing footer function:", error);
269
- this.footer = footer;
270
- }
271
- } else {
272
- this.footer = footer;
273
- }
274
- }
275
- /**
276
- * Get dialog template with all Bootstrap 5 features
277
- */
278
- async getTemplate() {
279
- const dialogClasses = ["modal-dialog"];
280
- if (this.size && this.size !== "auto") {
281
- if (this.size.startsWith("fullscreen")) {
282
- dialogClasses.push(`modal-${this.size}`);
283
- } else if (["sm", "lg", "xl", "xxl"].includes(this.size)) {
284
- dialogClasses.push(`modal-${this.size}`);
285
- if (["lg", "xl", "xxl"].includes(this.size)) {
286
- dialogClasses.push("modal-fullscreen-sm-down");
287
- }
288
- }
289
- }
290
- if (this.centered) {
291
- dialogClasses.push("modal-dialog-centered");
292
- }
293
- if (this.scrollable) {
294
- if (!this.maxHeight) {
295
- dialogClasses.push("modal-dialog-scrollable");
296
- } else {
297
- dialogClasses.push("overflow-hidden");
298
- }
299
- }
300
- return `
301
- <div class="${dialogClasses.join(" ")}">
302
- <div class="modal-content">
303
- ${await this.buildHeader()}
304
- ${await this.buildBody()}
305
- ${await this.buildFooter()}
306
- </div>
307
- </div>
308
- `;
309
- }
310
- /**
311
- * Build modal header
312
- */
313
- async buildHeader() {
314
- if (!this.header) {
315
- return "";
316
- }
317
- if (this.headerView) {
318
- this.headerView.replaceById = true;
319
- return `<div class="modal-header" data-view-container="header">
320
- <!-- View will be mounted here -->
321
- <div id="${this.headerView.id}"></div>
322
- </div>`;
323
- }
324
- if (this.headerContent) {
325
- return `<div class="modal-header">${this.headerContent}</div>`;
326
- }
327
- let headerActions = "";
328
- if (this.contextMenu && this.contextMenu.items && this.contextMenu.items.length > 0) {
329
- headerActions = await this.buildContextMenu();
330
- } else if (this.closeButton) {
331
- headerActions = '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>';
332
- }
333
- return `
334
- <div class="modal-header">
335
- ${this.title ? `<h5 class="modal-title" id="${this.titleId}">${this.title}</h5>` : ""}
336
- ${headerActions}
337
- </div>
338
- `;
339
- }
340
- async buildContextMenu() {
341
- const menuItems = await this.filterContextMenuItems();
342
- if (menuItems.length === 0) {
343
- return this.closeButton ? '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>' : "";
344
- }
345
- const triggerIcon = this.contextMenu.icon || "bi-three-dots-vertical";
346
- const buttonClass = this.contextMenu.buttonClass || "btn btn-link p-1 mojo-modal-context-menu-btn";
347
- const menuItemsHtml = menuItems.map((item) => {
348
- if (item.type === "divider") {
349
- return '<li><hr class="dropdown-divider"></li>';
350
- }
351
- const icon = item.icon ? `<i class="${item.icon} me-2"></i>` : "";
352
- const label = item.label || "";
353
- if (item.href) {
354
- return `<li><a class="dropdown-item" href="${item.href}"${item.target ? ` target="${item.target}"` : ""}>${icon}${label}</a></li>`;
355
- } else if (item.action) {
356
- const dataAttrs = Object.keys(item).filter((key) => key.startsWith("data-")).map((key) => `${key}="${item[key]}"`).join(" ");
357
- return `<li><a class="dropdown-item" data-action="${item.action}" ${dataAttrs}>${icon}${label}</a></li>`;
358
- }
359
- return "";
360
- }).join("");
361
- return `
362
- <div class="dropdown">
363
- <button class="${buttonClass}" type="button" data-bs-toggle="dropdown" aria-expanded="false">
364
- <i class="${triggerIcon}"></i>
365
- </button>
366
- <ul class="dropdown-menu dropdown-menu-end">
367
- ${menuItemsHtml}
368
- </ul>
369
- </div>
370
- `;
371
- }
372
- async filterContextMenuItems() {
373
- if (!this.contextMenu || !this.contextMenu.items) {
374
- return [];
375
- }
376
- const filteredItems = [];
377
- for (const item of this.contextMenu.items) {
378
- if (item.type === "divider") {
379
- filteredItems.push(item);
380
- continue;
381
- }
382
- if (item.permissions) {
383
- try {
384
- const app = this.getApp?.();
385
- let user = null;
386
- if (app) {
387
- user = app.activeUser || app.getState?.("activeUser");
388
- }
389
- if (!user && typeof window !== "undefined" && window.getApp) {
390
- try {
391
- const globalApp = window.getApp();
392
- user = globalApp?.activeUser;
393
- } catch (e) {
394
- }
395
- }
396
- if (user && user.hasPermission) {
397
- if (!user.hasPermission(item.permissions)) {
398
- continue;
399
- }
400
- } else {
401
- continue;
402
- }
403
- } catch (error) {
404
- console.warn("Error checking permissions for context menu item:", error);
405
- continue;
406
- }
407
- }
408
- filteredItems.push(item);
409
- }
410
- return filteredItems;
411
- }
412
- /**
413
- * Build modal body
414
- */
415
- async buildBody() {
416
- if (this.bodyView) {
417
- this.bodyView.replaceById = true;
418
- const bodyClass2 = this.noBodyPadding ? `modal-body p-0 ${this.bodyClass}` : `modal-body ${this.bodyClass}`;
419
- return `<div class="${bodyClass2}" data-view-container="body">
420
- <!-- View will be mounted here -->
421
- <div id="${this.bodyView.id}"></div>
422
- </div>`;
423
- }
424
- if (!this.body && this.body !== "") {
425
- return "";
426
- }
427
- const bodyClass = this.noBodyPadding ? `modal-body p-0 ${this.bodyClass}` : `modal-body ${this.bodyClass}`;
428
- return `
429
- <div class="${bodyClass}">
430
- ${this.body}
431
- </div>
432
- `;
433
- }
434
- /**
435
- * Build modal footer
436
- */
437
- async buildFooter() {
438
- if (this.footerView) {
439
- return `<div class="modal-footer ${this.footerClass}" data-view-container="footer"></div>`;
440
- }
441
- if (this.footer !== null && typeof this.footer === "string") {
442
- return `<div class="modal-footer ${this.footerClass}">${this.footer}</div>`;
443
- }
444
- if (this.buttons && this.buttons.length > 0) {
445
- const buttonsHtml = this.buttons.map((btn) => {
446
- const dismissAttr = btn.dismiss ? 'data-bs-dismiss="modal"' : "";
447
- const actionAttr = btn.action ? `data-action="${btn.action}"` : "";
448
- const idAttr = btn.id ? `id="${btn.id}"` : "";
449
- const disabledAttr = btn.disabled ? "disabled" : "";
450
- return `
451
- <button type="${btn.type || "button"}"
452
- class="btn ${btn.class || "btn-secondary"}"
453
- ${idAttr}
454
- ${dismissAttr}
455
- ${actionAttr}
456
- ${disabledAttr}>
457
- ${btn.icon ? `<i class="bi ${btn.icon} me-1"></i>` : ""}
458
- ${btn.text || "Button"}
459
- </button>
460
- `;
461
- }).join("");
462
- return `<div class="modal-footer ${this.footerClass}">${buttonsHtml}</div>`;
463
- }
464
- return "";
465
- }
466
- /**
467
- * Override mount to not require a container for dialogs
468
- * Dialogs are appended to body or fullscreen element
469
- */
470
- async mount(_container = null) {
471
- if (this.mounted || this.destroyed) {
472
- return;
473
- }
474
- if (!this.element) {
475
- throw new Error("Cannot mount dialog without element");
476
- }
477
- await this.onBeforeMount();
478
- const fullscreenElement = document.querySelector(".table-fullscreen");
479
- const targetContainer = fullscreenElement || document.body;
480
- targetContainer.appendChild(this.element);
481
- this.bindEvents();
482
- this.mounted = true;
483
- await this.onAfterMount();
484
- this.emit("mounted", { view: this });
485
- return this;
486
- }
487
- /**
488
- * After render - prepare for View instances and apply syntax highlighting
489
- */
490
- async onAfterRender() {
491
- await super.onAfterRender();
492
- if (window.Prism && this.element) {
493
- const codeBlocks = this.element.querySelectorAll("pre code");
494
- if (codeBlocks.length > 0) {
495
- window.Prism.highlightAllUnder(this.element);
496
- }
497
- }
498
- if (this.autoSize) {
499
- this.setupAutoSizing();
500
- } else if (this.maxHeight) {
501
- const modalBody = this.element.querySelector(".modal-body");
502
- if (modalBody) {
503
- modalBody.style.maxHeight = `${this.maxHeight}px`;
504
- }
505
- }
506
- }
507
- /**
508
- * After mount - initialize Bootstrap modal and mount child views
509
- */
510
- async onAfterMount() {
511
- await super.onAfterMount();
512
- if (typeof window !== "undefined" && window.bootstrap && window.bootstrap.Modal) {
513
- if (this.backdrop === "static") {
514
- this.element.setAttribute("data-bs-backdrop", "static");
515
- }
516
- if (!this.keyboard) {
517
- this.element.setAttribute("data-bs-keyboard", "false");
518
- }
519
- this.modal = new window.bootstrap.Modal(this.element, {
520
- backdrop: this.backdrop,
521
- keyboard: this.keyboard,
522
- focus: this.focus
523
- });
524
- this.bindBootstrapEvents();
525
- if (this.autoShow) {
526
- this.show(this.relatedTarget);
527
- }
528
- }
529
- }
530
- /**
531
- * Setup auto-sizing - wait for modal animation to complete
532
- */
533
- setupAutoSizing() {
534
- if (!this.element) return;
535
- this.element.addEventListener("shown.bs.modal", () => {
536
- this.applyAutoSizing();
537
- }, { once: true });
538
- setTimeout(() => {
539
- if (this.isShown()) {
540
- this.applyAutoSizing();
541
- }
542
- }, 100);
543
- }
544
- /**
545
- * Apply auto-sizing based on content dimensions
546
- */
547
- applyAutoSizing() {
548
- if (!this.element) return;
549
- try {
550
- const modalDialog = this.element.querySelector(".modal-dialog");
551
- const modalContent = this.element.querySelector(".modal-content");
552
- const modalBody = this.element.querySelector(".modal-body");
553
- if (!modalDialog || !modalContent || !modalBody) {
554
- console.warn("Dialog auto-sizing: Required elements not found");
555
- return;
556
- }
557
- if (this.bodyView && !this.bodyView.element) {
558
- setTimeout(() => this.applyAutoSizing(), 50);
559
- return;
560
- }
561
- const originalStyles = {
562
- dialogMaxWidth: modalDialog.style.maxWidth,
563
- dialogWidth: modalDialog.style.width,
564
- contentWidth: modalContent.style.width,
565
- contentMaxHeight: modalContent.style.maxHeight,
566
- hadScrollableClass: modalDialog.classList.contains("modal-dialog-scrollable")
567
- };
568
- modalDialog.style.maxWidth = "none";
569
- modalDialog.style.width = "auto";
570
- modalContent.style.width = "auto";
571
- modalContent.style.maxHeight = "none";
572
- modalContent.offsetHeight;
573
- const contentRect = modalContent.getBoundingClientRect();
574
- const viewportMargin = 40;
575
- const maxWidth = Math.min(
576
- window.innerWidth * this.maxWidthPercent,
577
- window.innerWidth - viewportMargin
578
- );
579
- let maxHeight = Math.min(
580
- window.innerHeight * this.maxHeightPercent,
581
- window.innerHeight - viewportMargin
582
- );
583
- let optimalWidth = Math.max(this.minWidth, Math.ceil(contentRect.width + 20));
584
- let optimalHeight = Math.max(this.minHeight, Math.ceil(contentRect.height));
585
- if (this.maxHeight) {
586
- maxHeight = Math.min(this.maxHeight, maxHeight);
587
- modalDialog.style.maxHeight = `${maxHeight}px`;
588
- }
589
- optimalWidth = Math.min(optimalWidth, maxWidth);
590
- const heightExceedsMax = contentRect.height > maxHeight;
591
- modalDialog.style.maxWidth = `${optimalWidth}px`;
592
- modalDialog.style.width = `${optimalWidth}px`;
593
- if (heightExceedsMax) {
594
- if (!modalDialog.classList.contains("modal-dialog-scrollable")) {
595
- modalDialog.classList.add("modal-dialog-scrollable");
596
- }
597
- modalContent.style.maxHeight = `${maxHeight}px`;
598
- optimalHeight = maxHeight;
599
- }
600
- this.autoSizedWidth = optimalWidth;
601
- this.autoSizedHeight = optimalHeight;
602
- this._originalStyles = originalStyles;
603
- } catch (error) {
604
- console.error("Error in dialog auto-sizing:", error);
605
- this.element.querySelector(".modal-dialog").style.maxWidth = "";
606
- }
607
- }
608
- /**
609
- * Reset auto-sizing and restore original modal dimensions
610
- */
611
- resetAutoSizing() {
612
- if (!this.autoSize || !this._originalStyles || !this.element) return;
613
- try {
614
- const modalDialog = this.element.querySelector(".modal-dialog");
615
- const modalContent = this.element.querySelector(".modal-content");
616
- const modalBody = this.element.querySelector(".modal-body");
617
- if (modalDialog && modalContent && modalBody) {
618
- modalDialog.style.maxWidth = this._originalStyles.dialogMaxWidth || "";
619
- modalDialog.style.width = this._originalStyles.dialogWidth || "";
620
- modalContent.style.width = this._originalStyles.contentWidth || "";
621
- modalContent.style.maxHeight = this._originalStyles.contentMaxHeight || "";
622
- if (!this._originalStyles.hadScrollableClass && modalDialog.classList.contains("modal-dialog-scrollable")) {
623
- modalDialog.classList.remove("modal-dialog-scrollable");
624
- }
625
- delete this.autoSizedWidth;
626
- delete this.autoSizedHeight;
627
- delete this._originalStyles;
628
- }
629
- } catch (error) {
630
- console.error("Error resetting dialog auto-sizing:", error);
631
- }
632
- }
633
- /**
634
- * Bind Bootstrap modal events
635
- */
636
- bindBootstrapEvents() {
637
- this.element.addEventListener("show.bs.modal", (e) => {
638
- const stackIndex = Dialog._openDialogs.length;
639
- const zIndexBase = Dialog.getFullscreenAwareZIndex();
640
- const newZIndex = zIndexBase.modal + stackIndex * 20;
641
- this.element.style.zIndex = newZIndex;
642
- this._dialogZIndex = newZIndex;
643
- this._backdropZIndex = newZIndex - 10;
644
- Dialog._openDialogs.push(this);
645
- if (this.onShow) this.onShow(e);
646
- this.emit("show", {
647
- dialog: this,
648
- relatedTarget: e.relatedTarget
649
- });
650
- });
651
- this.element.addEventListener("shown.bs.modal", (e) => {
652
- setTimeout(() => {
653
- Dialog.fixAllBackdropStacking();
654
- }, 50);
655
- if (this.onShown) this.onShown(e);
656
- this.emit("shown", {
657
- dialog: this,
658
- relatedTarget: e.relatedTarget
659
- });
660
- if (this.focus) {
661
- const firstInput = this.element.querySelector('input:not([type="hidden"]), textarea, select');
662
- if (firstInput) {
663
- firstInput.focus();
664
- }
665
- }
666
- });
667
- this.element.addEventListener("hide.bs.modal", (e) => {
668
- const focusedElement = this.element.querySelector(":focus");
669
- if (focusedElement) {
670
- focusedElement.blur();
671
- }
672
- if (this.onHide) {
673
- const result = this.onHide(e);
674
- if (result === false) {
675
- e.preventDefault();
676
- return;
677
- }
678
- }
679
- this.emit("hide", { dialog: this });
680
- });
681
- this.element.addEventListener("hidden.bs.modal", (e) => {
682
- const index = Dialog._openDialogs.indexOf(this);
683
- if (index > -1) {
684
- Dialog._openDialogs.splice(index, 1);
685
- }
686
- if (Dialog._openDialogs.length > 0) {
687
- document.body.classList.add("modal-open");
688
- setTimeout(() => {
689
- Dialog.fixAllBackdropStacking();
690
- }, 50);
691
- }
692
- if (this.previousFocus && document.body.contains(this.previousFocus)) {
693
- this.previousFocus.focus();
694
- }
695
- if (this.onHidden) this.onHidden(e);
696
- this.emit("hidden", { dialog: this });
697
- });
698
- this.element.addEventListener("hidePrevented.bs.modal", (e) => {
699
- if (this.onHidePrevented) this.onHidePrevented(e);
700
- this.emit("hidePrevented", { dialog: this });
701
- });
702
- }
703
- /**
704
- * Show the dialog
705
- * @param {HTMLElement} relatedTarget - Optional element that triggered the modal
706
- */
707
- show(relatedTarget = null) {
708
- this.previousFocus = document.activeElement;
709
- window.lastDialog = this;
710
- if (this.modal) {
711
- this.modal.show(relatedTarget);
712
- }
713
- }
714
- /**
715
- * Hide the dialog
716
- */
717
- hide() {
718
- const focusedElement = this.element?.querySelector(":focus");
719
- if (focusedElement) {
720
- focusedElement.blur();
721
- }
722
- if (this.modal) {
723
- this.modal.hide();
724
- }
725
- }
726
- /**
727
- * Toggle the dialog
728
- * @param {HTMLElement} relatedTarget - Optional element that triggered the modal
729
- */
730
- toggle(relatedTarget = null) {
731
- if (this.modal) {
732
- this.modal.toggle(relatedTarget);
733
- }
734
- }
735
- /**
736
- * Destroy the dialog and clean up resources
737
- */
738
- async destroy() {
739
- if (this.modal) {
740
- const focusedElement = this.element?.querySelector(":focus");
741
- if (focusedElement) {
742
- focusedElement.blur();
743
- }
744
- this.modal.dispose();
745
- this.modal = null;
746
- }
747
- if (this.previousFocus && document.body.contains(this.previousFocus)) {
748
- this.previousFocus.focus();
749
- this.previousFocus = null;
750
- }
751
- if (this.autoSize) {
752
- this.resetAutoSizing();
753
- }
754
- await super.destroy();
755
- }
756
- /**
757
- * Handle dynamic height changes
758
- */
759
- handleUpdate() {
760
- if (this.modal) {
761
- this.modal.handleUpdate();
762
- }
763
- }
764
- /**
765
- * Update dialog content
766
- * @param {string|View} content - New content (string or View instance)
767
- */
768
- async setContent(content) {
769
- if (content instanceof View) {
770
- if (this.bodyView) {
771
- await this.bodyView.destroy();
772
- this.removeChild(this.bodyView);
773
- }
774
- this.bodyView = content;
775
- this.body = "";
776
- this.addChild(this.bodyView);
777
- const bodyEl = this.element?.querySelector(".modal-body");
778
- if (bodyEl) {
779
- bodyEl.innerHTML = "";
780
- await this.bodyView.render(bodyEl);
781
- }
782
- } else {
783
- this.body = content;
784
- const bodyEl = this.element?.querySelector(".modal-body");
785
- if (bodyEl) {
786
- bodyEl.innerHTML = content;
787
- }
788
- }
789
- this.handleUpdate();
790
- }
791
- /**
792
- * Update dialog title
793
- */
794
- setTitle(title) {
795
- this.title = title;
796
- const titleEl = this.element?.querySelector(".modal-title");
797
- if (titleEl) {
798
- titleEl.textContent = title;
799
- }
800
- }
801
- /**
802
- * Set loading state
803
- */
804
- setLoading(loading = true, message = "Loading...") {
805
- const bodyEl = this.element?.querySelector(".modal-body");
806
- if (bodyEl) {
807
- if (loading) {
808
- bodyEl.innerHTML = `
809
- <div class="text-center py-4">
810
- <div class="spinner-border text-primary mb-3" role="status">
811
- <span class="visually-hidden">Loading...</span>
812
- </div>
813
- <p>${message}</p>
814
- </div>
815
- `;
816
- } else if (this.bodyView) {
817
- bodyEl.replaceChildren(this.bodyView.element);
818
- }
819
- }
820
- }
821
- /**
822
- * Clean up
823
- */
824
- async onBeforeDestroy() {
825
- if (this.headerView) {
826
- await this.headerView.destroy();
827
- }
828
- if (this.bodyView) {
829
- await this.bodyView.destroy();
830
- }
831
- if (this.footerView) {
832
- await this.footerView.destroy();
833
- }
834
- await super.onBeforeDestroy();
835
- if (this.modal) {
836
- this.modal.dispose();
837
- this.modal = null;
838
- }
839
- }
840
- /**
841
- * Static method to show code in a dialog
842
- */
843
- static async showCode(options = {}) {
844
- const dialog = new Dialog({
845
- title: options.title || "Source Code",
846
- size: options.size || "lg",
847
- scrollable: true,
848
- body: Dialog.formatCode(options.code, options.language),
849
- buttons: [
850
- {
851
- text: "Copy to Clipboard",
852
- class: "btn-primary",
853
- icon: "bi-clipboard",
854
- action: "copy"
855
- },
856
- {
857
- text: "Close",
858
- class: "btn-secondary",
859
- dismiss: true
860
- }
861
- ]
862
- });
863
- dialog.on("action:copy", async () => {
864
- if (navigator.clipboard) {
865
- try {
866
- await navigator.clipboard.writeText(options.code);
867
- dialog.showCopySuccess();
868
- } catch (err) {
869
- console.error("Failed to copy:", err);
870
- }
871
- }
872
- });
873
- const fullscreenElement = document.querySelector(".table-fullscreen");
874
- const targetContainer = fullscreenElement || document.body;
875
- await dialog.render(true, targetContainer);
876
- if (window.Prism && dialog.element) {
877
- window.Prism.highlightAllUnder(dialog.element);
878
- }
879
- dialog.show();
880
- dialog.on("hidden", () => {
881
- dialog.destroy();
882
- dialog.element.remove();
883
- });
884
- return dialog;
885
- }
886
- /**
887
- * Format code for display with syntax highlighting support
888
- */
889
- static formatCode(code, language = "javascript") {
890
- let highlightedCode;
891
- if (window.Prism && window.Prism.languages[language]) {
892
- highlightedCode = window.Prism.highlight(code, window.Prism.languages[language], language);
893
- } else {
894
- highlightedCode = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
895
- }
896
- const prismClass = window.Prism ? `language-${language}` : "";
897
- const codeStyles = `
898
- max-height: 60vh;
899
- overflow-y: auto;
900
- background: #1e1e1e;
901
- color: #d4d4d4;
902
- padding: 1.25rem;
903
- border-radius: 0.5rem;
904
- margin: 0;
905
- font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', 'Monaco', monospace;
906
- font-size: 0.9rem;
907
- line-height: 1.6;
908
- border: 1px solid #2d2d30;
909
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
910
- `.replace(/\s+/g, " ").trim();
911
- return `
912
- <style>
913
- /* Custom Prism theme overrides for Dialog */
914
- .dialog-code-block .token.comment { color: #6a9955; }
915
- .dialog-code-block .token.string { color: #ce9178; }
916
- .dialog-code-block .token.keyword { color: #569cd6; }
917
- .dialog-code-block .token.function { color: #dcdcaa; }
918
- .dialog-code-block .token.number { color: #b5cea8; }
919
- .dialog-code-block .token.operator { color: #d4d4d4; }
920
- .dialog-code-block .token.class-name { color: #4ec9b0; }
921
- .dialog-code-block .token.punctuation { color: #d4d4d4; }
922
- .dialog-code-block .token.boolean { color: #569cd6; }
923
- .dialog-code-block .token.property { color: #9cdcfe; }
924
- .dialog-code-block .token.tag { color: #569cd6; }
925
- .dialog-code-block .token.attr-name { color: #9cdcfe; }
926
- .dialog-code-block .token.attr-value { color: #ce9178; }
927
- .dialog-code-block ::selection { background: #264f78; }
928
- </style>
929
- <pre class="${prismClass} dialog-code-block" style="${codeStyles}">
930
- <code class="${prismClass}" style="color: inherit; background: transparent; text-shadow: none;">${highlightedCode}</code>
931
- </pre>
932
- `;
933
- }
934
- /**
935
- * Trigger Prism highlighting on already rendered code blocks
936
- * Call this after inserting code into the DOM if not using formatCode
937
- */
938
- static highlightCodeBlocks(container = document) {
939
- if (window.Prism && window.Prism.highlightAllUnder) {
940
- window.Prism.highlightAllUnder(container);
941
- }
942
- }
943
- /**
944
- * Show copy success feedback
945
- */
946
- showCopySuccess() {
947
- const btn = this.element.querySelector('[data-action="copy"]');
948
- if (btn) {
949
- const originalHtml = btn.innerHTML;
950
- btn.innerHTML = '<i class="bi bi-check me-1"></i>Copied!';
951
- btn.classList.remove("btn-primary");
952
- btn.classList.add("btn-success");
953
- btn.disabled = true;
954
- setTimeout(() => {
955
- btn.innerHTML = originalHtml;
956
- btn.classList.remove("btn-success");
957
- btn.classList.add("btn-primary");
958
- btn.disabled = false;
959
- }, 2e3);
960
- }
961
- }
962
- /**
963
- * Static method to show HTML preview in a sandboxed iframe
964
- */
965
- static async showHtmlPreview(options = {}) {
966
- const htmlContent = options.html || options.content || "";
967
- const title = options.title || "HTML Preview";
968
- const size = options.size || "lg";
969
- const height = options.height || 500;
970
- const previewHtml = `
971
- <div class="html-preview-container">
972
- <div class="d-flex justify-content-between align-items-center mb-2">
973
- <small class="text-muted">Preview (sandboxed)</small>
974
- <button type="button" class="btn btn-sm btn-outline-secondary" data-action="refresh-preview">
975
- <i class="bi bi-arrow-clockwise"></i> Refresh
976
- </button>
977
- </div>
978
- <iframe
979
- id="html-preview-frame"
980
- class="border rounded w-100"
981
- style="height: ${height}px; background: white;"
982
- sandbox="allow-same-origin"
983
- frameborder="0"
984
- ></iframe>
985
- </div>
986
- `;
987
- const dialog = new Dialog({
988
- title,
989
- size,
990
- scrollable: false,
991
- body: previewHtml,
992
- buttons: [
993
- {
994
- text: "Close",
995
- class: "btn-secondary",
996
- dismiss: true
997
- }
998
- ]
999
- });
1000
- dialog.on("action:refresh-preview", async (event) => {
1001
- const iframe2 = dialog.element.querySelector("#html-preview-frame");
1002
- if (!iframe2) return;
1003
- const iframeDoc = iframe2.contentDocument || iframe2.contentWindow.document;
1004
- iframeDoc.open();
1005
- iframeDoc.write(htmlContent);
1006
- iframeDoc.close();
1007
- });
1008
- const fullscreenElement = document.querySelector(".table-fullscreen");
1009
- const targetContainer = fullscreenElement || document.body;
1010
- await dialog.render(true, targetContainer);
1011
- const iframe = dialog.element.querySelector("#html-preview-frame");
1012
- if (iframe) {
1013
- const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
1014
- iframeDoc.open();
1015
- iframeDoc.write(htmlContent);
1016
- iframeDoc.close();
1017
- }
1018
- dialog.show();
1019
- dialog.on("hidden", () => {
1020
- dialog.destroy();
1021
- dialog.element.remove();
1022
- });
1023
- return dialog;
1024
- }
1025
- /**
1026
- * Show a dialog with promise-based button handling
1027
- * - If a button has a handler, it will be called. Return semantics:
1028
- * - true or undefined: resolve and close (with button.value || button.action || index)
1029
- * - null or false: keep dialog open (do not resolve)
1030
- * - any other value: resolve with that value and close
1031
- * - If no handler, resolve with action/value/index and close
1032
- * @param {Object} options - Dialog options
1033
- * @returns {Promise} Resolves with value/action/index or null on dismiss
1034
- */
1035
- static async showDialog(options = {}) {
1036
- if (typeof options === "string") {
1037
- const message2 = arguments[0];
1038
- const title2 = arguments[1] || "Alert";
1039
- const opts = arguments[2] || {};
1040
- options = {
1041
- ...opts,
1042
- body: message2,
1043
- title: title2
1044
- };
1045
- }
1046
- const {
1047
- title = "Dialog",
1048
- content,
1049
- // Aliases supported (common mistakes): `message` and `view`
1050
- // Priority: body > view > message > content
1051
- body,
1052
- view,
1053
- message,
1054
- size = "md",
1055
- centered = true,
1056
- buttons = [
1057
- { text: "OK", class: "btn-primary", value: true }
1058
- ],
1059
- rejectOnDismiss = false,
1060
- // Default to return null on dismissal
1061
- ...dialogOptions
1062
- } = options;
1063
- const resolvedBody = body ?? view ?? message ?? content ?? "";
1064
- const dialog = new Dialog({
1065
- title,
1066
- body: resolvedBody,
1067
- size,
1068
- centered,
1069
- buttons,
1070
- ...dialogOptions
1071
- });
1072
- const fullscreenElement = document.querySelector(".table-fullscreen");
1073
- const targetContainer = fullscreenElement || document.body;
1074
- await dialog.render(true, targetContainer);
1075
- return new Promise((resolve, reject) => {
1076
- let resolved = false;
1077
- const buttonElements = dialog.element.querySelectorAll(".modal-footer button");
1078
- buttonElements.forEach((btnElement, index) => {
1079
- const buttonConfig = buttons[index];
1080
- if (!buttonConfig) return;
1081
- btnElement.addEventListener("click", async (e) => {
1082
- if (resolved) return;
1083
- const defaultResolveValue = buttonConfig.value !== void 0 ? buttonConfig.value : buttonConfig.action ?? index;
1084
- if (typeof buttonConfig.handler === "function") {
1085
- try {
1086
- const result = await buttonConfig.handler({
1087
- dialog,
1088
- button: buttonConfig,
1089
- index,
1090
- event: e
1091
- });
1092
- if (result === null || result === false) {
1093
- return;
1094
- }
1095
- const valueToResolve = result === true || result === void 0 ? defaultResolveValue : result;
1096
- resolved = true;
1097
- if (!buttonConfig.dismiss) {
1098
- dialog.hide();
1099
- }
1100
- resolve(valueToResolve);
1101
- } catch (err) {
1102
- console.error("Dialog button handler error:", err);
1103
- return;
1104
- }
1105
- } else {
1106
- resolved = true;
1107
- if (!buttonConfig.dismiss) {
1108
- dialog.hide();
1109
- }
1110
- resolve(defaultResolveValue);
1111
- }
1112
- });
1113
- });
1114
- dialog.on("hidden", () => {
1115
- if (!resolved) {
1116
- resolved = true;
1117
- if (rejectOnDismiss) {
1118
- reject(new Error("Dialog dismissed"));
1119
- } else {
1120
- resolve(null);
1121
- }
1122
- }
1123
- setTimeout(() => {
1124
- dialog.destroy();
1125
- dialog.element.remove();
1126
- }, 100);
1127
- });
1128
- dialog.show();
1129
- });
1130
- }
1131
- /**
1132
- * Static alert dialog helper
1133
- * @param {Object|string} options - Alert options or message string
1134
- * @returns {Promise} Resolves when OK is clicked
1135
- */
1136
- static async alert(options = {}) {
1137
- if (typeof options === "string") {
1138
- options = {
1139
- message: options,
1140
- title: "Alert"
1141
- };
1142
- }
1143
- const {
1144
- message = "",
1145
- title = "Alert",
1146
- type = "info",
1147
- // info, success, warning, danger
1148
- ...dialogOptions
1149
- } = options;
1150
- let icon = "";
1151
- let titleClass = "";
1152
- switch (type) {
1153
- case "success":
1154
- icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>';
1155
- titleClass = "text-success";
1156
- break;
1157
- case "warning":
1158
- icon = '<i class="bi bi-exclamation-triangle-fill text-warning me-2"></i>';
1159
- titleClass = "text-warning";
1160
- break;
1161
- case "danger":
1162
- case "error":
1163
- icon = '<i class="bi bi-x-circle-fill text-danger me-2"></i>';
1164
- titleClass = "text-danger";
1165
- break;
1166
- default:
1167
- icon = '<i class="bi bi-info-circle-fill text-info me-2"></i>';
1168
- titleClass = "text-info";
1169
- }
1170
- return Dialog.showDialog({
1171
- title: `<span class="${titleClass}">${icon}${title}</span>`,
1172
- body: `<p>${message}</p>`,
1173
- size: "sm",
1174
- centered: true,
1175
- buttons: [
1176
- { text: "OK", class: "btn-primary", value: true }
1177
- ],
1178
- ...dialogOptions
1179
- });
1180
- }
1181
- /**
1182
- * Static confirm dialog
1183
- */
1184
- static async confirm(message, title = "Confirm", options = {}) {
1185
- if (typeof message === "object") {
1186
- options = message;
1187
- message = options.message;
1188
- title = options.title || title;
1189
- }
1190
- const dialog = new Dialog({
1191
- title,
1192
- body: `<p>${message}</p>`,
1193
- size: options.size || "sm",
1194
- centered: true,
1195
- backdrop: "static",
1196
- buttons: [
1197
- { text: options.cancelText || "Cancel", class: "btn-secondary", dismiss: true, action: "cancel" },
1198
- { text: options.confirmText || "Confirm", class: options.confirmClass || "btn-primary", action: "confirm" }
1199
- ],
1200
- ...options
1201
- });
1202
- const fullscreenElement = document.querySelector(".table-fullscreen");
1203
- const targetContainer = fullscreenElement || document.body;
1204
- await dialog.render(true, targetContainer);
1205
- dialog.show();
1206
- return new Promise((resolve) => {
1207
- let result = false;
1208
- dialog.on("action:confirm", () => {
1209
- result = true;
1210
- dialog.hide();
1211
- });
1212
- dialog.on("hidden", () => {
1213
- dialog.destroy();
1214
- dialog.element.remove();
1215
- resolve(result);
1216
- });
1217
- });
1218
- }
1219
- /**
1220
- * Static prompt dialog
1221
- */
1222
- static async prompt(message, title = "Input", options = {}) {
1223
- const inputId = `prompt-input-${Date.now()}`;
1224
- const defaultValue = options.defaultValue || "";
1225
- const inputType = options.inputType || "text";
1226
- const placeholder = options.placeholder || "";
1227
- const dialog = new Dialog({
1228
- title,
1229
- body: `
1230
- <p>${message}</p>
1231
- <input type="${inputType}"
1232
- class="form-control"
1233
- id="${inputId}"
1234
- value="${defaultValue}"
1235
- placeholder="${placeholder}">
1236
- `,
1237
- size: options.size || "sm",
1238
- centered: true,
1239
- backdrop: "static",
1240
- buttons: [
1241
- { text: "Cancel", class: "btn-secondary", dismiss: true },
1242
- { text: "OK", class: "btn-primary", action: "ok" }
1243
- ],
1244
- ...options
1245
- });
1246
- const fullscreenElement = document.querySelector(".table-fullscreen");
1247
- const targetContainer = fullscreenElement || document.body;
1248
- await dialog.render(true, targetContainer);
1249
- dialog.show();
1250
- dialog.on("shown", () => {
1251
- const input = dialog.element.querySelector(`#${inputId}`);
1252
- if (input) {
1253
- input.focus();
1254
- input.select();
1255
- }
1256
- });
1257
- return new Promise((resolve) => {
1258
- let result = null;
1259
- dialog.on("action:ok", () => {
1260
- const input = dialog.element.querySelector(`#${inputId}`);
1261
- result = input ? input.value : null;
1262
- dialog.hide();
1263
- });
1264
- dialog.on("hidden", () => {
1265
- dialog.destroy();
1266
- dialog.element.remove();
1267
- resolve(result);
1268
- });
1269
- });
1270
- }
1271
- /**
1272
- * Get Bootstrap modal instance
1273
- */
1274
- getModal() {
1275
- return this.modal;
1276
- }
1277
- /**
1278
- * Check if modal is shown
1279
- */
1280
- isShown() {
1281
- return this.element?.classList.contains("show") || false;
1282
- }
1283
- /**
1284
- * Show form in a dialog for simple data collection (no model saving)
1285
- * @param {object} options - Configuration options
1286
- * @returns {Promise} Promise that resolves with form data or null if cancelled
1287
- */
1288
- static async showForm(options = {}) {
1289
- const {
1290
- title = "Form",
1291
- formConfig = {},
1292
- size = "md",
1293
- centered = true,
1294
- submitText = "Submit",
1295
- cancelText = "Cancel",
1296
- ...dialogOptions
1297
- } = options;
1298
- const FormView = (await import("./FormView-Dz3mYasQ.js").then((n) => n.b)).default;
1299
- const formView = new FormView({
1300
- fileHandling: options.fileHandling || "base64",
1301
- data: options.data,
1302
- defaults: options.defaults,
1303
- model: options.model,
1304
- formConfig: {
1305
- fields: formConfig.fields || options.fields,
1306
- ...formConfig,
1307
- submitButton: false,
1308
- resetButton: false
1309
- }
1310
- });
1311
- const dialog = new Dialog({
1312
- title,
1313
- body: formView,
1314
- size,
1315
- centered,
1316
- buttons: [
1317
- {
1318
- text: cancelText,
1319
- class: "btn-secondary",
1320
- action: "cancel"
1321
- },
1322
- {
1323
- text: submitText,
1324
- class: "btn-primary",
1325
- action: "submit"
1326
- }
1327
- ],
1328
- ...dialogOptions
1329
- });
1330
- const fullscreenElement = document.querySelector(".table-fullscreen");
1331
- const targetContainer = fullscreenElement || document.body;
1332
- await dialog.render(true, targetContainer);
1333
- dialog.show();
1334
- return new Promise((resolve) => {
1335
- let resolved = false;
1336
- dialog.on("action:submit", async () => {
1337
- if (resolved) return;
1338
- if (!formView.validate()) {
1339
- formView.focusFirstError();
1340
- return;
1341
- }
1342
- if (options.autoSave && options.model) {
1343
- dialog.setLoading(true);
1344
- const result = await formView.saveModel();
1345
- if (!result.success) {
1346
- dialog.setLoading(false);
1347
- dialog.render();
1348
- dialog.getApp().toast.error(result.message);
1349
- return;
1350
- }
1351
- resolved = true;
1352
- dialog.hide();
1353
- resolve(result);
1354
- }
1355
- try {
1356
- const formData = await formView.getFormData();
1357
- resolved = true;
1358
- dialog.hide();
1359
- resolve(formData);
1360
- } catch (error) {
1361
- console.error("Error collecting form data:", error);
1362
- formView.showError("Error collecting form data");
1363
- }
1364
- });
1365
- dialog.on("action:cancel", () => {
1366
- if (resolved) return;
1367
- resolved = true;
1368
- dialog.hide();
1369
- resolve(null);
1370
- });
1371
- dialog.on("hidden", () => {
1372
- if (!resolved) {
1373
- resolved = true;
1374
- resolve(null);
1375
- }
1376
- setTimeout(() => {
1377
- formView.destroy();
1378
- dialog.destroy();
1379
- }, 100);
1380
- });
1381
- });
1382
- }
1383
- static async showModelView(model, options) {
1384
- const modelClass = model.constructor;
1385
- const modelView = modelClass.VIEW_CLASS;
1386
- const viewInstance = new modelView({ model });
1387
- options = options || {};
1388
- return await Dialog.showDialog({
1389
- header: false,
1390
- body: viewInstance,
1391
- size: "lg",
1392
- centered: false,
1393
- ...options
1394
- });
1395
- }
1396
- /**
1397
- * Show form in a dialog with automatic model saving
1398
- * @param {object} options - Configuration options (requires model)
1399
- * @returns {Promise} Promise that resolves with save result or null if cancelled
1400
- */
1401
- static async showModelForm(options = {}) {
1402
- const {
1403
- title = "Edit",
1404
- formConfig = {},
1405
- size = "md",
1406
- centered = true,
1407
- submitText = "Save",
1408
- cancelText = "Cancel",
1409
- model,
1410
- fields,
1411
- ...dialogOptions
1412
- } = options;
1413
- if (!model) {
1414
- throw new Error("showModelForm requires a model");
1415
- }
1416
- const FormView = (await import("./FormView-Dz3mYasQ.js").then((n) => n.b)).default;
1417
- const formView = new FormView({
1418
- fileHandling: options.fileHandling || "base64",
1419
- model,
1420
- data: options.data,
1421
- defaults: options.defaults,
1422
- formConfig: {
1423
- // Support both formConfig.fields and direct fields parameter
1424
- fields: fields || formConfig.fields || [],
1425
- ...formConfig,
1426
- submitButton: false,
1427
- resetButton: false
1428
- }
1429
- });
1430
- const dialog = new Dialog({
1431
- title,
1432
- body: formView,
1433
- size,
1434
- centered,
1435
- buttons: [
1436
- {
1437
- text: cancelText,
1438
- class: "btn-secondary",
1439
- action: "cancel"
1440
- },
1441
- {
1442
- text: submitText,
1443
- class: "btn-primary",
1444
- action: "submit"
1445
- }
1446
- ],
1447
- ...dialogOptions
1448
- });
1449
- const fullscreenElement = document.querySelector(".table-fullscreen");
1450
- const targetContainer = fullscreenElement || document.body;
1451
- await dialog.render(true, targetContainer);
1452
- dialog.show();
1453
- return new Promise((resolve) => {
1454
- let resolved = false;
1455
- dialog.on("action:submit", async () => {
1456
- if (resolved) return;
1457
- dialog.setLoading(true, "Saving...");
1458
- try {
1459
- const result = await formView.handleSubmit();
1460
- if (result.success) {
1461
- resolved = true;
1462
- dialog.hide();
1463
- resolve(result);
1464
- } else {
1465
- dialog.setLoading(false);
1466
- let errmsg = result.error;
1467
- if (result.data && result.data.error) {
1468
- errmsg = result.data.error;
1469
- }
1470
- dialog.getApp().toast.error(errmsg);
1471
- }
1472
- } catch (error) {
1473
- console.error("Error saving form:", error);
1474
- await dialog.setContent(formView);
1475
- formView.showError(error.message || "An error occurred while saving");
1476
- }
1477
- });
1478
- dialog.on("action:cancel", () => {
1479
- if (resolved) return;
1480
- resolved = true;
1481
- dialog.hide();
1482
- resolve(null);
1483
- });
1484
- dialog.on("hidden", () => {
1485
- if (!resolved) {
1486
- resolved = true;
1487
- resolve(null);
1488
- }
1489
- setTimeout(() => {
1490
- formView.destroy();
1491
- dialog.destroy();
1492
- }, 100);
1493
- });
1494
- });
1495
- }
1496
- /**
1497
- * Show data in a dialog using DataView component
1498
- * @param {object} options - Configuration options
1499
- * @returns {Promise} Promise that resolves when dialog is closed
1500
- */
1501
- static async showData(options = {}) {
1502
- const {
1503
- title = "Data View",
1504
- data = {},
1505
- model = null,
1506
- fields = [],
1507
- columns = 2,
1508
- responsive = true,
1509
- showEmptyValues = false,
1510
- emptyValueText = "—",
1511
- size = "lg",
1512
- centered = true,
1513
- closeText = "Close",
1514
- ...dialogOptions
1515
- } = options;
1516
- const DataView = (await import("./DataView-Dzo0jbs2.js")).default;
1517
- const dataView = new DataView({
1518
- data,
1519
- model,
1520
- fields,
1521
- columns,
1522
- responsive,
1523
- showEmptyValues,
1524
- emptyValueText
1525
- });
1526
- const dialog = new Dialog({
1527
- title,
1528
- body: dataView,
1529
- size,
1530
- centered,
1531
- buttons: [
1532
- {
1533
- text: closeText,
1534
- class: "btn-secondary",
1535
- value: "close"
1536
- }
1537
- ],
1538
- ...dialogOptions
1539
- });
1540
- const fullscreenElement = document.querySelector(".table-fullscreen");
1541
- const targetContainer = fullscreenElement || document.body;
1542
- await dialog.render(true, targetContainer);
1543
- dialog.show();
1544
- return new Promise((resolve) => {
1545
- let resolved = false;
1546
- const closeBtn = dialog.element.querySelector(".modal-footer button");
1547
- const handleClose = () => {
1548
- if (resolved) return;
1549
- resolved = true;
1550
- dialog.hide();
1551
- resolve(true);
1552
- };
1553
- closeBtn?.addEventListener("click", handleClose);
1554
- dialog.on("hidden", () => {
1555
- if (!resolved) {
1556
- resolved = true;
1557
- resolve(true);
1558
- }
1559
- setTimeout(() => {
1560
- dataView.destroy();
1561
- dialog.destroy();
1562
- dialog.element.remove();
1563
- }, 100);
1564
- });
1565
- dataView.on("field:click", (data2) => {
1566
- dialog.emit("dataview:field:click", data2);
1567
- });
1568
- dataView.on("error", (data2) => {
1569
- dialog.emit("dataview:error", data2);
1570
- });
1571
- });
1572
- }
1573
- }
1574
- Dialog.showConfirm = Dialog.confirm;
1575
- Dialog.showError = Dialog.alert;
1576
- export {
1577
- Dialog as default
1578
- };
1579
- //# sourceMappingURL=Dialog-DOGDalUq.js.map