web-mojo 2.5.5 → 2.5.6
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.
- package/CHANGELOG.md +27 -0
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/ChatView-3d50GW02.js +2 -0
- package/dist/chunks/ChatView-3d50GW02.js.map +1 -0
- package/dist/chunks/ChatView-C27ckVwL.js +2 -0
- package/dist/chunks/ChatView-C27ckVwL.js.map +1 -0
- package/dist/chunks/{ListView-BxxGqz7q.js → ListView-C-jiqALE.js} +2 -2
- package/dist/chunks/{ListView-Wpby1PCA.js.map → ListView-C-jiqALE.js.map} +1 -1
- package/dist/chunks/{ListView-Wpby1PCA.js → ListView-zpCxyyjq.js} +2 -2
- package/dist/chunks/{ListView-BxxGqz7q.js.map → ListView-zpCxyyjq.js.map} +1 -1
- package/dist/chunks/{Passkeys-C2VoBjDn.js → Passkeys-B4bndv5b.js} +2 -2
- package/dist/chunks/{Passkeys-C2VoBjDn.js.map → Passkeys-B4bndv5b.js.map} +1 -1
- package/dist/chunks/{Passkeys-eZrcSyHX.js → Passkeys-CIhIxwb2.js} +2 -2
- package/dist/chunks/{Passkeys-eZrcSyHX.js.map → Passkeys-CIhIxwb2.js.map} +1 -1
- package/dist/chunks/{UserProfileView-CXJKMGy6.js → UserProfileView-B_HnFtsf.js} +2 -2
- package/dist/chunks/{UserProfileView-CXJKMGy6.js.map → UserProfileView-B_HnFtsf.js.map} +1 -1
- package/dist/chunks/{UserProfileView-D3MGkVVN.js → UserProfileView-DugtA_qG.js} +2 -2
- package/dist/chunks/{UserProfileView-D3MGkVVN.js.map → UserProfileView-DugtA_qG.js.map} +1 -1
- package/dist/chunks/{index-Dc0tdf4C.js → index-D-bZ5zeg.js} +2 -2
- package/dist/chunks/{index-Dc0tdf4C.js.map → index-D-bZ5zeg.js.map} +1 -1
- package/dist/chunks/{index-51V5UL7N.js → index-D-gO-M9M.js} +2 -2
- package/dist/chunks/{index-51V5UL7N.js.map → index-D-gO-M9M.js.map} +1 -1
- package/dist/chunks/{version-b3ZqvsDw.js → version-B1TH_fkK.js} +2 -2
- package/dist/chunks/{version-b3ZqvsDw.js.map → version-B1TH_fkK.js.map} +1 -1
- package/dist/chunks/{version-CcV-NTY8.js → version-DlfxFCfQ.js} +2 -2
- package/dist/chunks/{version-CcV-NTY8.js.map → version-DlfxFCfQ.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/user-profile.cjs.js +1 -1
- package/dist/user-profile.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +8 -0
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +1 -1
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/ChatView-Ctjijnsd.js +0 -2
- package/dist/chunks/ChatView-Ctjijnsd.js.map +0 -1
- package/dist/chunks/ChatView-cPwjzX7r.js +0 -2
- package/dist/chunks/ChatView-cPwjzX7r.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Passkeys-C2VoBjDn.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 return permissions[permission] == 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 point (empty by default) ──────────────────\n// Mutate (push, splice) this array, then call Member.rebuildPermissions()\n// to refresh the cached field arrays the UI reads from.\nMember.APP_PERMISSIONS = [];\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\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\n// arrays (BASE_PERMISSIONS + APP_PERMISSIONS). Idempotent. Mutates\n// caches in place so existing references stay live.\nMember.rebuildPermissions = function() {\n Member.PERMISSIONS.length = 0;\n Member.PERMISSIONS.push(...Member.BASE_PERMISSIONS, ...Member.APP_PERMISSIONS);\n\n Member.PERMISSION_FIELDS.length = 0;\n Member.PERMISSION_FIELDS.push(...Member.PERMISSIONS.map(_permSwitch));\n};\n\n// Initial population — produces the same flat list as before.\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","fetchForGroup","groupId","fetch","url","MemberList","MemberForms","edit","title","fields","name","type","label","placeholder","columns","BASE_PERMISSIONS","APP_PERMISSIONS","PERMISSIONS","PERMISSION_FIELDS","_permSwitch","tooltip","rebuildPermissions","length","push","map","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","startsWith","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
|
+
{"version":3,"file":"Passkeys-B4bndv5b.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 return permissions[permission] == 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 point (empty by default) ──────────────────\n// Mutate (push, splice) this array, then call Member.rebuildPermissions()\n// to refresh the cached field arrays the UI reads from.\nMember.APP_PERMISSIONS = [];\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\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\n// arrays (BASE_PERMISSIONS + APP_PERMISSIONS). Idempotent. Mutates\n// caches in place so existing references stay live.\nMember.rebuildPermissions = function() {\n Member.PERMISSIONS.length = 0;\n Member.PERMISSIONS.push(...Member.BASE_PERMISSIONS, ...Member.APP_PERMISSIONS);\n\n Member.PERMISSION_FIELDS.length = 0;\n Member.PERMISSION_FIELDS.push(...Member.PERMISSIONS.map(_permSwitch));\n};\n\n// Initial population — produces the same flat list as before.\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","fetchForGroup","groupId","fetch","url","MemberList","MemberForms","edit","title","fields","name","type","label","placeholder","columns","BASE_PERMISSIONS","APP_PERMISSIONS","PERMISSIONS","PERMISSION_FIELDS","_permSwitch","tooltip","rebuildPermissions","length","push","map","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","startsWith","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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"}
|