web-mojo 2.1.550 → 2.1.626

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 (66) 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 +19 -10
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +3 -3
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +3 -3
  11. package/dist/chunks/{ChatView-DXUzOG8o.js → ChatView-0e0k3QSK.js} +2 -2
  12. package/dist/chunks/{ChatView-DXUzOG8o.js.map → ChatView-0e0k3QSK.js.map} +1 -1
  13. package/dist/chunks/{ChatView-C3oW0hvN.js → ChatView-BxbA5ob6.js} +6 -6
  14. package/dist/chunks/{ChatView-C3oW0hvN.js.map → ChatView-BxbA5ob6.js.map} +1 -1
  15. package/dist/chunks/{ContextMenu-BTEcH8VJ.js → ContextMenu-ATInMbaI.js} +3 -3
  16. package/dist/chunks/{ContextMenu-BTEcH8VJ.js.map → ContextMenu-ATInMbaI.js.map} +1 -1
  17. package/dist/chunks/{ContextMenu-B6VXD0nZ.js → ContextMenu-Ckttp-rD.js} +10 -3
  18. package/dist/chunks/{ContextMenu-B6VXD0nZ.js.map → ContextMenu-Ckttp-rD.js.map} +1 -1
  19. package/dist/chunks/{DataView-CCrESxij.js → DataView-BQOKPprj.js} +2 -2
  20. package/dist/chunks/{DataView-CCrESxij.js.map → DataView-BQOKPprj.js.map} +1 -1
  21. package/dist/chunks/{DataView-DX2DcGKA.js → DataView-BsWK0Ul7.js} +2 -2
  22. package/dist/chunks/{DataView-DX2DcGKA.js.map → DataView-BsWK0Ul7.js.map} +1 -1
  23. package/dist/chunks/{Dialog-CY3xpb40.js → Dialog-aTTrQ6uW.js} +5 -5
  24. package/dist/chunks/{Dialog-CY3xpb40.js.map → Dialog-aTTrQ6uW.js.map} +1 -1
  25. package/dist/chunks/{Dialog-DJkVf-r4.js → Dialog-t4J3qOjC.js} +2 -2
  26. package/dist/chunks/{Dialog-DJkVf-r4.js.map → Dialog-t4J3qOjC.js.map} +1 -1
  27. package/dist/chunks/{FormView-BFa00Ukw.js → FormView-DjypPrEw.js} +2 -2
  28. package/dist/chunks/{FormView-BFa00Ukw.js.map → FormView-DjypPrEw.js.map} +1 -1
  29. package/dist/chunks/{FormView-Czl1459d.js → FormView-DvKBq99H.js} +2 -2
  30. package/dist/chunks/{FormView-Czl1459d.js.map → FormView-DvKBq99H.js.map} +1 -1
  31. package/dist/chunks/{MetricsChart-Kx9ZS9n8.js → MetricsChart-BHB2dubV.js} +2 -2
  32. package/dist/chunks/{MetricsChart-Kx9ZS9n8.js.map → MetricsChart-BHB2dubV.js.map} +1 -1
  33. package/dist/chunks/{MetricsChart-CoKYs18I.js → MetricsChart-DJ_AMjGg.js} +3 -3
  34. package/dist/chunks/{MetricsChart-CoKYs18I.js.map → MetricsChart-DJ_AMjGg.js.map} +1 -1
  35. package/dist/chunks/{PDFViewer-CPRZHTMD.js → PDFViewer-DXoC6y5o.js} +2 -2
  36. package/dist/chunks/{PDFViewer-CPRZHTMD.js.map → PDFViewer-DXoC6y5o.js.map} +1 -1
  37. package/dist/chunks/{PDFViewer-CObvQFg-.js → PDFViewer-kDd18mqo.js} +3 -3
  38. package/dist/chunks/{PDFViewer-CObvQFg-.js.map → PDFViewer-kDd18mqo.js.map} +1 -1
  39. package/dist/chunks/{Page-csKPSoVL.js → Page-BrWp1FsC.js} +2 -2
  40. package/dist/chunks/{Page-csKPSoVL.js.map → Page-BrWp1FsC.js.map} +1 -1
  41. package/dist/chunks/{Page-oNlGYNf5.js → Page-DgKaDD9p.js} +2 -2
  42. package/dist/chunks/{Page-oNlGYNf5.js.map → Page-DgKaDD9p.js.map} +1 -1
  43. package/dist/chunks/TopNav-DGtRj5F4.js +1039 -0
  44. package/dist/chunks/TopNav-DGtRj5F4.js.map +1 -0
  45. package/dist/chunks/TopNav-DoAl9Rpd.js +2 -0
  46. package/dist/chunks/TopNav-DoAl9Rpd.js.map +1 -0
  47. package/dist/chunks/{WebApp-Z3SCGNp2.js → WebApp-DWIriU_w.js} +2 -2
  48. package/dist/chunks/{WebApp-Z3SCGNp2.js.map → WebApp-DWIriU_w.js.map} +1 -1
  49. package/dist/chunks/{WebApp-6ncOIVXa.js → WebApp-ciExPk0c.js} +21 -14
  50. package/dist/chunks/{WebApp-6ncOIVXa.js.map → WebApp-ciExPk0c.js.map} +1 -1
  51. package/dist/css/web-mojo.css +1 -1
  52. package/dist/docit.cjs.js +1 -1
  53. package/dist/docit.es.js +5 -5
  54. package/dist/index.cjs.js +1 -1
  55. package/dist/index.cjs.js.map +1 -1
  56. package/dist/index.es.js +237 -449
  57. package/dist/index.es.js.map +1 -1
  58. package/dist/lightbox.cjs.js +1 -1
  59. package/dist/lightbox.es.js +4 -4
  60. package/dist/portal.css +222 -0
  61. package/dist/table.css +0 -13
  62. package/package.json +1 -1
  63. package/dist/chunks/TopNav-CWXnES46.js +0 -2
  64. package/dist/chunks/TopNav-CWXnES46.js.map +0 -1
  65. package/dist/chunks/TopNav-Szt3ULMS.js +0 -381
  66. package/dist/chunks/TopNav-Szt3ULMS.js.map +0 -1
@@ -0,0 +1,1039 @@
1
+ import { V as View } from "./WebApp-ciExPk0c.js";
2
+ import Dialog from "./Dialog-aTTrQ6uW.js";
3
+ import { G as GroupList } from "./ContextMenu-Ckttp-rD.js";
4
+ class ResultsView extends View {
5
+ constructor(options = {}) {
6
+ super({
7
+ className: "search-results-view flex-grow-1 overflow-auto d-flex flex-column",
8
+ template: `
9
+ <div class="flex-grow-1 overflow-auto">
10
+ {{#data.loading}}
11
+ <div class="text-center p-4">
12
+ <div class="spinner-border spinner-border-sm text-muted" role="status">
13
+ <span class="visually-hidden">Loading...</span>
14
+ </div>
15
+ <div class="mt-2 small text-muted">{{data.loadingText}}</div>
16
+ </div>
17
+ {{/data.loading}}
18
+
19
+ {{^data.loading}}
20
+ {{#data.items}}
21
+ <div class="simple-search-item position-relative"
22
+ data-action="select-item"
23
+ data-item-index="{{index}}">
24
+ {{{itemContent}}}
25
+ <i class="bi bi-chevron-right position-absolute end-0 top-50 translate-middle-y me-3 text-muted"></i>
26
+ </div>
27
+ {{/data.items}}
28
+
29
+ {{#data.showNoResults}}
30
+ <div class="text-center p-4">
31
+ <i class="bi bi-search text-muted mb-2" style="font-size: 1.5rem;"></i>
32
+ <div class="text-muted small">{{data.noResultsText}}</div>
33
+ <button type="button"
34
+ class="btn btn-link btn-sm mt-2 p-0"
35
+ data-action="clear-search">
36
+ Clear search
37
+ </button>
38
+ </div>
39
+ {{/data.showNoResults}}
40
+
41
+ {{#data.showEmpty}}
42
+ <div class="text-center p-4">
43
+ <i class="{{data.emptyIcon}} text-muted mb-2" style="font-size: 2rem;"></i>
44
+ <div class="text-muted small mb-2">{{data.emptyText}}</div>
45
+ {{#data.emptySubtext}}
46
+ <div class="text-muted" style="font-size: 0.75rem;">
47
+ {{data.emptySubtext}}
48
+ </div>
49
+ {{/data.emptySubtext}}
50
+ </div>
51
+ {{/data.showEmpty}}
52
+ {{/data.loading}}
53
+ </div>
54
+
55
+ {{#data.showResultsCount}}
56
+ <div class="border-top bg-light p-2 text-center">
57
+ <small class="text-muted">
58
+ {{data.filteredCount}} of {{data.totalCount}}
59
+ </small>
60
+ </div>
61
+ {{/data.showResultsCount}}
62
+ `,
63
+ ...options
64
+ });
65
+ this.parentView = options.parentView;
66
+ }
67
+ async handleActionSelectItem(event, element) {
68
+ event.preventDefault();
69
+ const itemIndex = parseInt(element.getAttribute("data-item-index"));
70
+ if (this.parentView) {
71
+ this.parentView.handleItemSelection(itemIndex);
72
+ }
73
+ }
74
+ async handleActionClearSearch(event, _element) {
75
+ event.preventDefault();
76
+ if (this.parentView) {
77
+ this.parentView.clearSearch();
78
+ }
79
+ }
80
+ }
81
+ class SimpleSearchView extends View {
82
+ constructor(options = {}) {
83
+ super({
84
+ className: "simple-search-view h-100 d-flex flex-column",
85
+ template: `
86
+ <div class="p-3 border-bottom bg-light">
87
+ <div class="d-flex justify-content-between align-items-start mb-3">
88
+ <h6 class="text-muted fw-semibold mb-0">
89
+ {{#data.headerIcon}}<i class="{{data.headerIcon}} me-2"></i>{{/data.headerIcon}}
90
+ {{{data.headerText}}}
91
+ </h6>
92
+ {{#data.showExitButton}}
93
+ <button class="btn btn-link p-0 text-muted simple-search-exit-btn"
94
+ type="button"
95
+ data-action="exit-view"
96
+ title="Exit"
97
+ aria-label="Exit view">
98
+ <i class="bi bi-x-lg" aria-hidden="true"></i>
99
+ </button>
100
+ {{/data.showExitButton}}
101
+ </div>
102
+ <div class="position-relative">
103
+ <input type="text"
104
+ class="form-control form-control-sm pe-5"
105
+ placeholder="{{data.searchPlaceholder}}"
106
+ value="{{data.searchValue}}"
107
+ data-filter="live-search"
108
+ data-filter-debounce="{{data.debounceMs}}"
109
+ data-change-action="search-items">
110
+ <button class="btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn"
111
+ type="button"
112
+ data-action="clear-search"
113
+ title="Clear search"
114
+ aria-label="Clear search">
115
+ <i class="bi bi-x-circle-fill" aria-hidden="true"></i>
116
+ </button>
117
+ </div>
118
+ </div>
119
+
120
+ <div data-container="results"></div>
121
+
122
+ {{#data.showFooter}}
123
+ <div class="p-3 border-top bg-light">
124
+ <small class="text-muted">
125
+ <i class="{{data.footerIcon}} me-1"></i>
126
+ {{{data.footerContent}}}
127
+ </small>
128
+ </div>
129
+ {{/data.showFooter}}
130
+ `,
131
+ ...options
132
+ });
133
+ this.Collection = options.Collection;
134
+ this.collection = options.collection;
135
+ this.itemTemplate = options.itemTemplate || this.getDefaultItemTemplate();
136
+ this.searchFields = options.searchFields || ["name"];
137
+ this.collectionParams = { size: 25, ...options.collectionParams };
138
+ this.headerText = options.headerText || "Select Item";
139
+ this.headerIcon = options.headerIcon || "bi bi-list";
140
+ this.searchPlaceholder = options.searchPlaceholder || "Search...";
141
+ this.loadingText = options.loadingText || "Loading items...";
142
+ this.noResultsText = options.noResultsText || "No items match your search";
143
+ this.emptyText = options.emptyText || "No items available";
144
+ this.emptySubtext = options.emptySubtext || null;
145
+ this.emptyIcon = options.emptyIcon || "bi bi-inbox";
146
+ this.footerContent = options.footerContent || null;
147
+ this.footerIcon = options.footerIcon || "bi bi-info-circle";
148
+ this.showExitButton = options.showExitButton || false;
149
+ this.searchValue = "";
150
+ this.filteredItems = [];
151
+ this.loading = false;
152
+ this.hasSearched = false;
153
+ this.searchTimer = null;
154
+ this.debounceMs = options.debounceMs || 800;
155
+ this.resultsView = new ResultsView({
156
+ parentView: this
157
+ });
158
+ if (!this.collection && this.Collection) {
159
+ this.collection = new this.Collection();
160
+ }
161
+ this.addChild(this.resultsView);
162
+ }
163
+ onInit() {
164
+ if (this.collection) {
165
+ this.setupCollection();
166
+ }
167
+ if (this.collection && this.options.autoLoad !== false) {
168
+ this.loadItems();
169
+ }
170
+ }
171
+ setupCollection() {
172
+ Object.assign(this.collection.params, this.collectionParams);
173
+ this.collection.on("fetch:success", () => {
174
+ this.loading = false;
175
+ this.updateFilteredItems();
176
+ });
177
+ this.collection.on("fetch:error", () => {
178
+ this.loading = false;
179
+ });
180
+ }
181
+ async loadItems() {
182
+ if (!this.collection) {
183
+ console.warn("SimpleSearchView: No collection provided");
184
+ return;
185
+ }
186
+ this.loading = true;
187
+ this.updateResultsView();
188
+ try {
189
+ await this.collection.fetch();
190
+ this.updateFilteredItems();
191
+ } catch (error) {
192
+ console.error("Error loading items:", error);
193
+ const app = this.getApp();
194
+ app?.showError?.("Failed to load items. Please try again.");
195
+ } finally {
196
+ this.loading = false;
197
+ this.updateFilteredItems();
198
+ }
199
+ }
200
+ updateFilteredItems() {
201
+ if (!this.collection) {
202
+ this.filteredItems = [];
203
+ return;
204
+ }
205
+ const items = this.collection.toJSON();
206
+ if (!this.searchValue || !this.searchValue.trim()) {
207
+ this.filteredItems = items;
208
+ } else {
209
+ const searchTerm = this.searchValue.toLowerCase().trim();
210
+ this.filteredItems = items.filter((item) => {
211
+ return this.searchFields.some((field) => {
212
+ const value = this.getNestedValue(item, field);
213
+ return value && value.toString().toLowerCase().includes(searchTerm);
214
+ });
215
+ });
216
+ }
217
+ this.updateResultsView();
218
+ }
219
+ getNestedValue(obj, path) {
220
+ return path.split(".").reduce((current, key) => current?.[key], obj);
221
+ }
222
+ async getViewData() {
223
+ return {
224
+ searchValue: this.searchValue,
225
+ showFooter: !!this.footerContent,
226
+ showExitButton: this.showExitButton,
227
+ debounceMs: this.debounceMs,
228
+ // UI text
229
+ headerText: this.headerText,
230
+ headerIcon: this.headerIcon,
231
+ searchPlaceholder: this.searchPlaceholder,
232
+ footerContent: this.footerContent,
233
+ footerIcon: this.footerIcon
234
+ };
235
+ }
236
+ updateResultsView() {
237
+ if (!this.resultsView) return;
238
+ const hasItems = this.collection && this.collection.length() > 0;
239
+ const hasFilteredItems = this.filteredItems.length > 0;
240
+ const hasSearchValue = this.searchValue.length > 0;
241
+ const processedItems = this.filteredItems.map((item, index) => {
242
+ return {
243
+ ...item,
244
+ index,
245
+ itemContent: this.processItemTemplate(item)
246
+ };
247
+ });
248
+ this.resultsView.data = {
249
+ loading: this.loading,
250
+ items: processedItems,
251
+ showEmpty: !this.loading && !hasItems,
252
+ showNoResults: !this.loading && hasItems && !hasFilteredItems && hasSearchValue,
253
+ showResultsCount: !this.loading && hasItems,
254
+ filteredCount: this.filteredItems.length,
255
+ totalCount: this.collection?.restEnabled ? this.collection?.meta?.count || 0 : this.collection?.length() || 0,
256
+ // UI text
257
+ loadingText: this.loadingText,
258
+ noResultsText: this.noResultsText,
259
+ emptyText: this.emptyText,
260
+ emptySubtext: this.emptySubtext,
261
+ emptyIcon: this.emptyIcon
262
+ };
263
+ this.resultsView.render();
264
+ }
265
+ processItemTemplate(item) {
266
+ let template = this.itemTemplate;
267
+ template = template.replace(/\{\{(\w+)\}\}/g, (match, prop) => {
268
+ return this.getNestedValue(item, prop) || "";
269
+ });
270
+ return template;
271
+ }
272
+ getDefaultItemTemplate() {
273
+ return `
274
+ <div class="p-3 border-bottom">
275
+ <div class="fw-semibold text-dark">{{name}}</div>
276
+ <small class="text-muted">{{id}}</small>
277
+ </div>
278
+ `;
279
+ }
280
+ async onPassThruActionSearchItems(event, element) {
281
+ const searchValue = element.value || "";
282
+ console.log("search change...");
283
+ this.searchValue = searchValue;
284
+ this.hasSearched = true;
285
+ if (this.searchTimer) {
286
+ clearTimeout(this.searchTimer);
287
+ }
288
+ this.performSearch();
289
+ }
290
+ async performSearch() {
291
+ const searchParams = { ...this.collectionParams };
292
+ if (this.searchValue && this.searchValue.length > 1) {
293
+ searchParams.search = this.searchValue.trim();
294
+ }
295
+ this.collection.setParams(searchParams, true);
296
+ }
297
+ handleItemSelection(itemIndex) {
298
+ if (isNaN(itemIndex) || itemIndex < 0 || itemIndex >= this.filteredItems.length) {
299
+ console.error("Invalid item index:", itemIndex);
300
+ return;
301
+ }
302
+ const item = this.filteredItems[itemIndex];
303
+ const model = this.collection ? this.collection.get(item.id) : null;
304
+ this.emit("item:selected", {
305
+ item,
306
+ model,
307
+ index: itemIndex
308
+ });
309
+ }
310
+ /**
311
+ * Set the collection for this search view
312
+ */
313
+ setCollection(collection) {
314
+ this.collection = collection;
315
+ this.setupCollection();
316
+ return this;
317
+ }
318
+ /**
319
+ * Set the item template
320
+ */
321
+ setItemTemplate(template) {
322
+ this.itemTemplate = template;
323
+ this.updateResultsView();
324
+ return this;
325
+ }
326
+ /**
327
+ * Set search fields
328
+ */
329
+ setSearchFields(fields) {
330
+ this.searchFields = Array.isArray(fields) ? fields : [fields];
331
+ return this;
332
+ }
333
+ /**
334
+ * Refresh items list
335
+ */
336
+ async refresh() {
337
+ await this.loadItems();
338
+ }
339
+ /**
340
+ * Focus the search input
341
+ */
342
+ focusSearch() {
343
+ const searchInput = this.element?.querySelector('input[data-action="search-items"]');
344
+ if (searchInput) {
345
+ searchInput.focus();
346
+ }
347
+ }
348
+ /**
349
+ * Handle exit button click - emits event instead of closing
350
+ */
351
+ async handleActionExitView(event, element) {
352
+ this.emit("exit", { view: this });
353
+ }
354
+ /**
355
+ * Clear search and reset
356
+ */
357
+ async handleActionClearSearch(event, element) {
358
+ this.clearSearch();
359
+ }
360
+ clearSearch() {
361
+ this.searchValue = "";
362
+ this.hasSearched = false;
363
+ const searchInput = this.element?.querySelector('input[data-change-action="search-items"]');
364
+ if (searchInput) {
365
+ searchInput.value = "";
366
+ searchInput.focus();
367
+ }
368
+ this.performSearch();
369
+ }
370
+ /**
371
+ * Get the number of available items
372
+ */
373
+ getItemCount() {
374
+ return this.collection ? this.collection.length() : 0;
375
+ }
376
+ /**
377
+ * Get the number of filtered items
378
+ */
379
+ getFilteredItemCount() {
380
+ return this.filteredItems.length;
381
+ }
382
+ /**
383
+ * Check if items are loaded
384
+ */
385
+ hasItems() {
386
+ return this.getItemCount() > 0;
387
+ }
388
+ /**
389
+ * Get current search value
390
+ */
391
+ getSearchValue() {
392
+ return this.searchValue;
393
+ }
394
+ /**
395
+ * Set search value programmatically
396
+ */
397
+ setSearchValue(value) {
398
+ this.searchValue = value || "";
399
+ this.hasSearched = !!this.searchValue;
400
+ const searchInput = this.element?.querySelector('input[data-action="search-items"]');
401
+ if (searchInput) {
402
+ searchInput.value = this.searchValue;
403
+ }
404
+ this.performSearch();
405
+ return this;
406
+ }
407
+ async onAfterRender() {
408
+ await super.onAfterRender();
409
+ if (this.resultsView && !this.resultsView.isMounted()) {
410
+ const container = this.element?.querySelector('[data-container="results"]');
411
+ if (container) {
412
+ await this.resultsView.render(true, container);
413
+ }
414
+ }
415
+ this.updateResultsView();
416
+ }
417
+ /**
418
+ * Cleanup on destroy
419
+ */
420
+ async onBeforeDestroy() {
421
+ if (this.searchTimer) {
422
+ clearTimeout(this.searchTimer);
423
+ }
424
+ if (this.collection) {
425
+ this.collection.off("update");
426
+ }
427
+ await super.onBeforeDestroy();
428
+ }
429
+ }
430
+ class GroupSelectorButton extends View {
431
+ constructor(options = {}) {
432
+ super({
433
+ tagName: "div",
434
+ className: "nav-item",
435
+ ...options
436
+ });
437
+ const app = this.getApp();
438
+ this.Collection = options.Collection || app?.GroupCollection || GroupList;
439
+ this.collection = options.collection || new this.Collection();
440
+ this.currentGroup = options.currentGroup !== void 0 ? options.currentGroup : app?.activeGroup;
441
+ this.buttonClass = options.buttonClass || "btn btn-link nav-link";
442
+ this.buttonIcon = options.buttonIcon || "bi-building";
443
+ this.defaultText = options.defaultText || "Select Group";
444
+ this.itemTemplate = options.itemTemplate;
445
+ this.searchFields = options.searchFields || ["name"];
446
+ this.headerText = options.headerText || "Select Group";
447
+ this.searchPlaceholder = options.searchPlaceholder || "Search groups...";
448
+ this.autoSetActiveGroup = options.autoSetActiveGroup !== false;
449
+ this.onGroupSelected = options.onGroupSelected;
450
+ this.dialog = null;
451
+ if (app?.events) {
452
+ app.events.on("group:changed", (data) => {
453
+ if (data.group !== this.currentGroup) {
454
+ this.setCurrentGroup(data.group);
455
+ }
456
+ });
457
+ }
458
+ }
459
+ async getTemplate() {
460
+ return `
461
+ <button class="{{buttonClass}}"
462
+ data-action="show-selector"
463
+ type="button"
464
+ style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
465
+ <i class="{{buttonIcon}} me-1"></i>
466
+ <span class="group-name">{{displayName}}</span>
467
+ </button>
468
+ `;
469
+ }
470
+ async onBeforeRender() {
471
+ await super.onBeforeRender();
472
+ console.log("GroupSelectorButton onBeforeRender - currentGroup:", this.currentGroup?.get?.("name") || this.currentGroup?.name || "none");
473
+ this.buttonClass = this.buttonClass;
474
+ this.buttonIcon = this.buttonIcon;
475
+ this.displayName = this.currentGroup?.get?.("name") || this.currentGroup?.name || this.defaultText;
476
+ }
477
+ /**
478
+ * Show the group selector dialog
479
+ */
480
+ async onActionShowSelector(event) {
481
+ const searchView = new SimpleSearchView({
482
+ Collection: this.Collection,
483
+ collection: this.collection,
484
+ itemTemplate: this.itemTemplate || this.getDefaultItemTemplate(),
485
+ searchFields: this.searchFields,
486
+ headerText: this.headerText,
487
+ searchPlaceholder: this.searchPlaceholder,
488
+ headerIcon: this.buttonIcon,
489
+ showExitButton: false
490
+ });
491
+ this.dialog = new Dialog({
492
+ title: this.headerText,
493
+ body: searchView,
494
+ size: "md",
495
+ scrollable: true,
496
+ noBodyPadding: true,
497
+ buttons: [],
498
+ closeButton: true
499
+ });
500
+ searchView.on("item:selected", (data) => {
501
+ this.handleGroupSelection(data.model || data.item);
502
+ if (this.dialog) {
503
+ this.dialog.hide();
504
+ }
505
+ });
506
+ this.dialog.on("hidden", () => {
507
+ this.dialog.destroy();
508
+ this.dialog = null;
509
+ });
510
+ await this.dialog.render(true, document.body);
511
+ this.dialog.show();
512
+ return true;
513
+ }
514
+ /**
515
+ * Handle group selection
516
+ */
517
+ handleGroupSelection(group) {
518
+ this.currentGroup = group;
519
+ this.displayName = group?.get?.("name") || group?.name || this.defaultText;
520
+ this.render();
521
+ const app = this.getApp();
522
+ if (this.autoSetActiveGroup && app?.setActiveGroup) {
523
+ app.setActiveGroup(group);
524
+ }
525
+ if (this.onGroupSelected) {
526
+ this.onGroupSelected({ group });
527
+ }
528
+ this.emit("group-selected", { group });
529
+ if (app?.events) {
530
+ app.events.emit("group:selected", { group });
531
+ app.events.emit("group:changed", { group });
532
+ }
533
+ }
534
+ /**
535
+ * Default item template for groups (matches Sidebar pattern)
536
+ * Note: data-action and data-item-index are added by ResultsView wrapper
537
+ */
538
+ getDefaultItemTemplate() {
539
+ return `
540
+ <div class="d-flex align-items-center p-3 border-bottom">
541
+ <div class="flex-grow-1">
542
+ <div class="fw-semibold text-dark">{{name}}</div>
543
+ <small class="text-muted">#{{id}} {{kind}}</small>
544
+ </div>
545
+ </div>
546
+ `;
547
+ }
548
+ /**
549
+ * Set the current group programmatically
550
+ */
551
+ setCurrentGroup(group) {
552
+ this.currentGroup = group;
553
+ this.displayName = group?.get?.("name") || group?.name || this.defaultText;
554
+ if (this.mounted) {
555
+ this.render();
556
+ }
557
+ }
558
+ /**
559
+ * Get the current group
560
+ */
561
+ getCurrentGroup() {
562
+ return this.currentGroup;
563
+ }
564
+ }
565
+ class TopNav extends View {
566
+ constructor(options = {}) {
567
+ const themes = {
568
+ light: "navbar navbar-expand-lg navbar-light topnav-light",
569
+ dark: "navbar navbar-expand-lg navbar-dark topnav-dark",
570
+ clean: "navbar navbar-expand-lg navbar-light topnav-clean",
571
+ gradient: "navbar navbar-expand-lg navbar-dark topnav-gradient"
572
+ };
573
+ const themeName = options.theme || "light";
574
+ let navbarClass = themes[themeName] || themes.light;
575
+ if (options.shadow) {
576
+ navbarClass += ` topnav-shadow-${options.shadow}`;
577
+ }
578
+ super({
579
+ tagName: "nav",
580
+ className: navbarClass,
581
+ style: "position: relative; z-index: 1030;",
582
+ ...options
583
+ });
584
+ this.displayMode = options.displayMode || "both";
585
+ this.showPageIcon = options.showPageIcon !== false;
586
+ this.showPageDescription = options.showPageDescription || false;
587
+ this.showBreadcrumbs = options.showBreadcrumbs || false;
588
+ this.groupIcon = options.groupIcon || "bi-building";
589
+ this.currentPage = null;
590
+ this.previousPage = null;
591
+ this.config = {
592
+ brand: options.brand || "MOJO App",
593
+ brandIcon: options.brandIcon || "bi bi-play-circle",
594
+ brandRoute: options.brandRoute || "/",
595
+ navItems: options.navItems || [],
596
+ rightItems: options.rightItems || [],
597
+ showSidebarToggle: options.showSidebarToggle || false,
598
+ sidebarToggleAction: options.sidebarToggleAction || "toggle-sidebar",
599
+ ...options
600
+ };
601
+ this.userMenu = options.userMenu || this.findMenuItem("user");
602
+ if (this.userMenu) this.userMenu.id = "user";
603
+ this.loginMenu = options.loginMenu || this.findMenuItem("login");
604
+ this.setupPageListeners();
605
+ this.setupGroupListeners();
606
+ this.groupSelectorButton = null;
607
+ this.currentGroup = null;
608
+ }
609
+ findMenuItem(id) {
610
+ let item = this.config.navItems.find((item2) => item2.id === id);
611
+ if (!item) {
612
+ item = this.config.rightItems.find((item2) => item2.id === id);
613
+ }
614
+ return item || null;
615
+ }
616
+ replaceMenuItem(id, new_menu) {
617
+ const navIndex = this.config.navItems.findIndex((item) => item.id === id);
618
+ if (navIndex !== -1) {
619
+ this.config.navItems[navIndex] = new_menu;
620
+ return true;
621
+ }
622
+ const rightIndex = this.config.rightItems.findIndex((item) => item.id === id);
623
+ if (rightIndex !== -1) {
624
+ this.config.rightItems[rightIndex] = new_menu;
625
+ return true;
626
+ }
627
+ return false;
628
+ }
629
+ setBrand(brand, icon = null) {
630
+ this.config.brand = brand;
631
+ this.config.brandIcon = icon || this.config.brandIcon;
632
+ this.render();
633
+ }
634
+ setUser(user) {
635
+ if (!user) {
636
+ this.replaceMenuItem("user", this.loginMenu);
637
+ } else {
638
+ this.userMenu.label = user.get("display_name");
639
+ this.replaceMenuItem("login", this.userMenu);
640
+ }
641
+ this.setModel(user);
642
+ }
643
+ _onModelChange() {
644
+ if (this.model) {
645
+ this.userMenu.label = this.model.get("display_name");
646
+ }
647
+ if (this.isMounted()) {
648
+ this.render();
649
+ }
650
+ }
651
+ /**
652
+ * Get template based on display mode
653
+ */
654
+ async getTemplate() {
655
+ return `
656
+ <div class="container-fluid">
657
+ {{#data.showSidebarToggle}}
658
+ <button class="topnav-sidebar-toggle me-2" data-action="{{data.sidebarToggleAction}}" aria-label="Toggle Sidebar">
659
+ <i class="bi bi-chevron-right toggle-chevron"></i>
660
+ </button>
661
+ {{/data.showSidebarToggle}}
662
+
663
+ {{#data.showGroupInfo}}
664
+ <div class="navbar-brand d-flex align-items-center">
665
+ {{#data.groupIcon}}<i class="{{data.groupIcon}} me-2"></i>{{/data.groupIcon}}
666
+ <div>
667
+ <span class="topnav-group-name"
668
+ role="button"
669
+ tabindex="0"
670
+ data-action="open-group-selector"
671
+ style="cursor: pointer;">
672
+ {{data.currentGroupName}}
673
+ </span>
674
+ {{#data.showPageTitle}}
675
+ <span class="text-muted mx-2">|</span>
676
+ <span>{{data.currentPageName}}</span>
677
+ {{/data.showPageTitle}}
678
+ </div>
679
+ </div>
680
+ {{/data.showGroupInfo}}
681
+
682
+ {{#data.showPageInfo}}
683
+ <div class="navbar-brand d-flex align-items-center">
684
+ {{#data.currentPageIcon}}<i class="{{data.currentPageIcon}} me-2"></i>{{/data.currentPageIcon}}
685
+ <div>
686
+ <span>{{data.currentPageName}}</span>
687
+ {{#data.currentPageDescription}}
688
+ <small class="d-block" style="font-size: 0.75rem; line-height: 1;">{{data.currentPageDescription}}</small>
689
+ {{/data.currentPageDescription}}
690
+ </div>
691
+ </div>
692
+ {{/data.showPageInfo}}
693
+
694
+ {{#data.showBrand}}
695
+ <a class="navbar-brand" href="{{data.brandRoute}}">
696
+ {{#data.brandIcon}}<i class="{{data.brandIcon}} me-2"></i>{{/data.brandIcon}}
697
+ {{data.brand}}
698
+ </a>
699
+ {{/data.showBrand}}
700
+
701
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#{{data.navbarId}}">
702
+ <span class="navbar-toggler-icon"></span>
703
+ </button>
704
+
705
+ <div class="collapse navbar-collapse" id="{{data.navbarId}}">
706
+ {{#data.showNavItems}}
707
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
708
+ {{#data.navItems}}
709
+ <li class="nav-item">
710
+ <a class="nav-link {{#active}}active{{/active}}" href="{{route}}">
711
+ {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
712
+ {{text}}
713
+ </a>
714
+ </li>
715
+ {{/data.navItems}}
716
+ </ul>
717
+ {{/data.showNavItems}}
718
+
719
+ {{#data.hasRightItems}}
720
+ <div class="navbar-nav ms-auto">
721
+ {{#data.rightItems}}
722
+ {{#isGroupSelector}}
723
+ <div data-container="group-selector-{{id}}"></div>
724
+ {{/isGroupSelector}}
725
+ {{^isGroupSelector}}
726
+ {{#isDropdown}}
727
+ <div class="nav-item dropdown">
728
+ <a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
729
+ {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
730
+ {{label}}
731
+ </a>
732
+ <ul class="dropdown-menu dropdown-menu-end">
733
+ {{#items}}
734
+ {{#divider}}
735
+ <li><hr class="dropdown-divider"></li>
736
+ {{/divider}}
737
+ {{^divider}}
738
+ <li>
739
+ <a class="dropdown-item" role="button" {{#action}}data-action="{{action}}"{{/action}}>
740
+ {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
741
+ {{label}}
742
+ </a>
743
+ </li>
744
+ {{/divider}}
745
+ {{/items}}
746
+ </ul>
747
+ </div>
748
+ {{/isDropdown}}
749
+ {{^isDropdown}}
750
+ {{#isButton}}
751
+ <button class="{{buttonClass}}" data-action="{{action}}" data-id="{{id}}">
752
+ {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
753
+ {{label}}
754
+ </button>
755
+ {{/isButton}}
756
+ {{^isButton}}
757
+ <a class="nav-link" href="{{href}}" {{#action}}data-action="{{action}}"{{/action}}>
758
+ {{#icon}}<i class="{{icon}} me-1"></i>{{/icon}}
759
+ {{label}}
760
+ </a>
761
+ {{/isButton}}
762
+ {{/isDropdown}}
763
+ {{/isGroupSelector}}
764
+ {{/data.rightItems}}
765
+ </div>
766
+ {{/data.hasRightItems}}
767
+ </div>
768
+ </div>
769
+ `;
770
+ }
771
+ /**
772
+ * Process and normalize data before rendering (like Sidebar)
773
+ */
774
+ async onBeforeRender() {
775
+ await super.onBeforeRender();
776
+ const app = this.getApp();
777
+ const activeGroup = this.currentGroup || app?.activeGroup;
778
+ const showGroupInfo = this.displayMode === "group" || this.displayMode === "group_page_titles";
779
+ const showPageTitle = this.displayMode === "group_page_titles";
780
+ const showPageInfo = this.displayMode === "page" || this.displayMode === "both";
781
+ const showBrand = !showGroupInfo && !showPageInfo;
782
+ const showNavItems = this.displayMode === "menu" || this.displayMode === "both";
783
+ const navItems = this.filterItemsByPermissions(this.config.navItems || []);
784
+ const rightItems = this.processRightItems(this.config.rightItems || []);
785
+ this.data = {
786
+ // Brand information
787
+ brand: this.config.brand,
788
+ brandIcon: this.config.brandIcon,
789
+ brandRoute: this.config.brandRoute,
790
+ showBrand,
791
+ // Navbar configuration
792
+ navbarId: `navbar-${this.id}`,
793
+ // Navigation items
794
+ navItems,
795
+ showNavItems,
796
+ // Right items
797
+ rightItems,
798
+ hasRightItems: rightItems.length > 0,
799
+ // Group display
800
+ showGroupInfo,
801
+ showPageTitle,
802
+ currentGroupName: activeGroup?.get?.("name") || activeGroup?.name || "Select Group",
803
+ groupIcon: this.groupIcon,
804
+ // Page display
805
+ showPageInfo,
806
+ currentPageName: this.currentPage?.title || this.currentPage?.name || "",
807
+ currentPageIcon: this.currentPage?.icon || this.currentPage?.pageIcon || "",
808
+ currentPageDescription: this.showPageDescription ? this.currentPage?.description : "",
809
+ // Sidebar toggle
810
+ showSidebarToggle: this.config.showSidebarToggle,
811
+ sidebarToggleAction: this.config.sidebarToggleAction,
812
+ // Display mode
813
+ displayMode: this.displayMode
814
+ };
815
+ }
816
+ /**
817
+ * Process right items configuration
818
+ */
819
+ processRightItems(rightItems) {
820
+ return this.filterItemsByPermissions(rightItems).map((item) => {
821
+ const processedItem = { ...item };
822
+ if (item.items) {
823
+ processedItem.items = this.filterItemsByPermissions(item.items);
824
+ }
825
+ if (item.type === "group-selector") {
826
+ processedItem.isGroupSelector = true;
827
+ processedItem.isDropdown = false;
828
+ processedItem.isButton = false;
829
+ const groupSelectorOptions = {
830
+ containerId: `group-selector-${item.id || "default"}`
831
+ };
832
+ if (item.Collection !== void 0) groupSelectorOptions.Collection = item.Collection;
833
+ if (item.collection !== void 0) groupSelectorOptions.collection = item.collection;
834
+ if (item.currentGroup !== void 0) groupSelectorOptions.currentGroup = item.currentGroup;
835
+ if (item.buttonClass !== void 0) groupSelectorOptions.buttonClass = item.buttonClass;
836
+ if (item.buttonIcon !== void 0) groupSelectorOptions.buttonIcon = item.buttonIcon;
837
+ if (item.defaultText !== void 0) groupSelectorOptions.defaultText = item.defaultText;
838
+ if (item.itemTemplate !== void 0) groupSelectorOptions.itemTemplate = item.itemTemplate;
839
+ if (item.searchFields !== void 0) groupSelectorOptions.searchFields = item.searchFields;
840
+ if (item.headerText !== void 0) groupSelectorOptions.headerText = item.headerText;
841
+ if (item.searchPlaceholder !== void 0) groupSelectorOptions.searchPlaceholder = item.searchPlaceholder;
842
+ if (item.autoSetActiveGroup !== void 0) groupSelectorOptions.autoSetActiveGroup = item.autoSetActiveGroup;
843
+ if (item.onGroupSelected !== void 0) groupSelectorOptions.onGroupSelected = item.onGroupSelected;
844
+ const groupSelector = new GroupSelectorButton(groupSelectorOptions);
845
+ this.groupSelectorButton = groupSelector;
846
+ this.addChild(groupSelector);
847
+ } else if (processedItem.items && processedItem.items.length > 0) {
848
+ processedItem.isDropdown = true;
849
+ processedItem.isButton = false;
850
+ } else if (item.buttonClass) {
851
+ processedItem.isButton = true;
852
+ processedItem.isDropdown = false;
853
+ } else {
854
+ processedItem.isButton = false;
855
+ processedItem.isDropdown = false;
856
+ }
857
+ if (item.handler) {
858
+ this.rightItemHandlers = this.rightItemHandlers || /* @__PURE__ */ new Map();
859
+ this.rightItemHandlers.set(item.id, item.handler);
860
+ }
861
+ return processedItem;
862
+ });
863
+ }
864
+ /**
865
+ * Setup listeners for page change events
866
+ */
867
+ setupPageListeners() {
868
+ this.getApp().events.on(["page:show", "page:hide", "page:denied"], (data) => {
869
+ this.onPageChanged(data);
870
+ });
871
+ }
872
+ /**
873
+ * Setup listeners for group change events
874
+ */
875
+ setupGroupListeners() {
876
+ const app = this.getApp();
877
+ if (!app?.events) return;
878
+ app.events.on(["group:changed", "group:loaded"], (data) => {
879
+ if (data?.group) {
880
+ this.currentGroup = data.group;
881
+ }
882
+ if (this.displayMode === "group" || this.displayMode === "group_page_titles") {
883
+ if (this.mounted) {
884
+ this.render();
885
+ }
886
+ }
887
+ });
888
+ }
889
+ /**
890
+ * Handle page before change event
891
+ * @param {object} data - Event data
892
+ */
893
+ onPageBeforeChange(data) {
894
+ if (this.displayMode === "page" || this.displayMode === "both") ;
895
+ }
896
+ /**
897
+ * Handle page changed event
898
+ * @param {object} data - Event data with previousPage and currentPage
899
+ */
900
+ onPageChanged(data) {
901
+ this.previousPage = this.currentPage;
902
+ this.currentPage = data.page;
903
+ if (this.displayMode === "page" || this.displayMode === "both") {
904
+ this.updatePageDisplay();
905
+ }
906
+ if (this.displayMode === "menu" || this.displayMode === "both") {
907
+ if (this.currentPage && this.currentPage.route) {
908
+ this.updateActiveItem(this.currentPage.route);
909
+ }
910
+ }
911
+ }
912
+ /**
913
+ * Update the display to show current page info
914
+ */
915
+ updatePageDisplay() {
916
+ if (!this.currentPage) return;
917
+ if (this.mounted) {
918
+ this.render();
919
+ }
920
+ }
921
+ updateActiveItem(currentRoute) {
922
+ const normalizeRoute = (route) => {
923
+ if (!route) return "/";
924
+ return route.startsWith("/") ? route : `/${route}`;
925
+ };
926
+ const normalizedCurrentRoute = normalizeRoute(currentRoute);
927
+ const navItems = this.data.navItems.map((item) => {
928
+ const normalizedItemRoute = normalizeRoute(item.route);
929
+ let isActive = false;
930
+ if (normalizedItemRoute === "/" && normalizedCurrentRoute === "/") {
931
+ isActive = true;
932
+ } else if (normalizedItemRoute !== "/" && normalizedCurrentRoute !== "/") {
933
+ isActive = normalizedCurrentRoute.startsWith(normalizedItemRoute) || normalizedCurrentRoute === normalizedItemRoute;
934
+ }
935
+ return {
936
+ ...item,
937
+ active: isActive
938
+ };
939
+ });
940
+ this.updateData({ navItems }, true);
941
+ }
942
+ onPassThruActionProfile() {
943
+ this.getApp().events.emit("portal:action", { action: "profile" });
944
+ }
945
+ onActionSettings() {
946
+ this.getApp().events.emit("portal:action", { action: "settings" });
947
+ }
948
+ onActionLogout() {
949
+ this.getApp().events.emit("auth:logout", { action: "logout" });
950
+ }
951
+ /**
952
+ * Handle open group selector action (from clicking group name in brand)
953
+ */
954
+ async onActionOpenGroupSelector(event) {
955
+ if (this.groupSelectorButton) {
956
+ await this.groupSelectorButton.onActionShowSelector(event);
957
+ return true;
958
+ }
959
+ const { GroupList: GroupList2 } = await import("./ContextMenu-Ckttp-rD.js").then((n) => n.k);
960
+ const tempSelector = new GroupSelectorButton({
961
+ Collection: GroupList2,
962
+ currentGroup: this.getApp()?.activeGroup
963
+ });
964
+ await tempSelector.onActionShowSelector(event);
965
+ return true;
966
+ }
967
+ /**
968
+ * Handle dynamic action dispatch for right items
969
+ */
970
+ async handleAction(actionName, event, element) {
971
+ const itemId = element.getAttribute("data-id");
972
+ if (itemId && this.rightItemHandlers && this.rightItemHandlers.has(itemId)) {
973
+ const handler = this.rightItemHandlers.get(itemId);
974
+ if (typeof handler === "function") {
975
+ return await handler.call(this, actionName, event, element);
976
+ }
977
+ }
978
+ const methodName = `onAction${actionName.charAt(0).toUpperCase() + actionName.slice(1).replace(/-([a-z])/g, (g) => g[1].toUpperCase())}`;
979
+ if (typeof this[methodName] === "function") {
980
+ return await this[methodName](event, element);
981
+ }
982
+ this.emit("action", {
983
+ action: actionName,
984
+ event,
985
+ element,
986
+ topnav: this
987
+ });
988
+ }
989
+ /**
990
+ * Handle default actions by searching through rightItems and navItems
991
+ */
992
+ async onActionDefault(action, event, el) {
993
+ if (this.config.navItems) {
994
+ for (const item of this.config.navItems) {
995
+ if (item.action === action && item.handler) {
996
+ await item.handler.call(this, action, event, el);
997
+ return true;
998
+ }
999
+ }
1000
+ }
1001
+ if (this.config.rightItems) {
1002
+ for (const item of this.config.rightItems) {
1003
+ if (item.action === action && item.handler) {
1004
+ await item.handler.call(this, action, event, el);
1005
+ return true;
1006
+ }
1007
+ if (item.items) {
1008
+ for (const subItem of item.items) {
1009
+ if (subItem.action === action && subItem.handler) {
1010
+ await subItem.handler.call(this, action, event, el);
1011
+ return true;
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+ }
1017
+ this.getApp().events.emit("portal:action", { action, event, el });
1018
+ return false;
1019
+ }
1020
+ /**
1021
+ * Filter items by user permissions
1022
+ */
1023
+ filterItemsByPermissions(items) {
1024
+ if (!items) return [];
1025
+ const app = this.getApp();
1026
+ const activeUser = app?.activeUser;
1027
+ return items.filter((item) => {
1028
+ if (item.permissions && activeUser) {
1029
+ return activeUser.hasPermission(item.permissions);
1030
+ }
1031
+ return true;
1032
+ });
1033
+ }
1034
+ }
1035
+ export {
1036
+ SimpleSearchView as S,
1037
+ TopNav as T
1038
+ };
1039
+ //# sourceMappingURL=TopNav-DGtRj5F4.js.map