web-mojo 2.2.68 → 2.2.70
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 +25 -9
- 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.cjs.js.map +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/charts.es.js.map +1 -1
- package/dist/chunks/ChatView-DH42WXgV.js +2 -0
- package/dist/chunks/ChatView-DH42WXgV.js.map +1 -0
- package/dist/chunks/ChatView-_8eQTETQ.js +2 -0
- package/dist/chunks/ChatView-_8eQTETQ.js.map +1 -0
- package/dist/chunks/Collection-BUv4E9op.js +2 -0
- package/dist/chunks/Collection-BUv4E9op.js.map +1 -0
- package/dist/chunks/Collection-r1ACzUeh.js +2 -0
- package/dist/chunks/Collection-r1ACzUeh.js.map +1 -0
- package/dist/chunks/ContextMenu-BFxliZ03.js +2 -0
- package/dist/chunks/{ContextMenu-8vTiZZQV.js.map → ContextMenu-BFxliZ03.js.map} +1 -1
- package/dist/chunks/ContextMenu-BwJJ4QJE.js +2 -0
- package/dist/chunks/{ContextMenu-DBw0WMTO.js.map → ContextMenu-BwJJ4QJE.js.map} +1 -1
- package/dist/chunks/DataView-DMpNXerv.js +2 -0
- package/dist/chunks/{DataView-DyJKgOn3.js.map → DataView-DMpNXerv.js.map} +1 -1
- package/dist/chunks/DataView-_CACqzRt.js +2 -0
- package/dist/chunks/{DataView-BEovBggn.js.map → DataView-_CACqzRt.js.map} +1 -1
- package/dist/chunks/Dialog-BVCCpLPw.js +3 -0
- package/dist/chunks/Dialog-BVCCpLPw.js.map +1 -0
- package/dist/chunks/Dialog-BYiynSW-.js +3 -0
- package/dist/chunks/Dialog-BYiynSW-.js.map +1 -0
- package/dist/chunks/FormView-Dw7HDwzy.js +3 -0
- package/dist/chunks/{FormView-Q_lFA0nr.js.map → FormView-Dw7HDwzy.js.map} +1 -1
- package/dist/chunks/FormView-OgrZ7x0z.js +3 -0
- package/dist/chunks/{FormView-EoB_ZdIB.js.map → FormView-OgrZ7x0z.js.map} +1 -1
- package/dist/chunks/ListView-2M4I8KHF.js +2 -0
- package/dist/chunks/{ListView-CMZpwyyC.js.map → ListView-2M4I8KHF.js.map} +1 -1
- package/dist/chunks/ListView-B0QbqSPv.js +2 -0
- package/dist/chunks/{ListView-BLFFK_Ir.js.map → ListView-B0QbqSPv.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-DDdDJQFA.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-B0kWK-Js.js.map → MetricsCountryMapView-DDdDJQFA.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-DIlezla0.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-DuBKO7gz.js.map → MetricsCountryMapView-DIlezla0.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-Dt2V0eXP.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-ukn-NRMR.js.map → MetricsMiniChartWidget-Dt2V0eXP.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-_N4kzNY_.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-lzq4lSTF.js.map → MetricsMiniChartWidget-_N4kzNY_.js.map} +1 -1
- package/dist/chunks/PDFViewer-BruR1RFn.js +2 -0
- package/dist/chunks/{PDFViewer-sFoyopz3.js.map → PDFViewer-BruR1RFn.js.map} +1 -1
- package/dist/chunks/PDFViewer-CyGFVcvX.js +2 -0
- package/dist/chunks/{PDFViewer-iOqYpg-6.js.map → PDFViewer-CyGFVcvX.js.map} +1 -1
- package/dist/chunks/TableView-CxYpxZvr.js +2 -0
- package/dist/chunks/TableView-CxYpxZvr.js.map +1 -0
- package/dist/chunks/TableView-DemRVhnX.js +2 -0
- package/dist/chunks/TableView-DemRVhnX.js.map +1 -0
- package/dist/chunks/TokenManager-BFaxNsXO.js +2 -0
- package/dist/chunks/{TokenManager-DKzxBt6g.js.map → TokenManager-BFaxNsXO.js.map} +1 -1
- package/dist/chunks/TokenManager-IlBEFXqZ.js +2 -0
- package/dist/chunks/{TokenManager-ChNOca0K.js.map → TokenManager-IlBEFXqZ.js.map} +1 -1
- package/dist/chunks/UserProfileView-9vkfCPsp.js +2 -0
- package/dist/chunks/UserProfileView-9vkfCPsp.js.map +1 -0
- package/dist/chunks/UserProfileView-tcBT6XcE.js +2 -0
- package/dist/chunks/UserProfileView-tcBT6XcE.js.map +1 -0
- package/dist/chunks/WebApp-BFR1zozS.js +2 -0
- package/dist/chunks/{WebApp-B0m6JCjO.js.map → WebApp-BFR1zozS.js.map} +1 -1
- package/dist/chunks/WebApp-C82womPC.js +2 -0
- package/dist/chunks/{WebApp-Bsic6FPo.js.map → WebApp-C82womPC.js.map} +1 -1
- package/dist/chunks/WebSocketClient-Ibi7mLQu.js +2 -0
- package/dist/chunks/{WebSocketClient-Bh0Mmtje.js.map → WebSocketClient-Ibi7mLQu.js.map} +1 -1
- package/dist/chunks/WebSocketClient-QaCUN3EQ.js +2 -0
- package/dist/chunks/{WebSocketClient-CLgYPxWX.js.map → WebSocketClient-QaCUN3EQ.js.map} +1 -1
- package/dist/chunks/index-BaPQHxbL.js +2 -0
- package/dist/chunks/index-BaPQHxbL.js.map +1 -0
- package/dist/chunks/index-BdfwxVMZ.js +2 -0
- package/dist/chunks/index-BdfwxVMZ.js.map +1 -0
- package/dist/chunks/{version-i7K_82Qy.js → version-C2yYRyPn.js} +2 -2
- package/dist/chunks/{version-i7K_82Qy.js.map → version-C2yYRyPn.js.map} +1 -1
- package/dist/chunks/{version-BmVUtM_7.js → version-CaiqhdME.js} +2 -2
- package/dist/chunks/{version-BmVUtM_7.js.map → version-CaiqhdME.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/lightbox.es.js.map +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.cjs.js.map +1 -1
- package/dist/map.es.js +1 -1
- package/dist/map.es.js.map +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.cjs.js.map +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/timeline.es.js.map +1 -1
- package/dist/user-profile.cjs.js +2 -0
- package/dist/user-profile.cjs.js.map +1 -0
- package/dist/user-profile.es.js +2 -0
- package/dist/user-profile.es.js.map +1 -0
- package/dist/web-mojo.lite.iife.js +5436 -5433
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +76 -76
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunks/ChatView-Cfe0ZGvr.js +0 -2
- package/dist/chunks/ChatView-Cfe0ZGvr.js.map +0 -1
- package/dist/chunks/ChatView-DuQVFrCY.js +0 -2
- package/dist/chunks/ChatView-DuQVFrCY.js.map +0 -1
- package/dist/chunks/Collection-BWKmydl5.js +0 -2
- package/dist/chunks/Collection-BWKmydl5.js.map +0 -1
- package/dist/chunks/Collection-CmjTsmrP.js +0 -2
- package/dist/chunks/Collection-CmjTsmrP.js.map +0 -1
- package/dist/chunks/ContextMenu-8vTiZZQV.js +0 -2
- package/dist/chunks/ContextMenu-DBw0WMTO.js +0 -2
- package/dist/chunks/DataView-BEovBggn.js +0 -2
- package/dist/chunks/DataView-DyJKgOn3.js +0 -2
- package/dist/chunks/Dialog-DW7PHzUc.js +0 -2
- package/dist/chunks/Dialog-DW7PHzUc.js.map +0 -1
- package/dist/chunks/Dialog-jfBsXy5X.js +0 -2
- package/dist/chunks/Dialog-jfBsXy5X.js.map +0 -1
- package/dist/chunks/Files-C-ChBvr5.js +0 -2
- package/dist/chunks/Files-C-ChBvr5.js.map +0 -1
- package/dist/chunks/Files-DNbHDy43.js +0 -2
- package/dist/chunks/Files-DNbHDy43.js.map +0 -1
- package/dist/chunks/FormView-EoB_ZdIB.js +0 -3
- package/dist/chunks/FormView-Q_lFA0nr.js +0 -3
- package/dist/chunks/ListView-BLFFK_Ir.js +0 -2
- package/dist/chunks/ListView-CMZpwyyC.js +0 -2
- package/dist/chunks/MetricsCountryMapView-B0kWK-Js.js +0 -2
- package/dist/chunks/MetricsCountryMapView-DuBKO7gz.js +0 -2
- package/dist/chunks/MetricsMiniChartWidget-lzq4lSTF.js +0 -2
- package/dist/chunks/MetricsMiniChartWidget-ukn-NRMR.js +0 -2
- package/dist/chunks/PDFViewer-iOqYpg-6.js +0 -2
- package/dist/chunks/PDFViewer-sFoyopz3.js +0 -2
- package/dist/chunks/Rest-B1eUyLX5.js +0 -2
- package/dist/chunks/Rest-B1eUyLX5.js.map +0 -1
- package/dist/chunks/Rest-BJ3Mvx1L.js +0 -2
- package/dist/chunks/Rest-BJ3Mvx1L.js.map +0 -1
- package/dist/chunks/TokenManager-ChNOca0K.js +0 -2
- package/dist/chunks/TokenManager-DKzxBt6g.js +0 -2
- package/dist/chunks/User-BnlvMG5J.js +0 -3
- package/dist/chunks/User-BnlvMG5J.js.map +0 -1
- package/dist/chunks/User-DSqcOwPL.js +0 -3
- package/dist/chunks/User-DSqcOwPL.js.map +0 -1
- package/dist/chunks/WebApp-B0m6JCjO.js +0 -2
- package/dist/chunks/WebApp-Bsic6FPo.js +0 -2
- package/dist/chunks/WebSocketClient-Bh0Mmtje.js +0 -2
- package/dist/chunks/WebSocketClient-CLgYPxWX.js +0 -2
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Files-DNbHDy43.js","sources":["../../src/core/views/feedback/ProgressView.js","../../src/core/services/FileUpload.js","../../src/core/models/Files.js"],"sourcesContent":["/**\n * ProgressView - File upload progress component\n * \n * Shows upload progress with progress bar, filename, and cancellation option\n * Integrates with FileUpload service for real-time progress updates\n * \n * Features:\n * - Bootstrap progress bar with percentage\n * - File information (name, size)\n * - Bytes uploaded/total display\n * - Cancel button with confirmation\n * - Responsive design\n * \n * Events:\n * - 'cancel' - Emitted when user cancels upload\n * \n * @example\n * const progressView = new ProgressView({\n * filename: 'document.pdf',\n * filesize: 1024000,\n * onCancel: () => fileUpload.cancel()\n * });\n * \n * // Update progress\n * progressView.updateProgress({ progress: 0.5, loaded: 512000, total: 1024000 });\n */\n\nimport View from '@core/View.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass ProgressView extends View {\n constructor(options = {}) {\n super({\n template: 'progress-view-template',\n ...options\n });\n\n // Initialize progress data\n this.filename = options.filename || 'Unknown file';\n this.filesize = options.filesize || 0;\n this.filesizeFormatted = dataFormatter.pipe(this.filesize, 'filesize');\n \n // Progress state\n this.progress = 0;\n this.percentage = 0;\n this.loaded = 0;\n this.total = this.filesize;\n this.loadedFormatted = '0 B';\n this.totalFormatted = this.filesizeFormatted;\n this.status = 'Starting upload...';\n \n // Options\n this.showCancel = options.showCancel !== false;\n this.onCancel = options.onCancel || null;\n \n // State\n this.cancelled = false;\n this.completed = false;\n }\n\n /**\n * Get template for the progress view\n */\n getTemplate() {\n return `\n <div class=\"progress-view\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div class=\"flex-grow-1 min-width-0\">\n <div class=\"fw-medium text-truncate\" title=\"{{filename}}\">\n <i class=\"bi bi-file-earmark me-1\"></i>\n {{filename}}\n </div>\n <small class=\"text-muted\">{{status}}</small>\n </div>\n {{#showCancel}}\n <button type=\"button\" \n class=\"btn btn-sm btn-outline-secondary ms-2\" \n data-action=\"cancel\"\n {{#cancelled}}disabled{{/cancelled}}>\n <i class=\"bi bi-x\"></i>\n </button>\n {{/showCancel}}\n </div>\n \n <div class=\"progress mb-2\" style=\"height: 8px;\">\n <div class=\"progress-bar\" \n role=\"progressbar\" \n style=\"width: {{percentage}}%\"\n aria-valuenow=\"{{percentage}}\" \n aria-valuemin=\"0\" \n aria-valuemax=\"100\">\n </div>\n </div>\n \n <div class=\"d-flex justify-content-between\">\n <small class=\"text-muted\">\n {{loadedFormatted}} / {{totalFormatted}}\n </small>\n <small class=\"text-muted\">\n {{percentage}}%\n </small>\n </div>\n </div>\n `;\n }\n\n /**\n * Update progress information\n * @param {Object} progressInfo - Progress data\n * @param {number} progressInfo.progress - Progress as decimal (0-1)\n * @param {number} progressInfo.loaded - Bytes loaded\n * @param {number} progressInfo.total - Total bytes\n * @param {number} progressInfo.percentage - Progress as percentage (0-100)\n */\n updateProgress(progressInfo) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n this.progress = progressInfo.progress;\n this.percentage = progressInfo.percentage;\n this.loaded = progressInfo.loaded;\n this.total = progressInfo.total || this.filesize;\n \n // Format bytes for display\n this.loadedFormatted = dataFormatter.pipe(this.loaded, 'filesize');\n this.totalFormatted = dataFormatter.pipe(this.total, 'filesize');\n \n // Update status message\n if (this.percentage < 100) {\n this.status = `Uploading... ${this.percentage}%`;\n } else {\n this.status = 'Finalizing upload...';\n }\n\n // Re-render to show updated progress\n this.render();\n }\n\n /**\n * Mark upload as completed\n * @param {string} message - Success message\n */\n markCompleted(message = 'Upload completed!') {\n this.completed = true;\n this.progress = 1;\n this.percentage = 100;\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as failed\n * @param {string} message - Error message\n */\n markFailed(message = 'Upload failed') {\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as cancelled\n */\n markCancelled() {\n this.cancelled = true;\n this.status = 'Upload cancelled';\n this.render();\n }\n\n /**\n * Handle cancel button click\n * @param {string} action - Action name\n * @param {Event} event - Click event\n * @param {Element} element - Button element\n */\n async onActionCancel(action, event, element) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n // Disable button immediately\n element.disabled = true;\n \n // Mark as cancelled\n this.markCancelled();\n \n // Emit cancel event\n this.emit('cancel');\n \n // Call cancel callback if provided\n if (typeof this.onCancel === 'function') {\n try {\n await this.onCancel();\n } catch (error) {\n console.error('Error in cancel callback:', error);\n }\n }\n }\n\n /**\n * Set filename\n * @param {string} filename - New filename\n */\n setFilename(filename) {\n this.filename = filename;\n this.render();\n }\n\n /**\n * Set file size\n * @param {number} size - File size in bytes\n */\n setFilesize(size) {\n this.filesize = size;\n this.filesizeFormatted = dataFormatter.pipe(size, 'filesize');\n this.total = size;\n this.totalFormatted = this.filesizeFormatted;\n this.render();\n }\n\n /**\n * Get current progress as percentage\n * @returns {number} Progress percentage (0-100)\n */\n getPercentage() {\n return this.percentage;\n }\n\n /**\n * Check if upload is completed\n * @returns {boolean} True if completed\n */\n isCompleted() {\n return this.completed;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.filename,\n filesize: this.filesize,\n progress: this.progress,\n percentage: this.percentage,\n loaded: this.loaded,\n total: this.total,\n cancelled: this.cancelled,\n completed: this.completed,\n status: this.status\n };\n }\n}\n\nexport default ProgressView;","/**\n * FileUpload - File upload service with progress tracking and UI integration\n *\n * Features:\n * - Auto-start upload process\n * - Progress tracking with detailed information\n * - Promise interface with cancellation support\n * - Toast integration for user feedback\n * - Three-stage upload process (initiate → upload → complete)\n *\n * @example\n * const file = new File();\n * const upload = file.upload({\n * file: fileObject,\n * name: 'avatar.jpg',\n * group: 'profile-pics',\n * description: 'User avatar',\n * onProgress: ({ progress, loaded, total, percentage }) => {\n * console.log(`${percentage}% complete`);\n * }\n * });\n *\n * upload.then(result => console.log('Success!'))\n * .catch(error => console.error('Failed:', error));\n */\n\nimport ToastService from './ToastService.js';\nimport ProgressView from '../views/feedback/ProgressView.js';\n\nclass FileUpload {\n constructor(fileModel, options = {}) {\n this.fileModel = fileModel;\n this.options = {\n file: null,\n name: null,\n group: null,\n description: null,\n onProgress: null,\n onComplete: null,\n onError: null,\n showToast: true,\n ...options\n };\n\n // Validation\n if (!this.options.file || !(this.options.file instanceof File)) {\n throw new Error('FileUpload requires a valid File object');\n }\n\n // State management\n this.cancelled = false;\n this.uploadRequest = null;\n this.progressToast = null;\n this.progressView = null;\n this.toastService = null;\n\n // Initialize toast service if needed\n if (this.options.showToast) {\n this.toastService = new ToastService();\n }\n\n // Auto-start upload (Option 3 behavior)\n this.promise = this._startUpload();\n }\n\n /**\n * Main upload orchestration\n * @returns {Promise} Upload promise\n * @private\n */\n async _startUpload() {\n try {\n if (this.options.showToast) {\n this._showProgressToast();\n }\n\n // Stage 1: Initiate upload and get signed URL\n let uploadData;\n try {\n uploadData = await this._initiateUpload();\n } catch (error) {\n throw new Error(`Failed to initiate upload: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Validate upload data\n if (!uploadData || !uploadData.upload_url) {\n throw new Error('Invalid upload response: missing upload URL');\n }\n\n // Normalise upload_url — the backend can return either:\n // • a plain string → direct PUT with raw bytes (e.g. S3 signed URL)\n // • a config object → POST multipart/form-data (e.g. filesystem backend)\n // { upload_url: string, method: string, fields: object, headers: object }\n let uploadConfig;\n if (typeof uploadData.upload_url === 'string') {\n uploadConfig = {\n url: uploadData.upload_url,\n method: 'PUT',\n fields: null,\n headers: {}\n };\n } else if (uploadData.upload_url && typeof uploadData.upload_url === 'object'\n && uploadData.upload_url.upload_url) {\n uploadConfig = {\n url: uploadData.upload_url.upload_url,\n method: uploadData.upload_url.method || 'POST',\n fields: uploadData.upload_url.fields || null,\n headers: uploadData.upload_url.headers || {}\n };\n } else {\n throw new Error(\n `Invalid upload response: unrecognised upload_url format. ` +\n `Server returned: ${JSON.stringify(uploadData.upload_url)}`\n );\n }\n\n // Stage 2: Upload file to signed URL / endpoint\n let result;\n try {\n result = await this._performUpload(uploadConfig);\n } catch (error) {\n throw new Error(`File upload failed: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Stage 3: Mark upload as completed\n try {\n await this._completeUpload();\n } catch (error) {\n console.warn('Failed to mark upload as completed:', error);\n // Don't fail the entire upload for completion marking errors\n // The file was successfully uploaded\n }\n\n // Handle success\n this._onComplete(this.fileModel);\n return this.fileModel;\n\n } catch (error) {\n if (error.message !== 'Upload cancelled') {\n this._onError(error);\n }\n throw error;\n }\n }\n\n /**\n * Initiate upload by calling the API to get signed URL\n * @returns {Promise<Object>} Upload initiation data\n * @private\n */\n async _initiateUpload() {\n try {\n const payload = {\n filename: this.options.name || this.options.file.name,\n file_size: this.options.file.size,\n content_type: this.options.file.type,\n };\n\n if (this.options.group) payload.group = this.options.group;\n if (this.options.description) payload.description = this.options.description;\n\n const response = await this.fileModel.rest.POST('/api/fileman/upload/initiate', payload);\n\n if (!response) {\n throw new Error('No response from upload initiation API');\n }\n\n if (!response.data) {\n throw new Error('Upload initiation response missing data');\n }\n\n // Check server response first (prefer server error messages)\n if (!response.data.status) {\n const errorMessage = response.data.error || 'Upload initiation failed';\n throw new Error(errorMessage);\n }\n\n if (!response.data.data) {\n throw new Error('Upload initiation response missing data payload');\n }\n\n // Set model ID for completion step\n if (response.data.data.id) {\n this.fileModel.set('id', response.data.data.id);\n }\n\n return response.data.data; // { id, upload_url }\n\n } catch (error) {\n // Re-throw with more context if it's a generic error\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload initiation. Please check your connection.');\n }\n throw error;\n }\n }\n\n /**\n * Upload file using the normalised upload config from _startUpload.\n *\n * Two dispatch paths:\n * - PUT + raw bytes → legacy plain-string upload_url, or any config with method PUT.\n * Used by backends that issue a direct signed PUT URL (e.g. S3 signed URL).\n * - POST + FormData → config object with method POST and optional fields dict.\n * Used by the local filesystem backend and S3 presigned POST.\n * fields are appended first, then the file as the \"file\" key,\n * matching Django's request.FILES['file'] convention.\n * Content-Type is intentionally NOT set manually so the browser\n * can write the correct multipart boundary.\n *\n * @param {{ url: string, method: string, fields: object|null, headers: object }} uploadConfig\n * @returns {Promise} Upload result\n * @private\n */\n async _performUpload(uploadConfig) {\n return new Promise((resolve, reject) => {\n if (!(this.options.file instanceof File)) {\n reject(new Error('Only single File objects are supported'));\n return;\n }\n\n const { url, method, fields, headers } = uploadConfig;\n const useFormData = method === 'POST' && fields !== null;\n\n const xhr = new XMLHttpRequest();\n this.uploadRequest = xhr;\n\n // Progress tracking\n xhr.upload.onprogress = (event) => {\n if (this.cancelled) return;\n this._onProgress({\n progress: event.loaded / event.total,\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100)\n });\n };\n\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({ data: xhr.response, status: xhr.status, statusText: xhr.statusText, xhr });\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n };\n\n xhr.onerror = () => reject(new Error('Upload failed: Network error'));\n xhr.ontimeout = () => reject(new Error('Upload timed out — file may be too large or connection too slow'));\n xhr.onabort = () => reject(new Error('Upload cancelled'));\n\n // Relative URLs must be prefixed with /api/ so Django's URL router picks them\n // up correctly, then resolved through the REST service so they target the\n // configured API host rather than the browser's current web host.\n // Absolute URLs (http/https) are returned unchanged — needed for S3 etc.\n let apiUrl = url;\n if (url.startsWith('/') && !url.startsWith('/api/')) {\n apiUrl = '/api' + url;\n }\n const resolvedUrl = this.fileModel.rest.buildUrl(apiUrl);\n xhr.open(method, resolvedUrl);\n xhr.timeout = 30000;\n\n if (useFormData) {\n // POST multipart/form-data — filesystem backend and S3 presigned POST.\n // Any extra headers from the config (e.g. x-amz-* for S3) are set here,\n // but Content-Type is deliberately skipped so the browser sets the boundary.\n for (const [key, value] of Object.entries(headers || {})) {\n if (key.toLowerCase() !== 'content-type') {\n xhr.setRequestHeader(key, value);\n }\n }\n\n const formData = new FormData();\n for (const [key, value] of Object.entries(fields)) {\n formData.append(key, value);\n }\n // File must be last — required by S3 presigned POST policy; harmless for Django.\n formData.append('file', this.options.file);\n xhr.send(formData);\n\n } else {\n // PUT raw bytes — legacy / direct signed URL path.\n xhr.setRequestHeader('Content-Type', this.options.file.type);\n for (const [key, value] of Object.entries(headers || {})) {\n if (key.toLowerCase() !== 'content-type') {\n xhr.setRequestHeader(key, value);\n }\n }\n xhr.send(this.options.file);\n }\n });\n }\n\n /**\n * Mark upload as completed in the API\n * @returns {Promise} Completion result\n * @private\n */\n async _completeUpload() {\n try {\n const response = await this.fileModel.save({ action: 'mark_as_completed' });\n\n if (!response) {\n throw new Error('No response from upload completion API');\n }\n\n // Check server response format (prefer server errors over HTTP errors)\n if (response.data && !response.data.status) {\n const errorMessage = response.data.error || 'Failed to mark upload as completed';\n throw new Error(errorMessage);\n }\n\n return response;\n } catch (error) {\n // Re-throw with more context\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload completion. The file may have uploaded successfully.');\n }\n throw error;\n }\n }\n\n /**\n * Handle progress updates\n * @param {Object} progressInfo - Progress information\n * @private\n */\n _onProgress(progressInfo) {\n // Update progress toast if shown\n if (this.progressToast && this.progressToast.updateProgress) {\n this.progressToast.updateProgress(progressInfo);\n }\n\n // Call user-provided progress callback\n if (typeof this.options.onProgress === 'function') {\n this.options.onProgress(progressInfo);\n }\n }\n\n /**\n * Handle successful upload completion\n * @param {Object} result - Upload result\n * @private\n */\n _onComplete(result) {\n // Mark progress view as completed\n if (this.progressView) {\n this.progressView.markCompleted('Upload completed successfully!');\n }\n\n // Auto-hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast:', error);\n }\n }, 2000);\n }\n\n // Call user-provided completion callback\n if (typeof this.options.onComplete === 'function') {\n this.options.onComplete(result);\n }\n }\n\n /**\n * Handle upload errors\n * @param {Error} error - Error object\n * @private\n */\n _onError(error) {\n // Hide progress toast immediately and show error toast\n if (this.progressToast) {\n try {\n this.progressToast.hide();\n } catch (error) {\n console.warn('Error hiding progress toast on error:', error);\n }\n }\n\n // Show error toast with immediate feedback\n if (this.toastService) {\n this.toastService.error(`Upload failed: ${error.message}`);\n }\n\n // Call user-provided error callback\n if (typeof this.options.onError === 'function') {\n this.options.onError(error);\n }\n }\n\n /**\n * Show progress toast with ProgressView component\n * @private\n */\n _showProgressToast() {\n // Create progress view with file information\n this.progressView = new ProgressView({\n filename: this.options.name || this.options.file.name,\n filesize: this.options.file.size,\n showCancel: true,\n onCancel: () => this.cancel()\n });\n\n // Show progress view in toast\n this.progressToast = this.toastService.showView(this.progressView, 'info', {\n title: 'File Upload',\n autohide: false,\n dismissible: false\n });\n }\n\n /**\n * Cancel the upload\n * @returns {boolean} True if cancelled, false if already completed\n */\n cancel() {\n if (this.cancelled) {\n return false;\n }\n\n this.cancelled = true;\n\n // Cancel the upload request if in progress\n if (this.uploadRequest && typeof this.uploadRequest.abort === 'function') {\n this.uploadRequest.abort();\n }\n\n // Mark progress view as cancelled\n if (this.progressView) {\n this.progressView.markCancelled();\n }\n\n // Hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast on cancel:', error);\n }\n }, 1500);\n }\n\n return true;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Promise interface - then\n * @param {function} onSuccess - Success handler\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n then(onSuccess, onError) {\n return this.promise.then(onSuccess, onError);\n }\n\n /**\n * Promise interface - catch\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n catch(onError) {\n return this.promise.catch(onError);\n }\n\n /**\n * Promise interface - finally\n * @param {function} onFinally - Finally handler\n * @returns {Promise} Promise chain\n */\n finally(onFinally) {\n return this.promise.finally(onFinally);\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.options.file.name,\n size: this.options.file.size,\n type: this.options.file.type,\n cancelled: this.cancelled,\n group: this.options.group,\n description: this.options.description\n };\n }\n}\n\nexport default FileUpload;\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport FileUpload from '@core/services/FileUpload.js';\nimport {UserList} from '@core/models/User.js';\nimport {GroupList} from '@core/models/Group.js';\n/* =========================\n * FileManager\n * ========================= */\nclass FileManager extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/manager',\n });\n }\n}\n\nclass FileManagerList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: FileManager,\n endpoint: '/api/fileman/manager',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileManagerForms = {\n create: {\n title: 'Add Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'use',\n type: 'text',\n label: 'Use',\n placeholder: 'Enter User or Leave Blank',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n value: \"s3://BUCKET_NAME/OPTION_FOLDER\",\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n },\n ],\n },\n\n edit: {\n title: 'Edit Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'use',\n type: 'text',\n label: 'Use',\n placeholder: 'Enter User or Leave Blank',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'allowed_origins',\n type: 'text',\n label: 'Domains Who Can Upload',\n cols: 12,\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n },\n {\n name: 'is_public',\n type: 'switch',\n label: 'Is Public',\n default: true,\n cols: 6,\n }\n ],\n },\n\n owners: {\n fields: [\n {\n type: 'collection',\n name: 'group',\n label: 'Group (Owner)',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n {\n type: 'collection',\n name: 'user',\n label: 'User (Owner)',\n Collection: UserList, // Collection class\n labelField: 'display_name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search users...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n ],\n },\n\n credentials: {\n fields: [\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n ]\n }\n};\n\n/* =========================\n * File\n * ========================= */\nclass File extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/file',\n });\n }\n\n isImage() {\n return this.get(\"category\") === 'image';\n }\n\n /**\n * Upload file with progress tracking and UI integration\n * Returns a FileUpload instance with promise interface and cancellation support\n *\n * @param {object} options - Upload configuration\n * @param {File} options.file - File object to upload\n * @param {string} options.name - Custom filename (optional)\n * @param {string} options.group - File group/category (optional)\n * @param {string} options.description - File description (optional)\n * @param {function} options.onProgress - Progress callback ({ progress, loaded, total, percentage })\n * @param {function} options.onComplete - Success callback\n * @param {function} options.onError - Error callback\n * @param {boolean} options.showToast - Show progress toast (default: true)\n * @returns {FileUpload} Upload instance with promise interface\n */\n upload(options = {}) {\n return new FileUpload(this, options);\n }\n}\n\nclass FileList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: File,\n endpoint: '/api/fileman/file',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileForms = {\n create: {\n title: 'Add File',\n fields: [\n\n ],\n },\n\n edit: {\n title: 'Edit File Backend',\n fields: [\n\n ],\n },\n};\n\nexport {\n FileManager,\n FileManagerList,\n FileManagerForms,\n File,\n FileList,\n FileForms,\n};\n"],"names":["ProgressView","View","constructor","options","super","template","this","filename","filesize","filesizeFormatted","dataFormatter","pipe","progress","percentage","loaded","total","loadedFormatted","totalFormatted","status","showCancel","onCancel","cancelled","completed","getTemplate","updateProgress","progressInfo","render","markCompleted","message","markFailed","markCancelled","onActionCancel","action","event","element","disabled","emit","error","console","setFilename","setFilesize","size","getPercentage","isCompleted","isCancelled","getStats","FileUpload","fileModel","file","name","group","description","onProgress","onComplete","onError","showToast","File","Error","uploadRequest","progressToast","progressView","toastService","ToastService","promise","_startUpload","uploadData","uploadConfig","result","_showProgressToast","_initiateUpload","upload_url","url","method","fields","headers","JSON","stringify","_performUpload","_completeUpload","warn","_onComplete","_onError","payload","file_size","content_type","type","response","rest","POST","data","errorMessage","id","set","Promise","resolve","reject","useFormData","xhr","XMLHttpRequest","upload","onprogress","_onProgress","Math","round","onload","statusText","onerror","ontimeout","onabort","apiUrl","startsWith","resolvedUrl","buildUrl","open","timeout","key","value","Object","entries","toLowerCase","setRequestHeader","formData","FormData","append","send","save","setTimeout","hide","cancel","showView","title","autohide","dismissible","abort","then","onSuccess","catch","onFinally","finally","FileManager","Model","endpoint","FileManagerList","Collection","ModelClass","FileManagerForms","create","label","placeholder","cols","required","help","text","columns","default","edit","owners","GroupList","labelField","valueField","maxItems","emptyFetch","debounceMs","UserList","credentials","isImage","get","FileList","FileForms"],"mappings":"yHA8BA,MAAMA,qBAAqBC,EAAAA,KACvB,WAAAC,CAAYC,EAAU,IAClBC,MAAM,CACFC,SAAU,4BACPF,IAIPG,KAAKC,SAAWJ,EAAQI,UAAY,eACpCD,KAAKE,SAAWL,EAAQK,UAAY,EACpCF,KAAKG,kBAAoBC,EAAAA,cAAcC,KAAKL,KAAKE,SAAU,YAG3DF,KAAKM,SAAW,EAChBN,KAAKO,WAAa,EAClBP,KAAKQ,OAAS,EACdR,KAAKS,MAAQT,KAAKE,SAClBF,KAAKU,gBAAkB,MACvBV,KAAKW,eAAiBX,KAAKG,kBAC3BH,KAAKY,OAAS,qBAGdZ,KAAKa,YAAoC,IAAvBhB,EAAQgB,WAC1Bb,KAAKc,SAAWjB,EAAQiB,UAAY,KAGpCd,KAAKe,WAAY,EACjBf,KAAKgB,WAAY,CACrB,CAKA,WAAAC,GACI,MAAO,iuDAwCX,CAUA,cAAAC,CAAeC,GACPnB,KAAKe,WAAaf,KAAKgB,YAI3BhB,KAAKM,SAAWa,EAAab,SAC7BN,KAAKO,WAAaY,EAAaZ,WAC/BP,KAAKQ,OAASW,EAAaX,OAC3BR,KAAKS,MAAQU,EAAaV,OAAST,KAAKE,SAGxCF,KAAKU,gBAAkBN,EAAAA,cAAcC,KAAKL,KAAKQ,OAAQ,YACvDR,KAAKW,eAAiBP,EAAAA,cAAcC,KAAKL,KAAKS,MAAO,YAGjDT,KAAKO,WAAa,IAClBP,KAAKY,OAAS,gBAAgBZ,KAAKO,cAEnCP,KAAKY,OAAS,uBAIlBZ,KAAKoB,SACT,CAMA,aAAAC,CAAcC,EAAU,qBACpBtB,KAAKgB,WAAY,EACjBhB,KAAKM,SAAW,EAChBN,KAAKO,WAAa,IAClBP,KAAKY,OAASU,EACdtB,KAAKoB,QACT,CAMA,UAAAG,CAAWD,EAAU,iBACjBtB,KAAKY,OAASU,EACdtB,KAAKoB,QACT,CAKA,aAAAI,GACIxB,KAAKe,WAAY,EACjBf,KAAKY,OAAS,mBACdZ,KAAKoB,QACT,CAQA,oBAAMK,CAAeC,EAAQC,EAAOC,GAChC,IAAI5B,KAAKe,YAAaf,KAAKgB,YAK3BY,EAAQC,UAAW,EAGnB7B,KAAKwB,gBAGLxB,KAAK8B,KAAK,UAGmB,mBAAlB9B,KAAKc,UACZ,UACUd,KAAKc,UACf,OAASiB,GACLC,QAAQD,MAAM,4BAA6BA,EAC/C,CAER,CAMA,WAAAE,CAAYhC,GACRD,KAAKC,SAAWA,EAChBD,KAAKoB,QACT,CAMA,WAAAc,CAAYC,GACRnC,KAAKE,SAAWiC,EAChBnC,KAAKG,kBAAoBC,EAAAA,cAAcC,KAAK8B,EAAM,YAClDnC,KAAKS,MAAQ0B,EACbnC,KAAKW,eAAiBX,KAAKG,kBAC3BH,KAAKoB,QACT,CAMA,aAAAgB,GACI,OAAOpC,KAAKO,UAChB,CAMA,WAAA8B,GACI,OAAOrC,KAAKgB,SAChB,CAMA,WAAAsB,GACI,OAAOtC,KAAKe,SAChB,CAMA,QAAAwB,GACI,MAAO,CACHtC,SAAUD,KAAKC,SACfC,SAAUF,KAAKE,SACfI,SAAUN,KAAKM,SACfC,WAAYP,KAAKO,WACjBC,OAAQR,KAAKQ,OACbC,MAAOT,KAAKS,MACZM,UAAWf,KAAKe,UAChBC,UAAWhB,KAAKgB,UAChBJ,OAAQZ,KAAKY,OAErB,ECvOJ,MAAM4B,WACF,WAAA5C,CAAY6C,EAAW5C,EAAU,IAe7B,GAdAG,KAAKyC,UAAYA,EACjBzC,KAAKH,QAAU,CACX6C,KAAM,KACNC,KAAM,KACNC,MAAO,KACPC,YAAa,KACbC,WAAY,KACZC,WAAY,KACZC,QAAS,KACTC,WAAW,KACRpD,KAIFG,KAAKH,QAAQ6C,MAAU1C,KAAKH,QAAQ6C,gBAAgBQ,MACrD,MAAM,IAAIC,MAAM,2CAIpBnD,KAAKe,WAAY,EACjBf,KAAKoD,cAAgB,KACrBpD,KAAKqD,cAAgB,KACrBrD,KAAKsD,aAAe,KACpBtD,KAAKuD,aAAe,KAGhBvD,KAAKH,QAAQoD,YACbjD,KAAKuD,aAAe,IAAIC,gBAI5BxD,KAAKyD,QAAUzD,KAAK0D,cACxB,CAOA,kBAAMA,GACF,IAMI,IAAIC,EAoBAC,EAwBAC,EAjDA7D,KAAKH,QAAQoD,WACbjD,KAAK8D,qBAKT,IACIH,QAAmB3D,KAAK+D,iBAC5B,OAAShC,GACL,MAAM,IAAIoB,MAAM,8BAA8BpB,EAAMT,UACxD,CAEA,GAAItB,KAAKe,UACL,MAAM,IAAIoC,MAAM,oBAIpB,IAAKQ,IAAeA,EAAWK,WAC3B,MAAM,IAAIb,MAAM,+CAQpB,GAAqC,iBAA1BQ,EAAWK,WAClBJ,EAAe,CACXK,IAAKN,EAAWK,WAChBE,OAAQ,MACRC,OAAQ,KACRC,QAAS,CAAA,OAEjB,KAAWT,EAAWK,YAA+C,iBAA1BL,EAAWK,aACxCL,EAAWK,WAAWA,WAQhC,MAAM,IAAIb,MACN,6EACoBkB,KAAKC,UAAUX,EAAWK,eATlDJ,EAAe,CACXK,IAAKN,EAAWK,WAAWA,WAC3BE,OAAQP,EAAWK,WAAWE,QAAU,OACxCC,OAAQR,EAAWK,WAAWG,QAAU,KACxCC,QAAST,EAAWK,WAAWI,SAAW,CAAA,EAOlD,CAIA,IACIP,QAAe7D,KAAKuE,eAAeX,EACvC,OAAS7B,GACL,MAAM,IAAIoB,MAAM,uBAAuBpB,EAAMT,UACjD,CAEA,GAAItB,KAAKe,UACL,MAAM,IAAIoC,MAAM,oBAIpB,UACUnD,KAAKwE,iBACf,OAASzC,GACLC,QAAQyC,KAAK,sCAAuC1C,EAGxD,CAIA,OADA/B,KAAK0E,YAAY1E,KAAKyC,WACfzC,KAAKyC,SAEhB,OAASV,GAIL,KAHsB,qBAAlBA,EAAMT,SACNtB,KAAK2E,SAAS5C,GAEZA,CACV,CACJ,CAOA,qBAAMgC,GACF,IACI,MAAMa,EAAU,CACZ3E,SAAUD,KAAKH,QAAQ8C,MAAQ3C,KAAKH,QAAQ6C,KAAKC,KACjDkC,UAAW7E,KAAKH,QAAQ6C,KAAKP,KAC7B2C,aAAc9E,KAAKH,QAAQ6C,KAAKqC,MAGhC/E,KAAKH,QAAQ+C,QAAOgC,EAAQhC,MAAQ5C,KAAKH,QAAQ+C,OACjD5C,KAAKH,QAAQgD,cAAa+B,EAAQ/B,YAAc7C,KAAKH,QAAQgD,aAEjE,MAAMmC,QAAiBhF,KAAKyC,UAAUwC,KAAKC,KAAK,+BAAgCN,GAEhF,IAAKI,EACD,MAAM,IAAI7B,MAAM,0CAGpB,IAAK6B,EAASG,KACV,MAAM,IAAIhC,MAAM,2CAIpB,IAAK6B,EAASG,KAAKvE,OAAQ,CACvB,MAAMwE,EAAeJ,EAASG,KAAKpD,OAAS,2BAC5C,MAAM,IAAIoB,MAAMiC,EACpB,CAEA,IAAKJ,EAASG,KAAKA,KACf,MAAM,IAAIhC,MAAM,mDAQpB,OAJI6B,EAASG,KAAKA,KAAKE,IACnBrF,KAAKyC,UAAU6C,IAAI,KAAMN,EAASG,KAAKA,KAAKE,IAGzCL,EAASG,KAAKA,IAEzB,OAASpD,GAEL,GAAsB,kBAAlBA,EAAMT,SAA8C,cAAfS,EAAMY,KAC3C,MAAM,IAAIQ,MAAM,yEAEpB,MAAMpB,CACV,CACJ,CAmBA,oBAAMwC,CAAeX,GACjB,OAAO,IAAI2B,QAAQ,CAACC,EAASC,KACzB,KAAMzF,KAAKH,QAAQ6C,gBAAgBQ,MAE/B,YADAuC,EAAO,IAAItC,MAAM,2CAIrB,MAAMc,IAAEA,EAAAC,OAAKA,EAAAC,OAAQA,EAAAC,QAAQA,GAAYR,EACnC8B,EAAyB,SAAXxB,GAAgC,OAAXC,EAEnCwB,EAAM,IAAIC,eAChB5F,KAAKoD,cAAgBuC,EAGrBA,EAAIE,OAAOC,WAAcnE,IACjB3B,KAAKe,WACTf,KAAK+F,YAAY,CACbzF,SAAUqB,EAAMnB,OAASmB,EAAMlB,MAC/BD,OAAQmB,EAAMnB,OACdC,MAAOkB,EAAMlB,MACbF,WAAYyF,KAAKC,MAAOtE,EAAMnB,OAASmB,EAAMlB,MAAS,QAI9DkF,EAAIO,OAAS,KACLP,EAAI/E,QAAU,KAAO+E,EAAI/E,OAAS,IAClC4E,EAAQ,CAAEL,KAAMQ,EAAIX,SAAUpE,OAAQ+E,EAAI/E,OAAQuF,WAAYR,EAAIQ,WAAYR,QAE9EF,EAAO,IAAItC,MAAM,kBAAkBwC,EAAI/E,UAAU+E,EAAIQ,gBAI7DR,EAAIS,QAAW,IAAMX,EAAO,IAAItC,MAAM,iCACtCwC,EAAIU,UAAY,IAAMZ,EAAO,IAAItC,MAAM,oEACvCwC,EAAIW,QAAW,IAAMb,EAAO,IAAItC,MAAM,qBAMtC,IAAIoD,EAAStC,EACTA,EAAIuC,WAAW,OAASvC,EAAIuC,WAAW,WACvCD,EAAS,OAAStC,GAEtB,MAAMwC,EAAczG,KAAKyC,UAAUwC,KAAKyB,SAASH,GAIjD,GAHAZ,EAAIgB,KAAKzC,EAAQuC,GACjBd,EAAIiB,QAAU,IAEVlB,EAAa,CAIb,IAAA,MAAYmB,EAAKC,KAAUC,OAAOC,QAAQ5C,GAAW,CAAA,GACvB,iBAAtByC,EAAII,eACJtB,EAAIuB,iBAAiBL,EAAKC,GAIlC,MAAMK,EAAW,IAAIC,SACrB,IAAA,MAAYP,EAAKC,KAAUC,OAAOC,QAAQ7C,GACtCgD,EAASE,OAAOR,EAAKC,GAGzBK,EAASE,OAAO,OAAQrH,KAAKH,QAAQ6C,MACrCiD,EAAI2B,KAAKH,EAEb,KAAO,CAEHxB,EAAIuB,iBAAiB,eAAgBlH,KAAKH,QAAQ6C,KAAKqC,MACvD,IAAA,MAAY8B,EAAKC,KAAUC,OAAOC,QAAQ5C,GAAW,CAAA,GACvB,iBAAtByC,EAAII,eACJtB,EAAIuB,iBAAiBL,EAAKC,GAGlCnB,EAAI2B,KAAKtH,KAAKH,QAAQ6C,KAC1B,GAER,CAOA,qBAAM8B,GACF,IACI,MAAMQ,QAAiBhF,KAAKyC,UAAU8E,KAAK,CAAE7F,OAAQ,sBAErD,IAAKsD,EACD,MAAM,IAAI7B,MAAM,0CAIpB,GAAI6B,EAASG,OAASH,EAASG,KAAKvE,OAAQ,CACxC,MAAMwE,EAAeJ,EAASG,KAAKpD,OAAS,qCAC5C,MAAM,IAAIoB,MAAMiC,EACpB,CAEA,OAAOJ,CACX,OAASjD,GAEL,GAAsB,kBAAlBA,EAAMT,SAA8C,cAAfS,EAAMY,KAC3C,MAAM,IAAIQ,MAAM,oFAEpB,MAAMpB,CACV,CACJ,CAOA,WAAAgE,CAAY5E,GAEJnB,KAAKqD,eAAiBrD,KAAKqD,cAAcnC,gBACzClB,KAAKqD,cAAcnC,eAAeC,GAIC,mBAA5BnB,KAAKH,QAAQiD,YACpB9C,KAAKH,QAAQiD,WAAW3B,EAEhC,CAOA,WAAAuD,CAAYb,GAEJ7D,KAAKsD,cACLtD,KAAKsD,aAAajC,cAAc,kCAIhCrB,KAAKqD,eACLmE,WAAW,KACP,IACQxH,KAAKqD,eAAoD,mBAA5BrD,KAAKqD,cAAcoE,MAChDzH,KAAKqD,cAAcoE,MAE3B,OAAS1F,GACLC,QAAQyC,KAAK,+BAAgC1C,EACjD,GACD,KAIgC,mBAA5B/B,KAAKH,QAAQkD,YACpB/C,KAAKH,QAAQkD,WAAWc,EAEhC,CAOA,QAAAc,CAAS5C,GAEL,GAAI/B,KAAKqD,cACL,IACIrD,KAAKqD,cAAcoE,MACvB,OAAS1F,GACLC,QAAQyC,KAAK,wCAAyC1C,EAC1D,CAIA/B,KAAKuD,cACLvD,KAAKuD,aAAaxB,MAAM,kBAAkBA,EAAMT,WAIhB,mBAAzBtB,KAAKH,QAAQmD,SACpBhD,KAAKH,QAAQmD,QAAQjB,EAE7B,CAMA,kBAAA+B,GAEI9D,KAAKsD,aAAe,IAAI5D,aAAa,CACjCO,SAAUD,KAAKH,QAAQ8C,MAAQ3C,KAAKH,QAAQ6C,KAAKC,KACjDzC,SAAUF,KAAKH,QAAQ6C,KAAKP,KAC5BtB,YAAY,EACZC,SAAU,IAAMd,KAAK0H,WAIzB1H,KAAKqD,cAAgBrD,KAAKuD,aAAaoE,SAAS3H,KAAKsD,aAAc,OAAQ,CACvEsE,MAAO,cACPC,UAAU,EACVC,aAAa,GAErB,CAMA,MAAAJ,GACI,OAAI1H,KAAKe,YAITf,KAAKe,WAAY,EAGbf,KAAKoD,eAAqD,mBAA7BpD,KAAKoD,cAAc2E,OAChD/H,KAAKoD,cAAc2E,QAInB/H,KAAKsD,cACLtD,KAAKsD,aAAa9B,gBAIlBxB,KAAKqD,eACLmE,WAAW,KACP,IACQxH,KAAKqD,eAAoD,mBAA5BrD,KAAKqD,cAAcoE,MAChDzH,KAAKqD,cAAcoE,MAE3B,OAAS1F,GACLC,QAAQyC,KAAK,yCAA0C1C,EAC3D,GACD,OAGA,EACX,CAMA,WAAAO,GACI,OAAOtC,KAAKe,SAChB,CAQA,IAAAiH,CAAKC,EAAWjF,GACZ,OAAOhD,KAAKyD,QAAQuE,KAAKC,EAAWjF,EACxC,CAOA,MAAMA,GACF,OAAOhD,KAAKyD,QAAQyE,MAAMlF,EAC9B,CAOA,QAAQmF,GACJ,OAAOnI,KAAKyD,QAAQ2E,QAAQD,EAChC,CAMA,QAAA5F,GACI,MAAO,CACHtC,SAAUD,KAAKH,QAAQ6C,KAAKC,KAC5BR,KAAMnC,KAAKH,QAAQ6C,KAAKP,KACxB4C,KAAM/E,KAAKH,QAAQ6C,KAAKqC,KACxBhE,UAAWf,KAAKe,UAChB6B,MAAO5C,KAAKH,QAAQ+C,MACpBC,YAAa7C,KAAKH,QAAQgD,YAElC,ECrfJ,MAAMwF,oBAAoBC,EAAAA,MACtB,WAAA1I,CAAYuF,EAAO,IACfrF,MAAMqF,EAAM,CACRoD,SAAU,wBAElB,EAGJ,MAAMC,wBAAwBC,EAAAA,WAC1B,WAAA7I,CAAYC,EAAU,IAClBC,MAAM,CACF4I,WAAYL,YACZE,SAAU,uBACVpG,KAAM,MACHtC,GAEX,EAGC,MAAC8I,EAAmB,CACrBC,OAAQ,CACJhB,MAAO,sBACPzD,OAAQ,CACJ,CACIxB,KAAM,OACNoC,KAAM,OACN8D,MAAO,eACPC,YAAa,qBACbC,KAAM,IAEV,CACIpG,KAAM,MACNoC,KAAM,OACN8D,MAAO,MACPC,YAAa,4BACbC,KAAM,IAEV,CACIpG,KAAM,cACNoC,KAAM,OACN8D,MAAO,cACPG,UAAU,EACVlC,MAAO,iCACPgC,YAAa,mCACbG,KAAM,6CACNF,KAAM,IAEV,CACIpG,KAAM,aACNoC,KAAM,SACN8D,MAAO,wBACP/B,MAAO,YACPjH,QAAS,CACL,CAAEiH,MAAO,GAAIoC,KAAM,kBACnB,CAAEpC,MAAO,YAAaoC,KAAM,yBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,kBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,2BAC5B,CAAEpC,MAAO,YAAaoC,KAAM,oBAC5B,CAAEpC,MAAO,eAAgBoC,KAAM,oBAC/B,CAAEpC,MAAO,YAAaoC,KAAM,oBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,mBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,kBAC5B,CAAEpC,MAAO,eAAgBoC,KAAM,sBAC/B,CAAEpC,MAAO,aAAcoC,KAAM,sBAC7B,CAAEpC,MAAO,aAAcoC,KAAM,kBAC7B,CAAEpC,MAAO,iBAAkBoC,KAAM,0BAErCC,QAAS,GACTF,KAAM,wDAEV,CACItG,KAAM,UACNoC,KAAM,OACN8D,MAAO,qBACPC,YAAa,yCACbK,QAAS,GACTF,KAAM,yCAEV,CACItG,KAAM,aACNoC,KAAM,OACN8D,MAAO,wBACPC,YAAa,4CACbK,QAAS,GACTF,KAAM,4CAEV,CACItG,KAAM,aACNoC,KAAM,SACN8D,MAAO,aACPE,KAAM,GAEV,CACIpG,KAAM,YACNoC,KAAM,SACN8D,MAAO,YACPO,SAAS,EACTL,KAAM,KAKlBM,KAAM,CACFzB,MAAO,uBACPzD,OAAQ,CACJ,CACIxB,KAAM,OACNoC,KAAM,OACN8D,MAAO,eACPC,YAAa,qBACbC,KAAM,IAEV,CACIpG,KAAM,MACNoC,KAAM,OACN8D,MAAO,MACPC,YAAa,4BACbC,KAAM,IAEV,CACIpG,KAAM,cACNoC,KAAM,OACN8D,MAAO,cACPG,UAAU,EACVF,YAAa,mCACbG,KAAM,6CACNF,KAAM,IAEV,CACIpG,KAAM,kBACNoC,KAAM,OACN8D,MAAO,yBACPE,KAAM,IAEV,CACIpG,KAAM,aACNoC,KAAM,SACN8D,MAAO,aACPE,KAAM,GAEV,CACIpG,KAAM,YACNoC,KAAM,SACN8D,MAAO,YACPO,SAAS,EACTL,KAAM,GAEV,CACIpG,KAAM,YACNoC,KAAM,SACN8D,MAAO,YACPO,SAAS,EACTL,KAAM,KAKlBO,OAAQ,CACJnF,OAAQ,CACJ,CACIY,KAAM,aACNpC,KAAM,QACNkG,MAAO,gBACPJ,WAAYc,EAAAA,UACZC,WAAY,OACZC,WAAY,KACZC,SAAU,GACVZ,YAAa,mBACba,YAAY,EACZC,WAAY,KAEhB,CACI7E,KAAM,aACNpC,KAAM,OACNkG,MAAO,eACPJ,WAAYoB,EAAAA,SACZL,WAAY,eACZC,WAAY,KACZC,SAAU,GACVZ,YAAa,kBACba,YAAY,EACZC,WAAY,OAKxBE,YAAa,CACT3F,OAAQ,CACJ,CACIxB,KAAM,aACNoC,KAAM,SACN8D,MAAO,wBACP/B,MAAO,YACPjH,QAAS,CACL,CAAEiH,MAAO,GAAIoC,KAAM,kBACnB,CAAEpC,MAAO,YAAaoC,KAAM,yBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,kBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,2BAC5B,CAAEpC,MAAO,YAAaoC,KAAM,oBAC5B,CAAEpC,MAAO,eAAgBoC,KAAM,oBAC/B,CAAEpC,MAAO,YAAaoC,KAAM,oBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,mBAC5B,CAAEpC,MAAO,YAAaoC,KAAM,kBAC5B,CAAEpC,MAAO,eAAgBoC,KAAM,sBAC/B,CAAEpC,MAAO,aAAcoC,KAAM,sBAC7B,CAAEpC,MAAO,aAAcoC,KAAM,kBAC7B,CAAEpC,MAAO,iBAAkBoC,KAAM,0BAErCC,QAAS,GACTF,KAAM,wDAEV,CACItG,KAAM,UACNoC,KAAM,OACN8D,MAAO,qBACPC,YAAa,yCACbK,QAAS,GACTF,KAAM,yCAEV,CACItG,KAAM,aACNoC,KAAM,OACN8D,MAAO,wBACPC,YAAa,4CACbK,QAAS,GACTF,KAAM,qDAStB,cAAmBX,EAAAA,MACf,WAAA1I,CAAYuF,EAAO,IACfrF,MAAMqF,EAAM,CACRoD,SAAU,qBAElB,CAEA,OAAAwB,GACI,MAAgC,UAAzB/J,KAAKgK,IAAI,WACpB,CAiBA,MAAAnE,CAAOhG,EAAU,IACb,OAAO,IAAI2C,WAAWxC,KAAMH,EAChC,GAGJ,MAAMoK,iBAAiBxB,EAAAA,WACnB,WAAA7I,CAAYC,EAAU,IAClBC,MAAM,CACF4I,WAAYxF,EACZqF,SAAU,oBACVpG,KAAM,MACHtC,GAEX,EAGC,MAACqK,EAAY,CACdtB,OAAQ,CACJhB,MAAO,WACPzD,OAAQ,IAKZkF,KAAM,CACFzB,MAAO,oBACPzD,OAAQ"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("./Rest-B1eUyLX5.js"),t=require("./FormPlugins-CEjco_Hb.js");class FormBuilder{constructor(e={}){this.fields=e.fields||[],this.structureOnly=e.structureOnly||!1,this.fields.forEach(e=>{e.cols&&!e.columns?(e.columns=e.cols,delete e.cols):e.columns||(e.columns=12),"group"===e.type&&e.fields&&e.fields.forEach(e=>{e.cols&&!e.columns?(e.columns=e.cols,delete e.cols):e.columns||(e.columns=12)})}),this.options={formClass:"needs-validation",formMethod:"POST",formAction:"",groupClass:"row mb-3",fieldWrapper:"",labelClass:"form-label",inputClass:"form-control",errorClass:"invalid-feedback",helpClass:"form-text",submitButton:!1,resetButton:!1,...e.options},this.buttons=e.buttons||[],this.data=e.data||{},this.errors=e.errors||{},this.initializeTemplates()}initializeTemplates(){t.FormPlugins.onFormBuilderInit?.(this),this.templates={input:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n {{#showCopy}}\n <div class="input-group">\n <input type="{{type}}" id="{{fieldId}}" name="{{name}}"\n class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>\n <button class="btn btn-outline-secondary" type="button" data-action="copy-to-clipboard" data-target="{{fieldId}}" title="Copy to clipboard">\n <i class="bi bi-clipboard"></i>\n </button>\n </div>\n {{/showCopy}}\n {{^showCopy}}\n <input type="{{type}}" id="{{fieldId}}" name="{{name}}"\n class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>\n {{/showCopy}}\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',password:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="input-group">\n <input type="password" id="{{fieldId}}" name="{{name}}"\n class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}}\n data-field-type="password" {{{attrs}}}>\n {{#showToggle}}\n <button type="button" class="btn btn-outline-secondary"\n data-action="toggle-password"\n data-target="{{fieldId}}"\n aria-label="Show password" aria-pressed="false">\n <i class="bi bi-eye"></i>\n </button>\n {{/showToggle}}\n </div>\n {{#strengthMeter}}\n <div class="mt-2">\n <div class="progress" style="height: 4px;">\n <div class="progress-bar bg-secondary" role="progressbar"\n style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"\n id="{{fieldId}}_strength_bar"></div>\n </div>\n <small class="{{helpClass}}" id="{{fieldId}}_strength_text">Strength</small>\n </div>\n {{/strengthMeter}}\n {{#capsLockWarning}}\n <div class="{{helpClass}} text-warning d-none" id="{{fieldId}}_caps_warning">\n Caps Lock is on\n </div>\n {{/capsLockWarning}}\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',textarea:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n {{#showCopy}}\n <div class="position-relative">\n <textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>{{fieldValue}}</textarea>\n <button class="btn btn-sm btn-outline-secondary position-absolute top-0 end-0 m-2" type="button" data-action="copy-to-clipboard" data-target="{{fieldId}}" title="Copy to clipboard">\n <i class="bi bi-clipboard"></i>\n </button>\n </div>\n {{/showCopy}}\n {{^showCopy}}\n <textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>{{fieldValue}}</textarea>\n {{/showCopy}}\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',htmlpreview:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="position-relative">\n <textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}}\n data-field-type="htmlpreview" {{{attrs}}}>{{fieldValue}}</textarea>\n <div class="position-absolute d-flex gap-1" style="top: 8px; right: 8px; z-index: 10;">\n {{#showCopy}}\n <button type="button" class="btn btn-sm btn-outline-secondary"\n data-action="copy-to-clipboard"\n data-target="{{fieldId}}"\n title="Copy to clipboard"\n aria-label="Copy to clipboard">\n <i class="bi bi-clipboard"></i>\n </button>\n {{/showCopy}}\n <button type="button" class="btn btn-sm btn-outline-secondary"\n data-action="preview-html"\n data-target="{{fieldId}}"\n title="Preview HTML"\n aria-label="Preview HTML">\n <i class="bi bi-eye"></i>\n </button>\n </div>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',select:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n {{#searchInput}}{{{searchInput}}}{{/searchInput}}\n <select id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#multiple}}multiple{{/multiple}} {{{attrs}}}>\n {{{optionsHTML}}}\n </select>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',checkbox:'\n <div class="mojo-form-control">\n <div class="form-check {{fieldClass}}">\n <input type="checkbox" id="{{fieldId}}" name="{{name}}"\n class="form-check-input{{#error}} is-invalid{{/error}}" value="{{value}}"\n {{#checked}}checked{{/checked}} {{#required}}required{{/required}}\n {{#disabled}}disabled{{/disabled}} {{{attrs}}}>\n <label class="form-check-label" for="{{fieldId}}">{{label}}</label>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',switch:'\n <div class="mojo-form-control">\n <div class="form-check form-switch {{sizeClass}} {{fieldClass}}">\n <input type="checkbox" id="{{fieldId}}" name="{{name}}" role="switch"\n class="form-check-input{{#error}} is-invalid{{/error}}" value="{{value}}"\n {{#checked}}checked{{/checked}} {{#required}}required{{/required}}\n {{#disabled}}disabled{{/disabled}} data-change-action="toggle-switch"\n data-field="{{name}}" {{{attrs}}}>\n <label class="form-check-label" for="{{fieldId}}">{{label}}</label>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',image:'\n <div class="mojo-form-control" style="display: flex; flex-direction: column; align-items: center;">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="image-field-container {{containerClass}}" id="{{dropZoneId}}" style="width: {{width}}px; height: {{height}}px;">\n <input type="file" id="{{fieldId}}" name="{{name}}" class="{{inputClass}} d-none{{#error}} is-invalid{{/error}}"\n accept="{{accept}}" {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n data-change-action="image-selected" data-field="{{name}}" {{{attrs}}}>\n <div class="image-drop-zone{{#allowDrop}} droppable{{/allowDrop}}" data-action="click-image-upload"\n data-field-id="{{fieldId}}" data-field="{{name}}" style="width: 100%; height: 100%; cursor: {{cursor}};">\n {{#imageUrl}}\n <img id="{{previewId}}" src="{{imageUrl}}" alt="Preview" class="img-thumbnail w-100 h-100" style="object-fit: cover;">\n {{#showRemove}}\n <button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"\n data-action="remove-image" data-field-id="{{fieldId}}" data-field="{{name}}" style="opacity: 0.8;">\n <i class="bi bi-x"></i>\n </button>\n {{/showRemove}}\n {{/imageUrl}}\n {{^imageUrl}}\n <div class="d-flex flex-column align-items-center justify-content-center h-100 text-muted border border-2 border-dashed">\n <i class="bi bi-image fs-1 mb-2"></i>\n <small class="text-center px-2">{{placeholderText}}</small>\n </div>\n {{/imageUrl}}\n </div>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',range:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}: <span id="{{fieldId}}_value">{{fieldValue}}</span>\n </label>\n {{/label}}\n <input type="range" id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n min="{{min}}" max="{{max}}" step="{{step}}" value="{{fieldValue}}" {{#disabled}}disabled{{/disabled}}\n data-change-action="range-changed" data-target="{{fieldId}}_value" {{{attrs}}}>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',file:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <input type="file" id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n accept="{{accept}}" {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#multiple}}multiple{{/multiple}} data-change-action="file-selected" {{{attrs}}}>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',radio:'\n <div class="mojo-form-control">\n {{#label}}<div class="{{labelClass}}">{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}</div>{{/label}}\n {{#options}}\n <div class="form-check">\n <input type="radio" id="{{fieldId}}_{{value}}" name="{{name}}" value="{{value}}"\n class="form-check-input{{#error}} is-invalid{{/error}}" {{#checked}}checked{{/checked}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{{attrs}}}>\n <label class="form-check-label" for="{{fieldId}}_{{value}}">{{text}}</label>\n </div>\n {{/options}}\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',button:'\n <button type="button" {{#name}}name="{{name}}"{{/name}} class="btn {{fieldClass}}"\n {{#action}}data-action="{{action}}"{{/action}} {{#disabled}}disabled{{/disabled}} {{{attrs}}}>\n {{label}}\n </button>\n ',divider:'\n <hr class="{{dividerClass}}">\n ',html:'\n <div class="form-html {{fieldClass}}">{{{html}}}</div>\n ',header:'\n <h{{level}} {{#id}}id="{{id}}"{{/id}} class="text-primary mb-3 {{fieldClass}}">{{text}}</h{{level}}>\n ',hidden:'\n <input type="hidden" id="{{fieldId}}" name="{{name}}" value="{{fieldValue}}">\n ',checklistdropdown:'\n <div class="dropdown">\n <button class="{{buttonClass}}" type="button" data-bs-toggle="dropdown">\n <i class="{{buttonIcon}} me-1"></i> {{buttonText}}\n </button>\n <div class="{{dropdownClass}}" style="min-width: {{minWidth}};">\n {{#options}}\n <div class="form-check">\n <input class="form-check-input" type="checkbox"\n value="{{value}}"\n id="{{id}}"\n {{#checked}}checked{{/checked}}\n data-change-action="update-checklist"\n data-field="{{fieldName}}">\n <label class="form-check-label" for="{{id}}">\n {{label}}\n </label>\n </div>\n {{/options}}\n <hr class="my-2">\n <button class="btn btn-sm btn-primary w-100" data-action="apply-filter">\n Apply\n </button>\n </div>\n </div>\n ',buttongroup:'\n <div class="btn-group btn-group-{{size}}" role="group">\n {{#options}}\n <button type="button" class="{{buttonClass}} {{#active}}active{{/active}}"\n {{^action}}\n data-action="select-button-option"\n {{/action}}\n {{#action}}\n data-action=\'{{action}}\'\n {{/action}}\n data-field="{{fieldName}}"\n data-value="{{value}}">\n {{#icon}}<i class="{{icon}} me-1"></i> {{/icon}} {{label}}\n </button>\n {{/options}}\n </div>\n ',toolbarform:'\n <div class="mojo-toolbar-form">\n <div class="row g-2 align-items-center">\n {{#fields}}\n <div class="{{containerClass}}">\n {{#label}}<label class="form-label-sm mb-1">{{label}}</label>{{/label}}\n {{{fieldHtml}}}\n </div>\n {{/fields}}\n </div>\n </div>\n ',color:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="d-flex align-items-center gap-2">\n <input type="color" id="{{fieldId}}" name="{{name}}"\n class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n value="{{fieldValue}}"\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>\n <button type="button" class="btn-sm text-muted border-0 bg-transparent p-1"\n data-action="clear-color" data-field="{{name}}"\n title="Clear color">\n <i class="bi bi-x fs-5"></i>\n </button>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n '}}buildFormHTML(){const e=this.buildFieldsHTML(),t=this.buildButtonsHTML();return`\n <form class="${this.options.formClass}" novalidate>\n ${e}\n ${t}\n </form>\n `}isAutoSizingField(e){return!e.columns||"auto"===e.columns||""===e.columns}buildFieldsHTML(){const e=[];let t=0;for(;t<this.fields.length;){const s=this.fields[t];if(s.columns=s.columns||s.cols,"group"===s.type){const i=[s];let a=s.columns||12;"object"==typeof a&&null!==a&&(a=a.md||a.sm||a.xs||12);let n=t+1;for(;n<this.fields.length&&"group"===this.fields[n].type&&a<12;){const e=this.fields[n];let t=e.columns||12;if("object"==typeof t&&null!==t&&(t=t.md||t.sm||t.xs||12),!(a+t<=12))break;i.push(e),a+=t,n++}let l=i.length>1;if(1===i.length&&s.columns){let e=s.columns;"object"==typeof e&&null!==e&&(e=e.md||e.sm||e.xs||12),l=l||e<12}if(l){const t=i.map(e=>this.buildGroupHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(this.buildGroupHTML(s));t=n}else if(s.columns&&s.columns<12){const i=[s];let a=s.columns||12;"object"==typeof a&&null!==a&&(a=a.md||a.sm||a.xs||12);let n=t+1;for(;n<this.fields.length&&this.fields[n].columns&&a<12;){const e=this.fields[n];let t=e.columns||12;if("object"==typeof t&&null!==t&&(t=t.md||t.sm||t.xs||12),!(a+t<=12))break;i.push(e),a+=t,n++}const l=i.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${l}</div>`),t=n}else if(this.isAutoSizingField(s)){const i=[s];let a=t+1;for(;a<this.fields.length;){const e=this.fields[a];if(!this.isAutoSizingField(e))break;i.push(e),a++}if(i.length>1){const t=i.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(`<div class="row">${this.buildFieldHTML(s)}</div>`);t=a}else e.push(this.buildFieldHTML(s)),t++}return e.join("")}buildGroupHTML(e){const{columns:t=12,title:s,fields:i=[],class:a="",titleClass:n="h6 mb-3",responsive:l={}}=e;let r=[];if("object"==typeof t&&null!==t){if(t.xs&&r.push(`col-${t.xs}`),t.sm&&r.push(`col-sm-${t.sm}`),t.md&&r.push(`col-md-${t.md}`),t.lg&&r.push(`col-lg-${t.lg}`),t.xl&&r.push(`col-xl-${t.xl}`),t.xxl&&r.push(`col-xxl-${t.xxl}`),!t.md&&(t.xs||t.sm)){const e=t.sm||t.xs;r.push(`col-md-${e}`)}0===r.length&&r.push("col-md-12")}else r.push(`col-md-${t}`);l.xs&&r.push(`col-${l.xs}`),l.sm&&r.push(`col-sm-${l.sm}`),l.lg&&r.push(`col-lg-${l.lg}`),l.xl&&r.push(`col-xl-${l.xl}`);const o=r.join(" "),d=i.map(e=>"group"===e.type?this.buildGroupHTML(e):this.buildFieldHTML(e)).join("");return`\n <div class="${o}">\n <div class="mojo-form-group ${a}">\n ${s?`<div class="${n}">${this.escapeHtml(s)}</div>`:""}\n <div class="row">\n ${d}\n </div>\n </div>\n </div>\n `}buildFieldHTML(e){const{type:s,columns:i,class:a=""}=e;let n="";const l=t.FormPlugins&&"function"==typeof t.FormPlugins.getRenderer?t.FormPlugins.getRenderer(s):null;if("function"==typeof l)try{const t=l(this,e);null!=t&&(n=String(t))}catch(o){console.error("FormPlugins custom renderer error:",o)}if(!n)switch(s){case"text":n=this.renderTextField(e);break;case"email":n=this.renderEmailField(e);break;case"password":n=this.renderPasswordField(e);break;case"number":n=this.renderNumberField(e);break;case"tel":n=this.renderTelField(e);break;case"url":n=this.renderUrlField(e);break;case"search":n=this.renderSearchField(e);break;case"hex":n=this.renderHexField(e);break;case"textarea":n=this.renderTextareaField(e);break;case"htmlpreview":n=this.renderHtmlPreviewField(e);break;case"json":n=this.renderJsonField(e);break;case"select":n=this.renderSelectField(e);break;case"multiselect":n=this.renderMultiSelectField(e);break;case"checkbox":n=this.renderCheckboxField(e);break;case"toggle":case"switch":n=this.renderSwitchField(e);break;case"radio":n=this.renderRadioField(e);break;case"date":n=this.renderDateField(e);break;case"datetime":n=this.renderDateTimeField(e);break;case"time":n=this.renderTimeField(e);break;case"file":n=this.renderFileField(e);break;case"image":n=this.renderImageField(e);break;case"color":n=this.renderColorField(e);break;case"range":n=this.renderRangeField(e);break;case"hidden":n=this.renderHiddenField(e);break;case"button":n=this.renderButton(e);break;case"divider":n=this.renderDivider(e);break;case"html":n=this.renderHtmlField(e);break;case"heading":case"header":n=this.renderHeaderField(e);break;case"tag":case"tags":n=this.renderTagField(e);break;case"collection":n=this.renderCollectionField(e);break;case"collectionmultiselect":case"collection-multiselect":n=this.renderCollectionMultiSelectField(e);break;case"datepicker":n=this.renderDatePickerField(e);break;case"daterange":n=this.renderDateRangeField(e);break;case"checklistdropdown":n=this.renderChecklistDropdownField(e);break;case"buttongroup":n=this.renderButtonGroupField(e);break;case"combo":case"combobox":case"autocomplete":n=this.renderComboField(e);break;case"tabset":n=this.renderTabsetField(e);break;default:console.warn(`Unknown field type: ${s}`),n=this.renderTextField(e)}let r;return r=this.isAutoSizingField(e)?`col ${a}`.trim():`col-${i} ${a}`.trim(),`<div class="${r}">${n}</div>`}getFieldId(e){return e?`field_${e.replace(/[.\s\[\]]/g,"_")}`:`field_${Math.random().toString(36).substr(2,9)}`}renderTextField(e){return this.renderInputField(e,"text")}renderEmailField(e){return this.renderInputField(e,"email")}renderPasswordField(e){const t=e.passwordUsage||"current",s="new"===t||"new-password"===t?"new-password":"current-password",i={...e.attributes||{},autocomplete:e.attributes&&e.attributes.autocomplete||s};return this.renderInputField({...e,showToggle:!1!==e.showToggle,attributes:i},"password")}renderNumberField(e){const{min:t,max:s,step:i=1,...a}=e,n=[];return void 0!==t&&n.push(`min="${t}"`),void 0!==s&&n.push(`max="${s}"`),void 0!==i&&n.push(`step="${i}"`),this.renderInputField({...a,attributes:{...a.attributes,...n.reduce((e,t)=>{const[s,i]=t.split("=");return e[s]=i.replace(/"/g,""),e},{})}},"number")}renderTelField(e){return this.renderInputField(e,"tel")}renderUrlField(e){return this.renderInputField(e,"url")}renderSearchField(e){const t={...e,attributes:{"data-filter":"live-search","data-change-action":"filter-search","data-filter-debounce":e.debounce||"300",...e.attributes}};return this.renderInputField(t,"search")}renderHexField(e){const{hexType:t="color",allowPrefix:s=!0,minLength:i,maxLength:a,...n}=e;let l,r,o,d,c;switch(t){case"color":l=s?"^#?[0-9A-Fa-f]{6}$":"^[0-9A-Fa-f]{6}$",r=6,o=s?7:6,d=s?"#FF0000":"FF0000",c=c||"Enter a valid hex color (e.g., "+d+")";break;case"color-short":l=s?"^#?[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$":"^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$",r=s?4:3,o=s?7:6,d=s?"#F00 or #FF0000":"F00 or FF0000",c=c||"Enter a valid hex color (3 or 6 digits)";break;case"string":l="^[0-9A-Fa-f]+$",r=i||1,o=a||64,d="ABCDEF123456",c=c||"Only hexadecimal characters (0-9, A-F) allowed";break;default:l=s?"^#?[0-9A-Fa-f]+$":"^[0-9A-Fa-f]+$",r=i||1,o=a||64,d=s?"#ABCDEF or ABCDEF":"ABCDEF",c=c||"Enter hexadecimal characters only"}const h={...n,pattern:l,minLength:r,maxLength:o,placeholder:n.placeholder||d,help:n.help||c,attributes:{"data-hex-type":t,"data-allow-prefix":s,style:"text-transform: uppercase;",...n.attributes}};return this.renderInputField(h,"text")}renderInputField(t,s="text"){const{name:i,label:a,value:n="",placeholder:l="",required:r=!1,disabled:o=!1,readonly:d=!1,class:c="",attributes:h={},help:u=t.helpText||t.help||""}=t,p=`${this.options.inputClass} ${c}`.trim(),m=this.errors[i],g=this.getFieldValue(i)??n,f=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),b=this.getFieldId(i),v={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:i,type:s,fieldValue:this.escapeHtml(g),label:a?this.escapeHtml(a):null,placeholder:l?this.escapeHtml(l):null,help:u?this.escapeHtml(u):null,error:m?this.escapeHtml(m):null,required:r,disabled:o,readonly:d,attrs:f,showCopy:!!t.showCopy};if("password"===s&&(t.showToggle||t.strengthMeter||t.capsLockWarning)){const s={...v,showToggle:!!t.showToggle,strengthMeter:!!t.strengthMeter,capsLockWarning:!!t.capsLockWarning};return e.Mustache.render(this.templates.password,s)}if("password"===s){const s={...v,showToggle:!1!==t.showToggle,strengthMeter:!!t.strengthMeter,capsLockWarning:!!t.capsLockWarning};return e.Mustache.render(this.templates.password,s)}return e.Mustache.render(this.templates.input,v)}renderTextareaField(t){const{name:s,label:i,value:a="",placeholder:n="",required:l=!1,disabled:r=!1,readonly:o=!1,rows:d=3,cols:c,class:h="",attributes:u={},help:p=t.helpText||t.help||""}=t,m=`${this.options.inputClass} ${h}`.trim(),g=this.errors[s],f=this.getFieldValue(s)??a,b=Object.entries(u).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),v=this.getFieldId(s),y={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:v,name:s,fieldValue:f,label:i?this.escapeHtml(i):null,placeholder:n?this.escapeHtml(n):null,help:p?this.escapeHtml(p):null,error:g?this.escapeHtml(g):null,rows:d||3,required:l,disabled:r,readonly:o,showCopy:!!t.showCopy,attrs:b};return e.Mustache.render(this.templates.textarea,y)}renderHtmlPreviewField(t){const{name:s,label:i,value:a="",placeholder:n="",required:l=!1,disabled:r=!1,readonly:o=!1,rows:d=5,class:c="",attributes:h={},help:u=t.helpText||t.help||""}=t,p=`${this.options.inputClass} ${c}`.trim(),m=this.errors[s],g=this.getFieldValue(s)??a,f=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),b=this.getFieldId(s),v={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:s,fieldValue:g,label:i?this.escapeHtml(i):null,placeholder:n?this.escapeHtml(n):null,help:u?this.escapeHtml(u):null,error:m?this.escapeHtml(m):null,rows:d||5,required:l,disabled:r,readonly:o,attrs:f,showCopy:!!t.showCopy};return e.Mustache.render(this.templates.htmlpreview,v)}renderJsonField(t){const{name:s,label:i,placeholder:a="",required:n=!1,disabled:l=!1,readonly:r=!1,rows:o=3,class:d="",attributes:c={},help:h=t.helpText||t.help||""}=t,u=this.getFieldValue(t.name)??t.value??{};let p=u;if("object"==typeof u&&null!==u)try{p=JSON.stringify(u,null,2)}catch(y){p="{}"}else"string"!=typeof u&&(p=String(u));const m=`${this.options.inputClass} ${d}`.trim(),g=this.errors[s],f=this.getFieldId(s),b=Object.entries({...c,"data-field-type":"json"}).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),v={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:s,fieldValue:p,label:i?this.escapeHtml(i):null,placeholder:a?this.escapeHtml(a):null,help:h?this.escapeHtml(h):null,error:g?this.escapeHtml(g):null,rows:o||3,required:n,disabled:l,readonly:r,attrs:b};return e.Mustache.render(this.templates.textarea,v)}renderSelectField(t){const{name:s,label:i,options:a=[],value:n="",required:l=!1,disabled:r=!1,multiple:o=!1,searchable:d=!1,class:c="",attributes:h={},help:u=t.helpText||t.help||"",start:p=t.start,end:m=t.end,step:g=t.step,format:f=t.format,prefix:b=t.prefix,suffix:v=t.suffix}=t,y=`form-select ${c}`.trim(),w=this.errors[s],C=this.getFieldValue(s)??n,x=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),D=this.getFieldId(s);let F=[...a];if(void 0!==p&&void 0!==m){const e=void 0!==g?g:1,t=this.generateSelectOptions(p,m,e,{format:f,prefix:b,suffix:v});F=[...F,...t]}let $="";Array.isArray(F)&&($=F.map(e=>{if("string"==typeof e){const t=e===C?"selected":"";return`<option value="${this.escapeHtml(e)}" ${t}>${this.escapeHtml(e)}</option>`}if(e&&"object"==typeof e){const t=e.value==C?"selected":"";return`<option value="${this.escapeHtml(e.value)}" ${t}>${this.escapeHtml(e.label||e.text||e.value)}</option>`}return""}).join(""));const S=d?`\n <input type="text"\n class="form-control form-control-sm mb-2"\n placeholder="Search options..."\n data-filter="live-search"\n data-change-action="filter-select-options"\n data-target="${D}">\n `:"",V={labelClass:this.options.labelClass,inputClass:y,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:D,name:s,label:i?this.escapeHtml(i):null,help:u?this.escapeHtml(u):null,error:w?this.escapeHtml(w):null,searchInput:d?S:null,optionsHTML:$,required:l,disabled:r,multiple:o,attrs:x};return e.Mustache.render(this.templates.select,V)}renderMultiSelectField(e){const{name:t,label:s,options:i=[],value:a=[],required:n=!1,disabled:l=!1,maxHeight:r=300,help:o=e.helpText||e.help||""}=e,d=e.placeholder||e.placeHolder||"Select...";this.getFieldId(t);const c=this.errors[t],h=e.value??this.getFieldValue(t)??a;return`\n <div class="multiselect-placeholder"\n data-field-name="${t}"\n data-field-type="multiselect"\n data-field-config='${JSON.stringify({name:t,value:h,placeholder:d,maxHeight:r,disabled:l,required:n})}'>\n <input type="hidden" name="${t}" value="${this.escapeHtml(JSON.stringify(h))}">\n <select class="form-select${c?" is-invalid":""}" \n multiple \n ${l?"disabled":""}\n ${n?"required":""}>\n ${i.map(e=>{const t="string"==typeof e?e:e.value,s="string"==typeof e?e:e.label||e.value,i=Array.isArray(h)&&h.includes(t)?"selected":"";return`<option value="${this.escapeHtml(t)}" ${i}>${this.escapeHtml(s)}</option>`}).join("")}\n </select>\n <small class="form-text text-muted">This will be enhanced with MultiSelectDropdown component</small>\n </div>\n `}renderCheckboxField(t){const{name:s,label:i,value:a=!1,required:n=!1,disabled:l=!1,class:r="",attributes:o={},help:d=t.helpText||t.help||""}=t,c=this.errors[s],h=this.getFieldValue(s)??a,u=!0===h||"true"===h||"1"===h,p=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(s),g={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:s,label:this.escapeHtml(i),help:d?this.escapeHtml(d):null,error:c?this.escapeHtml(c):null,value:this.escapeHtml(a),fieldClass:r,checked:u,required:n,disabled:l,attrs:p};return e.Mustache.render(this.templates.checkbox,g)}renderSwitchField(t){const{name:s,label:i,value:a=!1,required:n=!1,disabled:l=!1,size:r="md",class:o="",attributes:d={},help:c=t.helpText||t.help||""}=t,h=this.errors[s],u=this.getFieldValue(s)??a,p=!0===u||"true"===u||"1"===u,m=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),g=this.getFieldId(s),f="md"!==r?`form-switch-${r}`:"",b={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:s,label:this.escapeHtml(i),help:c?this.escapeHtml(c):null,error:h?this.escapeHtml(h):null,value:this.escapeHtml(a),sizeClass:f,fieldClass:o,checked:p,required:n,disabled:l,attrs:m};return e.Mustache.render(this.templates.switch,b)}renderRadioField(e){const{name:t,label:s,options:i=[],value:a="",disabled:n=!1,inline:l=!1,class:r="",attributes:o={},help:d=e.helpText||e.help||""}=e,c=this.errors[t],h=this.getFieldValue(t)??a,u=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" ");let p="";return Array.isArray(i)&&(p=i.map((e,s)=>{const i=`${t}_${s}`,a="string"==typeof e?e:e.value,r="string"==typeof e?e:e.label||e.text||e.value,o=a===h?"checked":"";return`\n <div class="form-check ${l?"form-check-inline":""}">\n <input\n type="radio"\n id="${i}"\n name="${t}"\n class="form-check-input ${c?"is-invalid":""}"\n value="${this.escapeHtml(a)}"\n ${o}\n ${n?"disabled":""}\n\n ${u}\n >\n <label class="form-check-label" for="${i}">\n ${this.escapeHtml(r)}\n </label>\n </div>\n `}).join("")),`\n <div class="mojo-form-control">\n ${s?`<fieldset>\n <legend class="${this.options.labelClass}">${this.escapeHtml(s)}</legend>\n <div class="${r}">\n ${p}\n </div>\n </fieldset>`:`<div class="${r}">${p}</div>`}\n ${d?`<div class="${this.options.helpClass}">${this.escapeHtml(d)}</div>`:""}\n ${c?`<div class="${this.options.errorClass}">${this.escapeHtml(c)}</div>`:""}\n </div>\n `}renderDateField(e){return this.renderInputField(e,"date")}renderDateTimeField(e){return this.renderInputField(e,"datetime-local")}renderTimeField(e){return this.renderInputField(e,"time")}renderFileField(t){const{name:s,label:i,required:a=!1,disabled:n=!1,multiple:l=!1,accept:r="*/*",class:o="",attributes:d={},help:c=t.helpText||t.help||""}=t,h=`${this.options.inputClass} ${o}`.trim(),u=this.errors[s],p=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(s),g={labelClass:this.options.labelClass,inputClass:h,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:s,label:i?this.escapeHtml(i):null,help:c?this.escapeHtml(c):null,error:u?this.escapeHtml(u):null,accept:r,required:a,disabled:n,multiple:l,attrs:p};return e.Mustache.render(this.templates.file,g)}renderImageField(t){const{name:s,label:i,required:a=!1,disabled:n=!1,accept:l="image/*",class:r="",attributes:o={},help:d=t.helpText||t.help||"",size:c="md",allowDrop:h=!0,placeholder:u="Drop image here or click to upload"}=t,p=`${this.options.inputClass} ${r}`.trim(),m=this.errors[s],g=this.getFieldId(s),f=`${g}_dropzone`,b=`${g}_preview`,v={xs:{width:48,height:48,containerClass:"image-field-xs"},sm:{width:96,height:96,containerClass:"image-field-sm"},md:{width:150,height:150,containerClass:"image-field-md"},lg:{width:200,height:200,containerClass:"image-field-lg"},xl:{width:300,height:300,containerClass:"image-field-xl"}},y=v[c]||v.md,w=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),C=this.getFieldValue(s),x=this.extractImageUrl(C,c),D={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:s,label:i?this.escapeHtml(i):null,help:d?this.escapeHtml(d):null,error:m?this.escapeHtml(m):null,dropZoneId:f,previewId:b,containerClass:y.containerClass,width:y.width,height:y.height,accept:l,imageUrl:x,placeholderText:n?"No image":this.escapeHtml(u),cursor:n?"default":"pointer",allowDrop:h,showRemove:!n,required:a,disabled:n,attrs:w};return e.Mustache.render(this.templates.image,D)}extractImageUrl(e,t="md"){if(!e)return null;if("string"==typeof e)return e;if("object"==typeof e&&e.url){if(e.renditions){const s={xs:["thumbnail_sm","thumbnail","square_sm"],sm:["thumbnail","thumbnail_sm","square_sm"],md:["thumbnail_md","thumbnail","thumbnail_lg"],lg:["thumbnail_lg","thumbnail_md","thumbnail"],xl:["original","thumbnail_lg"]},i=s[t]||s.md;for(const t of i)if(e.renditions[t]&&e.renditions[t].url)return e.renditions[t].url}return e.url}return null}renderColorField(t){const{name:s,label:i,value:a="",placeholder:n="",required:l=!1,disabled:r=!1,readonly:o=!1,class:d="",attributes:c={},help:h=t.helpText||t.help||""}=t,u=`${this.options.inputClass} ${d}`.trim(),p=this.errors[s],m=this.getFieldValue(s)??a,g=Object.entries(c).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),f=this.getFieldId(s),b={labelClass:this.options.labelClass,inputClass:u,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:s,fieldValue:this.escapeHtml(m),label:i?this.escapeHtml(i):null,placeholder:n?this.escapeHtml(n):null,help:h?this.escapeHtml(h):null,error:p?this.escapeHtml(p):null,required:l,disabled:r,readonly:o,attrs:g};return e.Mustache.render(this.templates.color,b)}renderRangeField(t){const{name:s,label:i,min:a=0,max:n=100,step:l=1,value:r=a,disabled:o=!1,class:d="",attributes:c={},help:h=t.helpText||t.help||""}=t,u=`${this.options.inputClass} ${d}`.trim(),p=this.errors[s],m=this.getFieldValue(s)??r,g=Object.entries(c).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),f=this.getFieldId(s),b={labelClass:this.options.labelClass,inputClass:u,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:s,label:i?this.escapeHtml(i):null,help:h?this.escapeHtml(h):null,error:p?this.escapeHtml(p):null,min:a,max:n,step:l,fieldValue:m,disabled:o,attrs:g};return e.Mustache.render(this.templates.range,b)}renderHiddenField(e){const{name:t,value:s=""}=e,i=this.getFieldValue(t)??s;return`<input type="hidden" name="${t}" value="${this.escapeHtml(i)}">`}renderButton(e){const{name:t="",label:s="Button",type:i="button",action:a="",class:n="btn-secondary",disabled:l=!1,attributes:r={}}=e;let o=a;return o||("submit"===i?o="submit-form":"reset"===i&&(o="reset-form")),`\n <button\n type="button"\n ${t?`name="${t}"`:""}\n class="btn ${n}"\n ${o?`data-action="${o}"`:""}\n ${l?"disabled":""}\n ${Object.entries(r).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" ")}\n >\n ${this.escapeHtml(s)}\n </button>\n `}renderDivider(e){const{label:t="",class:s=""}=e;return`\n <div class="form-divider ${s}">\n <hr>\n ${t?`<div class="form-divider-label">${this.escapeHtml(t)}</div>`:""}\n </div>\n `}renderHtmlField(e){const{html:t="",class:s=""}=e;return`\n <div class="form-html ${s}">\n ${t}\n </div>\n `}renderHeaderField(e){const{text:t="",level:s=3,class:i="",id:a=""}=e,n=Math.max(1,Math.min(6,parseInt(s)));return`<h${n}${a?` id="${this.escapeHtml(a)}"`:""}${i?` class="${this.escapeHtml(i)}"`:""}>${this.escapeHtml(t)}</h${n}>`}buildButtonsHTML(){if(!this.options.submitButton&&!this.options.resetButton&&!this.buttons.length)return"";let e="";if(this.buttons.forEach(t=>{e+=this.renderButton(t)+" "}),this.options.submitButton){let t="Submit";"string"==typeof this.options.submitButton?t=this.options.submitButton:!0===this.options.submitButton&&(t="Submit"),e+=`<button type="submit" class="btn btn-primary me-2" data-action="submit-form">${t}</button>`}if(this.options.resetButton){let t="Reset";"string"==typeof this.options.resetButton?t=this.options.resetButton:!0===this.options.resetButton&&(t="Reset"),e+=`<button type="button" class="btn btn-secondary" data-action="reset-form">${t}</button>`}return e?`\n <div class="form-actions mt-3">\n ${e}\n </div>\n `:""}getFieldValue(t){return this.structureOnly?"":e.MOJOUtils.getContextData(this.data,t)}renderTagField(e){const{name:t,label:s,value:i="",placeholder:a="Add tags...",required:n=!1,disabled:l=!1,readonly:r=!1,maxTags:o=50,allowDuplicates:d=!1,separator:c=",",help:h=e.helpText||e.help||""}=e,u=this.getFieldId(t),p=this.errors[t],m=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${u}" class="${this.options.labelClass}">${this.escapeHtml(s)}${n?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="tag-input-placeholder"\n data-field-name="${t}"\n data-field-type="tag"\n data-field-config='${JSON.stringify({name:t,value:m,placeholder:a,maxTags:o,allowDuplicates:d,separator:c,disabled:l,readonly:r,required:n})}'>\n <input type="text"\n id="${u}"\n name="${t}_display"\n class="${this.options.inputClass}${p?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n ${l?"disabled":""}\n ${r?"readonly":""}\n\n <input type="hidden" name="${t}" value="${this.escapeHtml(m)}">\n <small class="form-text text-muted">This will be enhanced with TagInput component</small>\n </div>\n ${h?`<div class="${this.options.helpClass}">${this.escapeHtml(h)}</div>`:""}\n ${p?`<div class="${this.options.errorClass}">${this.escapeHtml(p)}</div>`:""}\n </div>\n `}renderCollectionField(e){const{name:t,label:s,value:i="",placeholder:a="Search...",required:n=!1,disabled:l=!1,readonly:r=!1,Collection:o,labelField:d="name",valueField:c="id",maxItems:h=10,emptyFetch:u=!1,debounceMs:p=300,requiresActiveGroup:m=!1,help:g=e.helpText||e.help||""}=e,f=this.getFieldId(t),b=this.errors[t],v=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${f}" class="${this.options.labelClass}">${this.escapeHtml(s)}${n?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="collection-select-placeholder"\n data-field-name="${t}"\n data-field-type="collection"\n data-field-config='${JSON.stringify({name:t,value:v,placeholder:a,labelField:d,valueField:c,maxItems:h,emptyFetch:u,debounceMs:p,disabled:l,readonly:r,required:n,requiresActiveGroup:m})}'>\n <input type="text"\n id="${f}"\n name="${t}_display"\n class="${this.options.inputClass}${b?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n ${l?"disabled":""}\n ${r?"readonly":""}\n\n <input type="hidden" name="${t}" value="${this.escapeHtml(v)}">\n <small class="form-text text-muted">This will be enhanced with CollectionSelect component</small>\n </div>\n ${g?`<div class="${this.options.helpClass}">${this.escapeHtml(g)}</div>`:""}\n ${b?`<div class="${this.options.errorClass}">${this.escapeHtml(b)}</div>`:""}\n </div>\n `}renderCollectionMultiSelectField(e){const{name:t,label:s,value:i=[],required:a=!1,disabled:n=!1,Collection:l,collectionParams:r={},labelField:o="name",valueField:d="id",excludeIds:c=[],ignoreIds:h=[],size:u=8,maxHeight:p=null,showSelectAll:m=!0,enableSearch:g=!1,searchPlaceholder:f="Search...",searchDebounce:b=400,requiresActiveGroup:v=!1,help:y=e.helpText||e.help||""}=e;this.getFieldId(t);const w=this.errors[t],C=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label class="${this.options.labelClass}">${this.escapeHtml(s)}${a?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="collection-multiselect-placeholder"\n data-field-name="${t}"\n data-field-type="collectionmultiselect"\n data-field-config='${JSON.stringify({name:t,value:C,labelField:o,valueField:d,excludeIds:c,ignoreIds:h,size:u,maxHeight:p,showSelectAll:m,enableSearch:g,searchPlaceholder:f,searchDebounce:b,disabled:n,required:a,requiresActiveGroup:v})}'>\n <input type="hidden" name="${t}" value="${this.escapeHtml(JSON.stringify(C))}">\n <small class="form-text text-muted">This will be enhanced with CollectionMultiSelect component</small>\n </div>\n ${y?`<div class="${this.options.helpClass}">${this.escapeHtml(y)}</div>`:""}\n ${w?`<div class="${this.options.errorClass}">${this.escapeHtml(w)}</div>`:""}\n </div>\n `}renderDatePickerField(e){const{name:t,label:s,value:i="",placeholder:a="Select date...",required:n=!1,disabled:l=!1,readonly:r=!1,min:o=null,max:d=null,format:c="YYYY-MM-DD",displayFormat:h="MMM DD, YYYY",help:u=e.helpText||e.help||""}=e,p=this.getFieldId(t),m=this.errors[t],g=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${p}" class="${this.options.labelClass}">${this.escapeHtml(s)}${n?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="date-picker-placeholder"\n data-field-name="${t}"\n data-field-type="datepicker"\n data-field-config='${JSON.stringify({name:t,value:g,placeholder:a,min:o,max:d,format:c,displayFormat:h,disabled:l,readonly:r,required:n})}'>\n <input type="date"\n id="${p}"\n name="${t}"\n class="${this.options.inputClass}${m?" is-invalid":""}"\n value="${this.escapeHtml(g)}"\n placeholder="${this.escapeHtml(a)}"\n ${o?`min="${o}"`:""}\n ${d?`max="${d}"`:""}\n ${l?"disabled":""}\n ${r?"readonly":""}\n ${n?"required":""}\n\n <small class="form-text text-muted">This will be enhanced with Easepick DatePicker</small>\n </div>\n ${u?`<div class="${this.options.helpClass}">${this.escapeHtml(u)}</div>`:""}\n ${m?`<div class="${this.options.errorClass}">${this.escapeHtml(m)}</div>`:""}\n </div>\n `}renderDateRangeField(e){const{name:t,startName:s,endName:i,fieldName:a,label:n,startDate:l="",endDate:r="",placeholder:o="Select date range...",required:d=!1,disabled:c=!1,readonly:h=!1,min:u=null,max:p=null,format:m="YYYY-MM-DD",displayFormat:g="MMM DD, YYYY",outputFormat:f="date",separator:b=" - ",help:v=e.helpText||e.help||""}=e,y=this.getFieldId(t||s||"daterange"),w=this.errors[t],C=s||(t?t+"_start":""),x=i||(t?t+"_end":""),D=this.getFieldValue(C)||l,F=this.getFieldValue(x)||r;return`\n <div class="mojo-form-control">\n ${n?`<label for="${y}" class="${this.options.labelClass}">${this.escapeHtml(n)}${d?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="date-range-picker-placeholder"\n data-field-name="${t||s||"daterange"}"\n data-field-type="daterange"\n data-field-config='${JSON.stringify({name:t,startName:s,endName:i,fieldName:a,startDate:D,endDate:F,placeholder:o,min:u,max:p,format:m,displayFormat:g,outputFormat:f,separator:b,disabled:c,readonly:h,required:d})}'>\n <div class="row g-2">\n <div class="col">\n <input type="date"\n id="${y}_start"\n name="${t}_start"\n class="${this.options.inputClass}${w?" is-invalid":""}"\n value="${this.escapeHtml(D)}"\n placeholder="Start date..."\n ${u?`min="${u}"`:""}\n ${p?`max="${p}"`:""}\n ${c?"disabled":""}\n ${h?"readonly":""}\n ${d?"required":""}\n\n </div>\n <div class="col-auto d-flex align-items-center">\n <span class="text-muted">${this.escapeHtml(b.trim())}</span>\n </div>\n <div class="col">\n <input type="date"\n id="${y}_end"\n name="${t}_end"\n class="${this.options.inputClass}${w?" is-invalid":""}"\n value="${this.escapeHtml(F)}"\n placeholder="End date..."\n ${u?`min="${u}"`:""}\n ${p?`max="${p}"`:""}\n ${c?"disabled":""}\n ${h?"readonly":""}\n ${d?"required":""}\n\n </div>\n </div>\n <small class="form-text text-muted">This will be enhanced with Easepick DateRangePicker</small>\n </div>\n ${v?`<div class="${this.options.helpClass}">${this.escapeHtml(v)}</div>`:""}\n ${w?`<div class="${this.options.errorClass}">${this.escapeHtml(w)}</div>`:""}\n </div>\n `}renderChecklistDropdownField(t){const s=this.getFieldId(t.name),i=this.getFieldValue(t.name)??[],a={fieldId:s,fieldName:t.name,buttonText:t.buttonText||"Select Options",buttonIcon:t.buttonIcon||"bi-chevron-down",buttonClass:t.buttonClass||"btn btn-outline-secondary btn-sm dropdown-toggle",dropdownClass:t.dropdownClass||"dropdown-menu p-2",minWidth:t.minWidth||"200px",options:t.options.map(e=>({value:e.value,label:e.label,id:`${t.name}-${e.value}`,checked:i.includes(e.value)}))};return e.Mustache.render(this.templates.checklistdropdown,a)}renderButtonGroupField(t){const s=this.getFieldId(t.name),i=this.getFieldValue(t.name)??t.value,a={fieldId:s,fieldName:t.name,size:t.size||"sm",variant:t.variant||"outline-primary",options:t.options.map(e=>({value:e.value,label:e.label,action:e.action,active:e.value===i,buttonClass:this.getButtonClass(e.value===i,t.variant)}))};return e.Mustache.render(this.templates.buttongroup,a)}getButtonClass(e,t="outline-primary"){return e?`btn btn-${t.replace("outline-","")}`:`btn btn-${t}`}renderComboField(e){const{name:t,label:s,value:i="",required:a=!1,disabled:n=!1,maxHeight:l=300,help:r=e.helpText||e.help||""}=e,o=e.placeholder||e.placeHolder||"Type or select...",d=!1!==e.allowCustom;this.getFieldId(t);const c=this.errors[t],h=e.value??this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label class="${this.options.labelClass}">${this.escapeHtml(s)}${a?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="combobox-placeholder"\n data-field-name="${t}"\n data-field-type="combobox"\n data-field-config='${JSON.stringify({name:t,value:h,placeholder:o,maxHeight:l,allowCustom:d,disabled:n,required:a})}'>\n <input type="text" \n class="form-control${c?" is-invalid":""}"\n value="${this.escapeHtml(h)}"\n placeholder="${this.escapeHtml(o)}"\n ${n?"disabled":""}\n ${a?"required":""}>\n <small class="form-text text-muted">This will be enhanced with ComboBox component</small>\n </div>\n ${r?`<div class="${this.options.helpClass}">${this.escapeHtml(r)}</div>`:""}\n ${c?`<div class="${this.options.errorClass}">${this.escapeHtml(c)}</div>`:""}\n </div>\n `}renderTabsetField(e){const{tabs:t=[],name:s=`tabset-${Date.now()}`,navClass:i="nav nav-tabs mb-3",contentClass:a="tab-content"}=e,n=String(s).toLowerCase().replace(/[^a-z0-9]/g,"-");return`\n <div class="mojo-form-tabset">\n <ul class="${i}" role="tablist">\n ${t.map((e,t)=>{const s=`${n}-pane-${t}`,i=0===t;return`\n <li class="nav-item" role="presentation">\n <button class="nav-link ${i?"active":""}"\n id="${s}-tab"\n data-bs-toggle="tab"\n data-bs-target="#${s}"\n type="button"\n role="tab"\n aria-controls="${s}"\n aria-selected="${i}">\n ${this.escapeHtml(e.label||`Tab ${t+1}`)}\n </button>\n </li>\n `}).join("")}\n </ul>\n <div class="${a}">\n ${t.map((e,t)=>{const s=`${n}-pane-${t}`;return`\n <div class="tab-pane fade ${0===t?"show active":""}"\n id="${s}"\n role="tabpanel"\n aria-labelledby="${s}-tab"\n data-tab-index="${t}">\n <div class="row">\n ${(e.fields||[]).map(e=>"group"===e.type?this.buildGroupHTML(e):this.buildFieldHTML(e)).join("")}\n </div>\n </div>\n `}).join("")}\n </div>\n </div>\n `}generateSelectOptions(e,t,s=1,i={}){const{format:a,prefix:n="",suffix:l=""}=i,r=[],o=e<=t?Math.abs(s):-Math.abs(s);for(let d=e;e<=t?d<=t:d>=t;d+=o){let s=String(d);if("function"==typeof a)s=a(d);else if("padded"===a||"pad"===a){const i=String(Math.max(Math.abs(e),Math.abs(t))).length;s=String(d).padStart(i,"0")}else"ordinal"===a&&(s=this.formatOrdinal(d));s=`${n}${s}${l}`,r.push({value:d,label:s})}return r}formatOrdinal(e){const t=e%10,s=e%100;return 1===t&&11!==s?e+"st":2===t&&12!==s?e+"nd":3===t&&13!==s?e+"rd":e+"th"}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}}const s={enableFileDrop(e={}){if(this._fileDropConfig={acceptedTypes:e.acceptedTypes||["*/*"],maxFileSize:e.maxFileSize||10485760,dropZoneSelector:e.dropZoneSelector||null,visualFeedback:!1!==e.visualFeedback,multiple:e.multiple||!1,validateOnDrop:!1!==e.validateOnDrop,dragOverClass:e.dragOverClass||"drag-over",dragActiveClass:e.dragActiveClass||"drag-active"},this._fileDropState={isDragActive:!1,dragCounter:0},this._boundFileDropHandlers={dragEnter:this._onFileDropDragEnter.bind(this),dragOver:this._onFileDropDragOver.bind(this),dragLeave:this._onFileDropDragLeave.bind(this),drop:this._onFileDropDrop.bind(this),preventDefault:this._onFileDropPreventDefault.bind(this)},this.element)this._setupFileDropListeners();else{const e=this.onAfterRender.bind(this);this.onAfterRender=async()=>{await e(),this._setupFileDropListeners()}}const t=this.onBeforeDestroy.bind(this);this.onBeforeDestroy=async()=>{this._cleanupFileDropListeners(),await t()}},_setupFileDropListeners(){if(!this._fileDropConfig)return;const e=this._getFileDropZone();e?(this._fileDropZone=e,e.addEventListener("dragenter",this._boundFileDropHandlers.dragEnter),e.addEventListener("dragover",this._boundFileDropHandlers.dragOver),e.addEventListener("dragleave",this._boundFileDropHandlers.dragLeave),e.addEventListener("drop",this._boundFileDropHandlers.drop),["dragenter","dragover","dragleave","drop"].forEach(e=>{document.addEventListener(e,this._boundFileDropHandlers.preventDefault)})):console.warn("FileDropMixin: Drop zone not found")},_cleanupFileDropListeners(){this._boundFileDropHandlers&&(this._fileDropZone&&(this._fileDropZone.removeEventListener("dragenter",this._boundFileDropHandlers.dragEnter),this._fileDropZone.removeEventListener("dragover",this._boundFileDropHandlers.dragOver),this._fileDropZone.removeEventListener("dragleave",this._boundFileDropHandlers.dragLeave),this._fileDropZone.removeEventListener("drop",this._boundFileDropHandlers.drop)),["dragenter","dragover","dragleave","drop"].forEach(e=>{document.removeEventListener(e,this._boundFileDropHandlers.preventDefault)}),this._fileDropZone=null,this._boundFileDropHandlers=null,this._fileDropConfig=null,this._fileDropState=null)},_getFileDropZone(){return this._fileDropConfig.dropZoneSelector?this.element.querySelector(this._fileDropConfig.dropZoneSelector):this.element},_onFileDropPreventDefault(e){e.preventDefault(),e.stopPropagation()},_onFileDropDragEnter(e){this._onFileDropPreventDefault(e),this._fileDropState.dragCounter++,this._fileDropState.isDragActive||(this._fileDropState.isDragActive=!0,this._applyFileDropVisualFeedback(!0))},_onFileDropDragOver(e){this._onFileDropPreventDefault(e),e.dataTransfer.dropEffect="copy"},_onFileDropDragLeave(e){this._onFileDropPreventDefault(e),this._fileDropState.dragCounter--,this._fileDropState.dragCounter<=0&&(this._fileDropState.isDragActive=!1,this._fileDropState.dragCounter=0,this._applyFileDropVisualFeedback(!1))},async _onFileDropDrop(e){this._onFileDropPreventDefault(e),this._fileDropState.isDragActive=!1,this._fileDropState.dragCounter=0,this._applyFileDropVisualFeedback(!1);const t=Array.from(e.dataTransfer.files);if(0===t.length)return;const s=this._fileDropConfig.multiple?t:[t[0]];let i={valid:!0,errors:[]};if(!this._fileDropConfig.validateOnDrop||(i=this._validateFileDropFiles(s),i.valid))if("function"==typeof this.onFileDrop)try{await this.onFileDrop(s,e,i)}catch(a){"function"==typeof this.onFileDropError?await this.onFileDropError(a,e,s):console.error("FileDropMixin: Error in onFileDrop callback:",a)}else console.warn("FileDropMixin: No onFileDrop method found on view");else"function"==typeof this.onFileDropError&&await this.onFileDropError(new Error(i.errors.join(", ")),e,s)},_applyFileDropVisualFeedback(e){if(!this._fileDropConfig.visualFeedback||!this._fileDropZone)return;const{dragOverClass:t,dragActiveClass:s}=this._fileDropConfig;e?this._fileDropZone.classList.add(t,s):this._fileDropZone.classList.remove(t,s)},_validateFileDropFiles(e){const t=[],s=this._fileDropConfig;for(const i of e)this._isFileDropTypeAccepted(i.type)?i.size>s.maxFileSize&&t.push(`File "${i.name}" (${this._formatFileDropSize(i.size)}) exceeds maximum size (${this._formatFileDropSize(s.maxFileSize)})`):t.push(`File type "${i.type}" is not accepted for file "${i.name}"`);return{valid:0===t.length,errors:t}},_isFileDropTypeAccepted(e){const{acceptedTypes:t}=this._fileDropConfig;return!!t.includes("*/*")||t.some(t=>{if(t===e)return!0;if(t.endsWith("/*")){const s=t.split("/")[0];return e.startsWith(s+"/")}return!1})},_formatFileDropSize(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,t)).toFixed(2))+" "+["Bytes","KB","MB","GB"][t]}};function i(e){Object.assign(e.prototype,s)}class TagInputView extends e.View{constructor(e={}){const{name:t,value:s="",placeholder:i="Add tags...",maxTags:a=50,allowDuplicates:n=!1,separator:l=",",trimTags:r=!0,minLength:o=1,maxLength:d=50,disabled:c=!1,readonly:h=!1,class:u="",tagClass:p="badge bg-primary",inputClass:m="form-control",...g}=e;super({tagName:"div",className:`tag-input-view ${u}`,...g}),this.name=t,this.placeholder=i,this.maxTags=a,this.allowDuplicates=n,this.separator=l,this.trimTags=r,this.minLength=o,this.maxLength=d,this.disabled=c,this.readonly=h,this.tagClass=p,this.inputClass=m,this.tags=[],this.focusedTagIndex=-1,s&&(this.tags=this.parseTagString(s))}async renderTemplate(){const e=this.renderTags(),t=this.renderHiddenInput();return`\n <div class="tag-input-container">\n <div class="tag-input-wrapper border rounded p-2"\n data-action="focus-input"\n tabindex="0"\n role="combobox"\n aria-expanded="false"\n aria-label="Tag input">\n <div class="tags-container d-flex flex-wrap gap-1 mb-2">\n ${e}\n </div>\n ${this.renderInput()}\n </div>\n ${t}\n <div class="tag-input-feedback small text-muted mt-1">\n <span class="tag-count">${this.tags.length}</span>/${this.maxTags} tags\n </div>\n </div>\n `}renderTags(){return this.tags.map((e,t)=>`\n <span class="${this.tagClass} tag-item"\n data-tag-index="${t}"\n tabindex="0"\n role="button"\n aria-label="Tag: ${this.escapeHtml(e)}. Press Delete or Backspace to remove.">\n <span class="tag-text">${this.escapeHtml(e)}</span>\n ${this.readonly||this.disabled?"":`\n <i class="bi bi-x tag-remove ms-1"\n data-action="remove-tag"\n data-tag-index="${t}"\n aria-label="Remove tag"></i>\n `}\n </span>\n `).join("")}renderInput(){return this.readonly?"":`\n <input type="text"\n class="${this.inputClass} tag-input-field border-0 p-0"\n placeholder="${this.escapeHtml(this.placeholder)}"\n ${this.disabled?"disabled":""}\n data-change-action="input-change"\n style="outline: none; box-shadow: none; min-width: 120px;"\n autocomplete="off">\n `}renderHiddenInput(){return this.name?`\n <input type="hidden"\n name="${this.name}"\n value="${this.escapeHtml(this.getTagString())}"\n class="tag-input-hidden">\n `:""}async onAfterRender(){await super.onAfterRender(),this.updateTagCount()}async onActionFocusInput(e,t){this.focus()}focus(){const e=this.element.querySelector(".tag-input-field");e&&!this.disabled&&e.focus(),this.focusedTagIndex=-1}async onActionRemoveTag(e,t){e.stopPropagation();const s=parseInt(t.getAttribute("data-tag-index"));s>=0&&s<this.tags.length&&await this.removeTag(s)}async onChangeInputChange(e,t){const s=t.value,i=s.slice(-1);if(i===this.separator||"\n"===i){e.preventDefault();const i=s.slice(0,-1);return void(i.trim()&&(await this.addTag(i),t.value=""))}}bindEvents(){this.__bnd_keydown||(this.__bnd_keydown=this.handleInputKeydown.bind(this)),this.element.addEventListener("keydown",this.__bnd_keydown),this.events.bind(this.element)}unbindEvents(){this.__bnd_keydown&&this.element.removeEventListener("keydown",this.__bnd_keydown),this.events.unbind()}handleInputKeydown(e){const t=e.target,s=t.value||"";switch(e.key){case"Enter":case"Tab":case",":s.trim()&&(e.preventDefault(),this.addTag(s),t.value="");break;case"Backspace":""===s&&this.tags.length>0&&(e.preventDefault(),this.focusedTagIndex>=0?(this.removeTag(this.focusedTagIndex),0==this.focusedTagIndex?this.focus():this.focusTag(this.focusedTagIndex-1)):this.removeTag(this.tags.length-1));break;case"ArrowLeft":if(""===s&&this.tags.length>0)if(e.preventDefault(),this.focusedTagIndex>=0){const e=this.focusedTagIndex-1;e>=0?this.focusTag(e):this.focus()}else this.focusTag(this.tags.length-1);break;case"ArrowRight":if(""===s&&this.tags.length>0)if(e.preventDefault(),this.focusedTagIndex>=0){const e=this.focusedTagIndex+1;e<this.tags.length?this.focusTag(e):this.focus()}else this.focusTag(0);break;case"Escape":t.value="",t.blur()}}async addTag(e){if(this.readonly||this.disabled)return!1;const t=this.trimTags?e.trim():e;return!(!this.isValidTag(t)||(!this.allowDuplicates&&this.tags.includes(t)?(this.showTagError(`Tag "${t}" already exists`),1):this.tags.length>=this.maxTags?(this.showTagError(`Maximum ${this.maxTags} tags allowed`),1):(this.tags.push(t),await this.updateDisplay(),this.emit("tag:added",{tag:t,tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags}),0)))}async removeTag(e){if(this.readonly||this.disabled)return!1;if(e>=0&&e<this.tags.length){const t=this.tags[e];return this.tags.splice(e,1),await this.updateDisplay(),this.emit("tag:removed",{tag:t,tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags}),!0}return!1}async removeTagByValue(e){const t=this.tags.indexOf(e);return t>=0&&await this.removeTag(t)}async clearTags(){if(this.readonly||this.disabled)return!1;const e=[...this.tags];return this.tags=[],await this.updateDisplay(),this.emit("tags:cleared",{oldTags:e}),this.emit("change",{value:"",tags:[]}),!0}async setTags(e){let t=[];Array.isArray(e)?t=e:"string"==typeof e&&(t=this.parseTagString(e)),t=t.filter(e=>this.isValidTag(e)).slice(0,this.maxTags),this.allowDuplicates||(t=[...new Set(t)]),this.tags=t,await this.updateDisplay(),this.emit("tags:set",{tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags})}isValidTag(e){return!("string"!=typeof e||e.length<this.minLength||e.length>this.maxLength||""===e.trim())}parseTagString(e){return e?e.split(this.separator).map(e=>this.trimTags?e.trim():e).filter(e=>e.length>0):[]}getTagString(){return this.tags.join(this.separator)}getTags(){return[...this.tags]}focusTag(e){const t=this.element.querySelectorAll(".tag-item");t[e]&&(this.focusedTagIndex=e,t[e].focus())}async updateDisplay(){const e=this.element.querySelector(".tags-container");e&&(e.innerHTML=this.renderTags());const t=this.element.querySelector(".tag-input-hidden");t&&(t.value=this.getTagString()),this.updateTagCount()}updateTagCount(){const e=this.element.querySelector(".tag-count");e&&(e.textContent=this.tags.length)}showTagError(e){let t=this.element.querySelector(".tag-error");if(!t){t=document.createElement("div"),t.className="tag-error small text-danger mt-1";const e=this.element.querySelector(".tag-input-feedback");e&&e.parentNode.insertBefore(t,e.nextSibling)}t.textContent=e,setTimeout(()=>{t.parentNode&&t.remove()},3e3)}setEnabled(e){this.disabled=!e;const t=this.element.querySelector(".tag-input-field");t&&(t.disabled=this.disabled);const s=this.element.querySelector(".tag-input-wrapper");s&&s.classList.toggle("disabled",this.disabled)}setReadonly(e){this.readonly=e;const t=this.element.querySelector(".tag-input-field");t&&(t.style.display=e?"none":""),this.element.querySelectorAll(".tag-remove").forEach(t=>{t.style.display=e?"none":""})}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}getFormValue(){return this.getTagString()}async setFormValue(e){await this.setTags(e)}static create(e={}){return new TagInputView(e)}}class CollectionDropdownView extends e.View{constructor(e={}){super({tagName:"div",className:"collection-dropdown-view dropdown-menu show w-100 position-absolute",style:"max-height: 250px; overflow-y: auto; z-index: 1000;",template:'\n {{#data.loading}}\n <div class="dropdown-item text-center">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <button type="button"\n class="dropdown-item {{#isSelected}}active{{/isSelected}} {{#isFocused}}bg-light{{/isFocused}}"\n data-action="select-item"\n data-value="{{valueField}}"\n data-label="{{labelField}}"\n data-index="{{index}}">\n {{labelField}}\n </button>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class="dropdown-item text-muted">\n {{#data.hasSearched}}No results found{{/data.hasSearched}}\n {{^data.hasSearched}}Start typing to search...{{/data.hasSearched}}\n </div>\n {{/data.showNoResults}}\n {{/data.loading}}\n ',...e}),this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.selectedValue=e.selectedValue||"",this.loading=e.loading||!1,this.hasSearched=e.hasSearched||!1,this.focusedIndex=e.focusedIndex||-1}async getViewData(){const t=this.collection?this.collection.toJSON().map((t,s)=>{const i=e.MOJOUtils.getNestedValue(t,this.labelField),a=e.MOJOUtils.getNestedValue(t,this.valueField);return{...t,labelField:i,valueField:a,isSelected:a==this.selectedValue,isFocused:s===this.focusedIndex,index:s}}):[];return{loading:this.loading,hasSearched:this.hasSearched,showNoResults:!this.loading&&this.hasSearched&&0===t.length,items:t}}async handleActionSelectItem(e,t){e.preventDefault();const s=t.getAttribute("data-value"),i=t.getAttribute("data-label");this.emit("item-selected",{value:s,label:i})}updateState(e){Object.assign(this,e)}updateFocusedItem(e){this.focusedIndex=e;const t=this.element?.querySelectorAll('.dropdown-item[data-action="select-item"]');t?.forEach((e,t)=>{e.classList.toggle("bg-light",t===this.focusedIndex)})}getItemCount(){return this.collection?this.collection.length():0}getFocusedItem(){return this.focusedIndex>=0&&this.collection&&this.collection.toJSON()[this.focusedIndex]||null}}class CollectionSelectView extends e.View{constructor(t={}){super({className:"collection-select-view",template:'\n <div class="position-relative">\n <input type="text"\n class="form-control {{#data.hasError}}is-invalid{{/data.hasError}} {{#data.showClear}}pe-5{{/data.showClear}}"\n placeholder="{{data.placeholder}}"\n value="{{data.displayValue}}"\n autocomplete="off" />\n\n <input type="hidden"\n name="{{data.name}}"\n value="{{data.selectedValue}}" />\n\n {{#data.showClear}}\n <button type="button"\n class="btn btn-link position-absolute top-50 end-0 translate-middle-y me-2 p-0 border-0"\n style="z-index: 10; color: #6c757d;"\n data-action="clear-selection"\n title="Clear selection">\n <i class="bi bi-x-circle"></i>\n </button>\n {{/data.showClear}}\n\n <div class="dropdown-container"></div>\n\n {{#data.hasError}}\n <div class="invalid-feedback">{{data.errorMessage}}</div>\n {{/data.hasError}}\n </div>\n ',...t}),this.collection=t.collection,this.labelField=t.labelField||"name",this.valueField=t.valueField||"id",this.maxItems=t.maxItems||10,this.placeholder=t.placeholder||"Search...",this.debounceMs=t.debounceMs||400,this.name=t.name||"collection_select",this.emptyFetch=!1!==t.emptyFetch,this.requiresActiveGroup=t.requiresActiveGroup||!1,this.selectedValue=t.value||"0",this.selectedLabel="",this.searchValue="",this.showDropdown=!1,this.loading=!1,this.hasSearched=!1,this.focusedIndex=-1,this.hasError=!1,this.errorMessage="",this.selectedValue&&"object"==typeof this.selectedValue&&(this.selectedLabel=e.MOJOUtils.getNestedValue(this.selectedValue,this.labelField)||"",this.selectedValue=e.MOJOUtils.getNestedValue(this.selectedValue,this.valueField)||"0"),this.searchTimer=null,this.dropdownView=null,this.defaultParams={},this.defaultParamsOption=t.defaultParams||null,this.handleDocumentClick=this.handleDocumentClick.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleInputEvents=this.handleInputEvents.bind(this)}onInit(){this.collection&&this.setupCollection()}setupCollection(){if(this.defaultParams={...this.collection.params},this.collection.params.size=this.maxItems,this.defaultParams.size=this.maxItems,this.defaultParamsOption){const e="function"==typeof this.defaultParamsOption?this.defaultParamsOption():this.defaultParamsOption;e&&"object"==typeof e&&(Object.assign(this.defaultParams,e),Object.assign(this.collection.params,e))}if(this.requiresActiveGroup){const e=this.getApp();e&&e.activeGroup&&e.activeGroup.id&&(this.collection.params.group=e.activeGroup.id,this.defaultParams.group=e.activeGroup.id)}this.collection.on("fetch:start",()=>{this.loading=!0,this.showDropdown=!0,this.updateDropdown()}),this.collection.on("fetch:end",()=>{this.loading=!1,this.showDropdown=!0,this.updateDropdown()}),this.selectedValue&&this.loadSelectedItem(),this.emptyFetch&&this.collection.isEmpty()&&this.performInitialFetch()}async performInitialFetch(){if(this.collection)try{const e={...this.defaultParams};delete e.search,await this.collection.updateParams(e,!0)}catch(e){console.error("Initial fetch error:",e)}}async loadSelectedItem(){try{if(!this.selectedValue||"0"==this.selectedValue)return;if(this.selectedLabel)return;const e=this.collection?.get(this.selectedValue);if(e)return this.selectedLabel=this.getFieldValue(e,this.labelField),void this.render(!1);let t=await this.collection.fetchOne(this.selectedValue);t&&(this.selectedLabel=this.getFieldValue(t,this.labelField)||`${t.constructor.name} #${t.id}`,this.render(!1))}catch(e){console.error("Error loading selected item:",e)}}async getViewData(){let e="";return this.showDropdown&&this.hasSearched?e=this.searchValue:this.selectedValue&&this.selectedLabel&&(e=this.selectedLabel),{name:this.name,placeholder:this.placeholder,displayValue:e,selectedValue:this.selectedValue,showClear:!(!this.selectedValue||"0"===this.selectedValue||!this.selectedLabel),hasError:this.hasError,errorMessage:this.errorMessage}}async onAfterRender(){await super.onAfterRender();const e=this.getInput();e&&(e.addEventListener("input",this.handleInputEvents),e.addEventListener("focus",this.handleInputEvents),e.addEventListener("keydown",this.handleKeyDown)),document.addEventListener("click",this.handleDocumentClick),this.createDropdownView()}async onBeforeDestroy(){await super.onBeforeDestroy();const e=this.getInput();e&&(e.removeEventListener("input",this.handleInputEvents),e.removeEventListener("focus",this.handleInputEvents),e.removeEventListener("keydown",this.handleKeyDown)),document.removeEventListener("click",this.handleDocumentClick),this.searchTimer&&clearTimeout(this.searchTimer),this.dropdownView&&this.dropdownView.destroy()}createDropdownView(){this.dropdownView&&this.dropdownView.destroy(),this.dropdownView=new CollectionDropdownView({collection:this.collection,labelField:this.labelField,valueField:this.valueField,selectedValue:this.selectedValue,loading:this.loading,hasSearched:this.hasSearched,focusedIndex:this.focusedIndex}),this.dropdownView.on("item-selected",e=>{this.selectItem(e.value,e.label)})}async handleInputEvents(e){const t=e.target;"focus"===e.type?(this.showDropdown=!0,!this.hasSearched&&this.emptyFetch&&this.collection?.isEmpty()&&this.performInitialFetch(),this.updateDropdown()):"input"===e.type&&(this.searchValue=t.value,this.showDropdown=!0,this.hasSearched=!0,this.focusedIndex=-1,this.searchValue!==this.selectedLabel&&(this.selectedValue="0",this.selectedLabel="",this.emit("change",{value:"0",label:""})),this.searchTimer&&clearTimeout(this.searchTimer),this.searchTimer=setTimeout(()=>{this.performSearch()},this.debounceMs),this.updateDropdown())}async handleActionClearSelection(e,t){e.preventDefault(),e.stopPropagation(),this.clearSelection()}clearSelection(){this.selectedValue="0",this.selectedLabel="",this.searchValue="",this.showDropdown=!1,this.hasError=!1,this.focusedIndex=-1,this.hasSearched=!1;const e=this.getInput();e&&(e.value="",e.focus());const t=this.getHiddenInput();t&&(t.value="0"),this.updateDropdown(),this.render(),this.emit("change",{value:"0",label:""})}async performSearch(){if(this.collection)try{const e={...this.defaultParams};this.searchValue&&this.searchValue.trim()&&(e.search=this.searchValue.trim()),await this.collection.updateParams(e,!0)}catch(e){console.error("Search error:",e),this.loading=!1,this.updateDropdown()}}updateDropdown(){if(this.dropdownView)if(this.dropdownView.updateState({selectedValue:this.selectedValue,loading:this.loading,hasSearched:this.hasSearched,focusedIndex:this.focusedIndex}),this.showDropdown)if(this.dropdownView.isMounted())this.dropdownView.render();else{const e=this.element?.querySelector(".dropdown-container");e&&this.dropdownView.render(!0,e)}else this.dropdownView.isMounted()&&(this.dropdownView.destroy(),this.createDropdownView())}selectItem(e,t){this.selectedValue=e,this.selectedLabel=t,this.searchValue="",this.showDropdown=!1,this.hasError=!1,this.focusedIndex=-1,this.hasSearched=!1;const s=this.getInput();s&&(s.value=t);const i=this.getHiddenInput();i&&(i.value=e),this.updateDropdown(),this.emit("change",{value:e,label:t})}handleDocumentClick(e){this.element?.contains(e.target)||(this.showDropdown=!1,this.focusedIndex=-1,this.updateDropdown())}handleKeyDown(e){if(!this.showDropdown||!this.collection)return;const t=this.dropdownView?.getItemCount()||0;switch(e.key){case"ArrowDown":e.preventDefault(),this.focusedIndex=Math.min(this.focusedIndex+1,t-1),this.dropdownView?.updateFocusedItem(this.focusedIndex);break;case"ArrowUp":e.preventDefault(),this.focusedIndex=Math.max(this.focusedIndex-1,0),this.dropdownView?.updateFocusedItem(this.focusedIndex);break;case"Enter":{e.preventDefault();const t=this.dropdownView?.getFocusedItem();t&&this.selectItem(t[this.valueField],t[this.labelField]);break}case"Escape":e.preventDefault(),this.showDropdown=!1,this.focusedIndex=-1,this.updateDropdown()}}getInput(){return this.element?.querySelector('input[type="text"]')}getHiddenInput(){return this.element?.querySelector('input[type="hidden"]')}setValue(e,t=""){this.selectedValue=e,this.selectedLabel=t,this.searchValue="",this.hasError=!1,this.hasSearched=!1;const s=this.getInput();s&&(s.value=t);const i=this.getHiddenInput();i&&(i.value=e)}getValue(){return 0===this.selectedValue||"0"===this.selectedValue?null:this.selectedValue}getLabel(){return this.selectedLabel}setError(e){this.hasError=!0,this.errorMessage=e,this.render()}clearError(){this.hasError=!1,this.errorMessage="",this.render()}focus(){const e=this.getInput();e&&e.focus()}getFormValue(){return 0===this.selectedValue||"0"===this.selectedValue?null:this.selectedValue}setFormValue(t){let s=t,i="";s&&"object"==typeof s&&(i=e.MOJOUtils.getNestedValue(s,this.labelField)||"",s=e.MOJOUtils.getNestedValue(s,this.valueField)),s=s||"0",s!=this.selectedValue&&(this.selectedValue=s,this.selectedLabel=i,this.searchValue="",this.hasSearched=!1,this.showDropdown=!1,this.hasError=!1,this.selectedValue&&"0"!==this.selectedValue?(this.selectedLabel||(this.selectedLabel=`${this.collection.getModelName()} #${this.selectedValue}`),this.loadSelectedItem()):this.render())}getFieldValue(t,s){if(t&&s){if("function"==typeof t.get){const i=t.get(s);return void 0===i&&s.includes(".")?e.MOJOUtils.getNestedValue(t,s):i}return e.MOJOUtils.getNestedValue(t,s)}}}class SearchView extends e.View{constructor(e={}){super({tagName:"div",className:"collection-multiselect-search",template:'\n <input type="text" \n class="form-control form-control-sm mb-2" \n placeholder="{{placeholder}}"\n data-change-action="search"\n data-filter="live-search"\n data-filter-debounce="{{debounce}}" />\n ',...e}),this.placeholder=e.placeholder||"Search...",this.debounce=e.debounce||400}async onChangeSearch(e,t){const s=t.value.trim();this.emit("search",s)}getValue(){return this.element?.querySelector("input")?.value||""}clear(){const e=this.element?.querySelector("input");e&&(e.value="")}}class ListItemsView extends e.View{constructor(e={}){super({tagName:"div",className:"collection-multiselect-items",template:`\n {{#loading}}\n <div class="text-center py-3">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/loading}}\n\n {{^loading}}\n {{#items.length}}\n {{#showSelectAll}}\n <div class="collection-multiselect-actions d-flex justify-content-between align-items-center mb-2 py-1">\n <button type="button" \n class="btn btn-link btn-sm text-decoration-none p-0 {{#allSelected}}text-muted{{/allSelected}}" \n data-action="select-all"\n {{#allSelected}}disabled{{/allSelected}}>\n <i class="bi bi-check-square me-1"></i>\n SELECT {{#unselectedCount}}({{unselectedCount}}){{/unselectedCount}}\n </button>\n <button type="button" \n class="btn btn-link btn-sm text-decoration-none p-0 {{#noneSelected}}text-muted{{/noneSelected}}" \n data-action="deselect-all"\n {{#noneSelected}}disabled{{/noneSelected}}>\n DESELECT {{#selectedCount}}({{selectedCount}}){{/selectedCount}}\n <i class="bi bi-square ms-1"></i>\n </button>\n </div>\n {{/showSelectAll}}\n \n <div class="collection-multiselect-list border rounded" \n style="max-height: {{maxHeight}}px; overflow-y: auto;">\n {{#items}}\n <div class="collection-multiselect-item d-flex align-items-center py-2 px-3 {{^disabled}}clickable{{/disabled}}" \n data-action="{{^disabled}}toggle{{/disabled}}"\n data-value="{{value}}"\n data-index="{{index}}">\n <i class="bi {{#selected}}bi-check-square-fill text-primary{{/selected}}{{^selected}}bi-square{{/selected}} me-2" \n style="font-size: 1.1rem;"></i>\n ${e.customItemTemplate?"{{{customContent}}}":'<span {{#disabled}}class="text-muted"{{/disabled}}>{{label}}</span>'}\n </div>\n {{/items}}\n </div>\n {{/items.length}}\n\n {{^items.length}}\n <div class="collection-multiselect-empty text-muted text-center py-4 border rounded">\n <i class="bi bi-inbox fs-3 d-block mb-2 opacity-50"></i>\n <div>No items available</div>\n </div>\n {{/^items.length}}\n {{/loading}}\n `,...e}),this.items=e.items||[],this.loading=e.loading||!1,this.maxHeight=e.maxHeight||336,this.showSelectAll=!1!==e.showSelectAll,this.selectedCount=e.selectedCount||0,this.totalCount=e.totalCount||0,this.unselectedCount=e.unselectedCount||0,this.allSelected=e.allSelected||!1,this.noneSelected=e.noneSelected||!0,this.customItemTemplate=e.customItemTemplate||null,this.lastClickedIndex=-1}handleActionToggle(e,t){const s=t.getAttribute("data-value"),i=parseInt(t.getAttribute("data-index"),10);this.emit("toggle",{value:s,index:i,shiftKey:e.shiftKey,element:t}),this.lastClickedIndex=i}updateItemCheckbox(e,t){const s=e.querySelector("i.bi");s&&(t?(s.classList.remove("bi-square"),s.classList.add("bi-check-square-fill","text-primary")):(s.classList.remove("bi-check-square-fill","text-primary"),s.classList.add("bi-square")))}updateActionButtons(){const e=this.element?.querySelector('[data-action="select-all"]'),t=this.element?.querySelector('[data-action="deselect-all"]');e&&(e.querySelector("span"),this.allSelected?(e.classList.add("text-muted"),e.disabled=!0):(e.classList.remove("text-muted"),e.disabled=!1)),t&&(this.noneSelected?(t.classList.add("text-muted"),t.disabled=!0):(t.classList.remove("text-muted"),t.disabled=!1))}async handleActionSelectAll(e){e.preventDefault(),this.emit("select-all")}async handleActionDeselectAll(e){e.preventDefault(),this.emit("deselect-all")}updateState(e){Object.assign(this,e)}}class CollectionMultiSelectView extends e.View{constructor(e={}){super({tagName:"div",className:"collection-multiselect-view",template:'\n <div class="mojo-form-control">\n {{#label}}\n <label class="form-label">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n \n <div class="collection-multiselect-search-container"></div>\n <div class="collection-multiselect-list-container"></div>\n\n {{#help}}\n <div class="form-text">{{help}}</div>\n {{/help}}\n {{#error}}\n <div class="invalid-feedback d-block">{{error}}</div>\n {{/error}}\n </div>\n ',...e}),this.name=e.name||"collection_multiselect",this.label=e.label||"",this.help=e.help||"",this.error=e.error||"",this.required=e.required||!1,this.disabled=e.disabled||!1,this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.excludeIds=e.excludeIds||[],this.ignoreIds=e.ignoreIds||[],this.itemTemplate=e.itemTemplate||null,this.collectionParams=e.collectionParams||{},this.defaultParamsOption=e.defaultParams||null,this.baseParams={},this.requiresActiveGroup=e.requiresActiveGroup||!1,this.size=e.size||8,this.maxHeight=e.maxHeight||42*this.size,this.showSelectAll=!1!==e.showSelectAll,this.enableSearch=e.enableSearch||!1,this.searchPlaceholder=e.searchPlaceholder||"Search...",this.searchDebounce=e.searchDebounce||400,this.selectedValues=Array.isArray(e.value)?e.value:[],this.loading=!1,this.items=[],this.searchView=null,this.listView=null}onInit(){this.collection&&this.setupCollection()}setupCollection(){if(this.baseParams={...this.collection.params},Object.keys(this.collectionParams).length>0&&(Object.assign(this.baseParams,this.collectionParams),Object.assign(this.collection.params,this.collectionParams)),this.defaultParamsOption){const e="function"==typeof this.defaultParamsOption?this.defaultParamsOption():this.defaultParamsOption;e&&(Object.assign(this.baseParams,e),Object.assign(this.collection.params,e))}if(this.requiresActiveGroup){const e=this.getApp();e?.activeGroup?.id&&(this.baseParams.group=e.activeGroup.id,this.collection.params.group=e.activeGroup.id)}this.collection.on("fetch:start",()=>{this.loading=!0,this.updateListView()}),this.collection.on("fetch:end",()=>{this.loading=!1,this.buildItems(),this.updateListView()}),this.collection.isEmpty()||this.buildItems()}async onAfterRender(){await super.onAfterRender(),this.enableSearch&&this.createSearchView(),this.createListView(),this.collection?.isEmpty()&&this.collection.fetch()}createSearchView(){const e=this.element?.querySelector(".collection-multiselect-search-container");e&&(this.searchView=new SearchView({placeholder:this.searchPlaceholder,debounce:this.searchDebounce}),this.searchView.on("search",e=>{this.handleSearch(e)}),this.searchView.render(!0,e))}createListView(){const e=this.element?.querySelector(".collection-multiselect-list-container");if(!e)return;const t=this.selectedValues.length,s=this.items.length,i=s-t;this.listView=new ListItemsView({items:this.items,loading:this.loading,maxHeight:this.maxHeight,showSelectAll:this.showSelectAll,selectedCount:t,totalCount:s,unselectedCount:i,allSelected:t===s&&s>0,noneSelected:0===t,customItemTemplate:this.itemTemplate}),this.listView.on("toggle",e=>{this.handleToggle(e)}),this.listView.on("select-all",()=>{this.selectAll()}),this.listView.on("deselect-all",()=>{this.deselectAll()}),this.listView.render(!0,e)}updateListView(){if(this.listView){const e=this.selectedValues.length,t=this.items.length,s=t-e;this.listView.updateState({items:this.items,loading:this.loading,selectedCount:e,totalCount:t,unselectedCount:s,allSelected:e===t&&t>0,noneSelected:0===e}),this.listView.render(!1)}}buildItems(){const e=this.collection.models.filter(e=>{const t=this.getFieldValue(e,this.valueField);return null!=t&&!this.excludeIds.includes(t)&&!this.ignoreIds.some(e=>e==t)});this.items=e.map((e,t)=>{const s=e.toJSON?e.toJSON():e,i=this.getFieldValue(e,this.valueField),a={label:this.getFieldValue(e,this.labelField),value:i,index:t,selected:this.selectedValues.some(e=>e==i),disabled:this.disabled,model:s};return this.itemTemplate&&(a.customContent=this.renderItemTemplate(a)),a})}renderItemTemplate(e){if(!this.itemTemplate)return"";try{return this.renderTemplateString(this.itemTemplate,e)}catch(t){return console.error("Error rendering item template:",t),e.label}}getFieldValue(t,s){if(t&&s)return"function"==typeof t.get?t.get(s)??e.MOJOUtils.getNestedValue(t,s):e.MOJOUtils.getNestedValue(t,s)}handleSearch(e){const t={...this.baseParams};e&&(t.search=e),this.collection.updateParams(t,!0)}handleToggle({value:e,index:t,shiftKey:s,element:i}){if(s&&this.listView.lastClickedIndex>=0){const e=Math.min(this.listView.lastClickedIndex,t),s=Math.max(this.listView.lastClickedIndex,t),i=!this.items[t].selected;for(let t=e;t<=s;t++){const e=this.items[t];e.disabled||(i?this.selectedValues.includes(e.value)||this.selectedValues.push(e.value):this.selectedValues=this.selectedValues.filter(t=>t!=e.value),e.selected=i)}this.updateListView()}else{const s=this.items[t];s.selected?(this.selectedValues=this.selectedValues.filter(t=>t!=e),s.selected=!1):(this.selectedValues.push(e),s.selected=!0),i&&this.listView&&(this.listView.updateItemCheckbox(i,s.selected),this.listView.selectedCount=this.selectedValues.length,this.listView.unselectedCount=this.items.length-this.selectedValues.length,this.listView.allSelected=this.selectedValues.length===this.items.length&&this.items.length>0,this.listView.noneSelected=0===this.selectedValues.length,this.listView.updateActionButtons())}this.emit("change",{value:this.selectedValues,name:this.name})}selectAll(){this.selectedValues=this.items.filter(e=>!e.disabled).map(e=>e.value),this.items.forEach(e=>{e.disabled||(e.selected=!0)}),this.updateListView(),this.emit("change",{value:this.selectedValues,name:this.name})}deselectAll(){this.selectedValues=[],this.items.forEach(e=>e.selected=!1),this.updateListView(),this.emit("change",{value:this.selectedValues,name:this.name})}async onBeforeDestroy(){await super.onBeforeDestroy(),this.searchView&&this.searchView.destroy(),this.listView&&this.listView.destroy()}getValue(){return this.selectedValues}setValue(e){this.selectedValues=Array.isArray(e)?e:[],this.buildItems(),this.updateListView()}setExcludeIds(e){this.excludeIds=Array.isArray(e)?e:[],this.buildItems(),this.updateListView()}setIgnoreIds(e){this.ignoreIds=Array.isArray(e)?e:[],this.buildItems(),this.updateListView()}async refresh(){await this.collection.fetch()}getFormValue(){return this.selectedValues}setFormValue(e){this.setValue(e)}}class MultiSelectItemsView extends e.View{constructor(e={}){super({tagName:"div",className:"multiselect-items",template:'\n {{#items.length}}\n <div class="multiselect-list" style="max-height: {{maxHeight}}px; overflow-y: auto;">\n {{#items}}\n <div class="multiselect-item form-check px-3 py-2" \n data-action="toggle"\n data-value="{{value}}"\n data-index="{{index}}">\n <input type="checkbox" \n class="form-check-input" \n id="{{id}}"\n {{#selected}}checked{{/selected}}\n {{#disabled}}disabled{{/disabled}}>\n <label class="form-check-label w-100" for="{{id}}">\n {{label}}\n </label>\n </div>\n {{/items}}\n </div>\n <div class="multiselect-footer border-top p-2">\n <button type="button" class="btn btn-sm btn-primary w-100" data-action="close-dropdown">\n Done\n </button>\n </div>\n {{/items.length}}\n \n {{^items.length}}\n <div class="multiselect-empty text-muted text-center py-3">\n <small>No options available</small>\n </div>\n {{/items.length}}\n ',...e}),this.items=e.items||[],this.maxHeight=e.maxHeight||300}handleActionToggle(e,t){const s=t.getAttribute("data-value"),i=parseInt(t.getAttribute("data-index"),10),a=this.items[i];if(!a||a.disabled)return;a.selected=!a.selected;const n=t.querySelector('input[type="checkbox"]');n&&(n.checked=a.selected),this.emit("toggle",{value:s,index:i,selected:a.selected})}handleActionCloseDropdown(e,t){this.emit("close-dropdown")}getValue(){return this.items.filter(e=>e.selected).map(e=>e.value)}setValue(e){const t=new Set(Array.isArray(e)?e:[e]);this.items.forEach(e=>{e.selected=t.has(e.value)}),this.render(!1)}updateItems(e){this.items=e,this.render(!1)}}class MultiSelectDropdown extends e.View{constructor(e={}){super({tagName:"div",className:"multiselect-dropdown",template:'\n <div class="mojo-form-control">\n {{#label}}\n <label class="form-label">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n \n <div class="dropdown w-100">\n <button class="btn btn-outline-secondary dropdown-toggle w-100 text-start d-flex justify-content-between align-items-center" \n type="button" \n data-bs-toggle="dropdown" \n aria-expanded="false"\n {{#disabled}}disabled{{/disabled}}>\n <span class="multiselect-button-text">{{buttonText}}</span>\n <i class="bi bi-chevron-down"></i>\n </button>\n <div class="dropdown-menu w-100" data-bs-auto-close="outside" data-container="items"></div>\n </div>\n \n {{#help}}\n <div class="form-text">{{help}}</div>\n {{/help}}\n {{#error}}\n <div class="invalid-feedback d-block">{{error}}</div>\n {{/error}}\n </div>\n ',...e}),this.name=e.name||"multiselect",this.label=e.label||"",this.help=e.help||"",this.error=e.error||"",this.required=e.required||!1,this.disabled=e.disabled||!1,this.placeholder=e.placeholder||e.placeHolder||"Select...",this.maxHeight=e.maxHeight||300,this.showSelectedLabels=!1!==e.showSelectedLabels,this.maxLabelsToShow=e.maxLabelsToShow||3,this.options=e.options||[],this.selectedValues=Array.isArray(e.value)?e.value:[],this.buttonText=this.computeButtonText(),this.listView=null}computeButtonText(){const e=this.selectedValues.length;return 0===e?this.placeholder||"Select...":this.showSelectedLabels&&e<=this.maxLabelsToShow?this.selectedValues.map(e=>{const t=this.options.find(t=>("string"==typeof t?t:t.value)===e);return"string"==typeof t?t:t?.label||t?.value||e}).join(", "):`${e} selected`}async onAfterRender(){await super.onAfterRender(),this.createListView()}createListView(){const e=this.element?.querySelector('[data-container="items"]');if(!e)return;const t=this.options.map((e,t)=>{const s="string"==typeof e?e:e.value,i="string"==typeof e?e:e.label||e.text||e.value,a="object"==typeof e&&e.disabled;return{id:`${this.name}_${t}`,value:s,label:i,index:t,selected:this.selectedValues.includes(s),disabled:a}});this.listView=new MultiSelectItemsView({items:t,maxHeight:this.maxHeight}),this.listView.on("toggle",e=>{this.handleToggle(e)}),this.listView.on("close-dropdown",()=>{this.closeDropdown()}),this.listView.render(!0,e)}closeDropdown(){const e=this.element?.querySelector(".dropdown-toggle");if(e&&window.bootstrap?.Dropdown){const t=window.bootstrap.Dropdown.getInstance(e);t&&t.hide()}}handleToggle(e){const{value:t,selected:s}=e;s?this.selectedValues.includes(t)||this.selectedValues.push(t):this.selectedValues=this.selectedValues.filter(e=>e!==t),this.updateButtonText(),this.emit("change",{value:this.selectedValues,name:this.name})}updateButtonText(){const e=this.element?.querySelector(".multiselect-button-text");if(!e)return;const t=this.selectedValues.length;this.buttonText=this.computeButtonText(),e.textContent=this.buttonText,0===t?e.classList.add("text-muted"):e.classList.remove("text-muted")}getValue(){return this.selectedValues}setValue(e){this.selectedValues=Array.isArray(e)?e:e?[e]:[],this.listView&&this.listView.setValue(this.selectedValues),this.updateButtonText()}setOptions(e){if(this.options=e,this.listView){const e=this.options.map((e,t)=>{const s="string"==typeof e?e:e.value,i="string"==typeof e?e:e.label||e.text||e.value,a="object"==typeof e&&e.disabled;return{id:`${this.name}_${t}`,value:s,label:i,index:t,selected:this.selectedValues.includes(s),disabled:a}});this.listView.updateItems(e)}}clear(){this.setValue([])}getFormValue(){return this.getValue()}setFormValue(e){this.setValue(e)}async onBeforeDestroy(){await super.onBeforeDestroy(),this.listView&&this.listView.destroy()}}class DatePicker extends e.View{constructor(e={}){const{name:t,value:s="",format:i="YYYY-MM-DD",displayFormat:a="MMM DD, YYYY",min:n=null,max:l=null,placeholder:r="Select date...",disabled:o=!1,readonly:d=!1,required:c=!1,class:h="",inputClass:u="form-control",autoApply:p=!0,inline:m=!1,...g}=e;super({tagName:"div",className:`date-picker-view ${h}`,...g}),this.name=t,this.format=i,this.displayFormat=a,this.min=n,this.max=l,this.placeholder=r,this.disabled=o,this.readonly=d,this.required=c,this.inputClass=u,this.autoApply=p,this.inline=m,this.currentValue=s,this.picker=null,this.useNative=!1,this.easepickLoaded=!1,this.checkEasepickAvailability()}async checkEasepickAvailability(){if("undefined"!=typeof window&&window.easepick)return this.easepickLoaded=!0,!0;try{return await this.loadEasepick(),this.easepickLoaded=!0,!0}catch(e){return console.warn("Easepick failed to load, falling back to native date input:",e),this.useNative=!0,!1}}async loadEasepick(){return new Promise((e,t)=>{if(window.easepick)return void e();const s=document.createElement("link");s.rel="stylesheet",s.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(s);const i=document.createElement("script");i.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",i.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},i.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(i)})}async renderTemplate(){const e=this.getInputId(),t=this.useNative?"date":"text",s=this.formatValueForInput(this.currentValue);return`\n <div class="date-picker-container">\n <input \n type="${t}"\n id="${e}"\n name="${this.name||""}"\n class="${this.inputClass}${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(s)}"\n placeholder="${this.escapeHtml(this.placeholder)}"\n ${this.min?`min="${this.min}"`:""}\n ${this.max?`max="${this.max}"`:""}\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n autocomplete="off"\n data-change-action="date-changed"\n />\n <div class="date-picker-feedback"></div>\n </div>\n `}async onAfterRender(){await super.onAfterRender(),this.easepickLoaded&&!this.useNative?await this.initializeEasepick():this.initializeNativeFallback()}async initializeEasepick(){const e=this.getInputElement();if(e&&window.easepick)try{const t={element:e,css:["https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css"],format:this.displayFormat,lang:"en-US",autoApply:this.autoApply,inline:this.inline,readonly:this.readonly,zIndex:9999};this.min&&(t.minDate=new Date(this.min)),this.max&&(t.maxDate=new Date(this.max)),t.setup=e=>{e.on("select",e=>{const t=e.detail.date;this.handleDateChange(t?this.formatDate(t,this.format):"")}),e.on("clear",()=>{this.handleDateChange("")}),e.on("show",()=>{this.emit("picker:show")}),e.on("hide",()=>{this.emit("picker:hide")})},this.picker=new window.easepick.create(t),this.currentValue&&this.picker.setDate(this.currentValue)}catch(t){console.error("Failed to initialize Easepick:",t),this.useNative=!0,this.initializeNativeFallback()}}initializeNativeFallback(){const e=this.getInputElement();e&&(e.type="date",this.currentValue&&(e.value=this.formatDate(this.currentValue,"YYYY-MM-DD")))}async onChangeDateChanged(e,t,s){const i=s.value;this.handleDateChange(i)}handleDateChange(e){const t=this.currentValue;this.currentValue=e,this.updateHiddenInput(),t!==e&&(this.emit("change",{value:e,formatted:this.formatValueForDisplay(e),oldValue:t}),this.emit("date:changed",{value:e,oldValue:t}))}formatDate(e,t=this.format){if(!e)return"";const s=new Date(e);if(isNaN(s.getTime()))return"";const i=s.getFullYear(),a=String(s.getMonth()+1).padStart(2,"0"),n=String(s.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":default:return`${i}-${a}-${n}`;case"MM/DD/YYYY":return`${a}/${n}/${i}`;case"DD/MM/YYYY":return`${n}/${a}/${i}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][s.getMonth()]} ${n}, ${i}`}}formatValueForInput(e){return e?this.useNative?this.formatDate(e,"YYYY-MM-DD"):e:""}formatValueForDisplay(e){return e?this.formatDate(e,this.displayFormat):""}getInputId(){return this.name?`datepicker_${this.name}_${Date.now()}`:`datepicker_${Date.now()}`}getInputElement(){return this.element?.querySelector("input")}updateHiddenInput(){}hasError(){return!1}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}setValue(e){if(this.currentValue=e,this.picker&&this.easepickLoaded)this.picker.setDate(e||null);else{const t=this.getInputElement();t&&(t.value=this.formatValueForInput(e))}this.emit("value:set",{value:e})}getValue(){return this.currentValue}getFormattedValue(e=this.displayFormat){return this.formatDate(this.currentValue,e)}clear(){this.setValue("")}setMin(e){if(this.min=e,this.picker&&this.easepickLoaded)this.picker.options.minDate=new Date(e);else{const t=this.getInputElement();t&&(t.min=e)}}setMax(e){if(this.max=e,this.picker&&this.easepickLoaded)this.picker.options.maxDate=new Date(e);else{const t=this.getInputElement();t&&(t.max=e)}}setEnabled(e){this.disabled=!e;const t=this.getInputElement();t&&(t.disabled=this.disabled),this.picker&&this.easepickLoaded&&this.disabled&&this.picker.hide()}setReadonly(e){this.readonly=e;const t=this.getInputElement();t&&(t.readonly=e)}focus(){const e=this.getInputElement();e&&e.focus()}show(){this.picker&&this.easepickLoaded&&this.picker.show()}hide(){this.picker&&this.easepickLoaded&&this.picker.hide()}getFormValue(){return this.getValue()}async setFormValue(e){this.setValue(e)}async onBeforeDestroy(){if(this.picker&&this.easepickLoaded)try{this.picker.destroy()}catch(e){console.warn("Error destroying Easepick instance:",e)}this.picker=null,await super.onBeforeDestroy()}static create(e={}){return new DatePicker(e)}}class DateRangePicker extends e.View{constructor(e={}){const{name:t,startName:s,endName:i,fieldName:a,startDate:n="",endDate:l="",format:r="YYYY-MM-DD",displayFormat:o="MMM DD, YYYY",outputFormat:d="date",min:c=null,max:h=null,placeholder:u="Select date range...",startPlaceholder:p="Start date...",endPlaceholder:m="End date...",disabled:g=!1,readonly:f=!1,required:b=!1,class:v="",inputClass:y="form-control",autoApply:w=!0,inline:C=!1,separator:x=" - ",...D}=e;super({tagName:"div",className:`date-range-picker-view ${v}`,...D}),this.name=t,this.startName=s,this.endName=i,this.fieldName=a,this.format=r,this.displayFormat=o,this.outputFormat=d,this.min=c,this.max=h,this.placeholder=u,this.startPlaceholder=p,this.endPlaceholder=m,this.disabled=g,this.readonly=f,this.required=b,this.inputClass=y,this.autoApply=w,this.inline=C,this.separator=x,this.currentStartDate=n,this.currentEndDate=l,this.picker=null,this.useNative=!1,this.easepickLoaded=!1,this.checkEasepickAvailability()}async checkEasepickAvailability(){if("undefined"!=typeof window&&window.easepick)return this.easepickLoaded=!0,!0;try{return await this.loadEasepick(),this.easepickLoaded=!0,!0}catch(e){return console.warn("Easepick failed to load, falling back to native date inputs:",e),this.useNative=!0,!1}}async loadEasepick(){return new Promise((e,t)=>{if(window.easepick)return void e();const s=document.createElement("link");s.rel="stylesheet",s.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(s);const i=document.createElement("script");i.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",i.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},i.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(i)})}async renderTemplate(){const e=this.getInputId(),t=this.getDisplayValue();if(this.useNative)return this.renderNativeTemplate(e);const s=this.startName||(this.name?`${this.name}_start`:""),i=this.endName||(this.name?`${this.name}_end`:""),a=this.currentStartDate?this.formatForOutput(this.currentStartDate):"",n=this.currentEndDate?this.formatForOutput(this.currentEndDate):"";return`\n <div class="date-range-picker-container">\n <input \n type="text"\n id="${e}"\n ${this.name?`name="${this.name}"`:""}\n class="${this.inputClass} date-range-picker-input${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(t)}"\n placeholder="${this.escapeHtml(this.placeholder)}"\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n autocomplete="off"\n data-change-action="range-changed"\n style="background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22><path fill=%22%236c757d%22 fill-rule=%22evenodd%22 d=%22M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z%22/></svg>'); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding-right: 2.25rem; cursor: pointer;"\n />\n \n \x3c!-- Hidden inputs for form submission --\x3e\n ${s?`<input type="hidden" name="${s}" value="${this.escapeHtml(a)}" />`:""}\n ${i?`<input type="hidden" name="${i}" value="${this.escapeHtml(n)}" />`:""}\n ${this.fieldName?`<input type="hidden" name="${this.fieldName}" value="${this.escapeHtml(this.name||"")}" />`:""}\n \n <div class="date-range-picker-feedback"></div>\n </div>\n `}renderNativeTemplate(e){return`\n <div class="date-range-picker-container date-range-native">\n <div class="row g-2">\n <div class="col">\n <input \n type="date"\n id="${e}_start"\n name="${this.name}_start"\n class="${this.inputClass}${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(this.formatDate(this.currentStartDate,"YYYY-MM-DD"))}"\n placeholder="${this.escapeHtml(this.startPlaceholder)}"\n ${this.min?`min="${this.min}"`:""}\n ${this.max?`max="${this.max}"`:""}\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n data-change-action="start-date-changed"\n />\n </div>\n <div class="col-auto d-flex align-items-center">\n <span class="text-muted">${this.escapeHtml(this.separator.trim())}</span>\n </div>\n <div class="col">\n <input \n type="date"\n id="${e}_end"\n name="${this.name}_end"\n class="${this.inputClass}${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(this.formatDate(this.currentEndDate,"YYYY-MM-DD"))}"\n placeholder="${this.escapeHtml(this.endPlaceholder)}"\n ${this.min?`min="${this.min}"`:""}\n ${this.max?`max="${this.max}"`:""}\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n data-change-action="end-date-changed"\n />\n </div>\n </div>\n \n \x3c!-- Hidden input for combined value --\x3e\n <input type="hidden" name="${this.name}" value="${this.escapeHtml(this.getCombinedValue())}" />\n ${this.fieldName?`<input type="hidden" name="${this.fieldName}" value="${this.escapeHtml(this.name||"")}" />`:""}\n \n <div class="date-range-picker-feedback"></div>\n </div>\n `}async onAfterRender(){await super.onAfterRender(),this.easepickLoaded&&!this.useNative?await this.initializeEasepick():this.initializeNativeFallback()}async initializeEasepick(){const e=this.getInputElement();if(e&&window.easepick)try{const t={element:e,css:["https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css"],format:this.displayFormat,lang:"en-US",autoApply:this.autoApply,inline:this.inline,readonly:this.readonly,zIndex:9999,plugins:["RangePlugin"],RangePlugin:{tooltip:!0,locale:{one:"day",other:"days"}}};this.min&&(t.minDate=new Date(this.min)),this.max&&(t.maxDate=new Date(this.max)),t.setup=e=>{e.on("select",e=>{const{start:t,end:s}=e.detail,i=t?this.normalizeDateFromPicker(t):"",a=s?this.normalizeDateFromPicker(s):"";this.handleRangeChange(i,a)}),e.on("clear",()=>{this.handleRangeChange("","")}),e.on("show",()=>{this.emit("picker:show")}),e.on("hide",()=>{this.emit("picker:hide")}),e.on("ready",()=>{this.applyInitialRange(e)})},this.picker=new window.easepick.create(t),this.applyInitialRange(this.picker)}catch(t){console.error("Failed to initialize Easepick range picker:",t),this.useNative=!0,await this.render(),this.initializeNativeFallback()}}initializeNativeFallback(){this.updateConstraints()}async onChangeRangeChanged(e,t,s){}async onChangeStartDateChanged(e,t,s){const i=s.value;this.handleRangeChange(i,this.currentEndDate),this.updateConstraints()}async onChangeEndDateChanged(e,t,s){const i=s.value;this.handleRangeChange(this.currentStartDate,i),this.updateConstraints()}handleRangeChange(e,t){const s=this.currentStartDate,i=this.currentEndDate;this.currentStartDate=e,this.currentEndDate=t,this.updateHiddenInputs(),s===e&&i===t||(this.emit("change",{startDate:e,endDate:t,combined:this.getCombinedValue(),formatted:this.getDisplayValue(),oldStartDate:s,oldEndDate:i}),this.emit("range:changed",{startDate:e,endDate:t,oldStartDate:s,oldEndDate:i}))}updateConstraints(){if(!this.useNative)return;const e=this.element?.querySelector(`[name="${this.name}_start"]`),t=this.element?.querySelector(`[name="${this.name}_end"]`);e&&t&&(this.currentStartDate&&(t.min=this.currentStartDate),this.currentEndDate&&(e.max=this.currentEndDate))}normalizeDateFromPicker(e){if(!e)return"";if("function"==typeof e.toJSDate){const t=e.toJSDate();return this.formatDate(t,this.format)}if("function"==typeof e.getFullYear){const t=e.getFullYear(),s=String(e.getMonth()+1).padStart(2,"0"),i=String(e.getDate()).padStart(2,"0");switch(this.format){case"YYYY-MM-DD":default:return`${t}-${s}-${i}`;case"MM/DD/YYYY":return`${s}/${i}/${t}`;case"DD/MM/YYYY":return`${i}/${s}/${t}`}}return this.formatDate(e,this.format)}formatDate(e,t=this.format){if(!e)return"";let s,i,a,n;if("string"==typeof e&&/^\d{4}-\d{2}-\d{2}$/.test(e)){const t=e.split("-");s=parseInt(t[0]),i=String(parseInt(t[1])).padStart(2,"0"),a=String(parseInt(t[2])).padStart(2,"0")}else{if(n=e instanceof Date?e:new Date(e),isNaN(n.getTime()))return"";s=n.getFullYear(),i=String(n.getMonth()+1).padStart(2,"0"),a=String(n.getDate()).padStart(2,"0")}switch(t){case"YYYY-MM-DD":default:return`${s}-${i}-${a}`;case"MM/DD/YYYY":return`${i}/${a}/${s}`;case"DD/MM/YYYY":return`${a}/${i}/${s}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][parseInt(i)-1]} ${a}, ${s}`}}formatForOutput(e){if(!e)return"";if("string"==typeof e&&/^\d{4}-\d{2}-\d{2}$/.test(e))switch(this.outputFormat){case"epoch":const t=e.split("-"),s=new Date(parseInt(t[0]),parseInt(t[1])-1,parseInt(t[2]));return Math.floor(s.getTime()/1e3).toString();case"iso":/* @__PURE__ */
|
|
2
|
-
return new Date(e+"T00:00:00").toISOString();default:return e}const t=new Date(e);if(isNaN(t.getTime()))return"";switch(this.outputFormat){case"epoch":return Math.floor(t.getTime()/1e3).toString();case"iso":return t.toISOString();default:return this.formatDate(e,this.format)}}getDisplayValue(){if(!this.currentStartDate&&!this.currentEndDate)return"";const e=this.currentStartDate?this.formatDate(this.currentStartDate,this.displayFormat):"",t=this.currentEndDate?this.formatDate(this.currentEndDate,this.displayFormat):"";return e&&t?`${e}${this.separator}${t}`:e||t||""}getCombinedValue(){return this.currentStartDate||this.currentEndDate?JSON.stringify({start:this.currentStartDate,end:this.currentEndDate}):""}getInputId(){return this.name?`daterange_${this.name}_${Date.now()}`:`daterange_${Date.now()}`}getInputElement(){return this.element?.querySelector('input[type="text"], input[name="'+this.name+'"]')}updateHiddenInputs(){const e=this.startName||(this.name?`${this.name}_start`:""),t=this.endName||(this.name?`${this.name}_end`:""),s=e?this.element?.querySelector(`[name="${e}"]`):null,i=t?this.element?.querySelector(`[name="${t}"]`):null,a=this.name?this.element?.querySelector(`[name="${this.name}"]`):null,n=this.fieldName?this.element?.querySelector(`[name="${this.fieldName}"]`):null,l=this.currentStartDate?this.formatForOutput(this.currentStartDate):"",r=this.currentEndDate?this.formatForOutput(this.currentEndDate):"";s&&(s.value=l),i&&(i.value=r),a&&(a.value=this.getDisplayValue()),n&&(n.value=this.name||"")}hasError(){return!(!this.currentStartDate||!this.currentEndDate)&&new Date(this.currentEndDate)<new Date(this.currentStartDate)}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}setRange(e,t){if(this.currentStartDate=e,this.currentEndDate=t,this.picker&&this.easepickLoaded){const s=this.normalizeDateValue(e),i=this.normalizeDateValue(t);this.picker.setDateRange(s||null,i||null)}else if(this.useNative){const s=this.element?.querySelector(`[name="${this.name}_start"]`),i=this.element?.querySelector(`[name="${this.name}_end"]`);s&&(s.value=this.formatDate(e,"YYYY-MM-DD")),i&&(i.value=this.formatDate(t,"YYYY-MM-DD"))}else{const e=this.getInputElement();e&&(e.value=this.getDisplayValue())}this.updateHiddenInputs(),this.emit("range:set",{startDate:e,endDate:t})}applyInitialRange(e){if(!e||!this.currentStartDate&&!this.currentEndDate)return;const t=this.normalizeDateValue(this.currentStartDate),s=this.normalizeDateValue(this.currentEndDate);(t||s)&&e.setDateRange(t||null,s||null)}normalizeDateValue(e){if(!e&&0!==e)return null;if(e instanceof Date)return isNaN(e)?null:e;const t=String(e).trim();if(!t)return null;if(/^\d{4}-\d{2}-\d{2}$/.test(t)){const[e,s,i]=t.split("-").map(Number),a=new Date(e,s-1,i);return isNaN(a)?null:a}if(/^-?\d+$/.test(t)){const e=Number(t),s=t.length<=10?1e3*e:e,i=new Date(s);return isNaN(i)?null:i}const s=new Date(t);return isNaN(s)?null:s}getRange(){return{start:this.currentStartDate,end:this.currentEndDate,combined:this.getCombinedValue()}}clear(){this.setRange("","")}setStartDate(e){this.setRange(e,this.currentEndDate)}setEndDate(e){this.setRange(this.currentStartDate,e)}getStartDate(){return this.currentStartDate}getEndDate(){return this.currentEndDate}setEnabled(e){this.disabled=!e;const t=this.element?.querySelectorAll("input");t?.forEach(e=>{e.disabled=this.disabled})}setReadonly(e){this.readonly=e;const t=this.element?.querySelectorAll('input:not([type="hidden"])');t?.forEach(t=>{t.readonly=e})}focus(){const e=this.getInputElement();e&&e.focus()}show(){this.picker&&this.easepickLoaded&&this.picker.show()}hide(){this.picker&&this.easepickLoaded&&this.picker.hide()}getFormValue(){return this.getRange()}async setFormValue(e){if("string"==typeof e)try{const t=JSON.parse(e);this.setRange(t.start,t.end)}catch{this.setRange(e,"")}else e&&"object"==typeof e&&this.setRange(e.start||"",e.end||"")}async onBeforeDestroy(){if(this.picker&&this.easepickLoaded)try{this.picker.destroy()}catch(e){console.warn("Error destroying Easepick range picker instance:",e)}this.picker=null,await super.onBeforeDestroy()}static create(e={}){return new DateRangePicker(e)}}class ComboInput extends e.View{constructor(e={}){const{name:t,value:s="",placeholder:i="Select or type...",options:a=[],allowCustom:n=!0,showDescription:l=!0,minChars:r=0,maxSuggestions:o=10,disabled:d=!1,readonly:c=!1,required:h=!1,class:u="",inputClass:p="form-control",onSelect:m=null,onChange:g=null,...f}=e;super({tagName:"div",className:`combo-input ${u}`,...f}),this.name=t,this.placeholder=i,this.options=this.normalizeOptions(a),this.allowCustom=n,this.showDescription=l,this.minChars=r,this.maxSuggestions=o,this.disabled=d,this.readonly=c,this.required=h,this.inputClass=p,this.onSelectCallback=m,this.onChangeCallback=g,this.currentValue=s,this.inputValue=this.getDisplayValue(s),this.filteredOptions=[],this.highlightedIndex=-1,this.isOpen=!1,this.selectedOption=this.findOptionByValue(s)}normalizeOptions(e){return Array.isArray(e)?e.map(e=>"string"==typeof e?{value:e,label:e}:"object"==typeof e&&void 0!==e.value?{value:e.value,label:e.label||String(e.value),description:e.description||e.label||"",meta:e.meta||{}}:null).filter(e=>null!==e):[]}findOptionByValue(e){return this.options.find(t=>t.value===e)||null}getDisplayValue(e){const t=this.findOptionByValue(e);return t?t.label:e}async renderTemplate(){return`\n <div class="combo-input-container position-relative">\n <div class="input-wrapper position-relative">\n ${this.renderInput()}\n ${this.renderDropdownToggle()}\n </div>\n ${this.renderHiddenInput()}\n ${this.renderDropdown()}\n </div>\n `}renderInput(){return`\n <input type="text"\n class="${this.inputClass} combo-input-field"\n placeholder="${this.escapeHtml(this.placeholder)}"\n value="${this.escapeHtml(this.inputValue)}"\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n data-change-action="input-change"\n data-action="input-keydown"\n autocomplete="off"\n role="combobox"\n aria-expanded="${this.isOpen}"\n aria-autocomplete="list"\n aria-controls="combo-dropdown-${this.cid}">\n `}renderDropdownToggle(){return this.readonly||this.disabled?"":'\n <button type="button"\n class="btn btn-sm combo-toggle position-absolute top-50 end-0 translate-middle-y border-0"\n data-action="toggle-dropdown"\n tabindex="-1"\n aria-label="Toggle dropdown"\n style="padding: 0.25rem 0.5rem;">\n <i class="bi bi-chevron-down"></i>\n </button>\n '}renderHiddenInput(){return this.name?`\n <input type="hidden"\n name="${this.name}"\n value="${this.escapeHtml(this.currentValue)}"\n class="combo-input-hidden">\n `:""}renderDropdown(){return`\n <div id="combo-dropdown-${this.cid}"\n class="combo-dropdown dropdown-menu position-absolute w-100 ${this.isOpen?"show":""}"\n role="listbox"\n style="max-height: 300px; overflow-y: auto; z-index: 1050;">\n ${this.renderDropdownContent()}\n </div>\n `}renderDropdownContent(){return 0===this.filteredOptions.length?this.renderNoResults():this.filteredOptions.slice(0,this.maxSuggestions).map((e,t)=>this.renderOption(e,t)).join("")}renderOption(e,t){const s=t===this.highlightedIndex,i=e.value===this.currentValue;return`\n <div class="dropdown-item combo-option ${s?"active":""} ${i?"selected":""}"\n data-action="select-option"\n data-option-index="${t}"\n role="option"\n aria-selected="${i}"\n style="cursor: pointer;">\n <div class="d-flex justify-content-between align-items-start">\n <div class="flex-grow-1">\n <div class="combo-option-label fw-semibold">${this.highlightMatch(e.label)}</div>\n ${this.showDescription&&e.description?`\n <div class="combo-option-description small text-muted">${this.escapeHtml(e.description)}</div>\n `:""}\n </div>\n ${i?'<i class="bi bi-check text-primary ms-2"></i>':""}\n </div>\n </div>\n `}renderNoResults(){return this.allowCustom&&this.inputValue.length>=this.minChars?`\n <div class="dropdown-item-text text-muted small">\n <i class="bi bi-info-circle me-1"></i>\n ${this.inputValue?"No matches found. Press Enter to use custom value.":"Start typing to see suggestions..."}\n </div>\n `:'\n <div class="dropdown-item-text text-muted small">\n <i class="bi bi-search me-1"></i>\n No matching options found.\n </div>\n '}highlightMatch(e){if(!this.inputValue)return this.escapeHtml(e);const t=this.escapeHtml(e),s=new RegExp(`(${this.escapeRegex(this.inputValue)})`,"gi");return t.replace(s,'<mark class="bg-warning bg-opacity-25">$1</mark>')}async onAfterRender(){await super.onAfterRender(),this.updateFilteredOptions(),this.handleOutsideClick=e=>{this.element&&!this.element.contains(e.target)&&this.closeDropdown()},document.addEventListener("click",this.handleOutsideClick)}async onChangeInputChange(e,t){this.inputValue=t.value,this.updateFilteredOptions(),this.inputValue.length>=this.minChars?this.openDropdown():this.closeDropdown(),this.highlightedIndex=-1,await this.updateDropdownDisplay()}async onActionInputKeydown(e,t){switch(e.key){case"ArrowDown":e.preventDefault(),this.isOpen?this.highlightNext():this.openDropdown(),await this.updateDropdownDisplay();break;case"ArrowUp":e.preventDefault(),this.isOpen&&(this.highlightPrevious(),await this.updateDropdownDisplay());break;case"Enter":e.preventDefault(),this.isOpen&&this.highlightedIndex>=0?await this.selectHighlightedOption():this.allowCustom&&this.inputValue&&await this.selectCustomValue(this.inputValue);break;case"Escape":e.preventDefault(),this.closeDropdown();const t=this.element.querySelector(".combo-input-field");t&&(t.value=this.getDisplayValue(this.currentValue),this.inputValue=t.value);break;case"Tab":this.isOpen&&this.closeDropdown()}}async onActionToggleDropdown(e,t){if(e.preventDefault(),e.stopPropagation(),this.isOpen)this.closeDropdown();else{this.inputValue="";const e=this.element.querySelector(".combo-input-field");e&&(e.value="",e.focus()),this.updateFilteredOptions(),this.openDropdown(),await this.updateDropdownDisplay()}}async onActionSelectOption(e,t){e.preventDefault(),e.stopPropagation();const s=parseInt(t.getAttribute("data-option-index"));s>=0&&s<this.filteredOptions.length&&await this.selectOption(this.filteredOptions[s])}openDropdown(){this.isOpen=!0;const e=this.element?.querySelector(".combo-dropdown");e&&e.classList.add("show");const t=this.element?.querySelector(".combo-input-field");t&&t.setAttribute("aria-expanded","true")}closeDropdown(){this.isOpen=!1,this.highlightedIndex=-1;const e=this.element?.querySelector(".combo-dropdown");e&&e.classList.remove("show");const t=this.element?.querySelector(".combo-input-field");t&&t.setAttribute("aria-expanded","false")}updateFilteredOptions(){const e=this.inputValue.toLowerCase().trim();e?(this.filteredOptions=this.options.filter(t=>{const s=t.label.toLowerCase().includes(e),i=String(t.value).toLowerCase().includes(e),a=t.description?.toLowerCase().includes(e);return s||i||a}),this.filteredOptions.sort((t,s)=>{const i=t.label.toLowerCase()===e,a=s.label.toLowerCase()===e;if(i&&!a)return-1;if(!i&&a)return 1;const n=t.label.toLowerCase().startsWith(e),l=s.label.toLowerCase().startsWith(e);return n&&!l?-1:!n&&l?1:0})):this.filteredOptions=[...this.options]}async updateDropdownDisplay(){const e=this.element?.querySelector(".combo-dropdown");if(e&&(e.innerHTML=this.renderDropdownContent(),this.highlightedIndex>=0)){const t=e.querySelector(".combo-option.active");t&&t.scrollIntoView({block:"nearest"})}}highlightNext(){0!==this.filteredOptions.length&&(this.highlightedIndex=(this.highlightedIndex+1)%Math.min(this.filteredOptions.length,this.maxSuggestions))}highlightPrevious(){0!==this.filteredOptions.length&&(this.highlightedIndex=this.highlightedIndex<=0?Math.min(this.filteredOptions.length,this.maxSuggestions)-1:this.highlightedIndex-1)}async selectHighlightedOption(){this.highlightedIndex>=0&&this.highlightedIndex<this.filteredOptions.length&&await this.selectOption(this.filteredOptions[this.highlightedIndex])}async selectOption(e){this.currentValue=e.value,this.inputValue=e.label,this.selectedOption=e;const t=this.element?.querySelector(".combo-input-field");t&&(t.value=e.label);const s=this.element?.querySelector(".combo-input-hidden");s&&(s.value=e.value),this.closeDropdown(),this.emit("select",{option:e,value:e.value,meta:e.meta}),this.emit("change",{value:e.value,option:e,meta:e.meta}),"function"==typeof this.onSelectCallback&&this.onSelectCallback(e),"function"==typeof this.onChangeCallback&&this.onChangeCallback(e.value)}async selectCustomValue(e){if(!this.allowCustom)return;this.currentValue=e,this.inputValue=e,this.selectedOption=null;const t=this.element?.querySelector(".combo-input-hidden");t&&(t.value=e),this.closeDropdown(),this.emit("custom",{value:e}),this.emit("change",{value:e,custom:!0}),"function"==typeof this.onChangeCallback&&this.onChangeCallback(e)}getValue(){return this.currentValue}async setValue(e){this.currentValue=e,this.selectedOption=this.findOptionByValue(e),this.inputValue=this.getDisplayValue(e);const t=this.element?.querySelector(".combo-input-field");t&&(t.value=this.inputValue);const s=this.element?.querySelector(".combo-input-hidden");s&&(s.value=e),this.updateFilteredOptions()}getSelectedOption(){return this.selectedOption}async setOptions(e){this.options=this.normalizeOptions(e),this.updateFilteredOptions(),this.isOpen&&await this.updateDropdownDisplay()}setEnabled(e){this.disabled=!e;const t=this.element?.querySelector(".combo-input-field");t&&(t.disabled=this.disabled);const s=this.element?.querySelector(".combo-toggle");s&&(s.disabled=this.disabled)}setReadonly(e){this.readonly=e;const t=this.element?.querySelector(".combo-input-field");t&&(e?t.setAttribute("readonly",""):t.removeAttribute("readonly"))}focus(){const e=this.element?.querySelector(".combo-input-field");e&&e.focus()}async clear(){await this.setValue(""),this.inputValue="";const e=this.element?.querySelector(".combo-input-field");e&&(e.value=""),this.emit("clear")}getFormValue(){return this.allowCustom&&this.inputValue&&this.inputValue!==this.getDisplayValue(this.currentValue)?this.inputValue:this.currentValue}async setFormValue(e){await this.setValue(e)}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async onBeforeDestroy(){this.handleOutsideClick&&document.removeEventListener("click",this.handleOutsideClick),await super.onBeforeDestroy()}static create(e={}){return new ComboInput(e)}}class ComboBox extends e.View{constructor(e={}){super(e),this.name=e.name||"combo",this.placeholder=e.placeholder||e.placeHolder||"Type or select...",this.value=e.value||"",this.options=(e.options||[]).map(e=>"string"==typeof e?{label:e,value:e}:"object"==typeof e&&null!==e?e:{label:e,value:e}),this.allowCustom=!1!==e.allowCustom,this.disabled=e.disabled||!1,this.required=e.required||!1,this.maxHeight=e.maxHeight||300,this.filteredOptions=[...this.options],this.highlightedIndex=-1,this.isOpen=!1,this.template='\n <div class="combobox-container">\n <div class="input-group">\n <input type="text"\n class="form-control combobox-input"\n placeholder="{{placeholder}}"\n value="{{value}}"\n {{#disabled}}disabled{{/disabled}}\n {{#required}}required{{/required}}\n data-action="combobox-input"\n autocomplete="off">\n <button class="btn btn-outline-secondary combobox-toggle"\n type="button"\n data-action="combobox-toggle"\n {{#disabled}}disabled{{/disabled}}>\n <i class="bi bi-chevron-down"></i>\n </button>\n </div>\n <div class="dropdown-menu combobox-dropdown"\n style="max-height: {{maxHeight}}px; overflow-y: auto; width: 100%;">\n <div data-region="dropdown-items"></div>\n {{^allowCustom}}\n <div class="combobox-no-match dropdown-item text-muted" style="display: none;">\n No matches found\n </div>\n {{/allowCustom}}\n </div>\n </div>\n ',this.itemTemplate='\n {{#items}}\n <button type="button"\n class="dropdown-item combobox-item {{#highlighted}}active{{/highlighted}}"\n data-action="select-item"\n data-value="{{value}}"\n data-index="{{index}}">\n {{label}}\n </button>\n {{/items}}\n '}async onInit(){await super.onInit()}async onAfterRender(){if(await super.onAfterRender(),this.input=this.element.querySelector(".combobox-input"),this.dropdown=this.element.querySelector(".combobox-dropdown"),this.dropdownItems=this.element.querySelector('[data-region="dropdown-items"]'),this.noMatchDiv=this.element.querySelector(".combobox-no-match"),this.value&&this.input){const e=this.options.find(e=>e.value===this.value);e?this.input.value=e.label||e.value:this.allowCustom&&(this.input.value=this.value)}this.renderItems(),this.setupEventListeners()}setupEventListeners(){this.input.addEventListener("focus",()=>this.openDropdown()),this.input.addEventListener("input",e=>this.handleInput(e)),this.input.addEventListener("keydown",e=>this.handleKeydown(e)),document.addEventListener("click",e=>{this.element.contains(e.target)||this.closeDropdown()})}handleInput(e){const t=e.target.value.toLowerCase();this.filteredOptions=this.options.filter(e=>(e.label||e.value).toLowerCase().includes(t)),this.highlightedIndex=-1,this.renderItems(),this.openDropdown(),!this.allowCustom&&this.noMatchDiv&&(this.noMatchDiv.style.display=0===this.filteredOptions.length?"block":"none"),this.value=e.target.value,this.emit("change",{value:this.value})}handleKeydown(e){if(!this.isOpen&&("ArrowDown"===e.key||"ArrowUp"===e.key))return this.openDropdown(),void e.preventDefault();if(this.isOpen)switch(e.key){case"ArrowDown":e.preventDefault(),this.highlightedIndex=Math.min(this.highlightedIndex+1,this.filteredOptions.length-1),this.renderItems(),this.scrollToHighlighted();break;case"ArrowUp":e.preventDefault(),this.highlightedIndex=Math.max(this.highlightedIndex-1,-1),this.renderItems(),this.scrollToHighlighted();break;case"Enter":e.preventDefault(),this.highlightedIndex>=0&&this.selectItem(this.filteredOptions[this.highlightedIndex]);break;case"Escape":e.preventDefault(),this.closeDropdown();break;case"Tab":this.closeDropdown()}}scrollToHighlighted(){if(this.highlightedIndex<0)return;const e=this.dropdownItems.querySelectorAll(".combobox-item")[this.highlightedIndex];e&&e.scrollIntoView({block:"nearest"})}openDropdown(){this.disabled||this.isOpen||(this.isOpen=!0,this.dropdown.classList.add("show"),""===this.input.value&&(this.filteredOptions=[...this.options],this.renderItems()))}closeDropdown(){this.isOpen&&(this.isOpen=!1,this.dropdown.classList.remove("show"),this.highlightedIndex=-1,!this.allowCustom)&&(this.options.find(e=>e.value===this.input.value||e.label===this.input.value)||""===this.input.value||(this.input.value=this.value))}selectItem(e){const t=e.value,s=e.label||e.value;this.input.value=s,this.value=t,this.closeDropdown(),this.filteredOptions=[...this.options],this.highlightedIndex=-1,this.emit("change",{value:this.value,label:s})}renderItems(){const t=this.filteredOptions.map((e,t)=>({value:e.value,label:e.label||e.value,index:t,highlighted:t===this.highlightedIndex})),s=e.Mustache.render(this.itemTemplate,{items:t});this.dropdownItems.innerHTML=s}async onActionComboboxInput(e,t){}async onActionComboboxToggle(e,t){this.isOpen?this.closeDropdown():(this.input.focus(),this.openDropdown())}async onActionSelectItem(e,t){const s=t.getAttribute("data-value"),i=this.options.find(e=>e.value===s);i&&this.selectItem(i)}getValue(){return this.value}setValue(e){if(this.value=e,!this.input)return;const t=this.options.find(t=>t.value===e);t?this.input.value=t.label||t.value:this.allowCustom&&(this.input.value=e)}setFormValue(e){this.setValue(e)}getTemplateData(){return{placeholder:this.placeholder,value:this.input?this.input.value:this.value,disabled:this.disabled,required:this.required,maxHeight:this.maxHeight,allowCustom:this.allowCustom}}}class FormView extends e.View{constructor(e={}){const{formConfig:s=e.config,fields:i,model:a=null,data:n={},defaults:l=null,errors:r={},fileHandling:o="base64",autosaveModelField:d=!1,...c}=e;super({tagName:"div",className:"form-view",...c}),t.FormPlugins.onFormViewInit?.(this),this.model=a,this.defaults=l||n,this._originalData=n,this.errors=r,this.loading=!1,this.fileHandling=o,this.autosaveModelField=d,this.customComponents=/* @__PURE__ */new Map,this.fieldStatusManagers=/* @__PURE__ */new Map,this.saveTimeouts=/* @__PURE__ */new Map,this.pendingSaveFields=/* @__PURE__ */new Map,this.batchSaveTimeout=null,this.isSaving=!1,this.data=this.prepareFormData(),this.formConfig=s||{fields:i||[]},this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:r})}prepareFormData(){const e={...this.defaults};if(this.model)if(this.model.attributes&&"object"==typeof this.model.attributes)Object.assign(e,this.model.attributes);else if("function"==typeof this.model.toJSON){const t=this.model.toJSON();Object.assign(e,t)}else"object"==typeof this.model&&this.model.constructor===Object&&Object.assign(e,this.model);return this._originalData&&Object.assign(e,this._originalData),e}getFormConfig(){const e={...this.formConfig},t=this.getApp();return this.formConfig.fields&&Array.isArray(this.formConfig.fields)?e.fields=this.formConfig.fields.filter(e=>!e.permissions||t.activeUser?.hasPermission(e.permissions)):e.fields=[],e}async renderTemplate(){return this.formBuilder.buildFormHTML()}async onAfterRender(){await super.onAfterRender(),this.data=this.prepareFormData(),this.populateFormValues(),this.initializeFormComponents(),this.initializeChangeHandlers();const e=this.getFormElement();e&&e.addEventListener("submit",e=>(e.preventDefault(),!1)),t.FormPlugins.onFormViewAfterRender?.(this)}populateFormValues(){if(this.element&&this.formConfig?.fields){this._isPopulating=!0;try{this.formConfig.fields.forEach(e=>{this.populateFieldRecursive(e)})}finally{this._isPopulating=!1}}}populateFieldRecursive(e){"group"===e.type&&e.fields?e.fields.forEach(e=>{this.populateFieldRecursive(e)}):"tabset"===e.type&&e.tabs?e.tabs.forEach(e=>{e.fields&&Array.isArray(e.fields)&&e.fields.forEach(e=>{this.populateFieldRecursive(e)})}):this.populateFieldValue(e)}populateFieldValue(t){if(!t.name||!this.element)return;const s=this.element.querySelector(`[name="${t.name}"]`);if(!s)return;const i=e.MOJOUtils.getContextData(this.data,t.name);null!=i&&this.setFieldValue(s,t,i)}initializeFormComponents(){this.initializeImageFields(),this.initializeCustomComponents(),this.initializeTagInputs(),this.initializeMultiSelectDropdowns(),this.initializeComboBoxes(),this.initializeCollectionSelects(),this.initializeCollectionMultiSelects(),this.initializeDatePickers(),this.initializeDateRangePickers(),this.initializePasswordFields()}initializeImageFields(){this.element.querySelectorAll(".image-drop-zone.droppable").length>0&&this.enableFileDrop({acceptedTypes:["image/*"],maxFileSize:10485760,multiple:!1,dropZoneSelector:".image-drop-zone.droppable",visualFeedback:!0,dragOverClass:"drag-over",dragActiveClass:"drag-active"})}initializeCustomComponents(){this.initializeTagInputs(),this.initializeCollectionSelects(),this.initializeCollectionMultiSelects(),this.initializeDatePickers(),this.initializeDateRangePickers(),this.initializeComboInputs();try{const e=(s=[])=>{s.forEach(s=>{if(s&&"group"===s.type&&Array.isArray(s.fields))e(s.fields);else if(s&&s.name){const e=this.element.querySelector(`[name="${s.name}"], #${s.id||s.name}`);e&&t.FormPlugins.onFieldInit?.(this,e,s)}})};e(this.formConfig?.fields||[])}catch(e){console.warn("FormPlugins.onFieldInit error:",e)}this.element.querySelectorAll("[data-component]").forEach(e=>{e.getAttribute("data-component"),e.getAttribute("data-field")})}initializeChangeHandlers(){if(!this.element)return;const e=this.element.querySelectorAll("input:not([data-action]), select:not([data-action]), textarea:not([data-action])");e.length,e.forEach(e=>{e.type,e.name,e.getAttribute("data-change-action"),e.hasAttribute("data-component")||e.hasAttribute("data-change-action")||e.classList.contains("form-check-input")||(e.addEventListener("change",t=>{if(this._isPopulating)return;const s=e.name;if(s){let i=e.value;if("checkbox"===e.type)i=e.checked;else if("radio"===e.type){if(!e.checked)return}else if(e.multiple&&e.selectedOptions)i=Array.from(e.selectedOptions).map(e=>e.value);else if("file"===e.type){const s=e.getAttribute("data-change-action");if("image-selected"===s)return void this.onChangeImageSelected(t,e);if("file-selected"===s)return void this.onChangeFileSelected(t,e)}this.handleFieldChange(s,i)}}),"text"!==e.type&&"email"!==e.type&&"url"!==e.type&&"TEXTAREA"!==e.tagName||e.addEventListener("input",t=>{if(this._isPopulating)return;const s=e.name;s&&this.handleFieldChange(s,e.value)}))})}initializeTagInputs(){this.element.querySelectorAll('[data-field-type="tag"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),s=e.getAttribute("data-field-config"),i=JSON.parse(s),a=new TagInputView({...i,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){}})}initializeMultiSelectDropdowns(){this.element.querySelectorAll('[data-field-type="multiselect"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=this.getFormFieldConfig(s);if(!n)return;const l=new MultiSelectDropdown({...a,options:n.options||[],placeholder:n.placeholder||a.placeholder||"Select...",label:n.label,containerId:null});let r=a.value??e.MOJOUtils.getContextData(this.data,s);r&&l.setFormValue(r),l.render(!0,t),this.customComponents.set(s,l),l.on("change",e=>{this.handleFieldChange(s,e.value)})}catch(s){console.error("MultiSelectDropdown initialization failed:",s)}})}initializeComboBoxes(){this.element.querySelectorAll('[data-field-type="combobox"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=this.getFormFieldConfig(s);if(!n)return;const l=new ComboBox({...a,options:n.options||[],placeholder:n.placeholder||a.placeholder||"Type or select...",containerId:null});let r=a.value??e.MOJOUtils.getContextData(this.data,s);r&&l.setFormValue(r),l.render(!0,t),this.customComponents.set(s,l),l.on("change",e=>{this.handleFieldChange(s,e.value)})}catch(s){console.error("ComboBox initialization failed:",s)}})}initializeCollectionSelects(){this.element.querySelectorAll('[data-field-type="collection"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=this.getFormFieldConfig(s);if(!n||!n.Collection)return;const l=new n.Collection;n.collectionParams&&(l.params={...l.params,...n.collectionParams});const r=new CollectionSelectView({...a,collection:l,defaultParams:n.defaultParams||null,containerId:null});let o=e.MOJOUtils.getContextData(this.data,s);o&&r.setFormValue(o),r.render(!0,t),this.customComponents.set(s,r),r.on("change",e=>{this.handleFieldChange(s,e.value)})}catch(s){}})}initializeCollectionMultiSelects(){this.element.querySelectorAll('[data-field-type="collectionmultiselect"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=this.getFormFieldConfig(s);if(!n||!n.Collection)return;const l=new n.Collection;n.collectionParams&&(l.params={...l.params,...n.collectionParams});const r=new CollectionMultiSelectView({...a,collection:l,defaultParams:n.defaultParams||null,itemTemplate:n.itemTemplate||null,containerId:null});let o=e.MOJOUtils.getContextData(this.data,s);o&&r.setFormValue(o),r.render(!0,t),this.customComponents.set(s,r),r.on("change",e=>{this.handleFieldChange(s,e.value)})}catch(s){console.error("CollectionMultiSelect initialization failed:",s)}})}initializeDatePickers(){this.element.querySelectorAll('[data-field-type="datepicker"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),s=e.getAttribute("data-field-config"),i=JSON.parse(s),a=new DatePicker({...i,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){}})}initializeDateRangePickers(){this.element.querySelectorAll('[data-field-type="daterange"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),s=e.getAttribute("data-field-config"),i=JSON.parse(s),a=new DateRangePicker({...i,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.combined)})}catch(t){}})}initializeComboInputs(){this.element.querySelectorAll('[data-field-type="combo"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=new ComboInput({...a,containerId:null});let l=e.MOJOUtils.getContextData(this.data,s);l&&n.setValue(l),n.render(!0,t),this.customComponents.set(s,n),n.on("change",e=>{this.handleFieldChange(s,e.value)}),n.on("select",e=>{this.emit("field:select",{field:s,value:e.value,option:e.option,meta:e.meta})})}catch(s){console.error("ComboInput initialization failed:",s)}})}handleFieldChange(e,s){this._isPopulating||(this.data[e]=s,this.autosaveModelField&&this.model?this.handleFieldSave(e,s):this.model&&this.options.allowModelChange&&(this._isFormDrivenChange=!0,this.model.set(e,s)),this.emit("field:change",{field:e,value:s}),t.FormPlugins.onFieldChange?.(this,e,s))}async handleFieldSave(e,t){this.model&&(this.pendingSaveFields.set(e,t),this.getFieldStatusManager(e).showStatus("saving"),this.batchSaveTimeout&&clearTimeout(this.batchSaveTimeout),this.batchSaveTimeout=setTimeout(async()=>{await this.executeBatchSave()},300))}async executeBatchSave(){if(this.isSaving||0===this.pendingSaveFields.size)return;const e=Object.fromEntries(this.pendingSaveFields),t=Array.from(this.pendingSaveFields.keys());try{if(this.isSaving=!0,this.pendingSaveFields.clear(),this.batchSaveTimeout=null,this._isFormDrivenChange=!0,"function"==typeof this.model.save){const s=await this.model.save(e);if(!s||!s.success||s.data&&!s.data.status){const e=s?.data?.error||s?.error||s?.message||"Save failed";return this.getApp()?.toast?.error(e),this.revertFields(t),void t.forEach(t=>{this.getFieldStatusManager(t).showStatus("error",{message:e})})}}else Object.entries(e).forEach(([e,t])=>{this.model.set(e,t)});t.forEach(e=>{this.getFieldStatusManager(e).showStatus("saved")})}catch(s){console.error("Batch save error:",s),this.getApp()?.toast?.error(s.message||"An error occurred while saving"),this.revertFields(t),t.forEach(e=>{this.getFieldStatusManager(e).showStatus("error",{message:s.message})})}finally{this.isSaving=!1}}revertFields(e){if(!this.model)return;const t=this._isPopulating;this._isPopulating=!0;try{e.forEach(e=>{const t=this.model.get(e);this.data[e]=t;const s=this.element?.querySelector(`[name="${e}"]`);if(s){const i=this.getFormFieldConfig(e);i?this.setFieldValue(s,i,t):"checkbox"===s.type?s.checked=Boolean(t):s.value=t??""}})}finally{this._isPopulating=t}}getFieldStatusManager(e){if(!this.fieldStatusManagers.has(e)){const t=this.element.querySelector(`[name="${e}"]`);if(t){const s=new FieldStatusManager(t);this.fieldStatusManagers.set(e,s)}}return this.fieldStatusManagers.get(e)}refreshForm(){this.data=this.prepareFormData(),this.element&&this.populateFormValues()}getChangeReason(e,t){if(e instanceof File)return 0===e.size||""===e.name||"blob"===e.name?"empty file, no change":`file upload: ${e.name}, ${e.size} bytes`;if("string"==typeof e&&e.startsWith("data:image/"))return"base64 image upload";if("boolean"==typeof e||"boolean"==typeof t){const s=Boolean(e);return`boolean: ${null!=t&&Boolean(t)} → ${s}`}return null!=e&&String(e).trim(),null!=t&&String(t).trim(),null==t?"was null/undefined, now has value":null==e?"was value, now null/undefined":"text content changed"}setFormData(e){this._originalData={...this._originalData,...e},this.refreshForm()}async onActionSubmitForm(e,t){e.preventDefault();const s=await this.handleSubmit();s.success?(this.data=s.data,this.emit("submit",{data:s.data,result:s.result,form:this,event:e}),!this.model&&this.formConfig.onSubmit&&"function"==typeof this.formConfig.onSubmit&&await this.formConfig.onSubmit(s.data,this)):this.emit("error",{error:s.error,result:s,form:this})}async onActionResetForm(e,t){const s=this.getFormElement();s&&(s.reset(),this.data={},this.clearAllErrors(),this.emit("reset",{form:this,event:e}))}async onActionClickImageUpload(e,t){const s=t.getAttribute("data-field-id");if(!s)return void console.error("FormView: No fieldId attribute found");const i=this.element.querySelector(`#${s}`);i&&!i.disabled?i.click():i||console.error("FormView: fileInput not found for fieldId:",s)}async onActionRemoveImage(e,t){const s=t.getAttribute("data-field");if(!s)return;const i=this.element.querySelector(`input[name="${s}"]`);i&&(i.value="",i.dispatchEvent(new Event("change",{bubbles:!0}))),delete this.data[s],this.emit("change",{field:s,value:null,form:this}),await this.updateField(s)}async onActionClearColor(e,t){const s=t.getAttribute("data-field");if(!s)return;const i=this.element.querySelector(`input[name="${s}"]`);i&&(i.value="",this.handleFieldChange(s,""),await this.updateField(s))}async onActionPreviewHtml(e,t){e.preventDefault();const s=t.getAttribute("data-target");if(!s)return;const i=this.element.querySelector(`#${s}`);if(!i)return;const a=i.value||"";(await Promise.resolve().then(()=>require("./Dialog-DW7PHzUc.js"))).default.showHtmlPreview({html:a,title:"HTML Preview"})}async onActionSelectButtonOption(e,t){const s=t.getAttribute("data-field"),i=t.getAttribute("data-value");if(!s||!i)return;this.data[s]=i;const a=t.closest(".btn-group");a&&(a.querySelectorAll("button").forEach(e=>{e.classList.remove("active"),e.classList.add("btn-outline-primary"),e.classList.remove("btn-primary")}),t.classList.add("active"),t.classList.remove("btn-outline-primary"),t.classList.add("btn-primary")),this.emit("field:changed",{field:s,value:i,form:this}),this.emit("change",{field:s,value:i,form:this}),this.emit("form:changed",await this.getFormData())}async onActionApplyFilter(e,t){const s=t.closest(".dropdown"),i=s?.querySelectorAll('input[type="checkbox"]');if(!i||0===i.length)return;const a=i[0].getAttribute("data-field");if(!a)return;const n=[];i.forEach(e=>{e.checked&&n.push(e.value)}),this.data[a]=n;const l=s.querySelector('[data-bs-toggle="dropdown"]');if(l&&window.bootstrap?.Dropdown){const e=window.bootstrap.Dropdown.getInstance(l);e?.hide()}this.emit("field:changed",{field:a,value:n,form:this}),this.emit("change",{field:a,value:n,form:this}),this.emit("form:changed",await this.getFormData())}async onChangeValidateField(e,t){const s=t.name;if(s){const e=t.value;this.handleFieldChange(s,e),this.validateField(s)}}async onChangeToggleSwitch(e,t){const s=t.getAttribute("data-field");if(s){const e=t.checked;this.handleFieldChange(s,e),this.emit("switch:toggle",{field:s,checked:e,form:this})}}async onChangeImageSelected(e,t){t.files;const s=t.getAttribute("data-field"),i=t.files[0];if(s&&i){const e=this.findFieldConfig(s),n=URL.createObjectURL(i);if(e&&e.imageSize){e.imageSize;try{const a=window.MOJO?.plugins?.ImageCropView;if(!a)return this.data[s]=i,await this.updateImagePreview(s,n),void this.emit("image:selected",{field:s,file:i,form:this});const l=await a.showDialog(n,{title:`Crop ${e.label||s}`,cropAndScale:e.imageSize,size:"lg"});if("crop"===l.action&&l.data){const e=await fetch(l.data),t=await e.blob(),a=new File([t],i.name,{type:i.type||"image/png"});this.data[s]=a,await this.updateImagePreview(s,l.data),this.emit("image:selected",{field:s,file:a,originalFile:i,cropped:!0,cropData:l.cropData,form:this}),this.emit("change",{field:s,value:a,form:this})}else t.value=""}catch(a){console.error("FormView: Error during image cropping:",a),this.data[s]=i,await this.updateImagePreview(s,n),this.emit("image:selected",{field:s,file:i,form:this}),this.emit("change",{field:s,value:i,form:this})}}else this.data[s]=i,await this.updateImagePreview(s,n),this.emit("image:selected",{field:s,file:i,form:this})}}async onChangeFileSelected(e,t){const s=Array.from(t.files);t.multiple?this.data[t.name]=t.files:this.data[t.name]=s[0]||null,this.emit("file:selected",{field:t.name,files:s,form:this}),this.emit("change",{field:t.name,value:s,form:this})}async onChangeRangeChanged(e,t){const s=t.name,i=t.value,a=t.getAttribute("data-target");if(a){const e=this.element.querySelector(`#${a}`);e&&(e.textContent=i)}s&&this.handleFieldChange(s,i),this.emit("range:changed",{field:s,value:i,form:this})}async onChangeFilterSearch(e,t){const s=t.value;this.emit("search",{query:s,field:t.name,form:this})}async onChangeFilterSelectOptions(e,t){const s=t.value.toLowerCase(),i=t.getAttribute("data-target"),a=i?this.element.querySelector(`#${i}`):null;a&&a.querySelectorAll("option").forEach(e=>{const t=e.textContent.toLowerCase();e.style.display=t.includes(s)?"":"none"})}async onFileDrop(e,t,s){const i=t.target.closest(".image-drop-zone");if(!i)return;const a=i.getAttribute("data-field");if(!a)return;const n=e[0],l=this.element.querySelector(`input[name="${a}"]`);if(l){const e=new DataTransfer;e.items.add(n),l.files=e.files,l.dispatchEvent(new Event("change",{bubbles:!0}))}this.data[a]=n;const r=URL.createObjectURL(n);await this.updateImagePreview(a,r),this.emit("image:dropped",{field:a,file:n,form:this})}async onFileDropError(e,t,s){this.showError(`File upload error: ${e.message}`),this.emit("file:error",{error:e,files:s,form:this})}getFormElement(){return this.element?this.element.querySelector("form"):null}getFormFieldConfig(e){const t=s=>{for(const i of s){if(i.name===e)return i;if(i.fields&&Array.isArray(i.fields)){const e=t(i.fields);if(e)return e}}return null};return t(this.formConfig.fields||[])}async getFormData(){const e=this.getFormElement();if(!e)return"multipart"===this.fileHandling?new FormData:{};if("multipart"===this.fileHandling){const t=new FormData(e);for(const[e,s]of Object.entries(this.data))if(s instanceof File)t.set(e,s);else if(s instanceof FileList)for(let i=0;i<s.length;i++)t.append(`${e}[${i}]`,s[i]);return t}{const s=new FormData(e),i={};for(const[e,t]of s.entries())i[e]?(Array.isArray(i[e])||(i[e]=[i[e]]),i[e].push(t)):i[e]=t;e.querySelectorAll('input[type="checkbox"]').forEach(e=>{i[e.name]=e.checked}),e.querySelectorAll('input[type="number"]').forEach(e=>{if(e.name&&void 0!==i[e.name]&&""!==i[e.name]){const t=Number(i[e.name]);isNaN(t)||(i[e.name]=t)}}),this.formConfig.fields?.forEach(e=>{if("select"===e.type&&e.name&&void 0!==i[e.name]){const t=this.getFormFieldConfig(e.name);if(t?.options&&Array.isArray(t.options)&&t.options.every(e=>{const t="object"==typeof e?e.value:e;return""===t||!isNaN(Number(t))})&&""!==i[e.name]){const t=Number(i[e.name]);isNaN(t)||(i[e.name]=t)}}}),e.querySelectorAll('[data-field-type="json"]').forEach(e=>{try{i[e.name]=JSON.parse(e.value)}catch(t){i[e.name]=e.value}}),this.customComponents.forEach((e,t)=>{e.getFormValue?i[t]=e.getFormValue():e.getValue&&(i[t]=e.getValue())});for(const[e,a]of Object.entries(this.data))if(a instanceof File)try{i[e]=await this.fileToBase64(a)}catch(t){i[e]=null}else if(a instanceof FileList){const s=[];for(let e=0;e<a.length;e++)try{s.push(await this.fileToBase64(a[e]))}catch(t){s.push(null)}i[e]=s}return i}}_onModelChange(){this.isSaving||(this.data=this.prepareFormData(),this.isMounted()&&(this._isFormDrivenChange||this.syncFormWithModel(),this._isFormDrivenChange=!1))}syncFormWithModel(){this.model&&this.element&&(this.formDataMatchesModelData(this.data)||this.populateFormValues())}formDataMatchesModelData(e){if(!this.formConfig?.fields||!this.element)return!0;for(const t of this.formConfig.fields)if("group"===t.type&&t.fields){for(const s of t.fields)if(!this.fieldValueMatchesModel(s,e))return!1}else if(!this.fieldValueMatchesModel(t,e))return!1;return!0}fieldValueMatchesModel(t,s){if(!t.name)return!0;const i=this.element.querySelector(`[name="${t.name}"]`);if(!i)return!0;const a=this.getFieldCurrentValue(i,t),n=e.MOJOUtils.getContextData(s,t.name);return!1===this.valuesAreDifferent(a,n)}getFieldCurrentValue(e,t){switch(t.type){case"checkbox":case"toggle":case"switch":return e.checked;case"radio":const i=this.element.querySelector(`[name="${t.name}"]:checked`);return i?i.value:"";case"select":return e.multiple?Array.from(e.selectedOptions).map(e=>e.value):e.value;case"file":case"image":return null;case"json":try{return e.value?JSON.parse(e.value):null}catch(s){return e.value}default:return e.value}}setFieldValue(e,t,s){switch(t.type){case"checkbox":case"toggle":case"switch":e.checked=Boolean(s);break;case"radio":const a=this.element.querySelector(`[name="${t.name}"][value="${s}"]`);a&&(a.checked=!0);break;case"select":e.multiple&&Array.isArray(s)?Array.from(e.options).forEach(e=>{e.selected=s.includes(e.value)}):e.value=s??"";break;case"file":case"image":break;case"json":if("object"==typeof s&&null!==s)try{e.value=JSON.stringify(s,null,2)}catch(i){e.value="{}"}else e.value="string"==typeof s?s:String(s||"");break;default:e.value=s||""}e.dispatchEvent(new Event("change",{bubbles:!0}))}setDefaults(e){this.defaults={...this.defaults,...e},this.refreshForm()}async handleSubmit(){try{const e=await this.getFormData();if(!1!==this.formConfig.validateOnSubmit&&!this.validate())return this.focusFirstError(),{success:!1,data:e,error:"Form validation failed"};if(this.model&&"function"==typeof this.model.save){const t=await this.saveModel(e);return t&&!1!==t.success?{success:!0,data:e,result:t}:{success:!1,data:e,result:t,error:t?.message||t?.error||"Save failed. Please try again."}}return e}catch(e){return{success:!1,error:e.message||"An error occurred while submitting the form"}}}async saveModel(e=null){if(!this.model||"function"!=typeof this.model.save)throw new Error("No model available for saving");e||(e=await this.getFormData());const t=this.getChangedData(e);if(!t||0===Object.keys(t).length)return{success:!0,message:"No changes to save",data:e};try{return this._isFormDrivenChange=!0,await this.model.save(t)}catch(s){throw s}}getChangedData(e){if(!this.model)return e;const t=this.getOriginalModelData();let s;return s=e instanceof FormData?this.getChangedFormData(e,t):this.getChangedObjectData(e,t),s}getOriginalModelData(){return this.model.attributes?this.model.attributes:"function"==typeof this.model.toJSON?this.model.toJSON():{}}getChangedFormData(e,t){const s=new FormData;let i=!1;for(const[a,n]of e.entries())if(n instanceof File)0===n.size||""===n.name||"blob"===n.name||(s.set(a,n),i=!0);else{const e=t[a];n!==e&&n!==String(e)&&(s.set(a,n),i=!0)}return i?s:null}getChangedObjectData(e,t){const s={};let i=!1;const a=/* @__PURE__ */new Set([...Object.keys(t),...Object.keys(e)]),n=(e,t)=>t.split(".").reduce((e,t)=>e&&"object"==typeof e?e[t]:void 0,e);for(const l of a){const a=this.findFieldConfig(l);if(!a)continue;const r=e[l],o=n(t,l),d=a.type||"text";this.valuesAreDifferent(r,o,d,a)&&(s[l]=r,i=!0)}return i?s:null}valuesAreDifferent(e,t,s="text",i={}){if(e instanceof File)return e.size>0&&""!==e.name&&"blob"!==e.name;if("string"==typeof e&&e.startsWith("data:image/"))return!0;if("collection"===s&&"object"==typeof t&&null!=t&&"string"==typeof e){if("0"===e)return null!==t;if(t[i.valueField||"id"]==e)return!1}return"switch"===s||"checkbox"===s||"toggle"===s?!!e!=!!t:(null==e?"":String(e).trim())!==(null==t?"":String(t).trim())}validate(){const e=this.getFormElement();if(!e)return!1;const t=e.checkValidity();return t||e.classList.add("was-validated"),t}validateField(e){const t=this.getFormElement();if(!t)return!1;const s=t.elements[e];if(!s)return!1;const i=s.checkValidity();return i?(s.classList.remove("is-invalid"),s.classList.add("is-valid"),delete this.errors[e]):(s.classList.remove("is-valid"),s.classList.add("is-invalid"),this.errors[e]=s.validationMessage),i}focusFirstError(){const e=this.getFormElement();if(!e)return;const t=e.querySelector(":invalid");if(!t)return;const s=t.closest(".tab-pane");if(s&&!s.classList.contains("active")){const t=s.id,i=e.querySelector(`[role="tab"][aria-controls="${t}"], [data-bs-target="#${t}"]`);if(i){const t=window.bootstrap?.Tab?.getOrCreateInstance?window.bootstrap.Tab.getOrCreateInstance(i):null;t&&"function"==typeof t.show?t.show():(e.querySelectorAll('[role="tab"].nav-link').forEach(e=>{const t=e===i;e.classList.toggle("active",t),e.setAttribute("aria-selected",t?"true":"false")}),e.querySelectorAll(".tab-pane").forEach(e=>e.classList.remove("show","active")),s.classList.add("show","active"))}}t.focus(),t.scrollIntoView({behavior:"smooth",block:"center"})}clearAllErrors(){const e=this.getFormElement();e&&(this.errors={},e.classList.remove("was-validated"),e.querySelectorAll(".is-invalid").forEach(e=>e.classList.remove("is-invalid")),e.querySelectorAll(".is-valid").forEach(e=>e.classList.remove("is-valid")))}setLoading(e){this.loading=e;const t=this.getFormElement();if(!t)return;const s=t.querySelectorAll("input, select, textarea, button"),i=t.querySelector('button[type="submit"]');if(e)s.forEach(e=>e.disabled=!0),i&&(i.innerHTML='<span class="spinner-border spinner-border-sm me-2"></span>Loading...');else if(s.forEach(e=>e.disabled=!1),i){const e=this.formConfig.options?.submitButton||"Submit";i.innerHTML="string"==typeof e?e:"Submit"}}showError(e){if(this.emit("error",{message:e,form:this}),this.element){this.element.querySelectorAll(".alert").forEach(e=>e.remove());const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.remove()},5e3)}}async updateField(e){this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:this.errors}),await this.render()}async updateImagePreview(e,t){const s=this.element.querySelector(`[data-field="${e}"].image-drop-zone`);if(!s)return;let i=s.querySelector("img");const a=s.querySelector(".bi-image")?.parentElement,n=s.getAttribute("data-field-id");if(t){if(i)i.src=t;else{const i=`${n}_preview`;s.innerHTML=`\n <img id="${i}"\n src="${t}"\n alt="Preview"\n class="img-thumbnail w-100 h-100"\n style="object-fit: cover;">\n <button type="button"\n class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"\n data-action="remove-image"\n data-field-id="${n}"\n data-field="${e}"\n style="opacity: 0.8;">\n <i class="bi bi-x"></i>\n </button>\n `}a&&(a.style.display="none")}}findFieldConfig(e){const t=s=>{for(const i of s){if(i.name===e)return i;if(i.fields&&Array.isArray(i.fields)){const e=t(i.fields);if(e)return e}if(i.tabs&&Array.isArray(i.tabs))for(const e of i.tabs)if(e.fields&&Array.isArray(e.fields)){const s=t(e.fields);if(s)return s}}return null};return t(this.formConfig.fields||[])}async fileToBase64(e){return new Promise((t,s)=>{const i=new FileReader;i.onload=()=>t(i.result),i.onerror=s,i.readAsDataURL(e)})}hasFiles(e){if(e instanceof FormData){for(const[t,s]of e.entries())if(s instanceof File)return!0;return!1}for(const t of Object.values(e)){if(t instanceof File)return!0;if(Array.isArray(t)&&t.some(e=>e instanceof File))return!0}return!1}reset(){const e=this.getFormElement();e&&e.reset(),this.data={},this.errors={},this.clearAllErrors(),this.emit("reset",{form:this})}async updateConfig(e){this.formConfig={...this.formConfig,...e},this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:this.errors}),await this.render()}async onBeforeDestroy(){const e=[];for(const t of this.customComponents.values())t.destroy&&e.push(t.destroy());await Promise.all(e),this.customComponents.clear(),Object.values(this.data).forEach(e=>{"string"==typeof e&&e.startsWith("blob:")&&URL.revokeObjectURL(e)}),await super.onBeforeDestroy()}initializePasswordFields(){this.element&&this.element.querySelectorAll('input[data-field-type="password"], input[type="password"]').forEach(e=>{this.updatePasswordStrengthUI(e),e.addEventListener("input",e=>{this.updatePasswordStrengthUI(e.target)});const t=t=>{if("function"==typeof t.getModifierState){const s=t.getModifierState("CapsLock");this.updateCapsLockWarning(e,!!s)}};e.addEventListener("keydown",t),e.addEventListener("keyup",t),this.updateCapsLockWarning(e,!1)})}async onActionTogglePassword(e,t){e.preventDefault();const s=t.getAttribute("data-target");let i=null;if(s&&(i=this.element.querySelector("#"+s)),!i){const e=t.closest(".input-group");e&&(i=e.querySelector('input[type="password"], input[data-field-type="password"], input[type="text"]'))}if(!i)return;const a="password"===i.type;i.type=a?"text":"password",t.setAttribute("aria-pressed",a?"true":"false"),t.setAttribute("aria-label",a?"Hide password":"Show password");const n=t.querySelector("i");n&&(n.classList.toggle("bi-eye",!a),n.classList.toggle("bi-eye-slash",a)),i.focus();try{const e=i.value?.length??0;i.setSelectionRange(e,e)}catch(l){}}async onActionCopyToClipboard(e,t){e.preventDefault();const s=t.getAttribute("data-target");if(!s)return;const i=this.element.querySelector("#"+s);if(i)try{await navigator.clipboard.writeText(i.value);const e=t.querySelector("i");if(e){const s=e.className;e.className="bi bi-check2",t.classList.add("btn-success"),t.classList.remove("btn-outline-secondary"),setTimeout(()=>{e.className=s,t.classList.remove("btn-success"),t.classList.add("btn-outline-secondary")},1500)}this.app&&this.app.toast&&this.app.toast.success("Copied to clipboard!")}catch(a){console.error("Failed to copy to clipboard:",a),this.app&&this.app.toast&&this.app.toast.error("Failed to copy to clipboard")}}computePasswordStrength(e=""){const t=e.length;let s=0;t>=6&&s++,t>=8&&s++,t>=12&&s++;const i=[/[a-z]/.test(e),/[A-Z]/.test(e),/\d/.test(e),/[^A-Za-z0-9]/.test(e)].filter(Boolean).length;return i>=2&&s++,i>=3&&s++,s=Math.max(0,Math.min(4,s)),[{percent:0,label:"Too short",barClass:"bg-secondary"},{percent:25,label:"Weak",barClass:"bg-danger"},{percent:50,label:"Fair",barClass:"bg-warning"},{percent:75,label:"Good",barClass:"bg-info"},{percent:100,label:"Strong",barClass:"bg-success"}][s]}updatePasswordStrengthUI(e){if(!e||!e.id)return;const t=this.element.querySelector(`#${e.id}_strength_bar`),s=this.element.querySelector(`#${e.id}_strength_text`);if(!t&&!s)return;const{percent:i,label:a,barClass:n}=this.computePasswordStrength(e.value||"");t&&(t.className=`progress-bar ${n}`,t.style.width=`${i}%`,t.setAttribute("aria-valuenow",String(i))),s&&(s.textContent=a)}updateCapsLockWarning(e,t){if(!e||!e.id)return;const s=this.element.querySelector(`#${e.id}_caps_warning`);s&&(t?s.classList.remove("d-none"):s.classList.add("d-none"))}}class FieldStatusManager{constructor(e){this.fieldElement=e,this.statusContainer=this.findOrCreateStatusContainer(),this.timeouts=/* @__PURE__ */new Map}findOrCreateStatusContainer(){let e=this.fieldElement.parentElement.querySelector(".field-status-label-inline");if(!e){const t=this.findFieldLabel();t&&(e=t.querySelector(".field-status-label-inline"))}return e||(e=this.createStatusContainer()),e}createStatusContainer(){const e=this.getFieldType();this.getPlacementStrategy(e);const t=document.createElement("div");return this.createLabelInlineContainer(t)}getFieldType(){const e=this.fieldElement.tagName.toLowerCase(),t=this.fieldElement.type?.toLowerCase(),s=this.fieldElement.className;return"checkbox"===t||s.includes("form-check-input")||s.includes("form-switch")?"toggle":"select"===e?"select":"textarea"===e?"textarea":"input"}getPlacementStrategy(e){return"label-inline"}createLabelInlineContainer(e){e.className="field-status-label-inline",e.innerHTML=this.getStatusHTML();const t=this.findFieldLabel();return t?t.appendChild(e):this.fieldElement.parentElement.appendChild(e),e}findFieldLabel(){if(this.fieldElement.id){const e=document.querySelector(`label[for="${this.fieldElement.id}"]`);if(e)return e}const e=this.fieldElement.parentElement.querySelector("label");if(e)return e;return this.fieldElement.closest("label")||null}createInputOverlayContainer(e){e.className="field-status-overlay",e.innerHTML=this.getStatusHTML();const t=this.fieldElement.parentElement;return"static"===getComputedStyle(t).position&&(t.style.position="relative"),t.appendChild(e),e}createFullOverlayContainer(e){e.className="field-status-full-overlay d-none",e.innerHTML='\n <div class="saving-indicator">\n <div class="spinner-border spinner-border-sm text-primary" role="status">\n <span class="visually-hidden">Saving...</span>\n </div>\n <span class="ms-2">Saving...</span>\n </div>\n <div class="success-indicator d-none">\n <i class="bi bi-check-circle text-success"></i>\n <span class="ms-2">Saved</span>\n </div>\n <div class="error-indicator d-none">\n <i class="bi bi-exclamation-circle text-danger"></i>\n <span class="ms-2">Error saving</span>\n </div>\n ';const t=this.fieldElement.parentElement;return"static"===getComputedStyle(t).position&&(t.style.position="relative"),t.appendChild(e),e}getStatusHTML(){return'\n <div class="spinner-border spinner-border-sm text-primary d-none" data-status="saving" role="status">\n <span class="visually-hidden">Saving...</span>\n </div>\n <i class="bi bi-check-circle text-success d-none" data-status="saved"></i>\n <i class="bi bi-exclamation-circle text-danger d-none" data-status="error"></i>\n '}showStatus(e,t={}){this.clearTimeout(e),this.showStandardStatus(e,t)}showStandardStatus(e,t={}){this.hideAllStatuses();const s=this.statusContainer.querySelector(`[data-status="${e}"]`);s&&(s.classList.remove("d-none"),s.classList.add("d-inline-block","show"),"saved"===e?this.setTimeout(e,()=>this.hideStatus(e),2500):"error"===e&&(t.message&&(s.title=t.message),this.setTimeout(e,()=>this.hideStatus(e),6e3)))}showFullOverlayStatus(e,t={}){let s;switch(this.statusContainer.querySelectorAll(".saving-indicator, .success-indicator, .error-indicator").forEach(e=>e.classList.add("d-none")),this.statusContainer.classList.remove("d-none"),e){case"saving":s=".saving-indicator";break;case"saved":s=".success-indicator",this.setTimeout(e,()=>this.hideStatus(e),2500);break;case"error":if(s=".error-indicator",t.message){const e=this.statusContainer.querySelector(".error-indicator span");e&&(e.textContent=t.message)}this.setTimeout(e,()=>this.hideStatus(e),6e3)}if(s){const e=this.statusContainer.querySelector(s);e&&e.classList.remove("d-none")}}hideStatus(e){const t=this.statusContainer.querySelector(`[data-status="${e}"]`);t&&(t.classList.remove("show"),t.classList.add("hide"),setTimeout(()=>{t.classList.add("d-none"),t.classList.remove("d-inline-block","hide"),t.title=""},300))}hideAllStatuses(){this.statusContainer.querySelectorAll("[data-status]").forEach(e=>{e.classList.add("d-none"),e.classList.remove("d-inline-block","show","hide"),e.title=""})}setTimeout(e,t,s){const i=setTimeout(t,s);this.timeouts.set(e,i)}clearTimeout(e){this.timeouts.has(e)&&(clearTimeout(this.timeouts.get(e)),this.timeouts.delete(e))}destroy(){this.timeouts.forEach(e=>clearTimeout(e)),this.timeouts.clear()}}i(FormView);const a=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,FormView:FormView,default:FormView},Symbol.toStringTag,{value:"Module"}));exports.FormView=FormView,exports.FormView$1=a,exports.applyFileDropMixin=i;
|
|
3
|
-
//# sourceMappingURL=FormView-EoB_ZdIB.js.map
|