web-mojo 2.5.22 → 2.5.24

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 (102) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/admin-models.cjs.js +1 -1
  3. package/dist/admin-models.es.js +1 -1
  4. package/dist/admin.cjs.js +1 -1
  5. package/dist/admin.es.js +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.es.js +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -1
  10. package/dist/chunks/ChatView-C-KpJhfA.js +2 -0
  11. package/dist/chunks/ChatView-C-KpJhfA.js.map +1 -0
  12. package/dist/chunks/ChatView-JdSac2NG.js +2 -0
  13. package/dist/chunks/ChatView-JdSac2NG.js.map +1 -0
  14. package/dist/chunks/{ContextMenu-CuMPRavw.js → ContextMenu-C_z-sZyT.js} +2 -2
  15. package/dist/chunks/{ContextMenu-CuMPRavw.js.map → ContextMenu-C_z-sZyT.js.map} +1 -1
  16. package/dist/chunks/{ContextMenu-DErGabOQ.js → ContextMenu-CzXteE58.js} +2 -2
  17. package/dist/chunks/{ContextMenu-DErGabOQ.js.map → ContextMenu-CzXteE58.js.map} +1 -1
  18. package/dist/chunks/{DataView-ByYwczrk.js → DataView-7Z36Sfkm.js} +2 -2
  19. package/dist/chunks/{DataView-ByYwczrk.js.map → DataView-7Z36Sfkm.js.map} +1 -1
  20. package/dist/chunks/{DataView-BdVCiAp9.js → DataView-FQ8Bj-1a.js} +2 -2
  21. package/dist/chunks/{DataView-BdVCiAp9.js.map → DataView-FQ8Bj-1a.js.map} +1 -1
  22. package/dist/chunks/{FormView-DgFp-jxi.js → FormView-BYEBjBvp.js} +2 -2
  23. package/dist/chunks/{FormView-DgFp-jxi.js.map → FormView-BYEBjBvp.js.map} +1 -1
  24. package/dist/chunks/{FormView-BhtVKwL9.js → FormView-wnZvgkRU.js} +2 -2
  25. package/dist/chunks/{FormView-BhtVKwL9.js.map → FormView-wnZvgkRU.js.map} +1 -1
  26. package/dist/chunks/{ListView-CjPzUY3T.js → ListView-Cz8VheqD.js} +2 -2
  27. package/dist/chunks/{ListView-gdCMcQIE.js.map → ListView-Cz8VheqD.js.map} +1 -1
  28. package/dist/chunks/{ListView-gdCMcQIE.js → ListView-SntURxdx.js} +2 -2
  29. package/dist/chunks/{ListView-CjPzUY3T.js.map → ListView-SntURxdx.js.map} +1 -1
  30. package/dist/chunks/{MetricsCountryMapView-DZ7mV3K_.js → MetricsCountryMapView-BAIY6xcc.js} +2 -2
  31. package/dist/chunks/{MetricsCountryMapView-DZ7mV3K_.js.map → MetricsCountryMapView-BAIY6xcc.js.map} +1 -1
  32. package/dist/chunks/{MetricsCountryMapView-Bz2-6GXP.js → MetricsCountryMapView-alcjxYAZ.js} +2 -2
  33. package/dist/chunks/{MetricsCountryMapView-Bz2-6GXP.js.map → MetricsCountryMapView-alcjxYAZ.js.map} +1 -1
  34. package/dist/chunks/{Modal-BK-B8pvg.js → Modal-Bf1tgLQq.js} +3 -3
  35. package/dist/chunks/{Modal-BK-B8pvg.js.map → Modal-Bf1tgLQq.js.map} +1 -1
  36. package/dist/chunks/{Modal-BAEK2ub4.js → Modal-Cfl5pmGX.js} +3 -3
  37. package/dist/chunks/{Modal-BAEK2ub4.js.map → Modal-Cfl5pmGX.js.map} +1 -1
  38. package/dist/chunks/Passkeys-BSs7ymie.js +2 -0
  39. package/dist/chunks/Passkeys-BSs7ymie.js.map +1 -0
  40. package/dist/chunks/Passkeys-DLX1_x2i.js +2 -0
  41. package/dist/chunks/Passkeys-DLX1_x2i.js.map +1 -0
  42. package/dist/chunks/{TokenManager-C_MSIpHm.js → TokenManager-70nRjPaQ.js} +2 -2
  43. package/dist/chunks/{TokenManager-C_MSIpHm.js.map → TokenManager-70nRjPaQ.js.map} +1 -1
  44. package/dist/chunks/{TokenManager-Dt6BcbMV.js → TokenManager-BKGkrLQn.js} +2 -2
  45. package/dist/chunks/{TokenManager-Dt6BcbMV.js.map → TokenManager-BKGkrLQn.js.map} +1 -1
  46. package/dist/chunks/User-DcN50rzR.js +2 -0
  47. package/dist/chunks/User-DcN50rzR.js.map +1 -0
  48. package/dist/chunks/User-yt80Vt-N.js +2 -0
  49. package/dist/chunks/User-yt80Vt-N.js.map +1 -0
  50. package/dist/chunks/{UserProfileView-CkE4VkPs.js → UserProfileView-DAYes3bB.js} +2 -2
  51. package/dist/chunks/{UserProfileView-CkE4VkPs.js.map → UserProfileView-DAYes3bB.js.map} +1 -1
  52. package/dist/chunks/{UserProfileView-BmWgWJMm.js → UserProfileView-DfSZGKDI.js} +2 -2
  53. package/dist/chunks/{UserProfileView-BmWgWJMm.js.map → UserProfileView-DfSZGKDI.js.map} +1 -1
  54. package/dist/chunks/{WebApp-SFmx4IhA.js → WebApp-Cb-0Tng-.js} +2 -2
  55. package/dist/chunks/{WebApp-SFmx4IhA.js.map → WebApp-Cb-0Tng-.js.map} +1 -1
  56. package/dist/chunks/{WebApp-BNlf4v3h.js → WebApp-mu4uvQhk.js} +2 -2
  57. package/dist/chunks/{WebApp-BNlf4v3h.js.map → WebApp-mu4uvQhk.js.map} +1 -1
  58. package/dist/chunks/{admin-models-DAztlbwB.js → admin-models-2uvSh3iU.js} +2 -2
  59. package/dist/chunks/{admin-models-DAztlbwB.js.map → admin-models-2uvSh3iU.js.map} +1 -1
  60. package/dist/chunks/{admin-models-DU-YmVqA.js → admin-models-BphWv_rs.js} +2 -2
  61. package/dist/chunks/{admin-models-DU-YmVqA.js.map → admin-models-BphWv_rs.js.map} +1 -1
  62. package/dist/chunks/{exportChart-DBUjlLsc.js → exportChart-BHSwK1Iz.js} +2 -2
  63. package/dist/chunks/{exportChart-DBUjlLsc.js.map → exportChart-BHSwK1Iz.js.map} +1 -1
  64. package/dist/chunks/{exportChart-_YXZlTpb.js → exportChart-CntZqw5i.js} +2 -2
  65. package/dist/chunks/{exportChart-_YXZlTpb.js.map → exportChart-CntZqw5i.js.map} +1 -1
  66. package/dist/chunks/{index-SAcE49b3.js → index-4wpCg6uC.js} +2 -2
  67. package/dist/chunks/{index-SAcE49b3.js.map → index-4wpCg6uC.js.map} +1 -1
  68. package/dist/chunks/{index-CokBDrQ-.js → index-EgU167e-.js} +2 -2
  69. package/dist/chunks/{index-CokBDrQ-.js.map → index-EgU167e-.js.map} +1 -1
  70. package/dist/chunks/{version-E6pgkslg.js → version-Bg3T6lug.js} +2 -2
  71. package/dist/chunks/{version-E6pgkslg.js.map → version-Bg3T6lug.js.map} +1 -1
  72. package/dist/chunks/{version-CraQlYEB.js → version-BuUMTIvJ.js} +2 -2
  73. package/dist/chunks/{version-CraQlYEB.js.map → version-BuUMTIvJ.js.map} +1 -1
  74. package/dist/docit.cjs.js +1 -1
  75. package/dist/docit.es.js +1 -1
  76. package/dist/index.cjs.js +1 -1
  77. package/dist/index.es.js +1 -1
  78. package/dist/lightbox.cjs.js +1 -1
  79. package/dist/lightbox.es.js +1 -1
  80. package/dist/map.cjs.js +1 -1
  81. package/dist/map.es.js +1 -1
  82. package/dist/timeline.cjs.js +1 -1
  83. package/dist/timeline.es.js +1 -1
  84. package/dist/user-profile.cjs.js +1 -1
  85. package/dist/user-profile.es.js +1 -1
  86. package/dist/web-mojo.lite.iife.js +74 -21
  87. package/dist/web-mojo.lite.iife.js.map +1 -1
  88. package/dist/web-mojo.lite.iife.min.js +30 -30
  89. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  90. package/package.json +1 -1
  91. package/dist/chunks/ChatView-C9_eY2ow.js +0 -2
  92. package/dist/chunks/ChatView-C9_eY2ow.js.map +0 -1
  93. package/dist/chunks/ChatView-DoCBGAM0.js +0 -2
  94. package/dist/chunks/ChatView-DoCBGAM0.js.map +0 -1
  95. package/dist/chunks/Passkeys-BrX-55UB.js +0 -2
  96. package/dist/chunks/Passkeys-BrX-55UB.js.map +0 -1
  97. package/dist/chunks/Passkeys-DXYu8Plj.js +0 -2
  98. package/dist/chunks/Passkeys-DXYu8Plj.js.map +0 -1
  99. package/dist/chunks/User-8dMZt39a.js +0 -2
  100. package/dist/chunks/User-8dMZt39a.js.map +0 -1
  101. package/dist/chunks/User-BghEdscC.js +0 -2
  102. package/dist/chunks/User-BghEdscC.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"Passkeys-DXYu8Plj.js","sources":["../../src/core/models/Log.js","../../src/core/models/Member.js","../../src/core/views/table/TableRow.js","../../src/core/views/table/TableView.js","../../src/core/models/Passkeys.js"],"sourcesContent":["\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Log extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/logs',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass LogList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Log,\n endpoint: '/api/logs',\n size: 10,\n ...options,\n });\n }\n}\n\nexport { Log, LogList };\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Member extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group/member',\n });\n }\n\n hasPermission(permission) {\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n if (permissions[permission] == true) {\n return true;\n }\n // Group `admin` is a full-access grant within this group (never the\n // system-scoped `sys.*` permissions — those aren't group-resolved).\n return !permission.startsWith('sys.') && permissions[\"admin\"] == true;\n }\n\n async fetchForGroup(groupId) {\n return await this.fetch({ url: `/api/group/${groupId}/member` });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass MemberList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Member,\n endpoint: '/api/group/member',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst MemberForms = {\n\n edit: {\n title: 'Edit Membership',\n fields: [\n {\n name: 'user.display_name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name'\n },\n {\n name: 'metadata.role',\n type: 'text',\n label: 'Role',\n placeholder: 'Enter role'\n },\n {\n name: `is_active`,\n type: 'switch',\n label: \"Is Enabled\",\n columns: 12\n }\n ]\n }\n};\n\n\n// ── Source: framework-defined member permissions ──────────────────\nMember.BASE_PERMISSIONS = [\n { name: \"manage_group\", label: \"Group Admin\" },\n { name: \"view_metrics\", label: \"View Metrics\" },\n { name: \"view_logs\", label: \"View Logs\" },\n { name: \"view_tickets\", label: \"View Tickets\" },\n { name: \"view_members\", label: \"View Members\" },\n { name: \"manage_members\", label: \"Manage Members\" },\n { name: \"view_billing\", label: \"View Billing\" }\n];\n\n// ── App-level extension points (empty by default) ─────────────────\n// Mutate these, then call Member.rebuildPermissions() to refresh the\n// cached field arrays the UI reads from.\n// APP_PERMISSIONS — flat app perms → a single \"App\" tab\n// APP_PERMISSION_TABS — [{ label, permissions:[{name,label}] }] → one\n// named tab each (apps with several domains)\nMember.APP_PERMISSIONS = [];\nMember.APP_PERMISSION_TABS = [];\n\n// ── Live caches — populated by rebuildPermissions() ───────────────\n// Initialized here so consumers (e.g. forms that capture\n// Member.PERMISSION_FIELDS at module-load) can hold a reference;\n// rebuildPermissions() mutates these in place to keep cached\n// references current across re-registrations.\nMember.PERMISSIONS = [];\nMember.PERMISSION_FIELDS = [];\n// Section-aligned cache for the MemberView \"Permissions\" section — one\n// tabset (one row): Standard (framework group perms) + an \"App\" tab for\n// flat app perms + one tab per registered app permission tab.\nMember.PERMISSION_TABSET = [];\n\n// Field-shape builder — kept aligned with User._permSwitch.\nconst _permSwitch = (p) => ({\n name: `permissions.${p.name}`,\n type: 'switch',\n label: p.label,\n columns: 6,\n ...(p.tooltip ? { tooltip: p.tooltip } : {})\n});\n\n// Recompute the cached permission structures from the live source arrays\n// (BASE_PERMISSIONS + APP_PERMISSIONS + APP_PERMISSION_TABS). Idempotent.\n// Mutates caches in place so existing references stay live.\nMember.rebuildPermissions = function() {\n const appTabPerms = Member.APP_PERMISSION_TABS.flatMap(t => t.permissions || []);\n\n Member.PERMISSIONS.length = 0;\n Member.PERMISSIONS.push(...Member.BASE_PERMISSIONS, ...Member.APP_PERMISSIONS, ...appTabPerms);\n\n Member.PERMISSION_FIELDS.length = 0;\n Member.PERMISSION_FIELDS.push(...Member.PERMISSIONS.map(_permSwitch));\n\n // One tabset for the detail-view section: Standard + App + named tabs.\n const tabs = [{ label: 'Standard', fields: Member.BASE_PERMISSIONS.map(_permSwitch) }];\n if (Member.APP_PERMISSIONS.length > 0) {\n tabs.push({ label: 'App', fields: Member.APP_PERMISSIONS.map(_permSwitch) });\n }\n for (const t of Member.APP_PERMISSION_TABS) {\n tabs.push({ label: t.label, fields: (t.permissions || []).map(_permSwitch) });\n }\n Member.PERMISSION_TABSET.length = 0;\n Member.PERMISSION_TABSET.push({ type: 'tabset', tabs });\n};\n\n// One-shot registration of app-level group permissions — mirrors\n// User.registerPermissions for the Member extension points. Appends and\n// rebuilds caches in one call so apps don't have to know which array to\n// push or to call rebuildPermissions() themselves.\n//\n// Member.registerPermissions({\n// // flat perms → a single \"App\" tab\n// permissions: [{ name: 'manage_app_thing', label: 'Manage App Thing' }],\n// // and/or named tabs → one tab each (multiple app domains)\n// tabs: [{ label: 'Verify', permissions: [{ name: 'view_verify', label: 'View Verify' }] }]\n// });\nMember.registerPermissions = function(spec) {\n if (!spec) return;\n if (Array.isArray(spec.permissions)) {\n Member.APP_PERMISSIONS.push(...spec.permissions);\n }\n if (Array.isArray(spec.tabs)) {\n Member.APP_PERMISSION_TABS.push(...spec.tabs);\n }\n Member.rebuildPermissions();\n};\n\n// Initial population.\nMember.rebuildPermissions();\n\nMember.EDIT_FORM = MemberForms.edit;\nMember.ADD_FORM = MemberForms.create;\n\n\nexport { Member, MemberList, MemberForms };\n","/**\n * TableRow - Individual row view for TableView\n *\n * Extends ListViewItem to render table rows with proper cell formatting\n * and support for all table features like selection, actions, and context menus.\n *\n * @example\n * const row = new TableRow({\n * model: userModel,\n * columns: tableColumns,\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport ListViewItem from '../list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TableRow extends ListViewItem {\n constructor(options = {}) {\n super({\n tagName: 'tr',\n className: 'table-row',\n enableTooltips: true,\n ...options\n });\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.tableView = options.tableView || options.listView || null;\n\n // Inline editing state\n this.editingCells = new Set(); // Track which cells are being edited\n\n // Override template to generate table cells\n this.template = this.buildRowTemplate();\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Build the row template with table cells\n */\n buildRowTemplate() {\n let template = '';\n\n // Selection checkbox cell\n if (this.tableView && this.tableView.isSelectable()) {\n template += `\n <td style=\"padding: 0;\">\n <div class=\"mojo-select-cell {{#selected}}selected{{/selected}}\"\n data-action=\"select\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </td>\n `;\n }\n\n // Data cells for each column\n this.columns.forEach((column, columnIndex) => {\n const cellClass = column.class || column.className || '';\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n const editableClass = column.editable ? 'editable-cell' : '';\n const alignClass = this.tableView && this.tableView.getAlignClass\n ? this.tableView.getAlignClass(column.align)\n : '';\n const combinedClasses = [cellClass, responsiveClasses, editableClass, alignClass].filter(c => c).join(' ');\n const cellContent = this.buildCellTemplate(column, columnIndex);\n\n // Determine cell action\n let cellAction = column.action;\n if (!cellAction && column.editable) {\n cellAction = 'edit-cell';\n } else if (!cellAction && this.tableView.rowAction) {\n cellAction = this.tableView.rowAction;\n }\n\n if (cellAction) {\n template += `<td class=\"${combinedClasses}\" data-action=\"${cellAction}\" data-column=\"${column.key}\">${cellContent}</td>`;\n } else {\n template += `<td class=\"${combinedClasses}\" data-column=\"${column.key}\">${cellContent}</td>`;\n }\n });\n\n // Actions cell\n if (this.actions) {\n template += this.buildActionsTemplate();\n } else if (this.contextMenu) {\n template += this.buildContextMenuTemplate();\n }\n\n return template;\n }\n\n /**\n * Build template for a single cell\n */\n /**\n * Build template for a single cell\n */\n buildCellTemplate(column, columnIndex = 0) {\n // Build path for Mustache to access the value\n const path = `model.${column.key}`;\n // Support both 'formatter' and 'format' for consistency with DataView\n const formatter = column.formatter || column.format;\n // editable cells need a `.cell-content` wrapper because enterEditMode()\n // hides it and inserts the editor in its place.\n const editableAttr = column.editable ? ` class=\"cell-content\" data-field=\"${column.key}\"` : '';\n if (formatter) {\n // For string formatters that are pipe expressions\n if (typeof formatter === 'string') {\n if (column.editable) {\n return `<span${editableAttr}>{{{${path}|${formatter}}}}</span>`;\n }\n return `{{{${path}|${formatter}}}}`;\n } else if (typeof formatter === 'function') {\n // Keep legacy data-formatter key selector for compatibility, but\n // use a per-column id so duplicate keys can be formatted correctly.\n const cls = column.editable ? 'cell-content' : '';\n const fieldAttr = column.editable ? ` data-field=\"${column.key}\"` : '';\n return `<span class=\"${cls}\" data-formatter=\"${column.key}\" data-formatter-id=\"${columnIndex}\"${fieldAttr}>{{${path}}}</span>`;\n }\n }\n\n if (column.template) {\n if (column.editable) {\n return `<span${editableAttr}>${column.template}</span>`;\n }\n return column.template;\n }\n\n // For editable cells, wrap content in a span for easy replacement\n if (column.editable) {\n return `<span${editableAttr}>{{{${path}}}}</span>`;\n }\n\n return `{{{${path}}}}`;\n }\n\n /**\n * Build actions cell template\n */\n buildActionsTemplate() {\n if (!this.actions || this.actions.length === 0) return '';\n\n const buttons = this.actions.map(action => {\n if (typeof action === 'string') {\n switch (action) {\n case 'view':\n return `\n <button class=\"btn btn-sm btn-outline-primary\"\n data-action=\"view\"\n title=\"View\">\n <i class=\"bi bi-eye\"></i>\n </button>\n `;\n\n case 'edit':\n return `\n <button class=\"btn btn-sm btn-outline-secondary\"\n data-action=\"edit\"\n title=\"Edit\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n `;\n\n case 'delete':\n return `\n <button class=\"btn btn-sm btn-outline-danger\"\n data-action=\"delete\"\n title=\"Delete\">\n <i class=\"bi bi-trash\"></i>\n </button>\n `;\n\n default:\n return '';\n }\n } else if (typeof action === 'object') {\n return `\n <button class=\"btn btn-sm ${action.class || 'btn-outline-primary'}\"\n data-id=\"${this.model.id}\"\n data-action=\"${action.action}\"\n title=\"${action.label || ''}\">\n ${action.icon ? `<i class=\"${action.icon}\"></i>` : ''}\n ${action.label && !action.icon ? action.label : ''}\n </button>\n `;\n }\n return '';\n }).join('');\n\n return `<td><div class=\"btn-group btn-group-sm\">${buttons}</div></td>`;\n }\n\n /**\n * Build context menu cell template\n */\n buildContextMenuTemplate() {\n if (!this.contextMenu || this.contextMenu.length === 0) return '';\n\n return `\n <td class=\"text-end\" style=\"width: 1px;\">\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-link border-0\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n style=\"color: #6c757d;\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end shadow-sm\">\n ${this.buildContextMenuItems()}\n </ul>\n </div>\n </td>\n `;\n }\n\n /**\n * Build context menu items\n */\n buildContextMenuItems() {\n return this.contextMenu.map(menuItem => {\n if (menuItem.separator||menuItem.divider) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n let itemClass = 'dropdown-item';\n if (menuItem.action === 'delete' || menuItem.danger) {\n itemClass += ' text-danger';\n }\n if (menuItem.disabled) {\n itemClass += ' disabled';\n }\n\n return `\n <li>\n <a class=\"${itemClass}\" href=\"#\"\n data-id=\"{{model.id}}\"\n data-action=\"${menuItem.action}\"\n ${menuItem.disabled ? 'aria-disabled=\"true\" tabindex=\"-1\"' : ''}>\n ${menuItem.icon ? `<i class=\"${menuItem.icon} me-2\"></i>` : ''}\n ${menuItem.label}\n </a>\n </li>\n `;\n }).join('');\n }\n\n /**\n * Override onAfterRender to apply function formatters and templates\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Apply function formatters\n this.columns.forEach((column, columnIndex) => {\n if (column.formatter && typeof column.formatter === 'function') {\n let cell = this.element.querySelector(`[data-formatter-id=\"${columnIndex}\"]`);\n if (!cell) {\n // Backward-compatible fallback for existing markup/selectors.\n cell = this.element.querySelector(`[data-formatter=\"${column.key}\"]`);\n }\n if (cell) {\n const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n const context = {\n value,\n row: this.model, // deprecate this\n model: this.model,\n column,\n table: this.tableView,\n index: this.index\n };\n try {\n cell.innerHTML = column.formatter(value, context);\n } catch (error) {\n console.error(`Error formatting cell for column ${column.key}:`, error);\n }\n }\n }\n\n // Apply function templates\n // if (column.template && typeof column.template === 'function') {\n // const cell = this.element.querySelector(`[data-template=\"${column.key}\"]`);\n // if (cell) {\n // const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n // cell.innerHTML = column.template(value, this.model);\n // }\n // }\n });\n\n // Update selection state\n if (this.selected) {\n this.element.classList.add('selected');\n }\n\n // Set data-id attribute for easy identification\n const id = this.model.get ? this.model.get('id') : this.model.id;\n if (id) {\n this.element.setAttribute('data-id', id);\n }\n }\n\n /**\n * Handle edit cell action\n */\n async onActionEditCell(event, element) {\n event.stopPropagation();\n\n const columnKey = element.getAttribute('data-column');\n const column = this.columns.find(col => col.key === columnKey);\n\n if (!column || !column.editable) return;\n\n // Don't enter edit mode if already editing this cell\n if (this.editingCells.has(columnKey)) return;\n\n await this.enterEditMode(columnKey, column, element);\n }\n\n /**\n * Handle row click action\n */\n async onActionRowClick(event, element) {\n // Don't trigger row click if clicking on action buttons or editing\n if (event.target.closest('.btn-group') || event.target.closest('.dropdown') || event.target.closest('.cell-editor')) {\n return;\n }\n\n // Emit row click event\n this.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n\n // Notify parent TableView\n if (this.tableView) {\n this.tableView.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n }\n }\n\n /**\n * Handle view action\n */\n async onActionView(event, element) {\n event.stopPropagation();\n\n this.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Handle edit action\n */\n async onActionEdit(event, element) {\n event.stopPropagation();\n\n this.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n }\n return true;\n }\n\n /**\n * Handle delete action\n */\n async onActionDelete(event, element) {\n event.stopPropagation();\n\n this.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Enter edit mode for a cell\n */\n async enterEditMode(columnKey, column, cellElement) {\n const contentSpan = cellElement.querySelector('.cell-content');\n if (!contentSpan) return;\n\n this.editingCells.add(columnKey);\n const currentValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Create editor based on column configuration\n const editor = this.createCellEditor(column, currentValue);\n\n // Replace content with editor\n const originalContent = contentSpan.innerHTML;\n contentSpan.style.display = 'none';\n\n const editorContainer = document.createElement('div');\n editorContainer.className = 'cell-editor';\n editorContainer.innerHTML = editor;\n cellElement.appendChild(editorContainer);\n\n // Focus the input\n const input = editorContainer.querySelector('input, select, .form-check-input');\n if (input) {\n input.focus();\n if (input.type === 'text' || input.type === 'textarea') {\n input.select();\n }\n }\n\n // Store original content for cancel\n editorContainer.dataset.originalContent = originalContent;\n editorContainer.dataset.columnKey = columnKey;\n\n // Set up event listeners\n this.setupEditorEvents(editorContainer, columnKey, column);\n\n this.emit('cell:edit', {\n row: this,\n model: this.model,\n column: columnKey,\n originalValue: currentValue\n });\n }\n\n /**\n * Create cell editor HTML based on column configuration\n */\n createCellEditor(column, currentValue) {\n const options = column.editableOptions || {};\n\n switch (options.type) {\n case 'select':\n return this.createSelectEditor(options, currentValue);\n case 'switch':\n case 'checkbox':\n return this.createSwitchEditor(options, currentValue);\n case 'textarea':\n return this.createTextareaEditor(options, currentValue);\n default:\n return this.createTextEditor(options, currentValue);\n }\n }\n\n /**\n * Create text input editor\n */\n createTextEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const inputType = options.inputType || 'text';\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <input type=\"${inputType}\"\n class=\"form-control form-control-sm cell-input\"\n value=\"${this.escapeHtml(currentValue || '')}\"\n placeholder=\"${placeholder}\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create textarea editor\n */\n createTextareaEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const rows = options.rows || 2;\n\n return `\n <div class=\"d-flex gap-1\">\n <textarea class=\"form-control form-control-sm cell-input\"\n rows=\"${rows}\"\n placeholder=\"${placeholder}\">${this.escapeHtml(currentValue || '')}</textarea>\n <div class=\"d-flex flex-column gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Create select dropdown editor\n */\n createSelectEditor(options, currentValue) {\n const optionsArray = options.options || [];\n let optionsHtml = '';\n\n optionsArray.forEach(option => {\n if (typeof option === 'string') {\n const selected = option === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option}\" ${selected}>${option}</option>`;\n } else if (typeof option === 'object' && option.value !== undefined) {\n const selected = option.value === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option.value}\" ${selected}>${option.label || option.value}</option>`;\n }\n });\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm cell-input\">\n ${optionsHtml}\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create switch/checkbox editor\n */\n createSwitchEditor(options, currentValue) {\n const checked = currentValue ? 'checked' : '';\n const switchType = options.type === 'switch' ? 'form-switch' : '';\n\n return `\n <div class=\"d-flex gap-2 align-items-center\">\n <div class=\"form-check ${switchType}\">\n <input class=\"form-check-input cell-input\" type=\"checkbox\" ${checked}>\n </div>\n <div class=\"d-flex gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Setup event listeners for cell editor\n */\n setupEditorEvents(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n const saveBtn = editorContainer.querySelector('.cell-save');\n const cancelBtn = editorContainer.querySelector('.cell-cancel');\n\n // Save on Enter (for text inputs)\n if (input && (input.type === 'text' || input.type === 'email' || input.type === 'number')) {\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.saveCellEdit(editorContainer, columnKey, column);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.cancelCellEdit(editorContainer, columnKey);\n }\n });\n }\n\n // Save on change for selects and checkboxes (if auto-save enabled)\n if (input && (input.type === 'checkbox' || input.tagName === 'SELECT') && column.autoSave !== false) {\n input.addEventListener('change', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n }\n\n // Button events\n saveBtn?.addEventListener('click', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n\n cancelBtn?.addEventListener('click', () => {\n this.cancelCellEdit(editorContainer, columnKey);\n });\n }\n\n /**\n * Save cell edit\n */\n async saveCellEdit(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n if (!input) return;\n\n let newValue;\n\n // Extract value based on input type\n if (input.type === 'checkbox') {\n newValue = input.checked;\n } else if (input.tagName === 'SELECT') {\n newValue = input.value;\n } else {\n newValue = input.value;\n }\n\n const oldValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Save to model and backend\n try {\n if (this.model.save) {\n await this.model.save({ [columnKey]: newValue });\n } else {\n // Fallback for models without save method\n this.model[columnKey] = newValue;\n }\n\n // Exit edit mode\n this.exitEditMode(editorContainer, columnKey, newValue);\n\n // Emit save event\n this.emit('cell:save', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue\n });\n\n } catch (error) {\n // Show error and keep in edit mode\n console.error('Failed to save cell edit:', error);\n this.emit('cell:save:error', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue,\n error: error\n });\n\n // Could show an error message in the UI\n editorContainer.classList.add('saving-error');\n setTimeout(() => editorContainer.classList.remove('saving-error'), 3000);\n }\n }\n\n /**\n * Cancel cell edit\n */\n cancelCellEdit(editorContainer, columnKey) {\n const originalContent = editorContainer.dataset.originalContent;\n this.exitEditMode(editorContainer, columnKey, null, originalContent);\n\n this.emit('cell:cancel', {\n row: this,\n model: this.model,\n column: columnKey\n });\n }\n\n /**\n * Exit edit mode and restore content\n */\n exitEditMode(editorContainer, columnKey, newValue = null, originalContent = null) {\n const cellElement = editorContainer.closest('td');\n const contentSpan = cellElement.querySelector('.cell-content');\n\n if (contentSpan) {\n if (newValue !== null) {\n // Update display with new value (with proper formatting if needed)\n const column = this.columns.find(col => col.key === columnKey);\n let displayValue = newValue;\n\n if (column && column.formatter && typeof column.formatter === 'string') {\n displayValue = dataFormatter.pipe(newValue, column.formatter);\n }\n\n contentSpan.innerHTML = this.escapeHtml(displayValue);\n } else if (originalContent) {\n // Restore original content on cancel\n contentSpan.innerHTML = originalContent;\n }\n\n contentSpan.style.display = '';\n }\n\n // Remove editor\n editorContainer.remove();\n this.editingCells.delete(columnKey);\n }\n\n /**\n * Escape HTML for safe display\n */\n escapeHtml(text) {\n if (text === null || text === undefined) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Override select to handle table-specific selection UI\n */\n select() {\n super.select();\n this.addClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.add('selected');\n }\n }\n\n /**\n * Override deselect to handle table-specific selection UI\n */\n deselect() {\n super.deselect();\n this.removeClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.remove('selected');\n }\n }\n}\n\nexport default TableRow;\n","/**\n * TableView - Advanced data table component extending ListView\n *\n * Renders a Collection as a `<table>` with sortable headers, per-column\n * filters, footer totals, batch actions, fullscreen mode, and Add/Export\n * toolbar buttons. The toolbar shell, search input, filter dropdown +\n * active-pill bar, numbered pagination, page-size selector, refresh\n * button, custom toolbar buttons, title/eyebrow, and right-slot view\n * are all inherited from ListView — TableView only adds table-specific\n * machinery (columns, sortable headers, footer totals, batch panel,\n * fullscreen, Add/Export buttons).\n *\n * @example\n * const table = new TableView({\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email', visibility: 'md' },\n * { key: 'phone', label: 'Phone', visibility: 'lg' },\n * { key: 'created', label: 'Created', formatter: 'date', visibility: 'xl' }\n * ],\n * actions: ['view', 'edit', 'delete'],\n * selectionMode: 'multiple'\n * });\n */\n\nimport ListView from '../list/ListView.js';\nimport TableRow from './TableRow.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\nimport { parseFilterKey } from '@core/utils/DjangoLookups.js';\n\nclass TableView extends ListView {\n constructor(options = {}) {\n // Set up table-specific defaults before calling super\n const tableOptions = {\n className: 'table-view-component',\n itemClass: options.itemClass || TableRow,\n selectionMode: options.selectable ? 'multiple' : 'none',\n emptyMessage: options.emptyMessage || 'No data available',\n addButtonIcon: options.addButtonIcon || 'bi bi-plus-circle',\n ...options\n };\n\n super(tableOptions);\n\n // Fullscreen state\n this.isFullscreen = false;\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n\n // Restore TableView's \"default true\" semantics for these toolbar flags.\n // ListView treats them as opt-in (default false). TableView preserves its\n // historical defaults so existing usage is unchanged.\n this.searchable = options.searchable !== false;\n this.sortable = options.sortable !== false;\n this.filterable = options.filterable !== false;\n this.paginated = options.paginated !== false;\n\n // Numbered pagination is the convention for tables; \"Show more\" wouldn't\n // make sense with row-per-record column layouts. Override ListView's\n // default of 'more' (set when only `paginated: true` was passed).\n this.paginationMode = options.paginationMode || 'pages';\n\n // TableView clears selection on page change unless the caller opts in.\n // This preserves prior behavior where rows are torn down per page.\n this.persistSelection = options.persistSelection === true;\n\n this.clickAction = options.clickAction || 'view';\n this.fetchOnView = options.fetchOnView !== false;\n\n // Model operation configurations\n this.itemView = options.itemView;\n this.addForm = options.addForm;\n this.editForm = options.editForm;\n this.deleteTemplate = options.deleteTemplate;\n this.formDialogConfig = options.formDialogConfig || {};\n this.viewDialogOptions = options.viewDialogOptions || {};\n\n // Export configuration\n this.exportOptions = options.exportOptions || null;\n if (this.options.showExport && !this.exportOptions) {\n this.exportOptions = [\n { format: 'csv', label: 'Export as CSV', icon: 'bi bi-file-earmark-spreadsheet' },\n { format: 'json', label: 'Export as JSON', icon: 'bi bi-file-earmark-code' }\n ];\n }\n this.exportSource = options.exportSource || 'remote';\n\n // Filter configuration — TableView populates `this.filters` from columns\n // (see extractColumnFilters); ListView's filter API consumes that map.\n this.filters = {};\n this.additionalFilters = options.filters || [];\n this.hideActivePills = options.hideActivePills === true;\n this.hideActivePillNames = options.hideActivePillNames || [];\n this.rowAction = options.rowAction || 'row-click';\n this.batchBarLocation = options.batchBarLocation || 'bottom';\n\n this.options.addButtonLabel = options.addButtonLabel || 'Add';\n\n // Custom toolbar buttons\n this.toolbarButtons = options.toolbarButtons || [];\n this.toolbarRight = options.toolbarRight || null;\n\n // Title block on the toolbar's left side.\n this.title = options.title || null;\n this.eyebrow = options.eyebrow || null;\n\n // Toolbar chrome gates — defaults preserve existing behavior.\n this.showRefresh = options.showRefresh !== false;\n this.showFullscreen = options.showFullscreen !== false;\n\n // Table display options\n this.tableOptions = {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n size: null,\n ...options.tableOptions\n };\n\n // Search configuration\n this.searchPlacement = options.searchPlacement || 'toolbar';\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n\n // Initialize column configuration BEFORE building template\n this.initializeColumns();\n\n // Extract filters from columns BEFORE building template\n this.extractColumnFilters();\n\n // Detect columns that need footer totals\n this.footerTotalColumns = this.columns.filter((col) => col.footer_total === true);\n this.hasFooterTotals = this.footerTotalColumns.length > 0;\n\n // Build template with Mustache variables\n this.template = this.buildTableTemplate();\n\n // Listen for collection changes to update totals\n this.setupCollectionListeners();\n }\n\n /**\n * Setup collection event listeners for totals updates\n */\n setupCollectionListeners() {\n if (this.hasFooterTotals && this.collection) {\n this.collection.on('reset add remove change', () => {\n this.updateFooterTotals();\n });\n }\n }\n\n /**\n * Initialize column configuration\n */\n initializeColumns() {\n this.columns.forEach((column) => {\n if (!column.key && column.name) column.key = column.name;\n if (!column.label && !column.title) {\n column.label = column.key.charAt(0).toUpperCase() + column.key.slice(1);\n }\n });\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return '';\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n if (typeof visibility === 'object') {\n const classes = [];\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n return classes.join(' ');\n }\n return '';\n }\n\n /**\n * Get Bootstrap text-alignment class for a column.\n */\n getAlignClass(align) {\n if (!align) return '';\n const map = {\n left: 'text-start',\n start: 'text-start',\n center: 'text-center',\n right: 'text-end',\n end: 'text-end'\n };\n const cls = map[String(align).toLowerCase()];\n if (!cls) {\n console.warn(`Invalid column align: ${align}. Valid options are: left, center, right`);\n return '';\n }\n return cls;\n }\n\n /**\n * Extract column key and formatter from combined key (e.g., \"sales_amount|currency\")\n */\n parseColumnKey(key) {\n const parts = key.split('|');\n return {\n fieldKey: parts[0],\n formatter: parts[1] || null\n };\n }\n\n /**\n * Update footer totals in the DOM without full re-render\n */\n updateFooterTotals() {\n if (!this.hasFooterTotals || !this.element) return;\n\n const totals = this.calculateFooterTotals();\n\n let totalColumnIndex = 0;\n this.columns.forEach((column) => {\n if (column.footer_total) {\n const safeKey = `col_${totalColumnIndex}`;\n const cell = this.element.querySelector(`[data-total-column=\"${safeKey}\"]`);\n\n if (cell && totals[safeKey]) {\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n let displayValue;\n\n if (formatter && typeof formatter === 'string') {\n displayValue = this.formatValue(totals[safeKey].value, formatter);\n } else {\n displayValue = totals[safeKey].value;\n }\n cell.textContent = displayValue;\n }\n totalColumnIndex++;\n }\n });\n }\n\n /**\n * Format a value using DataFormatter\n */\n formatValue(value, formatter) {\n try {\n return dataFormatter.pipe(value, formatter);\n } catch (e) {\n console.warn('Error formatting value:', e);\n return value;\n }\n }\n\n /**\n * Calculate totals for footer columns\n */\n calculateFooterTotals() {\n if (!this.hasFooterTotals || !this.collection || this.collection.length === 0) {\n return {};\n }\n\n const totals = {};\n\n this.footerTotalColumns.forEach((column, totalColumnIndex) => {\n const { fieldKey, formatter } = this.parseColumnKey(column.key);\n let sum = 0;\n\n this.collection.forEach((model) => {\n const value = model.get ? model.get(fieldKey) : model[fieldKey];\n const numValue = parseFloat(value) || 0;\n sum += numValue;\n });\n\n const safeKey = `col_${totalColumnIndex}`;\n totals[safeKey] = {\n value: sum,\n formatter: formatter || column.formatter,\n fieldKey: fieldKey,\n originalKey: column.key\n };\n });\n\n return totals;\n }\n\n /**\n * Extract filters from column configuration. Folds the column's `label`\n * into the filter config as a fallback so ListView's getFilterLabel()\n * (which only reads from `this.filters[key].label`) preserves the\n * historical \"filter label → column label → fieldKey\" fallback chain.\n */\n extractColumnFilters() {\n this.filters = {};\n this.columns.forEach((column) => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n this.filters[fieldKey] = {\n ...column.filter,\n label: column.filter.label || column.label || fieldKey\n };\n }\n });\n }\n\n isSelectable() {\n return this.batchActions && this.batchActions.length > 0 && this.selectionMode === 'multiple';\n }\n\n /**\n * Build the complete table template\n */\n buildTableTemplate() {\n const batchPanelTop = this.batchBarLocation === 'top' ? this.buildBatchActionsPanel() : '';\n const batchPanelBottom = this.batchBarLocation === 'bottom' ? this.buildBatchActionsPanel() : '';\n\n const fontSize = (() => {\n const __fs = (this.tableOptions && this.tableOptions.fontSize != null)\n ? this.tableOptions.fontSize\n : (this.options && this.options.fontSize);\n const __val = __fs === 'sm' ? '0.9rem' : (__fs === 'xs' ? '0.8rem' : (__fs ? String(__fs) : null));\n return __val ? ` style=\"font-size: ${__val};\"` : '';\n })();\n\n return `\n <div class=\"mojo-table-wrapper\">\n ${this.buildToolbarTemplate()}\n ${batchPanelTop}\n <div class=\"table-container\"${fontSize}>\n {{#loading}}\n <div class=\"mojo-table-loading d-flex justify-content-center align-items-center py-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"table-empty text-center py-5\">\n <i class=\"bi bi-inbox fa-2x mb-2 text-muted\"></i>\n <p class=\"text-muted\">{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <table class=\"${this.buildTableClasses()}\">\n ${this.buildTableHeaderTemplate()}\n <tbody data-container=\"items\"></tbody>\n ${this.hasFooterTotals ? this.buildTableFooterTemplate() : ''}\n </table>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${batchPanelBottom}\n ${this.paginated ? this.buildPaginationTemplate() : ''}\n </div>\n `;\n }\n\n /**\n * Build table CSS classes\n */\n buildTableClasses() {\n let classes = ['table'];\n\n if (this.tableOptions.striped) classes.push('table-striped');\n if (this.tableOptions.bordered) classes.push('table-bordered');\n if (this.tableOptions.hover) classes.push('table-hover');\n if (this.tableOptions.responsive) classes.push('table-responsive');\n if (this.tableOptions.background) classes.push(`table-${this.tableOptions.background}`);\n if (this.tableOptions.size === 'sm') classes.push('table-sm');\n if (this.tableOptions.size === 'lg') classes.push('table-lg');\n\n return classes.join(' ');\n }\n\n /**\n * Override buildActionButtonsTemplate to inject the Fullscreen button.\n * Refresh / Add / Export / custom toolbarButtons all come from the\n * inherited ListView implementation. Fullscreen is table-only because\n * full-screening a list of cards isn't a meaningful UX (it would\n * already use the full viewport).\n */\n buildActionButtonsTemplate() {\n let baseButtons = super.buildActionButtonsTemplate();\n\n if (this.showFullscreen && this.isFullscreenSupported()) {\n const fullscreenBtn = `\n <button class=\"btn btn-sm btn-outline-secondary btn-fullscreen\"\n data-action=\"toggle-fullscreen\"\n title=\"Toggle Fullscreen\">\n <i class=\"bi bi-fullscreen\"></i>\n </button>\n `;\n // Insert after the refresh button (if present) so the visual order\n // matches the historical TableView layout: refresh, fullscreen, add,\n // export, custom.\n const refreshIdx = baseButtons.indexOf('data-action=\"refresh\"');\n if (refreshIdx !== -1) {\n const closingTagEnd = baseButtons.indexOf('</button>', refreshIdx) + '</button>'.length;\n baseButtons = baseButtons.slice(0, closingTagEnd) + fullscreenBtn + baseButtons.slice(closingTagEnd);\n } else {\n baseButtons = fullscreenBtn + baseButtons;\n }\n }\n\n return baseButtons;\n }\n\n /**\n * Build table header template\n */\n buildTableHeaderTemplate() {\n let headerCells = '';\n\n // Selection checkbox header\n if (this.isSelectable()) {\n headerCells += `\n <th style=\"width: 40px; padding: 0;\">\n <div class=\"mojo-select-all-cell\" data-action=\"select-all\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </th>\n `;\n }\n\n // Column headers\n this.columns.forEach((column) => {\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const sortable = this.sortable && column.sortable !== false;\n const currentSort = this.getSortBy() === fieldKey ? this.getSortDirection() : null;\n const sortIcon = this.getSortIcon(currentSort);\n const label = column.label || column.title || fieldKey;\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n const sortDropdown = sortable ? `\n <div class=\"dropdown d-inline-block ms-2\">\n <button class=\"btn btn-sm btn-link p-0 text-decoration-none\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\"\n data-column=\"${fieldKey}\">\n ${sortIcon}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n <li><a class=\"dropdown-item ${currentSort === 'asc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"asc\">\n <i class=\"bi bi-sort-alpha-down me-2\"></i>Sort A-Z\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === 'desc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"desc\">\n <i class=\"bi bi-sort-alpha-down-alt me-2\"></i>Sort Z-A\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === null ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"none\">\n <i class=\"bi bi-x-circle me-2\"></i>No Sort\n </a></li>\n </ul>\n </div>\n ` : '';\n\n const alignClass = this.getAlignClass(column.align);\n const headerJustify = alignClass === 'text-center'\n ? 'justify-content-center'\n : alignClass === 'text-end'\n ? 'justify-content-end'\n : '';\n\n headerCells += `\n <th class=\"${sortable ? 'sortable' : ''} ${responsiveClasses} ${alignClass}\">\n <div class=\"d-flex align-items-center ${headerJustify}\">\n <span>${label}</span>\n ${sortDropdown}\n </div>\n </th>\n `;\n });\n\n if (this.actions) {\n headerCells += '<th>Actions</th>';\n } else if (this.contextMenu) {\n headerCells += '<th style=\"width: 1px;\"></th>';\n }\n\n return `\n <thead>\n <tr>\n ${headerCells}\n </tr>\n </thead>\n `;\n }\n\n /**\n * Build table footer template with totals\n */\n buildTableFooterTemplate() {\n let footerCells = '';\n\n if (this.isSelectable()) {\n footerCells += '<td></td>';\n }\n\n let totalColumnIndex = 0;\n this.columns.forEach((column, index) => {\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n const alignClass = this.getAlignClass(column.align);\n\n if (column.footer_total) {\n const safeKey = `col_${totalColumnIndex}`;\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n let cellContent;\n if (formatter && typeof formatter === 'string') {\n cellContent = `{{{footerTotals.${safeKey}.value|${formatter}}}}`;\n } else {\n cellContent = `{{footerTotals.${safeKey}.value}}`;\n }\n\n footerCells += `<td class=\"table-footer-total ${responsiveClasses} ${alignClass}\" data-total-column=\"${safeKey}\">${cellContent}</td>`;\n totalColumnIndex++;\n } else if (index === 0) {\n footerCells += `<td class=\"table-footer-label ${responsiveClasses} ${alignClass}\"><strong>Totals</strong></td>`;\n } else {\n footerCells += `<td class=\"${responsiveClasses} ${alignClass}\"></td>`;\n }\n });\n\n if (this.actions) {\n footerCells += '<td></td>';\n } else if (this.contextMenu) {\n footerCells += '<td></td>';\n }\n\n return `\n <tfoot>\n <tr class=\"table-totals-row\">\n ${footerCells}\n </tr>\n </tfoot>\n `;\n }\n\n /**\n * Build batch actions panel\n */\n buildBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) return '';\n\n if (this.batchBarLocation === 'top') {\n let actionsHTML = '';\n this.batchActions.forEach((action) => {\n actionsHTML += `\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"batch-${action.action}\" title=\"${action.label}\">\n <i class=\"${action.icon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${action.label}</span>\n </button>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel-top alert alert-info d-none mb-3\" role=\"alert\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"me-2\">\n <span class=\"batch-select-count\">0</span> ${this.options.batchPanelTitle || 'items'} selected\n </strong>\n </div>\n <div class=\"d-flex gap-2 align-items-center\">\n ${actionsHTML}\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"clear-selection\" title=\"Clear Selection\">\n <i class=\"bi bi-x-circle me-1\"></i>\n <span class=\"d-none d-lg-inline\">Clear</span>\n </button>\n </div>\n </div>\n </div>\n `;\n } else {\n let actionsHTML = '';\n this.batchActions.forEach((action) => {\n actionsHTML += `\n <div class=\"batch-select-action text-center px-2\" data-action=\"batch-${action.action}\">\n <div class=\"batch-action-icon fs-3\">\n <i class=\"${action.icon}\"></i>\n </div>\n <div class=\"batch-action-title small\">${action.label}</div>\n </div>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel rounded-start rounded-end\" style=\"display: none;\">\n <div class=\"batch-select-panel rounded-start rounded-end\">\n <div class=\"row g-0\">\n <div class=\"col-auto\">\n <div class=\"batch-select-count rounded-start\">0</div>\n </div>\n <div class=\"col\">\n <div class=\"ps-2 batch-select-title\">${this.options.batchPanelTitle || 'Rows'}</div>\n </div>\n <div class=\"col\">\n <div class=\"batch-select-actions d-flex justify-content-end\">\n ${actionsHTML}\n </div>\n </div>\n <div class=\"col-auto\">\n <div class=\"batch-select-end rounded-end\"></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n }\n\n /**\n * Override _createItemView to pass table-specific options (columns,\n * actions, contextMenu, batchActions). Routes through the inherited\n * `_wireItemViewListeners` for the standard select / click / view /\n * edit / delete event wiring, then layers on the table-only events\n * (cell:edit/save/cancel) and the batch-actions select hook.\n */\n _createItemView(model, index) {\n if (this.itemViews.has(model.id)) return this.itemViews.get(model.id);\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n tableView: this,\n template: this.itemTemplate,\n columns: this.columns,\n actions: this.actions,\n contextMenu: this.contextMenu,\n batchActions: this.batchActions,\n containerId: 'items'\n });\n\n this.itemViews.set(model.id, itemView);\n\n // Standard select / click / row:view/edit/delete listeners.\n this._wireItemViewListeners(itemView);\n\n // Batch-actions panel must update on every selection toggle —\n // augment the base select handlers (the parent already wired its\n // own _onItemSelect / _onItemDeselect; we add the panel update on top).\n itemView.on('item:select', () => this.updateBatchActionsPanel());\n itemView.on('item:deselect', () => this.updateBatchActionsPanel());\n\n // Table-only inline cell editing events.\n itemView.on('cell:edit', this._onCellEdit.bind(this));\n itemView.on('cell:save', this._onCellSave.bind(this));\n itemView.on('cell:cancel', this._onCellCancel.bind(this));\n\n return itemView;\n }\n\n /**\n * Override onBeforeRender to surface footerTotals into the template context\n * (still calls super for searchValue + hasMore).\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n this.footerTotals = this.calculateFooterTotals();\n }\n\n /**\n * Override onAfterRender to also update footer totals + sort icons after\n * the inherited toolbar / pagination / pills update.\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n if (this.hasFooterTotals) this.updateFooterTotals();\n this.updateSortIcons();\n }\n\n // -------- Cell-editing events (table-only) --------\n _onCellEdit(event) { this.emit('cell:edit', event); }\n async _onCellSave(event) { this.emit('cell:save', event); }\n _onCellCancel(event) { this.emit('cell:cancel', event); }\n\n // ============================================================\n // Grouped rows (TableView-aware overrides)\n //\n // ListView's grouping primitive emits a `<div class=\"list-group-header\">`\n // by default. That shape is invalid inside a `<tbody>` (browsers will\n // hoist the `<div>` out of the table). TableView overrides the outer\n // element to a `<tr class=\"list-group-header-row\">` and the inner\n // template to a `<th colspan=\"N\">…</th>` cell so the header sits in\n // the table grid and spans the full row.\n // ============================================================\n\n /**\n * Default group-header inner template for TableView. Wrapped by the\n * `<tr>` outer element from `_groupHeaderViewOptions` below.\n * @protected\n */\n _defaultGroupHeaderTemplate() {\n return '<th colspan=\"{{colspan}}\" class=\"list-group-header-cell\">{{key}}</th>';\n }\n\n /**\n * Inject TableView-specific constructor options on the header view.\n * `tagName: 'tr'` makes the outer element legal inside `<tbody>`;\n * `colspan` covers the selection checkbox col + data cols + actions col.\n * @protected\n */\n _groupHeaderViewOptions(_model, _key, _index) {\n const dataCols = this.columns?.length || 0;\n const selectCol = this.isSelectable() ? 1 : 0;\n const actionsCol = (this.actions || this.contextMenu) ? 1 : 0;\n return {\n tagName: 'tr',\n className: `list-group-header-row list-group-header-row--${this.groupHeaderStyle}`,\n colspan: Math.max(1, dataCols + selectCol + actionsCol)\n };\n }\n\n // ============================================================\n // Fullscreen\n // ============================================================\n\n isFullscreenSupported() {\n return !!(\n document.fullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.msFullscreenEnabled\n );\n }\n\n async onActionToggleFullscreen(_event, _element) {\n if (this.isFullscreen) {\n await this.exitFullscreen();\n } else {\n await this.enterFullscreen();\n }\n }\n\n async enterFullscreen() {\n try {\n if (this.element.requestFullscreen) {\n await this.element.requestFullscreen();\n } else if (this.element.mozRequestFullScreen) {\n await this.element.mozRequestFullScreen();\n } else if (this.element.webkitRequestFullscreen) {\n await this.element.webkitRequestFullscreen();\n } else if (this.element.msRequestFullscreen) {\n await this.element.msRequestFullscreen();\n }\n\n this.isFullscreen = true;\n this.element.classList.add('table-fullscreen');\n this.updateFullscreenButton();\n\n this.setupFullscreenListeners();\n this.emit('table:fullscreen:enter');\n } catch (error) {\n console.warn('Could not enter fullscreen:', error);\n }\n }\n\n async exitFullscreen() {\n try {\n if (document.exitFullscreen) {\n await document.exitFullscreen();\n } else if (document.mozCancelFullScreen) {\n await document.mozCancelFullScreen();\n } else if (document.webkitExitFullscreen) {\n await document.webkitExitFullscreen();\n } else if (document.msExitFullscreen) {\n await document.msExitFullscreen();\n }\n\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n\n this.emit('table:fullscreen:exit');\n } catch (error) {\n console.warn('Could not exit fullscreen:', error);\n }\n }\n\n updateFullscreenButton() {\n const button = this.element?.querySelector('.btn-fullscreen');\n const icon = button?.querySelector('i');\n\n if (button && icon) {\n if (this.isFullscreen) {\n icon.className = 'bi bi-fullscreen-exit';\n button.title = 'Exit Fullscreen';\n } else {\n icon.className = 'bi bi-fullscreen';\n button.title = 'Enter Fullscreen';\n }\n }\n }\n\n setupFullscreenListeners() {\n if (this._fullscreenHandler) return;\n\n const handleFullscreenChange = () => {\n const isCurrentlyFullscreen = !!(\n document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement ||\n document.msFullscreenElement\n );\n\n if (!isCurrentlyFullscreen && this.isFullscreen) {\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n this.emit('table:fullscreen:exit');\n }\n };\n\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n document.addEventListener('msfullscreenchange', handleFullscreenChange);\n\n this._fullscreenHandler = handleFullscreenChange;\n }\n\n cleanupFullscreenListeners() {\n if (this._fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this._fullscreenHandler);\n document.removeEventListener('mozfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('webkitfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('msfullscreenchange', this._fullscreenHandler);\n this._fullscreenHandler = null;\n }\n }\n\n /**\n * Override destroy to cleanup fullscreen listeners\n */\n destroy() {\n this.cleanupFullscreenListeners();\n super.destroy();\n }\n\n // ============================================================\n // Add / Export — backwards-compat event names\n //\n // The model lifecycle (Add dialog, Export download) lives on ListView\n // now. TableView keeps thin overrides solely to preserve the\n // historical `table:add` / `table:export` event names that\n // TablePage and other consumers listen for.\n // ============================================================\n\n async onActionAdd(event, element) {\n this.emit('table:add', { event });\n return super.onActionAdd(event, element);\n }\n\n async onActionExport(event, element) {\n const format = element.getAttribute('data-format') || 'json';\n this.emit('table:export', { format, source: this.exportSource, event });\n return super.onActionExport(event, element);\n }\n\n // ============================================================\n // Column-header sort (TableView-specific; ListView has its own\n // toolbar `sortOptions` dropdown via `onActionSortOption`)\n // ============================================================\n\n getSortBy() {\n const sort = this.collection?.params?.sort;\n if (!sort) return null;\n return sort.startsWith('-') ? sort.slice(1) : sort;\n }\n\n getSortDirection() {\n const sort = this.collection?.params?.sort;\n if (!sort) return 'asc';\n return sort.startsWith('-') ? 'desc' : 'asc';\n }\n\n getSortIcon(direction) {\n if (direction === 'asc') {\n return '<i class=\"bi bi-sort-alpha-down text-primary\"></i>';\n } else if (direction === 'desc') {\n return '<i class=\"bi bi-sort-alpha-down-alt text-primary\"></i>';\n }\n return '<i class=\"bi bi-three-dots-vertical text-muted\"></i>';\n }\n\n async onActionSort(event, element) {\n event.preventDefault();\n const field = element.getAttribute('data-field');\n const direction = element.getAttribute('data-direction');\n\n if (this.collection) {\n let newSort;\n\n if (direction === 'none') {\n newSort = undefined;\n } else if (direction === 'desc') {\n newSort = `-${field}`;\n } else {\n newSort = field;\n }\n\n this.collection.setParams({\n ...this.collection.params,\n sort: newSort,\n start: 0\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n if (newSort) {\n const desc = newSort.startsWith('-');\n const sortField = desc ? newSort.slice(1) : newSort;\n\n this.collection.sort((a, b) => {\n const aVal = a.get(sortField);\n const bVal = b.get(sortField);\n if (aVal < bVal) return desc ? 1 : -1;\n if (aVal > bVal) return desc ? -1 : 1;\n return 0;\n });\n }\n this.render();\n }\n }\n\n this.updateSortIcons();\n this.emit('table:sort', { field, event });\n this.emit('params-changed');\n }\n\n updateSortIcons() {\n if (!this.element) return;\n\n const currentSortField = this.getSortBy();\n const currentSortDir = this.getSortDirection();\n\n this.columns.forEach((column) => {\n if (this.sortable && column.sortable !== false) {\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const dropdown = this.element.querySelector(`[data-bs-toggle=\"dropdown\"][data-column=\"${fieldKey}\"]`);\n if (dropdown) {\n const isSorted = currentSortField === fieldKey;\n const sortIcon = this.getSortIcon(isSorted ? currentSortDir : null);\n dropdown.innerHTML = sortIcon;\n\n const dropdownMenu = dropdown.nextElementSibling;\n if (dropdownMenu) {\n const ascItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"asc\"]`);\n const descItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"desc\"]`);\n const noneItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"none\"]`);\n\n if (ascItem) ascItem.classList.toggle('active', isSorted && currentSortDir === 'asc');\n if (descItem) descItem.classList.toggle('active', isSorted && currentSortDir === 'desc');\n if (noneItem) noneItem.classList.toggle('active', !isSorted || currentSortField !== fieldKey);\n }\n }\n }\n });\n }\n\n // ============================================================\n // Batch actions / select-all\n // ============================================================\n\n async onActionSelectAll(event, _element) {\n event.stopPropagation();\n const isCurrentlyAllSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every((item) => item.selected);\n\n if (!isCurrentlyAllSelected) {\n this.forEachItem((itemView) => {\n if (!itemView.selected) itemView.select();\n });\n } else {\n this.clearSelection();\n }\n\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) selectAllCell.classList.toggle('selected', !isCurrentlyAllSelected);\n\n this.updateBatchActionsPanel();\n }\n\n updateBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) return;\n\n const selectedCount = this.getSelectedItems().length;\n\n if (this.batchBarLocation === 'top') {\n const panel = this.element?.querySelector('.batch-actions-panel-top');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n if (selectedCount > 0) {\n panel.classList.remove('d-none');\n } else {\n panel.classList.add('d-none');\n }\n }\n } else {\n const panel = this.element?.querySelector('.batch-actions-panel');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n panel.style.display = selectedCount > 0 ? 'block' : 'none';\n }\n }\n\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n const allSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every((item) => item.selected);\n const someSelected = Array.from(this.itemViews.values()).some((item) => item.selected);\n\n selectAllCell.classList.toggle('selected', allSelected);\n selectAllCell.classList.toggle('indeterminate', !allSelected && someSelected);\n\n const icon = selectAllCell.querySelector('i');\n if (icon) icon.className = !allSelected && someSelected ? 'bi bi-dash' : 'bi bi-check';\n }\n }\n\n async onActionBatch(event, element) {\n const batchAction = element.getAttribute('data-action').replace('batch-', '');\n const selectedItems = this.getSelectedItems();\n\n this.emit('batch:action', {\n action: batchAction,\n items: selectedItems,\n event\n });\n }\n\n async onActionClearSelection(_event, _element) {\n this.clearSelection();\n this.updateBatchActionsPanel();\n }\n\n // ============================================================\n // Lookup / parse helpers (kept for any external consumers that\n // imported them from TableView's namespace)\n // ============================================================\n\n /**\n * Re-export for callers that called this on a TableView instance.\n * (ListView's getActiveFilters reads `this.collection.params` and is fully\n * equivalent — we re-resolve via super.)\n */\n getFilterDisplayValue(key, value) {\n if (key === 'search') return `\"${value}\"`;\n\n const filter = this.filters[key] ||\n this.additionalFilters.find((f) => (f.name || f.key) === key);\n\n if (filter && filter.type === 'daterange' && typeof value === 'object') {\n const start = value.start || '';\n const end = value.end || '';\n return `${start} to ${end}`;\n }\n\n if (filter && filter.type === 'select' && filter.options) {\n if (typeof filter.options[0] === 'object') {\n const option = filter.options.find((opt) => opt.value === value);\n return option ? option.label : value;\n }\n return value;\n }\n\n return value;\n }\n}\n\n// Re-export parseFilterKey so any module that imported it from this file\n// (legacy import path) continues to work.\nexport { parseFilterKey };\n\nexport default TableView;\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\n// ─── WebAuthn base64url helpers ──────────────────────────────────────────────\nfunction base64urlToBytes(base64url) {\n const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);\n return Uint8Array.from(atob(padded), c => c.charCodeAt(0));\n}\n\nfunction bytesToBase64url(buffer) {\n return btoa(String.fromCharCode(...new Uint8Array(buffer)))\n .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\n/**\n * Passkey - WebAuthn/FIDO2 passkey model\n * Maps to REST endpoints under /api/account/passkeys\n *\n * Key operations:\n * - List/View/Update/Delete passkeys (standard CRUD)\n * - Register new passkeys via Passkey.register(friendlyName)\n *\n * Notes:\n * - Login flow is NOT handled here (separate auth flow)\n * - Passkeys are portal-specific (rp_id = domain)\n * - Most fields are read-only; only friendly_name and is_enabled are editable\n */\nclass Passkey extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/account/passkeys',\n ...options\n });\n }\n\n /**\n * Suggest a friendly name based on the user's device and browser.\n * @returns {string} e.g. \"Mac — Chrome\", \"iPhone — Safari\"\n */\n static suggestName() {\n const ua = navigator.userAgent;\n let device = 'Device';\n if (/iPad/.test(ua)) device = 'iPad';\n else if (/iPhone/.test(ua)) device = 'iPhone';\n else if (/Macintosh|MacIntel/.test(ua)) device = 'Mac';\n else if (/Android/.test(ua)) device = 'Android';\n else if (/Windows/.test(ua)) device = 'Windows PC';\n else if (/Linux/.test(ua)) device = 'Linux';\n\n let browser = '';\n if (/Edg\\//.test(ua)) browser = 'Edge';\n else if (/Chrome\\//.test(ua) && !/Chromium/.test(ua)) browser = 'Chrome';\n else if (/Safari\\//.test(ua) && !/Chrome/.test(ua)) browser = 'Safari';\n else if (/Firefox\\//.test(ua)) browser = 'Firefox';\n\n return browser ? `${device} — ${browser}` : device;\n }\n\n /**\n * Full passkey registration flow.\n * Handles: registerBegin → navigator.credentials.create → registerComplete\n *\n * Call this AFTER collecting the friendly name from the user.\n *\n * @param {string} friendlyName - Human-readable label for the passkey\n * @returns {Promise<{success: boolean, passkey?: object, error?: string}>}\n */\n static async register(friendlyName) {\n // 1. Begin — get challenge from server\n const beginResp = await Passkey.registerBegin();\n if (!beginResp?.data?.challenge_id || !beginResp?.data?.publicKey) {\n return { success: false, error: beginResp?.error || 'Could not start registration.' };\n }\n\n const { challenge_id, publicKey } = beginResp.data;\n\n // 2. Decode base64url fields the browser expects as ArrayBuffers\n if (typeof publicKey.challenge === 'string') {\n publicKey.challenge = base64urlToBytes(publicKey.challenge);\n }\n if (typeof publicKey.user?.id === 'string') {\n publicKey.user.id = base64urlToBytes(publicKey.user.id);\n }\n if (publicKey.excludeCredentials) {\n publicKey.excludeCredentials = publicKey.excludeCredentials.map(cred => ({\n ...cred,\n id: typeof cred.id === 'string' ? base64urlToBytes(cred.id) : cred.id\n }));\n }\n\n // 3. OS biometric prompt\n const credential = await navigator.credentials.create({ publicKey });\n if (!credential) {\n return { success: false, error: 'Passkey creation was cancelled.' };\n }\n\n // 4. Encode credential for the server\n const credentialData = {\n id: credential.id,\n rawId: bytesToBase64url(credential.rawId),\n type: credential.type,\n response: {\n clientDataJSON: bytesToBase64url(credential.response.clientDataJSON),\n attestationObject: bytesToBase64url(credential.response.attestationObject)\n }\n };\n if (credential.response.getTransports) {\n credentialData.transports = credential.response.getTransports();\n }\n\n // 5. Complete registration\n const completeResp = await Passkey.registerComplete({\n challenge_id,\n credential: credentialData,\n friendly_name: friendlyName || 'My Passkey'\n });\n\n if (completeResp?.data?.id) {\n return { success: true, passkey: completeResp.data };\n }\n return { success: false, error: completeResp?.error || 'Registration could not be completed.' };\n }\n\n /** @private */\n static async registerBegin(options = {}) {\n try {\n return await rest.POST('/api/account/passkeys/register/begin', {}, options.params, { dataOnly: true });\n } catch (err) {\n return { success: false, error: err?.message || 'Failed to begin passkey registration' };\n }\n }\n\n /** @private */\n static async registerComplete(data = {}, options = {}) {\n if (!data.challenge_id || !data.credential) {\n return { success: false, error: 'Missing challenge_id or credential data' };\n }\n try {\n return await rest.POST('/api/account/passkeys/register/complete', data, options.params, { dataOnly: true });\n } catch (err) {\n return { success: false, error: err?.message || 'Failed to complete passkey registration' };\n }\n }\n}\n\n/**\n * PasskeyList - Collection of Passkey\n * Supports standard MOJO list/search/sort/pagination patterns\n */\nclass PasskeyList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Passkey,\n endpoint: '/api/account/passkeys',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for Passkey\n *\n * Notes:\n * - No create form (registration uses WebAuthn flow)\n * - Edit form allows changing friendly_name and is_enabled only\n * - View form shows all fields as read-only for informational purposes\n */\nconst PasskeyForms = {\n edit: {\n title: 'Edit Passkey',\n fields: [\n {\n name: 'friendly_name',\n type: 'text',\n label: 'Name',\n placeholder: 'My iPhone',\n required: true,\n columns: 12,\n help: 'A friendly name to identify this passkey'\n },\n {\n name: 'is_enabled',\n type: 'switch',\n label: 'Enabled',\n columns: 12,\n help: 'Disable to prevent this passkey from being used for authentication'\n }\n ]\n },\n\n view: {\n title: 'Passkey Details',\n fields: [\n {\n name: 'friendly_name',\n type: 'text',\n label: 'Name',\n readonly: true,\n columns: 12\n },\n {\n name: 'is_enabled',\n type: 'switch',\n label: 'Enabled',\n readonly: true,\n columns: 6\n },\n {\n name: 'rp_id',\n type: 'text',\n label: 'Portal (RP ID)',\n readonly: true,\n columns: 6,\n help: 'The portal/domain this passkey is registered for'\n },\n {\n name: 'aaguid',\n type: 'text',\n label: 'Authenticator GUID',\n readonly: true,\n columns: 12,\n help: 'Unique identifier for the authenticator device'\n },\n {\n name: 'transports',\n type: 'text',\n label: 'Transports',\n readonly: true,\n columns: 6,\n help: 'Available transport methods (e.g., internal, usb, nfc, ble)'\n },\n {\n name: 'sign_count',\n type: 'number',\n label: 'Signature Count',\n readonly: true,\n columns: 6,\n help: 'Number of times this passkey has been used (for clone detection)'\n },\n {\n name: 'last_used',\n type: 'datetime',\n label: 'Last Used',\n readonly: true,\n columns: 6\n },\n {\n name: 'created',\n type: 'datetime',\n label: 'Created',\n readonly: true,\n columns: 6\n }\n ]\n }\n};\n\nexport {\n Passkey,\n PasskeyList,\n PasskeyForms\n};\n"],"names":["Log","Model","constructor","data","super","endpoint","LogList","Collection","options","ModelClass","size","Member","hasPermission","permission","Array","isArray","some","p","this","permissions","get","startsWith","fetchForGroup","groupId","fetch","url","MemberList","MemberForms","edit","title","fields","name","type","label","placeholder","columns","BASE_PERMISSIONS","APP_PERMISSIONS","APP_PERMISSION_TABS","PERMISSIONS","PERMISSION_FIELDS","PERMISSION_TABSET","_permSwitch","tooltip","rebuildPermissions","appTabPerms","flatMap","t","length","push","map","tabs","registerPermissions","spec","EDIT_FORM","ADD_FORM","create","TableRow","ListViewItem","tagName","className","enableTooltips","actions","contextMenu","batchActions","tableView","listView","editingCells","Set","template","buildRowTemplate","getResponsiveClasses","visibility","validBreakpoints","includes","console","warn","join","classes","hide","show","isSelectable","forEach","column","columnIndex","combinedClasses","class","editable","getAlignClass","align","filter","c","cellContent","buildCellTemplate","cellAction","action","rowAction","key","buildActionsTemplate","buildContextMenuTemplate","path","formatter","format","editableAttr","cls","fieldAttr","model","id","icon","buildContextMenuItems","menuItem","separator","divider","itemClass","danger","disabled","onAfterRender","cell","element","querySelector","value","context","row","table","index","innerHTML","error","selected","classList","add","setAttribute","onActionEditCell","event","stopPropagation","columnKey","getAttribute","find","col","has","enterEditMode","onActionRowClick","target","closest","emit","onActionView","onActionEdit","onActionDelete","cellElement","contentSpan","currentValue","editor","createCellEditor","originalContent","style","display","editorContainer","document","createElement","appendChild","input","focus","select","dataset","setupEditorEvents","originalValue","editableOptions","createSelectEditor","createSwitchEditor","createTextareaEditor","createTextEditor","inputType","escapeHtml","rows","optionsArray","optionsHtml","option","checked","saveBtn","cancelBtn","addEventListener","e","preventDefault","saveCellEdit","cancelCellEdit","autoSave","newValue","oldValue","save","exitEditMode","setTimeout","remove","displayValue","dataFormatter","pipe","delete","text","div","textContent","addClass","selectCell","deselect","removeClass","TableView","ListView","selectionMode","selectable","emptyMessage","addButtonIcon","isFullscreen","searchable","sortable","filterable","paginated","paginationMode","persistSelection","clickAction","fetchOnView","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","exportOptions","showExport","exportSource","filters","additionalFilters","hideActivePills","hideActivePillNames","batchBarLocation","addButtonLabel","toolbarButtons","toolbarRight","eyebrow","showRefresh","showFullscreen","tableOptions","striped","bordered","hover","responsive","searchPlacement","searchPlaceholder","initializeColumns","extractColumnFilters","footerTotalColumns","footer_total","hasFooterTotals","buildTableTemplate","setupCollectionListeners","collection","on","updateFooterTotals","charAt","toUpperCase","slice","left","start","center","right","end","String","toLowerCase","parseColumnKey","parts","split","fieldKey","totals","calculateFooterTotals","totalColumnIndex","safeKey","formatValue","sum","numValue","parseFloat","originalKey","batchPanelTop","buildBatchActionsPanel","batchPanelBottom","fontSize","__fs","__val","buildToolbarTemplate","buildTableClasses","buildTableHeaderTemplate","buildTableFooterTemplate","buildPaginationTemplate","background","buildActionButtonsTemplate","baseButtons","isFullscreenSupported","fullscreenBtn","refreshIdx","indexOf","closingTagEnd","headerCells","currentSort","getSortBy","getSortDirection","sortIcon","getSortIcon","responsiveClasses","sortDropdown","alignClass","footerCells","actionsHTML","batchPanelTitle","_createItemView","itemViews","itemTemplate","containerId","set","_wireItemViewListeners","updateBatchActionsPanel","_onCellEdit","bind","_onCellSave","_onCellCancel","onBeforeRender","footerTotals","updateSortIcons","_defaultGroupHeaderTemplate","_groupHeaderViewOptions","_model","_key","_index","dataCols","selectCol","actionsCol","groupHeaderStyle","colspan","Math","max","fullscreenEnabled","mozFullScreenEnabled","webkitFullscreenEnabled","msFullscreenEnabled","onActionToggleFullscreen","_event","_element","exitFullscreen","enterFullscreen","requestFullscreen","mozRequestFullScreen","webkitRequestFullscreen","msRequestFullscreen","updateFullscreenButton","setupFullscreenListeners","mozCancelFullScreen","webkitExitFullscreen","msExitFullscreen","button","_fullscreenHandler","handleFullscreenChange","fullscreenElement","mozFullScreenElement","webkitFullscreenElement","msFullscreenElement","cleanupFullscreenListeners","removeEventListener","destroy","onActionAdd","onActionExport","source","sort","params","direction","onActionSort","field","newSort","setParams","restEnabled","desc","sortField","a","b","aVal","bVal","render","currentSortField","currentSortDir","dropdown","isSorted","dropdownMenu","nextElementSibling","ascItem","descItem","noneItem","toggle","onActionSelectAll","isCurrentlyAllSelected","from","values","every","item","clearSelection","forEachItem","selectAllCell","selectedCount","getSelectedItems","panel","countEl","allSelected","someSelected","onActionBatch","batchAction","replace","selectedItems","items","onActionClearSelection","getFilterDisplayValue","f","opt","base64urlToBytes","base64url","base64","padded","repeat","Uint8Array","atob","charCodeAt","bytesToBase64url","buffer","btoa","fromCharCode","Passkey","suggestName","ua","navigator","userAgent","device","test","browser","register","friendlyName","beginResp","registerBegin","challenge_id","publicKey","success","challenge","user","excludeCredentials","cred","credential","credentials","credentialData","rawId","response","clientDataJSON","attestationObject","getTransports","transports","completeResp","registerComplete","friendly_name","passkey","rest","POST","dataOnly","err","message","PasskeyList","PasskeyForms","required","help"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=new class{constructor(){this.formatters=/* @__PURE__ */new Map,this.registerBuiltInFormatters()}escapeHtml(e){if(null==e)return"";const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"};return String(e).replace(/[&<>"']/g,e=>t[e])}registerBuiltInFormatters(){this.register("date",this.date.bind(this)),this.register("time",this.time.bind(this)),this.register("datetime",this.datetime.bind(this)),this.register("datetime_tz",this.datetime_tz.bind(this)),this.register("datatime_tz",this.datetime_tz.bind(this)),this.register("date_range",this.date_range.bind(this)),this.register("datetime_range",this.datetime_range.bind(this)),this.register("relative",this.relative.bind(this)),this.register("fromNow",this.relative.bind(this)),this.register("timeago",this.relative.bind(this)),this.register("relative_short",this.relative_short.bind(this)),this.register("iso",this.iso.bind(this)),this.register("epoch",e=>{if(null==e||""===e)return e;const t=parseFloat(e);return isNaN(t)?e:1e3*t}),this.register("number",this.number.bind(this)),this.register("currency",this.currency.bind(this)),this.register("percent",this.percent.bind(this)),this.register("filesize",this.filesize.bind(this)),this.register("ordinal",this.ordinal.bind(this)),this.register("compact",this.compact.bind(this)),this.register("add",this.add.bind(this)),this.register("subtract",this.subtract.bind(this)),this.register("multiply",this.multiply.bind(this)),this.register("divide",this.divide.bind(this)),this.register("sub",this.subtract.bind(this)),this.register("mult",this.multiply.bind(this)),this.register("div",this.divide.bind(this)),this.register("uppercase",e=>String(e).toUpperCase()),this.register("lowercase",e=>String(e).toLowerCase()),this.register("upper",e=>String(e).toUpperCase()),this.register("lower",e=>String(e).toLowerCase()),this.register("capitalize",this.capitalize.bind(this)),this.register("caps",this.capitalize.bind(this)),this.register("replace",this.replace.bind(this)),this.register("truncate",this.truncate.bind(this)),this.register("truncate_middle",this.truncate_middle.bind(this)),this.register("truncate_front",this.truncate_front.bind(this)),this.register("slug",this.slug.bind(this)),this.register("initials",this.initials.bind(this)),this.register("mask",this.mask.bind(this)),this.register("hex",this.hex.bind(this)),this.register("tohex",this.hex.bind(this)),this.register("unhex",this.unhex.bind(this)),this.register("fromhex",this.unhex.bind(this)),this.register("email",this.email.bind(this)),this.register("phone",this.phone.bind(this)),this.register("url",this.url.bind(this)),this.register("badge",this.badge.bind(this)),this.register("badgeClass",this.badgeClass.bind(this)),this.register("status",this.status.bind(this)),this.register("status_text",this.status_text.bind(this)),this.register("status_icon",this.status_icon.bind(this)),this.register("boolean",this.boolean.bind(this)),this.register("bool",this.bool.bind(this)),this.register("yesno",e=>this.boolean(e,"Yes","No")),this.register("yesnoicon",this.yesnoicon.bind(this)),this.register("icon",this.icon.bind(this)),this.register("avatar",this.avatar.bind(this)),this.register("image",this.image.bind(this)),this.register("tooltip",this.tooltip.bind(this)),this.register("linkify",this.linkify.bind(this)),this.register("clipboard",this.clipboard.bind(this)),this.register("default",this.default.bind(this)),this.register("equals",this.equals.bind(this)),this.register("json",this.json.bind(this)),this.register("raw",e=>e),this.register("custom",(e,t)=>"function"==typeof t?t(e):e),this.register("iter",this.iter.bind(this)),this.register("keys",e=>e&&"object"==typeof e&&!Array.isArray(e)?Object.keys(e):null),this.register("values",e=>e&&"object"==typeof e&&!Array.isArray(e)?Object.values(e):null),this.register("plural",this.plural.bind(this)),this.register("list",this.formatList.bind(this)),this.register("duration",this.duration.bind(this)),this.register("hash",this.hash.bind(this)),this.register("stripHtml",this.stripHtml.bind(this)),this.register("highlight",this.highlight.bind(this)),this.register("nl2br",this.nl2br.bind(this)),this.register("code",this.code.bind(this)),this.register("pre",e=>`<pre class="bg-light p-2 rounded border">${this.escapeHtml(String(e))}</pre>`)}relative_short(e){return this.relative(e,!0)}linkify(e,t={}){if(null==e)return"";const s=String(e),r=this.escapeHtml(s),i={urls:!0,emails:!0,target:"_blank",rel:"noopener noreferrer"},n=t&&"object"==typeof t?{...i,...t}:i;let a=r;if(!1!==n.urls){const e=/(^|\s)((?:https?:\/\/|www\.)[^\s<]+)/gi;a=a.replace(e,(e,t,s)=>`${t}<a href="${s.startsWith("www.")?`https://${s}`:s}" target="${n.target}" rel="${n.rel}">${s}</a>`)}if(!1!==n.emails){const e=/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;a=a.replace(e,e=>`<a href="mailto:${e}">${e}</a>`)}return a}clipboard(e,t="text"){if(null==e)return"";const s=String(e),r=this.escapeHtml(s);return`\n <span class="mojo-clipboard d-inline-flex align-items-center">\n ${"icon-only"!==t?`<span class="font-monospace">${r}</span>`:""}\n ${`\n <button type="button"\n class="btn btn-sm btn-outline-secondary ms-1 p-0 border-0 bg-transparent"\n title="Copy"\n data-bs-toggle="tooltip"\n data-action="copy-to-clipboard"\n data-clipboard="${r}">\n <i class="bi bi-clipboard"></i>\n </button>`.trim()}\n </span>\n `}nl2br(e){return null==e?"":this.escapeHtml(String(e)).replace(/\r\n|\r|\n/g,"<br>")}code(e,t=""){return null==e?"":`<pre class="bg-light p-2 rounded border"><code class="${t?`language-${this.escapeHtml(String(t))}`:""}">${this.escapeHtml(String(e))}</code></pre>`}register(e,t){if("function"!=typeof t)throw new Error("Formatter must be a function, got "+typeof t);return this.formatters.set(e.toLowerCase(),t),this}apply(e,t,...s){if(Array.isArray(t)&&t.every(e=>"string"==typeof e))return t.reduce((e,t)=>this.apply(t,e,...s),e);try{const r=this.formatters.get(e.toLowerCase());return r?r(t,...s):(console.warn(`Formatter '${e}' not found`),t)}catch(r){return console.error(`Error in formatter '${e}':`,r),t}}pipe(e,t,s=null){return t?this.parsePipeString(t,s).reduce((e,t)=>this.apply(t.name,e,...t.args),e):e}parsePipeString(e,t=null){const s=[],r=e.split("|").map(e=>e.trim());for(const i of r){const e=this.parseFormatter(i,t);e&&s.push(e)}return s}parseFormatter(e,t=null){const s=e.match(/^([a-zA-Z_]\w*)\s*\((.*)\)$/);if(s){const[,e,r]=s;return{name:e,args:r?this.parseArguments(r,t):[]}}const r=e.match(/^([a-zA-Z_]\w*)(?::(.+))?$/);if(r){const[,e,s]=r;return{name:e,args:s?this.parseColonArguments(s,t):[]}}return null}parseArguments(e,t=null){const s=[];let r="",i=!1,n=null,a=0;for(let o=0;o<e.length;o++){const l=e[o];i||'"'!==l&&"'"!==l?i&&l===n&&"\\"!==e[o-1]?(i=!1,n=null,r+=l):i||"{"!==l?i||"}"!==l?i||0!==a||","!==l?r+=l:(s.push(this.parseValue(r.trim(),t)),r=""):(a--,r+=l):(a++,r+=l):(i=!0,n=l,r+=l)}return r.trim()&&s.push(this.parseValue(r.trim(),t)),s}parseColonArguments(e,t=null){const s=[];let r="",i=!1,n=null;for(let a=0;a<e.length;a++){const o=e[a];i||'"'!==o&&"'"!==o?i&&o===n&&"\\"!==e[a-1]?(i=!1,n=null,r+=o):i||":"!==o?r+=o:(s.push(this.parseValue(r.trim(),t)),r=""):(i=!0,n=o,r+=o)}return r.trim()&&s.push(this.parseValue(r.trim(),t)),s}parseValue(e,t=null){if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if("true"===e)return!0;if("false"===e)return!1;if("null"===e)return null;if("undefined"!==e){if(!isNaN(e)&&""!==e)return Number(e);if(e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch(s){}if(t&&this.isIdentifier(e)){if(!e.includes(".")&&Object.prototype.hasOwnProperty.call(t,e))return t[e];if(t.get&&"function"==typeof t.get){const s=t.get(e);if(void 0!==s)return s}if(t.getContextValue&&"function"==typeof t.getContextValue){const s=t.getContextValue(e);if(void 0!==s)return s}if(e.includes(".")){const s=window.MOJOUtils||("undefined"!=typeof require?require("./MOJOUtils.js").default:null);if(s){const r=s.getNestedValue(t,e);if(void 0!==r)return r}}}return e}}isIdentifier(e){return/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(e)}date(e,t="MM/DD/YYYY"){if(!e)return"";let s;if((e=this.normalizeEpoch(e))instanceof Date)s=e;else if("string"==typeof e)if(/^\d{4}-\d{2}-\d{2}$/.test(e)){const[t,r,i]=e.split("-").map(Number);s=new Date(t,r-1,i)}else s=new Date(e);else s=new Date(e);if(isNaN(s.getTime()))return String(e);const r={YYYY:s.getFullYear(),YY:String(s.getFullYear()).slice(-2),MMMM:s.toLocaleDateString("en-US",{month:"long"}),MMM:s.toLocaleDateString("en-US",{month:"short"}),MM:String(s.getMonth()+1).padStart(2,"0"),M:s.getMonth()+1,dddd:s.toLocaleDateString("en-US",{weekday:"long"}),ddd:s.toLocaleDateString("en-US",{weekday:"short"}),DD:String(s.getDate()).padStart(2,"0"),D:s.getDate()};let i=t;const n=new RegExp(`(${Object.keys(r).join("|")})`,"g");return i=i.replace(n,e=>r[e]||e),i}time(e,t="HH:mm:ss"){if(!e)return"";const s=(e=this.normalizeEpoch(e))instanceof Date?e:new Date(e);if(isNaN(s.getTime()))return String(e);const r=s.getHours(),i={HH:String(r).padStart(2,"0"),H:r,hh:String(r%12||12).padStart(2,"0"),h:r%12||12,mm:String(s.getMinutes()).padStart(2,"0"),m:s.getMinutes(),ss:String(s.getSeconds()).padStart(2,"0"),s:s.getSeconds(),A:r>=12?"PM":"AM",a:r>=12?"pm":"am"};let n=t;const a=Object.keys(i).sort((e,t)=>t.length-e.length);for(const o of a)n=n.replace(new RegExp(o,"g"),i[o]);return n}datetime(e,t="MM/DD/YYYY",s="HH:mm:ss"){e=this.normalizeEpoch(e);const r=this.date(e,t),i=this.time(e,s);return r&&i?`${r} ${i}`:""}datetime_tz(e,t="MM/DD/YYYY",s="HH:mm:ss",r={}){if(!e)return"";const i=(e=this.normalizeEpoch(e))instanceof Date?e:new Date(e);if(isNaN(i.getTime()))return String(e);const n=r&&r.locale||"en-US",a=r&&r.timeZone?r.timeZone:void 0,o=()=>{let e="";try{const s=new Intl.DateTimeFormat(n,{hour:"2-digit",minute:"2-digit",timeZoneName:"short",...a?{timeZone:a}:{}}).formatToParts(i).find(e=>"timeZoneName"===e.type);if(e=s?s.value:"",e&&/^GMT[+-]/i.test(e))try{const t=new Intl.DateTimeFormat(n,{timeStyle:"short",timeZoneName:"short",...a?{timeZone:a}:{}}).formatToParts(i).find(e=>"timeZoneName"===e.type);t&&t.value&&!/^GMT[+-]/i.test(t.value)&&(e=t.value)}catch(t){}if(e&&/\s/.test(e)){const t=e.split(/\s+/).map(e=>e[0]).join("").toUpperCase();t.length>=2&&t.length<=4&&(e=t)}}catch(t){e=""}return e};if(!a){const e=this.date(i,t),r=this.time(i,s),n=o();return e&&r?`${e} ${r} ${n}`.trim():""}const l=new Intl.DateTimeFormat(n,{timeZone:a,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(i),c=e=>{const t=l.find(t=>t.type===e);return t?t.value:""},h=c("year"),u=c("month"),d=c("day"),p=c("hour"),m=c("minute"),g=c("second"),f=u?String(parseInt(u,10)):"",b=d?String(parseInt(d,10)):"",y=p?String(parseInt(p,10)):"",w=p?parseInt(p,10)%12||12:"",_=p?parseInt(p,10)>=12?"PM":"AM":"",S=_?_.toLowerCase():"",A=new Intl.DateTimeFormat(n,{timeZone:a,month:"long"}).format(i),v=new Intl.DateTimeFormat(n,{timeZone:a,month:"short"}).format(i),C=new Intl.DateTimeFormat(n,{timeZone:a,weekday:"long"}).format(i),T=new Intl.DateTimeFormat(n,{timeZone:a,weekday:"short"}).format(i),E={YYYY:h,YY:h?h.slice(-2):"",MMMM:A,MMM:v,MM:u,M:f,dddd:C,ddd:T,DD:d,D:b},M={HH:p,H:y,hh:""!==w?String(w).padStart(2,"0"):"",h:""!==w?String(w):"",mm:m,m:m?String(parseInt(m,10)):"",ss:g,s:g?String(parseInt(g,10)):"",A:_,a:S},U=(e,t)=>{if(!e)return"";const s=new RegExp(`(${Object.keys(t).sort((e,t)=>t.length-e.length).join("|")})`,"g");return e.replace(s,e=>t[e]??e)},O=U(t,E),I=U(s,M),N=o();return O&&I?`${O} ${I} ${N}`.trim():""}normalizeEpoch(e){if(e instanceof Date)return e;if("string"==typeof e){const t=Number(e);if(!Number.isFinite(t)){const t=Date.parse(e);return Number.isFinite(t)?t:""}e=t}else"number"!=typeof e&&(e=Number(e));if(isNaN(e))return"";if(e<1e11)return 1e3*e;if(e>1e12&&e<1e13)return e;throw new Error("Value doesn't look like epoch seconds or ms")}date_range(e,t=null,s="MM/DD/YYYY"){if(!e)return"";const r=t||/* @__PURE__ */new Date,i=this.date(e,s),n=this.date(r,s);return i&&n?`${i} - ${n}`:""}datetime_range(e,t=null,s="MM/DD/YYYY",r="HH:mm"){if(!e)return"";const i=t||/* @__PURE__ */new Date,n=this.datetime(e,s,r),a=this.datetime(i,s,r);return n&&a?`${n} - ${a}`:""}relative(e,t=!1){if(!e)return"";const s=(e=this.normalizeEpoch(e))instanceof Date?e:new Date(e);if(isNaN(s.getTime()))return String(e);const r=s-/* @__PURE__ */new Date,i=Math.abs(r),n=Math.floor(i/1e3),a=Math.floor(n/60),o=Math.floor(a/60),l=Math.floor(o/24),c=r>0;if(t)return l>365?Math.floor(l/365)+"y":l>30?Math.floor(l/30)+"mo":l>7?Math.floor(l/7)+"w":l>0?l+"d":o>0?o+"h":a>0?a+"m":"now";if(l>365){const e=Math.floor(l/365);return(c?"in ":"")+e+" year"+(e>1?"s":"")+(c?"":" ago")}if(l>30){const e=Math.floor(l/30);return(c?"in ":"")+e+" month"+(e>1?"s":"")+(c?"":" ago")}if(l>7){const e=Math.floor(l/7);return(c?"in ":"")+e+" week"+(e>1?"s":"")+(c?"":" ago")}return 1===l?c?"tomorrow":"yesterday":l>0?(c?"in ":"")+l+" days"+(c?"":" ago"):o>0?(c?"in ":"")+o+" hour"+(o>1?"s":"")+(c?"":" ago"):a>0?(c?"in ":"")+a+" minute"+(a>1?"s":"")+(c?"":" ago"):n>30?(c?"in ":"")+n+" seconds"+(c?"":" ago"):"just now"}iso(e,t=!1){if(!e)return"";const s=(e=this.normalizeEpoch(e))instanceof Date?e:new Date(e);return isNaN(s.getTime())?String(e):t?s.toISOString().split("T")[0]:s.toISOString()}number(e,t=2,s="en-US"){const r=parseFloat(e);return isNaN(r)?String(e):r.toLocaleString(s,{minimumFractionDigits:t,maximumFractionDigits:t})}currency(e,t="$",s=2){const r=parseInt(e);if(isNaN(r))return String(e);const i=Math.abs(r).toString(),n=r<0?"-":"";let a,o,l;return i.length<=2?(a="0",o=i.padStart(2,"0")):(a=i.slice(0,-2),o=i.slice(-2)),a=a.replace(/\B(?=(\d{3})+(?!\d))/g,","),0===s?(parseInt(o)>=50&&(a=(parseInt(a.replace(/,/g,""))+1).toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")),l=a):l=2===s?`${a}.${o}`:`${a}.${o.slice(0,s).padEnd(s,"0")}`,n+t+l}percent(e,t=0,s=!0){const r=parseFloat(e);if(isNaN(r))return String(e);const i=s?100*r:r;return this.number(i,t)+"%"}filesize(e,t=!1,s=1){const r=parseInt(e);if(isNaN(r))return String(e);const i=t?["B","KiB","MiB","GiB","TiB"]:["B","KB","MB","GB","TB"],n=t?1024:1e3;let a=r,o=0;for(;a>=n&&o<i.length-1;)a/=n,o++;const l=0===o?0:s;return`${a.toFixed(l)} ${i[o]}`}ordinal(e,t=!1){const s=parseInt(e);if(isNaN(s))return String(e);const r=s%10,i=s%100;let n="th";return 1===r&&11!==i?n="st":2===r&&12!==i?n="nd":3===r&&13!==i&&(n="rd"),t?n:s+n}compact(e,t=1){const s=parseFloat(e);if(isNaN(s))return String(e);const r=Math.abs(s),i=s<0?"-":"";return r>=1e9?i+(r/1e9).toFixed(t)+"B":r>=1e6?i+(r/1e6).toFixed(t)+"M":r>=1e3?i+(r/1e3).toFixed(t)+"K":String(s)}add(e,t){const s=parseFloat(e),r=parseFloat(t);return isNaN(s)||isNaN(r)?e:s+r}subtract(e,t){const s=parseFloat(e),r=parseFloat(t);return isNaN(s)||isNaN(r)?e:s-r}multiply(e,t){const s=parseFloat(e),r=parseFloat(t);return isNaN(s)||isNaN(r)?e:s*r}divide(e,t){const s=parseFloat(e),r=parseFloat(t);return isNaN(s)||isNaN(r)||0===r?e:s/r}capitalize(e,t=!0){const s=String(e);return s?t?s.replace(/\b\w/g,e=>e.toUpperCase()):s.charAt(0).toUpperCase()+s.slice(1):""}replace(e,t,s="",r="g"){if(null==e)return"";const i=String(e);if(null==t||""===t)return i;if(t instanceof RegExp)return i.replace(t,String(s));const n=String(t),a=n.match(/^\/(.+)\/([a-z]*)$/i);if(a){const[,e,t]=a;try{return i.replace(new RegExp(e,t),String(s))}catch(o){}}return String(r).includes("g")?i.split(n).join(String(s)):i.replace(n,String(s))}truncate(e,t=50,s="..."){const r=String(e);return r.length<=t?r:r.substring(0,t)+s}truncate_front(e,t=8,s="..."){const r=String(e);return r.length<=t?r:`${s}${r.slice(-t)}`}truncate_middle(e,t=8,s="***"){const r=String(e);if(r.length<=t)return r;const i=Math.floor(t/2);return`${r.substring(0,i)}${s}${r.substring(r.length-i)}`}slug(e,t="-"){return String(e).toLowerCase().replace(/[^\w\s-]/g,"").replace(/\s+/g,t).replace(new RegExp(`${t}+`,"g"),t).replace(new RegExp(`^${t}|${t}$`,"g"),"")}initials(e,t=2){return String(e).split(/\s+/).filter(e=>e.length>0).slice(0,t).map(e=>e.charAt(0).toUpperCase()).join("")}mask(e,t="*",s=4){const r=String(e);return r.length<=s?r:t.repeat(Math.max(0,r.length-s))+r.slice(-s)}email(e,t={}){const s=String(e).trim();return s?!1===t.link?s:`<a href="mailto:${s}${t.subject?`?subject=${encodeURIComponent(t.subject)}`:""}${t.body?`&body=${encodeURIComponent(t.body)}`:""}"${t.class?` class="${t.class}"`:""}>${s}</a>`:""}phone(e,t="US",s=!0){let r=String(e).replace(/\D/g,""),i=r;return"US"===t&&(10===r.length?i=`(${r.slice(0,3)}) ${r.slice(3,6)}-${r.slice(6)}`:11===r.length&&"1"===r[0]&&(i=`+1 (${r.slice(1,4)}) ${r.slice(4,7)}-${r.slice(7)}`)),s?`<a href="tel:${r}">${i}</a>`:i}url(e,t=null,s=!0){let r=String(e).trim();return r?(/^https?:\/\//.test(r)||(r="https://"+r),`<a href="${r}"${s?' target="_blank"':""}${s?' rel="noopener noreferrer"':""}>${t||r}</a>`):""}badge(e,t="auto"){if(Array.isArray(e))return e.map(e=>this.badge(e,t)).join(" ");const s=String(e);if("string"==typeof t&&t.includes("=")){const e=Object.fromEntries(t.split(",").map(e=>e.split("=").map(e=>e.trim()))),r=e[s]||e[s.toLowerCase()]||this.inferBadgeType(s);return`<span class="badge ${r?`bg-${r}`:"bg-secondary"}">${s}</span>`}const r="auto"===t?this.inferBadgeType(s):t;return`<span class="badge ${r?`bg-${r}`:"bg-secondary"}">${s}</span>`}badgeClass(e,t="auto"){const s=String(e),r="auto"===t?this.inferBadgeType(s):t;return r?`bg-${r}`:"bg-secondary"}inferBadgeType(e){const t=e.toLowerCase();return["active","pass","success","complete","completed","approved","done","true","on","yes"].includes(t)?"success":["error","failed","fail","rejected","deleted","cancelled","false","off","no","declined"].includes(t)?"danger":["warning","pending","review","processing","uploading"].includes(t)?"warning":["info","new","draft"].includes(t)?"info":(["inactive","disabled","archived","suspended"].includes(t),"secondary")}status(e){return this._status(e)}status_icon(e){return this._status(e,{},{},!1,!0)}status_text(e){return this._status(e,{},{},!0,!1)}_status(e,t={},s={},r=!1,i=!1){const n=String(e).toLowerCase(),a=t[n]||{active:"bi bi-check-circle-fill",approved:"bi bi-check-circle-fill",declined:"bi bi-x-circle-fill",inactive:"bi bi-pause-circle-fill",pending:"bi bi-clock-fill",success:"bi bi-check-circle-fill",error:"bi bi-exclamation-triangle-fill",warning:"bi bi-exclamation-triangle-fill"}[n]||"";let o="";!r&&a&&(o=`<i class="${a}"></i>`);let l="";return i||(l=e),`<span class="text-${s[n]||{active:"success",approved:"success",declined:"danger",inactive:"secondary",pending:"warning",success:"success",error:"danger",warning:"warning"}[n]||"secondary"}">${o}${o?" ":""}${l}</span>`}boolean(e,t="True",s="False",r=!1){const i=e?t:s;return r?`<span class="text-${e?"success":"danger"}">${i}</span>`:i}bool(e){return!(null==e||0===e||""===e||!1===e||"false"===e||!0!==e&&"true"!==e&&(Array.isArray(e)&&0===e.length||e&&"object"==typeof e&&e.constructor===Object&&0===Object.keys(e).length))}icon(e,t={}){const s=t[String(e).toLowerCase()]||"";return s?`<i class="${s}"></i>`:""}yesnoicon(e,t="bi bi-check-circle-fill text-success",s="bi bi-x-circle-fill text-danger"){return e?`<i class="${t}"></i>`:`<i class="${s}"></i>`}image(e,t="thumbnail",s="img-fluid",r=""){const i=this._extractImageUrl(e,t);return i?`<img src="${i}" class="${s}" alt="${r}" />`:""}avatar(e,t="md",s="rounded-circle",r=""){const i=this._extractImageUrl(e,"square_sm")||"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2NlZDRkYSI+PHBhdGggZD0iTTEyIDEyYzIuMjEgMCA0LTEuNzkgNC00cy0xLjc5LTQtNC00LTQgMS43OS00IDQgMS43OSA0IDQgNHptMCAyYy0yLjY3IDAtOCAxLjM0LTggNHYyaDE2di0yYzAtMi42Ni01LjMzLTQtOC00eiIvPjwvc3ZnPg==",n={xs:"width: 1.5rem; height: 1.5rem;",sm:"width: 2rem; height: 2rem;",md:"width: 3rem; height: 3rem;",lg:"width: 4rem; height: 4rem;",xl:"width: 5rem; height: 5rem;"},a=n[t]||n.md;return`<img src="${i}" class="${`object-fit-cover ${s}`.trim()}" style="${a}" alt="${r}" />`}tooltip(e,t="",s="top",r=""){if(null==e)return"";const i=String(e);return`<span data-bs-toggle="tooltip" data-bs-placement="${s}" ${"html"===r?'data-bs-html="true"':""} data-bs-title="${"html"===r?t:this.escapeHtml(t)}">${i}</span>`}_extractImageUrl(e,t="thumbnail"){if(!e)return null;if("string"==typeof e)return e;if("object"==typeof e){if(e.attributes&&(e=e.attributes),"thumbnail"===t&&e.thumbnail&&"string"==typeof e.thumbnail)return e.thumbnail;if(e.renditions&&"object"==typeof e.renditions){const s=e.renditions[t];if(s&&s.url)return s.url;const r=Object.values(e.renditions);if(r.length>0&&r[0].url)return r[0].url}if(e.url)return e.url}return null}default(e,t=""){return null==e||""===e?t:e}equals(e,t,s,r=""){return e==t?s:r}plural(e,t,s=null,r=!0){if(null==e||null==t)return r?`${e} ${t}`:t||"";const i=parseInt(e);if(isNaN(i))return r?`${e} ${t}`:t||"";const n=1===Math.abs(i)?t:s||t+"s";return r?`${i} ${n}`:n}formatList(e,t={}){if(!Array.isArray(e))return String(e);const{conjunction:s="and",limit:r=null,moreText:i="others"}=t;if(0===e.length)return"";if(1===e.length)return String(e[0]);let n=e.slice(),a=!1;if(r&&e.length>r&&(n=e.slice(0,r),a=!0),a){const t=e.length-r;return`${n.join(", ")}, ${s} ${t} ${i}`}return 2===n.length?`${n[0]} ${s} ${n[1]}`:`${n.slice(0,-1).join(", ")}, ${s} ${n[n.length-1]}`}duration(e,t="ms",s=!1,r=2){if(null==e)return"";const i=parseFloat(e);if(isNaN(i))return String(e);let n;switch(t){case"s":case"sec":case"seconds":n=1e3*i;break;case"m":case"min":case"minutes":n=6e4*i;break;case"h":case"hr":case"hours":n=36e5*i;break;case"d":case"day":case"days":n=864e5*i;break;default:n=i}const a=[{name:"day",short:"d",value:864e5},{name:"hour",short:"h",value:36e5},{name:"minute",short:"m",value:6e4},{name:"second",short:"s",value:1e3}];if(0===n)return s?"0s":"0 seconds";const o=Math.abs(n),l=n<0?"-":"",c=[];let h=o;for(const u of a)if(h>=u.value){const e=Math.floor(h/u.value);h%=u.value;const t=s?u.short:1===e?u.name:u.name+"s";if(c.push(s?`${e}${t}`:`${e} ${t}`),c.length>=r)break}return 0===c.length?s?`${Math.round(o)}ms`:`${Math.round(o)} milliseconds`:l+(s?c.join(""):c.join(" "))}hash(e,t=8,s="",r="..."){if(null==e)return"";const i=String(e);return i.length<=t?s+i:s+i.substring(0,t)+r}stripHtml(e){return null==e?"":String(e).replace(/<[^>]*>/g,"")}highlight(e,t,s="highlight"){if(null==e||!t)return String(e||"");const r=String(t).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),i=new RegExp(`(${r})`,"gi");return String(e).replace(i,`<mark class="${s}">$1</mark>`)}hex(e,t=!1,s=!1){if(null==e)return"";let r="";const i=e=>Array.from(e).map(e=>e.toString(16).padStart(2,"0")).join("");if("number"==typeof e){let t=Math.abs(Math.trunc(e)).toString(16);t.length%2&&(t="0"+t),r=t}else r=e instanceof Uint8Array?i(e):e instanceof ArrayBuffer?i(new Uint8Array(e)):Array.isArray(e)&&e.every(e=>"number"==typeof e)?i(Uint8Array.from(e.map(e=>255&e))):i((new TextEncoder).encode(String(e)));return t&&(r=r.toUpperCase()),(s?"0x":"")+r}unhex(e){if(null==e)return"";let t=String(e).trim();if((t.startsWith("0x")||t.startsWith("0X"))&&(t=t.slice(2)),t=t.replace(/\s+/g,""),0===t.length)return"";t.length%2!=0&&(t="0"+t);const s=new Uint8Array(t.length/2);for(let i=0;i<t.length;i+=2){const r=parseInt(t.slice(i,i+2),16);if(Number.isNaN(r))return String(e);s[i/2]=r}try{return(new TextDecoder).decode(s)}catch(r){let e="";for(const t of s)e+=String.fromCharCode(t);return e}}json(e,t=2){try{return JSON.stringify(e,null,t)}catch(s){return String(e)}}has(e){return this.formatters.has(e.toLowerCase())}unregister(e){return this.formatters.delete(e.toLowerCase())}listFormatters(){return Array.from(this.formatters.keys()).sort()}iter(e){return null==e?[]:Array.isArray(e)?e:"object"==typeof e?Object.entries(e).map(([e,t])=>({key:e,value:t})):[{key:"0",value:e}]}};window.dataFormatter=e;const t=/* @__PURE__ */new Set(["__proto__","constructor","prototype"]),s=(e,t)=>null!=e&&Object.prototype.hasOwnProperty.call(e,t);class MOJOUtils{static getContextData(t,s){if(!s||null==t)return;let r=s,i="",n=0,a=-1;for(let e=0;e<s.length;e++){const t=s[e];if("("===t)n++;else if(")"===t)n--;else if("|"===t&&0===n){a=e;break}}a>-1&&(r=s.substring(0,a).trim(),i=s.substring(a+1).trim());const o=this.getNestedValue(t,r);return i?e.pipe(o,i,t):o}static getNestedValue(e,r){if(!r||null==e)return;if(r.split(".").some(e=>t.has(e)))return;if(!r.includes(".")){if(r in e){const t=e[r];if("function"==typeof t){if(t===Object.prototype[r])return;return t.call(e)}return t}return}const i=r.split(".");let n=e;for(let t=0;t<i.length;t++){const r=i[t];if(null==n)return;if(0===t){if(!s(n,r))return;{const t=n[r];n="function"==typeof t?t.call(e):t}}else{if(n&&"function"==typeof n.getContextValue){const e=i.slice(t).join(".");return n.getContextValue(e)}if(Array.isArray(n)&&!isNaN(r))n=n[parseInt(r)];else{if(!s(n,r))return;n=n[r]}}}return n}static isNullOrUndefined(e){return null==e}static deepClone(e){if(null===e||"object"!=typeof e)return e;if(e instanceof Date)return new Date(e.getTime());if(e instanceof Array)return e.map(e=>this.deepClone(e));if(e instanceof Object){const t={};for(const s in e)e.hasOwnProperty(s)&&(t[s]=this.deepClone(e[s]));return t}}static deepMerge(e,...t){if(!t.length)return e;const s=t.shift();if(this.isObject(e)&&this.isObject(s))for(const r in s)this.isObject(s[r])?(e[r]||Object.assign(e,{[r]:{}}),this.deepMerge(e[r],s[r])):Object.assign(e,{[r]:s[r]});return this.deepMerge(e,...t)}static isObject(e){return e&&"object"==typeof e&&!Array.isArray(e)}static debounce(e,t){let s;return function(...r){clearTimeout(s),s=setTimeout(()=>{clearTimeout(s),e(...r)},t)}}static throttle(e,t){let s;return function(...r){s||(e.apply(this,r),s=!0,setTimeout(()=>s=!1,t))}}static generateId(e=""){const t=Date.now().toString(36),s=Math.random().toString(36).substr(2,9);return e?`${e}_${t}_${s}`:`${t}_${s}`}static escapeHtml(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};return String(e).replace(/[&<>"'`=\/]/g,e=>t[e])}static checkPasswordStrength(e){if(!e||"string"!=typeof e)return{score:0,strength:"invalid",feedback:["Password must be a non-empty string"],details:{length:0,hasLowercase:!1,hasUppercase:!1,hasNumbers:!1,hasSpecialChars:!1,hasCommonPatterns:!1,isCommonPassword:!1}};const t=[],s={length:e.length,hasLowercase:/[a-z]/.test(e),hasUppercase:/[A-Z]/.test(e),hasNumbers:/[0-9]/.test(e),hasSpecialChars:/[^a-zA-Z0-9]/.test(e),hasCommonPatterns:!1,isCommonPassword:!1};let r=0;e.length<6?t.push("Password should be at least 6 characters long"):e.length<8?(r+=1,t.push("Consider using at least 8 characters for better security")):e.length<12?r+=3:r+=4,s.hasLowercase?r+=1:t.push("Include lowercase letters"),s.hasUppercase?r+=1:t.push("Include uppercase letters"),s.hasNumbers?r+=1:t.push("Include numbers"),s.hasSpecialChars?r+=2:t.push("Include special characters (!@#$%^&* etc.)");const i=[/123/,/abc/i,/qwerty/i,/asdf/i,/(.)\1{2,}/,/password/i,/admin/i,/user/i,/login/i];for(const a of i)if(a.test(e)){s.hasCommonPatterns=!0,r-=1,t.push("Avoid common patterns and dictionary words");break}let n;return["123456","password","123456789","12345678","12345","1234567","1234567890","qwerty","abc123","111111","123123","admin","letmein","welcome","monkey","password123","123qwe","qwerty123","000000","dragon","sunshine","princess","azerty","1234","iloveyou","trustno1","superman","shadow","master","jennifer"].includes(e.toLowerCase())&&(s.isCommonPassword=!0,r=Math.max(0,r-3),t.push("This password is too common and easily guessed")),n=r<2?"very-weak":r<4?"weak":r<6?"fair":r<8?"good":"strong",r>=7&&0===t.length?t.push("Strong password! Consider using a password manager."):r>=5&&t.length<=1&&t.push("Good password strength. Consider adding more variety."),{score:Math.max(0,r),strength:n,feedback:t,details:s}}static generatePassword(e={}){const t={length:12,includeLowercase:!0,includeUppercase:!0,includeNumbers:!0,includeSpecialChars:!0,customChars:"",excludeAmbiguous:!1,...e};if(t.length<4)throw new Error("Password length must be at least 4 characters");let s="abcdefghijklmnopqrstuvwxyz",r="ABCDEFGHIJKLMNOPQRSTUVWXYZ",i="0123456789",n="!@#$%^&*()_+-=[]{}|;:,.<>?";t.excludeAmbiguous&&(s=s.replace(/[il]/g,""),r=r.replace(/[IOL]/g,""),i=i.replace(/[01]/g,""),n=n.replace(/[|]/g,""));let a="";const o=[];if(t.customChars?a=t.customChars:(t.includeLowercase&&(a+=s,o.push(s[Math.floor(Math.random()*s.length)])),t.includeUppercase&&(a+=r,o.push(r[Math.floor(Math.random()*r.length)])),t.includeNumbers&&(a+=i,o.push(i[Math.floor(Math.random()*i.length)])),t.includeSpecialChars&&(a+=n,o.push(n[Math.floor(Math.random()*n.length)]))),!a)throw new Error("No character types selected for password generation");let l="";for(const c of o)l+=c;for(let c=l.length;c<t.length;c++)l+=a[Math.floor(Math.random()*a.length)];return l.split("").sort(()=>Math.random()-.5).join("")}static parseQueryString(e){const t={},s=new URLSearchParams(e);for(const[r,i]of s.entries())t[r]=i;return t}static toQueryString(e){return new URLSearchParams(e).toString()}static wrapData(e,t=null,s=3){return e&&"object"==typeof e?e instanceof Date||e instanceof RegExp||e instanceof Error||s<=0||"function"==typeof e.getContextValue?e:Array.isArray(e)?e.map(e=>e&&"object"==typeof e&&!e.getContextValue?new DataWrapper(e,t):e):new DataWrapper(e,t):e}}class DataWrapper{constructor(e,t=null){if(Object.defineProperty(this,"_data",{value:e,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(this,"_rootContext",{value:t,writable:!1,enumerable:!1,configurable:!1}),e&&"object"==typeof e)for(const s in e)if(e.hasOwnProperty(s)){const r=e[s];this[s]=MOJOUtils.wrapData(r,t)}}getContextValue(s){let r,i=s,n="",a=0,o=-1;for(let e=0;e<s.length;e++){const t=s[e];if("("===t)a++;else if(")"===t)a--;else if("|"===t&&0===a){o=e;break}}return o>-1&&(i=s.substring(0,o).trim(),n=s.substring(o+1).trim()),r=i in this&&!t.has(i)&&"_data"!==i&&"_rootContext"!==i?this[i]:MOJOUtils.getNestedValue(this._data,i),n&&void 0!==r?e.pipe(r,n,this._rootContext||this._data):r}has(e){return this._data&&this._data.hasOwnProperty(e)}toJSON(){return this._data}}MOJOUtils.DataWrapper=DataWrapper,"undefined"!=typeof window&&(window.utils=MOJOUtils);const r=/* @__PURE__ */new Set(["__proto__","constructor","prototype"]),i=Object.prototype.toString,n=Array.isArray||function(e){return"[object Array]"===i.call(e)},a=function(e){return"function"==typeof e},o=function(e){return null!==e&&"object"==typeof e},l=function(e){const t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};return String(e).replace(/[&<>"'`=\/]/g,function(e){return t[e]})};class Scanner{constructor(e){this.string=e,this.tail=e,this.pos=0}eos(){return""===this.tail}scan(e){const t=this.tail.match(e);if(!t||0!==t.index)return"";const s=t[0];return this.tail=this.tail.substring(s.length),this.pos+=s.length,s}scanUntil(e){const t=this.tail.search(e);let s;switch(t){case-1:s=this.tail,this.tail="";break;case 0:s="";break;default:s=this.tail.substring(0,t),this.tail=this.tail.substring(t)}return this.pos+=s.length,s}}class Context{constructor(e,t){this.view=e,this.cache={".":this.view},this.parent=t,this.view?._cacheId||this.view&&"object"==typeof this.view&&(this.view._cacheId=Math.random().toString(36).substring(2))}push(e){return new Context(e,this)}lookup(e){if(this.renderCache&&this.view?._cacheId){const t=`${this.view._cacheId}:${e}`;if(this.renderCache.has(t))return this.renderCache.get(t)}if("."===e)return this.view;if(e&&(e.startsWith(".")?e.slice(1):e).split("|")[0].split(".").some(e=>r.has(e)))return;if(e&&e.startsWith(".")){let t=e.substring(1),s=!1,r=null;const l=t.indexOf("|");if(-1!==l&&(r=t.substring(l+1).trim(),t=t.substring(0,l).trim()),t.endsWith("|iter")&&(t=t.substring(0,t.length-5),s=!0),this.view&&"object"==typeof this.view){let e;if("function"==typeof this.view.getContextValue)try{e=this.view.getContextValue(t),void 0!==e&&a(e)&&(e=e.call(this.view))}catch(i){e=void 0}if(void 0===e&&(t.indexOf(".")>0?e=MOJOUtils.getNestedValue(this.view,t):t in this.view&&(e=this.view[t],a(e)&&(e=e===Object.prototype[t]?void 0:e.call(this.view)))),r&&void 0!==e)try{const t="undefined"==typeof window?null:window.MOJO?.dataFormatter?window.MOJO.dataFormatter:window.dataFormatter?window.dataFormatter:null;t&&"function"==typeof t.pipe&&(e=t.pipe(e,r,this))}catch(i){}return n(e)?s?e:e.length>0:o(e)?s?Object.entries(e).map(([e,t])=>({key:e,value:t})):Object.keys(e).length>0:e}return}const t=this.cache;let s;if(t.hasOwnProperty(e))s=t[e];else{let r,n,a,o=this,l=!1;for(;o;){if(o.view&&"function"==typeof o.view.getContextValue)try{r=o.view.getContextValue(e),void 0!==r&&(l=!0)}catch(i){l=!1}if(!l)if(e.indexOf(".")>0)for(r=o.view,n=e.split("."),a=0;null!=r&&a<n.length;)if(r&&"function"==typeof r.getContextValue&&a<n.length)try{const e=n.slice(a).join(".");r=r.getContextValue(e),a=n.length,void 0!==r&&(l=!0)}catch(i){a===n.length-1&&(l=c(r,n[a])||h(r,n[a])),r=r[n[a++]]}else a===n.length-1&&(l=c(r,n[a])||h(r,n[a])),r=r[n[a++]];else r=o.view[e],l=c(o.view,e);if(l){s=r;break}o=o.parent}t[e]=s}if(a(s)){const t=e.split("|")[0].split(".").pop();if(s===Object.prototype[t])s=void 0;else try{s=s.call(this.view)}catch(i){if(!(i instanceof TypeError&&/^Class constructor /.test(i.message)))throw i;s=void 0}}if(this.renderCache&&this.view?._cacheId){const t=`${this.view._cacheId}:${e}`;this.renderCache.set(t,s)}return s}}function c(e,t){return null!=e&&"object"==typeof e&&t in e}function h(e,t){return null!=e&&"object"!=typeof e&&e.hasOwnProperty&&e.hasOwnProperty(t)}class Writer{constructor(){this.templateCache=/* @__PURE__ */new Map}clearCache(){this.templateCache.clear()}parse(e,t){const s=e+":"+(t=t||["{{","}}"]).join(":");let r=this.templateCache.get(s);return null==r&&(r=this.parseTemplate(e,t),this.templateCache.set(s,r)),r}parseTemplate(e,t){if(!e)return[];const s=t[0],r=t[1],i=new Scanner(e),n=[];let a,o,l,c,h;const d=new RegExp(u(s)+"\\s*"),p=new RegExp("\\s*"+u(r)),m=new RegExp("\\s*"+u("}"+r));for(;!i.eos();){if(a=i.pos,l=i.scanUntil(d),l)for(let e=0;e<l.length;++e)c=l.charAt(e),n.push(["text",c]);if(!i.scan(d))break;if(o=i.scan(/[#^\/>{&=!]/),o||(o="name"),i.scan(/\s*/),"="===o?(l=i.scanUntil(/\s*=/),i.scan(/\s*=/),i.scanUntil(p)):"{"===o?(l=i.scanUntil(m),i.scan(m),o="&"):l=i.scanUntil(p),i.scan(p),"#"===o||"^"===o)h=[o,l,a,i.pos],n.push(h);else if("/"===o){let e;for(let t=n.length-1;t>=0;--t)if(("#"===n[t][0]||"^"===n[t][0])&&n[t][1]===l){e=n[t];break}e&&4===e.length&&e.push(i.pos),n.push([o,l,a,i.pos])}else n.push([o,l,a,i.pos])}return this.nestSections(this.squashTokens(n))}squashTokens(e){const t=[];let s,r;for(let i=0;i<e.length;++i)s=e[i],s&&("text"===s[0]&&r&&"text"===r[0]?(r[1]+=s[1],r[3]=s[3]):(t.push(s),r=s));return t}nestSections(e){const t=[];let s=t;const r=[];for(let i=0;i<e.length;++i){const n=e[i];switch(n[0]){case"#":case"^":const e=[n[0],n[1],n[2],n[3],[],n[4]||null];s.push(e),r.push(e),s=e[4];break;case"/":const i=r.pop();i&&(i[5]=n[2],s=r.length>0?r[r.length-1][4]:t);break;default:s.push(n)}}return t}render(e,t,s,r){const i=this.getConfigTags(r)||["{{","}}"],n=this.parse(e,i),a=/* @__PURE__ */new Map;return this.renderTokens(n,new Context(t),s,e,r,a)}renderTokens(e,t,s,r,i,o){o&&!t.renderCache&&(t.renderCache=o);let c="";for(let h=0;h<e.length;++h){const u=e[h];let d;switch(u[0]){case"#":if(d=t.lookup(u[1]),!d)continue;const e=u[4];if(!e||!n(e)){console.warn(`MUSTACHE WARNING - Section ${u[1]} has no child tokens:`,u);continue}if(n(d))for(let n=0;n<d.length;++n){const a=t.push(d[n]);t.renderCache&&(a.renderCache=t.renderCache),c+=this.renderTokens(e,a,s,r,i,o)}else if("object"==typeof d||"string"==typeof d||"number"==typeof d){const n=t.push(d);t.renderCache&&(n.renderCache=t.renderCache),c+=this.renderTokens(e,n,s,r,i,o)}else if(a(d)){const e=null==r?null:r.slice(u[3],u[5]);d=d.call(t.view,e,e=>this.render(e,t.view,s,i)),null!=d&&(c+=d)}else d&&(c+=this.renderTokens(e,t,s,r,i,o));break;case"^":if(d=t.lookup(u[1]),!d||n(d)&&0===d.length){const e=u[4];e&&n(e)&&(c+=this.renderTokens(e,t,s,r,i,o))}break;case">":if(!s)continue;d=a(s)?s(u[1]):s[u[1]],null!=d&&(c+=this.render(d,t.view,s,i));break;case"&":d=t.lookup(u[1]),null!=d&&(c+=d);break;case"name":d=t.lookup(u[1]),null!=d&&(c+=l(d));break;case"text":c+=u[1]}}return c}getConfigTags(e){return o(e)&&n(e.tags)?e.tags:null}}function u(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}const d=new Writer,p={name:"MOJO Mustache",version:"1.0.0",tags:["{{","}}"],Scanner:Scanner,Context:Context,Writer:Writer,escape:l,clearCache:()=>d.clearCache(),parse:(e,t)=>d.parse(e,t),render(e,t,s,r){if("string"!=typeof e)throw new TypeError('Invalid template! Template should be a "string"');return t&&"object"==typeof t&&!t.getContextValue&&"function"!=typeof t.toJSON&&(t=MOJOUtils.wrapData(t)),d.render(e,t,s,r)}};class EventDelegate{constructor(e){this.view=e,this.domListeners=[],this.debounceTimers=/* @__PURE__ */new Map}_enterDispatchChain(e){const t=e._mojoDispatch;let s;return e._mojoDispatch=new Promise(e=>{s=e}),{prior:t,done:s}}bind(e){if(this.unbind(),!e)return;const t=async e=>{const{prior:t,done:s}=this._enterDispatchChain(e);try{t&&await t;const s=e.target.closest("[data-action]");if(s&&this.shouldHandle(s,e)){const t=s.getAttribute("data-action");if("A"===s.tagName&&e.preventDefault(),this.hideTooltip(s),await this.dispatch(t,e,s))return e.preventDefault(),e.stopPropagation(),void(e.handledByChild=!0)}const r=e.target.closest("a[href], [data-page]");if(r&&!r.hasAttribute("data-action")&&this.shouldHandle(r,e)){if(e.ctrlKey||e.metaKey||e.shiftKey||1===e.button)return;if("A"===r.tagName){const e=r.getAttribute("href");if(e&&"#"!==e&&!e.startsWith("#")&&(this.view.isExternalLink(e)||r.hasAttribute("data-external")))return}this.hideTooltip(r),e.preventDefault(),e.stopPropagation(),e.handledByChild=!0,r.hasAttribute("data-page")?await this.view.handlePageNavigation(r):await this.view.handleHrefNavigation(r)}}finally{s()}},s=async e=>{const{prior:t,done:s}=this._enterDispatchChain(e);try{t&&await t;const s=e.target.closest("[data-change-action]");if(s&&this.shouldHandle(s,e)){const t=s.getAttribute("data-change-action");return void(await this.dispatchChange(t,e,s)&&(e.stopPropagation(),e.handledByChild=!0))}const r=e.target.closest("[data-action]");if(r&&this.isFormControl(r)&&this.shouldHandle(r,e)){const t=r.getAttribute("data-action");await this.dispatch(t,e,r)&&(e.stopPropagation(),e.handledByChild=!0)}}finally{s()}},r=e=>{const t=e.target.closest("[data-change-action]");if(t&&this.shouldHandle(t,e)&&e.target.matches('[data-filter="live-search"]')){const s=t.getAttribute("data-change-action"),r=parseInt(t.getAttribute("data-filter-debounce"))||300,i=`change-${s}-${t.getAttribute("data-container")||"default"}`;this.debounceTimers.has(i)&&clearTimeout(this.debounceTimers.get(i));const n=setTimeout(()=>{this.debounceTimers.delete(i),this.dispatchChange(s,e,t).then(t=>{t&&(e.stopPropagation(),e.handledByChild=!0)})},r);return void this.debounceTimers.set(i,n)}const s=e.target.closest("[data-action]");if(!s||!this.isFormControl(s))return;if(s.hasAttribute("data-change-action"))return;if(!this.shouldHandle(s,e))return;const r=s.getAttribute("data-action"),i=s.getAttribute("data-action-debounce"),n=null!=i&&parseInt(i)||0;if(n>0){const t=`action-${r}-${s.getAttribute("data-container")||"default"}`;this.debounceTimers.has(t)&&clearTimeout(this.debounceTimers.get(t));const i=setTimeout(()=>{this.debounceTimers.delete(t),this.dispatch(r,e,s).then(t=>{t&&(e.stopPropagation(),e.handledByChild=!0)})},n);return void this.debounceTimers.set(t,i)}this.dispatch(r,e,s).then(t=>{t&&(e.stopPropagation(),e.handledByChild=!0)})},i=async e=>{if(e.target.matches('[data-filter="search"]'))return;const t=e.target.closest("[data-keydown-action]")||e.target.closest("[data-change-action]");if(!t)return;const{prior:s,done:r}=this._enterDispatchChain(e);try{if(s&&await s,!this.shouldHandle(t,e))return;let r=["Enter"];if(t.getAttribute("data-change-keys")&&(r=t.getAttribute("data-change-keys").split(",").map(e=>e.trim())),r.includes("*")||r.includes(e.key)){const s=t.getAttribute("data-keydown-action")||t.getAttribute("data-change-action");await this.dispatch(s,e,t)&&(e.preventDefault(),e.stopPropagation(),e.handledByChild=!0)}}finally{r()}},n=e=>{const t=e.target.closest("form[data-action]");if(!t||!this.shouldHandle(t,e))return;e.preventDefault();const s=t.getAttribute("data-action");this.dispatch(s,e,t)};e.addEventListener("click",t),e.addEventListener("change",s),e.addEventListener("input",r),e.addEventListener("keydown",i),e.addEventListener("submit",n),this.domListeners.push({el:e,type:"click",fn:t},{el:e,type:"change",fn:s},{el:e,type:"input",fn:r},{el:e,type:"keydown",fn:i},{el:e,type:"submit",fn:n})}unbind(){for(const{el:e,type:t,fn:s}of this.domListeners)e.removeEventListener(t,s);this.domListeners=[];for(const e of this.debounceTimers.values())clearTimeout(e);this.debounceTimers.clear()}hideDropdown(e){const t=e.closest(".dropdown-menu").closest(".dropdown");if(!t)return;const s=t.querySelector('[data-bs-toggle="dropdown"]');if(s&&window.bootstrap?.Dropdown){const e=window.bootstrap.Dropdown.getInstance(s);e?.hide()}}hideTooltip(e){if(e&&window.bootstrap?.Tooltip){const t=window.bootstrap.Tooltip.getInstance(e);t&&t.dispose()}}hideAllTooltips(){window.bootstrap?.Tooltip&&document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(e=>{const t=window.bootstrap.Tooltip.getInstance(e);t&&t.hide()})}async dispatch(e,t,s){const r=this.view,i=e=>e.includes("-")?e.split("-").map(e=>e[0].toUpperCase()+e.slice(1)).join(""):e[0].toUpperCase()+e.slice(1),n=`handleAction${i(e)}`;if("function"==typeof r[n])try{return t.preventDefault(),await r[n](t,s),!0}catch(l){return console.error(`Error in action ${e}:`,l),r.handleActionError(e,l,t,s),!0}const a=`onAction${i(e)}`;if("function"==typeof r[a])try{return!!(await r[a](t,s))&&(!!s.closest(".dropdown-menu")&&this.hideDropdown(s),t.preventDefault(),t.stopPropagation(),!0)}catch(l){return console.error(`Error in action ${e}:`,l),r.handleActionError(e,l,t,s),!0}const o=`onPassThruAction${i(e)}`;if("function"==typeof r[o])try{return await r[o](t,s),!1}catch(l){return console.error(`Error in action ${e}:`,l),r.handleActionError(e,l,t,s),!0}if("function"==typeof r.onActionDefault)try{return await r.onActionDefault(e,t,s)}catch(l){return console.error(`Error in default action handler for ${e}:`,l),r.handleActionError(e,l,t,s),!0}return r.emit?.(`action:${e}`,{action:e,event:t,element:s}),!1}async dispatchChange(e,t,s){const r=this.view,i=`onChange${n=e,n.includes("-")?n.split("-").map(e=>e[0].toUpperCase()+e.slice(1)).join(""):n[0].toUpperCase()+n.slice(1)}`;var n;if("function"==typeof r[i])try{return await r[i](t,s),!0}catch(a){return console.error(`Error in onChange ${e}:`,a),r.handleActionError?.(e,a,t,s),!0}return await this.dispatch(e,t,s)}isFormControl(e){if(!e||!e.tagName)return!1;const t=e.tagName;return"INPUT"===t||"TEXTAREA"===t||"SELECT"===t}shouldHandle(e,t){return!!this.owns(e)||!(!this.contains(e)||t.handledByChild)}owns(e){const t=this.view.element;if(!t||!t.contains(e))return!1;for(const s of Object.values(this.view.children))if(s.element&&s.element.contains(e))return!1;return!0}contains(e){return!!this.view.element&&this.view.element.contains(e)}}const m={on(e,t,s){this._listeners||(this._listeners={}),this._listeners[e]||(this._listeners[e]=[]);const r={callback:t,context:s,fn:s?t.bind(s):t};return this._listeners[e].push(r),this},off(e,t,s){return this._listeners&&this._listeners[e]?(t?(this._listeners[e]=this._listeners[e].filter(e=>e.callback!==t||3===arguments.length&&e.context!==s),0===this._listeners[e].length&&delete this._listeners[e]):delete this._listeners[e],this):this},once(e,t,s){const r=(...i)=>{this.off(e,r),(s?t.bind(s):t).apply(s||this,i)};return this.on(e,r),this},emit(e,...t){if(!this._listeners||!this._listeners[e])return this;const s=this._listeners[e].slice();for(const i of s)try{i.fn.apply(i.context||this,t)}catch(r){console&&console.error&&console.error(`Error in ${e} event handler:`,r)}return this}};"undefined"!=typeof window&&(window.Mustache=p);class View{constructor(e={}){this.tagName=e.tagName??"div",this.className=e.className??"mojo-view",this.style=e.style??null,this.id=e.id??View._genId(),this.containerId=e.containerId??null,this.container=e.container??null,"string"==typeof this.container&&(this.containerId=this.container,this.container=null),this.parent=e.parent??null,this.children=e.children??{},this.template=e.template||e.templateUrl||"",this.data=e.data??{},this.isRendering=!1,this.lastRenderTime=0,this.mounted=!1,this.debug=e.debug??!1,this.app=e.app??null,this.cacheTemplate=e.cacheTemplate??!0,this.enableTooltips=e.enableTooltips??!1,this.options={...e},this.element=this._ensureElement(),this.events=new EventDelegate(this),e.model&&this.setModel(e.model)}async onInit(){}async onInitView(){this.initialized||(this.initialized=!0,await this.onInit())}async onBeforeRender(){}async onAfterRender(){}async onBeforeMount(){}async onAfterMount(){}async onBeforeUnmount(){}async onAfterUnmount(){}async onBeforeDestroy(){}async onAfterDestroy(){}setModel(e={}){let t=e!==this.model;if(!t)return this;this.model&&this.model.off&&this.model.off("change",this._onModelChange,this),this.model=e,this.model&&this.model.on&&this.model.on("change",this._onModelChange,this);for(const s in this.children){const t=this.children[s];t&&"function"==typeof t.setModel&&t.setModel(e)}return t&&this._onModelChange(),this}_onModelChange(e,t){t&&t.skipRender||this.isMounted()&&this.render()}setTemplate(e){return this.template=e??"",this}addChild(e,t){try{if(!e||"object"!=typeof e)return this;t&&((t.containerId||t.container)&&(e.containerId=t.containerId||t.container),t.id&&(e.id=t.id),!0===t.lazyMount&&(e._lazyMount=!0)),e.parent=this,this.getApp()&&(e.app=this.app),this.children[e.id]=e}catch(s){View._warn("addChild error",s)}return e}removeChild(e){try{const t="string"==typeof e?e:e&&e.id;if(!t)return this;const s=this.children[t];s&&(Promise.resolve(s.destroy()).catch(e=>View._warn("removeChild destroy error",e)),delete this.children[t])}catch(t){View._warn("removeChild error",t)}return this}getChild(e){return this.children[e]}async updateData(e,t=!1){return Object.assign(this.data,e),t&&this.isMounted()&&await this.render(),this}toggleClass(e,t){return void 0===t&&(t=!this.element.classList.contains(e)),this.element.classList.toggle(e,t),this}addClass(e){return this.element.classList.add(e),this}setClass(e){return this.element.className=e,this}removeClass(e){return this.element.classList.remove(e),this}canRender(){if(this.isRendering)return!1;const e=Date.now();if(this.options.renderCooldown>0&&e-this.lastRenderTime<this.options.renderCooldown)return View._warn(`View ${this.id}: Render called too quickly, cooldown active`),!1;if(this.options.noAppend&&this.parent){if(!this.parent.contains(this.containerId||this.container))return!1;if(this.containerId&&!document.getElementById(this.containerId))return!1;if(this.container&&!document.contains(this.container))return!1}return!0}async render(e=!0,t=null){const s=Date.now();if(!this.canRender())return this;this.isRendering=!0,this.lastRenderTime=s;try{this.initialized||await this.onInitView(),this.unbindEvents(),await this.onBeforeRender(),this.getViewData&&(this.data=await this.getViewData());const s=await this.renderTemplate();this.element.innerHTML=s,e&&!this.isMounted()&&await this.mount(t),await this._renderChildren(),await this.onAfterRender(),this.bindEvents()}catch(r){View._warn(`Render error in ${this.id}`,r)}finally{this.isRendering=!1}return this}async _renderChildren(){const e=[];for(const t in this.children){const s=this.children[t];s&&(s.parent=this,!s._lazyMount||s._lazyTriggered?await Promise.resolve(s.render()).catch(e=>View._warn(`Child render error (${t})`,e)):e.push(s))}e.length&&this._setupLazyMountObserver(e)}_setupLazyMountObserver(e){if("undefined"!=typeof IntersectionObserver){this._lazyObserver||(this._lazyObserver=new IntersectionObserver((e,t)=>{for(const s of e){if(!s.isIntersecting)continue;const e=s.target.__mojoLazyChild;t.unobserve(s.target),e&&!e._lazyTriggered&&(e._lazyTriggered=!0,Promise.resolve(e.render()).catch(t=>View._warn(`Lazy child render error (${e.id})`,t)))}},{rootMargin:"120px 0px",threshold:.01}));for(const t of e){const e=this.getChildElement(t.containerId);e&&(e.__mojoLazyChild=t,e.style.minHeight||(e.classList.add("mojo-lazy-placeholder"),e.style.minHeight="1px"),this._lazyObserver.observe(e),requestAnimationFrame(()=>{if(t._lazyTriggered)return;const s=e.getBoundingClientRect(),r="undefined"!=typeof window?window.innerHeight:0,i="undefined"!=typeof window?window.innerWidth:0;s.top<r+120&&s.bottom>-120&&s.left<i&&s.right>0&&(t._lazyTriggered=!0,this._lazyObserver?.unobserve(e),Promise.resolve(t.render()).catch(e=>View._warn(`Lazy child render error (${t.id})`,e)))}))}}else e.forEach(e=>{e._lazyTriggered=!0,Promise.resolve(e.render()).catch(t=>View._warn(`Lazy child render error (${e.id})`,t))})}async _unmountChildren(){for(const e in this.children){const t=this.children[e];t&&t.unbindEvents()}}isMounted(){return this.element?.isConnected}getChildElementById(e,t=null){const s=e.startsWith("#")?e.substring(1):e;return t?t.querySelector(`#${s}`):this.element.querySelector(`#${s}`)}getChildElement(e){if(e.startsWith("#"))return this.getChildElementById(e);let t=this.element?.querySelector(`[data-container="${e}"]`);return t||this.getChildElementById(e)}getContainer(){return this.replaceById?this.parent?this.parent.getChildElementById(this.id):null:this.containerId?this.parent?this.parent.getChildElement(this.containerId):this.getChildElementById(this.containerId,document.body):null}async mount(e=null){await this.onBeforeMount(),e||(e=this.getContainer()),!this.containerId||e?(e&&this.replaceById?e.replaceWith(this.element):e?e.replaceChildren(this.element):!this.containerId&&this.parent?this.parent.element.appendChild(this.element):this.containerId||this.parent||!this.options.allowAppendToBody?console.error(`Container not found for ${this.containerId}`):document.body.appendChild(this.element),await this.onAfterMount(),this.mounted=!0):console.error(`Container not found for ${this.containerId}`)}async unmount(){this.element&&this.element.parentNode&&(await this.onBeforeUnmount(),await this._unmountChildren(),this.element.parentNode&&this.element.parentNode.removeChild(this.element),this.events.unbind(),await this.onAfterUnmount(),this.mounted=!1)}async destroy(){try{this.events.unbind(),this._lazyObserver&&(this._lazyObserver.disconnect(),this._lazyObserver=null);for(const e in this.children){const t=this.children[e];t&&await Promise.resolve(t.destroy()).catch(t=>View._warn(`Child destroy error (${e})`,t))}this.mounted=!1,this.element&&this.element.parentNode&&(await this.onBeforeDestroy(),this.element.parentNode&&this.element.parentNode.removeChild(this.element),await this.onAfterDestroy())}catch(e){View._warn(`Destroy error in ${this.id}`,e)}}_ensureElement(){try{if(this.element&&this.element.tagName?.toLowerCase()===this.tagName)return this._syncAttrs(),this.element;const e=document.createElement(this.tagName);return this.element=e,this.el=e,this._syncAttrs(),e}catch(e){View._warn("ensureElement error",e);const t=document.createElement("div");return t.id=this.id||View._genId(),t}}_syncAttrs(){try{if(!this.element)return;this.id&&(this.element.id=this.id),this.element.className=this.className||"",null==this.style?this.element.removeAttribute("style"):this.element.style.cssText=String(this.style)}catch(e){View._warn("_syncAttrs error",e)}}bindEvents(){this.events.bind(this.element),this.enableTooltips&&this.initializeTooltips()}unbindEvents(){this.events.unbind(),this.enableTooltips&&this.disposeTooltips()}async renderTemplate(){const e=await this.getTemplate();if(!e)return"";const t=this.getPartials();return p.render(e,this,t)}renderTemplateString(e,t,s){return p.render(e,t,s)}getPartials(){return{}}async getTemplate(){if(this._templateCache&&this.cacheTemplate)return this._templateCache;const e=this.template||this.templateUrl;if(!e)throw new Error("Template not found");let t="";if("string"==typeof e)if(e.includes("<")||e.includes("{"))t=e;else try{let s=e;this.app||(this.app=this.getApp()),!this.app||!this.app.basePath||s.startsWith("/")||s.startsWith("http://")||s.startsWith("https://")||(s=`${this.app.basePath.endsWith("/")?this.app.basePath.slice(0,-1):this.app.basePath}/${s}`);const r=await fetch(s);if(!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);t=await r.text()}catch(s){View._warn(`Failed to load template from ${e}: ${s}`),this.showError?.(`Failed to load template from ${e}: ${s.message}`)}else"function"==typeof e&&(t=await this.template(this.data,this.state));return this.cacheTemplate&&t&&"function"!=typeof e&&(this._templateCache=t),t}getContextValue(e){const t=MOJOUtils.getContextData(this,e);return e&&e.startsWith("data.")&&t&&"object"==typeof t?MOJOUtils.wrapData(t,this):e&&e.startsWith("model.")&&"model"!==e&&t&&"object"==typeof t&&"function"!=typeof t.getContextValue?MOJOUtils.wrapData(t,null):t}async handlePageNavigation(e){const t=e.getAttribute("data-page"),s=e.getAttribute("data-params");let r={};if(s)try{r=JSON.parse(s)}catch(a){console.warn("Invalid JSON in data-params:",s)}const i=this.getApp();if(i)return void i.showPage(t,r);const n=this.findRouter();n&&"function"==typeof n.navigateToPage?await n.navigateToPage(t,r):console.error(`No router found for page navigation to '${t}'`)}async handleHrefNavigation(e){const t=e.getAttribute("href");if(this.isExternalLink(t)||e.hasAttribute("data-external"))return;const s=this.findRouter();if(s){if(s.options&&"param"===s.options.mode&&t.startsWith("?")){const e="/"+t;return void(await s.navigate(e))}if(s.options&&"hash"===s.options.mode&&t.startsWith("#"))return void(await s.navigate(t));const e=this.hrefToRoutePath(t);await s.navigate(e)}else console.warn("No router found for navigation, using default behavior"),window.location.href=t}isExternalLink(e){return!e||(e.startsWith("/")&&this.getApp()?!e.startsWith(this.findRouter().basePath):e.startsWith("#")||e.startsWith("mailto:")||e.startsWith("tel:")||e.startsWith("http://")||e.startsWith("https://")||e.startsWith("//"))}hrefToRoutePath(e){if(e.startsWith("/")){const t=this.findRouter();if(t&&t.options&&t.options.base){const s=t.options.base;if(e.startsWith(s))return e.substring(s.length)||"/"}return e}return e.startsWith("./")?e.substring(2):e}findRouter(){return this.getApp(),this.app?.router||null}getApp(){if(this.app)return this.app;const e=[window.__app__,window.MOJO?.app,window.APP,window.app,window.WebApp,window.matchUUID?window[window.matchUUID]():window[window.matchUUID]];return this.app=e.find(e=>e&&"function"==typeof e.showPage)||null,this.app}handleActionError(e,t,s,r){this.showError(`Action '${e}' failed: ${t}`,s,r)}escapeHtml(e){if("string"!=typeof e)return e;const t=document.createElement("div");return t.textContent=e,t.innerHTML}contains(e){if("string"==typeof e){if(!this.element)return!1;e=document.getElementById(e)}return!!e&&this.element.contains(e)}initializeTooltips(){this.element&&window.bootstrap?.Tooltip&&(this.disposeTooltips(),[...this.element.querySelectorAll('[data-bs-toggle="tooltip"]')].map(e=>{const t=e.getAttribute("data-tooltip-theme"),s=e.getAttribute("data-tooltip-size");let r="";t&&(r+=`tooltip-${t} `),s&&(r+=`tooltip-${s}`);const i={},n=r.trim();return n&&(i.customClass=n),new window.bootstrap.Tooltip(e,i)}))}disposeTooltips(){this.element&&window.bootstrap?.Tooltip&&this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(e=>{const t=window.bootstrap.Tooltip.getInstance(e);t&&t.dispose()})}async showError(e){console.error(`View ${this.id} error:`,e);const t=this.getApp?this.getApp():this.app||null;t&&"function"==typeof t.showError?await t.showError(e):alert(`Error: ${e}`)}async showSuccess(e){this.debug&&this.id;const t=this.getApp?this.getApp():this.app||null;t&&"function"==typeof t.showSuccess?await t.showSuccess(e):alert(`Success: ${e}`)}async showInfo(e){this.id;const t=this.getApp?this.getApp():this.app||null;t&&"function"==typeof t.showInfo?await t.showInfo(e):alert(`Info: ${e}`)}async showWarning(e){console.warn(`View ${this.id} warning:`,e);const t=this.getApp?this.getApp():this.app||null;t&&"function"==typeof t.showWarning?await t.showWarning(e):alert(`Warning: ${e}`)}async onActionCopyToClipboard(e,t){try{const e=t?.closest("[data-clipboard]")||t,s=e?.getAttribute("data-clipboard")||"";if(!s)return!0;if(navigator.clipboard&&window.isSecureContext)await navigator.clipboard.writeText(s);else{const e=document.createElement("textarea");e.value=s,document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)}const r=t.querySelector("i"),i=r&&r.className;return r&&(r.className="bi bi-check",setTimeout(()=>{r.className=i},1e3)),!0}catch(s){return console.warn("Copy to clipboard failed:",s),!0}}static _genId(){return`view_${Math.random().toString(36).substr(2,9)}`}static _warn(e,t){try{t?console.warn(`[View] ${e}:`,t):console.warn(`[View] ${e}`)}catch{}}}Object.assign(View.prototype,m);const g=new class{constructor(){this.config={baseURL:"",timeout:3e4,headers:{"Content-Type":"application/json",Accept:"application/json"},trackDevice:!0,duidHeader:"X-Mojo-UID",duidTransport:"header"},this.interceptors={request:[],response:[]},this.duid=null,this.config.trackDevice&&this._initializeDuid()}_initializeDuid(){const e="mojo_device_uid";try{let t=localStorage.getItem(e);t?this.duid=t:(this.duid=this._generateDuid(),localStorage.setItem(e,this.duid))}catch(t){console.error("Could not access localStorage to get/set DUID.",t),this.duid=this._generateDuid()}}_generateDuid(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})}configure(e){e.baseUrl&&(e.baseURL=e.baseUrl);const t=this.config.trackDevice;this.config={...this.config,...e,headers:{...this.config.headers,...e.headers}},this.config.trackDevice&&!t&&this._initializeDuid()}addInterceptor(e,t){this.interceptors[e]&&this.interceptors[e].push(t)}buildUrl(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.config.baseURL.endsWith("/")?this.config.baseURL.slice(0,-1):this.config.baseURL}${e.startsWith("/")?e:`/${e}`}`}categorizeError(e,t=0){if("TypeError"===e.name&&e.message.includes("fetch"))return{reason:"not_reachable",message:"Service is not reachable - please check your connection"};if("AbortError"===e.name)return{reason:"cancelled",message:"Request was cancelled"};if("TimeoutError"===e.name||e.message.includes("timeout"))return{reason:"timed_out",message:"Request timed out - please try again"};if(t>=400){if(400===t)return{reason:"bad_request",message:"Invalid request data"};if(401===t)return{reason:"unauthorized",message:"Authentication required"};if(403===t)return{reason:"forbidden",message:"Access denied"};if(404===t)return{reason:"not_found",message:"Resource not found"};if(409===t)return{reason:"conflict",message:"Resource conflict"};if(422===t)return{reason:"validation_error",message:"Validation failed"};if(429===t)return{reason:"rate_limited",message:"Too many requests - please wait"};if(t>=500)return{reason:"server_error",message:"Server error - please try again later"};if(t>=400)return{reason:"client_error",message:"Request error"}}return e.message.includes("CORS")?{reason:"cors_error",message:"Cross-origin request blocked"}:e.message.includes("DNS")||e.message.includes("ENOTFOUND")?{reason:"dns_error",message:"Unable to resolve server address"}:{reason:"unknown_error",message:`Network error: ${e.message}`}}buildQueryString(e={}){const t=new URLSearchParams;Object.entries(e).forEach(([e,s])=>{null!=s&&(Array.isArray(s)?s.forEach(s=>t.append(`${e}[]`,s)):t.append(e,s))});const s=t.toString();return s?`?${s}`:""}async processRequestInterceptors(e){let t={...e};for(const r of this.interceptors.request)try{t=await r(t)}catch(s){throw console.error("Request interceptor error:",s),s}return t}async processResponseInterceptors(e,t){let s={success:e.ok,status:e.status,statusText:e.statusText,headers:Object.fromEntries(e.headers.entries()),data:null,errors:null,message:null,reason:null};try{const t=e.headers.get("content-type");if(t&&t.includes("application/json")){const t=await e.json();if(s.data=t,!e.ok){const r=this.categorizeError(new Error("HTTP Error"),e.status);s.errors=t.errors||{},s.message=t.message||r.message,s.reason=r.reason}}else if(s.data=await e.text(),!e.ok){const t=this.categorizeError(new Error("HTTP Error"),e.status);s.message=t.message,s.reason=t.reason}}catch(r){s.errors={parse:"Failed to parse response"},s.message="Invalid response format"}for(const i of this.interceptors.response)try{s=await i(s,t)}catch(r){console.error("Response interceptor error:",r)}return s}async request(e,t,s=null,r={},i={}){let n={method:e.toUpperCase(),url:this.buildUrl(t)+this.buildQueryString(r),headers:{...this.config.headers,...i.headers},data:s,options:{timeout:this.config.timeout,...i}};try{n=await this.processRequestInterceptors(n)}catch(l){if("AuthRequiredError"===l.name)return{success:!1,status:401,statusText:"Unauthorized",headers:{},data:null,errors:{auth:l.message},message:"Authentication required",reason:"unauthorized"};throw l}if(this.config.trackDevice&&this.duid)if("header"===this.config.duidTransport)n.headers[this.config.duidHeader]=this.duid;else if("GET"===n.method){const e=new URL(n.url);e.searchParams.append("duid",this.duid),n.url=e.toString()}else!n.data||"object"!=typeof n.data||n.data instanceof FormData||(n.data.duid=this.duid);const a={method:n.method,headers:n.headers},o=[];n.options.timeout&&o.push(AbortSignal.timeout(n.options.timeout)),n.options.signal&&o.push(n.options.signal),o.length>1?a.signal=AbortSignal.any?AbortSignal.any(o):o[0]:1===o.length&&(a.signal=o[0]),n.data&&["POST","PUT","PATCH"].includes(n.method)&&(n.data instanceof FormData?(a.body=n.data,delete a.headers["Content-Type"]):"object"==typeof n.data?a.body=JSON.stringify(n.data):a.body=n.data);try{const e=await fetch(n.url,a),t=await this.processResponseInterceptors(e,n);return i.dataOnly&&t.data&&"object"==typeof t.data&&"data"in t.data&&(t.message=t.message||t.data.message,t.data=t.data.data),t}catch(l){if("AbortError"===l.name)throw l;const e=this.categorizeError(l),t={success:!1,status:0,statusText:"Network Error",headers:{},data:null,errors:{network:l.message},message:e.message,reason:e.reason},s={ok:!1,status:0,statusText:"Network Error",headers:new Headers,json:async()=>({}),text:async()=>""};return await this.processResponseInterceptors(s,n),t}}async GET(e,t={},s={}){return this.request("GET",e,null,t,s)}async POST(e,t={},s={},r={}){return this.request("POST",e,t,s,r)}async PUT(e,t={},s={},r={}){return this.request("PUT",e,t,s,r)}async PATCH(e,t={},s={},r={}){return this.request("PATCH",e,t,s,r)}async DELETE(e,t={},s={}){return this.request("DELETE",e,null,t,s)}get(...e){return this.GET(...e)}post(...e){return this.POST(...e)}put(...e){return this.PUT(...e)}patch(...e){return this.PATCH(...e)}delete(...e){return this.DELETE(...e)}async download(e,t={},s={}){const r={method:"GET",url:this.buildUrl(e)+this.buildQueryString(t),headers:{...this.config.headers,Accept:"*/*",...s.headers},options:{...s}};delete r.headers["Content-Type"];try{const e=await fetch(r.url,{method:r.method,headers:r.headers,signal:r.options.signal});if(!e.ok)throw new Error(`Download failed: ${e.status} ${e.statusText}`);const t=e.headers.get("content-disposition");let i=s.filename||"download";if(t){const e=t.match(/filename="?(.+)"?/);e&&e.length>1&&(i=e[1])}const n=e.body.getReader(),a=new ReadableStream({start:e=>function t(){return n.read().then(({done:s,value:r})=>{if(!s)return e.enqueue(r),t();e.close()})}()}),o=await new Response(a).blob(),l=window.URL.createObjectURL(o),c=document.createElement("a");return c.style.display="none",c.href=l,c.download=i,document.body.appendChild(c),c.click(),window.URL.revokeObjectURL(l),c.remove(),{success:!0,message:"Download initiated"}}catch(i){return console.error("Download error:",i),{success:!1,message:i.message}}}async downloadBlob(e,t={},s={}){const r={method:"GET",url:this.buildUrl(e)+this.buildQueryString(t),headers:{...this.config.headers,Accept:"*/*",...s.headers},options:{...s}};delete r.headers["Content-Type"];try{const e=await fetch(r.url,{method:r.method,headers:r.headers,signal:r.options.signal});if(!e.ok)throw new Error(`Download failed: ${e.status} ${e.statusText}`);const t=await e.blob(),i=e.headers.get("content-disposition");let n=s.filename||"download";if(i){const e=i.match(/filename="?(.+)"?/);e&&e.length>1&&(n=e[1])}const a=window.URL.createObjectURL(t),o=document.createElement("a");return o.style.display="none",o.href=a,o.download=n,document.body.appendChild(o),o.click(),window.URL.revokeObjectURL(a),o.remove(),{success:!0,message:"Download initiated"}}catch(i){return console.error("Download error:",i),{success:!1,message:i.message}}}async upload(e,t,s={}){return new Promise((r,i)=>{if(!(t instanceof File))return void i(new Error("Only single File objects are supported for legacy backend compatibility"));const n=new XMLHttpRequest;s.onProgress&&"function"==typeof s.onProgress&&(n.upload.onprogress=s.onProgress),n.onload=function(){n.status>=200&&n.status<300?r({data:n.response,status:n.status,statusText:n.statusText,xhr:n}):i(new Error(`Upload failed: ${n.status} ${n.statusText}`))},n.onerror=function(){i(new Error("Upload failed: Network error"))},n.ontimeout=function(){i(new Error("Upload failed: Timeout"))},n.open("PUT",e),n.setRequestHeader("Content-Type",t.type),s.timeout&&(n.timeout=s.timeout),n.send(t)})}async uploadMultipart(e,t,s={},r={}){const i=new FormData;if(t instanceof FileList)Array.from(t).forEach((e,t)=>{i.append(`file_${t}`,e)});else if(t instanceof File)i.append("file",t);else if(t instanceof FormData)return this.POST(e,t,{},r);return Object.entries(s).forEach(([e,t])=>{i.append(e,t)}),this.POST(e,i,{},r)}setAuthToken(e,t="Bearer"){e?this.config.headers.Authorization=`${t} ${e}`:delete this.config.headers.Authorization}clearAuth(){delete this.config.headers.Authorization}isRetryableError(e){return["not_reachable","timed_out","server_error","dns_error"].includes(e.reason)}requiresAuth(e){return"unauthorized"===e.reason}isNetworkError(e){return["not_reachable","timed_out","cancelled","cors_error","dns_error"].includes(e.reason)}getUserMessage(e){return e.message?e.message:{not_reachable:"Unable to connect to the server. Please check your internet connection.",timed_out:"The request took too long. Please try again.",cancelled:"The request was cancelled.",unauthorized:"Please log in to continue.",forbidden:"You don't have permission to perform this action.",not_found:"The requested resource was not found.",validation_error:"Please check your input and try again.",rate_limited:"Too many requests. Please wait a moment before trying again.",server_error:"Server error. Please try again later.",cors_error:"Access blocked by security policy.",dns_error:"Unable to reach the server.",unknown_error:"An unexpected error occurred."}[e.reason]||"An error occurred. Please try again."}};class Model{constructor(e={},t={}){this.endpoint=t.endpoint||this.constructor.endpoint||"",this.id=e.id||null,this.attributes={...e},this._=this.attributes,this.originalAttributes={...e},this.errors={},this.loading=!1,this.rest=g,this.collection=t.collection||null,this.options={idAttribute:"id",timestamps:!0,...t}}getContextValue(e){return this.get(e)}get(e){return e.includes(".")||e.includes("|")||void 0===this[e]?MOJOUtils.getContextData(this.attributes,e):"function"==typeof this[e]?this[e]():this[e]}set(e,t,s={}){const r=JSON.parse(JSON.stringify(this.attributes));let i=!1;if(null!=e){if("object"==typeof e){for(const[t,s]of Object.entries(e))i=this._setNestedAttribute(t,s)||i;void 0!==e.id&&(this.id=e.id)}else"id"===e?(this.id=t,i=!0):i=this._setNestedAttribute(e,t);if(i&&!s.silent)if(this.emit("change",this,s),"string"==typeof e)this.emit(`change:${e}`,t,this);else for(const[t,s]of Object.entries(e)){const e=this._getNestedValue(t);JSON.stringify(this._getNestedValue(t,r))!==JSON.stringify(e)&&this.emit(`change:${t}`,e,this)}}}_setNestedAttribute(e,t){if(!e.includes(".")){const s=this.attributes[e];return this.attributes[e]=t,this[e]=t,s!==t}const s=e.split("."),r=s[0];this.attributes[r]&&"object"==typeof this.attributes[r]||(this.attributes[r]={}),this[r]&&"object"==typeof this[r]||(this[r]={});const i=this._getNestedValue(e);let n=this.attributes[r],a=this[r];for(let l=1;l<s.length-1;l++){const e=s[l];n[e]&&"object"==typeof n[e]||(n[e]={}),a[e]&&"object"==typeof a[e]||(a[e]={}),n=n[e],a=a[e]}const o=s[s.length-1];return n[o]=t,a[o]=t,JSON.stringify(i)!==JSON.stringify(t)}_getNestedValue(e,t=this.attributes){if(!e.includes("."))return t[e];const s=e.split(".");let r=t;for(const i of s){if(null==r||"object"!=typeof r)return;r=r[i]}return r}getData(){return this.attributes}getId(){return this.id}async fetch(e={}){let t=e.url;if(!t){const s=e.id||this.getId();if(!s&&!1!==this.options.requiresId)throw new Error("Model: ID is required for fetching");t=this.buildUrl(s)}const s=JSON.stringify({url:t,params:e.params});if(e.debounceMs&&e.debounceMs>0)return this._debouncedFetch(s,e);if(this.currentRequest&&this.currentRequestKey!==s&&(this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===s)return this.currentRequest;const r=Date.now();if(this.lastFetchTime&&r-this.lastFetchTime<100)return this;this.loading=!0,this.errors={},this.lastFetchTime=r,this.currentRequestKey=s,this.abortController=new AbortController,this.currentRequest=this._performFetch(t,e,this.abortController);try{return await this.currentRequest}catch(i){if("AbortError"===i.name)return this;throw i}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _debouncedFetch(e,t){return this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((e,s)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const s=await this.fetch({...t,debounceMs:0});e(s)}catch(r){s(r)}},t.debounceMs)})}async _performFetch(e,t,s){try{!t.graph||t.params&&t.params.graph||(t.params||(t.params={}),t.params.graph=t.graph);const r=await this.rest.GET(e,t.params,{signal:s.signal});return r.success?r.data.status?(this.originalAttributes={...this.attributes},r.data.data&&this.set(r.data.data),this.errors={}):this.errors=r.data:this.errors=r.errors||{},r}catch(r){if("AbortError"===r.name)throw r;return this.errors={fetch:r.message},{success:!1,error:r.message,status:r.status||500}}finally{this.loading=!1}}async save(e,t={}){const s=!this.id,r=s?"POST":"PUT",i=s?this.buildUrl():this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest[r](i,e,t.params);return s.success?s.data.status?(this.originalAttributes={...this.attributes},this.set(s.data.data,null,t),this.errors={}):this.errors=s.data:this.errors=s.errors||{},s}catch(n){return{success:!1,error:n.message,status:n.status||500}}finally{this.loading=!1}}async destroy(e={}){if(!this.id)return this.errors={destroy:"Cannot destroy model without ID"},{success:!1,error:"Cannot destroy model without ID",status:400};const t=this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest.DELETE(t,e.params);return s.success?(this.attributes={},this.originalAttributes={},this.id=null,this.errors={}):this.errors=s.errors||{},s}catch(s){return this.errors={destroy:s.message},{success:!1,error:s.message,status:s.status||500}}finally{this.loading=!1}}isDirty(){return JSON.stringify(this.attributes)!==JSON.stringify(this.originalAttributes)}getChangedAttributes(){const e={};for(const[t,s]of Object.entries(this.attributes))this.originalAttributes[t]!==s&&(e[t]=s);return e}reset(){for(const e of Object.keys(this.attributes))e in this.originalAttributes||delete this[e];for(const[e,t]of Object.entries(this.originalAttributes))this[e]=t;this.attributes={...this.originalAttributes},this._=this.attributes,this.errors={}}buildUrl(e=null){let t=this.endpoint;return e&&(t=t.endsWith("/")?`${t}${e}`:`${t}/${e}`),t}toJSON(){return{id:this.id,...this.attributes}}validate(){if(this.errors={},this.constructor.validations)for(const[e,t]of Object.entries(this.constructor.validations))this.validateField(e,t);return 0===Object.keys(this.errors).length}validateField(e,t){const s=this.get(e),r=Array.isArray(t)?t:[t];for(const i of r)if("function"==typeof i){const t=i(s,this);if(!0!==t){this.errors[e]=t||`${e} is invalid`;break}}else if("object"==typeof i){if(i.required&&(null==s||""===s)){this.errors[e]=i.message||`${e} is required`;break}if(i.minLength&&s&&s.length<i.minLength){this.errors[e]=i.message||`${e} must be at least ${i.minLength} characters`;break}if(i.maxLength&&s&&s.length>i.maxLength){this.errors[e]=i.message||`${e} must be no more than ${i.maxLength} characters`;break}if(i.pattern&&s&&!i.pattern.test(s)){this.errors[e]=i.message||`${e} format is invalid`;break}}}static async find(e,t={}){const s=new this({},t);return await s.fetch({id:e,...t}),s}static create(e={},t={}){return new this(e,t)}cancel(){return this.currentRequest&&this.abortController?(this.abortController.abort(),!0):!!this.debouncedFetchTimeout&&(clearTimeout(this.debouncedFetchTimeout),this.debouncedFetchTimeout=null,!0)}isFetching(){return!!this.currentRequest}async showError(e){const t=(await import("./Modal-BAEK2ub4.js").then(e=>e.h)).default;await t.alert(e,"Error",{size:"md",class:"text-danger"})}}Object.assign(Model.prototype,m);class Collection{constructor(e={},t=null){if(Array.isArray(e)?e=(t=e)||{}:t=t||e.data||[],this.ModelClass=e.ModelClass||Model,this.models=[],this.loading=!1,this.errors={},this.meta={},this.rest=g,t&&this.add(t),this.params={start:0,size:e.size||10,...e.params},this.endpoint=e.endpoint||this.ModelClass.endpoint||"",!this.endpoint){let e=new this.ModelClass;this.endpoint=e.endpoint}this.restEnabled=!!this.endpoint,void 0!==e.restEnabled&&(this.restEnabled=e.restEnabled),this.options={parse:!0,reset:!0,preloaded:!1,...e}}getModelName(){return this.ModelClass.name}async fetch(e={}){const t=JSON.stringify({...this.params,...e});if(this.currentRequest&&this.currentRequestKey!==t&&(this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===t)return this.currentRequest;const s=Date.now();if(this.options.rateLimiting&&this.lastFetchTime&&s-this.lastFetchTime<100)return{success:!0,message:"Rate limited, skipping fetch",data:{data:this.toJSON()}};if(!this.restEnabled)return{success:!0,message:"REST disabled, skipping fetch",data:{data:this.toJSON()}};if(this.options.preloaded&&this.models.length>0)return{success:!0,message:"Using preloaded data, skipping fetch",data:{data:this.toJSON()}};const r=this.buildUrl();this.loading=!0,this.errors={},this.lastFetchTime=s,this.currentRequestKey=t,this.abortController=new AbortController,this.currentRequest=this._performFetch(r,e,this.abortController);try{return await this.currentRequest}catch(i){return"AbortError"===i.name?{success:!1,error:"Request cancelled",status:0}:{success:!1,error:i.message,status:i.status||500}}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _performFetch(e,t,s){const r={...this.params,...t};try{this.emit("fetch:start");const i=await this.rest.GET(e,r,{signal:s.signal});if(i.success&&i.data.status){const e=this.options.parse?this.parse(i):i.data;!1!==t.reset&&this.options.reset&&this.reset(),this.add(e,{silent:t.silent}),this.errors={},this.emit("fetch:success")}else i.data&&i.data.error?(this.errors=i.data,this.emit("fetch:error",{message:i.data.error,error:i.data})):(this.errors=i.errors||{},this.emit("fetch:error",{error:i.errors}));return i}catch(i){return"AbortError"===i.name?{success:!1,error:"Request cancelled",status:0}:(this.errors={fetch:i.message},this.emit("fetch:error",{message:i.message,error:i}),{success:!1,error:i.message,status:i.status||500})}finally{this.loading=!1,this.emit("fetch:end")}}async updateParams(e,t=!1,s=0){return await this.setParams({...this.params,...e},t,s)}async setParams(e,t=!1,s=0){return this.params=e,t&&this.restEnabled?s>0?(this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((e,t)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const t=await this.fetch();e(t)}catch(s){t(s)}},s)})):this.fetch():Promise.resolve(this)}async fetchMore({pageDelta:e=1}={}){if(!this.restEnabled)return{success:!1,message:"REST disabled, cannot fetchMore"};const t=this.params.size||10,s=this.params.start||0,r=s+e*t;return this.meta&&"number"==typeof this.meta.count&&r>=this.meta.count?{success:!0,message:"No more results",data:{data:[],status:"ok",count:this.meta.count,start:s,size:t}}:(this.params={...this.params,start:r},this.emit("fetch:more",{start:r,pageDelta:e,collection:this}),this.fetch({reset:!1}))}async fetchOne(e,t={}){if(!e)return console.warn("Collection: fetchOne requires an ID"),null;if(!this.restEnabled)return null;try{const s=new this.ModelClass({id:e},{endpoint:this.endpoint,collection:this}),r=await s.fetch(t);if(r.success){if(!0===t.addToCollection){const e=this.get(s.id);e?!1!==t.merge&&e.set(s.attributes):this.add(s,{silent:t.silent})}return s}return console.warn("Collection: fetchOne failed -",r.error||"Unknown error"),null}catch(s){return console.error("Collection: fetchOne error -",s.message),null}}async download(e="json",t={}){if(!this.restEnabled)return console.warn("Collection: REST is not enabled, cannot download from remote."),{success:!1,message:"Remote downloads are not enabled for this collection."};const s=this.buildUrl(),r={...this.params};delete r.start,delete r.size,r.download_format=e;const i=`export-${this.getModelName().toLowerCase()}${this._buildDateRangeSuffix(r)}.${e}`,n={json:"application/json",csv:"text/csv"}[e]||"*/*";return r.filename=i,this.rest.download(s,r,{...t,filename:i,headers:{Accept:n}})}_buildDateRangeSuffix(e={}){const t=e.dr_start,s=e.dr_end;if(!t&&!s)return"";const r=e=>e?String(e).replace(/[^\dA-Za-z_-]/g,"-"):"",i=[],n=e.dr_field||"daterange";return i.push(r(n)),t&&i.push(`from-${r(e.dr_start)}`),s&&i.push(`to-${r(e.dr_end)}`),`-${i.filter(Boolean).join("-")}`}parse(e){return e.data&&Array.isArray(e.data.data)?(this.meta={size:e.data.size||10,start:e.data.start||0,count:e.data.count||0,status:e.data.status,graph:e.data.graph,...e.meta},e.data.data):Array.isArray(e.data)?e.data:Array.isArray(e)?e:[e]}add(e,t={}){const s=Array.isArray(e)?e:[e],r=[];for(const i of s){let e;i instanceof this.ModelClass?(e=i,e.collection||(e.collection=this)):e=new this.ModelClass(i,{endpoint:this.endpoint,collection:this});const s=this.models.findIndex(t=>t.id===e.id);-1!==s?!1!==t.merge&&this.models[s].set(e.attributes):(this.models.push(e),r.push(e))}return!t.silent&&r.length>0&&(this.emit("add",{models:r,collection:this}),this.emit("update",{collection:this})),r}remove(e,t={}){const s=Array.isArray(e)?e:[e],r=[];for(const i of s){let e=-1;if(e="string"==typeof i||"number"==typeof i?this.models.findIndex(e=>e.id==i):this.models.indexOf(i),-1!==e){const t=this.models.splice(e,1)[0];t.collection===this&&(t.collection=null),r.push(t)}}return!t.silent&&r.length>0&&(this.emit("remove",{models:r,collection:this}),this.emit("update",{collection:this})),r}reset(e=null,t={}){const s=[...this.models];return this.models=[],e&&this.add(e,{silent:!0,...t}),t.silent||this.emit("reset",{collection:this,previousModels:s}),this}get(e){return this.models.find(t=>t.id==e)}at(e){return this.models[e]}length(){return this.models.length}isEmpty(){return 0===this.models.length}where(e){return"function"==typeof e?this.models.filter(e):"object"==typeof e?this.models.filter(t=>Object.entries(e).every(([e,s])=>t.get(e)===s)):[]}findWhere(e){const t=this.where(e);return t.length>0?t[0]:void 0}forEach(e,t){if("function"!=typeof e)throw new TypeError("Callback must be a function");return this.models.forEach((s,r)=>{e.call(t,s,r,this)}),this}sort(e,t={}){if("string"==typeof e){const t=e;e=(e,s)=>{const r=e.get(t),i=s.get(t);return r<i?-1:r>i?1:0}}return this.models.sort(e),t.silent||this.emit("sort",{collection:this}),this}toJSON(){return this.models.map(e=>e.toJSON())}cancel(){return!(!this.currentRequest||!this.abortController||(this.abortController.abort(),0))}isFetching(){return!!this.currentRequest}buildUrl(){return this.endpoint}*[Symbol.iterator](){for(const e of this.models)yield e}static fromArray(e,t=[],s={}){const r=new this({ModelClass:e,...s});return r.add(t,{silent:!0}),r}}Object.assign(Collection.prototype,m);class Group extends Model{constructor(e={}){super(e,{endpoint:"/api/group"})}}class GroupList extends Collection{constructor(e={}){super({ModelClass:Group,endpoint:"/api/group",size:10,...e})}}const f={org:"Organization",platform:"Platform",division:"Division",department:"Department",team:"Team",merchant:"Merchant",partner:"Partner",client:"Client",iso:"ISO",sales:"Sales",reseller:"Reseller",location:"Location",region:"Region",route:"Route",project:"Project",inventory:"Inventory",test:"Testing",misc:"Miscellaneous",qa:"Quality Assurance"},b=Object.entries(f).map(([e,t])=>({value:e,label:t})),y=[{value:"America/New_York",label:"Eastern Time (ET)"},{value:"America/Chicago",label:"Central Time (CT)"},{value:"America/Denver",label:"Mountain Time (MT)"},{value:"America/Los_Angeles",label:"Pacific Time (PT)"},{value:"America/Anchorage",label:"Alaska Time (AKT)"},{value:"Pacific/Honolulu",label:"Hawaii Time (HT)"},{value:"UTC",label:"UTC"},{value:"Europe/London",label:"London (GMT/BST)"},{value:"Europe/Paris",label:"Paris (CET/CEST)"},{value:"Europe/Berlin",label:"Berlin (CET/CEST)"},{value:"Asia/Tokyo",label:"Tokyo (JST)"},{value:"Asia/Shanghai",label:"Shanghai (CST)"},{value:"Australia/Sydney",label:"Sydney (AEST)"}],w=Array.from({length:24},(e,t)=>{let s;return s=0===t?"Midnight":12===t?"Noon":`${t%12==0?12:t%12} ${t<12?"AM":"PM"}`,{value:t,label:s}});function _(){return{avatar:{type:"image",name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar",help:"Square images work best",columns:12},name:{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name",columns:12},kind:{name:"kind",type:"combo",label:"Group Kind",required:!0,placeholder:"Type or select kind...",options:b,columns:12},parent:{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300,columns:12},isActive:{name:"is_active",type:"switch",label:"Is Active",columns:12},uuid:{name:"uuid",type:"text",label:"UUID",placeholder:"32-character hex string",columns:12},shortName:{name:"metadata.short_name",type:"text",label:"Short Name",placeholder:"Enter short name",columns:6},domain:{name:"metadata.domain",type:"text",label:"Default Domain",placeholder:"Enter domain",columns:6},authDomain:{name:"metadata.auth_domain",type:"text",label:"Auth Domain",placeholder:"auth.example.com",help:"Used for white-label login pages.",columns:6},portal:{name:"metadata.portal",type:"text",label:"Default Portal",placeholder:"https://…",columns:6},timezone:{name:"metadata.timezone",type:"select",label:"Timezone",options:y,columns:6},eodHour:{name:"metadata.eod_hour",type:"select",label:"End of Day Hour",options:w,columns:6},emailTemplate:{name:"metadata.email_template",type:"text",label:"Email Template (Prefix)",placeholder:"Enter template prefix",columns:12}}}const S={get create(){const e=_();return{title:"Create Group",fields:[e.name,e.kind,e.parent]}},get edit(){return{...this.detailed,title:"Edit Group"}},get detailed(){const e=_();return{title:"Group Details",fields:[{type:"tabset",name:"group-detail",tabs:[{label:"Profile",fields:[e.name,e.kind,e.parent,e.isActive]},{label:"Identity",fields:[e.uuid,e.shortName,e.domain,e.authDomain,e.portal]},{label:"Localization",fields:[e.timezone,e.eodHour]},{label:"Branding",fields:[e.emailTemplate]},{label:"Avatar",fields:[e.avatar]}]}]}}};Group.EDIT_FORM=S.edit,Group.ADD_FORM=S.create,Group.CREATE_FORM=S.create,Group.FORM_DIALOG_CONFIG={size:"lg"},Group.GroupKindOptions=b,Group.GroupKinds=f,Group.TimezoneOptions=y,Group.EodHourOptions=w;const A=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,EodHourOptions:w,Group:Group,GroupForms:S,GroupKindOptions:b,GroupList:GroupList,TimezoneOptions:y},Symbol.toStringTag,{value:"Module"}));class User extends Model{constructor(e={}){super(e,{endpoint:"/api/user"})}hasPermission(e){if(this.get("is_superuser"))return!0;if(Array.isArray(e))return e.some(e=>this.hasPermission(e));const t=e.startsWith("sys."),s=t?e.substring(4):e;return!!this._hasPermission(s)||!!this._hasPermission("admin")||!(t||!this.member||!this.member.hasPermission(e))}_hasPermission(e){const t=this.get("permissions");if(!t)return!1;if(1==t[e])return!0;const s=User.GRANULAR_TO_CATEGORY[e];return!(!s||1!=t[s])}hasPerm(e){return this.hasPermission(e)}}class UserList extends Collection{constructor(e={}){super({ModelClass:User,endpoint:"/api/user",...e})}}User.CATEGORY_PERMISSIONS=[{name:"view_admin",label:"Admin Panel",tooltip:"Access the admin panel, Mojo, and system tools"},{name:"security",label:"Security",tooltip:"Incidents, events, rules, tickets, firewall, bouncer, GeoIP, system logs"},{name:"users",label:"Users",tooltip:"User records, passkeys, TOTP, API keys, OAuth, devices, locations"},{name:"groups",label:"Groups",tooltip:"Groups, members, group API keys, settings"},{name:"comms",label:"Communications",tooltip:"Email, phone, SMS, push notifications, chat, notifications"},{name:"jobs",label:"Jobs",tooltip:"Jobs, job events, job logs, runners, queue control, system stats"},{name:"metrics",label:"Metrics",tooltip:"Metrics recording, fetching, categories, values, permissions"},{name:"files",label:"Files",tooltip:"File managers, files, renditions, vault files, vault data, S3 buckets"},{name:"assistant",label:"Mojo",tooltip:"Access to Mojo"}],User.GRANULAR_PERMISSION_TABS=[{label:"Account",permissions:[{name:"view_users",label:"View Users"},{name:"manage_users",label:"Manage Users"},{name:"view_groups",label:"View Groups"},{name:"manage_groups",label:"Manage Groups"},{name:"manage_group",label:"Manage Own Group"},{name:"view_members",label:"View Members"},{name:"manage_settings",label:"Manage Settings"}]},{label:"Communication",permissions:[{name:"manage_chat",label:"Manage Chat"},{name:"manage_aws",label:"Manage Email (AWS)"},{name:"view_notifications",label:"View Notifications"},{name:"manage_notifications",label:"Manage Notifications"},{name:"send_notifications",label:"Send Notifications"},{name:"view_devices",label:"View Push Devices"},{name:"manage_devices",label:"Manage Push Devices"},{name:"manage_push_config",label:"Push Config"},{name:"view_phone_numbers",label:"View Phone Numbers"},{name:"manage_phone_numbers",label:"Manage Phone Numbers"},{name:"manage_phone_config",label:"Phone Config"},{name:"view_sms",label:"View SMS"},{name:"manage_sms",label:"Manage SMS"},{name:"send_sms",label:"Send SMS"}]},{label:"Platform",permissions:[{name:"view_security",label:"View Security"},{name:"manage_security",label:"Manage Security"},{name:"admin",label:"Log Admin"},{name:"view_logs",label:"View Logs"},{name:"manage_logs",label:"Manage Logs"},{name:"view_jobs",label:"View Jobs"},{name:"manage_jobs",label:"Manage Jobs"},{name:"view_metrics",label:"View Metrics"},{name:"manage_metrics",label:"Manage Metrics"},{name:"write_metrics",label:"Write Metrics"},{name:"view_fileman",label:"View File Managers"},{name:"manage_files",label:"Manage Files"},{name:"view_vault",label:"View Vault"},{name:"manage_vault",label:"Manage Vault"},{name:"manage_docit",label:"Manage Docs"},{name:"manage_shortlinks",label:"Manage Shortlinks"}]}],User.CATEGORY_GRANULAR_MAP={security:["view_security","manage_security"],users:["view_users","manage_users","view_members"],groups:["view_groups","manage_groups","manage_group"],comms:["manage_chat","manage_aws","view_notifications","manage_notifications","send_notifications","view_devices","manage_devices","manage_push_config","view_phone_numbers","manage_phone_numbers","manage_phone_config","view_sms","manage_sms","send_sms"],jobs:["view_jobs","manage_jobs"],metrics:["view_metrics","manage_metrics","write_metrics"],files:["view_fileman","manage_files","view_vault","manage_vault"]},User.APP_CATEGORY_PERMISSIONS=[],User.APP_GRANULAR_PERMISSIONS=[],User._permSwitch=function(e){return{name:`permissions.${e.name}`,type:"switch",label:e.label,columns:6,...e.tooltip?{tooltip:e.tooltip}:{}}},User.PERMISSIONS=[],User.PERMISSION_FIELDS=[],User.CATEGORY_PERMISSION_FIELDS=[],User.GRANULAR_PERMISSION_FIELDS=[],User.SYSTEM_PERMISSION_FIELDS=[],User.APP_PERMISSION_FIELDS=[],User.GRANULAR_TO_CATEGORY={},User.rebuildPermissions=function(){const e=User._permSwitch;User.PERMISSIONS.length=0,User.PERMISSIONS.push(...User.CATEGORY_PERMISSIONS,...User.GRANULAR_PERMISSION_TABS.flatMap(e=>e.permissions),...User.APP_CATEGORY_PERMISSIONS,...User.APP_GRANULAR_PERMISSIONS),User.PERMISSION_FIELDS.length=0,User.PERMISSION_FIELDS.push(...User.PERMISSIONS.map(e));const t=[{label:"System",fields:User.CATEGORY_PERMISSIONS.map(e)}];User.APP_CATEGORY_PERMISSIONS.length>0&&t.push({label:"App",fields:User.APP_CATEGORY_PERMISSIONS.map(e)}),User.CATEGORY_PERMISSION_FIELDS.length=0,User.CATEGORY_PERMISSION_FIELDS.push({type:"tabset",tabs:t});const s=User.GRANULAR_PERMISSION_TABS.map(t=>({label:t.label,fields:t.permissions.map(e)}));User.APP_GRANULAR_PERMISSIONS.length>0&&s.push({label:"App",fields:User.APP_GRANULAR_PERMISSIONS.map(e)}),User.GRANULAR_PERMISSION_FIELDS.length=0,User.GRANULAR_PERMISSION_FIELDS.push({type:"tabset",tabs:s}),User.SYSTEM_PERMISSION_FIELDS.length=0,User.SYSTEM_PERMISSION_FIELDS.push({type:"tabset",tabs:[{label:"System",fields:User.CATEGORY_PERMISSIONS.map(e)},...User.GRANULAR_PERMISSION_TABS.map(t=>({label:t.label,fields:t.permissions.map(e)}))]}),User.APP_PERMISSION_FIELDS.length=0;const r=[];User.APP_CATEGORY_PERMISSIONS.length>0&&r.push({label:"Categories",fields:User.APP_CATEGORY_PERMISSIONS.map(e)}),User.APP_GRANULAR_PERMISSIONS.length>0&&r.push({label:"Permissions",fields:User.APP_GRANULAR_PERMISSIONS.map(e)}),r.length>0&&User.APP_PERMISSION_FIELDS.push({type:"tabset",tabs:r});for(const i of Object.keys(User.GRANULAR_TO_CATEGORY))delete User.GRANULAR_TO_CATEGORY[i];for(const[i,n]of Object.entries(User.CATEGORY_GRANULAR_MAP))for(const e of n)User.GRANULAR_TO_CATEGORY[e]=i},User.registerCategoryMap=function(e){if(!e)return;let t=!1;for(const[s,r]of Object.entries(e)){if(!Array.isArray(r))continue;const e=User.CATEGORY_GRANULAR_MAP[s]||[];User.CATEGORY_GRANULAR_MAP[s]=Array.from(/* @__PURE__ */new Set([...e,...r])),t=!0}t&&User.rebuildPermissions()},User.registerPermissions=function(e){if(e){if(Array.isArray(e.categories)&&User.APP_CATEGORY_PERMISSIONS.push(...e.categories),Array.isArray(e.granularPermissions)&&User.APP_GRANULAR_PERMISSIONS.push(...e.granularPermissions),Array.isArray(e.granularTabs)&&User.GRANULAR_PERMISSION_TABS.push(...e.granularTabs),e.categoryGranularMap)for(const[t,s]of Object.entries(e.categoryGranularMap)){if(!Array.isArray(s))continue;const e=User.CATEGORY_GRANULAR_MAP[t]||[];User.CATEGORY_GRANULAR_MAP[t]=Array.from(/* @__PURE__ */new Set([...e,...s]))}User.rebuildPermissions()}},User.rebuildPermissions();const v={create:{title:"Create User",fields:[{name:"email",type:"text",label:"Email",required:!0},{name:"phone_number",type:"text",label:"Phone number",columns:12},{name:"display_name",type:"text",label:"Display Name"}]},edit:{title:"Edit User",fields:[{name:"email",type:"email",label:"Email",columns:12},{name:"display_name",type:"text",label:"Display Name",columns:12},{name:"phone_number",type:"text",label:"Phone number",columns:12},{type:"collection",name:"org",label:"Organization",Collection:GroupList,labelField:"name",valueField:"id",columns:12}]},permissions:{title:"Edit Permissions",fields:User.PERMISSION_FIELDS}},C={profile:{title:"User Profile",columns:2,fields:[{name:"id",label:"User ID",type:"number",columns:4},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",columns:4},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",columns:4},{name:"username",label:"Username",type:"text",format:"lowercase",columns:4},{name:"display_name",label:"Display Name",type:"text",columns:4},{name:"email",label:"Email",type:"email",columns:12},{name:"org.name",label:"Organization",type:"text",columns:6},{name:"phone_number",label:"Phone Number",type:"text",columns:6}]},activity:{title:"User Activity",columns:2,fields:[{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6}]},detailed:{title:"Detailed User Information",columns:2,showEmptyValues:!0,emptyValueText:"Not set",fields:[{name:"id",label:"User ID",type:"number",colSize:3},{name:"display_name",label:"Display Name",type:"text",format:'capitalize|default("Unnamed User")',colSize:9},{name:"username",label:"Username",type:"text",format:"lowercase",colSize:6},{name:"email",label:"Email Address",type:"email",colSize:6},{name:"phone_number",label:"Phone Number",type:"phone",format:'phone|default("Not provided")',colSize:6},{name:"is_active",label:"Account Status",type:"boolean",colSize:6},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6},{name:"avatar.url",label:"Avatar",type:"url",colSize:12},{name:"permissions",label:"User Permissions",type:"dataview",dataViewColumns:2,showEmptyValues:!1},{name:"metadata",label:"User Metadata",type:"dataview",dataViewColumns:1},{name:"avatar",label:"Avatar Details",type:"dataview",dataViewColumns:1}]},permissions:{title:"User Permissions",columns:1,fields:[{name:"display_name",label:"User",type:"text",format:"capitalize",columns:12},{name:"permissions",label:"Assigned Permissions",type:"dataview",dataViewColumns:3,showEmptyValues:!1,colSize:12}]},summary:{title:"User Summary",columns:3,fields:[{name:"display_name",label:"Name",type:"text",format:"capitalize|truncate(30)"},{name:"email",label:"Email",type:"email"},{name:"is_active",label:"Status",type:"boolean"},{name:"last_activity",label:"Last Seen",type:"datetime",format:"relative",colSize:12}]}};User.DATA_VIEW=C.detailed,User.EDIT_FORM=v.edit,User.ADD_FORM=v.create;class UserDevice extends Model{constructor(e={}){super(e,{endpoint:"/api/user/device"})}static async getByDuid(e){const t=new UserDevice,s=await t.rest.GET("/api/user/device/lookup",{duid:e});return s.success&&s.data&&s.data.data?new UserDevice(s.data.data):null}}class UserDeviceList extends Collection{constructor(e={}){super({ModelClass:UserDevice,endpoint:"/api/user/device",...e})}}class UserDeviceLocation extends Model{constructor(e={}){super(e,{endpoint:"/api/user/device/location"})}}class UserDeviceLocationList extends Collection{constructor(e={}){super({ModelClass:UserDeviceLocation,endpoint:"/api/user/device/location",...e})}}export{Collection as C,m as E,GroupList as G,Model as M,y as T,User as U,View as V,p as a,UserList as b,UserDeviceLocationList as c,e as d,UserDeviceList as e,MOJOUtils as f,Group as g,w as h,EventDelegate as i,S as j,b as k,C as l,UserDevice as m,UserDeviceLocation as n,v as o,A as p,g as r};
2
- //# sourceMappingURL=User-8dMZt39a.js.map