web-mojo 2.1.965 → 2.1.966

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 (47) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.es.js +7 -7
  3. package/dist/auth.cjs.js +1 -1
  4. package/dist/auth.cjs.js.map +1 -1
  5. package/dist/auth.es.js +2 -2
  6. package/dist/auth.es.js.map +1 -1
  7. package/dist/charts.cjs.js +1 -1
  8. package/dist/charts.es.js +2 -2
  9. package/dist/chunks/{ChatView-CZas69B4.js → ChatView-CV9aH11Q.js} +9 -3
  10. package/dist/chunks/ChatView-CV9aH11Q.js.map +1 -0
  11. package/dist/chunks/ChatView-ChDr4Ioj.js +2 -0
  12. package/dist/chunks/ChatView-ChDr4Ioj.js.map +1 -0
  13. package/dist/chunks/{Dialog-PtWBLCxR.js → Dialog-CWlVHv1e.js} +3 -3
  14. package/dist/chunks/{Dialog-PtWBLCxR.js.map → Dialog-CWlVHv1e.js.map} +1 -1
  15. package/dist/chunks/{Dialog-BB9GfRUS.js → Dialog-DVud9izE.js} +2 -2
  16. package/dist/chunks/{Dialog-BB9GfRUS.js.map → Dialog-DVud9izE.js.map} +1 -1
  17. package/dist/chunks/{FormView-CVJSmGHp.js → FormView-B6RpE-xh.js} +2 -2
  18. package/dist/chunks/{FormView-CVJSmGHp.js.map → FormView-B6RpE-xh.js.map} +1 -1
  19. package/dist/chunks/{FormView-DI6XsGAt.js → FormView-DG32DDOa.js} +4 -1
  20. package/dist/chunks/FormView-DG32DDOa.js.map +1 -0
  21. package/dist/chunks/{MetricsMiniChartWidget-kfSscCJC.js → MetricsMiniChartWidget-CFoUehOz.js} +2 -2
  22. package/dist/chunks/{MetricsMiniChartWidget-kfSscCJC.js.map → MetricsMiniChartWidget-CFoUehOz.js.map} +1 -1
  23. package/dist/chunks/{MetricsMiniChartWidget-DV4PG0mu.js → MetricsMiniChartWidget-D3wJf0_r.js} +2 -2
  24. package/dist/chunks/{MetricsMiniChartWidget-DV4PG0mu.js.map → MetricsMiniChartWidget-D3wJf0_r.js.map} +1 -1
  25. package/dist/chunks/{PDFViewer-B_BAtt64.js → PDFViewer-CG4fmE25.js} +2 -2
  26. package/dist/chunks/{PDFViewer-B_BAtt64.js.map → PDFViewer-CG4fmE25.js.map} +1 -1
  27. package/dist/chunks/{PDFViewer-DH3JtwMX.js → PDFViewer-CYubJH5C.js} +2 -2
  28. package/dist/chunks/{PDFViewer-DH3JtwMX.js.map → PDFViewer-CYubJH5C.js.map} +1 -1
  29. package/dist/chunks/{TopNav-bvFc_RNs.js → TopNav--Yi8OpsJ.js} +2 -2
  30. package/dist/chunks/{TopNav-bvFc_RNs.js.map → TopNav--Yi8OpsJ.js.map} +1 -1
  31. package/dist/chunks/{TopNav-Boud6DBK.js → TopNav-D5XFAFhG.js} +2 -2
  32. package/dist/chunks/{TopNav-Boud6DBK.js.map → TopNav-D5XFAFhG.js.map} +1 -1
  33. package/dist/chunks/{WebApp-D5wBRHAI.js → WebApp-BaUWZs9G.js} +2 -2
  34. package/dist/chunks/{WebApp-D5wBRHAI.js.map → WebApp-BaUWZs9G.js.map} +1 -1
  35. package/dist/chunks/{WebApp-BuKF6sUG.js → WebApp-Cm6xnOsj.js} +13 -13
  36. package/dist/chunks/{WebApp-BuKF6sUG.js.map → WebApp-Cm6xnOsj.js.map} +1 -1
  37. package/dist/docit.cjs.js +1 -1
  38. package/dist/docit.es.js +3 -3
  39. package/dist/index.cjs.js +1 -1
  40. package/dist/index.es.js +7 -7
  41. package/dist/lightbox.cjs.js +1 -1
  42. package/dist/lightbox.es.js +3 -3
  43. package/package.json +1 -1
  44. package/dist/chunks/ChatView-CZas69B4.js.map +0 -1
  45. package/dist/chunks/ChatView-UJAFHqiy.js +0 -2
  46. package/dist/chunks/ChatView-UJAFHqiy.js.map +0 -1
  47. package/dist/chunks/FormView-DI6XsGAt.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"TopNav-Boud6DBK.js","sources":["../../src/core/views/navigation/SimpleSearchView.js","../../src/core/views/navigation/GroupSelectorButton.js","../../src/core/views/navigation/TopNav.js"],"sourcesContent":["/**\n * SimpleSearchView - Generic searchable list component\n * Displays a searchable, scrollable list of items from any Collection\n * Emits item:selected event when user selects an item\n */\n\nimport { View } from '@core/View.js';\n\n/**\n * ResultsView - Internal child view for rendering search results\n * This is only used within SimpleSearchView and handles the scrollable results area\n */\nclass ResultsView extends View {\n constructor(options = {}) {\n super({\n className: 'search-results-view flex-grow-1 overflow-auto d-flex flex-column',\n template: `\n <div class=\"flex-grow-1 overflow-auto\">\n {{#data.loading}}\n <div class=\"text-center p-4\">\n <div class=\"spinner-border spinner-border-sm text-muted\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <div class=\"mt-2 small text-muted\">{{data.loadingText}}</div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <div class=\"simple-search-item position-relative\"\n data-action=\"select-item\"\n data-item-index=\"{{index}}\">\n {{{itemContent}}}\n <i class=\"bi bi-chevron-right position-absolute end-0 top-50 translate-middle-y me-3 text-muted\"></i>\n </div>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class=\"text-center p-4\">\n <i class=\"bi bi-search text-muted mb-2\" style=\"font-size: 1.5rem;\"></i>\n <div class=\"text-muted small\">{{data.noResultsText}}</div>\n <button type=\"button\"\n class=\"btn btn-link btn-sm mt-2 p-0\"\n data-action=\"clear-search\">\n Clear search\n </button>\n </div>\n {{/data.showNoResults}}\n\n {{#data.showEmpty}}\n <div class=\"text-center p-4\">\n <i class=\"{{data.emptyIcon}} text-muted mb-2\" style=\"font-size: 2rem;\"></i>\n <div class=\"text-muted small mb-2\">{{data.emptyText}}</div>\n {{#data.emptySubtext}}\n <div class=\"text-muted\" style=\"font-size: 0.75rem;\">\n {{data.emptySubtext}}\n </div>\n {{/data.emptySubtext}}\n </div>\n {{/data.showEmpty}}\n {{/data.loading}}\n </div>\n\n {{#data.showResultsCount}}\n <div class=\"border-top bg-light p-2 text-center\">\n <small class=\"text-muted\">\n {{data.filteredCount}} of {{data.totalCount}}\n </small>\n </div>\n {{/data.showResultsCount}}\n `,\n ...options\n });\n\n this.parentView = options.parentView;\n }\n\n async handleActionSelectItem(event, element) {\n event.preventDefault();\n const itemIndex = parseInt(element.getAttribute('data-item-index'));\n if (this.parentView) {\n this.parentView.handleItemSelection(itemIndex);\n }\n }\n\n async handleActionClearSearch(event, _element) {\n event.preventDefault();\n\n if (this.parentView) {\n this.parentView.clearSearch();\n }\n }\n}\n\nclass SimpleSearchView extends View {\n constructor(options = {}) {\n super({\n className: 'simple-search-view h-100 d-flex flex-column',\n template: `\n <div class=\"p-3 border-bottom bg-light\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <h6 class=\"text-muted fw-semibold mb-0\">\n {{#data.headerIcon}}<i class=\"{{data.headerIcon}} me-2\"></i>{{/data.headerIcon}}\n {{{data.headerText}}}\n </h6>\n {{#data.showExitButton}}\n <button class=\"btn btn-link p-0 text-muted simple-search-exit-btn\"\n type=\"button\"\n data-action=\"exit-view\"\n title=\"Exit\"\n aria-label=\"Exit view\">\n <i class=\"bi bi-x-lg\" aria-hidden=\"true\"></i>\n </button>\n {{/data.showExitButton}}\n </div>\n <div class=\"position-relative\">\n <input type=\"text\"\n class=\"form-control form-control-sm pe-5\"\n placeholder=\"{{data.searchPlaceholder}}\"\n value=\"{{data.searchValue}}\"\n data-filter=\"live-search\"\n data-filter-debounce=\"{{data.debounceMs}}\"\n data-change-action=\"search-items\">\n <button class=\"btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn\"\n type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\"\n aria-label=\"Clear search\">\n <i class=\"bi bi-x-circle-fill\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <div data-container=\"results\"></div>\n\n {{#data.showFooter}}\n <div class=\"p-3 border-top bg-light\">\n <small class=\"text-muted\">\n <i class=\"{{data.footerIcon}} me-1\"></i>\n {{{data.footerContent}}}\n </small>\n </div>\n {{/data.showFooter}}\n `,\n ...options\n });\n\n // Configuration options\n this.Collection = options.Collection;\n this.collection = options.collection;\n this.itemTemplate = options.itemTemplate || this.getDefaultItemTemplate();\n this.searchFields = options.searchFields || ['name'];\n this.collectionParams = { size: 25, ...options.collectionParams };\n\n // UI text configuration\n this.headerText = options.headerText || 'Select Item';\n this.headerIcon = options.headerIcon || 'bi bi-list';\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n this.loadingText = options.loadingText || 'Loading items...';\n this.noResultsText = options.noResultsText || 'No items match your search';\n this.emptyText = options.emptyText || 'No items available';\n this.emptySubtext = options.emptySubtext || null;\n this.emptyIcon = options.emptyIcon || 'bi bi-inbox';\n this.footerContent = options.footerContent || null;\n this.footerIcon = options.footerIcon || 'bi bi-info-circle';\n this.showExitButton = options.showExitButton || false;\n\n // State\n this.searchValue = '';\n this.filteredItems = [];\n this.loading = false;\n this.hasSearched = false;\n this.searchTimer = null;\n this.debounceMs = options.debounceMs || 300;\n\n // Create results child view\n this.resultsView = new ResultsView({\n parentView: this\n });\n\n if (!this.collection && this.Collection) {\n this.collection = new this.Collection();\n }\n\n // Add as child view\n this.addChild(this.resultsView);\n\n }\n\n onInit() {\n // Initialize collection if provided\n if (this.collection) {\n this.setupCollection();\n }\n\n // Load items on init if collection is available\n if (this.collection && this.options.autoLoad !== false) {\n this.loadItems();\n }\n }\n\n setupCollection() {\n // Set collection parameters\n Object.assign(this.collection.params, this.collectionParams);\n\n // Listen for collection updates\n this.collection.on('fetch:success', () => {\n this.loading = false;\n this.updateFilteredItems();\n });\n\n this.collection.on('fetch:error', () => {\n this.loading = false;\n });\n }\n\n async loadItems() {\n if (!this.collection) {\n console.warn('SimpleSearchView: No collection provided');\n return;\n }\n\n this.loading = true;\n this.updateResultsView();\n\n try {\n await this.collection.fetch();\n this.updateFilteredItems();\n } catch (error) {\n console.error('Error loading items:', error);\n const app = this.getApp();\n app?.showError?.('Failed to load items. Please try again.');\n } finally {\n this.loading = false;\n this.updateFilteredItems();\n }\n }\n\n updateFilteredItems() {\n if (!this.collection) {\n this.filteredItems = [];\n return;\n }\n\n const items = this.collection.toJSON();\n\n if (!this.searchValue || !this.searchValue.trim()) {\n this.filteredItems = items;\n } else {\n const searchTerm = this.searchValue.toLowerCase().trim();\n this.filteredItems = items.filter(item => {\n return this.searchFields.some(field => {\n const value = this.getNestedValue(item, field);\n return value && value.toString().toLowerCase().includes(searchTerm);\n });\n });\n }\n\n this.updateResultsView();\n }\n\n getNestedValue(obj, path) {\n return path.split('.').reduce((current, key) => current?.[key], obj);\n }\n\n async getViewData() {\n return {\n searchValue: this.searchValue,\n showFooter: !!this.footerContent,\n showExitButton: this.showExitButton,\n debounceMs: this.debounceMs,\n\n // UI text\n headerText: this.headerText,\n headerIcon: this.headerIcon,\n searchPlaceholder: this.searchPlaceholder,\n footerContent: this.footerContent,\n footerIcon: this.footerIcon\n };\n }\n\n updateResultsView() {\n if (!this.resultsView) return;\n\n const hasItems = this.collection && this.collection.length() > 0;\n const hasFilteredItems = this.filteredItems.length > 0;\n const hasSearchValue = this.searchValue.length > 0;\n\n // Process items with template\n const processedItems = this.filteredItems.map((item, index) => {\n return {\n ...item,\n index,\n itemContent: this.processItemTemplate(item)\n };\n });\n\n this.resultsView.data = {\n loading: this.loading,\n items: processedItems,\n showEmpty: !this.loading && !hasItems,\n showNoResults: !this.loading && hasItems && !hasFilteredItems && hasSearchValue,\n showResultsCount: !this.loading && hasItems,\n filteredCount: this.filteredItems.length,\n totalCount: this.collection?.restEnabled\n ? (this.collection?.meta?.count || 0)\n : (this.collection?.length() || 0),\n\n // UI text\n loadingText: this.loadingText,\n noResultsText: this.noResultsText,\n emptyText: this.emptyText,\n emptySubtext: this.emptySubtext,\n emptyIcon: this.emptyIcon\n };\n\n this.resultsView.render();\n }\n\n processItemTemplate(item) {\n let template = this.itemTemplate;\n\n // Simple template replacement for item properties\n template = template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, prop) => {\n return this.getNestedValue(item, prop) || '';\n });\n\n return template;\n }\n\n getDefaultItemTemplate() {\n return `\n <div class=\"p-3 border-bottom\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">{{id}}</small>\n </div>\n `;\n }\n\n async onPassThruActionSearchItems(event, element) {\n const searchValue = element.value || '';\n\n console.log(\"search change...\");\n this.searchValue = searchValue;\n this.hasSearched = true;\n\n // Clear existing timer\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n // Debounce the search\n this.performSearch();\n }\n\n async performSearch() {\n const searchParams = { ...this.collectionParams };\n if (this.searchValue && this.searchValue.length > 1) {\n searchParams.search = this.searchValue.trim();\n }\n this.collection.setParams(searchParams, true);\n }\n\n handleItemSelection(itemIndex) {\n if (isNaN(itemIndex) || itemIndex < 0 || itemIndex >= this.filteredItems.length) {\n console.error('Invalid item index:', itemIndex);\n return;\n }\n\n const item = this.filteredItems[itemIndex];\n const model = this.collection ? this.collection.get(item.id) : null;\n\n // Emit selection event\n this.emit('item:selected', {\n item: item,\n model: model,\n index: itemIndex\n });\n }\n\n /**\n * Set the collection for this search view\n */\n setCollection(collection) {\n this.collection = collection;\n this.setupCollection();\n return this;\n }\n\n /**\n * Set the item template\n */\n setItemTemplate(template) {\n this.itemTemplate = template;\n this.updateResultsView();\n return this;\n }\n\n /**\n * Set search fields\n */\n setSearchFields(fields) {\n this.searchFields = Array.isArray(fields) ? fields : [fields];\n return this;\n }\n\n /**\n * Refresh items list\n */\n async refresh() {\n await this.loadItems();\n }\n\n /**\n * Focus the search input\n */\n focusSearch() {\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.focus();\n }\n }\n\n /**\n * Handle exit button click - emits event instead of closing\n */\n async handleActionExitView(event, element) {\n this.emit('exit', { view: this });\n }\n\n /**\n * Clear search and reset\n */\n async handleActionClearSearch(event, element) {\n this.clearSearch();\n }\n\n clearSearch() {\n this.searchValue = '';\n this.hasSearched = false;\n const searchInput = this.element?.querySelector('input[data-change-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = '';\n searchInput.focus();\n }\n this.performSearch();\n }\n\n /**\n * Get the number of available items\n */\n getItemCount() {\n return this.collection ? this.collection.length() : 0;\n }\n\n /**\n * Get the number of filtered items\n */\n getFilteredItemCount() {\n return this.filteredItems.length;\n }\n\n /**\n * Check if items are loaded\n */\n hasItems() {\n return this.getItemCount() > 0;\n }\n\n /**\n * Get current search value\n */\n getSearchValue() {\n return this.searchValue;\n }\n\n /**\n * Set search value programmatically\n */\n setSearchValue(value) {\n this.searchValue = value || '';\n this.hasSearched = !!this.searchValue;\n\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = this.searchValue;\n }\n\n this.performSearch();\n return this;\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n // Mount results view to container if not already mounted\n if (this.resultsView && !this.resultsView.isMounted()) {\n const container = this.element?.querySelector('[data-container=\"results\"]');\n if (container) {\n await this.resultsView.render(true, container);\n }\n }\n\n // Update results view after main render\n this.updateResultsView();\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n if (this.collection) {\n this.collection.off('update');\n }\n\n await super.onBeforeDestroy();\n }\n}\n\nexport default SimpleSearchView;\n","/**\n * GroupSelectorButton - Button that shows current group and opens search dialog\n * Displays active group name in topnav, opens Dialog with SimpleSearchView for selection\n */\n\nimport View from '@core/View.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport SimpleSearchView from '@core/views/navigation/SimpleSearchView.js';\nimport { GroupList } from '@core/models/Group.js';\n\nclass GroupSelectorButton extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'nav-item',\n ...options\n });\n\n const app = this.getApp();\n\n // Auto-detect Collection from app or use GroupList by default\n this.Collection = options.Collection || app?.GroupCollection || GroupList;\n \n // Use existing collection or create new one\n this.collection = options.collection || new this.Collection();\n \n // Auto-detect current group from app\n this.currentGroup = options.currentGroup !== undefined \n ? options.currentGroup \n : app?.activeGroup;\n \n // UI configuration with defaults\n this.buttonClass = options.buttonClass || 'btn btn-link nav-link';\n this.buttonIcon = options.buttonIcon || 'bi-building';\n this.defaultText = options.defaultText || 'Select Group';\n \n // SimpleSearchView configuration\n this.itemTemplate = options.itemTemplate;\n this.searchFields = options.searchFields || ['name'];\n this.headerText = options.headerText || 'Select Group';\n this.searchPlaceholder = options.searchPlaceholder || 'Search groups...';\n \n // Auto group selection handler\n this.autoSetActiveGroup = options.autoSetActiveGroup !== false;\n this.onGroupSelected = options.onGroupSelected;\n \n // Dialog reference\n this.dialog = null;\n\n // Listen for app-level group changes\n if (app?.events) {\n app.events.on('group:changed', (data) => {\n if (data.group !== this.currentGroup) {\n this.setCurrentGroup(data.group);\n }\n });\n }\n }\n\n async getTemplate() {\n return `\n <button class=\"{{buttonClass}}\" \n data-action=\"show-selector\"\n type=\"button\"\n style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n <i class=\"{{buttonIcon}} me-1\"></i>\n <span class=\"group-name\">{{displayName}}</span>\n </button>\n `;\n }\n\n async onBeforeRender() {\n await super.onBeforeRender();\n \n console.log('GroupSelectorButton onBeforeRender - currentGroup:', this.currentGroup?.get?.('name') || this.currentGroup?.name || 'none');\n \n this.buttonClass = this.buttonClass;\n this.buttonIcon = this.buttonIcon;\n this.displayName = this.currentGroup?.get?.('name') || \n this.currentGroup?.name || \n this.defaultText;\n }\n\n /**\n * Show the group selector dialog\n */\n async onActionShowSelector(event) {\n // Create SimpleSearchView instance\n const searchView = new SimpleSearchView({\n Collection: this.Collection,\n collection: this.collection,\n itemTemplate: this.itemTemplate || this.getDefaultItemTemplate(),\n searchFields: this.searchFields,\n headerText: this.headerText,\n searchPlaceholder: this.searchPlaceholder,\n headerIcon: this.buttonIcon,\n showExitButton: false\n });\n\n // Create dialog directly (not using showDialog helper to avoid promise issues)\n this.dialog = new Dialog({\n title: this.headerText,\n body: searchView,\n size: 'md',\n scrollable: true,\n noBodyPadding: true,\n buttons: [],\n closeButton: true\n });\n\n // Listen for item selection (note: event is 'item:selected' not 'item-selected')\n searchView.on('item:selected', (data) => {\n this.handleGroupSelection(data.model || data.item);\n if (this.dialog) {\n this.dialog.hide();\n }\n });\n\n // Clean up dialog reference when closed\n this.dialog.on('hidden', () => {\n this.dialog.destroy();\n this.dialog = null;\n });\n\n // Render and show the dialog\n await this.dialog.render(true, document.body);\n this.dialog.show();\n \n return true; // Indicate action was handled\n }\n\n /**\n * Handle group selection\n */\n handleGroupSelection(group) {\n this.currentGroup = group;\n \n // Update button text\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n this.render();\n\n const app = this.getApp();\n\n // Automatically set active group on app if enabled\n if (this.autoSetActiveGroup && app?.setActiveGroup) {\n app.setActiveGroup(group);\n }\n\n // Call custom handler if provided\n if (this.onGroupSelected) {\n this.onGroupSelected({ group });\n }\n\n // Emit event for parent/app to handle\n this.emit('group-selected', { group });\n \n // Also emit to app events if available\n if (app?.events) {\n app.events.emit('group:selected', { group });\n app.events.emit('group:changed', { group });\n }\n }\n\n /**\n * Default item template for groups (matches Sidebar pattern)\n * Note: data-action and data-item-index are added by ResultsView wrapper\n */\n getDefaultItemTemplate() {\n return `\n <div class=\"d-flex align-items-center p-3 border-bottom\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">#{{id}} {{kind}}</small>\n </div>\n </div>\n `;\n }\n\n /**\n * Set the current group programmatically\n */\n setCurrentGroup(group) {\n this.currentGroup = group;\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n if (this.mounted) {\n this.render();\n }\n }\n\n /**\n * Get the current group\n */\n getCurrentGroup() {\n return this.currentGroup;\n }\n}\n\nexport default GroupSelectorButton;\n","/**\n * TopNav - Bootstrap navbar component for MOJO framework\n * Provides clean, responsive top navigation\n */\n\nimport View from '@core/View.js';\nimport GroupSelectorButton from '@core/views/navigation/GroupSelectorButton.js';\n\nclass TopNav extends View {\n constructor(options = {}) {\n // Define theme-to-class mappings\n const themes = {\n light: 'navbar navbar-expand-lg navbar-light topnav-light',\n dark: 'navbar navbar-expand-lg navbar-dark topnav-dark',\n clean: 'navbar navbar-expand-lg navbar-light topnav-clean',\n gradient: 'navbar navbar-expand-lg navbar-dark topnav-gradient',\n };\n\n // Set a default theme and determine the final class string\n const themeName = options.theme || 'light';\n let navbarClass = themes[themeName] || themes.light;\n\n // Add shadow class if specified\n if (options.shadow) {\n navbarClass += ` topnav-shadow-${options.shadow}`;\n }\n\n super({\n tagName: 'nav',\n className: navbarClass,\n enableTooltips: true,\n style: 'position: relative; z-index: 1030;',\n ...options\n });\n\n // Display mode configuration\n // 'menu' | 'page' | 'both' | 'group' | 'group_page_titles'\n this.displayMode = options.displayMode || 'both';\n this.showPageIcon = options.showPageIcon !== false;\n this.showPageDescription = options.showPageDescription || false;\n this.showBreadcrumbs = options.showBreadcrumbs || false;\n this.groupIcon = options.groupIcon || 'bi-building';\n\n // Current page tracking\n this.currentPage = null;\n this.previousPage = null;\n\n // Store raw config for processing in onBeforeRender\n this.config = {\n brand: options.brand || 'MOJO App',\n brandIcon: options.brandIcon || 'bi bi-play-circle',\n brandRoute: options.brandRoute || '/',\n navItems: options.navItems || [],\n rightItems: options.rightItems || [],\n showSidebarToggle: options.showSidebarToggle || false,\n sidebarToggleAction: options.sidebarToggleAction || 'toggle-sidebar',\n ...options\n };\n this.userMenu = options.userMenu || this.findMenuItem('user');\n if (this.userMenu) this.userMenu.id = \"user\";\n this.loginMenu = options.loginMenu || this.findMenuItem('login');\n\n // Setup page event listeners\n this.setupPageListeners();\n\n // Setup group event listeners for group display modes\n this.setupGroupListeners();\n\n // Store reference to group selector for click-to-open functionality\n this.groupSelectorButton = null;\n\n // Track current group for display modes\n this.currentGroup = null;\n }\n\n findMenuItem(id) {\n let item = this.config.navItems.find(item => item.id === id);\n if (!item) {\n item = this.config.rightItems.find(item => item.id === id);\n }\n return item || null;\n }\n\n replaceMenuItem(id, new_menu) {\n // Find and replace in navItems\n const navIndex = this.config.navItems.findIndex(item => item.id === id);\n if (navIndex !== -1) {\n this.config.navItems[navIndex] = new_menu;\n return true;\n }\n\n // Find and replace in rightItems\n const rightIndex = this.config.rightItems.findIndex(item => item.id === id);\n if (rightIndex !== -1) {\n this.config.rightItems[rightIndex] = new_menu;\n return true;\n }\n\n return false;\n }\n\n setBrand(brand, icon=null) {\n this.config.brand = brand;\n this.config.brandIcon = icon || this.config.brandIcon;\n this.render();\n }\n\n setUser(user) {\n if (!user) {\n this.replaceMenuItem('user', this.loginMenu);\n } else {\n this.userMenu.label = user.get(\"display_name\");\n this.replaceMenuItem('login', this.userMenu);\n }\n this.setModel(user);\n }\n\n _onModelChange() {\n if (this.model) {\n this.userMenu.label = this.model.get(\"display_name\");\n }\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Get template based on display mode\n */\n async getTemplate() {\n return `\n <div class=\"container-fluid\">\n {{#data.showSidebarToggle}}\n <button class=\"topnav-sidebar-toggle me-2\" data-action=\"{{data.sidebarToggleAction}}\" aria-label=\"Toggle Sidebar\">\n <i class=\"bi bi-chevron-right toggle-chevron\"></i>\n </button>\n {{/data.showSidebarToggle}}\n\n {{#data.showGroupInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.groupIcon}}<i class=\"{{data.groupIcon}} me-2\"></i>{{/data.groupIcon}}\n <div>\n <span class=\"topnav-group-name\"\n role=\"button\"\n tabindex=\"0\"\n data-action=\"open-group-selector\"\n style=\"cursor: pointer;\">\n {{data.currentGroupName}}\n </span>\n {{#data.showPageTitle}}\n <span class=\"text-muted mx-2\">|</span>\n <span>{{data.currentPageName}}</span>\n {{/data.showPageTitle}}\n </div>\n </div>\n {{/data.showGroupInfo}}\n\n {{#data.showPageInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.currentPageIcon}}<i class=\"{{data.currentPageIcon}} me-2\"></i>{{/data.currentPageIcon}}\n <div>\n <span>{{data.currentPageName}}</span>\n {{#data.currentPageDescription}}\n <small class=\"d-block\" style=\"font-size: 0.75rem; line-height: 1;\">{{data.currentPageDescription}}</small>\n {{/data.currentPageDescription}}\n </div>\n </div>\n {{/data.showPageInfo}}\n\n {{#data.showBrand}}\n <a class=\"navbar-brand\" href=\"{{data.brandRoute}}\">\n {{#data.brandIcon}}<i class=\"{{data.brandIcon}} me-2\"></i>{{/data.brandIcon}}\n {{data.brand}}\n </a>\n {{/data.showBrand}}\n\n <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#{{data.navbarId}}\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n\n <div class=\"collapse navbar-collapse\" id=\"{{data.navbarId}}\">\n {{#data.showNavItems}}\n <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n {{#data.navItems}}\n <li class=\"nav-item\">\n <a class=\"nav-link {{#active}}active{{/active}}\" href=\"{{route}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{text}}\n </a>\n </li>\n {{/data.navItems}}\n </ul>\n {{/data.showNavItems}}\n\n {{#data.hasRightItems}}\n <div class=\"navbar-nav ms-auto\">\n {{#data.rightItems}}\n {{#isGroupSelector}}\n <div data-container=\"group-selector-{{id}}\"></div>\n {{/isGroupSelector}}\n {{^isGroupSelector}}\n {{#isDropdown}}\n <div class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n {{#items}}\n {{#divider}}\n <li><hr class=\"dropdown-divider\"></li>\n {{/divider}}\n {{^divider}}\n <li>\n <a class=\"dropdown-item\" role=\"button\" {{#action}}data-action=\"{{action}}\"{{/action}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n </li>\n {{/divider}}\n {{/items}}\n </ul>\n </div>\n {{/isDropdown}}\n {{^isDropdown}}\n {{#isButton}}\n <button class=\"{{buttonClass}}\" data-action=\"{{action}}\" data-id=\"{{id}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </button>\n {{/isButton}}\n {{^isButton}}\n <a class=\"nav-link\" href=\"{{href}}\" {{#action}}data-action=\"{{action}}\"{{/action}} {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n {{/isButton}}\n {{/isDropdown}}\n {{/isGroupSelector}}\n {{/data.rightItems}}\n </div>\n {{/data.hasRightItems}}\n </div>\n </div>\n `;\n }\n\n /**\n * Process and normalize data before rendering (like Sidebar)\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n const app = this.getApp();\n // Use cached currentGroup or fall back to app.activeGroup\n const activeGroup = this.currentGroup || app?.activeGroup;\n\n // Determine what to show based on display mode\n const showGroupInfo = this.displayMode === 'group' || this.displayMode === 'group_page_titles';\n const showPageTitle = this.displayMode === 'group_page_titles';\n const showPageInfo = this.displayMode === 'page' || this.displayMode === 'both';\n const showBrand = !showGroupInfo && !showPageInfo;\n const showNavItems = this.displayMode === 'menu' || this.displayMode === 'both';\n\n // Filter navItems based on permissions\n const navItems = this.filterItemsByPermissions(this.config.navItems || []);\n\n // Process right items\n const rightItems = this.processRightItems(this.config.rightItems || []);\n\n this.data = {\n // Brand information\n brand: this.config.brand,\n brandIcon: this.config.brandIcon,\n brandRoute: this.config.brandRoute,\n showBrand: showBrand,\n\n // Navbar configuration\n navbarId: `navbar-${this.id}`,\n\n // Navigation items\n navItems: navItems,\n showNavItems: showNavItems,\n\n // Right items\n rightItems: rightItems,\n hasRightItems: rightItems.length > 0,\n\n // Group display\n showGroupInfo: showGroupInfo,\n showPageTitle: showPageTitle,\n currentGroupName: activeGroup?.get?.('name') || activeGroup?.name || 'Select Group',\n groupIcon: this.groupIcon,\n\n // Page display\n showPageInfo: showPageInfo,\n currentPageName: this.currentPage?.title || this.currentPage?.name || '',\n currentPageIcon: this.currentPage?.icon || this.currentPage?.pageIcon || '',\n currentPageDescription: this.showPageDescription ? this.currentPage?.description : '',\n\n // Sidebar toggle\n showSidebarToggle: this.config.showSidebarToggle,\n sidebarToggleAction: this.config.sidebarToggleAction,\n\n // Display mode\n displayMode: this.displayMode\n };\n }\n\n /**\n * Process right items configuration\n */\n processRightItems(rightItems) {\n return this.filterItemsByPermissions(rightItems).map(item => {\n const processedItem = { ...item };\n\n // Filter dropdown items by permissions if they exist\n if (item.items) {\n processedItem.items = this.filterItemsByPermissions(item.items);\n }\n\n // Check for group selector type\n if (item.type === 'group-selector') {\n processedItem.isGroupSelector = true;\n processedItem.isDropdown = false;\n processedItem.isButton = false;\n\n // Create group selector button with smart defaults\n // Only pass through explicitly provided options\n const groupSelectorOptions = {\n containerId: `group-selector-${item.id || 'default'}`\n };\n\n // Only add options if explicitly provided (allow auto-detection to work)\n if (item.Collection !== undefined) groupSelectorOptions.Collection = item.Collection;\n if (item.collection !== undefined) groupSelectorOptions.collection = item.collection;\n if (item.currentGroup !== undefined) groupSelectorOptions.currentGroup = item.currentGroup;\n if (item.buttonClass !== undefined) groupSelectorOptions.buttonClass = item.buttonClass;\n if (item.buttonIcon !== undefined) groupSelectorOptions.buttonIcon = item.buttonIcon;\n if (item.defaultText !== undefined) groupSelectorOptions.defaultText = item.defaultText;\n if (item.itemTemplate !== undefined) groupSelectorOptions.itemTemplate = item.itemTemplate;\n if (item.searchFields !== undefined) groupSelectorOptions.searchFields = item.searchFields;\n if (item.headerText !== undefined) groupSelectorOptions.headerText = item.headerText;\n if (item.searchPlaceholder !== undefined) groupSelectorOptions.searchPlaceholder = item.searchPlaceholder;\n if (item.autoSetActiveGroup !== undefined) groupSelectorOptions.autoSetActiveGroup = item.autoSetActiveGroup;\n if (item.onGroupSelected !== undefined) groupSelectorOptions.onGroupSelected = item.onGroupSelected;\n\n const groupSelector = new GroupSelectorButton(groupSelectorOptions);\n\n // Store reference for click-to-open functionality\n this.groupSelectorButton = groupSelector;\n\n // Add as child view\n this.addChild(groupSelector);\n\n } else if (processedItem.items && processedItem.items.length > 0) {\n // Dropdown menu\n processedItem.isDropdown = true;\n processedItem.isButton = false;\n } else if (item.buttonClass) {\n // Button\n processedItem.isButton = true;\n processedItem.isDropdown = false;\n } else {\n // Link\n processedItem.isButton = false;\n processedItem.isDropdown = false;\n }\n\n // Store handler if provided\n if (item.handler) {\n this.rightItemHandlers = this.rightItemHandlers || new Map();\n this.rightItemHandlers.set(item.id, item.handler);\n }\n\n return processedItem;\n });\n }\n\n /**\n * Setup listeners for page change events\n */\n setupPageListeners() {\n // Use global MOJO event bus if available\n this.getApp().events.on(\"page:show\", (data) => {\n this.onPageChanged(data);\n });\n }\n\n /**\n * Setup listeners for group change events\n */\n setupGroupListeners() {\n const app = this.getApp();\n if (!app?.events) return;\n\n // Listen for group changes and re-render if showing group info\n app.events.on(['group:changed', 'group:loaded'], (data) => {\n // Update our reference to current group\n if (data?.group) {\n this.currentGroup = data.group;\n }\n\n if (this.displayMode === 'group' || this.displayMode === 'group_page_titles') {\n if (this.mounted) {\n this.render();\n }\n }\n });\n }\n\n /**\n * Handle page before change event\n * @param {object} data - Event data\n */\n onPageBeforeChange(data) {\n // Can be used to show loading state\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n // Optionally show loading indicator\n }\n }\n\n /**\n * Handle page changed event\n * @param {object} data - Event data with previousPage and currentPage\n */\n onPageChanged(data) {\n this.previousPage = this.currentPage;\n this.currentPage = data.page;\n\n // Update display based on mode\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n this.updatePageDisplay();\n }\n\n // Update active menu items\n if (this.displayMode === 'menu' || this.displayMode === 'both') {\n if (this.currentPage && this.currentPage.route) {\n this.updateActiveItem(this.currentPage.route);\n }\n }\n }\n\n /**\n * Update the display to show current page info\n */\n updatePageDisplay() {\n if (!this.currentPage) return;\n\n // Just trigger re-render, onBeforeRender will handle the data processing\n if (this.mounted) {\n this.render();\n }\n }\n\n updateActiveItem(currentRoute) {\n // Normalize routes for comparison\n const normalizeRoute = (route) => {\n if (!route) return '/';\n return route.startsWith('/') ? route : `/${route}`;\n };\n\n const normalizedCurrentRoute = normalizeRoute(currentRoute);\n\n // Update active states with improved matching\n const navItems = this.data.navItems.map(item => {\n const normalizedItemRoute = normalizeRoute(item.route);\n\n // Check for active state\n let isActive = false;\n\n if (normalizedItemRoute === '/' && normalizedCurrentRoute === '/') {\n // Exact match for home route\n isActive = true;\n } else if (normalizedItemRoute !== '/' && normalizedCurrentRoute !== '/') {\n // For non-home routes, check if current route starts with nav item route\n // This allows /users to be active when on /users/123\n isActive = normalizedCurrentRoute.startsWith(normalizedItemRoute) ||\n normalizedCurrentRoute === normalizedItemRoute;\n }\n\n return {\n ...item,\n active: isActive\n };\n });\n\n this.updateData({ navItems }, true);\n }\n\n onPassThruActionProfile() {\n // Implement profile functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"profile\"});\n }\n\n onActionSettings() {\n // Implement settings functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"settings\"});\n }\n\n onActionLogout() {\n // Implement logout functionality here\n this.getApp().events.emit(\"auth:logout\", {action: \"logout\"});\n }\n\n /**\n * Handle open group selector action (from clicking group name in brand)\n */\n async onActionOpenGroupSelector(event) {\n // If we have a group selector button, trigger its dialog\n if (this.groupSelectorButton) {\n await this.groupSelectorButton.onActionShowSelector(event);\n return true;\n }\n\n // If no group selector in rightItems, create a temporary one\n const { GroupList } = await import('@core/models/Group.js');\n const tempSelector = new GroupSelectorButton({\n Collection: GroupList,\n currentGroup: this.getApp()?.activeGroup\n });\n\n await tempSelector.onActionShowSelector(event);\n return true;\n }\n\n /**\n * Handle dynamic action dispatch for right items\n */\n async handleAction(actionName, event, element) {\n // Check for custom handler first\n const itemId = element.getAttribute('data-id');\n if (itemId && this.rightItemHandlers && this.rightItemHandlers.has(itemId)) {\n const handler = this.rightItemHandlers.get(itemId);\n if (typeof handler === 'function') {\n return await handler.call(this, actionName, event, element);\n }\n }\n\n // Fallback to default action methods\n const methodName = `onAction${actionName.charAt(0).toUpperCase() + actionName.slice(1).replace(/-([a-z])/g, (g) => g[1].toUpperCase())}`;\n if (typeof this[methodName] === 'function') {\n return await this[methodName](event, element);\n }\n\n // Emit action event if no handler found\n this.emit('action', {\n action: actionName,\n event: event,\n element: element,\n topnav: this\n });\n }\n\n /**\n * Handle default actions by searching through rightItems and navItems\n */\n async onActionDefault(action, event, el) {\n // Check navItems first\n if (this.config.navItems) {\n for (const item of this.config.navItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n\n // Check rightItems\n if (this.config.rightItems) {\n for (const item of this.config.rightItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n // Also check dropdown items\n if (item.items) {\n for (const subItem of item.items) {\n if (subItem.action === action && subItem.handler) {\n await subItem.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n }\n }\n\n this.getApp().events.emit(\"portal:action\", { action, event, el });\n\n return false;\n }\n\n /**\n * Filter items by user permissions\n */\n filterItemsByPermissions(items) {\n if (!items) return [];\n\n const app = this.getApp();\n const activeUser = app?.activeUser;\n\n return items.filter(item => {\n // If item has permissions and user exists, check permissions\n if (item.permissions && activeUser) {\n return activeUser.hasPermission(item.permissions);\n }\n // If no permissions required or no user, show the item\n return true;\n });\n }\n\n}\n\nexport default TopNav;\n"],"names":["item","GroupList"],"mappings":"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"}
1
+ {"version":3,"file":"TopNav-D5XFAFhG.js","sources":["../../src/core/views/navigation/SimpleSearchView.js","../../src/core/views/navigation/GroupSelectorButton.js","../../src/core/views/navigation/TopNav.js"],"sourcesContent":["/**\n * SimpleSearchView - Generic searchable list component\n * Displays a searchable, scrollable list of items from any Collection\n * Emits item:selected event when user selects an item\n */\n\nimport { View } from '@core/View.js';\n\n/**\n * ResultsView - Internal child view for rendering search results\n * This is only used within SimpleSearchView and handles the scrollable results area\n */\nclass ResultsView extends View {\n constructor(options = {}) {\n super({\n className: 'search-results-view flex-grow-1 overflow-auto d-flex flex-column',\n template: `\n <div class=\"flex-grow-1 overflow-auto\">\n {{#data.loading}}\n <div class=\"text-center p-4\">\n <div class=\"spinner-border spinner-border-sm text-muted\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <div class=\"mt-2 small text-muted\">{{data.loadingText}}</div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <div class=\"simple-search-item position-relative\"\n data-action=\"select-item\"\n data-item-index=\"{{index}}\">\n {{{itemContent}}}\n <i class=\"bi bi-chevron-right position-absolute end-0 top-50 translate-middle-y me-3 text-muted\"></i>\n </div>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class=\"text-center p-4\">\n <i class=\"bi bi-search text-muted mb-2\" style=\"font-size: 1.5rem;\"></i>\n <div class=\"text-muted small\">{{data.noResultsText}}</div>\n <button type=\"button\"\n class=\"btn btn-link btn-sm mt-2 p-0\"\n data-action=\"clear-search\">\n Clear search\n </button>\n </div>\n {{/data.showNoResults}}\n\n {{#data.showEmpty}}\n <div class=\"text-center p-4\">\n <i class=\"{{data.emptyIcon}} text-muted mb-2\" style=\"font-size: 2rem;\"></i>\n <div class=\"text-muted small mb-2\">{{data.emptyText}}</div>\n {{#data.emptySubtext}}\n <div class=\"text-muted\" style=\"font-size: 0.75rem;\">\n {{data.emptySubtext}}\n </div>\n {{/data.emptySubtext}}\n </div>\n {{/data.showEmpty}}\n {{/data.loading}}\n </div>\n\n {{#data.showResultsCount}}\n <div class=\"border-top bg-light p-2 text-center\">\n <small class=\"text-muted\">\n {{data.filteredCount}} of {{data.totalCount}}\n </small>\n </div>\n {{/data.showResultsCount}}\n `,\n ...options\n });\n\n this.parentView = options.parentView;\n }\n\n async handleActionSelectItem(event, element) {\n event.preventDefault();\n const itemIndex = parseInt(element.getAttribute('data-item-index'));\n if (this.parentView) {\n this.parentView.handleItemSelection(itemIndex);\n }\n }\n\n async handleActionClearSearch(event, _element) {\n event.preventDefault();\n\n if (this.parentView) {\n this.parentView.clearSearch();\n }\n }\n}\n\nclass SimpleSearchView extends View {\n constructor(options = {}) {\n super({\n className: 'simple-search-view h-100 d-flex flex-column',\n template: `\n <div class=\"p-3 border-bottom bg-light\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <h6 class=\"text-muted fw-semibold mb-0\">\n {{#data.headerIcon}}<i class=\"{{data.headerIcon}} me-2\"></i>{{/data.headerIcon}}\n {{{data.headerText}}}\n </h6>\n {{#data.showExitButton}}\n <button class=\"btn btn-link p-0 text-muted simple-search-exit-btn\"\n type=\"button\"\n data-action=\"exit-view\"\n title=\"Exit\"\n aria-label=\"Exit view\">\n <i class=\"bi bi-x-lg\" aria-hidden=\"true\"></i>\n </button>\n {{/data.showExitButton}}\n </div>\n <div class=\"position-relative\">\n <input type=\"text\"\n class=\"form-control form-control-sm pe-5\"\n placeholder=\"{{data.searchPlaceholder}}\"\n value=\"{{data.searchValue}}\"\n data-filter=\"live-search\"\n data-filter-debounce=\"{{data.debounceMs}}\"\n data-change-action=\"search-items\">\n <button class=\"btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn\"\n type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\"\n aria-label=\"Clear search\">\n <i class=\"bi bi-x-circle-fill\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <div data-container=\"results\"></div>\n\n {{#data.showFooter}}\n <div class=\"p-3 border-top bg-light\">\n <small class=\"text-muted\">\n <i class=\"{{data.footerIcon}} me-1\"></i>\n {{{data.footerContent}}}\n </small>\n </div>\n {{/data.showFooter}}\n `,\n ...options\n });\n\n // Configuration options\n this.Collection = options.Collection;\n this.collection = options.collection;\n this.itemTemplate = options.itemTemplate || this.getDefaultItemTemplate();\n this.searchFields = options.searchFields || ['name'];\n this.collectionParams = { size: 25, ...options.collectionParams };\n\n // UI text configuration\n this.headerText = options.headerText || 'Select Item';\n this.headerIcon = options.headerIcon || 'bi bi-list';\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n this.loadingText = options.loadingText || 'Loading items...';\n this.noResultsText = options.noResultsText || 'No items match your search';\n this.emptyText = options.emptyText || 'No items available';\n this.emptySubtext = options.emptySubtext || null;\n this.emptyIcon = options.emptyIcon || 'bi bi-inbox';\n this.footerContent = options.footerContent || null;\n this.footerIcon = options.footerIcon || 'bi bi-info-circle';\n this.showExitButton = options.showExitButton || false;\n\n // State\n this.searchValue = '';\n this.filteredItems = [];\n this.loading = false;\n this.hasSearched = false;\n this.searchTimer = null;\n this.debounceMs = options.debounceMs || 300;\n\n // Create results child view\n this.resultsView = new ResultsView({\n parentView: this\n });\n\n if (!this.collection && this.Collection) {\n this.collection = new this.Collection();\n }\n\n // Add as child view\n this.addChild(this.resultsView);\n\n }\n\n onInit() {\n // Initialize collection if provided\n if (this.collection) {\n this.setupCollection();\n }\n\n // Load items on init if collection is available\n if (this.collection && this.options.autoLoad !== false) {\n this.loadItems();\n }\n }\n\n setupCollection() {\n // Set collection parameters\n Object.assign(this.collection.params, this.collectionParams);\n\n // Listen for collection updates\n this.collection.on('fetch:success', () => {\n this.loading = false;\n this.updateFilteredItems();\n });\n\n this.collection.on('fetch:error', () => {\n this.loading = false;\n });\n }\n\n async loadItems() {\n if (!this.collection) {\n console.warn('SimpleSearchView: No collection provided');\n return;\n }\n\n this.loading = true;\n this.updateResultsView();\n\n try {\n await this.collection.fetch();\n this.updateFilteredItems();\n } catch (error) {\n console.error('Error loading items:', error);\n const app = this.getApp();\n app?.showError?.('Failed to load items. Please try again.');\n } finally {\n this.loading = false;\n this.updateFilteredItems();\n }\n }\n\n updateFilteredItems() {\n if (!this.collection) {\n this.filteredItems = [];\n return;\n }\n\n const items = this.collection.toJSON();\n\n if (!this.searchValue || !this.searchValue.trim()) {\n this.filteredItems = items;\n } else {\n const searchTerm = this.searchValue.toLowerCase().trim();\n this.filteredItems = items.filter(item => {\n return this.searchFields.some(field => {\n const value = this.getNestedValue(item, field);\n return value && value.toString().toLowerCase().includes(searchTerm);\n });\n });\n }\n\n this.updateResultsView();\n }\n\n getNestedValue(obj, path) {\n return path.split('.').reduce((current, key) => current?.[key], obj);\n }\n\n async getViewData() {\n return {\n searchValue: this.searchValue,\n showFooter: !!this.footerContent,\n showExitButton: this.showExitButton,\n debounceMs: this.debounceMs,\n\n // UI text\n headerText: this.headerText,\n headerIcon: this.headerIcon,\n searchPlaceholder: this.searchPlaceholder,\n footerContent: this.footerContent,\n footerIcon: this.footerIcon\n };\n }\n\n updateResultsView() {\n if (!this.resultsView) return;\n\n const hasItems = this.collection && this.collection.length() > 0;\n const hasFilteredItems = this.filteredItems.length > 0;\n const hasSearchValue = this.searchValue.length > 0;\n\n // Process items with template\n const processedItems = this.filteredItems.map((item, index) => {\n return {\n ...item,\n index,\n itemContent: this.processItemTemplate(item)\n };\n });\n\n this.resultsView.data = {\n loading: this.loading,\n items: processedItems,\n showEmpty: !this.loading && !hasItems,\n showNoResults: !this.loading && hasItems && !hasFilteredItems && hasSearchValue,\n showResultsCount: !this.loading && hasItems,\n filteredCount: this.filteredItems.length,\n totalCount: this.collection?.restEnabled\n ? (this.collection?.meta?.count || 0)\n : (this.collection?.length() || 0),\n\n // UI text\n loadingText: this.loadingText,\n noResultsText: this.noResultsText,\n emptyText: this.emptyText,\n emptySubtext: this.emptySubtext,\n emptyIcon: this.emptyIcon\n };\n\n this.resultsView.render();\n }\n\n processItemTemplate(item) {\n let template = this.itemTemplate;\n\n // Simple template replacement for item properties\n template = template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, prop) => {\n return this.getNestedValue(item, prop) || '';\n });\n\n return template;\n }\n\n getDefaultItemTemplate() {\n return `\n <div class=\"p-3 border-bottom\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">{{id}}</small>\n </div>\n `;\n }\n\n async onPassThruActionSearchItems(event, element) {\n const searchValue = element.value || '';\n\n console.log(\"search change...\");\n this.searchValue = searchValue;\n this.hasSearched = true;\n\n // Clear existing timer\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n // Debounce the search\n this.performSearch();\n }\n\n async performSearch() {\n const searchParams = { ...this.collectionParams };\n if (this.searchValue && this.searchValue.length > 1) {\n searchParams.search = this.searchValue.trim();\n }\n this.collection.setParams(searchParams, true);\n }\n\n handleItemSelection(itemIndex) {\n if (isNaN(itemIndex) || itemIndex < 0 || itemIndex >= this.filteredItems.length) {\n console.error('Invalid item index:', itemIndex);\n return;\n }\n\n const item = this.filteredItems[itemIndex];\n const model = this.collection ? this.collection.get(item.id) : null;\n\n // Emit selection event\n this.emit('item:selected', {\n item: item,\n model: model,\n index: itemIndex\n });\n }\n\n /**\n * Set the collection for this search view\n */\n setCollection(collection) {\n this.collection = collection;\n this.setupCollection();\n return this;\n }\n\n /**\n * Set the item template\n */\n setItemTemplate(template) {\n this.itemTemplate = template;\n this.updateResultsView();\n return this;\n }\n\n /**\n * Set search fields\n */\n setSearchFields(fields) {\n this.searchFields = Array.isArray(fields) ? fields : [fields];\n return this;\n }\n\n /**\n * Refresh items list\n */\n async refresh() {\n await this.loadItems();\n }\n\n /**\n * Focus the search input\n */\n focusSearch() {\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.focus();\n }\n }\n\n /**\n * Handle exit button click - emits event instead of closing\n */\n async handleActionExitView(event, element) {\n this.emit('exit', { view: this });\n }\n\n /**\n * Clear search and reset\n */\n async handleActionClearSearch(event, element) {\n this.clearSearch();\n }\n\n clearSearch() {\n this.searchValue = '';\n this.hasSearched = false;\n const searchInput = this.element?.querySelector('input[data-change-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = '';\n searchInput.focus();\n }\n this.performSearch();\n }\n\n /**\n * Get the number of available items\n */\n getItemCount() {\n return this.collection ? this.collection.length() : 0;\n }\n\n /**\n * Get the number of filtered items\n */\n getFilteredItemCount() {\n return this.filteredItems.length;\n }\n\n /**\n * Check if items are loaded\n */\n hasItems() {\n return this.getItemCount() > 0;\n }\n\n /**\n * Get current search value\n */\n getSearchValue() {\n return this.searchValue;\n }\n\n /**\n * Set search value programmatically\n */\n setSearchValue(value) {\n this.searchValue = value || '';\n this.hasSearched = !!this.searchValue;\n\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = this.searchValue;\n }\n\n this.performSearch();\n return this;\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n // Mount results view to container if not already mounted\n if (this.resultsView && !this.resultsView.isMounted()) {\n const container = this.element?.querySelector('[data-container=\"results\"]');\n if (container) {\n await this.resultsView.render(true, container);\n }\n }\n\n // Update results view after main render\n this.updateResultsView();\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n if (this.collection) {\n this.collection.off('update');\n }\n\n await super.onBeforeDestroy();\n }\n}\n\nexport default SimpleSearchView;\n","/**\n * GroupSelectorButton - Button that shows current group and opens search dialog\n * Displays active group name in topnav, opens Dialog with SimpleSearchView for selection\n */\n\nimport View from '@core/View.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport SimpleSearchView from '@core/views/navigation/SimpleSearchView.js';\nimport { GroupList } from '@core/models/Group.js';\n\nclass GroupSelectorButton extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'nav-item',\n ...options\n });\n\n const app = this.getApp();\n\n // Auto-detect Collection from app or use GroupList by default\n this.Collection = options.Collection || app?.GroupCollection || GroupList;\n \n // Use existing collection or create new one\n this.collection = options.collection || new this.Collection();\n \n // Auto-detect current group from app\n this.currentGroup = options.currentGroup !== undefined \n ? options.currentGroup \n : app?.activeGroup;\n \n // UI configuration with defaults\n this.buttonClass = options.buttonClass || 'btn btn-link nav-link';\n this.buttonIcon = options.buttonIcon || 'bi-building';\n this.defaultText = options.defaultText || 'Select Group';\n \n // SimpleSearchView configuration\n this.itemTemplate = options.itemTemplate;\n this.searchFields = options.searchFields || ['name'];\n this.headerText = options.headerText || 'Select Group';\n this.searchPlaceholder = options.searchPlaceholder || 'Search groups...';\n \n // Auto group selection handler\n this.autoSetActiveGroup = options.autoSetActiveGroup !== false;\n this.onGroupSelected = options.onGroupSelected;\n \n // Dialog reference\n this.dialog = null;\n\n // Listen for app-level group changes\n if (app?.events) {\n app.events.on('group:changed', (data) => {\n if (data.group !== this.currentGroup) {\n this.setCurrentGroup(data.group);\n }\n });\n }\n }\n\n async getTemplate() {\n return `\n <button class=\"{{buttonClass}}\" \n data-action=\"show-selector\"\n type=\"button\"\n style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n <i class=\"{{buttonIcon}} me-1\"></i>\n <span class=\"group-name\">{{displayName}}</span>\n </button>\n `;\n }\n\n async onBeforeRender() {\n await super.onBeforeRender();\n \n console.log('GroupSelectorButton onBeforeRender - currentGroup:', this.currentGroup?.get?.('name') || this.currentGroup?.name || 'none');\n \n this.buttonClass = this.buttonClass;\n this.buttonIcon = this.buttonIcon;\n this.displayName = this.currentGroup?.get?.('name') || \n this.currentGroup?.name || \n this.defaultText;\n }\n\n /**\n * Show the group selector dialog\n */\n async onActionShowSelector(event) {\n // Create SimpleSearchView instance\n const searchView = new SimpleSearchView({\n Collection: this.Collection,\n collection: this.collection,\n itemTemplate: this.itemTemplate || this.getDefaultItemTemplate(),\n searchFields: this.searchFields,\n headerText: this.headerText,\n searchPlaceholder: this.searchPlaceholder,\n headerIcon: this.buttonIcon,\n showExitButton: false\n });\n\n // Create dialog directly (not using showDialog helper to avoid promise issues)\n this.dialog = new Dialog({\n title: this.headerText,\n body: searchView,\n size: 'md',\n scrollable: true,\n noBodyPadding: true,\n buttons: [],\n closeButton: true\n });\n\n // Listen for item selection (note: event is 'item:selected' not 'item-selected')\n searchView.on('item:selected', (data) => {\n this.handleGroupSelection(data.model || data.item);\n if (this.dialog) {\n this.dialog.hide();\n }\n });\n\n // Clean up dialog reference when closed\n this.dialog.on('hidden', () => {\n this.dialog.destroy();\n this.dialog = null;\n });\n\n // Render and show the dialog\n await this.dialog.render(true, document.body);\n this.dialog.show();\n \n return true; // Indicate action was handled\n }\n\n /**\n * Handle group selection\n */\n handleGroupSelection(group) {\n this.currentGroup = group;\n \n // Update button text\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n this.render();\n\n const app = this.getApp();\n\n // Automatically set active group on app if enabled\n if (this.autoSetActiveGroup && app?.setActiveGroup) {\n app.setActiveGroup(group);\n }\n\n // Call custom handler if provided\n if (this.onGroupSelected) {\n this.onGroupSelected({ group });\n }\n\n // Emit event for parent/app to handle\n this.emit('group-selected', { group });\n \n // Also emit to app events if available\n if (app?.events) {\n app.events.emit('group:selected', { group });\n app.events.emit('group:changed', { group });\n }\n }\n\n /**\n * Default item template for groups (matches Sidebar pattern)\n * Note: data-action and data-item-index are added by ResultsView wrapper\n */\n getDefaultItemTemplate() {\n return `\n <div class=\"d-flex align-items-center p-3 border-bottom\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">#{{id}} {{kind}}</small>\n </div>\n </div>\n `;\n }\n\n /**\n * Set the current group programmatically\n */\n setCurrentGroup(group) {\n this.currentGroup = group;\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n if (this.mounted) {\n this.render();\n }\n }\n\n /**\n * Get the current group\n */\n getCurrentGroup() {\n return this.currentGroup;\n }\n}\n\nexport default GroupSelectorButton;\n","/**\n * TopNav - Bootstrap navbar component for MOJO framework\n * Provides clean, responsive top navigation\n */\n\nimport View from '@core/View.js';\nimport GroupSelectorButton from '@core/views/navigation/GroupSelectorButton.js';\n\nclass TopNav extends View {\n constructor(options = {}) {\n // Define theme-to-class mappings\n const themes = {\n light: 'navbar navbar-expand-lg navbar-light topnav-light',\n dark: 'navbar navbar-expand-lg navbar-dark topnav-dark',\n clean: 'navbar navbar-expand-lg navbar-light topnav-clean',\n gradient: 'navbar navbar-expand-lg navbar-dark topnav-gradient',\n };\n\n // Set a default theme and determine the final class string\n const themeName = options.theme || 'light';\n let navbarClass = themes[themeName] || themes.light;\n\n // Add shadow class if specified\n if (options.shadow) {\n navbarClass += ` topnav-shadow-${options.shadow}`;\n }\n\n super({\n tagName: 'nav',\n className: navbarClass,\n enableTooltips: true,\n style: 'position: relative; z-index: 1030;',\n ...options\n });\n\n // Display mode configuration\n // 'menu' | 'page' | 'both' | 'group' | 'group_page_titles'\n this.displayMode = options.displayMode || 'both';\n this.showPageIcon = options.showPageIcon !== false;\n this.showPageDescription = options.showPageDescription || false;\n this.showBreadcrumbs = options.showBreadcrumbs || false;\n this.groupIcon = options.groupIcon || 'bi-building';\n\n // Current page tracking\n this.currentPage = null;\n this.previousPage = null;\n\n // Store raw config for processing in onBeforeRender\n this.config = {\n brand: options.brand || 'MOJO App',\n brandIcon: options.brandIcon || 'bi bi-play-circle',\n brandRoute: options.brandRoute || '/',\n navItems: options.navItems || [],\n rightItems: options.rightItems || [],\n showSidebarToggle: options.showSidebarToggle || false,\n sidebarToggleAction: options.sidebarToggleAction || 'toggle-sidebar',\n ...options\n };\n this.userMenu = options.userMenu || this.findMenuItem('user');\n if (this.userMenu) this.userMenu.id = \"user\";\n this.loginMenu = options.loginMenu || this.findMenuItem('login');\n\n // Setup page event listeners\n this.setupPageListeners();\n\n // Setup group event listeners for group display modes\n this.setupGroupListeners();\n\n // Store reference to group selector for click-to-open functionality\n this.groupSelectorButton = null;\n\n // Track current group for display modes\n this.currentGroup = null;\n }\n\n findMenuItem(id) {\n let item = this.config.navItems.find(item => item.id === id);\n if (!item) {\n item = this.config.rightItems.find(item => item.id === id);\n }\n return item || null;\n }\n\n replaceMenuItem(id, new_menu) {\n // Find and replace in navItems\n const navIndex = this.config.navItems.findIndex(item => item.id === id);\n if (navIndex !== -1) {\n this.config.navItems[navIndex] = new_menu;\n return true;\n }\n\n // Find and replace in rightItems\n const rightIndex = this.config.rightItems.findIndex(item => item.id === id);\n if (rightIndex !== -1) {\n this.config.rightItems[rightIndex] = new_menu;\n return true;\n }\n\n return false;\n }\n\n setBrand(brand, icon=null) {\n this.config.brand = brand;\n this.config.brandIcon = icon || this.config.brandIcon;\n this.render();\n }\n\n setUser(user) {\n if (!user) {\n this.replaceMenuItem('user', this.loginMenu);\n } else {\n this.userMenu.label = user.get(\"display_name\");\n this.replaceMenuItem('login', this.userMenu);\n }\n this.setModel(user);\n }\n\n _onModelChange() {\n if (this.model) {\n this.userMenu.label = this.model.get(\"display_name\");\n }\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Get template based on display mode\n */\n async getTemplate() {\n return `\n <div class=\"container-fluid\">\n {{#data.showSidebarToggle}}\n <button class=\"topnav-sidebar-toggle me-2\" data-action=\"{{data.sidebarToggleAction}}\" aria-label=\"Toggle Sidebar\">\n <i class=\"bi bi-chevron-right toggle-chevron\"></i>\n </button>\n {{/data.showSidebarToggle}}\n\n {{#data.showGroupInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.groupIcon}}<i class=\"{{data.groupIcon}} me-2\"></i>{{/data.groupIcon}}\n <div>\n <span class=\"topnav-group-name\"\n role=\"button\"\n tabindex=\"0\"\n data-action=\"open-group-selector\"\n style=\"cursor: pointer;\">\n {{data.currentGroupName}}\n </span>\n {{#data.showPageTitle}}\n <span class=\"text-muted mx-2\">|</span>\n <span>{{data.currentPageName}}</span>\n {{/data.showPageTitle}}\n </div>\n </div>\n {{/data.showGroupInfo}}\n\n {{#data.showPageInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.currentPageIcon}}<i class=\"{{data.currentPageIcon}} me-2\"></i>{{/data.currentPageIcon}}\n <div>\n <span>{{data.currentPageName}}</span>\n {{#data.currentPageDescription}}\n <small class=\"d-block\" style=\"font-size: 0.75rem; line-height: 1;\">{{data.currentPageDescription}}</small>\n {{/data.currentPageDescription}}\n </div>\n </div>\n {{/data.showPageInfo}}\n\n {{#data.showBrand}}\n <a class=\"navbar-brand\" href=\"{{data.brandRoute}}\">\n {{#data.brandIcon}}<i class=\"{{data.brandIcon}} me-2\"></i>{{/data.brandIcon}}\n {{data.brand}}\n </a>\n {{/data.showBrand}}\n\n <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#{{data.navbarId}}\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n\n <div class=\"collapse navbar-collapse\" id=\"{{data.navbarId}}\">\n {{#data.showNavItems}}\n <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n {{#data.navItems}}\n <li class=\"nav-item\">\n <a class=\"nav-link {{#active}}active{{/active}}\" href=\"{{route}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{text}}\n </a>\n </li>\n {{/data.navItems}}\n </ul>\n {{/data.showNavItems}}\n\n {{#data.hasRightItems}}\n <div class=\"navbar-nav ms-auto\">\n {{#data.rightItems}}\n {{#isGroupSelector}}\n <div data-container=\"group-selector-{{id}}\"></div>\n {{/isGroupSelector}}\n {{^isGroupSelector}}\n {{#isDropdown}}\n <div class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n {{#items}}\n {{#divider}}\n <li><hr class=\"dropdown-divider\"></li>\n {{/divider}}\n {{^divider}}\n <li>\n <a class=\"dropdown-item\" role=\"button\" {{#action}}data-action=\"{{action}}\"{{/action}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n </li>\n {{/divider}}\n {{/items}}\n </ul>\n </div>\n {{/isDropdown}}\n {{^isDropdown}}\n {{#isButton}}\n <button class=\"{{buttonClass}}\" data-action=\"{{action}}\" data-id=\"{{id}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </button>\n {{/isButton}}\n {{^isButton}}\n <a class=\"nav-link\" href=\"{{href}}\" {{#action}}data-action=\"{{action}}\"{{/action}} {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n {{/isButton}}\n {{/isDropdown}}\n {{/isGroupSelector}}\n {{/data.rightItems}}\n </div>\n {{/data.hasRightItems}}\n </div>\n </div>\n `;\n }\n\n /**\n * Process and normalize data before rendering (like Sidebar)\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n const app = this.getApp();\n // Use cached currentGroup or fall back to app.activeGroup\n const activeGroup = this.currentGroup || app?.activeGroup;\n\n // Determine what to show based on display mode\n const showGroupInfo = this.displayMode === 'group' || this.displayMode === 'group_page_titles';\n const showPageTitle = this.displayMode === 'group_page_titles';\n const showPageInfo = this.displayMode === 'page' || this.displayMode === 'both';\n const showBrand = !showGroupInfo && !showPageInfo;\n const showNavItems = this.displayMode === 'menu' || this.displayMode === 'both';\n\n // Filter navItems based on permissions\n const navItems = this.filterItemsByPermissions(this.config.navItems || []);\n\n // Process right items\n const rightItems = this.processRightItems(this.config.rightItems || []);\n\n this.data = {\n // Brand information\n brand: this.config.brand,\n brandIcon: this.config.brandIcon,\n brandRoute: this.config.brandRoute,\n showBrand: showBrand,\n\n // Navbar configuration\n navbarId: `navbar-${this.id}`,\n\n // Navigation items\n navItems: navItems,\n showNavItems: showNavItems,\n\n // Right items\n rightItems: rightItems,\n hasRightItems: rightItems.length > 0,\n\n // Group display\n showGroupInfo: showGroupInfo,\n showPageTitle: showPageTitle,\n currentGroupName: activeGroup?.get?.('name') || activeGroup?.name || 'Select Group',\n groupIcon: this.groupIcon,\n\n // Page display\n showPageInfo: showPageInfo,\n currentPageName: this.currentPage?.title || this.currentPage?.name || '',\n currentPageIcon: this.currentPage?.icon || this.currentPage?.pageIcon || '',\n currentPageDescription: this.showPageDescription ? this.currentPage?.description : '',\n\n // Sidebar toggle\n showSidebarToggle: this.config.showSidebarToggle,\n sidebarToggleAction: this.config.sidebarToggleAction,\n\n // Display mode\n displayMode: this.displayMode\n };\n }\n\n /**\n * Process right items configuration\n */\n processRightItems(rightItems) {\n return this.filterItemsByPermissions(rightItems).map(item => {\n const processedItem = { ...item };\n\n // Filter dropdown items by permissions if they exist\n if (item.items) {\n processedItem.items = this.filterItemsByPermissions(item.items);\n }\n\n // Check for group selector type\n if (item.type === 'group-selector') {\n processedItem.isGroupSelector = true;\n processedItem.isDropdown = false;\n processedItem.isButton = false;\n\n // Create group selector button with smart defaults\n // Only pass through explicitly provided options\n const groupSelectorOptions = {\n containerId: `group-selector-${item.id || 'default'}`\n };\n\n // Only add options if explicitly provided (allow auto-detection to work)\n if (item.Collection !== undefined) groupSelectorOptions.Collection = item.Collection;\n if (item.collection !== undefined) groupSelectorOptions.collection = item.collection;\n if (item.currentGroup !== undefined) groupSelectorOptions.currentGroup = item.currentGroup;\n if (item.buttonClass !== undefined) groupSelectorOptions.buttonClass = item.buttonClass;\n if (item.buttonIcon !== undefined) groupSelectorOptions.buttonIcon = item.buttonIcon;\n if (item.defaultText !== undefined) groupSelectorOptions.defaultText = item.defaultText;\n if (item.itemTemplate !== undefined) groupSelectorOptions.itemTemplate = item.itemTemplate;\n if (item.searchFields !== undefined) groupSelectorOptions.searchFields = item.searchFields;\n if (item.headerText !== undefined) groupSelectorOptions.headerText = item.headerText;\n if (item.searchPlaceholder !== undefined) groupSelectorOptions.searchPlaceholder = item.searchPlaceholder;\n if (item.autoSetActiveGroup !== undefined) groupSelectorOptions.autoSetActiveGroup = item.autoSetActiveGroup;\n if (item.onGroupSelected !== undefined) groupSelectorOptions.onGroupSelected = item.onGroupSelected;\n\n const groupSelector = new GroupSelectorButton(groupSelectorOptions);\n\n // Store reference for click-to-open functionality\n this.groupSelectorButton = groupSelector;\n\n // Add as child view\n this.addChild(groupSelector);\n\n } else if (processedItem.items && processedItem.items.length > 0) {\n // Dropdown menu\n processedItem.isDropdown = true;\n processedItem.isButton = false;\n } else if (item.buttonClass) {\n // Button\n processedItem.isButton = true;\n processedItem.isDropdown = false;\n } else {\n // Link\n processedItem.isButton = false;\n processedItem.isDropdown = false;\n }\n\n // Store handler if provided\n if (item.handler) {\n this.rightItemHandlers = this.rightItemHandlers || new Map();\n this.rightItemHandlers.set(item.id, item.handler);\n }\n\n return processedItem;\n });\n }\n\n /**\n * Setup listeners for page change events\n */\n setupPageListeners() {\n // Use global MOJO event bus if available\n this.getApp().events.on(\"page:show\", (data) => {\n this.onPageChanged(data);\n });\n }\n\n /**\n * Setup listeners for group change events\n */\n setupGroupListeners() {\n const app = this.getApp();\n if (!app?.events) return;\n\n // Listen for group changes and re-render if showing group info\n app.events.on(['group:changed', 'group:loaded'], (data) => {\n // Update our reference to current group\n if (data?.group) {\n this.currentGroup = data.group;\n }\n\n if (this.displayMode === 'group' || this.displayMode === 'group_page_titles') {\n if (this.mounted) {\n this.render();\n }\n }\n });\n }\n\n /**\n * Handle page before change event\n * @param {object} data - Event data\n */\n onPageBeforeChange(data) {\n // Can be used to show loading state\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n // Optionally show loading indicator\n }\n }\n\n /**\n * Handle page changed event\n * @param {object} data - Event data with previousPage and currentPage\n */\n onPageChanged(data) {\n this.previousPage = this.currentPage;\n this.currentPage = data.page;\n\n // Update display based on mode\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n this.updatePageDisplay();\n }\n\n // Update active menu items\n if (this.displayMode === 'menu' || this.displayMode === 'both') {\n if (this.currentPage && this.currentPage.route) {\n this.updateActiveItem(this.currentPage.route);\n }\n }\n }\n\n /**\n * Update the display to show current page info\n */\n updatePageDisplay() {\n if (!this.currentPage) return;\n\n // Just trigger re-render, onBeforeRender will handle the data processing\n if (this.mounted) {\n this.render();\n }\n }\n\n updateActiveItem(currentRoute) {\n // Normalize routes for comparison\n const normalizeRoute = (route) => {\n if (!route) return '/';\n return route.startsWith('/') ? route : `/${route}`;\n };\n\n const normalizedCurrentRoute = normalizeRoute(currentRoute);\n\n // Update active states with improved matching\n const navItems = this.data.navItems.map(item => {\n const normalizedItemRoute = normalizeRoute(item.route);\n\n // Check for active state\n let isActive = false;\n\n if (normalizedItemRoute === '/' && normalizedCurrentRoute === '/') {\n // Exact match for home route\n isActive = true;\n } else if (normalizedItemRoute !== '/' && normalizedCurrentRoute !== '/') {\n // For non-home routes, check if current route starts with nav item route\n // This allows /users to be active when on /users/123\n isActive = normalizedCurrentRoute.startsWith(normalizedItemRoute) ||\n normalizedCurrentRoute === normalizedItemRoute;\n }\n\n return {\n ...item,\n active: isActive\n };\n });\n\n this.updateData({ navItems }, true);\n }\n\n onPassThruActionProfile() {\n // Implement profile functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"profile\"});\n }\n\n onActionSettings() {\n // Implement settings functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"settings\"});\n }\n\n onActionLogout() {\n // Implement logout functionality here\n this.getApp().events.emit(\"auth:logout\", {action: \"logout\"});\n }\n\n /**\n * Handle open group selector action (from clicking group name in brand)\n */\n async onActionOpenGroupSelector(event) {\n // If we have a group selector button, trigger its dialog\n if (this.groupSelectorButton) {\n await this.groupSelectorButton.onActionShowSelector(event);\n return true;\n }\n\n // If no group selector in rightItems, create a temporary one\n const { GroupList } = await import('@core/models/Group.js');\n const tempSelector = new GroupSelectorButton({\n Collection: GroupList,\n currentGroup: this.getApp()?.activeGroup\n });\n\n await tempSelector.onActionShowSelector(event);\n return true;\n }\n\n /**\n * Handle dynamic action dispatch for right items\n */\n async handleAction(actionName, event, element) {\n // Check for custom handler first\n const itemId = element.getAttribute('data-id');\n if (itemId && this.rightItemHandlers && this.rightItemHandlers.has(itemId)) {\n const handler = this.rightItemHandlers.get(itemId);\n if (typeof handler === 'function') {\n return await handler.call(this, actionName, event, element);\n }\n }\n\n // Fallback to default action methods\n const methodName = `onAction${actionName.charAt(0).toUpperCase() + actionName.slice(1).replace(/-([a-z])/g, (g) => g[1].toUpperCase())}`;\n if (typeof this[methodName] === 'function') {\n return await this[methodName](event, element);\n }\n\n // Emit action event if no handler found\n this.emit('action', {\n action: actionName,\n event: event,\n element: element,\n topnav: this\n });\n }\n\n /**\n * Handle default actions by searching through rightItems and navItems\n */\n async onActionDefault(action, event, el) {\n // Check navItems first\n if (this.config.navItems) {\n for (const item of this.config.navItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n\n // Check rightItems\n if (this.config.rightItems) {\n for (const item of this.config.rightItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n // Also check dropdown items\n if (item.items) {\n for (const subItem of item.items) {\n if (subItem.action === action && subItem.handler) {\n await subItem.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n }\n }\n\n this.getApp().events.emit(\"portal:action\", { action, event, el });\n\n return false;\n }\n\n /**\n * Filter items by user permissions\n */\n filterItemsByPermissions(items) {\n if (!items) return [];\n\n const app = this.getApp();\n const activeUser = app?.activeUser;\n\n return items.filter(item => {\n // If item has permissions and user exists, check permissions\n if (item.permissions && activeUser) {\n return activeUser.hasPermission(item.permissions);\n }\n // If no permissions required or no user, show the item\n return true;\n });\n }\n\n}\n\nexport default TopNav;\n"],"names":["item","GroupList"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./Rest-BNYqGlnP.js"),t="2.1.965",s="2025-10-21T19:39:28.110Z",r={full:t,major:2,minor:1,revision:965,buildTime:s,toString(){return this.full},compare(e){const t=e=>e.split(".").map(Number),[s,r,i]=t(this.full),[n,o,a]=t(e);return s!==n?s-n:r!==o?r-o:i-a}};"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.VERSION=t,window.MOJO.VERSION_INFO=r,window.MOJO.version=t);class Router{constructor(e={}){this.defaultRoute=e.defaultRoute||"home",this.routes=[],this.currentRoute=null,this.eventEmitter=e.eventEmitter||null,this.boundHandlePopState=this.handlePopState.bind(this)}start(){window.addEventListener("popstate",this.boundHandlePopState),this.handleCurrentLocation()}stop(){window.removeEventListener("popstate",this.boundHandlePopState)}addRoute(e,t){this.routes.push({pattern:this.normalizePattern(e),regex:this.patternToRegex(e),pageName:t,paramNames:this.extractParamNames(e)})}async navigate(e,t={}){const{replace:s=!1,state:r=null,trigger:i=!0}=t,{pageName:n,queryParams:o}=this.parseInput(e);i&&await this.handleRouteChange(n,o)}back(){window.history.back()}forward(){window.history.forward()}getCurrentRoute(){return this.currentRoute}getCurrentPath(){const{pageName:e,queryParams:t}=this.parseCurrentUrl();return this.buildPublicUrl(e,t)}handlePopState(e){this.allowPopState?this.handleCurrentLocation():console.warn("PopStateEvent is not allowed")}async handleCurrentLocation(){const{pageName:e,queryParams:t}=this.parseCurrentUrl();await this.handleRouteChange(e,t)}async handleRouteChange(e,t){const s="/"+e,r=this.matchRoute(s),i=this.buildPublicUrl(e,t);return r?(this.currentRoute=r,this.eventEmitter&&this.eventEmitter.emit("route:changed",{path:i,pageName:r.pageName,params:r.params,query:t,route:r}),r):(this.eventEmitter&&this.eventEmitter.emit("route:notfound",{path:i}),null)}matchRoute(e){for(const t of this.routes){const s=e.match(t.regex);if(s){const r={};return t.paramNames.forEach((e,t)=>{r[e]=s[t+1]}),{...t,params:r,path:e}}}return null}parseInput(e){let t=this.defaultRoute,s={};if(!e)return{pageName:t,queryParams:s};try{if(e.includes("?")){const[r,i]=e.split("?",2),n=new URLSearchParams(i);if(n.has("page")){t=n.get("page")||this.defaultRoute;for(const[e,t]of n)"page"!==e&&(s[e]=t)}else{t=r.startsWith("/")?r.substring(1)||this.defaultRoute:r||this.defaultRoute;for(const[e,t]of n)s[e]=t}}else t=e.startsWith("/")?e.substring(1)||this.defaultRoute:e}catch(r){console.warn("Failed to parse input:",e,r),t=this.defaultRoute,s={}}return{pageName:t,queryParams:s}}parseCurrentUrl(){const e=new URLSearchParams(window.location.search),t=e.get("page")||this.defaultRoute,s={};for(const[r,i]of e)"page"!==r&&(s[r]=i);return{pageName:t,queryParams:s}}buildPublicUrl(e,t={}){const s=new URLSearchParams;return s.set("page",e),Object.entries(t).forEach(([e,t])=>{null!=t&&""!==t&&s.set(e,String(t))}),"?"+s.toString()}updateBrowserUrl(e,t,s,r){const i=new URL(window.location.origin+window.location.pathname);i.searchParams.set("page",e),Object.entries(t).forEach(([e,t])=>{null!=t&&""!==t&&i.searchParams.set(e,String(t))});const n=i.toString();s?window.history.replaceState(r,"",n):window.history.pushState(r,"",n)}patternToRegex(e){let t=e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&").replace(/\/:([^/?]+)\?/g,"(?:/([^/]+))?").replace(/:([^/]+)/g,"([^/]+)");return new RegExp(`^${t}$`)}extractParamNames(e){return(e.match(/:([^/?]+)\??/g)||[]).map(e=>e.replace(/[:?]/g,""))}normalizePattern(e){return e.startsWith("/")?e:`/${e}`}updateUrl(e={},t={}){const{replace:s=!1}=t,{pageName:r}=this.parseCurrentUrl();this.updateBrowserUrl(r,e,s)}buildUrl(e,t={}){return this.buildPublicUrl(e,t)}doRoutesMatch(e,t){if(!e||!t)return!1;const{pageName:s}=this.parseInput(e),{pageName:r}=this.parseInput(t);return s===r}}class EventBus{constructor(){this.listeners={},this.onceListeners={},this.maxListeners=100,this.debugMode=!1,this.eventStats={}}on(e,t){if("function"!=typeof t)throw new Error("Callback must be a function");return Array.isArray(e)?(e.forEach(e=>this.on(e,t)),this):(this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].length>=this.maxListeners&&console.warn(`Max listeners (${this.maxListeners}) exceeded for event: ${e}`),this.listeners[e].push(t),this)}once(e,t){if("function"!=typeof t)throw new Error("Callback must be a function");return Array.isArray(e)?(e.forEach(e=>this.once(e,t)),this):(this.onceListeners[e]||(this.onceListeners[e]=[]),this.onceListeners[e].push(t),this)}off(e,t){if(Array.isArray(e))return e.forEach(e=>this.off(e,t)),this;if(!t)return delete this.listeners[e],delete this.onceListeners[e],this;if(this.listeners[e]){const s=this.listeners[e].indexOf(t);-1!==s&&(this.listeners[e].splice(s,1),0===this.listeners[e].length&&delete this.listeners[e])}if(this.onceListeners[e]){const s=this.onceListeners[e].indexOf(t);-1!==s&&(this.onceListeners[e].splice(s,1),0===this.onceListeners[e].length&&delete this.onceListeners[e])}return this}emit(e,t){this.updateEventStats(e),this.debugMode;const s=[];return this.listeners[e]&&s.push(...this.listeners[e]),this.listeners["*"]&&s.push(...this.listeners["*"]),this.onceListeners[e]&&(s.push(...this.onceListeners[e]),delete this.onceListeners[e]),this.onceListeners["*"]&&(s.push(...this.onceListeners["*"]),delete this.onceListeners["*"]),this.debugMode&&s.length>0&&s.length,s.forEach(s=>{try{s(t,e)&&(e.stopPropagation&&e.stopPropagation(),e.preventDefault&&e.preventDefault())}catch(r){console.error(`Error in event listener for '${e}':`,r),this.emitError(r,e,s)}}),this}async emitAsync(e,t){const s=[];this.listeners[e]&&s.push(...this.listeners[e]),this.listeners["*"]&&s.push(...this.listeners["*"]),this.onceListeners[e]&&(s.push(...this.onceListeners[e]),delete this.onceListeners[e]),this.onceListeners["*"]&&(s.push(...this.onceListeners["*"]),delete this.onceListeners["*"]);const r=s.map(s=>new Promise(r=>{try{r(s(t,e))}catch(i){console.error(`Error in async event listener for '${e}':`,i),this.emitError(i,e,s),r()}}));return await Promise.all(r),this}removeAllListeners(){return this.listeners={},this.onceListeners={},this}listenerCount(e){return(this.listeners[e]?this.listeners[e].length:0)+(this.onceListeners[e]?this.onceListeners[e].length:0)}eventNames(){const e=Object.keys(this.listeners),t=Object.keys(this.onceListeners);return[.../* @__PURE__ */new Set([...e,...t])]}setMaxListeners(e){if("number"!=typeof e||e<0)throw new Error("Max listeners must be a non-negative number");return this.maxListeners=e,this}namespace(e){const t=t=>`${e}:${t}`;return{on:(e,s)=>this.on(t(e),s),once:(e,s)=>this.once(t(e),s),off:(e,s)=>this.off(t(e),s),emit:(e,s)=>this.emit(t(e),s),emitAsync:(e,s)=>this.emitAsync(t(e),s)}}use(e){if("function"!=typeof e)throw new Error("Middleware must be a function");const t=this.emit;return this.emit=(s,r)=>{try{const i=e(s,r);if(!1===i)return this;const n=void 0!==i?i:r;return t.call(this,s,n)}catch(i){return console.error("Error in event middleware:",i),t.call(this,s,r)}},this}emitError(e,t,s){"error"!==t&&setTimeout(()=>{this.emit("error",{error:e,originalEvent:t,callback:s.toString()})},0)}waitFor(e,t=null){return new Promise((s,r)=>{let i=null;const n=e=>{i&&clearTimeout(i),s(e)};this.once(e,n),t&&(i=setTimeout(()=>{this.off(e,n),r(new Error(`Timeout waiting for event: ${e}`))},t))})}debug(e=!0){return this.debugMode=e,this}getStats(){const e=this.eventNames(),t={totalEvents:e.length,totalListeners:0,events:{},emissions:{...this.eventStats}};return e.forEach(e=>{const s=this.listenerCount(e);t.events[e]=s,t.totalListeners+=s}),t}updateEventStats(e){this.eventStats[e]||(this.eventStats[e]={count:0,firstEmission:Date.now(),lastEmission:null}),this.eventStats[e].count++,this.eventStats[e].lastEmission=Date.now()}getEventStats(e){const t=this.eventStats[e];return t?{...t,listenerCount:this.listenerCount(e),avgEmissionsPerMinute:this.calculateEmissionRate(t)}:null}calculateEmissionRate(e){if(!e.firstEmission||!e.lastEmission)return 0;const t=e.lastEmission-e.firstEmission;if(0===t)return 0;const s=t/6e4;return Math.round(e.count/s*100)/100}resetStats(){return this.eventStats={},this}getTopEvents(e=10){return Object.entries(this.eventStats).map(([e,t])=>({event:e,count:t.count,rate:this.calculateEmissionRate(t),listeners:this.listenerCount(e)})).sort((e,t)=>t.count-e.count).slice(0,e)}debugInfo(){this.debugMode,this.maxListeners;const e=this.getStats();return e.totalEvents,e.totalListeners,Object.keys(this.eventStats).length>0&&this.getTopEvents(5),this}}class WebApp{constructor(t={}){this.config=t,this.initPluginRegistry(),this.name=t.name||"MOJO App",this.version=t.version||"1.0.0",this.debug=t.debug||!1,this.container=t.container||"#app",this.layoutType=t.layout||"portal",this.layoutConfig=t.layoutConfig||{},t.sidebar&&(this.layoutConfig.sidebarConfig=t.sidebar),t.topbar&&(this.layoutConfig.topbarConfig=t.topbar),this.layout=null,this.layoutConfig.containerId=this.container||this.containerId||"#app",this.pageContainer=t.pageContainer||"#page-container",this.basePath=t.basePath||"",this.routerMode=t.routerMode||t.router?.mode||"param",this.basePath=t.basePath||t.router?.base||"",this.defaultRoute=t.defaultRoute||"home",this.session=t.session||{},this.router=null,this.navigation=t.navigation||{},this.state={currentPage:null,previousPage:null,loading:!1},this.events=new EventBus,this.rest=e.rest,t.api&&this.rest.configure(t.api),this.router=new Router({mode:"param"===this.routerMode?"params":this.routerMode,basePath:this.basePath,defaultRoute:this.defaultRoute,eventEmitter:this.events}),this.events.on("route:changed",async e=>{const{pageName:t,params:s,query:r}=e;await this.showPage(t,r,s,{fromRouter:!0})}),"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.router=this.router),this.setupFocusTracking(),this.pageCache=/* @__PURE__ */new Map,this.pageClasses=/* @__PURE__ */new Map,this.componentClasses=/* @__PURE__ */new Map,this.modelClasses=/* @__PURE__ */new Map,this.currentPage=null,this.isStarted=!1,window.matchUUID?window[window.matchUUID]=this:window.MOJO?window.MOJO.app=this:window.__app__=this}async start(){if(this.isStarted)console.warn("WebApp already started");else try{this.setupPageContainer(),this.validateDefaultRoute(),await this.setupRouter(),this.isStarted=!0,this.router.allowPopState=!1,this.events.emit("app:ready",{app:this})}catch(e){throw console.error(`Failed to start ${this.name}:`,e),this.showError("Failed to start application"),e}}async setupRouter(){this.router?(this.events.on("route:notfound",async e=>{console.warn(`Route not found: ${e.path}`),this._show404(e.path)}),this.router.start(),this.routerMode):console.error("Router not initialized")}setupPageContainer(){const e="string"==typeof this.container?document.querySelector(this.container):this.container;e&&!e.querySelector("#page-container")&&(e.innerHTML='<div id="page-container"></div>'),this.pageContainer="#page-container"}registerPage(e,t,s={}){if("string"!=typeof e||!e)return console.error("registerPage: pageName must be a non-empty string"),this;if("function"!=typeof t)return console.error("registerPage: PageClass must be a constructor function"),this;if(s.containerId||(s.containerId=this.pageContainer),this.pageClasses.set(e,{PageClass:t,constructorOptions:s}),this.router){let t=s.route||`/${e}`;t.startsWith("/")||(t=`/${t}`),s.route=t,this.router.addRoute(t,e)}return this}getPage(e){return this.pageCache.get(e)}getPagePermissions(e){if(this.pageCache.has(e))return this.pageCache.get(e).permissions;const t=this.pageClasses.get(e);if(!t)return null;const{PageClass:s,constructorOptions:r}=t;return r?r.permissions:null}getOrCreatePage(e){if(this.pageCache.has(e))return this.pageCache.get(e);const t=this.pageClasses.get(e);if(!t)return console.error(`Page not registered: ${e}`),null;const{PageClass:s,constructorOptions:r}=t;try{const t=new s({pageName:e,...r,app:this});return r.route&&(t.route=r.route),this.pageCache.set(e,t),t.route,t}catch(i){return console.error(`Failed to create page ${e}:`,i),null}}async showPage(e,t={},s={},r={}){const{fromRouter:i=!1,replace:n=!1,force:o=!1}=r;try{let r,n;"string"==typeof e?(n=e,r=this.getOrCreatePage(e)):e&&"object"==typeof e&&(r=e,n=e.pageName),this.events.emit("page:showing",{page:r,pageName:r.pageName,params:s,query:t,fromRouter:i});const o=this.currentPage;if(!r)return void this._show404(n,s,t,i);if(!r.canEnter())return void this._showDeniedPage(r,s,t,i);o&&o!==r&&await this._exitOldPage(o),await r.onParams(s,t),o!==r&&await r.onEnter(),r.syncUrl(),this.events.emit("page:show",{page:r,pageName:r.pageName,params:s,query:t,fromRouter:i}),await r.render(),this.currentPage=r,r.pageName}catch(a){console.error("Error in showPage:",a),this.showError(`Failed to load page: ${a.message}`),"error"!==e&&await this.showPage("error",{},{error:a,originalPage:e},{fromRouter:i})}}async _show404(e,t,s,r){const i=this.getOrCreatePage("404");i&&(i.setInfo&&i.setInfo(e),await this._exitOldPage(this.currentPage),await i.render(),this.currentPage=i,this.events.emit("page:404",{page:null,pageName:e,params:t,query:s,fromRouter:r}))}async _showDeniedPage(e,t,s,r){const i=this.getOrCreatePage("denied");i.setDeniedPage&&i.setDeniedPage(e),await this._exitOldPage(this.currentPage),await i.render(),this.currentPage=i,this.events.emit("page:denied",{page:e,pageName:e.pageName,params:t,query:s,fromRouter:r})}async _exitOldPage(e){if(e)try{await e.onExit(),await e.unmount(),this.events.emit("page:hide",{page:e})}catch(t){console.error(`Error exiting page ${e.pageName}:`,t)}}async navigate(e,t={},s={}){if(!this.router)return void console.error("Router not initialized");let r=e;if(Object.keys(t).length>0){const s=new URLSearchParams(t).toString();r+=(e.includes("?")?"&":"?")+s}return await this.router.navigate(r,s)}async navigateToDefault(e={}){return await this.showPage(this.defaultRoute,{},{},e)}back(){this.router?this.router.back():console.warn("Router not initialized")}forward(){this.router?this.router.forward():console.warn("Router not initialized")}getCurrentPage(){return this.currentPage}getPageContainer(){return this.layout&&this.layout.getPageContainer?this.layout.getPageContainer():"string"==typeof this.pageContainer?document.querySelector(this.pageContainer):this.pageContainer}async showError(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;await t.alert(e,"Error",{size:"md",class:"text-danger"})}catch(t){this.events.emit("notification",{message:e,type:"error"}),"undefined"!=typeof window&&window?.console&&console.error("[WebApp] showError fallback:",t),"undefined"!=typeof window&&alert(`Error: ${e}`)}}async showSuccess(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;await t.alert(e,"Success",{size:"md",class:"text-success"})}catch(t){this.events.emit("notification",{message:e,type:"success"}),"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] showSuccess fallback:",t),"undefined"!=typeof window&&alert(`Success: ${e}`)}}async showInfo(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;await t.alert(e,"Information",{size:"md",class:"text-info"})}catch(t){this.events.emit("notification",{message:e,type:"info"}),"undefined"!=typeof window&&window,"undefined"!=typeof window&&alert(`Info: ${e}`)}}async showWarning(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;await t.alert(e,"Warning",{size:"md",class:"text-warning"})}catch(t){this.events.emit("notification",{message:e,type:"warning"}),"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] showWarning fallback:",t),"undefined"!=typeof window&&alert(`Warning: ${e}`)}}showNotification(e,t="info"){this.events.emit("notification",{message:e,type:t})}async showLoading(e={}){"string"==typeof e&&(e={message:e});try{(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default.showBusy(e)}catch(t){"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] showLoading fallback:",t,e),this.events.emit("notification",{message:e.message||"Loading...",type:"info"})}}async hideLoading(){try{(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default.hideBusy()}catch(e){"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] hideLoading fallback:",e)}}async showModelForm(e={}){try{const t=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;return await t.showModelForm(e)}catch(t){throw"undefined"!=typeof window&&window?.console&&console.error("[WebApp] showModelForm failed:",t),t}}async showForm(e={}){try{const t=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;return await t.showForm(e)}catch(t){throw"undefined"!=typeof window&&window?.console&&console.error("[WebApp] showForm failed:",t),t}}async confirm(e,t="Confirm",s={}){const r=(await Promise.resolve().then(()=>require("./Dialog-BB9GfRUS.js"))).default;return await r.confirm(e,t,s)}setupFocusTracking(){if("undefined"==typeof window)return;this.isFocused=!document.hidden;const e=()=>{const e=this.isFocused;this.isFocused=!document.hidden,e!==this.isFocused&&(this.isFocused?this.events.emit("browser:focus"):this.events.emit("browser:blur"))},t=()=>{this.isFocused||(this.isFocused=!0,this.events.emit("browser:focus"))},s=()=>{this.isFocused&&(this.isFocused=!1,this.events.emit("browser:blur"))};document.addEventListener("visibilitychange",e),window.addEventListener("focus",t),window.addEventListener("blur",s),this._focusHandlers={visibilitychange:e,focus:t,blur:s}}setupErrorHandling(){window.addEventListener("error",e=>{console.error("Global error:",e.error),this.debug&&this.showError(`Error: ${e.error?.message||"Unknown error"}`)}),window.addEventListener("unhandledrejection",e=>{console.error("Unhandled promise rejection:",e.reason),this.debug&&this.showError(`Promise rejected: ${e.reason?.message||"Unknown error"}`)})}escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}getState(e){return e?this.state[e]:this.state}setState(e){const t={...this.state};Object.assign(this.state,e),this.events.emit("state:changed",{oldState:t,newState:this.state,updates:e})}registerComponent(e,t){this.componentClasses.set(e,t)}getComponent(e){return this.componentClasses.get(e)}registerModel(e,t){this.modelClasses.set(e,t)}getModel(e){return this.modelClasses.get(e)}setupRest(){this.rest=e.rest,e.rest.configure(this.api)}async destroy(){this.router&&this.router.stop(),this._focusHandlers&&"undefined"!=typeof window&&(document.removeEventListener("visibilitychange",this._focusHandlers.visibilitychange),window.removeEventListener("focus",this._focusHandlers.focus),window.removeEventListener("blur",this._focusHandlers.blur));const e=Array.from(this.pageCache.values());if(await Promise.allSettled(e.map(async e=>{try{e.destroy&&await e.destroy()}catch(t){console.error("Error destroying page:",t)}})),this.layout&&this.layout.destroy)try{await this.layout.destroy()}catch(t){console.error("Error destroying layout:",t)}this.pageCache.clear(),this.pageClasses.clear(),this.componentClasses.clear(),this.modelClasses.clear(),"undefined"!=typeof window&&window.MOJO&&delete window.MOJO.router,this.isStarted=!1,this.name}buildPagePath(e,t,s){let r=e.route||`/${e.pageName.toLowerCase()}`;if(Object.keys(t).forEach(e=>{"string"!=typeof t[e]&&"number"!=typeof t[e]||(r=r.replace(`:${e}`,t[e]))}),s&&Object.keys(s).length>0){const e=new URLSearchParams(s).toString();r+=(r.includes("?")?"&":"?")+e}return r}validateDefaultRoute(){this.pageClasses.has(this.defaultRoute)?this.defaultRoute:(console.warn(`⚠️ Default route '${this.defaultRoute}' is not registered!`),console.warn(` Please register a page: app.registerPage('${this.defaultRoute}', YourPageClass);`),console.warn(" Or change default route: new WebApp({ defaultRoute: 'your-page' });"))}findFallbackPage(){const e=["404","error","denied"];for(const[t]of this.pageClasses.entries())if(!e.includes(t))return t;return null}static create(e={}){return new WebApp(e)}initPluginRegistry(){"undefined"!=typeof window&&(window.MOJO||(window.MOJO={}),window.MOJO.plugins||(window.MOJO.plugins={}),window.MOJO.app=this)}static registerPlugin(e,t){"undefined"!=typeof window&&(window.MOJO||(window.MOJO={}),window.MOJO.plugins||(window.MOJO.plugins={}),window.MOJO.plugins[e]=t)}}exports.BUILD_TIME=s,exports.EventBus=EventBus,exports.Router=Router,exports.VERSION=t,exports.VERSION_INFO=r,exports.VERSION_MAJOR=2,exports.VERSION_MINOR=1,exports.VERSION_REVISION=965,exports.WebApp=WebApp;
2
- //# sourceMappingURL=WebApp-D5wBRHAI.js.map
1
+ "use strict";const e=require("./Rest-BNYqGlnP.js"),t="2.1.966",s="2025-10-21T20:12:05.868Z",r={full:t,major:2,minor:1,revision:966,buildTime:s,toString(){return this.full},compare(e){const t=e=>e.split(".").map(Number),[s,r,i]=t(this.full),[n,o,a]=t(e);return s!==n?s-n:r!==o?r-o:i-a}};"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.VERSION=t,window.MOJO.VERSION_INFO=r,window.MOJO.version=t);class Router{constructor(e={}){this.defaultRoute=e.defaultRoute||"home",this.routes=[],this.currentRoute=null,this.eventEmitter=e.eventEmitter||null,this.boundHandlePopState=this.handlePopState.bind(this)}start(){window.addEventListener("popstate",this.boundHandlePopState),this.handleCurrentLocation()}stop(){window.removeEventListener("popstate",this.boundHandlePopState)}addRoute(e,t){this.routes.push({pattern:this.normalizePattern(e),regex:this.patternToRegex(e),pageName:t,paramNames:this.extractParamNames(e)})}async navigate(e,t={}){const{replace:s=!1,state:r=null,trigger:i=!0}=t,{pageName:n,queryParams:o}=this.parseInput(e);i&&await this.handleRouteChange(n,o)}back(){window.history.back()}forward(){window.history.forward()}getCurrentRoute(){return this.currentRoute}getCurrentPath(){const{pageName:e,queryParams:t}=this.parseCurrentUrl();return this.buildPublicUrl(e,t)}handlePopState(e){this.allowPopState?this.handleCurrentLocation():console.warn("PopStateEvent is not allowed")}async handleCurrentLocation(){const{pageName:e,queryParams:t}=this.parseCurrentUrl();await this.handleRouteChange(e,t)}async handleRouteChange(e,t){const s="/"+e,r=this.matchRoute(s),i=this.buildPublicUrl(e,t);return r?(this.currentRoute=r,this.eventEmitter&&this.eventEmitter.emit("route:changed",{path:i,pageName:r.pageName,params:r.params,query:t,route:r}),r):(this.eventEmitter&&this.eventEmitter.emit("route:notfound",{path:i}),null)}matchRoute(e){for(const t of this.routes){const s=e.match(t.regex);if(s){const r={};return t.paramNames.forEach((e,t)=>{r[e]=s[t+1]}),{...t,params:r,path:e}}}return null}parseInput(e){let t=this.defaultRoute,s={};if(!e)return{pageName:t,queryParams:s};try{if(e.includes("?")){const[r,i]=e.split("?",2),n=new URLSearchParams(i);if(n.has("page")){t=n.get("page")||this.defaultRoute;for(const[e,t]of n)"page"!==e&&(s[e]=t)}else{t=r.startsWith("/")?r.substring(1)||this.defaultRoute:r||this.defaultRoute;for(const[e,t]of n)s[e]=t}}else t=e.startsWith("/")?e.substring(1)||this.defaultRoute:e}catch(r){console.warn("Failed to parse input:",e,r),t=this.defaultRoute,s={}}return{pageName:t,queryParams:s}}parseCurrentUrl(){const e=new URLSearchParams(window.location.search),t=e.get("page")||this.defaultRoute,s={};for(const[r,i]of e)"page"!==r&&(s[r]=i);return{pageName:t,queryParams:s}}buildPublicUrl(e,t={}){const s=new URLSearchParams;return s.set("page",e),Object.entries(t).forEach(([e,t])=>{null!=t&&""!==t&&s.set(e,String(t))}),"?"+s.toString()}updateBrowserUrl(e,t,s,r){const i=new URL(window.location.origin+window.location.pathname);i.searchParams.set("page",e),Object.entries(t).forEach(([e,t])=>{null!=t&&""!==t&&i.searchParams.set(e,String(t))});const n=i.toString();s?window.history.replaceState(r,"",n):window.history.pushState(r,"",n)}patternToRegex(e){let t=e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&").replace(/\/:([^/?]+)\?/g,"(?:/([^/]+))?").replace(/:([^/]+)/g,"([^/]+)");return new RegExp(`^${t}$`)}extractParamNames(e){return(e.match(/:([^/?]+)\??/g)||[]).map(e=>e.replace(/[:?]/g,""))}normalizePattern(e){return e.startsWith("/")?e:`/${e}`}updateUrl(e={},t={}){const{replace:s=!1}=t,{pageName:r}=this.parseCurrentUrl();this.updateBrowserUrl(r,e,s)}buildUrl(e,t={}){return this.buildPublicUrl(e,t)}doRoutesMatch(e,t){if(!e||!t)return!1;const{pageName:s}=this.parseInput(e),{pageName:r}=this.parseInput(t);return s===r}}class EventBus{constructor(){this.listeners={},this.onceListeners={},this.maxListeners=100,this.debugMode=!1,this.eventStats={}}on(e,t){if("function"!=typeof t)throw new Error("Callback must be a function");return Array.isArray(e)?(e.forEach(e=>this.on(e,t)),this):(this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].length>=this.maxListeners&&console.warn(`Max listeners (${this.maxListeners}) exceeded for event: ${e}`),this.listeners[e].push(t),this)}once(e,t){if("function"!=typeof t)throw new Error("Callback must be a function");return Array.isArray(e)?(e.forEach(e=>this.once(e,t)),this):(this.onceListeners[e]||(this.onceListeners[e]=[]),this.onceListeners[e].push(t),this)}off(e,t){if(Array.isArray(e))return e.forEach(e=>this.off(e,t)),this;if(!t)return delete this.listeners[e],delete this.onceListeners[e],this;if(this.listeners[e]){const s=this.listeners[e].indexOf(t);-1!==s&&(this.listeners[e].splice(s,1),0===this.listeners[e].length&&delete this.listeners[e])}if(this.onceListeners[e]){const s=this.onceListeners[e].indexOf(t);-1!==s&&(this.onceListeners[e].splice(s,1),0===this.onceListeners[e].length&&delete this.onceListeners[e])}return this}emit(e,t){this.updateEventStats(e),this.debugMode;const s=[];return this.listeners[e]&&s.push(...this.listeners[e]),this.listeners["*"]&&s.push(...this.listeners["*"]),this.onceListeners[e]&&(s.push(...this.onceListeners[e]),delete this.onceListeners[e]),this.onceListeners["*"]&&(s.push(...this.onceListeners["*"]),delete this.onceListeners["*"]),this.debugMode&&s.length>0&&s.length,s.forEach(s=>{try{s(t,e)&&(e.stopPropagation&&e.stopPropagation(),e.preventDefault&&e.preventDefault())}catch(r){console.error(`Error in event listener for '${e}':`,r),this.emitError(r,e,s)}}),this}async emitAsync(e,t){const s=[];this.listeners[e]&&s.push(...this.listeners[e]),this.listeners["*"]&&s.push(...this.listeners["*"]),this.onceListeners[e]&&(s.push(...this.onceListeners[e]),delete this.onceListeners[e]),this.onceListeners["*"]&&(s.push(...this.onceListeners["*"]),delete this.onceListeners["*"]);const r=s.map(s=>new Promise(r=>{try{r(s(t,e))}catch(i){console.error(`Error in async event listener for '${e}':`,i),this.emitError(i,e,s),r()}}));return await Promise.all(r),this}removeAllListeners(){return this.listeners={},this.onceListeners={},this}listenerCount(e){return(this.listeners[e]?this.listeners[e].length:0)+(this.onceListeners[e]?this.onceListeners[e].length:0)}eventNames(){const e=Object.keys(this.listeners),t=Object.keys(this.onceListeners);return[.../* @__PURE__ */new Set([...e,...t])]}setMaxListeners(e){if("number"!=typeof e||e<0)throw new Error("Max listeners must be a non-negative number");return this.maxListeners=e,this}namespace(e){const t=t=>`${e}:${t}`;return{on:(e,s)=>this.on(t(e),s),once:(e,s)=>this.once(t(e),s),off:(e,s)=>this.off(t(e),s),emit:(e,s)=>this.emit(t(e),s),emitAsync:(e,s)=>this.emitAsync(t(e),s)}}use(e){if("function"!=typeof e)throw new Error("Middleware must be a function");const t=this.emit;return this.emit=(s,r)=>{try{const i=e(s,r);if(!1===i)return this;const n=void 0!==i?i:r;return t.call(this,s,n)}catch(i){return console.error("Error in event middleware:",i),t.call(this,s,r)}},this}emitError(e,t,s){"error"!==t&&setTimeout(()=>{this.emit("error",{error:e,originalEvent:t,callback:s.toString()})},0)}waitFor(e,t=null){return new Promise((s,r)=>{let i=null;const n=e=>{i&&clearTimeout(i),s(e)};this.once(e,n),t&&(i=setTimeout(()=>{this.off(e,n),r(new Error(`Timeout waiting for event: ${e}`))},t))})}debug(e=!0){return this.debugMode=e,this}getStats(){const e=this.eventNames(),t={totalEvents:e.length,totalListeners:0,events:{},emissions:{...this.eventStats}};return e.forEach(e=>{const s=this.listenerCount(e);t.events[e]=s,t.totalListeners+=s}),t}updateEventStats(e){this.eventStats[e]||(this.eventStats[e]={count:0,firstEmission:Date.now(),lastEmission:null}),this.eventStats[e].count++,this.eventStats[e].lastEmission=Date.now()}getEventStats(e){const t=this.eventStats[e];return t?{...t,listenerCount:this.listenerCount(e),avgEmissionsPerMinute:this.calculateEmissionRate(t)}:null}calculateEmissionRate(e){if(!e.firstEmission||!e.lastEmission)return 0;const t=e.lastEmission-e.firstEmission;if(0===t)return 0;const s=t/6e4;return Math.round(e.count/s*100)/100}resetStats(){return this.eventStats={},this}getTopEvents(e=10){return Object.entries(this.eventStats).map(([e,t])=>({event:e,count:t.count,rate:this.calculateEmissionRate(t),listeners:this.listenerCount(e)})).sort((e,t)=>t.count-e.count).slice(0,e)}debugInfo(){this.debugMode,this.maxListeners;const e=this.getStats();return e.totalEvents,e.totalListeners,Object.keys(this.eventStats).length>0&&this.getTopEvents(5),this}}class WebApp{constructor(t={}){this.config=t,this.initPluginRegistry(),this.name=t.name||"MOJO App",this.version=t.version||"1.0.0",this.debug=t.debug||!1,this.container=t.container||"#app",this.layoutType=t.layout||"portal",this.layoutConfig=t.layoutConfig||{},t.sidebar&&(this.layoutConfig.sidebarConfig=t.sidebar),t.topbar&&(this.layoutConfig.topbarConfig=t.topbar),this.layout=null,this.layoutConfig.containerId=this.container||this.containerId||"#app",this.pageContainer=t.pageContainer||"#page-container",this.basePath=t.basePath||"",this.routerMode=t.routerMode||t.router?.mode||"param",this.basePath=t.basePath||t.router?.base||"",this.defaultRoute=t.defaultRoute||"home",this.session=t.session||{},this.router=null,this.navigation=t.navigation||{},this.state={currentPage:null,previousPage:null,loading:!1},this.events=new EventBus,this.rest=e.rest,t.api&&this.rest.configure(t.api),this.router=new Router({mode:"param"===this.routerMode?"params":this.routerMode,basePath:this.basePath,defaultRoute:this.defaultRoute,eventEmitter:this.events}),this.events.on("route:changed",async e=>{const{pageName:t,params:s,query:r}=e;await this.showPage(t,r,s,{fromRouter:!0})}),"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.router=this.router),this.setupFocusTracking(),this.pageCache=/* @__PURE__ */new Map,this.pageClasses=/* @__PURE__ */new Map,this.componentClasses=/* @__PURE__ */new Map,this.modelClasses=/* @__PURE__ */new Map,this.currentPage=null,this.isStarted=!1,window.matchUUID?window[window.matchUUID]=this:window.MOJO?window.MOJO.app=this:window.__app__=this}async start(){if(this.isStarted)console.warn("WebApp already started");else try{this.setupPageContainer(),this.validateDefaultRoute(),await this.setupRouter(),this.isStarted=!0,this.router.allowPopState=!1,this.events.emit("app:ready",{app:this})}catch(e){throw console.error(`Failed to start ${this.name}:`,e),this.showError("Failed to start application"),e}}async setupRouter(){this.router?(this.events.on("route:notfound",async e=>{console.warn(`Route not found: ${e.path}`),this._show404(e.path)}),this.router.start(),this.routerMode):console.error("Router not initialized")}setupPageContainer(){const e="string"==typeof this.container?document.querySelector(this.container):this.container;e&&!e.querySelector("#page-container")&&(e.innerHTML='<div id="page-container"></div>'),this.pageContainer="#page-container"}registerPage(e,t,s={}){if("string"!=typeof e||!e)return console.error("registerPage: pageName must be a non-empty string"),this;if("function"!=typeof t)return console.error("registerPage: PageClass must be a constructor function"),this;if(s.containerId||(s.containerId=this.pageContainer),this.pageClasses.set(e,{PageClass:t,constructorOptions:s}),this.router){let t=s.route||`/${e}`;t.startsWith("/")||(t=`/${t}`),s.route=t,this.router.addRoute(t,e)}return this}getPage(e){return this.pageCache.get(e)}getPagePermissions(e){if(this.pageCache.has(e))return this.pageCache.get(e).permissions;const t=this.pageClasses.get(e);if(!t)return null;const{PageClass:s,constructorOptions:r}=t;return r?r.permissions:null}getOrCreatePage(e){if(this.pageCache.has(e))return this.pageCache.get(e);const t=this.pageClasses.get(e);if(!t)return console.error(`Page not registered: ${e}`),null;const{PageClass:s,constructorOptions:r}=t;try{const t=new s({pageName:e,...r,app:this});return r.route&&(t.route=r.route),this.pageCache.set(e,t),t.route,t}catch(i){return console.error(`Failed to create page ${e}:`,i),null}}async showPage(e,t={},s={},r={}){const{fromRouter:i=!1,replace:n=!1,force:o=!1}=r;try{let r,n;"string"==typeof e?(n=e,r=this.getOrCreatePage(e)):e&&"object"==typeof e&&(r=e,n=e.pageName),this.events.emit("page:showing",{page:r,pageName:r.pageName,params:s,query:t,fromRouter:i});const o=this.currentPage;if(!r)return void this._show404(n,s,t,i);if(!r.canEnter())return void this._showDeniedPage(r,s,t,i);o&&o!==r&&await this._exitOldPage(o),await r.onParams(s,t),o!==r&&await r.onEnter(),r.syncUrl(),this.events.emit("page:show",{page:r,pageName:r.pageName,params:s,query:t,fromRouter:i}),await r.render(),this.currentPage=r,r.pageName}catch(a){console.error("Error in showPage:",a),this.showError(`Failed to load page: ${a.message}`),"error"!==e&&await this.showPage("error",{},{error:a,originalPage:e},{fromRouter:i})}}async _show404(e,t,s,r){const i=this.getOrCreatePage("404");i&&(i.setInfo&&i.setInfo(e),await this._exitOldPage(this.currentPage),await i.render(),this.currentPage=i,this.events.emit("page:404",{page:null,pageName:e,params:t,query:s,fromRouter:r}))}async _showDeniedPage(e,t,s,r){const i=this.getOrCreatePage("denied");i.setDeniedPage&&i.setDeniedPage(e),await this._exitOldPage(this.currentPage),await i.render(),this.currentPage=i,this.events.emit("page:denied",{page:e,pageName:e.pageName,params:t,query:s,fromRouter:r})}async _exitOldPage(e){if(e)try{await e.onExit(),await e.unmount(),this.events.emit("page:hide",{page:e})}catch(t){console.error(`Error exiting page ${e.pageName}:`,t)}}async navigate(e,t={},s={}){if(!this.router)return void console.error("Router not initialized");let r=e;if(Object.keys(t).length>0){const s=new URLSearchParams(t).toString();r+=(e.includes("?")?"&":"?")+s}return await this.router.navigate(r,s)}async navigateToDefault(e={}){return await this.showPage(this.defaultRoute,{},{},e)}back(){this.router?this.router.back():console.warn("Router not initialized")}forward(){this.router?this.router.forward():console.warn("Router not initialized")}getCurrentPage(){return this.currentPage}getPageContainer(){return this.layout&&this.layout.getPageContainer?this.layout.getPageContainer():"string"==typeof this.pageContainer?document.querySelector(this.pageContainer):this.pageContainer}async showError(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;await t.alert(e,"Error",{size:"md",class:"text-danger"})}catch(t){this.events.emit("notification",{message:e,type:"error"}),"undefined"!=typeof window&&window?.console&&console.error("[WebApp] showError fallback:",t),"undefined"!=typeof window&&alert(`Error: ${e}`)}}async showSuccess(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;await t.alert(e,"Success",{size:"md",class:"text-success"})}catch(t){this.events.emit("notification",{message:e,type:"success"}),"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] showSuccess fallback:",t),"undefined"!=typeof window&&alert(`Success: ${e}`)}}async showInfo(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;await t.alert(e,"Information",{size:"md",class:"text-info"})}catch(t){this.events.emit("notification",{message:e,type:"info"}),"undefined"!=typeof window&&window,"undefined"!=typeof window&&alert(`Info: ${e}`)}}async showWarning(e){try{const t=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;await t.alert(e,"Warning",{size:"md",class:"text-warning"})}catch(t){this.events.emit("notification",{message:e,type:"warning"}),"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] showWarning fallback:",t),"undefined"!=typeof window&&alert(`Warning: ${e}`)}}showNotification(e,t="info"){this.events.emit("notification",{message:e,type:t})}async showLoading(e={}){"string"==typeof e&&(e={message:e});try{(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default.showBusy(e)}catch(t){"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] showLoading fallback:",t,e),this.events.emit("notification",{message:e.message||"Loading...",type:"info"})}}async hideLoading(){try{(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default.hideBusy()}catch(e){"undefined"!=typeof window&&window?.console&&console.warn("[WebApp] hideLoading fallback:",e)}}async showModelForm(e={}){try{const t=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;return await t.showModelForm(e)}catch(t){throw"undefined"!=typeof window&&window?.console&&console.error("[WebApp] showModelForm failed:",t),t}}async showForm(e={}){try{const t=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;return await t.showForm(e)}catch(t){throw"undefined"!=typeof window&&window?.console&&console.error("[WebApp] showForm failed:",t),t}}async confirm(e,t="Confirm",s={}){const r=(await Promise.resolve().then(()=>require("./Dialog-DVud9izE.js"))).default;return await r.confirm(e,t,s)}setupFocusTracking(){if("undefined"==typeof window)return;this.isFocused=!document.hidden;const e=()=>{const e=this.isFocused;this.isFocused=!document.hidden,e!==this.isFocused&&(this.isFocused?this.events.emit("browser:focus"):this.events.emit("browser:blur"))},t=()=>{this.isFocused||(this.isFocused=!0,this.events.emit("browser:focus"))},s=()=>{this.isFocused&&(this.isFocused=!1,this.events.emit("browser:blur"))};document.addEventListener("visibilitychange",e),window.addEventListener("focus",t),window.addEventListener("blur",s),this._focusHandlers={visibilitychange:e,focus:t,blur:s}}setupErrorHandling(){window.addEventListener("error",e=>{console.error("Global error:",e.error),this.debug&&this.showError(`Error: ${e.error?.message||"Unknown error"}`)}),window.addEventListener("unhandledrejection",e=>{console.error("Unhandled promise rejection:",e.reason),this.debug&&this.showError(`Promise rejected: ${e.reason?.message||"Unknown error"}`)})}escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}getState(e){return e?this.state[e]:this.state}setState(e){const t={...this.state};Object.assign(this.state,e),this.events.emit("state:changed",{oldState:t,newState:this.state,updates:e})}registerComponent(e,t){this.componentClasses.set(e,t)}getComponent(e){return this.componentClasses.get(e)}registerModel(e,t){this.modelClasses.set(e,t)}getModel(e){return this.modelClasses.get(e)}setupRest(){this.rest=e.rest,e.rest.configure(this.api)}async destroy(){this.router&&this.router.stop(),this._focusHandlers&&"undefined"!=typeof window&&(document.removeEventListener("visibilitychange",this._focusHandlers.visibilitychange),window.removeEventListener("focus",this._focusHandlers.focus),window.removeEventListener("blur",this._focusHandlers.blur));const e=Array.from(this.pageCache.values());if(await Promise.allSettled(e.map(async e=>{try{e.destroy&&await e.destroy()}catch(t){console.error("Error destroying page:",t)}})),this.layout&&this.layout.destroy)try{await this.layout.destroy()}catch(t){console.error("Error destroying layout:",t)}this.pageCache.clear(),this.pageClasses.clear(),this.componentClasses.clear(),this.modelClasses.clear(),"undefined"!=typeof window&&window.MOJO&&delete window.MOJO.router,this.isStarted=!1,this.name}buildPagePath(e,t,s){let r=e.route||`/${e.pageName.toLowerCase()}`;if(Object.keys(t).forEach(e=>{"string"!=typeof t[e]&&"number"!=typeof t[e]||(r=r.replace(`:${e}`,t[e]))}),s&&Object.keys(s).length>0){const e=new URLSearchParams(s).toString();r+=(r.includes("?")?"&":"?")+e}return r}validateDefaultRoute(){this.pageClasses.has(this.defaultRoute)?this.defaultRoute:(console.warn(`⚠️ Default route '${this.defaultRoute}' is not registered!`),console.warn(` Please register a page: app.registerPage('${this.defaultRoute}', YourPageClass);`),console.warn(" Or change default route: new WebApp({ defaultRoute: 'your-page' });"))}findFallbackPage(){const e=["404","error","denied"];for(const[t]of this.pageClasses.entries())if(!e.includes(t))return t;return null}static create(e={}){return new WebApp(e)}initPluginRegistry(){"undefined"!=typeof window&&(window.MOJO||(window.MOJO={}),window.MOJO.plugins||(window.MOJO.plugins={}),window.MOJO.app=this)}static registerPlugin(e,t){"undefined"!=typeof window&&(window.MOJO||(window.MOJO={}),window.MOJO.plugins||(window.MOJO.plugins={}),window.MOJO.plugins[e]=t)}}exports.BUILD_TIME=s,exports.EventBus=EventBus,exports.Router=Router,exports.VERSION=t,exports.VERSION_INFO=r,exports.VERSION_MAJOR=2,exports.VERSION_MINOR=1,exports.VERSION_REVISION=966,exports.WebApp=WebApp;
2
+ //# sourceMappingURL=WebApp-BaUWZs9G.js.map