web-mojo 2.1.244 → 2.1.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +60 -23
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +3 -3
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +2 -2
  11. package/dist/chunks/{ContextMenu-qToF2PNG.js → ContextMenu-4ltJ5APp.js} +2 -2
  12. package/dist/chunks/{ContextMenu-qToF2PNG.js.map → ContextMenu-4ltJ5APp.js.map} +1 -1
  13. package/dist/chunks/{ContextMenu-CPQ_ZrT6.js → ContextMenu-zFaO5-N4.js} +2 -2
  14. package/dist/chunks/{ContextMenu-CPQ_ZrT6.js.map → ContextMenu-zFaO5-N4.js.map} +1 -1
  15. package/dist/chunks/{DataView-kd5iuE-9.js → DataView-Cejy1Cr8.js} +2 -2
  16. package/dist/chunks/{DataView-kd5iuE-9.js.map → DataView-Cejy1Cr8.js.map} +1 -1
  17. package/dist/chunks/{DataView-COXtv8kh.js → DataView-iOFKWlbv.js} +2 -2
  18. package/dist/chunks/{DataView-COXtv8kh.js.map → DataView-iOFKWlbv.js.map} +1 -1
  19. package/dist/chunks/{Dialog-Bz4Z9bSO.js → Dialog-BJ18jw7T.js} +2 -2
  20. package/dist/chunks/{Dialog-Bz4Z9bSO.js.map → Dialog-BJ18jw7T.js.map} +1 -1
  21. package/dist/chunks/{Dialog-DWDn0XUt.js → Dialog-DTP5nS69.js} +5 -5
  22. package/dist/chunks/{Dialog-DWDn0XUt.js.map → Dialog-DTP5nS69.js.map} +1 -1
  23. package/dist/chunks/FilePreviewView-B_9CFtFI.js +2 -0
  24. package/dist/chunks/FilePreviewView-B_9CFtFI.js.map +1 -0
  25. package/dist/chunks/{FilePreviewView-CCngMe-J.js → FilePreviewView-DUa8Q8kR.js} +29 -8
  26. package/dist/chunks/FilePreviewView-DUa8Q8kR.js.map +1 -0
  27. package/dist/chunks/{FormView-CG2GC9qH.js → FormView-CG2dIaZ6.js} +2 -2
  28. package/dist/chunks/{FormView-CG2GC9qH.js.map → FormView-CG2dIaZ6.js.map} +1 -1
  29. package/dist/chunks/{FormView-GxoZ1H6P.js → FormView-J-1RnLu0.js} +2 -2
  30. package/dist/chunks/{FormView-GxoZ1H6P.js.map → FormView-J-1RnLu0.js.map} +1 -1
  31. package/dist/chunks/{MetricsChart-DDiVVisT.js → MetricsChart-BCYtvXip.js} +2 -2
  32. package/dist/chunks/{MetricsChart-DDiVVisT.js.map → MetricsChart-BCYtvXip.js.map} +1 -1
  33. package/dist/chunks/{MetricsChart-D1n5a4YY.js → MetricsChart-igTZsC5W.js} +14 -8
  34. package/dist/chunks/{MetricsChart-D1n5a4YY.js.map → MetricsChart-igTZsC5W.js.map} +1 -1
  35. package/dist/chunks/{PDFViewer-DrJPeSLI.js → PDFViewer-B0h2W3v3.js} +2 -2
  36. package/dist/chunks/{PDFViewer-DrJPeSLI.js.map → PDFViewer-B0h2W3v3.js.map} +1 -1
  37. package/dist/chunks/{PDFViewer-DGAtX0Ms.js → PDFViewer-BPS5xKz0.js} +3 -3
  38. package/dist/chunks/{PDFViewer-DGAtX0Ms.js.map → PDFViewer-BPS5xKz0.js.map} +1 -1
  39. package/dist/chunks/{Page-BsqCluiN.js → Page-BfSiGr0L.js} +2 -2
  40. package/dist/chunks/{Page-BsqCluiN.js.map → Page-BfSiGr0L.js.map} +1 -1
  41. package/dist/chunks/{Page-CouRTtLr.js → Page-CL6mad8S.js} +2 -2
  42. package/dist/chunks/{Page-CouRTtLr.js.map → Page-CL6mad8S.js.map} +1 -1
  43. package/dist/chunks/{TopNav-B0OgzwWD.js → TopNav-LneaWujS.js} +2 -2
  44. package/dist/chunks/{TopNav-B0OgzwWD.js.map → TopNav-LneaWujS.js.map} +1 -1
  45. package/dist/chunks/{TopNav-CJGYrCmM.js → TopNav-Y9xm0v2v.js} +2 -2
  46. package/dist/chunks/{TopNav-CJGYrCmM.js.map → TopNav-Y9xm0v2v.js.map} +1 -1
  47. package/dist/chunks/User-BgVd3vvo.js +3 -0
  48. package/dist/chunks/User-BgVd3vvo.js.map +1 -0
  49. package/dist/chunks/{User-DzR9RPjg.js → User-CifH24ZI.js} +30 -26
  50. package/dist/chunks/User-CifH24ZI.js.map +1 -0
  51. package/dist/chunks/{WebApp-D_j_HtgS.js → WebApp-BTURAtHS.js} +2 -2
  52. package/dist/chunks/{WebApp-D_j_HtgS.js.map → WebApp-BTURAtHS.js.map} +1 -1
  53. package/dist/chunks/{WebApp-CR6b7HZz.js → WebApp-Ri8Knc5O.js} +12 -12
  54. package/dist/chunks/{WebApp-CR6b7HZz.js.map → WebApp-Ri8Knc5O.js.map} +1 -1
  55. package/dist/docit.cjs.js +1 -1
  56. package/dist/docit.es.js +6 -6
  57. package/dist/index.cjs.js +1 -1
  58. package/dist/index.cjs.js.map +1 -1
  59. package/dist/index.es.js +12 -11
  60. package/dist/index.es.js.map +1 -1
  61. package/dist/lightbox.cjs.js +1 -1
  62. package/dist/lightbox.es.js +4 -4
  63. package/package.json +1 -1
  64. package/dist/chunks/FilePreviewView-CCngMe-J.js.map +0 -1
  65. package/dist/chunks/FilePreviewView-Dacxyem-.js +0 -2
  66. package/dist/chunks/FilePreviewView-Dacxyem-.js.map +0 -1
  67. package/dist/chunks/User-DARwVvpV.js +0 -3
  68. package/dist/chunks/User-DARwVvpV.js.map +0 -1
  69. package/dist/chunks/User-DzR9RPjg.js.map +0 -1
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./WebApp-D_j_HtgS.js");class FormBuilder{constructor(e={}){this.fields=e.fields||[],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)})}),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(){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}} data-change-action="validate-field" {{{attrs}}}>\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}} data-change-action="validate-field" {{{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}} data-change-action="validate-field" {{{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}} data-change-action="validate-field" {{{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 data-change-action="validate-field" {{{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 data-action="select-button-option"\n data-field="{{fieldName}}"\n data-value="{{value}}">\n {{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 '}}buildFormHTML(){const e=this.buildFieldsHTML(),t=this.buildButtonsHTML();return`\n <form class="${this.options.formClass}"\n method="${this.options.formMethod}"\n ${this.options.formAction?`action="${this.options.formAction}"`:""}\n 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 i=this.fields[t];if(i.columns=i.columns||i.cols,"group"===i.type){const s=[i];let a=i.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;s.push(e),a+=t,n++}let l=s.length>1;if(1===s.length&&i.columns){let e=i.columns;"object"==typeof e&&null!==e&&(e=e.md||e.sm||e.xs||12),l=l||e<12}if(l){const t=s.map(e=>this.buildGroupHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(this.buildGroupHTML(i));t=n}else if(i.columns&&i.columns<12){const s=[i];let a=i.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;s.push(e),a+=t,n++}const l=s.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${l}</div>`),t=n}else if(this.isAutoSizingField(i)){const s=[i];let a=t+1;for(;a<this.fields.length;){const e=this.fields[a];if(!this.isAutoSizingField(e))break;s.push(e),a++}if(s.length>1){const t=s.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(`<div class="row">${this.buildFieldHTML(i)}</div>`);t=a}else e.push(this.buildFieldHTML(i)),t++}return e.join("")}buildGroupHTML(e){const{columns:t=12,title:i,fields:s=[],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=s.map(e=>"group"===e.type?this.buildGroupHTML(e):this.buildFieldHTML(e)).join("");return`\n <div class="mojo-form-group ${o} ${a}">\n ${i?`<div class="${n}">${this.escapeHtml(i)}</div>`:""}\n <div class="row">\n ${d}\n </div>\n </div>\n `}buildFieldHTML(e){const{type:t,columns:i,class:s=""}=e;let a,n="";switch(t){case"text":n=this.renderTextField(e);break;case"email":n=this.renderEmailField(e);break;case"password":n=this.renderPasswordField(e);break;case"number":n=this.renderNumberField(e);break;case"tel":n=this.renderTelField(e);break;case"url":n=this.renderUrlField(e);break;case"search":n=this.renderSearchField(e);break;case"hex":n=this.renderHexField(e);break;case"textarea":n=this.renderTextareaField(e);break;case"json":n=this.renderJsonField(e);break;case"select":n=this.renderSelectField(e);break;case"checkbox":n=this.renderCheckboxField(e);break;case"toggle":case"switch":n=this.renderSwitchField(e);break;case"radio":n=this.renderRadioField(e);break;case"date":n=this.renderDateField(e);break;case"datetime":n=this.renderDateTimeField(e);break;case"time":n=this.renderTimeField(e);break;case"file":n=this.renderFileField(e);break;case"image":n=this.renderImageField(e);break;case"color":n=this.renderColorField(e);break;case"range":n=this.renderRangeField(e);break;case"hidden":n=this.renderHiddenField(e);break;case"button":n=this.renderButton(e);break;case"divider":n=this.renderDivider(e);break;case"html":n=this.renderHtmlField(e);break;case"heading":case"header":n=this.renderHeaderField(e);break;case"tag":case"tags":n=this.renderTagField(e);break;case"collection":n=this.renderCollectionField(e);break;case"datepicker":n=this.renderDatePickerField(e);break;case"daterange":n=this.renderDateRangeField(e);break;case"checklistdropdown":n=this.renderChecklistDropdownField(e);break;case"buttongroup":n=this.renderButtonGroupField(e);break;default:console.warn(`Unknown field type: ${t}`),n=this.renderTextField(e)}return a=this.isAutoSizingField(e)?`col ${s}`.trim():`col-${i} ${s}`.trim(),`<div class="${a}">${n}</div>`}getFieldId(e){return`field_${e}_${Date.now()}`}renderTextField(e){return this.renderInputField(e,"text")}renderEmailField(e){return this.renderInputField(e,"email")}renderPasswordField(e){return this.renderInputField(e,"password")}renderNumberField(e){const{min:t,max:i,step:s=1,...a}=e,n=[];return void 0!==t&&n.push(`min="${t}"`),void 0!==i&&n.push(`max="${i}"`),void 0!==s&&n.push(`step="${s}"`),this.renderInputField({...a,attributes:{...a.attributes,...n.reduce((e,t)=>{const[i,s]=t.split("=");return e[i]=s.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:i=!0,minLength:s,maxLength:a,...n}=e;let l,r,o,d,c;switch(t){case"color":l=i?"^#?[0-9A-Fa-f]{6}$":"^[0-9A-Fa-f]{6}$",r=6,o=i?7:6,d=i?"#FF0000":"FF0000",c=c||"Enter a valid hex color (e.g., "+d+")";break;case"color-short":l=i?"^#?[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$":"^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$",r=i?4:3,o=i?7:6,d=i?"#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=s||1,o=a||64,d="ABCDEF123456",c=c||"Only hexadecimal characters (0-9, A-F) allowed";break;default:l=i?"^#?[0-9A-Fa-f]+$":"^[0-9A-Fa-f]+$",r=s||1,o=a||64,d=i?"#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":i,style:"text-transform: uppercase;",...n.attributes}};return this.renderInputField(h,"text")}renderInputField(t,i="text"){const{name:s,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[s],g=this.getFieldValue(s)??n,f=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),b=this.getFieldId(s),v={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:s,type:i,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};return e.Mustache.render(this.templates.input,v)}renderTextareaField(t){const{name:i,label:s,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[i],f=this.getFieldValue(i)??a,b=Object.entries(u).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),v=this.getFieldId(i),y={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:v,name:i,fieldValue:f,label:s?this.escapeHtml(s):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:i,label:s,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[i],f=this.getFieldId(i),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:i,fieldValue:p,label:s?this.escapeHtml(s):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:i,label:s,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||""}=t,p=`form-select ${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);let v="";Array.isArray(a)&&(v=a.map(e=>{if("string"==typeof e){const t=e===g?"selected":"";return`<option value="${this.escapeHtml(e)}" ${t}>${this.escapeHtml(e)}</option>`}if(e&&"object"==typeof e){const t=e.value===g?"selected":"";return`<option value="${this.escapeHtml(e.value)}" ${t}>${this.escapeHtml(e.label||e.text||e.value)}</option>`}return""}).join(""));const y=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="${b}">\n `:"",D={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:i,label:s?this.escapeHtml(s):null,help:u?this.escapeHtml(u):null,error:m?this.escapeHtml(m):null,searchInput:d?y:null,optionsHTML:v,required:l,disabled:r,multiple:o,attrs:f};return e.Mustache.render(this.templates.select,D)}renderCheckboxField(t){const{name:i,label:s,value:a=!1,required:n=!1,disabled:l=!1,class:r="",attributes:o={},help:d=t.helpText||t.help||""}=t,c=this.errors[i],h=this.getFieldValue(i)??a,u=!0===h||"true"===h||"1"===h,p=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(i),g={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:i,label:this.escapeHtml(s),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:i,label:s,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[i],u=this.getFieldValue(i)??a,p=!0===u||"true"===u||"1"===u,m=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),g=this.getFieldId(i),f="md"!==r?`form-switch-${r}`:"",b={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:i,label:this.escapeHtml(s),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:i,options:s=[],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(s)&&(p=s.map((e,i)=>{const s=`${t}_${i}`,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="${s}"\n name="${t}"\n class="form-check-input ${c?"is-invalid":""}"\n value="${this.escapeHtml(a)}"\n ${o}\n ${n?"disabled":""}\n data-change-action="validate-field"\n ${u}\n >\n <label class="form-check-label" for="${s}">\n ${this.escapeHtml(r)}\n </label>\n </div>\n `}).join("")),`\n <div class="mojo-form-control">\n ${i?`<fieldset>\n <legend class="${this.options.labelClass}">${this.escapeHtml(i)}</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:i,label:s,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[i],p=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(i),g={labelClass:this.options.labelClass,inputClass:h,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:i,label:s?this.escapeHtml(s):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:i,label:s,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[i],g=this.getFieldId(i),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,D=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),w=this.getFieldValue(i),F=this.extractImageUrl(w,c),$={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:i,label:s?this.escapeHtml(s):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:F,placeholderText:n?"No image":this.escapeHtml(u),cursor:n?"default":"pointer",allowDrop:h,showRemove:!n,required:a,disabled:n,attrs:D};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 i={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"]},s=i[t]||i.md;for(const t of s)if(e.renditions[t]&&e.renditions[t].url)return e.renditions[t].url}return e.url}return null}renderColorField(e){return this.renderInputField(e,"color")}renderRangeField(t){const{name:i,label:s,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[i],m=this.getFieldValue(i)??r,g=Object.entries(c).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),f=this.getFieldId(i),b={labelClass:this.options.labelClass,inputClass:u,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:i,label:s?this.escapeHtml(s):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:i=""}=e,s=this.getFieldValue(t)??i;return`<input type="hidden" name="${t}" value="${this.escapeHtml(s)}">`}renderButton(e){const{name:t="",label:i="Button",type:s="button",action:a="",class:n="btn-secondary",disabled:l=!1,attributes:r={}}=e;let o=a;return o||("submit"===s?o="submit-form":"reset"===s&&(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(i)}\n </button>\n `}renderDivider(e){const{label:t="",class:i=""}=e;return`\n <div class="form-divider ${i}">\n <hr>\n ${t?`<div class="form-divider-label">${this.escapeHtml(t)}</div>`:""}\n </div>\n `}renderHtmlField(e){const{html:t="",class:i=""}=e;return`\n <div class="form-html ${i}">\n ${t}\n </div>\n `}renderHeaderField(e){const{text:t="",level:i=3,class:s="",id:a=""}=e,n=Math.max(1,Math.min(6,parseInt(i)));return`<h${n}${a?` id="${this.escapeHtml(a)}"`:""}${s?` class="${this.escapeHtml(s)}"`:""}>${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 e.MOJOUtils.getContextData(this.data,t)}renderTagField(e){const{name:t,label:i,value:s="",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)??s;return`\n <div class="mojo-form-control">\n ${i?`<label for="${u}" class="${this.options.labelClass}">${this.escapeHtml(i)}${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 data-change-action="validate-field">\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:i,value:s="",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,help:m=e.helpText||e.help||""}=e,g=this.getFieldId(t),f=this.errors[t],b=this.getFieldValue(t)??s;return`\n <div class="mojo-form-control">\n ${i?`<label for="${g}" class="${this.options.labelClass}">${this.escapeHtml(i)}${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:b,placeholder:a,labelField:d,valueField:c,maxItems:h,emptyFetch:u,debounceMs:p,disabled:l,readonly:r,required:n})}'>\n <input type="text"\n id="${g}"\n name="${t}_display"\n class="${this.options.inputClass}${f?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n ${l?"disabled":""}\n ${r?"readonly":""}\n data-change-action="validate-field">\n <input type="hidden" name="${t}" value="${this.escapeHtml(b)}">\n <small class="form-text text-muted">This will be enhanced with CollectionSelect component</small>\n </div>\n ${m?`<div class="${this.options.helpClass}">${this.escapeHtml(m)}</div>`:""}\n ${f?`<div class="${this.options.errorClass}">${this.escapeHtml(f)}</div>`:""}\n </div>\n `}renderDatePickerField(e){const{name:t,label:i,value:s="",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)??s;return`\n <div class="mojo-form-control">\n ${i?`<label for="${p}" class="${this.options.labelClass}">${this.escapeHtml(i)}${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 data-change-action="validate-field">\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:i,endName:s,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||i||"daterange"),D=this.errors[t],w=i||(t?t+"_start":""),F=s||(t?t+"_end":""),$=this.getFieldValue(w)||l,C=this.getFieldValue(F)||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||i||"daterange"}"\n data-field-type="daterange"\n data-field-config='${JSON.stringify({name:t,startName:i,endName:s,fieldName:a,startDate:$,endDate:C,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}${D?" 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 data-change-action="validate-field">\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}${D?" is-invalid":""}"\n value="${this.escapeHtml(C)}"\n placeholder="End date..."\n ${u?`min="${u}"`:""}\n ${p?`max="${p}"`:""}\n ${c?"disabled":""}\n ${h?"readonly":""}\n ${d?"required":""}\n data-change-action="validate-field">\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 ${D?`<div class="${this.options.errorClass}">${this.escapeHtml(D)}</div>`:""}\n </div>\n `}renderChecklistDropdownField(t){const i=this.getFieldId(t.name),s=this.getFieldValue(t.name)??[],a={fieldId:i,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:s.includes(e.value)}))};return e.Mustache.render(this.templates.checklistdropdown,a)}renderButtonGroupField(t){const i=this.getFieldId(t.name),s=this.getFieldValue(t.name)??t.value,a={fieldId:i,fieldName:t.name,size:t.size||"sm",variant:t.variant||"outline-primary",options:t.options.map(e=>({value:e.value,label:e.label,active:e.value===s,buttonClass:this.getButtonClass(e.value===s,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}`}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 i=this._fileDropConfig.multiple?t:[t[0]];let s={valid:!0,errors:[]};if(!this._fileDropConfig.validateOnDrop||(s=this._validateFileDropFiles(i),s.valid))if("function"==typeof this.onFileDrop)try{await this.onFileDrop(i,e,s)}catch(a){"function"==typeof this.onFileDropError?await this.onFileDropError(a,e,i):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(s.errors.join(", ")),e,i)},_applyFileDropVisualFeedback(e){if(!this._fileDropConfig.visualFeedback||!this._fileDropZone)return;const{dragOverClass:t,dragActiveClass:i}=this._fileDropConfig;e?this._fileDropZone.classList.add(t,i):this._fileDropZone.classList.remove(t,i)},_validateFileDropFiles(e){const t=[],i=this._fileDropConfig;for(const s of e)this._isFileDropTypeAccepted(s.type)?s.size>i.maxFileSize&&t.push(`File "${s.name}" (${this._formatFileDropSize(s.size)}) exceeds maximum size (${this._formatFileDropSize(i.maxFileSize)})`):t.push(`File type "${s.type}" is not accepted for file "${s.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 i=t.split("/")[0];return e.startsWith(i+"/")}return!1})},_formatFileDropSize(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,t)).toFixed(2))+" "+["Bytes","KB","MB","GB"][t]}};function i(e){Object.assign(e.prototype,t)}class TagInputView extends e.View{constructor(e={}){const{name:t,value:i="",placeholder:s="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=s,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,i&&(this.tags=this.parseTagString(i))}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 i=parseInt(t.getAttribute("data-tag-index"));i>=0&&i<this.tags.length&&await this.removeTag(i)}async onChangeInputChange(e,t){const i=t.value,s=i.slice(-1);if(s===this.separator||"\n"===s){e.preventDefault();const s=i.slice(0,-1);return void(s.trim()&&(await this.addTag(s),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,i=t.value||"";switch(e.key){case"Enter":case"Tab":case",":i.trim()&&(e.preventDefault(),this.addTag(i),t.value="");break;case"Backspace":""===i&&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(""===i&&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(""===i&&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,console.log(`Focused tag index: ${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 i=this.element.querySelector(".tag-input-wrapper");i&&i.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 e=this.collection?this.collection.toJSON().map((e,t)=>({...e,labelField:e[this.labelField],valueField:e[this.valueField],isSelected:e[this.valueField]==this.selectedValue,isFocused:t===this.focusedIndex,index:t})):[];return{loading:this.loading,hasSearched:this.hasSearched,showNoResults:!this.loading&&this.hasSearched&&0===e.length,items:e}}async handleActionSelectItem(e,t){e.preventDefault();const i=t.getAttribute("data-value"),s=t.getAttribute("data-label");this.emit("item-selected",{value:i,label:s})}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(e={}){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 ',...e}),this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.maxItems=e.maxItems||10,this.placeholder=e.placeholder||"Search...",this.debounceMs=e.debounceMs||1e3,this.name=e.name||"collection_select",this.emptyFetch=!1!==e.emptyFetch,this.selectedValue=e.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=this.selectedValue[this.labelField]||"",this.selectedValue=this.selectedValue[this.valueField]||"0"),this.searchTimer=null,this.dropdownView=null,this.defaultParams={},this.handleDocumentClick=this.handleDocumentClick.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleInputEvents=this.handleInputEvents.bind(this),this.collection&&this.setupCollection()}setupCollection(){this.defaultParams={...this.collection.params},this.collection.params.size=this.maxItems,this.defaultParams.size=this.maxItems,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{const e=this.collection?.get(this.selectedValue);if(e)return this.selectedLabel=e.get(this.labelField),void this.render();let t=await this.collection.fetchOne(this.selectedValue);t&&(this.selectedLabel=t.get(this.labelField,`${t.constructor.name} #${t.id}`),this.render())}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 i=this.getInput();i&&(i.value=t);const s=this.getHiddenInput();s&&(s.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 i=this.getInput();i&&(i.value=t);const s=this.getHiddenInput();s&&(s.value=e)}getValue(){return 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 this.selectedValue}setFormValue(e){let t=e,i="";t&&"object"==typeof t&&(i=t[this.labelField]||"",t=t[this.valueField]),t=t||"0",t!=this.selectedValue&&(this.selectedValue=t,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())}}class DatePicker extends e.View{constructor(e={}){const{name:t,value:i="",format:s="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=s,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=i,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 i=document.createElement("link");i.rel="stylesheet",i.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(i);const s=document.createElement("script");s.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",s.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},s.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(s)})}async renderTemplate(){const e=this.getInputId(),t=this.useNative?"date":"text",i=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(i)}"\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,i){const s=i.value;this.handleDateChange(s)}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 i=new Date(e);if(isNaN(i.getTime()))return"";const s=i.getFullYear(),a=String(i.getMonth()+1).padStart(2,"0"),n=String(i.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":default:return`${s}-${a}-${n}`;case"MM/DD/YYYY":return`${a}/${n}/${s}`;case"DD/MM/YYYY":return`${n}/${a}/${s}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][i.getMonth()]} ${n}, ${s}`}}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:i,endName:s,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:D=!0,inline:w=!1,separator:F=" - ",...$}=e;super({tagName:"div",className:`date-range-picker-view ${v}`,...$}),this.name=t,this.startName=i,this.endName=s,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=D,this.inline=w,this.separator=F,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 i=document.createElement("link");i.rel="stylesheet",i.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(i);const s=document.createElement("script");s.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",s.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},s.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(s)})}async renderTemplate(){const e=this.getInputId(),t=this.getDisplayValue();if(this.useNative)return this.renderNativeTemplate(e);const i=this.startName||(this.name?`${this.name}_start`:""),s=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 ${i?`<input type="hidden" name="${i}" value="${this.escapeHtml(a)}" />`:""}\n ${s?`<input type="hidden" name="${s}" 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:i}=e.detail;this.handleRangeChange(t?this.formatDate(t,this.format):"",i?this.formatDate(i,this.format):"")}),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,i){}async onChangeStartDateChanged(e,t,i){const s=i.value;this.handleRangeChange(s,this.currentEndDate),this.updateConstraints()}async onChangeEndDateChanged(e,t,i){const s=i.value;this.handleRangeChange(this.currentStartDate,s),this.updateConstraints()}handleRangeChange(e,t){const i=this.currentStartDate,s=this.currentEndDate;this.currentStartDate=e,this.currentEndDate=t,this.updateHiddenInputs(),i===e&&s===t||(this.emit("change",{startDate:e,endDate:t,combined:this.getCombinedValue(),formatted:this.getDisplayValue(),oldStartDate:i,oldEndDate:s}),this.emit("range:changed",{startDate:e,endDate:t,oldStartDate:i,oldEndDate:s}))}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))}formatDate(e,t=this.format){if(!e)return"";const i=new Date(e);if(isNaN(i.getTime()))return"";const s=i.getFullYear(),a=String(i.getMonth()+1).padStart(2,"0"),n=String(i.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":default:return`${s}-${a}-${n}`;case"MM/DD/YYYY":return`${a}/${n}/${s}`;case"DD/MM/YYYY":return`${n}/${a}/${s}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][i.getMonth()]} ${n}, ${s}`}}formatForOutput(e){if(!e)return"";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`:""),i=e?this.element?.querySelector(`[name="${e}"]`):null,s=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;i&&(i.value=this.currentStartDate?this.formatForOutput(this.currentStartDate):""),s&&(s.value=this.currentEndDate?this.formatForOutput(this.currentEndDate):""),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 i=this.element?.querySelector(`[name="${this.name}_start"]`),s=this.element?.querySelector(`[name="${this.name}_end"]`);i&&(i.value=this.formatDate(e,"YYYY-MM-DD")),s&&(s.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 FormView extends e.View{constructor(e={}){const{formConfig:t=e.config,fields:i,model:s=null,data:a={},defaults:n={},errors:l={},fileHandling:r="base64",...o}=e;super({tagName:"div",className:"form-view",...o}),this.model=s,this.defaults=n,this._originalData=a,this.errors=l,this.loading=!1,this.fileHandling=r,this.customComponents=/* @__PURE__ */new Map,this.data=this.prepareFormData(),this.formConfig=t||{fields:i||[]},this.formBuilder=new FormBuilder({...this.formConfig,data:this.data,errors:l})}prepareFormData(){const e={...this.defaults};return this.model&&(this.model.attributes&&"object"==typeof this.model.attributes?Object.assign(e,this.model.attributes):"function"==typeof this.model.toJSON?Object.assign(e,this.model.toJSON()):"object"==typeof this.model&&this.model.constructor===Object&&Object.assign(e,this.model)),this._originalData&&Object.assign(e,this._originalData),e}async renderTemplate(){return this.formBuilder.buildFormHTML()}async onAfterRender(){await super.onAfterRender(),this.initializeImageFields(),this.initializeCustomComponents()}initializeImageFields(){this.element.querySelectorAll(".image-drop-zone.droppable").length>0&&(i(FormView),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.initializeDatePickers(),this.initializeDateRangePickers(),this.element.querySelectorAll("[data-component]").forEach(e=>{const t=e.getAttribute("data-component"),i=e.getAttribute("data-field");t&&i&&console.log(`Found ${t} component for field: ${i}`)})}initializeTagInputs(){this.element.querySelectorAll('[data-field-type="tag"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=new TagInputView({...s,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){console.error("Failed to initialize TagInput:",t)}})}initializeCollectionSelects(){this.element.querySelectorAll('[data-field-type="collection"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=this.getFormFieldConfig(t);if(!a||!a.Collection)return void console.warn(`CollectionSelect field ${t} missing Collection class`);const n=new a.Collection;a.collectionParams&&(n.params={...n.params,...a.collectionParams});const l=new CollectionSelectView({...s,collection:n,containerId:null});l.render(!0,e),this.customComponents.set(t,l),l.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){console.error("Failed to initialize CollectionSelect:",t)}})}initializeDatePickers(){this.element.querySelectorAll('[data-field-type="datepicker"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=new DatePicker({...s,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){console.error("Failed to initialize DatePicker:",t)}})}initializeDateRangePickers(){this.element.querySelectorAll('[data-field-type="daterange"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=new DateRangePicker({...s,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.combined)})}catch(t){console.error("Failed to initialize DateRangePicker:",t)}})}handleFieldChange(e,t){this.data[e]=t,this.model&&this.options.allowModelChange&&this.model.set(e,t),this.emit("field:change",{field:e,value:t})}refreshForm(){this.data=this.prepareFormData(),this.formBuilder=new FormBuilder({...this.formConfig,data:this.data,errors:this.errors}),this.element&&this.render()}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 i=Boolean(e);return`boolean: ${null!=t&&Boolean(t)} → ${i}`}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 i=await this.handleSubmit();i.success?(this.data=i.data,this.emit("submit",{data:i.data,result:i.result,form:this,event:e}),!this.model&&this.formConfig.onSubmit&&"function"==typeof this.formConfig.onSubmit&&await this.formConfig.onSubmit(i.data,this)):this.emit("error",{error:i.error,result:i,form:this})}async onActionResetForm(e,t){const i=this.getFormElement();i&&(i.reset(),this.data={},this.clearAllErrors(),this.emit("reset",{form:this,event:e}))}async onActionClickImageUpload(e,t){const i=t.getAttribute("data-field-id");if(!i)return;const s=this.element.querySelector(`#${i}`);s&&!s.disabled&&s.click()}async onActionRemoveImage(e,t){const i=t.getAttribute("data-field");if(!i)return;const s=this.element.querySelector(`input[name="${i}"]`);s&&(s.value="",s.dispatchEvent(new Event("change",{bubbles:!0}))),delete this.data[i],this.emit("change",{field:i,value:null,form:this}),await this.updateField(i)}async onActionSelectButtonOption(e,t){const i=t.getAttribute("data-field"),s=t.getAttribute("data-value");if(!i||!s)return;this.data[i]=s;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:i,value:s,form:this}),this.emit("change",{field:i,value:s,form:this}),this.emit("form:changed",await this.getFormData())}async onActionApplyFilter(e,t){const i=t.closest(".dropdown"),s=i?.querySelectorAll('input[type="checkbox"]');if(!s||0===s.length)return;const a=s[0].getAttribute("data-field");if(!a)return;const n=[];s.forEach(e=>{e.checked&&n.push(e.value)}),this.data[a]=n;const l=i.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 i=t.name;i&&(this.validateField(i),this.emit("change",{field:i,value:t.value,form:this}))}async onChangeToggleSwitch(e,t){const i=t.getAttribute("data-field");i&&(this.data[i]=t.checked,this.emit("switch:toggle",{field:i,checked:t.checked,form:this}),this.emit("change",{field:i,value:t.checked,form:this}))}async onChangeImageSelected(e,t){const i=t.getAttribute("data-field"),s=t.files[0];if(i&&s){const e=this.findFieldConfig(i),n=URL.createObjectURL(s);if(e&&e.imageSize)try{const a=window.MOJO?.plugins?.ImageCropView;if(!a)return void console.warn("ImageCropView not available. Load lightbox extension for image cropping.");const l=await a.showDialog(n,{title:`Crop ${e.label||i}`,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],s.name,{type:s.type||"image/png"});this.data[i]=a,await this.updateImagePreview(i,l.data),this.emit("image:selected",{field:i,file:a,originalFile:s,cropped:!0,cropData:l.cropData,form:this}),this.emit("change",{field:i,value:a,form:this})}else t.value=""}catch(a){console.error("Error during image cropping:",a),this.data[i]=s,await this.updateImagePreview(i,n),this.emit("image:selected",{field:i,file:s,form:this}),this.emit("change",{field:i,value:s,form:this})}else this.data[i]=s,await this.updateImagePreview(i,n),this.emit("image:selected",{field:i,file:s,form:this})}}async onChangeFileSelected(e,t){const i=Array.from(t.files);this.emit("file:selected",{field:t.name,files:i,form:this}),this.emit("change",{field:t.name,value:i,form:this})}async onChangeRangeChanged(e,t){const i=t.getAttribute("data-target");if(i){const e=this.element.querySelector(`#${i}`);e&&(e.textContent=t.value)}this.emit("range:changed",{field:t.name,value:t.value,form:this}),this.emit("change",{field:t.name,value:t.value,form:this})}async onChangeFilterSearch(e,t){const i=t.value;this.emit("search",{query:i,field:t.name,form:this})}async onChangeFilterSelectOptions(e,t){const i=t.value.toLowerCase(),s=t.getAttribute("data-target"),a=s?this.element.querySelector(`#${s}`):null;a&&a.querySelectorAll("option").forEach(e=>{const t=e.textContent.toLowerCase();e.style.display=t.includes(i)?"":"none"})}async onFileDrop(e,t,i){const s=t.target.closest(".image-drop-zone");if(!s)return;const a=s.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,i){console.error("File drop error:",e.message),this.showError(`File upload error: ${e.message}`),this.emit("file:error",{error:e,files:i,form:this})}getFormElement(){return this.element?this.element.querySelector("form"):null}getFormFieldConfig(e){const t=i=>{for(const s of i){if(s.name===e)return s;if(s.fields&&Array.isArray(s.fields)){const e=t(s.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,i]of Object.entries(this.data))if(i instanceof File)t.set(e,i);else if(i instanceof FileList)for(let s=0;s<i.length;s++)t.append(`${e}[${s}]`,i[s]);return t}{const i=new FormData(e),s={};for(const[e,t]of i.entries())s[e]?(Array.isArray(s[e])||(s[e]=[s[e]]),s[e].push(t)):s[e]=t;e.querySelectorAll('input[type="checkbox"]').forEach(e=>{s[e.name]=e.checked}),e.querySelectorAll('[data-field-type="json"]').forEach(e=>{try{s[e.name]=JSON.parse(e.value)}catch(t){console.warn(`Invalid JSON in field ${e.name}:`,e.value),s[e.name]=e.value}});for(const[e,a]of Object.entries(this.data))if(a instanceof File)try{s[e]=await this.fileToBase64(a)}catch(t){console.error(`Failed to convert file ${e} to base64:`,t),s[e]=null}else if(a instanceof FileList){const i=[];for(let s=0;s<a.length;s++)try{i.push(await this.fileToBase64(a[s]))}catch(t){console.error(`Failed to convert file ${e}[${s}] to base64:`,t),i.push(null)}s[e]=i}return s}}_onModelChange(){this.isMounted()&&this.refreshForm()}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 console.error("Form submission error:",e),{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 console.log("No changes detected, skipping save"),{success:!0,message:"No changes to save",data:e};console.log("Saving changed data via model:",t),console.log("Data type:",t instanceof FormData?"FormData (multipart)":"Object (JSON/base64)");try{const e=await this.model.save(t);return console.log("Model save result:",e),e}catch(i){throw console.error("Model save error:",i),i}}getChangedData(e){if(!this.model)return e;const t=this.getOriginalModelData();let i;return console.log("=== Change Detection ==="),console.log("Original model data:",t),console.log("Current form data:",e instanceof FormData?"[FormData object]":e),e instanceof FormData?(console.log("Comparing FormData..."),i=this.getChangedFormData(e,t)):(console.log("Comparing Object data..."),i=this.getChangedObjectData(e,t)),console.log("Changes detected:",i instanceof FormData?"[FormData with changes]":i),console.log("=== End Change Detection ==="),i}getOriginalModelData(){return this.model.attributes?this.model.attributes:"function"==typeof this.model.toJSON?this.model.toJSON():{}}getChangedFormData(e,t){const i=new FormData;let s=!1;for(const[a,n]of e.entries())if(n instanceof File)0===n.size||""===n.name||"blob"===n.name?console.log(` - ${a}: Empty file field (no change)`):(console.log(` - ${a}: File upload detected (${n.name}, ${n.size} bytes)`),i.set(a,n),s=!0);else{const e=t[a];n!==e&&n!==String(e)?(console.log(` - ${a}: "${e}" → "${n}"`),i.set(a,n),s=!0):console.log(` - ${a}: unchanged ("${n}")`)}return s?i:null}getChangedObjectData(e,t){const i={};let s=!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)&&(i[l]=r,s=!0)}return s?i:null}valuesAreDifferent(e,t,i="text",s={}){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"===i&&"object"==typeof t&&null!=t&&"string"==typeof e){if("0"===e)return null!==t;if(t[s.valueField||"id"]==e)return!1}return"switch"===i||"checkbox"===i?!!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 i=t.elements[e];if(!i)return!1;const s=i.checkValidity();return s?(i.classList.remove("is-invalid"),i.classList.add("is-valid"),delete this.errors[e]):(i.classList.remove("is-valid"),i.classList.add("is-invalid"),this.errors[e]=i.validationMessage),s}focusFirstError(){const e=this.getFormElement();if(!e)return;const t=e.querySelector(":invalid");t&&(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 i=t.querySelectorAll("input, select, textarea, button"),s=t.querySelector('button[type="submit"]');if(e)i.forEach(e=>e.disabled=!0),s&&(s.innerHTML='<span class="spinner-border spinner-border-sm me-2"></span>Loading...');else if(i.forEach(e=>e.disabled=!1),s){const e=this.formConfig.options?.submitButton||"Submit";s.innerHTML="string"==typeof e?e:"Submit"}}showError(e){if(console.error("Form error:",e),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.formConfig,data:this.data,errors:this.errors}),await this.render()}async updateImagePreview(e,t){const i=this.element.querySelector(`[data-field="${e}"].image-drop-zone`);if(!i)return void console.warn(`Could not find drop zone for field: ${e}`);let s=i.querySelector("img");const a=i.querySelector(".bi-image")?.parentElement,n=i.getAttribute("data-field-id");if(t){if(s)s.src=t;else{const s=`${n}_preview`;i.innerHTML=`\n <img id="${s}"\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=i=>{for(const s of i){if(s.name===e)return s;if("group"===s.type&&s.fields){const e=t(s.fields);if(e)return e}}return null};return t(this.formConfig.fields||[])}async fileToBase64(e){return new Promise((t,i)=>{const s=new FileReader;s.onload=()=>t(s.result),s.onerror=i,s.readAsDataURL(e)})}hasFiles(e){if(e instanceof FormData){for(const[t,i]of e.entries())if(i 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.formConfig,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()}}const s=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,FormView:FormView,default:FormView},Symbol.toStringTag,{value:"Module"}));exports.FormView=FormView,exports.FormView$1=s,exports.applyFileDropMixin=i;
2
- //# sourceMappingURL=FormView-CG2GC9qH.js.map
1
+ "use strict";const e=require("./WebApp-BTURAtHS.js");class FormBuilder{constructor(e={}){this.fields=e.fields||[],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)})}),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(){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}} data-change-action="validate-field" {{{attrs}}}>\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}} data-change-action="validate-field" {{{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}} data-change-action="validate-field" {{{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}} data-change-action="validate-field" {{{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 data-change-action="validate-field" {{{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 data-action="select-button-option"\n data-field="{{fieldName}}"\n data-value="{{value}}">\n {{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 '}}buildFormHTML(){const e=this.buildFieldsHTML(),t=this.buildButtonsHTML();return`\n <form class="${this.options.formClass}"\n method="${this.options.formMethod}"\n ${this.options.formAction?`action="${this.options.formAction}"`:""}\n 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 i=this.fields[t];if(i.columns=i.columns||i.cols,"group"===i.type){const s=[i];let a=i.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;s.push(e),a+=t,n++}let l=s.length>1;if(1===s.length&&i.columns){let e=i.columns;"object"==typeof e&&null!==e&&(e=e.md||e.sm||e.xs||12),l=l||e<12}if(l){const t=s.map(e=>this.buildGroupHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(this.buildGroupHTML(i));t=n}else if(i.columns&&i.columns<12){const s=[i];let a=i.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;s.push(e),a+=t,n++}const l=s.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${l}</div>`),t=n}else if(this.isAutoSizingField(i)){const s=[i];let a=t+1;for(;a<this.fields.length;){const e=this.fields[a];if(!this.isAutoSizingField(e))break;s.push(e),a++}if(s.length>1){const t=s.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(`<div class="row">${this.buildFieldHTML(i)}</div>`);t=a}else e.push(this.buildFieldHTML(i)),t++}return e.join("")}buildGroupHTML(e){const{columns:t=12,title:i,fields:s=[],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=s.map(e=>"group"===e.type?this.buildGroupHTML(e):this.buildFieldHTML(e)).join("");return`\n <div class="mojo-form-group ${o} ${a}">\n ${i?`<div class="${n}">${this.escapeHtml(i)}</div>`:""}\n <div class="row">\n ${d}\n </div>\n </div>\n `}buildFieldHTML(e){const{type:t,columns:i,class:s=""}=e;let a,n="";switch(t){case"text":n=this.renderTextField(e);break;case"email":n=this.renderEmailField(e);break;case"password":n=this.renderPasswordField(e);break;case"number":n=this.renderNumberField(e);break;case"tel":n=this.renderTelField(e);break;case"url":n=this.renderUrlField(e);break;case"search":n=this.renderSearchField(e);break;case"hex":n=this.renderHexField(e);break;case"textarea":n=this.renderTextareaField(e);break;case"json":n=this.renderJsonField(e);break;case"select":n=this.renderSelectField(e);break;case"checkbox":n=this.renderCheckboxField(e);break;case"toggle":case"switch":n=this.renderSwitchField(e);break;case"radio":n=this.renderRadioField(e);break;case"date":n=this.renderDateField(e);break;case"datetime":n=this.renderDateTimeField(e);break;case"time":n=this.renderTimeField(e);break;case"file":n=this.renderFileField(e);break;case"image":n=this.renderImageField(e);break;case"color":n=this.renderColorField(e);break;case"range":n=this.renderRangeField(e);break;case"hidden":n=this.renderHiddenField(e);break;case"button":n=this.renderButton(e);break;case"divider":n=this.renderDivider(e);break;case"html":n=this.renderHtmlField(e);break;case"heading":case"header":n=this.renderHeaderField(e);break;case"tag":case"tags":n=this.renderTagField(e);break;case"collection":n=this.renderCollectionField(e);break;case"datepicker":n=this.renderDatePickerField(e);break;case"daterange":n=this.renderDateRangeField(e);break;case"checklistdropdown":n=this.renderChecklistDropdownField(e);break;case"buttongroup":n=this.renderButtonGroupField(e);break;default:console.warn(`Unknown field type: ${t}`),n=this.renderTextField(e)}return a=this.isAutoSizingField(e)?`col ${s}`.trim():`col-${i} ${s}`.trim(),`<div class="${a}">${n}</div>`}getFieldId(e){return`field_${e}_${Date.now()}`}renderTextField(e){return this.renderInputField(e,"text")}renderEmailField(e){return this.renderInputField(e,"email")}renderPasswordField(e){return this.renderInputField(e,"password")}renderNumberField(e){const{min:t,max:i,step:s=1,...a}=e,n=[];return void 0!==t&&n.push(`min="${t}"`),void 0!==i&&n.push(`max="${i}"`),void 0!==s&&n.push(`step="${s}"`),this.renderInputField({...a,attributes:{...a.attributes,...n.reduce((e,t)=>{const[i,s]=t.split("=");return e[i]=s.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:i=!0,minLength:s,maxLength:a,...n}=e;let l,r,o,d,c;switch(t){case"color":l=i?"^#?[0-9A-Fa-f]{6}$":"^[0-9A-Fa-f]{6}$",r=6,o=i?7:6,d=i?"#FF0000":"FF0000",c=c||"Enter a valid hex color (e.g., "+d+")";break;case"color-short":l=i?"^#?[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$":"^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$",r=i?4:3,o=i?7:6,d=i?"#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=s||1,o=a||64,d="ABCDEF123456",c=c||"Only hexadecimal characters (0-9, A-F) allowed";break;default:l=i?"^#?[0-9A-Fa-f]+$":"^[0-9A-Fa-f]+$",r=s||1,o=a||64,d=i?"#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":i,style:"text-transform: uppercase;",...n.attributes}};return this.renderInputField(h,"text")}renderInputField(t,i="text"){const{name:s,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[s],g=this.getFieldValue(s)??n,f=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),b=this.getFieldId(s),v={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:s,type:i,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};return e.Mustache.render(this.templates.input,v)}renderTextareaField(t){const{name:i,label:s,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[i],f=this.getFieldValue(i)??a,b=Object.entries(u).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),v=this.getFieldId(i),y={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:v,name:i,fieldValue:f,label:s?this.escapeHtml(s):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:i,label:s,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[i],f=this.getFieldId(i),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:i,fieldValue:p,label:s?this.escapeHtml(s):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:i,label:s,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||""}=t,p=`form-select ${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);let v="";Array.isArray(a)&&(v=a.map(e=>{if("string"==typeof e){const t=e===g?"selected":"";return`<option value="${this.escapeHtml(e)}" ${t}>${this.escapeHtml(e)}</option>`}if(e&&"object"==typeof e){const t=e.value===g?"selected":"";return`<option value="${this.escapeHtml(e.value)}" ${t}>${this.escapeHtml(e.label||e.text||e.value)}</option>`}return""}).join(""));const y=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="${b}">\n `:"",D={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:i,label:s?this.escapeHtml(s):null,help:u?this.escapeHtml(u):null,error:m?this.escapeHtml(m):null,searchInput:d?y:null,optionsHTML:v,required:l,disabled:r,multiple:o,attrs:f};return e.Mustache.render(this.templates.select,D)}renderCheckboxField(t){const{name:i,label:s,value:a=!1,required:n=!1,disabled:l=!1,class:r="",attributes:o={},help:d=t.helpText||t.help||""}=t,c=this.errors[i],h=this.getFieldValue(i)??a,u=!0===h||"true"===h||"1"===h,p=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(i),g={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:i,label:this.escapeHtml(s),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:i,label:s,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[i],u=this.getFieldValue(i)??a,p=!0===u||"true"===u||"1"===u,m=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),g=this.getFieldId(i),f="md"!==r?`form-switch-${r}`:"",b={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:i,label:this.escapeHtml(s),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:i,options:s=[],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(s)&&(p=s.map((e,i)=>{const s=`${t}_${i}`,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="${s}"\n name="${t}"\n class="form-check-input ${c?"is-invalid":""}"\n value="${this.escapeHtml(a)}"\n ${o}\n ${n?"disabled":""}\n data-change-action="validate-field"\n ${u}\n >\n <label class="form-check-label" for="${s}">\n ${this.escapeHtml(r)}\n </label>\n </div>\n `}).join("")),`\n <div class="mojo-form-control">\n ${i?`<fieldset>\n <legend class="${this.options.labelClass}">${this.escapeHtml(i)}</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:i,label:s,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[i],p=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(i),g={labelClass:this.options.labelClass,inputClass:h,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:i,label:s?this.escapeHtml(s):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:i,label:s,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[i],g=this.getFieldId(i),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,D=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),w=this.getFieldValue(i),F=this.extractImageUrl(w,c),$={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:i,label:s?this.escapeHtml(s):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:F,placeholderText:n?"No image":this.escapeHtml(u),cursor:n?"default":"pointer",allowDrop:h,showRemove:!n,required:a,disabled:n,attrs:D};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 i={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"]},s=i[t]||i.md;for(const t of s)if(e.renditions[t]&&e.renditions[t].url)return e.renditions[t].url}return e.url}return null}renderColorField(e){return this.renderInputField(e,"color")}renderRangeField(t){const{name:i,label:s,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[i],m=this.getFieldValue(i)??r,g=Object.entries(c).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),f=this.getFieldId(i),b={labelClass:this.options.labelClass,inputClass:u,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:i,label:s?this.escapeHtml(s):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:i=""}=e,s=this.getFieldValue(t)??i;return`<input type="hidden" name="${t}" value="${this.escapeHtml(s)}">`}renderButton(e){const{name:t="",label:i="Button",type:s="button",action:a="",class:n="btn-secondary",disabled:l=!1,attributes:r={}}=e;let o=a;return o||("submit"===s?o="submit-form":"reset"===s&&(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(i)}\n </button>\n `}renderDivider(e){const{label:t="",class:i=""}=e;return`\n <div class="form-divider ${i}">\n <hr>\n ${t?`<div class="form-divider-label">${this.escapeHtml(t)}</div>`:""}\n </div>\n `}renderHtmlField(e){const{html:t="",class:i=""}=e;return`\n <div class="form-html ${i}">\n ${t}\n </div>\n `}renderHeaderField(e){const{text:t="",level:i=3,class:s="",id:a=""}=e,n=Math.max(1,Math.min(6,parseInt(i)));return`<h${n}${a?` id="${this.escapeHtml(a)}"`:""}${s?` class="${this.escapeHtml(s)}"`:""}>${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 e.MOJOUtils.getContextData(this.data,t)}renderTagField(e){const{name:t,label:i,value:s="",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)??s;return`\n <div class="mojo-form-control">\n ${i?`<label for="${u}" class="${this.options.labelClass}">${this.escapeHtml(i)}${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 data-change-action="validate-field">\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:i,value:s="",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,help:m=e.helpText||e.help||""}=e,g=this.getFieldId(t),f=this.errors[t],b=this.getFieldValue(t)??s;return`\n <div class="mojo-form-control">\n ${i?`<label for="${g}" class="${this.options.labelClass}">${this.escapeHtml(i)}${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:b,placeholder:a,labelField:d,valueField:c,maxItems:h,emptyFetch:u,debounceMs:p,disabled:l,readonly:r,required:n})}'>\n <input type="text"\n id="${g}"\n name="${t}_display"\n class="${this.options.inputClass}${f?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n ${l?"disabled":""}\n ${r?"readonly":""}\n data-change-action="validate-field">\n <input type="hidden" name="${t}" value="${this.escapeHtml(b)}">\n <small class="form-text text-muted">This will be enhanced with CollectionSelect component</small>\n </div>\n ${m?`<div class="${this.options.helpClass}">${this.escapeHtml(m)}</div>`:""}\n ${f?`<div class="${this.options.errorClass}">${this.escapeHtml(f)}</div>`:""}\n </div>\n `}renderDatePickerField(e){const{name:t,label:i,value:s="",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)??s;return`\n <div class="mojo-form-control">\n ${i?`<label for="${p}" class="${this.options.labelClass}">${this.escapeHtml(i)}${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 data-change-action="validate-field">\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:i,endName:s,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||i||"daterange"),D=this.errors[t],w=i||(t?t+"_start":""),F=s||(t?t+"_end":""),$=this.getFieldValue(w)||l,C=this.getFieldValue(F)||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||i||"daterange"}"\n data-field-type="daterange"\n data-field-config='${JSON.stringify({name:t,startName:i,endName:s,fieldName:a,startDate:$,endDate:C,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}${D?" 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 data-change-action="validate-field">\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}${D?" is-invalid":""}"\n value="${this.escapeHtml(C)}"\n placeholder="End date..."\n ${u?`min="${u}"`:""}\n ${p?`max="${p}"`:""}\n ${c?"disabled":""}\n ${h?"readonly":""}\n ${d?"required":""}\n data-change-action="validate-field">\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 ${D?`<div class="${this.options.errorClass}">${this.escapeHtml(D)}</div>`:""}\n </div>\n `}renderChecklistDropdownField(t){const i=this.getFieldId(t.name),s=this.getFieldValue(t.name)??[],a={fieldId:i,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:s.includes(e.value)}))};return e.Mustache.render(this.templates.checklistdropdown,a)}renderButtonGroupField(t){const i=this.getFieldId(t.name),s=this.getFieldValue(t.name)??t.value,a={fieldId:i,fieldName:t.name,size:t.size||"sm",variant:t.variant||"outline-primary",options:t.options.map(e=>({value:e.value,label:e.label,active:e.value===s,buttonClass:this.getButtonClass(e.value===s,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}`}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 i=this._fileDropConfig.multiple?t:[t[0]];let s={valid:!0,errors:[]};if(!this._fileDropConfig.validateOnDrop||(s=this._validateFileDropFiles(i),s.valid))if("function"==typeof this.onFileDrop)try{await this.onFileDrop(i,e,s)}catch(a){"function"==typeof this.onFileDropError?await this.onFileDropError(a,e,i):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(s.errors.join(", ")),e,i)},_applyFileDropVisualFeedback(e){if(!this._fileDropConfig.visualFeedback||!this._fileDropZone)return;const{dragOverClass:t,dragActiveClass:i}=this._fileDropConfig;e?this._fileDropZone.classList.add(t,i):this._fileDropZone.classList.remove(t,i)},_validateFileDropFiles(e){const t=[],i=this._fileDropConfig;for(const s of e)this._isFileDropTypeAccepted(s.type)?s.size>i.maxFileSize&&t.push(`File "${s.name}" (${this._formatFileDropSize(s.size)}) exceeds maximum size (${this._formatFileDropSize(i.maxFileSize)})`):t.push(`File type "${s.type}" is not accepted for file "${s.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 i=t.split("/")[0];return e.startsWith(i+"/")}return!1})},_formatFileDropSize(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,t)).toFixed(2))+" "+["Bytes","KB","MB","GB"][t]}};function i(e){Object.assign(e.prototype,t)}class TagInputView extends e.View{constructor(e={}){const{name:t,value:i="",placeholder:s="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=s,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,i&&(this.tags=this.parseTagString(i))}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 i=parseInt(t.getAttribute("data-tag-index"));i>=0&&i<this.tags.length&&await this.removeTag(i)}async onChangeInputChange(e,t){const i=t.value,s=i.slice(-1);if(s===this.separator||"\n"===s){e.preventDefault();const s=i.slice(0,-1);return void(s.trim()&&(await this.addTag(s),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,i=t.value||"";switch(e.key){case"Enter":case"Tab":case",":i.trim()&&(e.preventDefault(),this.addTag(i),t.value="");break;case"Backspace":""===i&&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(""===i&&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(""===i&&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,console.log(`Focused tag index: ${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 i=this.element.querySelector(".tag-input-wrapper");i&&i.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 e=this.collection?this.collection.toJSON().map((e,t)=>({...e,labelField:e[this.labelField],valueField:e[this.valueField],isSelected:e[this.valueField]==this.selectedValue,isFocused:t===this.focusedIndex,index:t})):[];return{loading:this.loading,hasSearched:this.hasSearched,showNoResults:!this.loading&&this.hasSearched&&0===e.length,items:e}}async handleActionSelectItem(e,t){e.preventDefault();const i=t.getAttribute("data-value"),s=t.getAttribute("data-label");this.emit("item-selected",{value:i,label:s})}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(e={}){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 ',...e}),this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.maxItems=e.maxItems||10,this.placeholder=e.placeholder||"Search...",this.debounceMs=e.debounceMs||1e3,this.name=e.name||"collection_select",this.emptyFetch=!1!==e.emptyFetch,this.selectedValue=e.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=this.selectedValue[this.labelField]||"",this.selectedValue=this.selectedValue[this.valueField]||"0"),this.searchTimer=null,this.dropdownView=null,this.defaultParams={},this.handleDocumentClick=this.handleDocumentClick.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleInputEvents=this.handleInputEvents.bind(this),this.collection&&this.setupCollection()}setupCollection(){this.defaultParams={...this.collection.params},this.collection.params.size=this.maxItems,this.defaultParams.size=this.maxItems,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{const e=this.collection?.get(this.selectedValue);if(e)return this.selectedLabel=e.get(this.labelField),void this.render();let t=await this.collection.fetchOne(this.selectedValue);t&&(this.selectedLabel=t.get(this.labelField,`${t.constructor.name} #${t.id}`),this.render())}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 i=this.getInput();i&&(i.value=t);const s=this.getHiddenInput();s&&(s.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 i=this.getInput();i&&(i.value=t);const s=this.getHiddenInput();s&&(s.value=e)}getValue(){return 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 this.selectedValue}setFormValue(e){let t=e,i="";t&&"object"==typeof t&&(i=t[this.labelField]||"",t=t[this.valueField]),t=t||"0",t!=this.selectedValue&&(this.selectedValue=t,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())}}class DatePicker extends e.View{constructor(e={}){const{name:t,value:i="",format:s="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=s,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=i,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 i=document.createElement("link");i.rel="stylesheet",i.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(i);const s=document.createElement("script");s.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",s.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},s.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(s)})}async renderTemplate(){const e=this.getInputId(),t=this.useNative?"date":"text",i=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(i)}"\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,i){const s=i.value;this.handleDateChange(s)}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 i=new Date(e);if(isNaN(i.getTime()))return"";const s=i.getFullYear(),a=String(i.getMonth()+1).padStart(2,"0"),n=String(i.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":default:return`${s}-${a}-${n}`;case"MM/DD/YYYY":return`${a}/${n}/${s}`;case"DD/MM/YYYY":return`${n}/${a}/${s}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][i.getMonth()]} ${n}, ${s}`}}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:i,endName:s,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:D=!0,inline:w=!1,separator:F=" - ",...$}=e;super({tagName:"div",className:`date-range-picker-view ${v}`,...$}),this.name=t,this.startName=i,this.endName=s,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=D,this.inline=w,this.separator=F,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 i=document.createElement("link");i.rel="stylesheet",i.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(i);const s=document.createElement("script");s.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",s.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},s.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(s)})}async renderTemplate(){const e=this.getInputId(),t=this.getDisplayValue();if(this.useNative)return this.renderNativeTemplate(e);const i=this.startName||(this.name?`${this.name}_start`:""),s=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 ${i?`<input type="hidden" name="${i}" value="${this.escapeHtml(a)}" />`:""}\n ${s?`<input type="hidden" name="${s}" 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:i}=e.detail;this.handleRangeChange(t?this.formatDate(t,this.format):"",i?this.formatDate(i,this.format):"")}),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,i){}async onChangeStartDateChanged(e,t,i){const s=i.value;this.handleRangeChange(s,this.currentEndDate),this.updateConstraints()}async onChangeEndDateChanged(e,t,i){const s=i.value;this.handleRangeChange(this.currentStartDate,s),this.updateConstraints()}handleRangeChange(e,t){const i=this.currentStartDate,s=this.currentEndDate;this.currentStartDate=e,this.currentEndDate=t,this.updateHiddenInputs(),i===e&&s===t||(this.emit("change",{startDate:e,endDate:t,combined:this.getCombinedValue(),formatted:this.getDisplayValue(),oldStartDate:i,oldEndDate:s}),this.emit("range:changed",{startDate:e,endDate:t,oldStartDate:i,oldEndDate:s}))}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))}formatDate(e,t=this.format){if(!e)return"";const i=new Date(e);if(isNaN(i.getTime()))return"";const s=i.getFullYear(),a=String(i.getMonth()+1).padStart(2,"0"),n=String(i.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":default:return`${s}-${a}-${n}`;case"MM/DD/YYYY":return`${a}/${n}/${s}`;case"DD/MM/YYYY":return`${n}/${a}/${s}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][i.getMonth()]} ${n}, ${s}`}}formatForOutput(e){if(!e)return"";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`:""),i=e?this.element?.querySelector(`[name="${e}"]`):null,s=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;i&&(i.value=this.currentStartDate?this.formatForOutput(this.currentStartDate):""),s&&(s.value=this.currentEndDate?this.formatForOutput(this.currentEndDate):""),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 i=this.element?.querySelector(`[name="${this.name}_start"]`),s=this.element?.querySelector(`[name="${this.name}_end"]`);i&&(i.value=this.formatDate(e,"YYYY-MM-DD")),s&&(s.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 FormView extends e.View{constructor(e={}){const{formConfig:t=e.config,fields:i,model:s=null,data:a={},defaults:n={},errors:l={},fileHandling:r="base64",...o}=e;super({tagName:"div",className:"form-view",...o}),this.model=s,this.defaults=n,this._originalData=a,this.errors=l,this.loading=!1,this.fileHandling=r,this.customComponents=/* @__PURE__ */new Map,this.data=this.prepareFormData(),this.formConfig=t||{fields:i||[]},this.formBuilder=new FormBuilder({...this.formConfig,data:this.data,errors:l})}prepareFormData(){const e={...this.defaults};return this.model&&(this.model.attributes&&"object"==typeof this.model.attributes?Object.assign(e,this.model.attributes):"function"==typeof this.model.toJSON?Object.assign(e,this.model.toJSON()):"object"==typeof this.model&&this.model.constructor===Object&&Object.assign(e,this.model)),this._originalData&&Object.assign(e,this._originalData),e}async renderTemplate(){return this.formBuilder.buildFormHTML()}async onAfterRender(){await super.onAfterRender(),this.initializeImageFields(),this.initializeCustomComponents()}initializeImageFields(){this.element.querySelectorAll(".image-drop-zone.droppable").length>0&&(i(FormView),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.initializeDatePickers(),this.initializeDateRangePickers(),this.element.querySelectorAll("[data-component]").forEach(e=>{const t=e.getAttribute("data-component"),i=e.getAttribute("data-field");t&&i&&console.log(`Found ${t} component for field: ${i}`)})}initializeTagInputs(){this.element.querySelectorAll('[data-field-type="tag"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=new TagInputView({...s,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){console.error("Failed to initialize TagInput:",t)}})}initializeCollectionSelects(){this.element.querySelectorAll('[data-field-type="collection"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=this.getFormFieldConfig(t);if(!a||!a.Collection)return void console.warn(`CollectionSelect field ${t} missing Collection class`);const n=new a.Collection;a.collectionParams&&(n.params={...n.params,...a.collectionParams});const l=new CollectionSelectView({...s,collection:n,containerId:null});l.render(!0,e),this.customComponents.set(t,l),l.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){console.error("Failed to initialize CollectionSelect:",t)}})}initializeDatePickers(){this.element.querySelectorAll('[data-field-type="datepicker"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=new DatePicker({...s,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){console.error("Failed to initialize DatePicker:",t)}})}initializeDateRangePickers(){this.element.querySelectorAll('[data-field-type="daterange"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),i=e.getAttribute("data-field-config"),s=JSON.parse(i),a=new DateRangePicker({...s,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.combined)})}catch(t){console.error("Failed to initialize DateRangePicker:",t)}})}handleFieldChange(e,t){this.data[e]=t,this.model&&this.options.allowModelChange&&this.model.set(e,t),this.emit("field:change",{field:e,value:t})}refreshForm(){this.data=this.prepareFormData(),this.formBuilder=new FormBuilder({...this.formConfig,data:this.data,errors:this.errors}),this.element&&this.render()}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 i=Boolean(e);return`boolean: ${null!=t&&Boolean(t)} → ${i}`}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 i=await this.handleSubmit();i.success?(this.data=i.data,this.emit("submit",{data:i.data,result:i.result,form:this,event:e}),!this.model&&this.formConfig.onSubmit&&"function"==typeof this.formConfig.onSubmit&&await this.formConfig.onSubmit(i.data,this)):this.emit("error",{error:i.error,result:i,form:this})}async onActionResetForm(e,t){const i=this.getFormElement();i&&(i.reset(),this.data={},this.clearAllErrors(),this.emit("reset",{form:this,event:e}))}async onActionClickImageUpload(e,t){const i=t.getAttribute("data-field-id");if(!i)return;const s=this.element.querySelector(`#${i}`);s&&!s.disabled&&s.click()}async onActionRemoveImage(e,t){const i=t.getAttribute("data-field");if(!i)return;const s=this.element.querySelector(`input[name="${i}"]`);s&&(s.value="",s.dispatchEvent(new Event("change",{bubbles:!0}))),delete this.data[i],this.emit("change",{field:i,value:null,form:this}),await this.updateField(i)}async onActionSelectButtonOption(e,t){const i=t.getAttribute("data-field"),s=t.getAttribute("data-value");if(!i||!s)return;this.data[i]=s;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:i,value:s,form:this}),this.emit("change",{field:i,value:s,form:this}),this.emit("form:changed",await this.getFormData())}async onActionApplyFilter(e,t){const i=t.closest(".dropdown"),s=i?.querySelectorAll('input[type="checkbox"]');if(!s||0===s.length)return;const a=s[0].getAttribute("data-field");if(!a)return;const n=[];s.forEach(e=>{e.checked&&n.push(e.value)}),this.data[a]=n;const l=i.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 i=t.name;i&&(this.validateField(i),this.emit("change",{field:i,value:t.value,form:this}))}async onChangeToggleSwitch(e,t){const i=t.getAttribute("data-field");i&&(this.data[i]=t.checked,this.emit("switch:toggle",{field:i,checked:t.checked,form:this}),this.emit("change",{field:i,value:t.checked,form:this}))}async onChangeImageSelected(e,t){const i=t.getAttribute("data-field"),s=t.files[0];if(i&&s){const e=this.findFieldConfig(i),n=URL.createObjectURL(s);if(e&&e.imageSize)try{const a=window.MOJO?.plugins?.ImageCropView;if(!a)return void console.warn("ImageCropView not available. Load lightbox extension for image cropping.");const l=await a.showDialog(n,{title:`Crop ${e.label||i}`,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],s.name,{type:s.type||"image/png"});this.data[i]=a,await this.updateImagePreview(i,l.data),this.emit("image:selected",{field:i,file:a,originalFile:s,cropped:!0,cropData:l.cropData,form:this}),this.emit("change",{field:i,value:a,form:this})}else t.value=""}catch(a){console.error("Error during image cropping:",a),this.data[i]=s,await this.updateImagePreview(i,n),this.emit("image:selected",{field:i,file:s,form:this}),this.emit("change",{field:i,value:s,form:this})}else this.data[i]=s,await this.updateImagePreview(i,n),this.emit("image:selected",{field:i,file:s,form:this})}}async onChangeFileSelected(e,t){const i=Array.from(t.files);this.emit("file:selected",{field:t.name,files:i,form:this}),this.emit("change",{field:t.name,value:i,form:this})}async onChangeRangeChanged(e,t){const i=t.getAttribute("data-target");if(i){const e=this.element.querySelector(`#${i}`);e&&(e.textContent=t.value)}this.emit("range:changed",{field:t.name,value:t.value,form:this}),this.emit("change",{field:t.name,value:t.value,form:this})}async onChangeFilterSearch(e,t){const i=t.value;this.emit("search",{query:i,field:t.name,form:this})}async onChangeFilterSelectOptions(e,t){const i=t.value.toLowerCase(),s=t.getAttribute("data-target"),a=s?this.element.querySelector(`#${s}`):null;a&&a.querySelectorAll("option").forEach(e=>{const t=e.textContent.toLowerCase();e.style.display=t.includes(i)?"":"none"})}async onFileDrop(e,t,i){const s=t.target.closest(".image-drop-zone");if(!s)return;const a=s.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,i){console.error("File drop error:",e.message),this.showError(`File upload error: ${e.message}`),this.emit("file:error",{error:e,files:i,form:this})}getFormElement(){return this.element?this.element.querySelector("form"):null}getFormFieldConfig(e){const t=i=>{for(const s of i){if(s.name===e)return s;if(s.fields&&Array.isArray(s.fields)){const e=t(s.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,i]of Object.entries(this.data))if(i instanceof File)t.set(e,i);else if(i instanceof FileList)for(let s=0;s<i.length;s++)t.append(`${e}[${s}]`,i[s]);return t}{const i=new FormData(e),s={};for(const[e,t]of i.entries())s[e]?(Array.isArray(s[e])||(s[e]=[s[e]]),s[e].push(t)):s[e]=t;e.querySelectorAll('input[type="checkbox"]').forEach(e=>{s[e.name]=e.checked}),e.querySelectorAll('[data-field-type="json"]').forEach(e=>{try{s[e.name]=JSON.parse(e.value)}catch(t){console.warn(`Invalid JSON in field ${e.name}:`,e.value),s[e.name]=e.value}});for(const[e,a]of Object.entries(this.data))if(a instanceof File)try{s[e]=await this.fileToBase64(a)}catch(t){console.error(`Failed to convert file ${e} to base64:`,t),s[e]=null}else if(a instanceof FileList){const i=[];for(let s=0;s<a.length;s++)try{i.push(await this.fileToBase64(a[s]))}catch(t){console.error(`Failed to convert file ${e}[${s}] to base64:`,t),i.push(null)}s[e]=i}return s}}_onModelChange(){this.isMounted()&&this.refreshForm()}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 console.error("Form submission error:",e),{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 console.log("No changes detected, skipping save"),{success:!0,message:"No changes to save",data:e};console.log("Saving changed data via model:",t),console.log("Data type:",t instanceof FormData?"FormData (multipart)":"Object (JSON/base64)");try{const e=await this.model.save(t);return console.log("Model save result:",e),e}catch(i){throw console.error("Model save error:",i),i}}getChangedData(e){if(!this.model)return e;const t=this.getOriginalModelData();let i;return console.log("=== Change Detection ==="),console.log("Original model data:",t),console.log("Current form data:",e instanceof FormData?"[FormData object]":e),e instanceof FormData?(console.log("Comparing FormData..."),i=this.getChangedFormData(e,t)):(console.log("Comparing Object data..."),i=this.getChangedObjectData(e,t)),console.log("Changes detected:",i instanceof FormData?"[FormData with changes]":i),console.log("=== End Change Detection ==="),i}getOriginalModelData(){return this.model.attributes?this.model.attributes:"function"==typeof this.model.toJSON?this.model.toJSON():{}}getChangedFormData(e,t){const i=new FormData;let s=!1;for(const[a,n]of e.entries())if(n instanceof File)0===n.size||""===n.name||"blob"===n.name?console.log(` - ${a}: Empty file field (no change)`):(console.log(` - ${a}: File upload detected (${n.name}, ${n.size} bytes)`),i.set(a,n),s=!0);else{const e=t[a];n!==e&&n!==String(e)?(console.log(` - ${a}: "${e}" → "${n}"`),i.set(a,n),s=!0):console.log(` - ${a}: unchanged ("${n}")`)}return s?i:null}getChangedObjectData(e,t){const i={};let s=!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)&&(i[l]=r,s=!0)}return s?i:null}valuesAreDifferent(e,t,i="text",s={}){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"===i&&"object"==typeof t&&null!=t&&"string"==typeof e){if("0"===e)return null!==t;if(t[s.valueField||"id"]==e)return!1}return"switch"===i||"checkbox"===i?!!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 i=t.elements[e];if(!i)return!1;const s=i.checkValidity();return s?(i.classList.remove("is-invalid"),i.classList.add("is-valid"),delete this.errors[e]):(i.classList.remove("is-valid"),i.classList.add("is-invalid"),this.errors[e]=i.validationMessage),s}focusFirstError(){const e=this.getFormElement();if(!e)return;const t=e.querySelector(":invalid");t&&(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 i=t.querySelectorAll("input, select, textarea, button"),s=t.querySelector('button[type="submit"]');if(e)i.forEach(e=>e.disabled=!0),s&&(s.innerHTML='<span class="spinner-border spinner-border-sm me-2"></span>Loading...');else if(i.forEach(e=>e.disabled=!1),s){const e=this.formConfig.options?.submitButton||"Submit";s.innerHTML="string"==typeof e?e:"Submit"}}showError(e){if(console.error("Form error:",e),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.formConfig,data:this.data,errors:this.errors}),await this.render()}async updateImagePreview(e,t){const i=this.element.querySelector(`[data-field="${e}"].image-drop-zone`);if(!i)return void console.warn(`Could not find drop zone for field: ${e}`);let s=i.querySelector("img");const a=i.querySelector(".bi-image")?.parentElement,n=i.getAttribute("data-field-id");if(t){if(s)s.src=t;else{const s=`${n}_preview`;i.innerHTML=`\n <img id="${s}"\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=i=>{for(const s of i){if(s.name===e)return s;if("group"===s.type&&s.fields){const e=t(s.fields);if(e)return e}}return null};return t(this.formConfig.fields||[])}async fileToBase64(e){return new Promise((t,i)=>{const s=new FileReader;s.onload=()=>t(s.result),s.onerror=i,s.readAsDataURL(e)})}hasFiles(e){if(e instanceof FormData){for(const[t,i]of e.entries())if(i 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.formConfig,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()}}const s=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,FormView:FormView,default:FormView},Symbol.toStringTag,{value:"Module"}));exports.FormView=FormView,exports.FormView$1=s,exports.applyFileDropMixin=i;
2
+ //# sourceMappingURL=FormView-CG2dIaZ6.js.map