web-mojo 2.2.4 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.es.js +12 -12
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +4 -4
- package/dist/chunks/{ChatView-DoA8h81E.js → ChatView-BLe7SAMj.js} +2 -2
- package/dist/chunks/{ChatView-DoA8h81E.js.map → ChatView-BLe7SAMj.js.map} +1 -1
- package/dist/chunks/{ChatView-DZ7f2Rl5.js → ChatView-BcPvM8jM.js} +13 -19
- package/dist/chunks/{ChatView-DZ7f2Rl5.js.map → ChatView-BcPvM8jM.js.map} +1 -1
- package/dist/chunks/{Collection-CREl6Fa9.js → Collection-BgX6OcUC.js} +2 -2
- package/dist/chunks/{Collection-CREl6Fa9.js.map → Collection-BgX6OcUC.js.map} +1 -1
- package/dist/chunks/{Collection-DJaa8dhS.js → Collection-G5_fJcpB.js} +2 -2
- package/dist/chunks/{Collection-DJaa8dhS.js.map → Collection-G5_fJcpB.js.map} +1 -1
- package/dist/chunks/{ContextMenu-DvsDXgOe.js → ContextMenu-C4_MWleT.js} +2 -2
- package/dist/chunks/{ContextMenu-DvsDXgOe.js.map → ContextMenu-C4_MWleT.js.map} +1 -1
- package/dist/chunks/{ContextMenu-CvLNRuJb.js → ContextMenu-CMoik8q6.js} +3 -3
- package/dist/chunks/{ContextMenu-CvLNRuJb.js.map → ContextMenu-CMoik8q6.js.map} +1 -1
- package/dist/chunks/{DataView-Bm8dffDn.js → DataView-BYNjIf--.js} +2 -2
- package/dist/chunks/{DataView-Bm8dffDn.js.map → DataView-BYNjIf--.js.map} +1 -1
- package/dist/chunks/{DataView-gc1VouWJ.js → DataView-i7kpJjVQ.js} +2 -2
- package/dist/chunks/{DataView-gc1VouWJ.js.map → DataView-i7kpJjVQ.js.map} +1 -1
- package/dist/chunks/{Dialog-Cl_rBFSh.js → Dialog-C-Xl4SYW.js} +2 -2
- package/dist/chunks/{Dialog-Cl_rBFSh.js.map → Dialog-C-Xl4SYW.js.map} +1 -1
- package/dist/chunks/{Dialog-LPU__Q7Z.js → Dialog-D_TcKXUc.js} +5 -5
- package/dist/chunks/{Dialog-LPU__Q7Z.js.map → Dialog-D_TcKXUc.js.map} +1 -1
- package/dist/chunks/{FormView-DMMUiciW.js → FormView-DCtLjTmF.js} +2 -2
- package/dist/chunks/{FormView-DMMUiciW.js.map → FormView-DCtLjTmF.js.map} +1 -1
- package/dist/chunks/{FormView-Cc5iagqS.js → FormView-UtTgxKhq.js} +2 -2
- package/dist/chunks/{FormView-Cc5iagqS.js.map → FormView-UtTgxKhq.js.map} +1 -1
- package/dist/chunks/{ListView-Xcsfu4IL.js → ListView-9b-jQTQZ.js} +2 -2
- package/dist/chunks/{ListView-Xcsfu4IL.js.map → ListView-9b-jQTQZ.js.map} +1 -1
- package/dist/chunks/{ListView-8FqhYWRU.js → ListView-C9OoIPfC.js} +3 -3
- package/dist/chunks/{ListView-8FqhYWRU.js.map → ListView-C9OoIPfC.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-CsawNLeS.js → MetricsMiniChartWidget-CJZo6Mzi.js} +4 -4
- package/dist/chunks/{MetricsMiniChartWidget-CsawNLeS.js.map → MetricsMiniChartWidget-CJZo6Mzi.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-BuvhT5NG.js → MetricsMiniChartWidget-DDDtbxVu.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-BuvhT5NG.js.map → MetricsMiniChartWidget-DDDtbxVu.js.map} +1 -1
- package/dist/chunks/{PDFViewer-Dr72gJt1.js → PDFViewer-BGvS4SV2.js} +3 -3
- package/dist/chunks/{PDFViewer-Dr72gJt1.js.map → PDFViewer-BGvS4SV2.js.map} +1 -1
- package/dist/chunks/{PDFViewer-Bg06w3Cu.js → PDFViewer-CBMSMWI8.js} +2 -2
- package/dist/chunks/{PDFViewer-Bg06w3Cu.js.map → PDFViewer-CBMSMWI8.js.map} +1 -1
- package/dist/chunks/{Rest-BoKp2Hj4.js → Rest-ChN4Ntac.js} +97 -14
- package/dist/chunks/Rest-ChN4Ntac.js.map +1 -0
- package/dist/chunks/Rest-DhD-U1vp.js +2 -0
- package/dist/chunks/Rest-DhD-U1vp.js.map +1 -0
- package/dist/chunks/{TokenManager-BoolOcxQ.js → TokenManager-BCgqjJTM.js} +5 -5
- package/dist/chunks/{TokenManager-BoolOcxQ.js.map → TokenManager-BCgqjJTM.js.map} +1 -1
- package/dist/chunks/{TokenManager-Bh1WshcR.js → TokenManager-sw1FUivr.js} +2 -2
- package/dist/chunks/{TokenManager-Bh1WshcR.js.map → TokenManager-sw1FUivr.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-DcJBT-iB.js → WebSocketClient-JHjYcYbU.js} +2 -2
- package/dist/chunks/{WebSocketClient-DcJBT-iB.js.map → WebSocketClient-JHjYcYbU.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-Do0CazHx.js → WebSocketClient-bLYhu2Wv.js} +2 -2
- package/dist/chunks/{WebSocketClient-Do0CazHx.js.map → WebSocketClient-bLYhu2Wv.js.map} +1 -1
- package/dist/chunks/{version-_FYWwpG_.js → version-Cg90gIP7.js} +4 -4
- package/dist/chunks/{version-_FYWwpG_.js.map → version-Cg90gIP7.js.map} +1 -1
- package/dist/chunks/{version-Bado0p82.js → version-e45B_7me.js} +2 -2
- package/dist/chunks/{version-Bado0p82.js.map → version-e45B_7me.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +6 -6
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +15 -15
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +5 -5
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +2 -2
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +4 -4
- package/package.json +1 -1
- package/dist/chunks/Rest-29RtA7pe.js +0 -2
- package/dist/chunks/Rest-29RtA7pe.js.map +0 -1
- package/dist/chunks/Rest-BoKp2Hj4.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";const e=require("./Rest-29RtA7pe.js");class FormPlugins{static _plugins=[];static _renderers=/* @__PURE__ */new Map;static register(e){return e&&"object"==typeof e?e.id&&"string"==typeof e.id?(this.unregister(e.id),e.fieldTypes&&"object"==typeof e.fieldTypes&&Object.entries(e.fieldTypes).forEach(([t,s])=>{"function"==typeof s?this._renderers.set(t,{renderer:s,pluginId:e.id}):console.warn(`[FormPlugins] renderer for type "${t}" is not a function`)}),this._plugins.push(e),()=>this.unregister(e.id)):(console.warn('[FormPlugins] plugin must have a unique string "id"',e),()=>{}):(console.warn("[FormPlugins] register called with invalid plugin:",e),()=>{})}static unregister(e){if(e){this._plugins=this._plugins.filter(t=>t.id!==e);for(const[t,s]of this._renderers.entries())s?.pluginId===e&&this._renderers.delete(t)}}static getRenderer(e){const t=this._renderers.get(e);return t?.renderer||null}static hasRenderer(e){return this._renderers.has(e)}static getPlugins(){return[...this._plugins]}static _invoke(e,t,...s){const i=e?.[t];if("function"==typeof i)try{return i.apply(e,s)}catch(a){console.error(`[FormPlugins] ${t} error from plugin "${e.id}":`,a)}}static onFormBuilderInit(e){this._plugins.forEach(t=>this._invoke(t,"onFormBuilderInit",e))}static onFormViewInit(e){this._plugins.forEach(t=>this._invoke(t,"onFormViewInit",e))}static onFormViewAfterRender(e){this._plugins.forEach(t=>this._invoke(t,"onAfterRender",e))}static onFieldInit(e,t,s){this._plugins.forEach(i=>this._invoke(i,"onFieldInit",e,t,s))}static onFieldChange(e,t,s){this._plugins.forEach(i=>this._invoke(i,"onFieldChange",e,t,s))}}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(){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 <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 {{#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 <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 {{#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 </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">\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:t,columns:s,class:i=""}=e;let a="";const n=FormPlugins&&"function"==typeof FormPlugins.getRenderer?FormPlugins.getRenderer(t):null;if("function"==typeof n)try{const t=n(this,e);null!=t&&(a=String(t))}catch(r){console.error("FormPlugins custom renderer error:",r)}if(!a)switch(e.name,t){case"text":a=this.renderTextField(e);break;case"email":a=this.renderEmailField(e);break;case"password":a=this.renderPasswordField(e);break;case"number":a=this.renderNumberField(e);break;case"tel":a=this.renderTelField(e);break;case"url":a=this.renderUrlField(e);break;case"search":a=this.renderSearchField(e);break;case"hex":a=this.renderHexField(e);break;case"textarea":a=this.renderTextareaField(e);break;case"json":a=this.renderJsonField(e);break;case"select":a=this.renderSelectField(e);break;case"checkbox":a=this.renderCheckboxField(e);break;case"toggle":case"switch":a=this.renderSwitchField(e);break;case"radio":a=this.renderRadioField(e);break;case"date":a=this.renderDateField(e);break;case"datetime":a=this.renderDateTimeField(e);break;case"time":a=this.renderTimeField(e);break;case"file":a=this.renderFileField(e);break;case"image":a=this.renderImageField(e);break;case"color":a=this.renderColorField(e);break;case"range":a=this.renderRangeField(e);break;case"hidden":a=this.renderHiddenField(e);break;case"button":a=this.renderButton(e);break;case"divider":a=this.renderDivider(e);break;case"html":a=this.renderHtmlField(e);break;case"heading":case"header":a=this.renderHeaderField(e);break;case"tag":case"tags":a=this.renderTagField(e);break;case"collection":a=this.renderCollectionField(e);break;case"collectionmultiselect":case"collection-multiselect":a=this.renderCollectionMultiSelectField(e);break;case"datepicker":a=this.renderDatePickerField(e);break;case"daterange":a=this.renderDateRangeField(e);break;case"checklistdropdown":a=this.renderChecklistDropdownField(e);break;case"buttongroup":a=this.renderButtonGroupField(e);break;case"combo":case"combobox":case"autocomplete":a=this.renderComboField(e);break;case"tabset":a=this.renderTabsetField(e);break;default:console.warn(`Unknown field type: ${t}`),a=this.renderTextField(e)}let l;return l=this.isAutoSizingField(e)?`col ${i}`.trim():`col-${s} ${i}`.trim(),`<div class="${l}">${a}</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};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,attrs:b};return e.Mustache.render(this.templates.textarea,y)}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],F=this.getFieldValue(s)??n,C=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),$=this.getFieldId(s);let D=[...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});D=[...D,...t]}let S="";Array.isArray(D)&&(S=D.map(e=>{if("string"==typeof e){const t=e===F?"selected":"";return`<option value="${this.escapeHtml(e)}" ${t}>${this.escapeHtml(e)}</option>`}if(e&&"object"==typeof e){const t=e.value==F?"selected":"";return`<option value="${this.escapeHtml(e.value)}" ${t}>${this.escapeHtml(e.label||e.text||e.value)}</option>`}return""}).join(""));const x=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="${$}">\n `:"",k={labelClass:this.options.labelClass,inputClass:y,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:$,name:s,label:i?this.escapeHtml(i):null,help:u?this.escapeHtml(u):null,error:w?this.escapeHtml(w):null,searchInput:d?x:null,optionsHTML:S,required:l,disabled:r,multiple:o,attrs:C};return e.Mustache.render(this.templates.select,k)}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(" "),F=this.getFieldValue(s),C=this.extractImageUrl(F,c),$={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:C,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,$)}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],F=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:F,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(F))}">\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],F=s||(t?t+"_start":""),C=i||(t?t+"_end":""),$=this.getFieldValue(F)||l,D=this.getFieldValue(C)||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:$,endDate:D,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($)}"\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(D)}"\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="",placeholder:a="Select or type...",options:n=[],required:l=!1,disabled:r=!1,readonly:o=!1,allowCustom:d=!0,showDescription:c=!0,minChars:h=0,maxSuggestions:u=10,help:p=e.helpText||e.help||""}=e,m=this.getFieldId(t),g=this.errors[t],f=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${m}" class="${this.options.labelClass}">${this.escapeHtml(s)}${l?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="combo-input-placeholder"\n data-field-name="${t}"\n data-field-type="combo"\n data-field-config='${JSON.stringify({name:t,value:f,placeholder:a,options:n,allowCustom:d,showDescription:c,minChars:h,maxSuggestions:u,disabled:r,readonly:o,required:l})}'>\n <input type="text"\n id="${m}"\n name="${t}_display"\n class="${this.options.inputClass}${g?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n value="${this.escapeHtml(f)}"\n ${r?"disabled":""}\n ${o?"readonly":""}\n ${l?"required":""}>\n <input type="hidden" name="${t}" value="${this.escapeHtml(f)}">\n <small class="form-text text-muted">This will be enhanced with ComboInput component</small>\n </div>\n ${p?`<div class="${this.options.helpClass}">${this.escapeHtml(p)}</div>`:""}\n ${g?`<div class="${this.options.errorClass}">${this.escapeHtml(g)}</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=>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 t={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 s(e){Object.assign(e.prototype,t)}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 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:F=!1,separator:C=" - ",...$}=e;super({tagName:"div",className:`date-range-picker-view ${v}`,...$}),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=F,this.separator=C,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")})},this.picker=new window.easepick.create(t),this.currentStartDate&&this.currentEndDate&&this.picker.setDateRange(this.currentStartDate,this.currentEndDate)}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__ */
|
|
1
|
+
"use strict";const e=require("./Rest-DhD-U1vp.js");class FormPlugins{static _plugins=[];static _renderers=/* @__PURE__ */new Map;static register(e){return e&&"object"==typeof e?e.id&&"string"==typeof e.id?(this.unregister(e.id),e.fieldTypes&&"object"==typeof e.fieldTypes&&Object.entries(e.fieldTypes).forEach(([t,s])=>{"function"==typeof s?this._renderers.set(t,{renderer:s,pluginId:e.id}):console.warn(`[FormPlugins] renderer for type "${t}" is not a function`)}),this._plugins.push(e),()=>this.unregister(e.id)):(console.warn('[FormPlugins] plugin must have a unique string "id"',e),()=>{}):(console.warn("[FormPlugins] register called with invalid plugin:",e),()=>{})}static unregister(e){if(e){this._plugins=this._plugins.filter(t=>t.id!==e);for(const[t,s]of this._renderers.entries())s?.pluginId===e&&this._renderers.delete(t)}}static getRenderer(e){const t=this._renderers.get(e);return t?.renderer||null}static hasRenderer(e){return this._renderers.has(e)}static getPlugins(){return[...this._plugins]}static _invoke(e,t,...s){const i=e?.[t];if("function"==typeof i)try{return i.apply(e,s)}catch(a){console.error(`[FormPlugins] ${t} error from plugin "${e.id}":`,a)}}static onFormBuilderInit(e){this._plugins.forEach(t=>this._invoke(t,"onFormBuilderInit",e))}static onFormViewInit(e){this._plugins.forEach(t=>this._invoke(t,"onFormViewInit",e))}static onFormViewAfterRender(e){this._plugins.forEach(t=>this._invoke(t,"onAfterRender",e))}static onFieldInit(e,t,s){this._plugins.forEach(i=>this._invoke(i,"onFieldInit",e,t,s))}static onFieldChange(e,t,s){this._plugins.forEach(i=>this._invoke(i,"onFieldChange",e,t,s))}}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(){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 <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 {{#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 <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 {{#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 </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">\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:t,columns:s,class:i=""}=e;let a="";const n=FormPlugins&&"function"==typeof FormPlugins.getRenderer?FormPlugins.getRenderer(t):null;if("function"==typeof n)try{const t=n(this,e);null!=t&&(a=String(t))}catch(r){console.error("FormPlugins custom renderer error:",r)}if(!a)switch(e.name,t){case"text":a=this.renderTextField(e);break;case"email":a=this.renderEmailField(e);break;case"password":a=this.renderPasswordField(e);break;case"number":a=this.renderNumberField(e);break;case"tel":a=this.renderTelField(e);break;case"url":a=this.renderUrlField(e);break;case"search":a=this.renderSearchField(e);break;case"hex":a=this.renderHexField(e);break;case"textarea":a=this.renderTextareaField(e);break;case"json":a=this.renderJsonField(e);break;case"select":a=this.renderSelectField(e);break;case"checkbox":a=this.renderCheckboxField(e);break;case"toggle":case"switch":a=this.renderSwitchField(e);break;case"radio":a=this.renderRadioField(e);break;case"date":a=this.renderDateField(e);break;case"datetime":a=this.renderDateTimeField(e);break;case"time":a=this.renderTimeField(e);break;case"file":a=this.renderFileField(e);break;case"image":a=this.renderImageField(e);break;case"color":a=this.renderColorField(e);break;case"range":a=this.renderRangeField(e);break;case"hidden":a=this.renderHiddenField(e);break;case"button":a=this.renderButton(e);break;case"divider":a=this.renderDivider(e);break;case"html":a=this.renderHtmlField(e);break;case"heading":case"header":a=this.renderHeaderField(e);break;case"tag":case"tags":a=this.renderTagField(e);break;case"collection":a=this.renderCollectionField(e);break;case"collectionmultiselect":case"collection-multiselect":a=this.renderCollectionMultiSelectField(e);break;case"datepicker":a=this.renderDatePickerField(e);break;case"daterange":a=this.renderDateRangeField(e);break;case"checklistdropdown":a=this.renderChecklistDropdownField(e);break;case"buttongroup":a=this.renderButtonGroupField(e);break;case"combo":case"combobox":case"autocomplete":a=this.renderComboField(e);break;case"tabset":a=this.renderTabsetField(e);break;default:console.warn(`Unknown field type: ${t}`),a=this.renderTextField(e)}let l;return l=this.isAutoSizingField(e)?`col ${i}`.trim():`col-${s} ${i}`.trim(),`<div class="${l}">${a}</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};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,attrs:b};return e.Mustache.render(this.templates.textarea,y)}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],F=this.getFieldValue(s)??n,C=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),$=this.getFieldId(s);let D=[...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});D=[...D,...t]}let S="";Array.isArray(D)&&(S=D.map(e=>{if("string"==typeof e){const t=e===F?"selected":"";return`<option value="${this.escapeHtml(e)}" ${t}>${this.escapeHtml(e)}</option>`}if(e&&"object"==typeof e){const t=e.value==F?"selected":"";return`<option value="${this.escapeHtml(e.value)}" ${t}>${this.escapeHtml(e.label||e.text||e.value)}</option>`}return""}).join(""));const x=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="${$}">\n `:"",k={labelClass:this.options.labelClass,inputClass:y,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:$,name:s,label:i?this.escapeHtml(i):null,help:u?this.escapeHtml(u):null,error:w?this.escapeHtml(w):null,searchInput:d?x:null,optionsHTML:S,required:l,disabled:r,multiple:o,attrs:C};return e.Mustache.render(this.templates.select,k)}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(" "),F=this.getFieldValue(s),C=this.extractImageUrl(F,c),$={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:C,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,$)}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],F=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:F,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(F))}">\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],F=s||(t?t+"_start":""),C=i||(t?t+"_end":""),$=this.getFieldValue(F)||l,D=this.getFieldValue(C)||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:$,endDate:D,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($)}"\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(D)}"\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="",placeholder:a="Select or type...",options:n=[],required:l=!1,disabled:r=!1,readonly:o=!1,allowCustom:d=!0,showDescription:c=!0,minChars:h=0,maxSuggestions:u=10,help:p=e.helpText||e.help||""}=e,m=this.getFieldId(t),g=this.errors[t],f=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${m}" class="${this.options.labelClass}">${this.escapeHtml(s)}${l?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="combo-input-placeholder"\n data-field-name="${t}"\n data-field-type="combo"\n data-field-config='${JSON.stringify({name:t,value:f,placeholder:a,options:n,allowCustom:d,showDescription:c,minChars:h,maxSuggestions:u,disabled:r,readonly:o,required:l})}'>\n <input type="text"\n id="${m}"\n name="${t}_display"\n class="${this.options.inputClass}${g?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n value="${this.escapeHtml(f)}"\n ${r?"disabled":""}\n ${o?"readonly":""}\n ${l?"required":""}>\n <input type="hidden" name="${t}" value="${this.escapeHtml(f)}">\n <small class="form-text text-muted">This will be enhanced with ComboInput component</small>\n </div>\n ${p?`<div class="${this.options.helpClass}">${this.escapeHtml(p)}</div>`:""}\n ${g?`<div class="${this.options.errorClass}">${this.escapeHtml(g)}</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=>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 t={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 s(e){Object.assign(e.prototype,t)}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 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:F=!1,separator:C=" - ",...$}=e;super({tagName:"div",className:`date-range-picker-view ${v}`,...$}),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=F,this.separator=C,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")})},this.picker=new window.easepick.create(t),this.currentStartDate&&this.currentEndDate&&this.picker.setDateRange(this.currentStartDate,this.currentEndDate)}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
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)this.picker.setDateRange(e||null,t||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})}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 FormView extends e.View{constructor(e={}){const{formConfig:t=e.config,fields:s,model:i=null,data:a={},defaults:n=null,errors:l={},fileHandling:r="base64",autosaveModelField:o=!1,...d}=e;super({tagName:"div",className:"form-view",...d}),FormPlugins.onFormViewInit?.(this),this.model=i,this.defaults=n||a,this._originalData=a,this.errors=l,this.loading=!1,this.fileHandling=r,this.autosaveModelField=o,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=t||{fields:s||[]},this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:l})}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)),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.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=(t=[])=>{t.forEach(t=>{if(t&&"group"===t.type&&Array.isArray(t.fields))e(t.fields);else if(t&&t.name){const e=this.element.querySelector(`[name="${t.name}"], #${t.id||t.name}`);e&&FormPlugins.onFieldInit?.(this,e,t)}})};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.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){}})}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,t){this._isPopulating||(this.data[e]=t,this.autosaveModelField&&this.model?this.handleFieldSave(e,t):this.model&&this.options.allowModelChange&&(this._isFormDrivenChange=!0,this.model.set(e,t)),this.emit("field:change",{field:e,value:t}),FormPlugins.onFieldChange?.(this,e,t))}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 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){}}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()}}s(FormView);const i=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,FormView:FormView,default:FormView},Symbol.toStringTag,{value:"Module"}));exports.FormView=FormView,exports.FormView$1=i,exports.applyFileDropMixin=s;
|
|
3
|
-
//# sourceMappingURL=FormView-
|
|
3
|
+
//# sourceMappingURL=FormView-DCtLjTmF.js.map
|