web-mojo 2.1.761 → 2.1.764
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +10 -10
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.cjs.js.map +1 -1
- package/dist/auth.es.js +3 -3
- package/dist/auth.es.js.map +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +2 -2
- package/dist/chunks/ChatView-6LQ_oPei.js +2 -0
- package/dist/chunks/{ChatView-CQLSGsuA.js.map → ChatView-6LQ_oPei.js.map} +1 -1
- package/dist/chunks/{ChatView-BaKGKYIW.js → ChatView-CUXfrhfS.js} +6 -6
- package/dist/chunks/{ChatView-BaKGKYIW.js.map → ChatView-CUXfrhfS.js.map} +1 -1
- package/dist/chunks/{ContextMenu-Ccy78itn.js → ContextMenu-DNlKavzq.js} +2 -2
- package/dist/chunks/{ContextMenu-Ccy78itn.js.map → ContextMenu-DNlKavzq.js.map} +1 -1
- package/dist/chunks/ContextMenu-kKsJ3HZJ.js +3 -0
- package/dist/chunks/{ContextMenu-EtrS3sqW.js.map → ContextMenu-kKsJ3HZJ.js.map} +1 -1
- package/dist/chunks/{DataView-B-NO26Rf.js → DataView-C4Wpf5d_.js} +2 -2
- package/dist/chunks/{DataView-B-NO26Rf.js.map → DataView-C4Wpf5d_.js.map} +1 -1
- package/dist/chunks/{DataView-DNnk7fTT.js → DataView-DGrhabJO.js} +2 -2
- package/dist/chunks/{DataView-DNnk7fTT.js.map → DataView-DGrhabJO.js.map} +1 -1
- package/dist/chunks/{Dialog-v6xXatPb.js → Dialog-CkXY2a9X.js} +2 -2
- package/dist/chunks/{Dialog-v6xXatPb.js.map → Dialog-CkXY2a9X.js.map} +1 -1
- package/dist/chunks/{Dialog-CBChqVIZ.js → Dialog-CqBiQSWd.js} +5 -5
- package/dist/chunks/{Dialog-CBChqVIZ.js.map → Dialog-CqBiQSWd.js.map} +1 -1
- package/dist/chunks/{FormView-B95jjgZr.js → FormView-CZ0bOvKY.js} +2 -2
- package/dist/chunks/{FormView-B95jjgZr.js.map → FormView-CZ0bOvKY.js.map} +1 -1
- package/dist/chunks/FormView-DUy10RuY.js +3 -0
- package/dist/chunks/{FormView-BzhM9nar.js.map → FormView-DUy10RuY.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-NAYKIDax.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-D5P4K2VW.js.map → MetricsMiniChartWidget-NAYKIDax.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-BLO12yFH.js → MetricsMiniChartWidget-hhVuYbr3.js} +3 -3
- package/dist/chunks/{MetricsMiniChartWidget-BLO12yFH.js.map → MetricsMiniChartWidget-hhVuYbr3.js.map} +1 -1
- package/dist/chunks/{PDFViewer-DO4ec_8P.js → PDFViewer-BmpTBi_2.js} +3 -3
- package/dist/chunks/{PDFViewer-DO4ec_8P.js.map → PDFViewer-BmpTBi_2.js.map} +1 -1
- package/dist/chunks/{PDFViewer-DHetE0mX.js → PDFViewer-DCLvYyXB.js} +2 -2
- package/dist/chunks/{PDFViewer-DHetE0mX.js.map → PDFViewer-DCLvYyXB.js.map} +1 -1
- package/dist/chunks/Page-DuN5VWhO.js +2 -0
- package/dist/chunks/{Page-BQ7bila2.js.map → Page-DuN5VWhO.js.map} +1 -1
- package/dist/chunks/{Page-Dn1oAANw.js → Page-XSD0OIyV.js} +2 -2
- package/dist/chunks/{Page-Dn1oAANw.js.map → Page-XSD0OIyV.js.map} +1 -1
- package/dist/chunks/{TokenManager-BXQKyhDc.js → TokenManager-CJBYcVqs.js} +2 -2
- package/dist/chunks/{TokenManager-BXQKyhDc.js.map → TokenManager-CJBYcVqs.js.map} +1 -1
- package/dist/chunks/{TopNav-OcY6ViTW.js → TopNav-CCXeeKNd.js} +5 -5
- package/dist/chunks/{TopNav-OcY6ViTW.js.map → TopNav-CCXeeKNd.js.map} +1 -1
- package/dist/chunks/TopNav-Db0bv7pM.js +2 -0
- package/dist/chunks/{TopNav-Dxw-w4PV.js.map → TopNav-Db0bv7pM.js.map} +1 -1
- package/dist/chunks/{WebApp-D0KJTN0p.js → WebApp-BZsvkqBm.js} +13 -19
- package/dist/chunks/{WebApp-D0KJTN0p.js.map → WebApp-BZsvkqBm.js.map} +1 -1
- package/dist/chunks/WebApp-BoCsFADo.js +2 -0
- package/dist/chunks/{WebApp-DlfbVd3B.js.map → WebApp-BoCsFADo.js.map} +1 -1
- package/dist/chunks/WebSocketClient-BITilqco.js +2 -0
- package/dist/chunks/{WebSocketClient-Dbz1XNJA.js.map → WebSocketClient-BITilqco.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.cjs.js.map +1 -1
- package/dist/docit.es.js +5 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +256 -29
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.cjs.js.map +1 -1
- package/dist/lightbox.es.js +4 -4
- package/package.json +1 -1
- package/dist/chunks/ChatView-CQLSGsuA.js +0 -2
- package/dist/chunks/ContextMenu-EtrS3sqW.js +0 -3
- package/dist/chunks/FormView-BzhM9nar.js +0 -3
- package/dist/chunks/MetricsMiniChartWidget-D5P4K2VW.js +0 -2
- package/dist/chunks/Page-BQ7bila2.js +0 -2
- package/dist/chunks/TopNav-Dxw-w4PV.js +0 -2
- package/dist/chunks/WebApp-DlfbVd3B.js +0 -2
- package/dist/chunks/WebSocketClient-Dbz1XNJA.js +0 -2
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("./WebApp-DlfbVd3B.js");class FormBuilder{constructor(e={}){this.fields=e.fields||[],this.structureOnly=e.structureOnly||!1,this.fields.forEach(e=>{e.cols&&!e.columns?(e.columns=e.cols,delete e.cols):e.columns||(e.columns=12),"group"===e.type&&e.fields&&e.fields.forEach(e=>{e.cols&&!e.columns&&(e.columns=e.cols,delete e.cols)})}),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}} {{{attrs}}}>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',password:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="input-group">\n <input type="password" id="{{fieldId}}" name="{{name}}"\n class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}}\n data-field-type="password" {{{attrs}}}>\n {{#showToggle}}\n <button type="button" class="btn btn-outline-secondary"\n data-action="toggle-password"\n data-target="{{fieldId}}"\n aria-label="Show password" aria-pressed="false">\n <i class="bi bi-eye"></i>\n </button>\n {{/showToggle}}\n </div>\n {{#strengthMeter}}\n <div class="mt-2">\n <div class="progress" style="height: 4px;">\n <div class="progress-bar bg-secondary" role="progressbar"\n style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"\n id="{{fieldId}}_strength_bar"></div>\n </div>\n <small class="{{helpClass}}" id="{{fieldId}}_strength_text">Strength</small>\n </div>\n {{/strengthMeter}}\n {{#capsLockWarning}}\n <div class="{{helpClass}} text-warning d-none" id="{{fieldId}}_caps_warning">\n Caps Lock is on\n </div>\n {{/capsLockWarning}}\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',textarea:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>{{fieldValue}}</textarea>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',select:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n {{#searchInput}}{{{searchInput}}}{{/searchInput}}\n <select id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#multiple}}multiple{{/multiple}} {{{attrs}}}>\n {{{optionsHTML}}}\n </select>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',checkbox:'\n <div class="mojo-form-control">\n <div class="form-check {{fieldClass}}">\n <input type="checkbox" id="{{fieldId}}" name="{{name}}"\n class="form-check-input{{#error}} is-invalid{{/error}}" value="{{value}}"\n {{#checked}}checked{{/checked}} {{#required}}required{{/required}}\n {{#disabled}}disabled{{/disabled}} {{{attrs}}}>\n <label class="form-check-label" for="{{fieldId}}">{{label}}</label>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n </div>\n ',switch:'\n <div class="mojo-form-control">\n <div class="form-check form-switch {{sizeClass}} {{fieldClass}}">\n <input type="checkbox" id="{{fieldId}}" name="{{name}}" role="switch"\n class="form-check-input{{#error}} is-invalid{{/error}}" value="{{value}}"\n {{#checked}}checked{{/checked}} {{#required}}required{{/required}}\n {{#disabled}}disabled{{/disabled}} data-change-action="toggle-switch"\n data-field="{{name}}" {{{attrs}}}>\n <label class="form-check-label" for="{{fieldId}}">{{label}}</label>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',image:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="image-field-container {{containerClass}}" id="{{dropZoneId}}" style="width: {{width}}px; height: {{height}}px;">\n <input type="file" id="{{fieldId}}" name="{{name}}" class="{{inputClass}} d-none{{#error}} is-invalid{{/error}}"\n accept="{{accept}}" {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n data-change-action="image-selected" data-field="{{name}}" {{{attrs}}}>\n <div class="image-drop-zone{{#allowDrop}} droppable{{/allowDrop}}" data-action="click-image-upload"\n data-field-id="{{fieldId}}" data-field="{{name}}" style="width: 100%; height: 100%; cursor: {{cursor}};">\n {{#imageUrl}}\n <img id="{{previewId}}" src="{{imageUrl}}" alt="Preview" class="img-thumbnail w-100 h-100" style="object-fit: cover;">\n {{#showRemove}}\n <button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"\n data-action="remove-image" data-field-id="{{fieldId}}" data-field="{{name}}" style="opacity: 0.8;">\n <i class="bi bi-x"></i>\n </button>\n {{/showRemove}}\n {{/imageUrl}}\n {{^imageUrl}}\n <div class="d-flex flex-column align-items-center justify-content-center h-100 text-muted border border-2 border-dashed">\n <i class="bi bi-image fs-1 mb-2"></i>\n <small class="text-center px-2">{{placeholderText}}</small>\n </div>\n {{/imageUrl}}\n </div>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',range:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}: <span id="{{fieldId}}_value">{{fieldValue}}</span>\n </label>\n {{/label}}\n <input type="range" id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n min="{{min}}" max="{{max}}" step="{{step}}" value="{{fieldValue}}" {{#disabled}}disabled{{/disabled}}\n data-change-action="range-changed" data-target="{{fieldId}}_value" {{{attrs}}}>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',file:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <input type="file" id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n accept="{{accept}}" {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#multiple}}multiple{{/multiple}} data-change-action="file-selected" {{{attrs}}}>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',radio:'\n <div class="mojo-form-control">\n {{#label}}<div class="{{labelClass}}">{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}</div>{{/label}}\n {{#options}}\n <div class="form-check">\n <input type="radio" id="{{fieldId}}_{{value}}" name="{{name}}" value="{{value}}"\n class="form-check-input{{#error}} is-invalid{{/error}}" {{#checked}}checked{{/checked}}\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{{attrs}}}>\n <label class="form-check-label" for="{{fieldId}}_{{value}}">{{text}}</label>\n </div>\n {{/options}}\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n ',button:'\n <button type="button" {{#name}}name="{{name}}"{{/name}} class="btn {{fieldClass}}"\n {{#action}}data-action="{{action}}"{{/action}} {{#disabled}}disabled{{/disabled}} {{{attrs}}}>\n {{label}}\n </button>\n ',divider:'\n <hr class="{{dividerClass}}">\n ',html:'\n <div class="form-html {{fieldClass}}">{{{html}}}</div>\n ',header:'\n <h{{level}} {{#id}}id="{{id}}"{{/id}} class="text-primary mb-3 {{fieldClass}}">{{text}}</h{{level}}>\n ',hidden:'\n <input type="hidden" id="{{fieldId}}" name="{{name}}" value="{{fieldValue}}">\n ',checklistdropdown:'\n <div class="dropdown">\n <button class="{{buttonClass}}" type="button" data-bs-toggle="dropdown">\n <i class="{{buttonIcon}} me-1"></i> {{buttonText}}\n </button>\n <div class="{{dropdownClass}}" style="min-width: {{minWidth}};">\n {{#options}}\n <div class="form-check">\n <input class="form-check-input" type="checkbox"\n value="{{value}}"\n id="{{id}}"\n {{#checked}}checked{{/checked}}\n data-change-action="update-checklist"\n data-field="{{fieldName}}">\n <label class="form-check-label" for="{{id}}">\n {{label}}\n </label>\n </div>\n {{/options}}\n <hr class="my-2">\n <button class="btn btn-sm btn-primary w-100" data-action="apply-filter">\n Apply\n </button>\n </div>\n </div>\n ',buttongroup:'\n <div class="btn-group btn-group-{{size}}" role="group">\n {{#options}}\n <button type="button" class="{{buttonClass}} {{#active}}active{{/active}}"\n {{^action}}\n data-action="select-button-option"\n {{/action}}\n {{#action}}\n data-action=\'{{action}}\'\n {{/action}}\n data-field="{{fieldName}}"\n data-value="{{value}}">\n {{#icon}}<i class="{{icon}} me-1"></i> {{/icon}} {{label}}\n </button>\n {{/options}}\n </div>\n ',toolbarform:'\n <div class="mojo-toolbar-form">\n <div class="row g-2 align-items-center">\n {{#fields}}\n <div class="{{containerClass}}">\n {{#label}}<label class="form-label-sm mb-1">{{label}}</label>{{/label}}\n {{{fieldHtml}}}\n </div>\n {{/fields}}\n </div>\n </div>\n ',color:'\n <div class="mojo-form-control">\n {{#label}}\n <label for="{{fieldId}}" class="{{labelClass}}">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n <div class="d-flex align-items-center gap-2">\n <input type="color" id="{{fieldId}}" name="{{name}}"\n class="{{inputClass}}{{#error}} is-invalid{{/error}}"\n value="{{fieldValue}}"\n {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}\n {{#readonly}}readonly{{/readonly}} {{{attrs}}}>\n <button type="button" class="btn-sm text-muted border-0 bg-transparent p-1"\n data-action="clear-color" data-field="{{name}}"\n title="Clear color">\n <i class="bi bi-x fs-5"></i>\n </button>\n </div>\n {{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}\n {{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}\n </div>\n '}}buildFormHTML(){const e=this.buildFieldsHTML(),t=this.buildButtonsHTML();return`\n <form class="${this.options.formClass}" novalidate>\n ${e}\n ${t}\n </form>\n `}isAutoSizingField(e){return!e.columns||"auto"===e.columns||""===e.columns}buildFieldsHTML(){const e=[];let t=0;for(;t<this.fields.length;){const s=this.fields[t];if(s.columns=s.columns||s.cols,"group"===s.type){const i=[s];let a=s.columns||12;"object"==typeof a&&null!==a&&(a=a.md||a.sm||a.xs||12);let n=t+1;for(;n<this.fields.length&&"group"===this.fields[n].type&&a<12;){const e=this.fields[n];let t=e.columns||12;if("object"==typeof t&&null!==t&&(t=t.md||t.sm||t.xs||12),!(a+t<=12))break;i.push(e),a+=t,n++}let l=i.length>1;if(1===i.length&&s.columns){let e=s.columns;"object"==typeof e&&null!==e&&(e=e.md||e.sm||e.xs||12),l=l||e<12}if(l){const t=i.map(e=>this.buildGroupHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(this.buildGroupHTML(s));t=n}else if(s.columns&&s.columns<12){const i=[s];let a=s.columns||12;"object"==typeof a&&null!==a&&(a=a.md||a.sm||a.xs||12);let n=t+1;for(;n<this.fields.length&&this.fields[n].columns&&a<12;){const e=this.fields[n];let t=e.columns||12;if("object"==typeof t&&null!==t&&(t=t.md||t.sm||t.xs||12),!(a+t<=12))break;i.push(e),a+=t,n++}const l=i.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${l}</div>`),t=n}else if(this.isAutoSizingField(s)){const i=[s];let a=t+1;for(;a<this.fields.length;){const e=this.fields[a];if(!this.isAutoSizingField(e))break;i.push(e),a++}if(i.length>1){const t=i.map(e=>this.buildFieldHTML(e)).join("");e.push(`<div class="row">${t}</div>`)}else e.push(`<div class="row">${this.buildFieldHTML(s)}</div>`);t=a}else e.push(this.buildFieldHTML(s)),t++}return e.join("")}buildGroupHTML(e){const{columns:t=12,title:s,fields:i=[],class:a="",titleClass:n="h6 mb-3",responsive:l={}}=e;let r=[];if("object"==typeof t&&null!==t){if(t.xs&&r.push(`col-${t.xs}`),t.sm&&r.push(`col-sm-${t.sm}`),t.md&&r.push(`col-md-${t.md}`),t.lg&&r.push(`col-lg-${t.lg}`),t.xl&&r.push(`col-xl-${t.xl}`),t.xxl&&r.push(`col-xxl-${t.xxl}`),!t.md&&(t.xs||t.sm)){const e=t.sm||t.xs;r.push(`col-md-${e}`)}0===r.length&&r.push("col-md-12")}else r.push(`col-md-${t}`);l.xs&&r.push(`col-${l.xs}`),l.sm&&r.push(`col-sm-${l.sm}`),l.lg&&r.push(`col-lg-${l.lg}`),l.xl&&r.push(`col-xl-${l.xl}`);const o=r.join(" "),d=i.map(e=>"group"===e.type?this.buildGroupHTML(e):this.buildFieldHTML(e)).join("");return`\n <div class="mojo-form-group ${o} ${a}">\n ${s?`<div class="${n}">${this.escapeHtml(s)}</div>`:""}\n <div class="row">\n ${d}\n </div>\n </div>\n `}buildFieldHTML(e){const{type:t,columns:s,class:i=""}=e;let a,n="";switch(console.log("buildFieldHTML - Processing field type:",t,"for field:",e.name),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"collectionmultiselect":case"collection-multiselect":n=this.renderCollectionMultiSelectField(e);break;case"datepicker":n=this.renderDatePickerField(e);break;case"daterange":n=this.renderDateRangeField(e);break;case"checklistdropdown":n=this.renderChecklistDropdownField(e);break;case"buttongroup":n=this.renderButtonGroupField(e);break;default:console.warn(`Unknown field type: ${t}`),n=this.renderTextField(e)}return a=this.isAutoSizingField(e)?`col ${i}`.trim():`col-${s} ${i}`.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){const t=e.passwordUsage||"current",s="new"===t||"new-password"===t?"new-password":"current-password",i={...e.attributes||{},autocomplete:e.attributes&&e.attributes.autocomplete||s};return this.renderInputField({...e,showToggle:!1!==e.showToggle,attributes:i},"password")}renderNumberField(e){const{min:t,max:s,step:i=1,...a}=e,n=[];return void 0!==t&&n.push(`min="${t}"`),void 0!==s&&n.push(`max="${s}"`),void 0!==i&&n.push(`step="${i}"`),this.renderInputField({...a,attributes:{...a.attributes,...n.reduce((e,t)=>{const[s,i]=t.split("=");return e[s]=i.replace(/"/g,""),e},{})}},"number")}renderTelField(e){return this.renderInputField(e,"tel")}renderUrlField(e){return this.renderInputField(e,"url")}renderSearchField(e){const t={...e,attributes:{"data-filter":"live-search","data-change-action":"filter-search","data-filter-debounce":e.debounce||"300",...e.attributes}};return this.renderInputField(t,"search")}renderHexField(e){const{hexType:t="color",allowPrefix:s=!0,minLength:i,maxLength:a,...n}=e;let l,r,o,d,c;switch(t){case"color":l=s?"^#?[0-9A-Fa-f]{6}$":"^[0-9A-Fa-f]{6}$",r=6,o=s?7:6,d=s?"#FF0000":"FF0000",c=c||"Enter a valid hex color (e.g., "+d+")";break;case"color-short":l=s?"^#?[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$":"^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$",r=s?4:3,o=s?7:6,d=s?"#F00 or #FF0000":"F00 or FF0000",c=c||"Enter a valid hex color (3 or 6 digits)";break;case"string":l="^[0-9A-Fa-f]+$",r=i||1,o=a||64,d="ABCDEF123456",c=c||"Only hexadecimal characters (0-9, A-F) allowed";break;default:l=s?"^#?[0-9A-Fa-f]+$":"^[0-9A-Fa-f]+$",r=i||1,o=a||64,d=s?"#ABCDEF or ABCDEF":"ABCDEF",c=c||"Enter hexadecimal characters only"}const h={...n,pattern:l,minLength:r,maxLength:o,placeholder:n.placeholder||d,help:n.help||c,attributes:{"data-hex-type":t,"data-allow-prefix":s,style:"text-transform: uppercase;",...n.attributes}};return this.renderInputField(h,"text")}renderInputField(t,s="text"){const{name:i,label:a,value:n="",placeholder:l="",required:r=!1,disabled:o=!1,readonly:d=!1,class:c="",attributes:h={},help:u=t.helpText||t.help||""}=t,p=`${this.options.inputClass} ${c}`.trim(),m=this.errors[i],g=this.getFieldValue(i)??n,f=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),b=this.getFieldId(i),v={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:b,name:i,type:s,fieldValue:this.escapeHtml(g),label:a?this.escapeHtml(a):null,placeholder:l?this.escapeHtml(l):null,help:u?this.escapeHtml(u):null,error:m?this.escapeHtml(m):null,required:r,disabled:o,readonly:d,attrs:f};if("password"===s&&(t.showToggle||t.strengthMeter||t.capsLockWarning)){const s={...v,showToggle:!!t.showToggle,strengthMeter:!!t.strengthMeter,capsLockWarning:!!t.capsLockWarning};return e.Mustache.render(this.templates.password,s)}if("password"===s){const s={...v,showToggle:!1!==t.showToggle,strengthMeter:!!t.strengthMeter,capsLockWarning:!!t.capsLockWarning};return e.Mustache.render(this.templates.password,s)}return e.Mustache.render(this.templates.input,v)}renderTextareaField(t){const{name:s,label:i,value:a="",placeholder:n="",required:l=!1,disabled:r=!1,readonly:o=!1,rows:d=3,cols:c,class:h="",attributes:u={},help:p=t.helpText||t.help||""}=t,m=`${this.options.inputClass} ${h}`.trim(),g=this.errors[s],f=this.getFieldValue(s)??a,b=Object.entries(u).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),v=this.getFieldId(s),y={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:v,name:s,fieldValue:f,label:i?this.escapeHtml(i):null,placeholder:n?this.escapeHtml(n):null,help:p?this.escapeHtml(p):null,error:g?this.escapeHtml(g):null,rows:d||3,required:l,disabled:r,readonly:o,attrs:b};return e.Mustache.render(this.templates.textarea,y)}renderJsonField(t){const{name:s,label:i,placeholder:a="",required:n=!1,disabled:l=!1,readonly:r=!1,rows:o=3,class:d="",attributes:c={},help:h=t.helpText||t.help||""}=t,u=this.getFieldValue(t.name)??t.value??{};let p=u;if("object"==typeof u&&null!==u)try{p=JSON.stringify(u,null,2)}catch(y){p="{}"}else"string"!=typeof u&&(p=String(u));const m=`${this.options.inputClass} ${d}`.trim(),g=this.errors[s],f=this.getFieldId(s),b=Object.entries({...c,"data-field-type":"json"}).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),v={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:s,fieldValue:p,label:i?this.escapeHtml(i):null,placeholder:a?this.escapeHtml(a):null,help:h?this.escapeHtml(h):null,error:g?this.escapeHtml(g):null,rows:o||3,required:n,disabled:l,readonly:r,attrs:b};return e.Mustache.render(this.templates.textarea,v)}renderSelectField(t){const{name:s,label:i,options:a=[],value:n="",required:l=!1,disabled:r=!1,multiple:o=!1,searchable:d=!1,class:c="",attributes:h={},help:u=t.helpText||t.help||"",start:p=t.start,end:m=t.end,step:g=t.step,format:f=t.format,prefix:b=t.prefix,suffix:v=t.suffix}=t,y=`form-select ${c}`.trim(),w=this.errors[s],F=this.getFieldValue(s)??n,D=Object.entries(h).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),$=this.getFieldId(s);let C=[...a];if(void 0!==p&&void 0!==m){const e=void 0!==g?g:1,t=this.generateSelectOptions(p,m,e,{format:f,prefix:b,suffix:v});C=[...C,...t]}let x="";Array.isArray(C)&&(x=C.map(e=>{if("string"==typeof e){const t=e===F?"selected":"";return`<option value="${this.escapeHtml(e)}" ${t}>${this.escapeHtml(e)}</option>`}if(e&&"object"==typeof e){const t=e.value===F?"selected":"";return`<option value="${this.escapeHtml(e.value)}" ${t}>${this.escapeHtml(e.label||e.text||e.value)}</option>`}return""}).join(""));const S=d?`\n <input type="text"\n class="form-control form-control-sm mb-2"\n placeholder="Search options..."\n data-filter="live-search"\n data-change-action="filter-select-options"\n data-target="${$}">\n `:"",k={labelClass:this.options.labelClass,inputClass:y,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:$,name:s,label:i?this.escapeHtml(i):null,help:u?this.escapeHtml(u):null,error:w?this.escapeHtml(w):null,searchInput:d?S:null,optionsHTML:x,required:l,disabled:r,multiple:o,attrs:D};return e.Mustache.render(this.templates.select,k)}renderCheckboxField(t){const{name:s,label:i,value:a=!1,required:n=!1,disabled:l=!1,class:r="",attributes:o={},help:d=t.helpText||t.help||""}=t,c=this.errors[s],h=this.getFieldValue(s)??a,u=!0===h||"true"===h||"1"===h,p=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(s),g={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:s,label:this.escapeHtml(i),help:d?this.escapeHtml(d):null,error:c?this.escapeHtml(c):null,value:this.escapeHtml(a),fieldClass:r,checked:u,required:n,disabled:l,attrs:p};return e.Mustache.render(this.templates.checkbox,g)}renderSwitchField(t){const{name:s,label:i,value:a=!1,required:n=!1,disabled:l=!1,size:r="md",class:o="",attributes:d={},help:c=t.helpText||t.help||""}=t,h=this.errors[s],u=this.getFieldValue(s)??a,p=!0===u||"true"===u||"1"===u,m=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),g=this.getFieldId(s),f="md"!==r?`form-switch-${r}`:"",b={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:s,label:this.escapeHtml(i),help:c?this.escapeHtml(c):null,error:h?this.escapeHtml(h):null,value:this.escapeHtml(a),sizeClass:f,fieldClass:o,checked:p,required:n,disabled:l,attrs:m};return e.Mustache.render(this.templates.switch,b)}renderRadioField(e){const{name:t,label:s,options:i=[],value:a="",disabled:n=!1,inline:l=!1,class:r="",attributes:o={},help:d=e.helpText||e.help||""}=e,c=this.errors[t],h=this.getFieldValue(t)??a,u=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" ");let p="";return Array.isArray(i)&&(p=i.map((e,s)=>{const i=`${t}_${s}`,a="string"==typeof e?e:e.value,r="string"==typeof e?e:e.label||e.text||e.value,o=a===h?"checked":"";return`\n <div class="form-check ${l?"form-check-inline":""}">\n <input\n type="radio"\n id="${i}"\n name="${t}"\n class="form-check-input ${c?"is-invalid":""}"\n value="${this.escapeHtml(a)}"\n ${o}\n ${n?"disabled":""}\n\n ${u}\n >\n <label class="form-check-label" for="${i}">\n ${this.escapeHtml(r)}\n </label>\n </div>\n `}).join("")),`\n <div class="mojo-form-control">\n ${s?`<fieldset>\n <legend class="${this.options.labelClass}">${this.escapeHtml(s)}</legend>\n <div class="${r}">\n ${p}\n </div>\n </fieldset>`:`<div class="${r}">${p}</div>`}\n ${d?`<div class="${this.options.helpClass}">${this.escapeHtml(d)}</div>`:""}\n ${c?`<div class="${this.options.errorClass}">${this.escapeHtml(c)}</div>`:""}\n </div>\n `}renderDateField(e){return this.renderInputField(e,"date")}renderDateTimeField(e){return this.renderInputField(e,"datetime-local")}renderTimeField(e){return this.renderInputField(e,"time")}renderFileField(t){const{name:s,label:i,required:a=!1,disabled:n=!1,multiple:l=!1,accept:r="*/*",class:o="",attributes:d={},help:c=t.helpText||t.help||""}=t,h=`${this.options.inputClass} ${o}`.trim(),u=this.errors[s],p=Object.entries(d).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),m=this.getFieldId(s),g={labelClass:this.options.labelClass,inputClass:h,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:s,label:i?this.escapeHtml(i):null,help:c?this.escapeHtml(c):null,error:u?this.escapeHtml(u):null,accept:r,required:a,disabled:n,multiple:l,attrs:p};return e.Mustache.render(this.templates.file,g)}renderImageField(t){const{name:s,label:i,required:a=!1,disabled:n=!1,accept:l="image/*",class:r="",attributes:o={},help:d=t.helpText||t.help||"",size:c="md",allowDrop:h=!0,placeholder:u="Drop image here or click to upload"}=t,p=`${this.options.inputClass} ${r}`.trim(),m=this.errors[s],g=this.getFieldId(s),f=`${g}_dropzone`,b=`${g}_preview`,v={xs:{width:48,height:48,containerClass:"image-field-xs"},sm:{width:96,height:96,containerClass:"image-field-sm"},md:{width:150,height:150,containerClass:"image-field-md"},lg:{width:200,height:200,containerClass:"image-field-lg"},xl:{width:300,height:300,containerClass:"image-field-xl"}},y=v[c]||v.md,w=Object.entries(o).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),F=this.getFieldValue(s),D=this.extractImageUrl(F,c),$={labelClass:this.options.labelClass,inputClass:p,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:s,label:i?this.escapeHtml(i):null,help:d?this.escapeHtml(d):null,error:m?this.escapeHtml(m):null,dropZoneId:f,previewId:b,containerClass:y.containerClass,width:y.width,height:y.height,accept:l,imageUrl:D,placeholderText:n?"No image":this.escapeHtml(u),cursor:n?"default":"pointer",allowDrop:h,showRemove:!n,required:a,disabled:n,attrs:w};return e.Mustache.render(this.templates.image,$)}extractImageUrl(e,t="md"){if(!e)return null;if("string"==typeof e)return e;if("object"==typeof e&&e.url){if(e.renditions){const s={xs:["thumbnail_sm","thumbnail","square_sm"],sm:["thumbnail","thumbnail_sm","square_sm"],md:["thumbnail_md","thumbnail","thumbnail_lg"],lg:["thumbnail_lg","thumbnail_md","thumbnail"],xl:["original","thumbnail_lg"]},i=s[t]||s.md;for(const t of i)if(e.renditions[t]&&e.renditions[t].url)return e.renditions[t].url}return e.url}return null}renderColorField(t){const{name:s,label:i,value:a="",placeholder:n="",required:l=!1,disabled:r=!1,readonly:o=!1,class:d="",attributes:c={},help:h=t.helpText||t.help||""}=t,u=`${this.options.inputClass} ${d}`.trim(),p=this.errors[s],m=this.getFieldValue(s)??a,g=Object.entries(c).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),f=this.getFieldId(s),b={labelClass:this.options.labelClass,inputClass:u,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:s,fieldValue:this.escapeHtml(m),label:i?this.escapeHtml(i):null,placeholder:n?this.escapeHtml(n):null,help:h?this.escapeHtml(h):null,error:p?this.escapeHtml(p):null,required:l,disabled:r,readonly:o,attrs:g};return e.Mustache.render(this.templates.color,b)}renderRangeField(t){const{name:s,label:i,min:a=0,max:n=100,step:l=1,value:r=a,disabled:o=!1,class:d="",attributes:c={},help:h=t.helpText||t.help||""}=t,u=`${this.options.inputClass} ${d}`.trim(),p=this.errors[s],m=this.getFieldValue(s)??r,g=Object.entries(c).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" "),f=this.getFieldId(s),b={labelClass:this.options.labelClass,inputClass:u,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:f,name:s,label:i?this.escapeHtml(i):null,help:h?this.escapeHtml(h):null,error:p?this.escapeHtml(p):null,min:a,max:n,step:l,fieldValue:m,disabled:o,attrs:g};return e.Mustache.render(this.templates.range,b)}renderHiddenField(e){const{name:t,value:s=""}=e,i=this.getFieldValue(t)??s;return`<input type="hidden" name="${t}" value="${this.escapeHtml(i)}">`}renderButton(e){const{name:t="",label:s="Button",type:i="button",action:a="",class:n="btn-secondary",disabled:l=!1,attributes:r={}}=e;let o=a;return o||("submit"===i?o="submit-form":"reset"===i&&(o="reset-form")),`\n <button\n type="button"\n ${t?`name="${t}"`:""}\n class="btn ${n}"\n ${o?`data-action="${o}"`:""}\n ${l?"disabled":""}\n ${Object.entries(r).map(([e,t])=>`${e}="${this.escapeHtml(t)}"`).join(" ")}\n >\n ${this.escapeHtml(s)}\n </button>\n `}renderDivider(e){const{label:t="",class:s=""}=e;return`\n <div class="form-divider ${s}">\n <hr>\n ${t?`<div class="form-divider-label">${this.escapeHtml(t)}</div>`:""}\n </div>\n `}renderHtmlField(e){const{html:t="",class:s=""}=e;return`\n <div class="form-html ${s}">\n ${t}\n </div>\n `}renderHeaderField(e){const{text:t="",level:s=3,class:i="",id:a=""}=e,n=Math.max(1,Math.min(6,parseInt(s)));return`<h${n}${a?` id="${this.escapeHtml(a)}"`:""}${i?` class="${this.escapeHtml(i)}"`:""}>${this.escapeHtml(t)}</h${n}>`}buildButtonsHTML(){if(!this.options.submitButton&&!this.options.resetButton&&!this.buttons.length)return"";let e="";if(this.buttons.forEach(t=>{e+=this.renderButton(t)+" "}),this.options.submitButton){let t="Submit";"string"==typeof this.options.submitButton?t=this.options.submitButton:!0===this.options.submitButton&&(t="Submit"),e+=`<button type="submit" class="btn btn-primary me-2" data-action="submit-form">${t}</button>`}if(this.options.resetButton){let t="Reset";"string"==typeof this.options.resetButton?t=this.options.resetButton:!0===this.options.resetButton&&(t="Reset"),e+=`<button type="button" class="btn btn-secondary" data-action="reset-form">${t}</button>`}return e?`\n <div class="form-actions mt-3">\n ${e}\n </div>\n `:""}getFieldValue(t){return this.structureOnly?"":e.MOJOUtils.getContextData(this.data,t)}renderTagField(e){const{name:t,label:s,value:i="",placeholder:a="Add tags...",required:n=!1,disabled:l=!1,readonly:r=!1,maxTags:o=50,allowDuplicates:d=!1,separator:c=",",help:h=e.helpText||e.help||""}=e,u=this.getFieldId(t),p=this.errors[t],m=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${u}" class="${this.options.labelClass}">${this.escapeHtml(s)}${n?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="tag-input-placeholder"\n data-field-name="${t}"\n data-field-type="tag"\n data-field-config='${JSON.stringify({name:t,value:m,placeholder:a,maxTags:o,allowDuplicates:d,separator:c,disabled:l,readonly:r,required:n})}'>\n <input type="text"\n id="${u}"\n name="${t}_display"\n class="${this.options.inputClass}${p?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n ${l?"disabled":""}\n ${r?"readonly":""}\n\n <input type="hidden" name="${t}" value="${this.escapeHtml(m)}">\n <small class="form-text text-muted">This will be enhanced with TagInput component</small>\n </div>\n ${h?`<div class="${this.options.helpClass}">${this.escapeHtml(h)}</div>`:""}\n ${p?`<div class="${this.options.errorClass}">${this.escapeHtml(p)}</div>`:""}\n </div>\n `}renderCollectionField(e){const{name:t,label:s,value:i="",placeholder:a="Search...",required:n=!1,disabled:l=!1,readonly:r=!1,Collection:o,labelField:d="name",valueField:c="id",maxItems:h=10,emptyFetch:u=!1,debounceMs:p=300,requiresActiveGroup:m=!1,help:g=e.helpText||e.help||""}=e,f=this.getFieldId(t),b=this.errors[t],v=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${f}" class="${this.options.labelClass}">${this.escapeHtml(s)}${n?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="collection-select-placeholder"\n data-field-name="${t}"\n data-field-type="collection"\n data-field-config='${JSON.stringify({name:t,value:v,placeholder:a,labelField:d,valueField:c,maxItems:h,emptyFetch:u,debounceMs:p,disabled:l,readonly:r,required:n,requiresActiveGroup:m})}'>\n <input type="text"\n id="${f}"\n name="${t}_display"\n class="${this.options.inputClass}${b?" is-invalid":""}"\n placeholder="${this.escapeHtml(a)}"\n ${l?"disabled":""}\n ${r?"readonly":""}\n\n <input type="hidden" name="${t}" value="${this.escapeHtml(v)}">\n <small class="form-text text-muted">This will be enhanced with CollectionSelect component</small>\n </div>\n ${g?`<div class="${this.options.helpClass}">${this.escapeHtml(g)}</div>`:""}\n ${b?`<div class="${this.options.errorClass}">${this.escapeHtml(b)}</div>`:""}\n </div>\n `}renderCollectionMultiSelectField(e){const{name:t,label:s,value:i=[],required:a=!1,disabled:n=!1,Collection:l,collectionParams:r={},labelField:o="name",valueField:d="id",excludeIds:c=[],size:h=8,maxHeight:u=null,showSelectAll:p=!0,requiresActiveGroup:m=!1,help:g=e.helpText||e.help||""}=e;this.getFieldId(t);const f=this.errors[t],b=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label class="${this.options.labelClass}">${this.escapeHtml(s)}${a?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="collection-multiselect-placeholder"\n data-field-name="${t}"\n data-field-type="collectionmultiselect"\n data-field-config='${JSON.stringify({name:t,value:b,labelField:o,valueField:d,excludeIds:c,size:h,maxHeight:u,showSelectAll:p,disabled:n,required:a,requiresActiveGroup:m})}'>\n <input type="hidden" name="${t}" value="${this.escapeHtml(JSON.stringify(b))}">\n <small class="form-text text-muted">This will be enhanced with CollectionMultiSelect component</small>\n </div>\n ${g?`<div class="${this.options.helpClass}">${this.escapeHtml(g)}</div>`:""}\n ${f?`<div class="${this.options.errorClass}">${this.escapeHtml(f)}</div>`:""}\n </div>\n `}renderDatePickerField(e){const{name:t,label:s,value:i="",placeholder:a="Select date...",required:n=!1,disabled:l=!1,readonly:r=!1,min:o=null,max:d=null,format:c="YYYY-MM-DD",displayFormat:h="MMM DD, YYYY",help:u=e.helpText||e.help||""}=e,p=this.getFieldId(t),m=this.errors[t],g=this.getFieldValue(t)??i;return`\n <div class="mojo-form-control">\n ${s?`<label for="${p}" class="${this.options.labelClass}">${this.escapeHtml(s)}${n?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="date-picker-placeholder"\n data-field-name="${t}"\n data-field-type="datepicker"\n data-field-config='${JSON.stringify({name:t,value:g,placeholder:a,min:o,max:d,format:c,displayFormat:h,disabled:l,readonly:r,required:n})}'>\n <input type="date"\n id="${p}"\n name="${t}"\n class="${this.options.inputClass}${m?" is-invalid":""}"\n value="${this.escapeHtml(g)}"\n placeholder="${this.escapeHtml(a)}"\n ${o?`min="${o}"`:""}\n ${d?`max="${d}"`:""}\n ${l?"disabled":""}\n ${r?"readonly":""}\n ${n?"required":""}\n\n <small class="form-text text-muted">This will be enhanced with Easepick DatePicker</small>\n </div>\n ${u?`<div class="${this.options.helpClass}">${this.escapeHtml(u)}</div>`:""}\n ${m?`<div class="${this.options.errorClass}">${this.escapeHtml(m)}</div>`:""}\n </div>\n `}renderDateRangeField(e){const{name:t,startName:s,endName:i,fieldName:a,label:n,startDate:l="",endDate:r="",placeholder:o="Select date range...",required:d=!1,disabled:c=!1,readonly:h=!1,min:u=null,max:p=null,format:m="YYYY-MM-DD",displayFormat:g="MMM DD, YYYY",outputFormat:f="date",separator:b=" - ",help:v=e.helpText||e.help||""}=e,y=this.getFieldId(t||s||"daterange"),w=this.errors[t],F=s||(t?t+"_start":""),D=i||(t?t+"_end":""),$=this.getFieldValue(F)||l,C=this.getFieldValue(D)||r;return`\n <div class="mojo-form-control">\n ${n?`<label for="${y}" class="${this.options.labelClass}">${this.escapeHtml(n)}${d?'<span class="text-danger">*</span>':""}</label>`:""}\n <div class="date-range-picker-placeholder"\n data-field-name="${t||s||"daterange"}"\n data-field-type="daterange"\n data-field-config='${JSON.stringify({name:t,startName:s,endName:i,fieldName:a,startDate:$,endDate: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}${w?" is-invalid":""}"\n value="${this.escapeHtml($)}"\n placeholder="Start date..."\n ${u?`min="${u}"`:""}\n ${p?`max="${p}"`:""}\n ${c?"disabled":""}\n ${h?"readonly":""}\n ${d?"required":""}\n\n </div>\n <div class="col-auto d-flex align-items-center">\n <span class="text-muted">${this.escapeHtml(b.trim())}</span>\n </div>\n <div class="col">\n <input type="date"\n id="${y}_end"\n name="${t}_end"\n class="${this.options.inputClass}${w?" is-invalid":""}"\n value="${this.escapeHtml(C)}"\n placeholder="End date..."\n ${u?`min="${u}"`:""}\n ${p?`max="${p}"`:""}\n ${c?"disabled":""}\n ${h?"readonly":""}\n ${d?"required":""}\n\n </div>\n </div>\n <small class="form-text text-muted">This will be enhanced with Easepick DateRangePicker</small>\n </div>\n ${v?`<div class="${this.options.helpClass}">${this.escapeHtml(v)}</div>`:""}\n ${w?`<div class="${this.options.errorClass}">${this.escapeHtml(w)}</div>`:""}\n </div>\n `}renderChecklistDropdownField(t){const s=this.getFieldId(t.name),i=this.getFieldValue(t.name)??[],a={fieldId:s,fieldName:t.name,buttonText:t.buttonText||"Select Options",buttonIcon:t.buttonIcon||"bi-chevron-down",buttonClass:t.buttonClass||"btn btn-outline-secondary btn-sm dropdown-toggle",dropdownClass:t.dropdownClass||"dropdown-menu p-2",minWidth:t.minWidth||"200px",options:t.options.map(e=>({value:e.value,label:e.label,id:`${t.name}-${e.value}`,checked:i.includes(e.value)}))};return e.Mustache.render(this.templates.checklistdropdown,a)}renderButtonGroupField(t){const s=this.getFieldId(t.name),i=this.getFieldValue(t.name)??t.value,a={fieldId:s,fieldName:t.name,size:t.size||"sm",variant:t.variant||"outline-primary",options:t.options.map(e=>({value:e.value,label:e.label,action:e.action,active:e.value===i,buttonClass:this.getButtonClass(e.value===i,t.variant)}))};return e.Mustache.render(this.templates.buttongroup,a)}getButtonClass(e,t="outline-primary"){return e?`btn btn-${t.replace("outline-","")}`:`btn btn-${t}`}generateSelectOptions(e,t,s=1,i={}){const{format:a,prefix:n="",suffix:l=""}=i,r=[],o=e<=t?Math.abs(s):-Math.abs(s);for(let d=e;e<=t?d<=t:d>=t;d+=o){let s=String(d);if("function"==typeof a)s=a(d);else if("padded"===a||"pad"===a){const i=String(Math.max(Math.abs(e),Math.abs(t))).length;s=String(d).padStart(i,"0")}else"ordinal"===a&&(s=this.formatOrdinal(d));s=`${n}${s}${l}`,r.push({value:d,label:s})}return r}formatOrdinal(e){const t=e%10,s=e%100;return 1===t&&11!==s?e+"st":2===t&&12!==s?e+"nd":3===t&&13!==s?e+"rd":e+"th"}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}}const t={enableFileDrop(e={}){if(this._fileDropConfig={acceptedTypes:e.acceptedTypes||["*/*"],maxFileSize:e.maxFileSize||10485760,dropZoneSelector:e.dropZoneSelector||null,visualFeedback:!1!==e.visualFeedback,multiple:e.multiple||!1,validateOnDrop:!1!==e.validateOnDrop,dragOverClass:e.dragOverClass||"drag-over",dragActiveClass:e.dragActiveClass||"drag-active"},this._fileDropState={isDragActive:!1,dragCounter:0},this._boundFileDropHandlers={dragEnter:this._onFileDropDragEnter.bind(this),dragOver:this._onFileDropDragOver.bind(this),dragLeave:this._onFileDropDragLeave.bind(this),drop:this._onFileDropDrop.bind(this),preventDefault:this._onFileDropPreventDefault.bind(this)},this.element)this._setupFileDropListeners();else{const e=this.onAfterRender.bind(this);this.onAfterRender=async()=>{await e(),this._setupFileDropListeners()}}const t=this.onBeforeDestroy.bind(this);this.onBeforeDestroy=async()=>{this._cleanupFileDropListeners(),await t()}},_setupFileDropListeners(){if(!this._fileDropConfig)return;const e=this._getFileDropZone();e?(this._fileDropZone=e,e.addEventListener("dragenter",this._boundFileDropHandlers.dragEnter),e.addEventListener("dragover",this._boundFileDropHandlers.dragOver),e.addEventListener("dragleave",this._boundFileDropHandlers.dragLeave),e.addEventListener("drop",this._boundFileDropHandlers.drop),["dragenter","dragover","dragleave","drop"].forEach(e=>{document.addEventListener(e,this._boundFileDropHandlers.preventDefault)})):console.warn("FileDropMixin: Drop zone not found")},_cleanupFileDropListeners(){this._boundFileDropHandlers&&(this._fileDropZone&&(this._fileDropZone.removeEventListener("dragenter",this._boundFileDropHandlers.dragEnter),this._fileDropZone.removeEventListener("dragover",this._boundFileDropHandlers.dragOver),this._fileDropZone.removeEventListener("dragleave",this._boundFileDropHandlers.dragLeave),this._fileDropZone.removeEventListener("drop",this._boundFileDropHandlers.drop)),["dragenter","dragover","dragleave","drop"].forEach(e=>{document.removeEventListener(e,this._boundFileDropHandlers.preventDefault)}),this._fileDropZone=null,this._boundFileDropHandlers=null,this._fileDropConfig=null,this._fileDropState=null)},_getFileDropZone(){return this._fileDropConfig.dropZoneSelector?this.element.querySelector(this._fileDropConfig.dropZoneSelector):this.element},_onFileDropPreventDefault(e){e.preventDefault(),e.stopPropagation()},_onFileDropDragEnter(e){this._onFileDropPreventDefault(e),this._fileDropState.dragCounter++,this._fileDropState.isDragActive||(this._fileDropState.isDragActive=!0,this._applyFileDropVisualFeedback(!0))},_onFileDropDragOver(e){this._onFileDropPreventDefault(e),e.dataTransfer.dropEffect="copy"},_onFileDropDragLeave(e){this._onFileDropPreventDefault(e),this._fileDropState.dragCounter--,this._fileDropState.dragCounter<=0&&(this._fileDropState.isDragActive=!1,this._fileDropState.dragCounter=0,this._applyFileDropVisualFeedback(!1))},async _onFileDropDrop(e){this._onFileDropPreventDefault(e),this._fileDropState.isDragActive=!1,this._fileDropState.dragCounter=0,this._applyFileDropVisualFeedback(!1);const t=Array.from(e.dataTransfer.files);if(0===t.length)return;const s=this._fileDropConfig.multiple?t:[t[0]];let i={valid:!0,errors:[]};if(!this._fileDropConfig.validateOnDrop||(i=this._validateFileDropFiles(s),i.valid))if("function"==typeof this.onFileDrop)try{await this.onFileDrop(s,e,i)}catch(a){"function"==typeof this.onFileDropError?await this.onFileDropError(a,e,s):console.error("FileDropMixin: Error in onFileDrop callback:",a)}else console.warn("FileDropMixin: No onFileDrop method found on view");else"function"==typeof this.onFileDropError&&await this.onFileDropError(new Error(i.errors.join(", ")),e,s)},_applyFileDropVisualFeedback(e){if(!this._fileDropConfig.visualFeedback||!this._fileDropZone)return;const{dragOverClass:t,dragActiveClass:s}=this._fileDropConfig;e?this._fileDropZone.classList.add(t,s):this._fileDropZone.classList.remove(t,s)},_validateFileDropFiles(e){const t=[],s=this._fileDropConfig;for(const i of e)this._isFileDropTypeAccepted(i.type)?i.size>s.maxFileSize&&t.push(`File "${i.name}" (${this._formatFileDropSize(i.size)}) exceeds maximum size (${this._formatFileDropSize(s.maxFileSize)})`):t.push(`File type "${i.type}" is not accepted for file "${i.name}"`);return{valid:0===t.length,errors:t}},_isFileDropTypeAccepted(e){const{acceptedTypes:t}=this._fileDropConfig;return!!t.includes("*/*")||t.some(t=>{if(t===e)return!0;if(t.endsWith("/*")){const s=t.split("/")[0];return e.startsWith(s+"/")}return!1})},_formatFileDropSize(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,t)).toFixed(2))+" "+["Bytes","KB","MB","GB"][t]}};function s(e){Object.assign(e.prototype,t)}class TagInputView extends e.View{constructor(e={}){const{name:t,value:s="",placeholder:i="Add tags...",maxTags:a=50,allowDuplicates:n=!1,separator:l=",",trimTags:r=!0,minLength:o=1,maxLength:d=50,disabled:c=!1,readonly:h=!1,class:u="",tagClass:p="badge bg-primary",inputClass:m="form-control",...g}=e;super({tagName:"div",className:`tag-input-view ${u}`,...g}),this.name=t,this.placeholder=i,this.maxTags=a,this.allowDuplicates=n,this.separator=l,this.trimTags=r,this.minLength=o,this.maxLength=d,this.disabled=c,this.readonly=h,this.tagClass=p,this.inputClass=m,this.tags=[],this.focusedTagIndex=-1,s&&(this.tags=this.parseTagString(s))}async renderTemplate(){const e=this.renderTags(),t=this.renderHiddenInput();return`\n <div class="tag-input-container">\n <div class="tag-input-wrapper border rounded p-2"\n data-action="focus-input"\n tabindex="0"\n role="combobox"\n aria-expanded="false"\n aria-label="Tag input">\n <div class="tags-container d-flex flex-wrap gap-1 mb-2">\n ${e}\n </div>\n ${this.renderInput()}\n </div>\n ${t}\n <div class="tag-input-feedback small text-muted mt-1">\n <span class="tag-count">${this.tags.length}</span>/${this.maxTags} tags\n </div>\n </div>\n `}renderTags(){return this.tags.map((e,t)=>`\n <span class="${this.tagClass} tag-item"\n data-tag-index="${t}"\n tabindex="0"\n role="button"\n aria-label="Tag: ${this.escapeHtml(e)}. Press Delete or Backspace to remove.">\n <span class="tag-text">${this.escapeHtml(e)}</span>\n ${this.readonly||this.disabled?"":`\n <i class="bi bi-x tag-remove ms-1"\n data-action="remove-tag"\n data-tag-index="${t}"\n aria-label="Remove tag"></i>\n `}\n </span>\n `).join("")}renderInput(){return this.readonly?"":`\n <input type="text"\n class="${this.inputClass} tag-input-field border-0 p-0"\n placeholder="${this.escapeHtml(this.placeholder)}"\n ${this.disabled?"disabled":""}\n data-change-action="input-change"\n style="outline: none; box-shadow: none; min-width: 120px;"\n autocomplete="off">\n `}renderHiddenInput(){return this.name?`\n <input type="hidden"\n name="${this.name}"\n value="${this.escapeHtml(this.getTagString())}"\n class="tag-input-hidden">\n `:""}async onAfterRender(){await super.onAfterRender(),this.updateTagCount()}async onActionFocusInput(e,t){this.focus()}focus(){const e=this.element.querySelector(".tag-input-field");e&&!this.disabled&&e.focus(),this.focusedTagIndex=-1}async onActionRemoveTag(e,t){e.stopPropagation();const s=parseInt(t.getAttribute("data-tag-index"));s>=0&&s<this.tags.length&&await this.removeTag(s)}async onChangeInputChange(e,t){const s=t.value,i=s.slice(-1);if(i===this.separator||"\n"===i){e.preventDefault();const i=s.slice(0,-1);return void(i.trim()&&(await this.addTag(i),t.value=""))}}bindEvents(){this.__bnd_keydown||(this.__bnd_keydown=this.handleInputKeydown.bind(this)),this.element.addEventListener("keydown",this.__bnd_keydown),this.events.bind(this.element)}unbindEvents(){this.__bnd_keydown&&this.element.removeEventListener("keydown",this.__bnd_keydown),this.events.unbind()}handleInputKeydown(e){const t=e.target,s=t.value||"";switch(e.key){case"Enter":case"Tab":case",":s.trim()&&(e.preventDefault(),this.addTag(s),t.value="");break;case"Backspace":""===s&&this.tags.length>0&&(e.preventDefault(),this.focusedTagIndex>=0?(this.removeTag(this.focusedTagIndex),0==this.focusedTagIndex?this.focus():this.focusTag(this.focusedTagIndex-1)):this.removeTag(this.tags.length-1));break;case"ArrowLeft":if(""===s&&this.tags.length>0)if(e.preventDefault(),this.focusedTagIndex>=0){const e=this.focusedTagIndex-1;e>=0?this.focusTag(e):this.focus()}else this.focusTag(this.tags.length-1);break;case"ArrowRight":if(""===s&&this.tags.length>0)if(e.preventDefault(),this.focusedTagIndex>=0){const e=this.focusedTagIndex+1;e<this.tags.length?this.focusTag(e):this.focus()}else this.focusTag(0);break;case"Escape":t.value="",t.blur()}}async addTag(e){if(this.readonly||this.disabled)return!1;const t=this.trimTags?e.trim():e;return!(!this.isValidTag(t)||(!this.allowDuplicates&&this.tags.includes(t)?(this.showTagError(`Tag "${t}" already exists`),1):this.tags.length>=this.maxTags?(this.showTagError(`Maximum ${this.maxTags} tags allowed`),1):(this.tags.push(t),await this.updateDisplay(),this.emit("tag:added",{tag:t,tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags}),0)))}async removeTag(e){if(this.readonly||this.disabled)return!1;if(e>=0&&e<this.tags.length){const t=this.tags[e];return this.tags.splice(e,1),await this.updateDisplay(),this.emit("tag:removed",{tag:t,tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags}),!0}return!1}async removeTagByValue(e){const t=this.tags.indexOf(e);return t>=0&&await this.removeTag(t)}async clearTags(){if(this.readonly||this.disabled)return!1;const e=[...this.tags];return this.tags=[],await this.updateDisplay(),this.emit("tags:cleared",{oldTags:e}),this.emit("change",{value:"",tags:[]}),!0}async setTags(e){let t=[];Array.isArray(e)?t=e:"string"==typeof e&&(t=this.parseTagString(e)),t=t.filter(e=>this.isValidTag(e)).slice(0,this.maxTags),this.allowDuplicates||(t=[...new Set(t)]),this.tags=t,await this.updateDisplay(),this.emit("tags:set",{tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags})}isValidTag(e){return!("string"!=typeof e||e.length<this.minLength||e.length>this.maxLength||""===e.trim())}parseTagString(e){return e?e.split(this.separator).map(e=>this.trimTags?e.trim():e).filter(e=>e.length>0):[]}getTagString(){return this.tags.join(this.separator)}getTags(){return[...this.tags]}focusTag(e){const t=this.element.querySelectorAll(".tag-item");t[e]&&(this.focusedTagIndex=e,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 s=this.element.querySelector(".tag-input-wrapper");s&&s.classList.toggle("disabled",this.disabled)}setReadonly(e){this.readonly=e;const t=this.element.querySelector(".tag-input-field");t&&(t.style.display=e?"none":""),this.element.querySelectorAll(".tag-remove").forEach(t=>{t.style.display=e?"none":""})}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}getFormValue(){return this.getTagString()}async setFormValue(e){await this.setTags(e)}static create(e={}){return new TagInputView(e)}}class CollectionDropdownView extends e.View{constructor(e={}){super({tagName:"div",className:"collection-dropdown-view dropdown-menu show w-100 position-absolute",style:"max-height: 250px; overflow-y: auto; z-index: 1000;",template:'\n {{#data.loading}}\n <div class="dropdown-item text-center">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <button type="button"\n class="dropdown-item {{#isSelected}}active{{/isSelected}} {{#isFocused}}bg-light{{/isFocused}}"\n data-action="select-item"\n data-value="{{valueField}}"\n data-label="{{labelField}}"\n data-index="{{index}}">\n {{labelField}}\n </button>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class="dropdown-item text-muted">\n {{#data.hasSearched}}No results found{{/data.hasSearched}}\n {{^data.hasSearched}}Start typing to search...{{/data.hasSearched}}\n </div>\n {{/data.showNoResults}}\n {{/data.loading}}\n ',...e}),this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.selectedValue=e.selectedValue||"",this.loading=e.loading||!1,this.hasSearched=e.hasSearched||!1,this.focusedIndex=e.focusedIndex||-1}async getViewData(){const t=this.collection?this.collection.toJSON().map((t,s)=>{const i=e.MOJOUtils.getNestedValue(t,this.labelField),a=e.MOJOUtils.getNestedValue(t,this.valueField);return{...t,labelField:i,valueField:a,isSelected:a==this.selectedValue,isFocused:s===this.focusedIndex,index:s}}):[];return{loading:this.loading,hasSearched:this.hasSearched,showNoResults:!this.loading&&this.hasSearched&&0===t.length,items:t}}async handleActionSelectItem(e,t){e.preventDefault();const s=t.getAttribute("data-value"),i=t.getAttribute("data-label");this.emit("item-selected",{value:s,label:i})}updateState(e){Object.assign(this,e)}updateFocusedItem(e){this.focusedIndex=e;const t=this.element?.querySelectorAll('.dropdown-item[data-action="select-item"]');t?.forEach((e,t)=>{e.classList.toggle("bg-light",t===this.focusedIndex)})}getItemCount(){return this.collection?this.collection.length():0}getFocusedItem(){return this.focusedIndex>=0&&this.collection&&this.collection.toJSON()[this.focusedIndex]||null}}class CollectionSelectView extends e.View{constructor(t={}){super({className:"collection-select-view",template:'\n <div class="position-relative">\n <input type="text"\n class="form-control {{#data.hasError}}is-invalid{{/data.hasError}} {{#data.showClear}}pe-5{{/data.showClear}}"\n placeholder="{{data.placeholder}}"\n value="{{data.displayValue}}"\n autocomplete="off" />\n\n <input type="hidden"\n name="{{data.name}}"\n value="{{data.selectedValue}}" />\n\n {{#data.showClear}}\n <button type="button"\n class="btn btn-link position-absolute top-50 end-0 translate-middle-y me-2 p-0 border-0"\n style="z-index: 10; color: #6c757d;"\n data-action="clear-selection"\n title="Clear selection">\n <i class="bi bi-x-circle"></i>\n </button>\n {{/data.showClear}}\n\n <div class="dropdown-container"></div>\n\n {{#data.hasError}}\n <div class="invalid-feedback">{{data.errorMessage}}</div>\n {{/data.hasError}}\n </div>\n ',...t}),this.collection=t.collection,this.labelField=t.labelField||"name",this.valueField=t.valueField||"id",this.maxItems=t.maxItems||10,this.placeholder=t.placeholder||"Search...",this.debounceMs=t.debounceMs||1e3,this.name=t.name||"collection_select",this.emptyFetch=!1!==t.emptyFetch,this.requiresActiveGroup=t.requiresActiveGroup||!1,this.selectedValue=t.value||"0",this.selectedLabel="",this.searchValue="",this.showDropdown=!1,this.loading=!1,this.hasSearched=!1,this.focusedIndex=-1,this.hasError=!1,this.errorMessage="",this.selectedValue&&"object"==typeof this.selectedValue&&(this.selectedLabel=e.MOJOUtils.getNestedValue(this.selectedValue,this.labelField)||"",this.selectedValue=e.MOJOUtils.getNestedValue(this.selectedValue,this.valueField)||"0"),this.searchTimer=null,this.dropdownView=null,this.defaultParams={},this.handleDocumentClick=this.handleDocumentClick.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleInputEvents=this.handleInputEvents.bind(this)}onInit(){this.collection&&this.setupCollection()}setupCollection(){if(this.defaultParams={...this.collection.params},this.collection.params.size=this.maxItems,this.defaultParams.size=this.maxItems,this.requiresActiveGroup){const e=this.getApp();e&&e.activeGroup&&e.activeGroup.id&&(this.collection.params.group=e.activeGroup.id,this.defaultParams.group=e.activeGroup.id)}this.collection.on("fetch:start",()=>{this.loading=!0,this.showDropdown=!0,this.updateDropdown()}),this.collection.on("fetch:end",()=>{this.loading=!1,this.showDropdown=!0,this.updateDropdown()}),this.selectedValue&&this.loadSelectedItem(),this.emptyFetch&&this.collection.isEmpty()&&this.performInitialFetch()}async performInitialFetch(){if(this.collection)try{const e={...this.defaultParams};delete e.search,await this.collection.updateParams(e,!0)}catch(e){console.error("Initial fetch error:",e)}}async loadSelectedItem(){try{if(!this.selectedValue||"0"==this.selectedValue)return;if(this.selectedLabel)return;const e=this.collection?.get(this.selectedValue);if(e)return this.selectedLabel=this.getFieldValue(e,this.labelField),void this.render(!1);let t=await this.collection.fetchOne(this.selectedValue);t&&(this.selectedLabel=this.getFieldValue(t,this.labelField)||`${t.constructor.name} #${t.id}`,this.render(!1))}catch(e){console.error("Error loading selected item:",e)}}async getViewData(){let e="";return this.showDropdown&&this.hasSearched?e=this.searchValue:this.selectedValue&&this.selectedLabel&&(e=this.selectedLabel),{name:this.name,placeholder:this.placeholder,displayValue:e,selectedValue:this.selectedValue,showClear:!(!this.selectedValue||"0"===this.selectedValue||!this.selectedLabel),hasError:this.hasError,errorMessage:this.errorMessage}}async onAfterRender(){await super.onAfterRender();const e=this.getInput();e&&(e.addEventListener("input",this.handleInputEvents),e.addEventListener("focus",this.handleInputEvents),e.addEventListener("keydown",this.handleKeyDown)),document.addEventListener("click",this.handleDocumentClick),this.createDropdownView()}async onBeforeDestroy(){await super.onBeforeDestroy();const e=this.getInput();e&&(e.removeEventListener("input",this.handleInputEvents),e.removeEventListener("focus",this.handleInputEvents),e.removeEventListener("keydown",this.handleKeyDown)),document.removeEventListener("click",this.handleDocumentClick),this.searchTimer&&clearTimeout(this.searchTimer),this.dropdownView&&this.dropdownView.destroy()}createDropdownView(){this.dropdownView&&this.dropdownView.destroy(),this.dropdownView=new CollectionDropdownView({collection:this.collection,labelField:this.labelField,valueField:this.valueField,selectedValue:this.selectedValue,loading:this.loading,hasSearched:this.hasSearched,focusedIndex:this.focusedIndex}),this.dropdownView.on("item-selected",e=>{this.selectItem(e.value,e.label)})}async handleInputEvents(e){const t=e.target;"focus"===e.type?(this.showDropdown=!0,!this.hasSearched&&this.emptyFetch&&this.collection?.isEmpty()&&this.performInitialFetch(),this.updateDropdown()):"input"===e.type&&(this.searchValue=t.value,this.showDropdown=!0,this.hasSearched=!0,this.focusedIndex=-1,this.searchValue!==this.selectedLabel&&(this.selectedValue="0",this.selectedLabel="",this.emit("change",{value:"0",label:""})),this.searchTimer&&clearTimeout(this.searchTimer),this.searchTimer=setTimeout(()=>{this.performSearch()},this.debounceMs),this.updateDropdown())}async handleActionClearSelection(e,t){e.preventDefault(),e.stopPropagation(),this.clearSelection()}clearSelection(){this.selectedValue="0",this.selectedLabel="",this.searchValue="",this.showDropdown=!1,this.hasError=!1,this.focusedIndex=-1,this.hasSearched=!1;const e=this.getInput();e&&(e.value="",e.focus());const t=this.getHiddenInput();t&&(t.value="0"),this.updateDropdown(),this.render(),this.emit("change",{value:"0",label:""})}async performSearch(){if(this.collection)try{const e={...this.defaultParams};this.searchValue&&this.searchValue.trim()&&(e.search=this.searchValue.trim()),await this.collection.updateParams(e,!0)}catch(e){console.error("Search error:",e),this.loading=!1,this.updateDropdown()}}updateDropdown(){if(this.dropdownView)if(this.dropdownView.updateState({selectedValue:this.selectedValue,loading:this.loading,hasSearched:this.hasSearched,focusedIndex:this.focusedIndex}),this.showDropdown)if(this.dropdownView.isMounted())this.dropdownView.render();else{const e=this.element?.querySelector(".dropdown-container");e&&this.dropdownView.render(!0,e)}else this.dropdownView.isMounted()&&(this.dropdownView.destroy(),this.createDropdownView())}selectItem(e,t){this.selectedValue=e,this.selectedLabel=t,this.searchValue="",this.showDropdown=!1,this.hasError=!1,this.focusedIndex=-1,this.hasSearched=!1;const s=this.getInput();s&&(s.value=t);const i=this.getHiddenInput();i&&(i.value=e),this.updateDropdown(),this.emit("change",{value:e,label:t})}handleDocumentClick(e){this.element?.contains(e.target)||(this.showDropdown=!1,this.focusedIndex=-1,this.updateDropdown())}handleKeyDown(e){if(!this.showDropdown||!this.collection)return;const t=this.dropdownView?.getItemCount()||0;switch(e.key){case"ArrowDown":e.preventDefault(),this.focusedIndex=Math.min(this.focusedIndex+1,t-1),this.dropdownView?.updateFocusedItem(this.focusedIndex);break;case"ArrowUp":e.preventDefault(),this.focusedIndex=Math.max(this.focusedIndex-1,0),this.dropdownView?.updateFocusedItem(this.focusedIndex);break;case"Enter":{e.preventDefault();const t=this.dropdownView?.getFocusedItem();t&&this.selectItem(t[this.valueField],t[this.labelField]);break}case"Escape":e.preventDefault(),this.showDropdown=!1,this.focusedIndex=-1,this.updateDropdown()}}getInput(){return this.element?.querySelector('input[type="text"]')}getHiddenInput(){return this.element?.querySelector('input[type="hidden"]')}setValue(e,t=""){this.selectedValue=e,this.selectedLabel=t,this.searchValue="",this.hasError=!1,this.hasSearched=!1;const s=this.getInput();s&&(s.value=t);const i=this.getHiddenInput();i&&(i.value=e)}getValue(){return 0===this.selectedValue||"0"===this.selectedValue?null:this.selectedValue}getLabel(){return this.selectedLabel}setError(e){this.hasError=!0,this.errorMessage=e,this.render()}clearError(){this.hasError=!1,this.errorMessage="",this.render()}focus(){const e=this.getInput();e&&e.focus()}getFormValue(){return 0===this.selectedValue||"0"===this.selectedValue?null:this.selectedValue}setFormValue(t){let s=t,i="";s&&"object"==typeof s&&(i=e.MOJOUtils.getNestedValue(s,this.labelField)||"",s=e.MOJOUtils.getNestedValue(s,this.valueField)),s=s||"0",s!=this.selectedValue&&(this.selectedValue=s,this.selectedLabel=i,this.searchValue="",this.hasSearched=!1,this.showDropdown=!1,this.hasError=!1,this.selectedValue&&"0"!==this.selectedValue?(this.selectedLabel||(this.selectedLabel=`${this.collection.getModelName()} #${this.selectedValue}`),this.loadSelectedItem()):this.render())}getFieldValue(t,s){if(t&&s){if("function"==typeof t.get){const i=t.get(s);return void 0===i&&s.includes(".")?e.MOJOUtils.getNestedValue(t,s):i}return e.MOJOUtils.getNestedValue(t,s)}}}class CollectionMultiSelectView extends e.View{constructor(e={}){super({tagName:"div",className:"collection-multiselect-view",template:'\n <div class="mojo-form-control">\n {{#label}}\n <label class="form-label">\n {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}\n </label>\n {{/label}}\n\n {{#loading}}\n <div class="text-center py-3">\n <div class="spinner-border spinner-border-sm" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/loading}}\n\n {{^loading}}\n {{#items.length}}\n <div class="collection-multiselect-list border rounded p-3" style="max-height: {{maxHeight}}px; overflow-y: auto; background: #fff;">\n {{#items}}\n <div class="d-flex align-items-center mb-2 py-1 px-2 rounded {{^disabled}}hover-bg{{/disabled}}" \n style="cursor: {{^disabled}}pointer{{/disabled}}{{#disabled}}not-allowed{{/disabled}}; user-select: none; transition: background-color 0.15s;"\n data-action="{{^disabled}}toggle-item{{/disabled}}"\n data-value="{{value}}"\n data-index="{{index}}"\n {{#disabled}}data-disabled="true"{{/disabled}}>\n <i class="bi {{#isSelected}}bi-check-square-fill text-primary{{/isSelected}}{{^isSelected}}bi-square{{/isSelected}} me-2" \n style="font-size: 1.25rem;"></i>\n <span {{#disabled}}class="text-muted"{{/disabled}}>{{label}}</span>\n </div>\n {{/items}}\n </div>\n\n {{#showSelectAll}}\n <div class="mt-2">\n <button type="button" class="btn btn-sm btn-outline-secondary me-2" data-action="select-all">\n Select All\n </button>\n <button type="button" class="btn btn-sm btn-outline-secondary" data-action="deselect-all">\n Deselect All\n </button>\n </div>\n {{/showSelectAll}}\n {{/items.length}}\n\n {{^items.length}}\n <div class="text-muted text-center py-3 border rounded">\n No items available\n </div>\n {{/^items.length}}\n {{/loading}}\n\n {{#help}}\n <div class="form-text">{{help}}</div>\n {{/help}}\n {{#error}}\n <div class="invalid-feedback d-block">{{error}}</div>\n {{/error}}\n </div>\n ',...e}),this.name=e.name||"collection_multiselect",this.label=e.label||"",this.help=e.help||"",this.error=e.error||"",this.required=e.required||!1,this.disabled=e.disabled||!1,this.collection=e.collection,this.collectionParams=e.collectionParams||{},this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.excludeIds=e.excludeIds||[],this.requiresActiveGroup=e.requiresActiveGroup||!1,this.size=e.size||8,this.maxHeight=e.maxHeight||42*this.size,this.showSelectAll=!1!==e.showSelectAll,this.selectedValues=Array.isArray(e.value)?e.value:[],this.loading=!1,this.items=[],this.lastClickedIndex=-1,this.fieldId=e.fieldId||`field_${this.name}`}onInit(){this.collection&&this.setupCollection()}setupCollection(){if(this.collection){if(this.collectionParams&&Object.keys(this.collectionParams).length>0&&(this.collection.params={...this.collection.params,...this.collectionParams}),this.requiresActiveGroup){const e=this.getApp();e&&e.activeGroup&&e.activeGroup.id&&(this.collection.params.group=e.activeGroup.id)}this.collection.on("fetch:start",()=>{this.loading=!0,this.render(!1)}),this.collection.on("fetch:end",()=>{this.loading=!1,this.updateItems(),this.render(!1)}),this.collection.isEmpty()||this.updateItems()}else console.warn("CollectionMultiSelect: No collection provided")}onAfterMount(){this.collection&&this.collection.isEmpty()&&this.collection.fetch()}updateItems(){const e=this.collection.models.filter(e=>{const t=this.getFieldValue(e,this.valueField);return!this.excludeIds.some(e=>e==t)});this.items=e.map((e,t)=>{const s=this.getFieldValue(e,this.labelField),i=this.getFieldValue(e,this.valueField);return{label:s,value:i,index:t,isSelected:this.selectedValues.some(e=>e==i),disabled:this.disabled}})}getFieldValue(t,s){if(t&&s){if("function"==typeof t.get){const i=t.get(s);return void 0===i&&s.includes(".")?e.MOJOUtils.getNestedValue(t,s):i}return e.MOJOUtils.getNestedValue(t,s)}}handleActionToggleItem(e,t){const s=t.getAttribute("data-value"),i=parseInt(t.getAttribute("data-index"),10),a=Number(s),n=isNaN(a)||String(a)!==s?s:a;if(e.shiftKey&&-1!==this.lastClickedIndex&&this.lastClickedIndex!==i){const e=!this.selectedValues.some(e=>e==n),t=Math.min(this.lastClickedIndex,i),s=Math.max(this.lastClickedIndex,i);for(let i=t;i<=s;i++){const t=this.items[i];if(t&&!t.disabled){const s=Number(t.value),i=isNaN(s)||String(s)!==String(t.value)?t.value:s;e?(this.selectedValues.some(e=>e==i)||this.selectedValues.push(i),t.isSelected=!0):(this.selectedValues=this.selectedValues.filter(e=>e!=i),t.isSelected=!1)}}}else{const e=this.selectedValues.some(e=>e==n);e?this.selectedValues=this.selectedValues.filter(e=>e!=n):this.selectedValues.push(n);const t=this.items.find(e=>e.value==s);t&&(t.isSelected=!e)}this.lastClickedIndex=i,this.render(!1),this.emit("change",{value:this.selectedValues,name:this.name})}async handleActionSelectAll(e,t){e.preventDefault(),this.selectedValues=this.items.filter(e=>!e.disabled).map(e=>e.value),this.items.forEach(e=>{e.disabled||(e.isSelected=!0)}),this.render(!1),this.emit("change",{value:this.selectedValues,name:this.name})}async handleActionDeselectAll(e,t){e.preventDefault(),this.selectedValues=[],this.items.forEach(e=>{e.isSelected=!1}),this.render(!1),this.emit("change",{value:this.selectedValues,name:this.name})}getValue(){return this.selectedValues}setValue(e){this.selectedValues=Array.isArray(e)?e:[],this.updateItems(),this.render()}setExcludeIds(e){this.excludeIds=Array.isArray(e)?e:[],this.updateItems(),this.render()}async refresh(){await this.collection.fetch()}getFormValue(){return this.selectedValues}setFormValue(e){this.setValue(e)}}class DatePicker extends e.View{constructor(e={}){const{name:t,value:s="",format:i="YYYY-MM-DD",displayFormat:a="MMM DD, YYYY",min:n=null,max:l=null,placeholder:r="Select date...",disabled:o=!1,readonly:d=!1,required:c=!1,class:h="",inputClass:u="form-control",autoApply:p=!0,inline:m=!1,...g}=e;super({tagName:"div",className:`date-picker-view ${h}`,...g}),this.name=t,this.format=i,this.displayFormat=a,this.min=n,this.max=l,this.placeholder=r,this.disabled=o,this.readonly=d,this.required=c,this.inputClass=u,this.autoApply=p,this.inline=m,this.currentValue=s,this.picker=null,this.useNative=!1,this.easepickLoaded=!1,this.checkEasepickAvailability()}async checkEasepickAvailability(){if("undefined"!=typeof window&&window.easepick)return this.easepickLoaded=!0,!0;try{return await this.loadEasepick(),this.easepickLoaded=!0,!0}catch(e){return console.warn("Easepick failed to load, falling back to native date input:",e),this.useNative=!0,!1}}async loadEasepick(){return new Promise((e,t)=>{if(window.easepick)return void e();const s=document.createElement("link");s.rel="stylesheet",s.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(s);const i=document.createElement("script");i.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",i.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},i.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(i)})}async renderTemplate(){const e=this.getInputId(),t=this.useNative?"date":"text",s=this.formatValueForInput(this.currentValue);return`\n <div class="date-picker-container">\n <input \n type="${t}"\n id="${e}"\n name="${this.name||""}"\n class="${this.inputClass}${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(s)}"\n placeholder="${this.escapeHtml(this.placeholder)}"\n ${this.min?`min="${this.min}"`:""}\n ${this.max?`max="${this.max}"`:""}\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n autocomplete="off"\n data-change-action="date-changed"\n />\n <div class="date-picker-feedback"></div>\n </div>\n `}async onAfterRender(){await super.onAfterRender(),this.easepickLoaded&&!this.useNative?await this.initializeEasepick():this.initializeNativeFallback()}async initializeEasepick(){const e=this.getInputElement();if(e&&window.easepick)try{const t={element:e,css:["https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css"],format:this.displayFormat,lang:"en-US",autoApply:this.autoApply,inline:this.inline,readonly:this.readonly,zIndex:9999};this.min&&(t.minDate=new Date(this.min)),this.max&&(t.maxDate=new Date(this.max)),t.setup=e=>{e.on("select",e=>{const t=e.detail.date;this.handleDateChange(t?this.formatDate(t,this.format):"")}),e.on("clear",()=>{this.handleDateChange("")}),e.on("show",()=>{this.emit("picker:show")}),e.on("hide",()=>{this.emit("picker:hide")})},this.picker=new window.easepick.create(t),this.currentValue&&this.picker.setDate(this.currentValue)}catch(t){console.error("Failed to initialize Easepick:",t),this.useNative=!0,this.initializeNativeFallback()}}initializeNativeFallback(){const e=this.getInputElement();e&&(e.type="date",this.currentValue&&(e.value=this.formatDate(this.currentValue,"YYYY-MM-DD")))}async onChangeDateChanged(e,t,s){const i=s.value;this.handleDateChange(i)}handleDateChange(e){const t=this.currentValue;this.currentValue=e,this.updateHiddenInput(),t!==e&&(this.emit("change",{value:e,formatted:this.formatValueForDisplay(e),oldValue:t}),this.emit("date:changed",{value:e,oldValue:t}))}formatDate(e,t=this.format){if(!e)return"";const s=new Date(e);if(isNaN(s.getTime()))return"";const i=s.getFullYear(),a=String(s.getMonth()+1).padStart(2,"0"),n=String(s.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":default:return`${i}-${a}-${n}`;case"MM/DD/YYYY":return`${a}/${n}/${i}`;case"DD/MM/YYYY":return`${n}/${a}/${i}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][s.getMonth()]} ${n}, ${i}`}}formatValueForInput(e){return e?this.useNative?this.formatDate(e,"YYYY-MM-DD"):e:""}formatValueForDisplay(e){return e?this.formatDate(e,this.displayFormat):""}getInputId(){return this.name?`datepicker_${this.name}_${Date.now()}`:`datepicker_${Date.now()}`}getInputElement(){return this.element?.querySelector("input")}updateHiddenInput(){}hasError(){return!1}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}setValue(e){if(this.currentValue=e,this.picker&&this.easepickLoaded)this.picker.setDate(e||null);else{const t=this.getInputElement();t&&(t.value=this.formatValueForInput(e))}this.emit("value:set",{value:e})}getValue(){return this.currentValue}getFormattedValue(e=this.displayFormat){return this.formatDate(this.currentValue,e)}clear(){this.setValue("")}setMin(e){if(this.min=e,this.picker&&this.easepickLoaded)this.picker.options.minDate=new Date(e);else{const t=this.getInputElement();t&&(t.min=e)}}setMax(e){if(this.max=e,this.picker&&this.easepickLoaded)this.picker.options.maxDate=new Date(e);else{const t=this.getInputElement();t&&(t.max=e)}}setEnabled(e){this.disabled=!e;const t=this.getInputElement();t&&(t.disabled=this.disabled),this.picker&&this.easepickLoaded&&this.disabled&&this.picker.hide()}setReadonly(e){this.readonly=e;const t=this.getInputElement();t&&(t.readonly=e)}focus(){const e=this.getInputElement();e&&e.focus()}show(){this.picker&&this.easepickLoaded&&this.picker.show()}hide(){this.picker&&this.easepickLoaded&&this.picker.hide()}getFormValue(){return this.getValue()}async setFormValue(e){this.setValue(e)}async onBeforeDestroy(){if(this.picker&&this.easepickLoaded)try{this.picker.destroy()}catch(e){console.warn("Error destroying Easepick instance:",e)}this.picker=null,await super.onBeforeDestroy()}static create(e={}){return new DatePicker(e)}}class DateRangePicker extends e.View{constructor(e={}){const{name:t,startName:s,endName:i,fieldName:a,startDate:n="",endDate:l="",format:r="YYYY-MM-DD",displayFormat:o="MMM DD, YYYY",outputFormat:d="date",min:c=null,max:h=null,placeholder:u="Select date range...",startPlaceholder:p="Start date...",endPlaceholder:m="End date...",disabled:g=!1,readonly:f=!1,required:b=!1,class:v="",inputClass:y="form-control",autoApply:w=!0,inline:F=!1,separator:D=" - ",...$}=e;super({tagName:"div",className:`date-range-picker-view ${v}`,...$}),this.name=t,this.startName=s,this.endName=i,this.fieldName=a,this.format=r,this.displayFormat=o,this.outputFormat=d,this.min=c,this.max=h,this.placeholder=u,this.startPlaceholder=p,this.endPlaceholder=m,this.disabled=g,this.readonly=f,this.required=b,this.inputClass=y,this.autoApply=w,this.inline=F,this.separator=D,this.currentStartDate=n,this.currentEndDate=l,this.picker=null,this.useNative=!1,this.easepickLoaded=!1,this.checkEasepickAvailability()}async checkEasepickAvailability(){if("undefined"!=typeof window&&window.easepick)return this.easepickLoaded=!0,!0;try{return await this.loadEasepick(),this.easepickLoaded=!0,!0}catch(e){return console.warn("Easepick failed to load, falling back to native date inputs:",e),this.useNative=!0,!1}}async loadEasepick(){return new Promise((e,t)=>{if(window.easepick)return void e();const s=document.createElement("link");s.rel="stylesheet",s.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(s);const i=document.createElement("script");i.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",i.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},i.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(i)})}async renderTemplate(){const e=this.getInputId(),t=this.getDisplayValue();if(this.useNative)return this.renderNativeTemplate(e);const s=this.startName||(this.name?`${this.name}_start`:""),i=this.endName||(this.name?`${this.name}_end`:""),a=this.currentStartDate?this.formatForOutput(this.currentStartDate):"",n=this.currentEndDate?this.formatForOutput(this.currentEndDate):"";return`\n <div class="date-range-picker-container">\n <input \n type="text"\n id="${e}"\n ${this.name?`name="${this.name}"`:""}\n class="${this.inputClass} date-range-picker-input${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(t)}"\n placeholder="${this.escapeHtml(this.placeholder)}"\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n autocomplete="off"\n data-change-action="range-changed"\n style="background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22><path fill=%22%236c757d%22 fill-rule=%22evenodd%22 d=%22M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z%22/></svg>'); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding-right: 2.25rem; cursor: pointer;"\n />\n \n \x3c!-- Hidden inputs for form submission --\x3e\n ${s?`<input type="hidden" name="${s}" value="${this.escapeHtml(a)}" />`:""}\n ${i?`<input type="hidden" name="${i}" value="${this.escapeHtml(n)}" />`:""}\n ${this.fieldName?`<input type="hidden" name="${this.fieldName}" value="${this.escapeHtml(this.name||"")}" />`:""}\n \n <div class="date-range-picker-feedback"></div>\n </div>\n `}renderNativeTemplate(e){return`\n <div class="date-range-picker-container date-range-native">\n <div class="row g-2">\n <div class="col">\n <input \n type="date"\n id="${e}_start"\n name="${this.name}_start"\n class="${this.inputClass}${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(this.formatDate(this.currentStartDate,"YYYY-MM-DD"))}"\n placeholder="${this.escapeHtml(this.startPlaceholder)}"\n ${this.min?`min="${this.min}"`:""}\n ${this.max?`max="${this.max}"`:""}\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n data-change-action="start-date-changed"\n />\n </div>\n <div class="col-auto d-flex align-items-center">\n <span class="text-muted">${this.escapeHtml(this.separator.trim())}</span>\n </div>\n <div class="col">\n <input \n type="date"\n id="${e}_end"\n name="${this.name}_end"\n class="${this.inputClass}${this.hasError()?" is-invalid":""}"\n value="${this.escapeHtml(this.formatDate(this.currentEndDate,"YYYY-MM-DD"))}"\n placeholder="${this.escapeHtml(this.endPlaceholder)}"\n ${this.min?`min="${this.min}"`:""}\n ${this.max?`max="${this.max}"`:""}\n ${this.disabled?"disabled":""}\n ${this.readonly?"readonly":""}\n ${this.required?"required":""}\n data-change-action="end-date-changed"\n />\n </div>\n </div>\n \n \x3c!-- Hidden input for combined value --\x3e\n <input type="hidden" name="${this.name}" value="${this.escapeHtml(this.getCombinedValue())}" />\n ${this.fieldName?`<input type="hidden" name="${this.fieldName}" value="${this.escapeHtml(this.name||"")}" />`:""}\n \n <div class="date-range-picker-feedback"></div>\n </div>\n `}async onAfterRender(){await super.onAfterRender(),this.easepickLoaded&&!this.useNative?await this.initializeEasepick():this.initializeNativeFallback()}async initializeEasepick(){const e=this.getInputElement();if(e&&window.easepick)try{const t={element:e,css:["https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css"],format:this.displayFormat,lang:"en-US",autoApply:this.autoApply,inline:this.inline,readonly:this.readonly,zIndex:9999,plugins:["RangePlugin"],RangePlugin:{tooltip:!0,locale:{one:"day",other:"days"}}};this.min&&(t.minDate=new Date(this.min)),this.max&&(t.maxDate=new Date(this.max)),t.setup=e=>{e.on("select",e=>{const{start:t,end:s}=e.detail,i=t?this.normalizeDateFromPicker(t):"",a=s?this.normalizeDateFromPicker(s):"";this.handleRangeChange(i,a)}),e.on("clear",()=>{this.handleRangeChange("","")}),e.on("show",()=>{this.emit("picker:show")}),e.on("hide",()=>{this.emit("picker:hide")})},this.picker=new window.easepick.create(t),this.currentStartDate&&this.currentEndDate&&this.picker.setDateRange(this.currentStartDate,this.currentEndDate)}catch(t){console.error("Failed to initialize Easepick range picker:",t),this.useNative=!0,await this.render(),this.initializeNativeFallback()}}initializeNativeFallback(){this.updateConstraints()}async onChangeRangeChanged(e,t,s){}async onChangeStartDateChanged(e,t,s){const i=s.value;this.handleRangeChange(i,this.currentEndDate),this.updateConstraints()}async onChangeEndDateChanged(e,t,s){const i=s.value;this.handleRangeChange(this.currentStartDate,i),this.updateConstraints()}handleRangeChange(e,t){const s=this.currentStartDate,i=this.currentEndDate;this.currentStartDate=e,this.currentEndDate=t,this.updateHiddenInputs(),s===e&&i===t||(this.emit("change",{startDate:e,endDate:t,combined:this.getCombinedValue(),formatted:this.getDisplayValue(),oldStartDate:s,oldEndDate:i}),this.emit("range:changed",{startDate:e,endDate:t,oldStartDate:s,oldEndDate:i}))}updateConstraints(){if(!this.useNative)return;const e=this.element?.querySelector(`[name="${this.name}_start"]`),t=this.element?.querySelector(`[name="${this.name}_end"]`);e&&t&&(this.currentStartDate&&(t.min=this.currentStartDate),this.currentEndDate&&(e.max=this.currentEndDate))}normalizeDateFromPicker(e){if(!e)return"";if("function"==typeof e.toJSDate){const t=e.toJSDate();return this.formatDate(t,this.format)}if("function"==typeof e.getFullYear){const t=e.getFullYear(),s=String(e.getMonth()+1).padStart(2,"0"),i=String(e.getDate()).padStart(2,"0");switch(this.format){case"YYYY-MM-DD":default:return`${t}-${s}-${i}`;case"MM/DD/YYYY":return`${s}/${i}/${t}`;case"DD/MM/YYYY":return`${i}/${s}/${t}`}}return this.formatDate(e,this.format)}formatDate(e,t=this.format){if(!e)return"";let s,i,a,n;if("string"==typeof e&&/^\d{4}-\d{2}-\d{2}$/.test(e)){const t=e.split("-");s=parseInt(t[0]),i=String(parseInt(t[1])).padStart(2,"0"),a=String(parseInt(t[2])).padStart(2,"0")}else{if(n=e instanceof Date?e:new Date(e),isNaN(n.getTime()))return"";s=n.getFullYear(),i=String(n.getMonth()+1).padStart(2,"0"),a=String(n.getDate()).padStart(2,"0")}switch(t){case"YYYY-MM-DD":default:return`${s}-${i}-${a}`;case"MM/DD/YYYY":return`${i}/${a}/${s}`;case"DD/MM/YYYY":return`${a}/${i}/${s}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][parseInt(i)-1]} ${a}, ${s}`}}formatForOutput(e){if(!e)return"";if("string"==typeof e&&/^\d{4}-\d{2}-\d{2}$/.test(e))switch(this.outputFormat){case"epoch":const t=e.split("-"),s=new Date(parseInt(t[0]),parseInt(t[1])-1,parseInt(t[2]));return Math.floor(s.getTime()/1e3).toString();case"iso":/* @__PURE__ */
|
|
2
|
-
return new Date(e+"T00:00:00").toISOString();default:return e}const t=new Date(e);if(isNaN(t.getTime()))return"";switch(this.outputFormat){case"epoch":return Math.floor(t.getTime()/1e3).toString();case"iso":return t.toISOString();default:return this.formatDate(e,this.format)}}getDisplayValue(){if(!this.currentStartDate&&!this.currentEndDate)return"";const e=this.currentStartDate?this.formatDate(this.currentStartDate,this.displayFormat):"",t=this.currentEndDate?this.formatDate(this.currentEndDate,this.displayFormat):"";return e&&t?`${e}${this.separator}${t}`:e||t||""}getCombinedValue(){return this.currentStartDate||this.currentEndDate?JSON.stringify({start:this.currentStartDate,end:this.currentEndDate}):""}getInputId(){return this.name?`daterange_${this.name}_${Date.now()}`:`daterange_${Date.now()}`}getInputElement(){return this.element?.querySelector('input[type="text"], input[name="'+this.name+'"]')}updateHiddenInputs(){const e=this.startName||(this.name?`${this.name}_start`:""),t=this.endName||(this.name?`${this.name}_end`:""),s=e?this.element?.querySelector(`[name="${e}"]`):null,i=t?this.element?.querySelector(`[name="${t}"]`):null,a=this.name?this.element?.querySelector(`[name="${this.name}"]`):null,n=this.fieldName?this.element?.querySelector(`[name="${this.fieldName}"]`):null,l=this.currentStartDate?this.formatForOutput(this.currentStartDate):"",r=this.currentEndDate?this.formatForOutput(this.currentEndDate):"";s&&(s.value=l),i&&(i.value=r),a&&(a.value=this.getDisplayValue()),n&&(n.value=this.name||"")}hasError(){return!(!this.currentStartDate||!this.currentEndDate)&&new Date(this.currentEndDate)<new Date(this.currentStartDate)}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}setRange(e,t){if(this.currentStartDate=e,this.currentEndDate=t,this.picker&&this.easepickLoaded)this.picker.setDateRange(e||null,t||null);else if(this.useNative){const s=this.element?.querySelector(`[name="${this.name}_start"]`),i=this.element?.querySelector(`[name="${this.name}_end"]`);s&&(s.value=this.formatDate(e,"YYYY-MM-DD")),i&&(i.value=this.formatDate(t,"YYYY-MM-DD"))}else{const e=this.getInputElement();e&&(e.value=this.getDisplayValue())}this.updateHiddenInputs(),this.emit("range:set",{startDate:e,endDate:t})}getRange(){return{start:this.currentStartDate,end:this.currentEndDate,combined:this.getCombinedValue()}}clear(){this.setRange("","")}setStartDate(e){this.setRange(e,this.currentEndDate)}setEndDate(e){this.setRange(this.currentStartDate,e)}getStartDate(){return this.currentStartDate}getEndDate(){return this.currentEndDate}setEnabled(e){this.disabled=!e;const t=this.element?.querySelectorAll("input");t?.forEach(e=>{e.disabled=this.disabled})}setReadonly(e){this.readonly=e;const t=this.element?.querySelectorAll('input:not([type="hidden"])');t?.forEach(t=>{t.readonly=e})}focus(){const e=this.getInputElement();e&&e.focus()}show(){this.picker&&this.easepickLoaded&&this.picker.show()}hide(){this.picker&&this.easepickLoaded&&this.picker.hide()}getFormValue(){return this.getRange()}async setFormValue(e){if("string"==typeof e)try{const t=JSON.parse(e);this.setRange(t.start,t.end)}catch{this.setRange(e,"")}else e&&"object"==typeof e&&this.setRange(e.start||"",e.end||"")}async onBeforeDestroy(){if(this.picker&&this.easepickLoaded)try{this.picker.destroy()}catch(e){console.warn("Error destroying Easepick range picker instance:",e)}this.picker=null,await super.onBeforeDestroy()}static create(e={}){return new DateRangePicker(e)}}class FormView extends e.View{constructor(e={}){const{formConfig:t=e.config,fields:s,model:i=null,data:a={},defaults:n=null,errors:l={},fileHandling:r="base64",autosaveModelField:o=!1,...d}=e;super({tagName:"div",className:"form-view",...d}),this.model=i,this.defaults=n||a,this._originalData=a,this.errors=l,this.loading=!1,this.fileHandling=r,this.autosaveModelField=o,this.customComponents=/* @__PURE__ */new Map,this.fieldStatusManagers=/* @__PURE__ */new Map,this.saveTimeouts=/* @__PURE__ */new Map,this.isSaving=!1,this.data=this.prepareFormData(),this.formConfig=t||{fields:s||[]},this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:l})}prepareFormData(){const e={...this.defaults};if(this.model)if(this.model.attributes&&"object"==typeof this.model.attributes)Object.assign(e,this.model.attributes);else if("function"==typeof this.model.toJSON){const t=this.model.toJSON();Object.assign(e,t)}else"object"==typeof this.model&&this.model.constructor===Object&&Object.assign(e,this.model);return this._originalData&&Object.assign(e,this._originalData),e}getFormConfig(){const e={...this.formConfig},t=this.getApp();return e.fields=this.formConfig.fields.filter(e=>!e.permissions||t.activeUser?.hasPermission(e.permissions)),e}async renderTemplate(){return this.formBuilder.buildFormHTML()}async onAfterRender(){await super.onAfterRender(),this.data=this.prepareFormData(),this.populateFormValues(),this.initializeFormComponents(),this.initializeChangeHandlers();const e=this.getFormElement();e&&e.addEventListener("submit",e=>(e.preventDefault(),!1))}populateFormValues(){if(this.element&&this.formConfig?.fields){this._isPopulating=!0;try{this.formConfig.fields.forEach(e=>{"group"===e.type&&e.fields?e.fields.forEach(e=>{this.populateFieldValue(e)}):this.populateFieldValue(e)})}finally{this._isPopulating=!1}}}populateFieldValue(t){if(!t.name||!this.element)return;const s=this.element.querySelector(`[name="${t.name}"]`);if(!s)return;const i=e.MOJOUtils.getContextData(this.data,t.name);null!=i&&this.setFieldValue(s,t,i)}initializeFormComponents(){this.initializeImageFields(),this.initializeCustomComponents(),this.initializeTagInputs(),this.initializeCollectionSelects(),this.initializeCollectionMultiSelects(),this.initializeDatePickers(),this.initializeDateRangePickers(),this.initializePasswordFields()}initializeImageFields(){this.element.querySelectorAll(".image-drop-zone.droppable").length>0&&this.enableFileDrop({acceptedTypes:["image/*"],maxFileSize:10485760,multiple:!1,dropZoneSelector:".image-drop-zone.droppable",visualFeedback:!0,dragOverClass:"drag-over",dragActiveClass:"drag-active"})}initializeCustomComponents(){this.initializeTagInputs(),this.initializeCollectionSelects(),this.initializeCollectionMultiSelects(),this.initializeDatePickers(),this.initializeDateRangePickers(),this.element.querySelectorAll("[data-component]").forEach(e=>{e.getAttribute("data-component"),e.getAttribute("data-field")})}initializeChangeHandlers(){if(!this.element)return;const e=this.element.querySelectorAll("input:not([data-action]), select:not([data-action]), textarea:not([data-action])");console.log("FormView: initializeChangeHandlers - found",e.length,"inputs"),e.forEach(e=>{console.log("FormView: Processing input:",e.type,e.name,e.getAttribute("data-change-action")),e.hasAttribute("data-component")||e.classList.contains("form-check-input")||(e.addEventListener("change",t=>{if(this._isPopulating)return;const s=e.name;if(s){let i=e.value;if("checkbox"===e.type)i=e.checked;else if("radio"===e.type){if(!e.checked)return}else if(e.multiple&&e.selectedOptions)i=Array.from(e.selectedOptions).map(e=>e.value);else if("file"===e.type){const s=e.getAttribute("data-change-action");if("image-selected"===s)return void this.onChangeImageSelected(t,e);if("file-selected"===s)return void this.onChangeFileSelected(t,e)}this.handleFieldChange(s,i)}}),"text"!==e.type&&"email"!==e.type&&"url"!==e.type&&"TEXTAREA"!==e.tagName||e.addEventListener("input",t=>{if(this._isPopulating)return;const s=e.name;s&&this.handleFieldChange(s,e.value)}))})}initializeTagInputs(){this.element.querySelectorAll('[data-field-type="tag"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),s=e.getAttribute("data-field-config"),i=JSON.parse(s),a=new TagInputView({...i,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){}})}initializeCollectionSelects(){this.element.querySelectorAll('[data-field-type="collection"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=this.getFormFieldConfig(s);if(!n||!n.Collection)return;const l=new n.Collection;n.collectionParams&&(l.params={...l.params,...n.collectionParams});const r=new CollectionSelectView({...a,collection:l,containerId:null});let o=e.MOJOUtils.getContextData(this.data,s);o&&r.setFormValue(o),r.render(!0,t),this.customComponents.set(s,r),r.on("change",e=>{this.handleFieldChange(s,e.value)})}catch(s){}})}initializeCollectionMultiSelects(){this.element.querySelectorAll('[data-field-type="collectionmultiselect"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),n=this.getFormFieldConfig(s);if(!n||!n.Collection)return;const l=new n.Collection;n.collectionParams&&(l.params={...l.params,...n.collectionParams});const r=new CollectionMultiSelectView({...a,collection:l,containerId:null});let o=e.MOJOUtils.getContextData(this.data,s);o&&r.setFormValue(o),r.render(!0,t),this.customComponents.set(s,r),r.on("change",e=>{this.handleFieldChange(s,e.value)})}catch(s){console.error("CollectionMultiSelect initialization failed:",s)}})}initializeDatePickers(){this.element.querySelectorAll('[data-field-type="datepicker"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),s=e.getAttribute("data-field-config"),i=JSON.parse(s),a=new DatePicker({...i,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.value)})}catch(t){}})}initializeDateRangePickers(){this.element.querySelectorAll('[data-field-type="daterange"]').forEach(e=>{try{const t=e.getAttribute("data-field-name"),s=e.getAttribute("data-field-config"),i=JSON.parse(s),a=new DateRangePicker({...i,containerId:null});a.render(!0,e),this.customComponents.set(t,a),a.on("change",e=>{this.handleFieldChange(t,e.combined)})}catch(t){}})}handleFieldChange(e,t){this.data[e]=t,this.autosaveModelField&&this.model?this.handleFieldSave(e,t):this.model&&this.options.allowModelChange&&(this._isFormDrivenChange=!0,this.model.set(e,t)),this.emit("field:change",{field:e,value:t})}async handleFieldSave(e,t){if(!this.model||this.isSaving)return;this.saveTimeouts.has(e)&&clearTimeout(this.saveTimeouts.get(e));const s=this.getFieldStatusManager(e);s.showStatus("saving");const i=setTimeout(async()=>{try{this.isSaving=!0,this.saveTimeouts.delete(e),this._isFormDrivenChange=!0,"function"==typeof this.model.save?await this.model.save({[e]:t}):this.model.set(e,t),s.showStatus("saved")}catch(i){s.showStatus("error",{message:i.message})}finally{this.isSaving=!1}},300);this.saveTimeouts.set(e,i)}getFieldStatusManager(e){if(!this.fieldStatusManagers.has(e)){const t=this.element.querySelector(`[name="${e}"]`);if(t){const s=new FieldStatusManager(t);this.fieldStatusManagers.set(e,s)}}return this.fieldStatusManagers.get(e)}refreshForm(){this.data=this.prepareFormData(),this.element&&this.populateFormValues()}getChangeReason(e,t){if(e instanceof File)return 0===e.size||""===e.name||"blob"===e.name?"empty file, no change":`file upload: ${e.name}, ${e.size} bytes`;if("string"==typeof e&&e.startsWith("data:image/"))return"base64 image upload";if("boolean"==typeof e||"boolean"==typeof t){const s=Boolean(e);return`boolean: ${null!=t&&Boolean(t)} → ${s}`}return null!=e&&String(e).trim(),null!=t&&String(t).trim(),null==t?"was null/undefined, now has value":null==e?"was value, now null/undefined":"text content changed"}setFormData(e){this._originalData={...this._originalData,...e},this.refreshForm()}async onActionSubmitForm(e,t){e.preventDefault();const s=await this.handleSubmit();s.success?(this.data=s.data,this.emit("submit",{data:s.data,result:s.result,form:this,event:e}),!this.model&&this.formConfig.onSubmit&&"function"==typeof this.formConfig.onSubmit&&await this.formConfig.onSubmit(s.data,this)):this.emit("error",{error:s.error,result:s,form:this})}async onActionResetForm(e,t){const s=this.getFormElement();s&&(s.reset(),this.data={},this.clearAllErrors(),this.emit("reset",{form:this,event:e}))}async onActionClickImageUpload(e,t){console.log("FormView: onActionClickImageUpload called"),console.log("FormView: element:",t);const s=t.getAttribute("data-field-id");if(console.log("FormView: fieldId:",s),!s)return void console.error("FormView: No fieldId attribute found");const i=this.element.querySelector(`#${s}`);console.log("FormView: fileInput:",i),i&&!i.disabled?(i.click(),console.log("FormView: fileInput.click() called")):i?console.log("FormView: fileInput is disabled"):console.error("FormView: fileInput not found for fieldId:",s)}async onActionRemoveImage(e,t){const s=t.getAttribute("data-field");if(!s)return;const i=this.element.querySelector(`input[name="${s}"]`);i&&(i.value="",i.dispatchEvent(new Event("change",{bubbles:!0}))),delete this.data[s],this.emit("change",{field:s,value:null,form:this}),await this.updateField(s)}async onActionClearColor(e,t){const s=t.getAttribute("data-field");if(!s)return;const i=this.element.querySelector(`input[name="${s}"]`);i&&(i.value="",this.handleFieldChange(s,""),await this.updateField(s))}async onActionSelectButtonOption(e,t){const s=t.getAttribute("data-field"),i=t.getAttribute("data-value");if(!s||!i)return;this.data[s]=i;const a=t.closest(".btn-group");a&&(a.querySelectorAll("button").forEach(e=>{e.classList.remove("active"),e.classList.add("btn-outline-primary"),e.classList.remove("btn-primary")}),t.classList.add("active"),t.classList.remove("btn-outline-primary"),t.classList.add("btn-primary")),this.emit("field:changed",{field:s,value:i,form:this}),this.emit("change",{field:s,value:i,form:this}),this.emit("form:changed",await this.getFormData())}async onActionApplyFilter(e,t){const s=t.closest(".dropdown"),i=s?.querySelectorAll('input[type="checkbox"]');if(!i||0===i.length)return;const a=i[0].getAttribute("data-field");if(!a)return;const n=[];i.forEach(e=>{e.checked&&n.push(e.value)}),this.data[a]=n;const l=s.querySelector('[data-bs-toggle="dropdown"]');if(l&&window.bootstrap?.Dropdown){const e=window.bootstrap.Dropdown.getInstance(l);e?.hide()}this.emit("field:changed",{field:a,value:n,form:this}),this.emit("change",{field:a,value:n,form:this}),this.emit("form:changed",await this.getFormData())}async onChangeValidateField(e,t){const s=t.name;if(s){const e=t.value;this.handleFieldChange(s,e),this.validateField(s)}}async onChangeToggleSwitch(e,t){const s=t.getAttribute("data-field");if(s){const e=t.checked;this.handleFieldChange(s,e),this.emit("switch:toggle",{field:s,checked:e,form:this})}}async onChangeImageSelected(e,t){console.log("FormView: onChangeImageSelected called"),console.log("FormView: element:",t),console.log("FormView: element.files:",t.files);const s=t.getAttribute("data-field"),i=t.files[0];if(console.log("FormView: fieldName:",s),console.log("FormView: file:",i),s&&i){console.log("FormView: fieldName and file exist, processing...");const e=this.findFieldConfig(s);console.log("FormView: fieldConfig:",e);const n=URL.createObjectURL(i);if(console.log("FormView: previewUrl created:",n),e&&e.imageSize){console.log("FormView: Image cropping is required, imageSize:",e.imageSize);try{const a=window.MOJO?.plugins?.ImageCropView;if(console.log("FormView: ImageCropView available?",!!a),!a)return console.log("FormView: ImageCropView not available, falling back to normal handling"),this.data[s]=i,await this.updateImagePreview(s,n),void this.emit("image:selected",{field:s,file:i,form:this});const l=await a.showDialog(n,{title:`Crop ${e.label||s}`,cropAndScale:e.imageSize,size:"lg"});if("crop"===l.action&&l.data){const e=await fetch(l.data),t=await e.blob(),a=new File([t],i.name,{type:i.type||"image/png"});this.data[s]=a,await this.updateImagePreview(s,l.data),this.emit("image:selected",{field:s,file:a,originalFile:i,cropped:!0,cropData:l.cropData,form:this}),this.emit("change",{field:s,value:a,form:this})}else t.value=""}catch(a){console.error("FormView: Error during image cropping:",a),console.log("FormView: Falling back to normal image handling after error"),this.data[s]=i,await this.updateImagePreview(s,n),this.emit("image:selected",{field:s,file:i,form:this}),this.emit("change",{field:s,value:i,form:this})}}else console.log("FormView: Normal image handling (no cropping)"),this.data[s]=i,console.log("FormView: File stored in this.data["+s+"]"),await this.updateImagePreview(s,n),console.log("FormView: updateImagePreview completed"),this.emit("image:selected",{field:s,file:i,form:this}),console.log("FormView: image:selected event emitted")}else console.log("FormView: Missing fieldName or file - not processing")}async onChangeFileSelected(e,t){const s=Array.from(t.files);this.emit("file:selected",{field:t.name,files:s,form:this}),this.emit("change",{field:t.name,value:s,form:this})}async onChangeRangeChanged(e,t){const s=t.name,i=t.value,a=t.getAttribute("data-target");if(a){const e=this.element.querySelector(`#${a}`);e&&(e.textContent=i)}s&&this.handleFieldChange(s,i),this.emit("range:changed",{field:s,value:i,form:this})}async onChangeFilterSearch(e,t){const s=t.value;this.emit("search",{query:s,field:t.name,form:this})}async onChangeFilterSelectOptions(e,t){const s=t.value.toLowerCase(),i=t.getAttribute("data-target"),a=i?this.element.querySelector(`#${i}`):null;a&&a.querySelectorAll("option").forEach(e=>{const t=e.textContent.toLowerCase();e.style.display=t.includes(s)?"":"none"})}async onFileDrop(e,t,s){const i=t.target.closest(".image-drop-zone");if(!i)return;const a=i.getAttribute("data-field");if(!a)return;const n=e[0],l=this.element.querySelector(`input[name="${a}"]`);if(l){const e=new DataTransfer;e.items.add(n),l.files=e.files,l.dispatchEvent(new Event("change",{bubbles:!0}))}this.data[a]=n;const r=URL.createObjectURL(n);await this.updateImagePreview(a,r),this.emit("image:dropped",{field:a,file:n,form:this})}async onFileDropError(e,t,s){this.showError(`File upload error: ${e.message}`),this.emit("file:error",{error:e,files:s,form:this})}getFormElement(){return this.element?this.element.querySelector("form"):null}getFormFieldConfig(e){const t=s=>{for(const i of s){if(i.name===e)return i;if(i.fields&&Array.isArray(i.fields)){const e=t(i.fields);if(e)return e}}return null};return t(this.formConfig.fields||[])}async getFormData(){const e=this.getFormElement();if(!e)return"multipart"===this.fileHandling?new FormData:{};if("multipart"===this.fileHandling){const t=new FormData(e);for(const[e,s]of Object.entries(this.data))if(s instanceof File)t.set(e,s);else if(s instanceof FileList)for(let i=0;i<s.length;i++)t.append(`${e}[${i}]`,s[i]);return t}{const s=new FormData(e),i={};for(const[e,t]of s.entries())i[e]?(Array.isArray(i[e])||(i[e]=[i[e]]),i[e].push(t)):i[e]=t;e.querySelectorAll('input[type="checkbox"]').forEach(e=>{i[e.name]=e.checked}),e.querySelectorAll('input[type="number"]').forEach(e=>{if(e.name&&void 0!==i[e.name]&&""!==i[e.name]){const t=Number(i[e.name]);isNaN(t)||(i[e.name]=t)}}),this.formConfig.fields?.forEach(e=>{if("select"===e.type&&e.name&&void 0!==i[e.name]){const t=this.getFormFieldConfig(e.name);if(t?.options&&Array.isArray(t.options)&&t.options.every(e=>{const t="object"==typeof e?e.value:e;return""===t||!isNaN(Number(t))})&&""!==i[e.name]){const t=Number(i[e.name]);isNaN(t)||(i[e.name]=t)}}}),e.querySelectorAll('[data-field-type="json"]').forEach(e=>{try{i[e.name]=JSON.parse(e.value)}catch(t){i[e.name]=e.value}}),this.customComponents.forEach((e,t)=>{e.getFormValue?i[t]=e.getFormValue():e.getValue&&(i[t]=e.getValue())});for(const[e,a]of Object.entries(this.data))if(a instanceof File)try{i[e]=await this.fileToBase64(a)}catch(t){i[e]=null}else if(a instanceof FileList){const s=[];for(let e=0;e<a.length;e++)try{s.push(await this.fileToBase64(a[e]))}catch(t){s.push(null)}i[e]=s}return i}}_onModelChange(){this.isSaving||(this.data=this.prepareFormData(),this.isMounted()&&(this._isFormDrivenChange||this.syncFormWithModel(),this._isFormDrivenChange=!1))}syncFormWithModel(){this.model&&this.element&&(this.formDataMatchesModelData(this.data)||this.populateFormValues())}formDataMatchesModelData(e){if(!this.formConfig?.fields||!this.element)return!0;for(const t of this.formConfig.fields)if("group"===t.type&&t.fields){for(const s of t.fields)if(!this.fieldValueMatchesModel(s,e))return!1}else if(!this.fieldValueMatchesModel(t,e))return!1;return!0}fieldValueMatchesModel(t,s){if(!t.name)return!0;const i=this.element.querySelector(`[name="${t.name}"]`);if(!i)return!0;const a=this.getFieldCurrentValue(i,t),n=e.MOJOUtils.getContextData(s,t.name);return!1===this.valuesAreDifferent(a,n)}getFieldCurrentValue(e,t){switch(t.type){case"checkbox":case"toggle":case"switch":return e.checked;case"radio":const s=this.element.querySelector(`[name="${t.name}"]:checked`);return s?s.value:"";case"select":return e.multiple?Array.from(e.selectedOptions).map(e=>e.value):e.value;case"file":case"image":return null;default:return e.value}}setFieldValue(e,t,s){switch(t.type){case"checkbox":case"toggle":case"switch":e.checked=Boolean(s);break;case"radio":const i=this.element.querySelector(`[name="${t.name}"][value="${s}"]`);i&&(i.checked=!0);break;case"select":e.multiple&&Array.isArray(s)?Array.from(e.options).forEach(e=>{e.selected=s.includes(e.value)}):e.value=s||"";break;case"file":case"image":break;default:e.value=s||""}e.dispatchEvent(new Event("change",{bubbles:!0}))}setDefaults(e){this.defaults={...this.defaults,...e},this.refreshForm()}async handleSubmit(){try{const e=await this.getFormData();if(!1!==this.formConfig.validateOnSubmit&&!this.validate())return this.focusFirstError(),{success:!1,data:e,error:"Form validation failed"};if(this.model&&"function"==typeof this.model.save){const t=await this.saveModel(e);return t&&!1!==t.success?{success:!0,data:e,result:t}:{success:!1,data:e,result:t,error:t?.message||t?.error||"Save failed. Please try again."}}return e}catch(e){return{success:!1,error:e.message||"An error occurred while submitting the form"}}}async saveModel(e=null){if(!this.model||"function"!=typeof this.model.save)throw new Error("No model available for saving");e||(e=await this.getFormData());const t=this.getChangedData(e);if(!t||0===Object.keys(t).length)return{success:!0,message:"No changes to save",data:e};try{return this._isFormDrivenChange=!0,await this.model.save(t)}catch(s){throw s}}getChangedData(e){if(!this.model)return e;const t=this.getOriginalModelData();let s;return s=e instanceof FormData?this.getChangedFormData(e,t):this.getChangedObjectData(e,t),s}getOriginalModelData(){return this.model.attributes?this.model.attributes:"function"==typeof this.model.toJSON?this.model.toJSON():{}}getChangedFormData(e,t){const s=new FormData;let i=!1;for(const[a,n]of e.entries())if(n instanceof File)0===n.size||""===n.name||"blob"===n.name||(s.set(a,n),i=!0);else{const e=t[a];n!==e&&n!==String(e)&&(s.set(a,n),i=!0)}return i?s:null}getChangedObjectData(e,t){const s={};let i=!1;const a=/* @__PURE__ */new Set([...Object.keys(t),...Object.keys(e)]),n=(e,t)=>t.split(".").reduce((e,t)=>e&&"object"==typeof e?e[t]:void 0,e);for(const l of a){const a=this.findFieldConfig(l);if(!a)continue;const r=e[l],o=n(t,l),d=a.type||"text";this.valuesAreDifferent(r,o,d,a)&&(s[l]=r,i=!0)}return i?s:null}valuesAreDifferent(e,t,s="text",i={}){if(e instanceof File)return e.size>0&&""!==e.name&&"blob"!==e.name;if("string"==typeof e&&e.startsWith("data:image/"))return!0;if("collection"===s&&"object"==typeof t&&null!=t&&"string"==typeof e){if("0"===e)return null!==t;if(t[i.valueField||"id"]==e)return!1}return"switch"===s||"checkbox"===s||"toggle"===s?!!e!=!!t:(null==e?"":String(e).trim())!==(null==t?"":String(t).trim())}validate(){const e=this.getFormElement();if(!e)return!1;const t=e.checkValidity();return t||e.classList.add("was-validated"),t}validateField(e){const t=this.getFormElement();if(!t)return!1;const s=t.elements[e];if(!s)return!1;const i=s.checkValidity();return i?(s.classList.remove("is-invalid"),s.classList.add("is-valid"),delete this.errors[e]):(s.classList.remove("is-valid"),s.classList.add("is-invalid"),this.errors[e]=s.validationMessage),i}focusFirstError(){const e=this.getFormElement();if(!e)return;const t=e.querySelector(":invalid");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 s=t.querySelectorAll("input, select, textarea, button"),i=t.querySelector('button[type="submit"]');if(e)s.forEach(e=>e.disabled=!0),i&&(i.innerHTML='<span class="spinner-border spinner-border-sm me-2"></span>Loading...');else if(s.forEach(e=>e.disabled=!1),i){const e=this.formConfig.options?.submitButton||"Submit";i.innerHTML="string"==typeof e?e:"Submit"}}showError(e){if(this.emit("error",{message:e,form:this}),this.element){this.element.querySelectorAll(".alert").forEach(e=>e.remove());const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.remove()},5e3)}}async updateField(e){this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:this.errors}),await this.render()}async updateImagePreview(e,t){const s=this.element.querySelector(`[data-field="${e}"].image-drop-zone`);if(!s)return;let i=s.querySelector("img");const a=s.querySelector(".bi-image")?.parentElement,n=s.getAttribute("data-field-id");if(t){if(i)i.src=t;else{const i=`${n}_preview`;s.innerHTML=`\n <img id="${i}"\n src="${t}"\n alt="Preview"\n class="img-thumbnail w-100 h-100"\n style="object-fit: cover;">\n <button type="button"\n class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"\n data-action="remove-image"\n data-field-id="${n}"\n data-field="${e}"\n style="opacity: 0.8;">\n <i class="bi bi-x"></i>\n </button>\n `}a&&(a.style.display="none")}}findFieldConfig(e){const t=s=>{for(const i of s){if(i.name===e)return i;if("group"===i.type&&i.fields){const e=t(i.fields);if(e)return e}}return null};return t(this.formConfig.fields||[])}async fileToBase64(e){return new Promise((t,s)=>{const i=new FileReader;i.onload=()=>t(i.result),i.onerror=s,i.readAsDataURL(e)})}hasFiles(e){if(e instanceof FormData){for(const[t,s]of e.entries())if(s instanceof File)return!0;return!1}for(const t of Object.values(e)){if(t instanceof File)return!0;if(Array.isArray(t)&&t.some(e=>e instanceof File))return!0}return!1}reset(){const e=this.getFormElement();e&&e.reset(),this.data={},this.errors={},this.clearAllErrors(),this.emit("reset",{form:this})}async updateConfig(e){this.formConfig={...this.formConfig,...e},this.formBuilder=new FormBuilder({...this.getFormConfig(),data:this.data,errors:this.errors}),await this.render()}async onBeforeDestroy(){const e=[];for(const t of this.customComponents.values())t.destroy&&e.push(t.destroy());await Promise.all(e),this.customComponents.clear(),Object.values(this.data).forEach(e=>{"string"==typeof e&&e.startsWith("blob:")&&URL.revokeObjectURL(e)}),await super.onBeforeDestroy()}initializePasswordFields(){this.element&&this.element.querySelectorAll('input[data-field-type="password"], input[type="password"]').forEach(e=>{this.updatePasswordStrengthUI(e),e.addEventListener("input",e=>{this.updatePasswordStrengthUI(e.target)});const t=t=>{if("function"==typeof t.getModifierState){const s=t.getModifierState("CapsLock");this.updateCapsLockWarning(e,!!s)}};e.addEventListener("keydown",t),e.addEventListener("keyup",t),this.updateCapsLockWarning(e,!1)})}async onActionTogglePassword(e,t){e.preventDefault();const s=t.getAttribute("data-target");let i=null;if(s&&(i=this.element.querySelector("#"+s)),!i){const e=t.closest(".input-group");e&&(i=e.querySelector('input[type="password"], input[data-field-type="password"], input[type="text"]'))}if(!i)return;const a="password"===i.type;i.type=a?"text":"password",t.setAttribute("aria-pressed",a?"true":"false"),t.setAttribute("aria-label",a?"Hide password":"Show password");const n=t.querySelector("i");n&&(n.classList.toggle("bi-eye",!a),n.classList.toggle("bi-eye-slash",a)),i.focus();try{const e=i.value?.length??0;i.setSelectionRange(e,e)}catch(l){}}computePasswordStrength(e=""){const t=e.length;let s=0;t>=6&&s++,t>=8&&s++,t>=12&&s++;const i=[/[a-z]/.test(e),/[A-Z]/.test(e),/\d/.test(e),/[^A-Za-z0-9]/.test(e)].filter(Boolean).length;return i>=2&&s++,i>=3&&s++,s=Math.max(0,Math.min(4,s)),[{percent:0,label:"Too short",barClass:"bg-secondary"},{percent:25,label:"Weak",barClass:"bg-danger"},{percent:50,label:"Fair",barClass:"bg-warning"},{percent:75,label:"Good",barClass:"bg-info"},{percent:100,label:"Strong",barClass:"bg-success"}][s]}updatePasswordStrengthUI(e){if(!e||!e.id)return;const t=this.element.querySelector(`#${e.id}_strength_bar`),s=this.element.querySelector(`#${e.id}_strength_text`);if(!t&&!s)return;const{percent:i,label:a,barClass:n}=this.computePasswordStrength(e.value||"");t&&(t.className=`progress-bar ${n}`,t.style.width=`${i}%`,t.setAttribute("aria-valuenow",String(i))),s&&(s.textContent=a)}updateCapsLockWarning(e,t){if(!e||!e.id)return;const s=this.element.querySelector(`#${e.id}_caps_warning`);s&&(t?s.classList.remove("d-none"):s.classList.add("d-none"))}}class FieldStatusManager{constructor(e){this.fieldElement=e,this.statusContainer=this.findOrCreateStatusContainer(),this.timeouts=/* @__PURE__ */new Map}findOrCreateStatusContainer(){let e=this.fieldElement.parentElement.querySelector(".field-status-label-inline");if(!e){const t=this.findFieldLabel();t&&(e=t.querySelector(".field-status-label-inline"))}return e||(e=this.createStatusContainer()),e}createStatusContainer(){const e=this.getFieldType();this.getPlacementStrategy(e);const t=document.createElement("div");return this.createLabelInlineContainer(t)}getFieldType(){const e=this.fieldElement.tagName.toLowerCase(),t=this.fieldElement.type?.toLowerCase(),s=this.fieldElement.className;return"checkbox"===t||s.includes("form-check-input")||s.includes("form-switch")?"toggle":"select"===e?"select":"textarea"===e?"textarea":"input"}getPlacementStrategy(e){return"label-inline"}createLabelInlineContainer(e){e.className="field-status-label-inline",e.innerHTML=this.getStatusHTML();const t=this.findFieldLabel();return t?t.appendChild(e):this.fieldElement.parentElement.appendChild(e),e}findFieldLabel(){if(this.fieldElement.id){const e=document.querySelector(`label[for="${this.fieldElement.id}"]`);if(e)return e}const e=this.fieldElement.parentElement.querySelector("label");if(e)return e;return this.fieldElement.closest("label")||null}createInputOverlayContainer(e){e.className="field-status-overlay",e.innerHTML=this.getStatusHTML();const t=this.fieldElement.parentElement;return"static"===getComputedStyle(t).position&&(t.style.position="relative"),t.appendChild(e),e}createFullOverlayContainer(e){e.className="field-status-full-overlay d-none",e.innerHTML='\n <div class="saving-indicator">\n <div class="spinner-border spinner-border-sm text-primary" role="status">\n <span class="visually-hidden">Saving...</span>\n </div>\n <span class="ms-2">Saving...</span>\n </div>\n <div class="success-indicator d-none">\n <i class="bi bi-check-circle text-success"></i>\n <span class="ms-2">Saved</span>\n </div>\n <div class="error-indicator d-none">\n <i class="bi bi-exclamation-circle text-danger"></i>\n <span class="ms-2">Error saving</span>\n </div>\n ';const t=this.fieldElement.parentElement;return"static"===getComputedStyle(t).position&&(t.style.position="relative"),t.appendChild(e),e}getStatusHTML(){return'\n <div class="spinner-border spinner-border-sm text-primary d-none" data-status="saving" role="status">\n <span class="visually-hidden">Saving...</span>\n </div>\n <i class="bi bi-check-circle text-success d-none" data-status="saved"></i>\n <i class="bi bi-exclamation-circle text-danger d-none" data-status="error"></i>\n '}showStatus(e,t={}){this.clearTimeout(e),this.showStandardStatus(e,t)}showStandardStatus(e,t={}){this.hideAllStatuses();const s=this.statusContainer.querySelector(`[data-status="${e}"]`);s&&(s.classList.remove("d-none"),s.classList.add("d-inline-block","show"),"saved"===e?this.setTimeout(e,()=>this.hideStatus(e),2500):"error"===e&&(t.message&&(s.title=t.message),this.setTimeout(e,()=>this.hideStatus(e),6e3)))}showFullOverlayStatus(e,t={}){let s;switch(this.statusContainer.querySelectorAll(".saving-indicator, .success-indicator, .error-indicator").forEach(e=>e.classList.add("d-none")),this.statusContainer.classList.remove("d-none"),e){case"saving":s=".saving-indicator";break;case"saved":s=".success-indicator",this.setTimeout(e,()=>this.hideStatus(e),2500);break;case"error":if(s=".error-indicator",t.message){const e=this.statusContainer.querySelector(".error-indicator span");e&&(e.textContent=t.message)}this.setTimeout(e,()=>this.hideStatus(e),6e3)}if(s){const e=this.statusContainer.querySelector(s);e&&e.classList.remove("d-none")}}hideStatus(e){const t=this.statusContainer.querySelector(`[data-status="${e}"]`);t&&(t.classList.remove("show"),t.classList.add("hide"),setTimeout(()=>{t.classList.add("d-none"),t.classList.remove("d-inline-block","hide"),t.title=""},300))}hideAllStatuses(){this.statusContainer.querySelectorAll("[data-status]").forEach(e=>{e.classList.add("d-none"),e.classList.remove("d-inline-block","show","hide"),e.title=""})}setTimeout(e,t,s){const i=setTimeout(t,s);this.timeouts.set(e,i)}clearTimeout(e){this.timeouts.has(e)&&(clearTimeout(this.timeouts.get(e)),this.timeouts.delete(e))}destroy(){this.timeouts.forEach(e=>clearTimeout(e)),this.timeouts.clear()}}s(FormView);const i=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,FormView:FormView,default:FormView},Symbol.toStringTag,{value:"Module"}));exports.FormView=FormView,exports.FormView$1=i,exports.applyFileDropMixin=s;
|
|
3
|
-
//# sourceMappingURL=FormView-BzhM9nar.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";const t=require("./Dialog-v6xXatPb.js"),e=require("./WebApp-DlfbVd3B.js"),s=require("./WebSocketClient-Dbz1XNJA.js");class BaseChart extends e.View{constructor(t={}){super({...t,className:`chart-component ${t.className||""}`,tagName:"div"}),this.chart=null,this.chartType=t.chartType||"line",this.endpoint=t.endpoint||null,this.data=t.data||null,this.dataTransform=t.dataTransform||null,this.refreshInterval=t.refreshInterval||null,this.autoRefresh=!1!==t.autoRefresh,this.refreshTimer=null,this.websocketUrl=t.websocketUrl||null,this.websocket=null,this.websocketReconnect=!1!==t.websocketReconnect,this.width=t.width||null,this.height=t.height||null,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),void 0===t.maintainAspectRatio&&(t.maintainAspectRatio=!0),this.title=t.title||"",this.chartTitle=t.chartTitle||"",this.chartOptions={responsive:!0,maintainAspectRatio:t.maintainAspectRatio,interaction:{intersect:!1,mode:"index"},plugins:{legend:{display:!1!==t.showLegend,position:t.legendPosition||"top"},title:{display:!!this.chartTitle,text:this.chartTitle},tooltip:{enabled:!1!==t.showTooltips,backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",bodyColor:"#fff",borderColor:"rgba(255,255,255,0.1)",borderWidth:1}},...t.chartOptions},this.xAxis=t.xAxis||null,this.yAxis=t.yAxis||null,this.tooltipFormatters=t.tooltip||{},this.theme=t.theme||"light",this.colorScheme=t.colorScheme||"default",this.animations=!1!==t.animations,this.exportEnabled=!0===t.exportEnabled,this.exportFormats=t.exportFormats||["png","jpg","csv"],this.isLoading=!1,this.hasError=!1,this.lastFetch=null,this.dataPoints=0,this.canvas=null,this.chartJsCdn=t.chartJsCdn||"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js",this.dataFormatter=e.dataFormatter,this._essentialListeners=[]}get refreshEnabled(){return!(!this.endpoint&&!this.websocketUrl)}buildDefaultHeaderConfig(){return{titleHtml:this.title||"",chartTitle:this.chartTitle||"",showExport:!0===this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!0,controls:[]}}async getTemplate(){return'\n <div class="chart-container" data-theme="{{theme}}">\n <div class="chart-header mb-3">\n <div data-container="header"></div>\n <div class="chart-header-aux mt-2">\n <div data-container="header-aux"></div>\n </div>\n </div>\n\n <div class="chart-content position-relative" {{#contentStyle}}style="{{contentStyle}}"{{/contentStyle}}>\n <canvas class="chart-canvas" data-container="canvas"></canvas>\n\n \x3c!-- Loading overlay --\x3e\n <div class="chart-overlay d-none" data-loading>\n <div class="d-flex flex-column align-items-center">\n <div class="spinner-border text-primary mb-2" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <small class="text-muted">Loading chart data...</small>\n </div>\n </div>\n\n \x3c!-- Error overlay --\x3e\n <div class="chart-overlay d-none" data-error>\n <div class="alert alert-danger mb-0" role="alert">\n <div class="d-flex align-items-center">\n <i class="bi bi-exclamation-triangle me-2"></i>\n <div class="flex-grow-1">\n <strong>Error:</strong> <span class="error-message">Failed to load chart data</span>\n </div>\n <button class="btn btn-sm btn-outline-danger ms-2" data-action="retry-load">\n <i class="bi bi-arrow-clockwise"></i> Retry\n </button>\n </div>\n </div>\n </div>\n\n \x3c!-- No data overlay --\x3e\n <div class="chart-overlay d-none" data-no-data>\n <div class="text-center text-muted">\n <i class="bi bi-bar-chart display-4 mb-3 opacity-50"></i>\n <p class="mb-0">No data available</p>\n {{#refreshEnabled}}\n <button class="btn btn-sm btn-outline-secondary mt-2" data-action="refresh-chart">\n <i class="bi bi-arrow-clockwise"></i> Refresh\n </button>\n {{/refreshEnabled}}\n </div>\n </div>\n\n \x3c!-- WebSocket status indicator --\x3e\n <div class="position-absolute top-0 end-0 mt-2 me-2">\n <span class="badge bg-success websocket-status" style="display: none;" data-websocket-status>\n <i class="bi bi-wifi"></i> Live\n </span>\n </div>\n </div>\n\n <div class="chart-footer mt-2" style="display: none;">\n <div class="row">\n <div class="col">\n <small class="text-muted">\n <i class="bi bi-graph-up me-1"></i>\n <span class="data-points">0 data points</span>\n </small>\n </div>\n <div class="col text-end">\n <small class="text-muted refresh-info">\n Auto-refresh: <span class="refresh-status">Off</span>\n </small>\n </div>\n </div>\n </div>\n </div>\n '}async onInit(){await this.initializeChartJS();try{const t=this.headerConfig||(this.buildDefaultHeaderConfig?this.buildDefaultHeaderConfig():null);t&&(this.headerView=new ChartHeaderView({...t,containerId:"header"}),this.addChild(this.headerView))}catch(t){}}async onAfterRender(){this.canvas=this.element.querySelector(".chart-canvas"),this.titleElement=this.element.querySelector(".chart-title"),this.contentElement=this.element.querySelector(".chart-content"),this.footerElement=this.element.querySelector(".chart-footer"),this.loadingOverlay=this.element.querySelector("[data-loading]"),this.errorOverlay=this.element.querySelector("[data-error]"),this.noDataOverlay=this.element.querySelector("[data-no-data]"),this.websocketStatus=this.element.querySelector("[data-websocket-status]"),this.refreshBtn=this.element.querySelector(".refresh-btn"),this.themeToggle=this.element.querySelector(".theme-toggle"),this.applyTheme(),this.endpoint?(await this.fetchData(),await this.updateChart(this.data,!0),(this.height||this.width)&&this._updateChartDimensions()):this.data?(await this.updateChart(this.data,!0),(this.height||this.width)&&this._updateChartDimensions()):this.showNoData(),this.autoRefresh&&this.refreshInterval&&this.endpoint&&this.startAutoRefresh(),this.websocketUrl&&await this.connectWebSocket(),this.setupResizeObserver(),this.showFooter()}async initializeChartJS(){try{return void 0===window.Chart&&await this.loadChartJS(),!0}catch(t){return console.error("Failed to initialize Chart.js:",t),this.showError("Failed to initialize charting library"),!1}}async loadChartJS(){return new Promise((t,e)=>{const s=document.createElement("script");s.src=this.chartJsCdn,s.onload=()=>{console.log("Chart.js loaded successfully"),t()},s.onerror=()=>{e(new Error("Failed to load Chart.js"))},document.head.appendChild(s)})}async handleActionRefreshChart(){await this.fetchData()}async handleActionRetryLoad(){this.hideError(),await this.fetchData()}async handleActionExportChart(t,e){const s=e.getAttribute("data-format")||"png";this.exportChart(s)}async handleActionToggleTheme(){this.toggleTheme()}async handleActionSetChartType(t,e){const s=e.getAttribute("data-type");s&&this.setChartType&&await this.setChartType(s)}async fetchData(){if(this.endpoint){this.showLoading(),this.setRefreshButtonState(!0);try{const t=await fetch(this.endpoint,{method:"GET",headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!t.ok)throw new Error(`HTTP ${t.status}: ${t.statusText}`);let e=await t.json();this.dataTransform&&"function"==typeof this.dataTransform&&(e=this.dataTransform(e)),this.lastFetch=/* @__PURE__ */new Date,this.data=e,this.updateLastUpdatedTime();const s=this.getApp()?.events;s&&s.emit("chart:data-loaded",{chart:this,data:e,source:"http",endpoint:this.endpoint})}catch(t){console.error("Failed to fetch chart data:",t),this.showError(`Failed to load data: ${t.message}`),this.emit("chart:error",{chart:this,error:t,source:"http",endpoint:this.endpoint})}finally{this.hideLoading(),this.setRefreshButtonState(!1)}}}async connectWebSocket(){if(this.websocketUrl)try{this.websocket=new s.WebSocketClient({url:this.websocketUrl,autoReconnect:this.websocketReconnect,dataTransform:this.dataTransform,eventBus:this.getApp()?.events,debug:!1}),this.websocket.on("connected",()=>{this.showWebSocketStatus(!0),console.log("WebSocket connected for chart data")}),this.websocket.on("disconnected",()=>{this.showWebSocketStatus(!1),console.log("WebSocket disconnected")}),this.websocket.on("data",async t=>{await this.updateChart(t),this.updateLastUpdatedTime(),this.emit("chart:data-updated",{chart:this,data:t,source:"websocket"})}),this.websocket.on("error",t=>{console.error("WebSocket error:",t),this.showWebSocketStatus(!1,"error")}),await this.websocket.connect()}catch(t){console.error("Failed to connect WebSocket:",t),this.showWebSocketStatus(!1,"error")}}async updateChart(t,e=!1){if(!t)return void this.showNoData();if(this.data=t,!this.canvas||void 0===window.Chart)return;this.hideAllOverlays();const s=this.processChartData(t);e&&this.chart&&(this.chart.destroy(),this.chart=null),this.chart?(this.chart.data=s,this.chart.update("none")):await this.createChart(s),this.updateDataStats(s),(this.height||this.width)&&this._updateChartDimensions()}processChartData(t){let e={...t};const s=this.normalizeAxis(this.xAxis);return s&&s.formatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,s.formatter))),e}async createChart(t){if(!this.canvas||void 0===window.Chart)throw new Error("Chart.js not loaded or canvas not found");const e={type:this.chartType,data:t,options:this.buildChartOptions()};try{this.chart=new window.Chart(this.canvas,e),this.setupChartEventHandlers()}catch(s){throw console.error("Failed to create chart:",s),s}}buildChartOptions(){const t={...this.chartOptions};(this.width||this.height)&&(t.responsive=!0,t.maintainAspectRatio=!1);const e=this.normalizeAxis(this.xAxis),s=this.normalizeAxis(this.yAxis);return t.scales=t.scales||{},t.scales.x={type:this._detectAxisType(this.data,e,"x"),display:!0,title:{display:!!e.label,text:e.label||""},grid:{display:!0},ticks:{}},e.formatter&&(t.scales.x.ticks.callback=this._createFormatterCallback(e.formatter)),t.scales.y={type:this._detectAxisType(this.data,s,"y"),display:!0,beginAtZero:!1!==s.beginAtZero,title:{display:!!s.label,text:s.label||""},grid:{display:!0},ticks:{}},s.formatter&&(t.scales.y.ticks.callback=this._createFormatterCallback(s.formatter)),this.applyThemeToOptions(t),(this.tooltipFormatters.x||this.tooltipFormatters.y)&&(t.plugins=t.plugins||{},t.plugins.tooltip=t.plugins.tooltip||{},t.plugins.tooltip.callbacks=t.plugins.tooltip.callbacks||{},this.tooltipFormatters.x&&(t.plugins.tooltip.callbacks.title=t=>{const e=t[0]?.label;return e?this.dataFormatter.pipe(e,this.tooltipFormatters.x):e}),this.tooltipFormatters.y&&(t.plugins.tooltip.callbacks.label=t=>{const e=t.raw,s=this.dataFormatter.pipe(e,this.tooltipFormatters.y);return`${t.dataset.label}: ${s}`})),"function"==typeof this.applySubclassChartOptions&&this.applySubclassChartOptions(t),t}_createFormatterCallback(t){return t?e=>{try{return this.dataFormatter.pipe(e,t)}catch(s){return console.warn("Chart formatter error:",s),e}}:null}normalizeAxis(t){if(!t)return{};if("string"==typeof t)return{formatter:t};if("object"==typeof t){const{formatter:e,label:s,type:a,beginAtZero:i,...r}=t;return{formatter:e,label:s,type:a,beginAtZero:i,...r}}return{}}_detectAxisType(t,e,s="x"){if(e&&e.type)return e.type;if(e&&e.formatter){const t=e.formatter.toLowerCase();if(t.includes("date")||t.includes("time"))return"time"}if(t){if("x"===s&&t.labels&&t.labels.length>0){const e=t.labels[0];return"string"!=typeof e||/^\d+\.?\d*$/.test(e.trim())?e instanceof Date||"string"==typeof e&&!isNaN(Date.parse(e))?"time":"linear":"category"}if("y"===s&&t.datasets&&t.datasets.length>0){const e=t.datasets[0];if(e.data&&e.data.length>0){const t=e.data[0];return"number"!=typeof t&&isNaN(parseFloat(t))?"category":"linear"}}}return"x"===s?"category":"linear"}setupChartEventHandlers(){this.chart&&(this.chart.options.onClick=(t,e)=>{if(e.length>0){const t=e[0],s=t.datasetIndex,a=t.index,i=this.chart.data.datasets[s].data[a],r=this.chart.data.labels[a];this.emit("chart:point-clicked",{chart:this,datasetIndex:s,index:a,value:i,label:r,dataset:this.chart.data.datasets[s]})}},this.chart.options.onHover=(t,e)=>{this.canvas.style.cursor=e.length>0?"pointer":"default"})}applyTheme(){this.element.setAttribute("data-theme",this.theme),this.chart&&(this.chart.options=this.buildChartOptions(),this.chart.update("none"))}applyThemeToOptions(t){const e="dark"===this.theme;t.scales&&Object.keys(t.scales).forEach(s=>{const a=t.scales[s];a.grid=a.grid||{},a.ticks=a.ticks||{},a.grid.color=e?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.1)",a.ticks.color=e?"#e9ecef":"#495057"}),t.plugins?.legend&&(t.plugins.legend.labels=t.plugins.legend.labels||{},t.plugins.legend.labels.color=e?"#e9ecef":"#495057"),t.plugins?.title&&(t.plugins.title.color=e?"#ffffff":"#212529")}toggleTheme(){this.theme="light"===this.theme?"dark":"light",this.applyTheme(),this.emit("chart:theme-changed",{chart:this,theme:this.theme})}startAutoRefresh(){this.endpoint&&this.refreshInterval&&(this.stopAutoRefresh(),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval),this.updateRefreshStatus(!0))}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null),this.updateRefreshStatus(!1)}exportChart(t="png"){if(this.chart)try{if("csv"===t)this.exportCSV();else{const e=this.chart.toBase64Image("image/"+t,1),s=document.createElement("a");s.download=`chart-${Date.now()}.${t}`,s.href=e,s.click(),this.emit("chart:exported",{chart:this,format:t,filename:s.download})}}catch(e){console.error("Failed to export chart:",e),this.showError("Failed to export chart")}}exportCSV(){if(this.chart&&this.chart.data)try{const t=this.generateCSV(),e=new Blob([t],{type:"text/csv;charset=utf-8;"}),s=URL.createObjectURL(e),a=document.createElement("a");a.download=`chart-data-${Date.now()}.csv`,a.href=s,a.click(),URL.revokeObjectURL(s),this.emit("chart:exported",{chart:this,format:"csv",filename:a.download})}catch(t){console.error("Failed to export CSV:",t),this.showError("Failed to export CSV")}}generateCSV(){const t=this.chart.data,e=t.labels||[],s=t.datasets||[];let a="Label";return s.forEach(t=>{a+=","+(t.label||"Data")}),a+="\n",e.forEach((t,e)=>{a+=`"${t}"`,s.forEach(t=>{const s=t.data[e]||"";a+=","+s}),a+="\n"}),a}showLoading(){this.isLoading=!0,this.hideAllOverlays(),this.loadingOverlay?.classList.remove("d-none")}hideLoading(){this.isLoading=!1,this.loadingOverlay?.classList.add("d-none")}showError(t){this.hasError=!0,this.hideAllOverlays();const e=this.errorOverlay?.querySelector(".error-message");e&&(e.textContent=t),this.errorOverlay?.classList.remove("d-none")}hideError(){this.hasError=!1,this.errorOverlay?.classList.add("d-none")}showNoData(){this.hideAllOverlays(),this.noDataOverlay?.classList.remove("d-none")}hideAllOverlays(){this.loadingOverlay?.classList.add("d-none"),this.errorOverlay?.classList.add("d-none"),this.noDataOverlay?.classList.add("d-none")}showWebSocketStatus(t,e="connected"){this.websocketStatus&&(t?(this.websocketStatus.className="badge bg-success",this.websocketStatus.innerHTML='<i class="bi bi-wifi"></i> Live'):(this.websocketStatus.className="error"===e?"badge bg-danger":"badge bg-secondary",this.websocketStatus.innerHTML="error"===e?'<i class="bi bi-wifi-off"></i> Error':'<i class="bi bi-wifi-off"></i> Offline'),this.websocketStatus.style.display="inline-block")}setRefreshButtonState(t){if(!this.refreshBtn)return;const e=this.refreshBtn.querySelector("i");t?(this.refreshBtn.disabled=!0,e?.classList.add("spin")):(this.refreshBtn.disabled=!1,e?.classList.remove("spin"))}updateLastUpdatedTime(){const t=this.element.querySelector(".last-updated"),e=this.element.querySelector(".timestamp");t&&e&&(e.textContent=/* @__PURE__ */(new Date).toLocaleTimeString(),t.style.display="block")}updateRefreshStatus(t){const e=this.element.querySelector(".refresh-status");e&&(e.textContent=t?`Every ${this.refreshInterval/1e3}s`:"Off")}updateDataStats(t){let e=0;t.datasets&&(e=t.datasets.reduce((t,e)=>t+(e.data?e.data.length:0),0)),this.dataPoints=e;const s=this.element.querySelector(".data-points");s&&(s.textContent=`${e} data point${1!==e?"s":""}`)}showFooter(){this.footerElement&&(this.footerElement.style.display="block")}setupResizeObserver(){if(!window.ResizeObserver||!this.contentElement)return;const t=new ResizeObserver(()=>{this.chart&&this.chart.resize()});t.observe(this.contentElement),this._resizeObserver=t}async onBeforeDestroy(){this.stopAutoRefresh(),this.websocket&&(this.websocket.disconnect(),this.websocket=null),this.chart&&(this.chart.destroy(),this.chart=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._essentialListeners&&(this._essentialListeners.forEach(({el:t,type:e,fn:s})=>{t&&t.removeEventListener(e,s)}),this._essentialListeners=[]),this.emit("chart:destroyed",{chart:this})}setData(t){return this.data=t,this.updateChart(t)}setEndpoint(t){if(this.endpoint=t,t)return this.fetchData()}setWebSocketUrl(t){if(this.websocket&&this.websocket.disconnect(),this.websocketUrl=t,t)return this.connectWebSocket()}setWidth(t){this.width=t,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}setHeight(t){this.height=t,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}setDimensions(t,e){this.width=t,this.height=e,this.contentStyle=[this.width?`width: ${this.width}px;`:"",this.height?`height: ${this.height}px;`:""].filter(Boolean).join(" "),this.contentElement&&this._updateChartDimensions()}_updateChartDimensions(){this.chart&&(this.width||this.height?(this.chart.options.responsive=!0,this.chart.options.maintainAspectRatio=!1,this.width&&this.contentElement&&(this.contentElement.style.width=this.width?this.width+"px":""),this.height&&this.contentElement&&(this.contentElement.style.height=this.height?this.height+"px":"")):(this.chart.options.responsive=!0,this.chart.options.maintainAspectRatio=this.chartOptions.maintainAspectRatio),this.chart.resize())}resize(){this.chart&&this.chart.resize()}refresh(){return this.fetchData()}export(t="png"){return this.exportChart(t)}setTheme(t){this.theme=t,this.applyTheme()}getStats(){return{isLoading:this.isLoading,hasError:this.hasError,dataPoints:this.dataPoints,lastFetch:this.lastFetch,theme:this.theme,chartType:this.chartType,autoRefresh:!!this.refreshTimer,websocketConnected:this.websocket?.isConnected||!1}}}class ChartHeaderView extends e.View{constructor(t={}){super({...t,className:`mojo-chart-header ${t.className||""}`,tagName:"div"}),this.titleHtml=t.titleHtml||"",this.chartTitle=t.chartTitle||"",this.showExport=!0===t.showExport,this.showRefresh=!!t.showRefresh,this.showTheme=!1,this.showTheme=!0===t.showTheme,this.controls=Array.isArray(t.controls)?t.controls:[],this.controlsHtml=this._buildControlsHtml(this.controls)}async getTemplate(){return'\n <div class="d-flex justify-content-between align-items-center">\n <div class="chart-title-section">\n <h5 class="mb-2 chart-title">{{{titleHtml}}}</h5>\n <small class="text-muted last-updated" style="display: none;">\n Last updated: <span class="timestamp"></span>\n </small>\n </div>\n\n <div class="chart-controls">\n <div class="btn-toolbar" role="toolbar">\n {{{controlsHtml}}}\n\n <div class="btn-group btn-group-sm" role="group">\n\n {{#showTheme}}\n <button type="button" class="btn btn-outline-secondary theme-toggle" data-action="toggle-theme" title="Toggle Theme">\n <i class="bi bi-palette"></i>\n </button>\n {{/showTheme}}\n\n {{#showExport}}\n <div class="btn-group btn-group-sm" role="group">\n <button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" title="Export Chart">\n <i class="bi bi-download"></i>\n </button>\n <ul class="dropdown-menu">\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="png">\n <i class="bi bi-image"></i> PNG\n </a></li>\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="jpg">\n <i class="bi bi-image"></i> JPEG\n </a></li>\n <li><a class="dropdown-item" href="#" data-action="export-chart" data-format="csv">\n <i class="bi bi-file-earmark-spreadsheet"></i> CSV\n </a></li>\n </ul>\n </div>\n {{/showExport}}\n\n {{#showRefresh}}\n <button type="button" class="btn btn-outline-secondary refresh-btn" data-action="refresh-chart" title="Refresh Data">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n {{/showRefresh}}\n </div>\n </div>\n </div>\n </div>\n '}_buildControlsHtml(t){if(!Array.isArray(t)||0===t.length)return"";const e=[];return t.forEach(t=>{if(t&&t.type)switch(t.type){case"select":{const s=`form-select${"md"===t.size?"":" form-select-sm"} ${t.className||""}`.trim(),a=(t.options||[]).map(t=>`<option value="${this._escapeAttr(t.value)}"${t.selected?" selected":""}>${this._escapeHtml(t.label)}</option>`).join("");e.push(`\n <div class="btn-group btn-group-sm me-2" role="group">\n <select class="${s}" data-change-action="${this._escapeAttr(t.action||t.name||"select-changed")}" style="width: auto;">\n ${a}\n </select>\n </div>\n `);break}case"button":{const{variant:s="outline-secondary",size:a="sm"}=t,i=`btn btn-${s}${"md"===a?"":" btn-sm"} ${t.className||""}`.trim(),r=t.title?` title="${this._escapeAttr(t.title)}"`:"",h=this._buildDataAttrs(t.data);e.push(`\n <div class="btn-group btn-group-sm me-2" role="group">\n <button type="button" class="${i}" data-action="${this._escapeAttr(t.action||"button-action")}"${r}${h}>\n ${t.labelHtml||""}\n </button>\n </div>\n `);break}case"buttongroup":{const s=t.size||"sm",a=`btn-group btn-group-${s} me-2 ${t.className||""}`.trim(),i=(t.buttons||[]).map(t=>{const e=`btn btn-${t.variant||"outline-secondary"}${"md"===s?"":" btn-sm"} ${t.className||""}`.trim(),a=t.title?` title="${this._escapeAttr(t.title)}"`:"",i=this._buildDataAttrs(t.data);return`<button type="button" class="${e}" data-action="${this._escapeAttr(t.action||"button-action")}"${a}${i}>${t.labelHtml||""}</button>`}).join("");e.push(`\n <div class="${a}" role="group">\n ${i}\n </div>\n `);break}case"divider":e.push('<div class="vr mx-2"></div>');break;case"html":{const s=t.html||"";e.push(`<div class="me-2 d-inline-block">${s}</div>`);break}}}),e.join("\n")}_buildDataAttrs(t){return t&&"object"==typeof t?Object.entries(t).map(([t,e])=>` data-${this._kebabCase(String(t))}="${this._escapeAttr(String(e))}"`).join(""):""}_kebabCase(t){return t.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,"$1-$2").toLowerCase().replace(/[^a-z0-9\-]/g,"-").replace(/--+/g,"-").replace(/^-|-$/g,"")}_escapeAttr(t){return String(t).replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}_escapeHtml(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}}class SeriesChart extends BaseChart{constructor(t={}){super({...t,chartType:t.chartType||"line"}),this.showTypeSwitch=!0,void 0!==t.showTypeSwitch&&(this.showTypeSwitch=t.showTypeSwitch),this.orientation=t.orientation||"vertical",this.stacked=t.stacked||!1,this.stepped=t.stepped||!1,this.tension=t.tension||.4,this.fill=t.fill||!1,this.showRefreshButton=!1!==t.showRefreshButton,this.headerConfig||(this.headerConfig={titleHtml:this.title||"",chartTitle:this.chartTitle||"",showExport:this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!0,controls:[]}),this.series=t.series||[],this.xField=t.xField||"x",this.yField=t.yField||"y",this.colors=t.colors||["rgba(54, 162, 235, 0.8)","rgba(255, 99, 132, 0.8)","rgba(75, 192, 192, 0.8)","rgba(255, 206, 86, 0.8)","rgba(153, 102, 255, 0.8)","rgba(255, 159, 64, 0.8)","rgba(199, 199, 199, 0.8)","rgba(83, 102, 255, 0.8)"],this.tooltipFormatters=t.tooltip||{}}async getTemplate(){return await super.getTemplate()}async onInit(){this.showTypeSwitch&&this.headerConfig.controls.push({type:"buttongroup",size:"sm",buttons:[{action:"set-chart-type",labelHtml:'<i class="bi bi-graph-up"></i>',title:"Line",variant:"line"===this.chartType?"primary":"outline-primary",data:{type:"line"}},{action:"set-chart-type",labelHtml:'<i class="bi bi-bar-chart"></i>',title:"Bar",variant:"bar"===this.chartType?"primary":"outline-primary",data:{type:"bar"}}]}),await super.onInit()}async onActionSetChartType(t,e){t.stopPropagation();const s=e.getAttribute("data-type");s&&s!==this.chartType&&await this.setChartType(s)}async rebuildChart(){if(this.chart&&this.data){this.chart.destroy(),this.chart=null;const t=this.processChartData(this.data);await this.createChart(t)}}async setChartType(t){if(!["line","bar"].includes(t))throw new Error(`Unsupported chart type: ${t}`);const e=this.chartType;if(this.chartType=t,this.chart&&this.data){this.chart.destroy(),this.chart=null;const t=this.processChartData(this.data);await this.createChart(t)}this._updateTypeSwitcherButtons();const s=this.getApp()?.events;s&&s.emit("chart:type-changed",{chart:this,oldType:e,newType:this.chartType})}processChartData(t){if(!t)return t;let e;return e=Array.isArray(t)?this.processArrayData(t):t.labels&&t.datasets?this.processChartJSData(t):t.series?this.processSeriesData(t):t,this.applyFormattersToData(e)}processArrayData(t){const e=[],s=[];return t.forEach(t=>{const a=t[this.xField],i=t[this.yField];e.push(a),s.push(i)}),{labels:e,datasets:[{label:this.title||"Data",data:s,backgroundColor:this.colors[0].replace("0.8","0.6"),borderColor:this.colors[0],borderWidth:2,tension:"line"===this.chartType?this.tension:0,fill:"line"===this.chartType&&this.fill,stepped:"line"===this.chartType&&this.stepped}]}}processChartJSData(t){const e={...t};return e.datasets=e.datasets.map((t,e)=>({...t,backgroundColor:t.backgroundColor||this.colors[e%this.colors.length].replace("0.8","0.6"),borderColor:t.borderColor||this.colors[e%this.colors.length],borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill),stepped:"line"===this.chartType&&(t.stepped??this.stepped)})),e}processSeriesData(t){const e=t.labels||[],s=[];return t.series.forEach((t,e)=>{s.push({label:t.name||t.label||`Series ${e+1}`,data:t.data||[],backgroundColor:t.backgroundColor||this.colors[e%this.colors.length].replace("0.8","0.6"),borderColor:t.borderColor||this.colors[e%this.colors.length],borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill),stepped:"line"===this.chartType&&(t.stepped??this.stepped)})}),{labels:e,datasets:s}}applyFormattersToData(t){if(!t)return t;const e={...t},s=this.normalizeAxis?this.normalizeAxis(this.xAxis):{};return s.formatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,s.formatter))),e}applySubclassChartOptions(t){this.stacked&&"bar"===this.chartType&&t.scales&&(t.scales.x&&(t.scales.x.stacked=!0),t.scales.y&&(t.scales.y.stacked=!0)),"bar"===this.chartType&&"horizontal"===this.orientation&&(t.indexAxis="y"),t.interaction=t.interaction||{},t.interaction.intersect=!1,t.interaction.mode="line"===this.chartType?"index":"nearest",t.elements=t.elements||{},t.elements.line={...t.elements.line||{},tension:this.tension,borderWidth:2},t.elements.point={...t.elements.point||{},radius:"line"===this.chartType?4:0,hoverRadius:6,hitRadius:8},t.elements.bar={...t.elements.bar||{},borderWidth:1,borderSkipped:!1}}processAxisConfig(t){return t?"string"==typeof t?{formatter:t}:"object"==typeof t?{formatter:t.formatter,label:t.label,type:t.type,beginAtZero:t.beginAtZero,...t}:{}:{}}_updateTypeSwitcherButtons(){const t=this.element?.querySelectorAll('[data-action="set-chart-type"]');t&&0!==t.length&&t.forEach(t=>{const e=t.getAttribute("data-type")===this.chartType;t.classList.toggle("btn-primary",e),t.classList.toggle("btn-outline-primary",!e),t.classList.toggle("active",e)})}setOrientation(t){if(!["vertical","horizontal"].includes(t))throw new Error(`Invalid orientation: ${t}`);if(this.orientation=t,this.chart&&(this.chart.destroy(),this.chart=null,this.data)){const t=this.processChartData(this.data);this.createChart(t)}}setStacked(t){this.stacked=t,this.chart&&(this.chart.options.scales.x.stacked=t,this.chart.options.scales.y.stacked=t,this.chart.update())}addSeries(t){if(!this.data||!this.data.datasets)return;const e={label:t.label||t.name||`Series ${this.data.datasets.length+1}`,data:t.data||[],backgroundColor:t.backgroundColor||this.colors[this.data.datasets.length%this.colors.length].replace("0.8","0.6"),borderColor:t.borderColor||this.colors[this.data.datasets.length%this.colors.length],borderWidth:t.borderWidth||2,tension:"line"===this.chartType?t.tension??this.tension:0,fill:"line"===this.chartType&&(t.fill??this.fill)};this.data.datasets.push(e),this.chart&&(this.chart.data.datasets.push(e),this.chart.update());const s=this.getApp()?.events;s&&s.emit("chart:series-added",{chart:this,series:e})}removeSeries(t){if(!this.data||!this.data.datasets||t<0||t>=this.data.datasets.length)return;const e=this.data.datasets.splice(t,1)[0];this.chart&&(this.chart.data.datasets.splice(t,1),this.chart.update());const s=this.getApp()?.events;s&&s.emit("chart:series-removed",{chart:this,series:e,index:t})}static async showDialog(e={}){const{title:s="Chart Viewer",size:a="xl",...i}=e,r=new SeriesChart({...i,title:s}),h=new t.default({title:s,body:r,size:a,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Export PNG",action:"export",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}]});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(r)}),h.on("action:export",()=>{r.exportChart("png")}),h.on("action:close",()=>{h.hide()})})}}class PieChart extends BaseChart{constructor(t={}){super({...t,chartType:"pie"}),this.cutout=t.cutout||0,this.rotation=t.rotation||0,this.circumference=t.circumference||360,this.borderWidth=t.borderWidth||2,this.borderColor=t.borderColor||"#ffffff",this.hoverBorderWidth=t.hoverBorderWidth||3,this.showLabels=!1!==t.showLabels,this.labelPosition=t.labelPosition||"outside",this.labelFormatter=t.labelFormatter||null,this.valueFormatter=t.valueFormatter||null,this.labelField=t.labelField||"label",this.valueField=t.valueField||"value",this.colors=t.colors||["#FF6384","#36A2EB","#FFCE56","#4BC0C0","#9966FF","#FF9F40","#C9CBCF","#4BC0C0","#FF6384","#36A2EB"],this.animateRotate=!1!==t.animateRotate,this.animateScale=t.animateScale||!1,this.clickable=!1!==t.clickable,this.hoverable=!1!==t.hoverable,this.selectedSegment=null,this.highlightedSegments=/* @__PURE__ */new Set,this.valueFormatter=t.valueFormatter||null}processChartData(t){if(!t)return t;let e;return e=Array.isArray(t)?this.processArrayData(t):t.labels&&t.datasets?this.processChartJSData(t):"object"!=typeof t||t.labels?t:this.processObjectData(t),this.applyFormattersToData(e)}processArrayData(t){const e=[],s=[];return t.forEach(t=>{const a=t[this.labelField],i=t[this.valueField];void 0!==a&&void 0!==i&&(e.push(a),s.push(i))}),{labels:e,datasets:[{data:s,backgroundColor:this.generateColors(e.length),borderColor:this.borderColor,borderWidth:this.borderWidth,hoverBorderWidth:this.hoverBorderWidth}]}}processChartJSData(t){const e={...t};return e.datasets=e.datasets.map(t=>({...t,backgroundColor:t.backgroundColor||this.generateColors(e.labels.length),borderColor:t.borderColor||this.borderColor,borderWidth:t.borderWidth||this.borderWidth,hoverBorderWidth:t.hoverBorderWidth||this.hoverBorderWidth})),e}processObjectData(t){const e=Object.keys(t);return{labels:e,datasets:[{data:Object.values(t),backgroundColor:this.generateColors(e.length),borderColor:this.borderColor,borderWidth:this.borderWidth,hoverBorderWidth:this.hoverBorderWidth}]}}applyFormattersToData(t){if(!t)return t;const e={...t};return this.labelFormatter&&e.labels&&(e.labels=e.labels.map(t=>this.dataFormatter.pipe(t,this.labelFormatter))),e}generateColors(t){const e=[];for(let s=0;s<t;s++)e.push(this.colors[s%this.colors.length]);return e}buildChartOptions(){const t=super.buildChartOptions();return t.cutout=this.cutout,t.rotation=this.rotation,t.circumference=this.circumference,t.animation={animateRotate:this.animateRotate,animateScale:this.animateScale,duration:this.animations?1e3:0},t.plugins={...t.plugins,legend:{...t.plugins.legend,position:t.plugins.legend.position||"right",labels:{...t.plugins.legend.labels,usePointStyle:!0,padding:20,generateLabels:t=>{const e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((t,s)=>{const a=e.datasets[0],i=a.data[s],r=a.backgroundColor[s];return{text:`${t} (${(i/a.data.reduce((t,e)=>t+e,0)*100).toFixed(1)}%)`,fillStyle:r,strokeStyle:r,lineWidth:0,hidden:!1,index:s}}):[]}}},tooltip:{...t.plugins.tooltip,callbacks:{...t.plugins.tooltip.callbacks,label:t=>{const e=t.label||"",s=t.raw,a=(s/t.dataset.data.reduce((t,e)=>t+e,0)*100).toFixed(1);let i=s;return this.valueFormatter?i=this.dataFormatter.pipe(s,this.valueFormatter):this.tooltipFormatters&&this.tooltipFormatters.y&&(i=this.dataFormatter.pipe(s,this.tooltipFormatters.y)),`${e}: ${i} (${a}%)`}}}},delete t.scales,t}setupChartEventHandlers(){super.setupChartEventHandlers(),this.chart&&this.clickable&&(this.chart.options.onClick=(t,e)=>{if(e.length>0){const t=e[0].index,s=this.chart.data.datasets[0],a=this.chart.data.labels[t],i=s.data[t],r=(i/s.data.reduce((t,e)=>t+e,0)*100).toFixed(1);this.toggleSegmentSelection(t);const h=this.getApp()?.events;h&&h.emit("chart:segment-clicked",{chart:this,index:t,label:a,value:i,percentage:parseFloat(r),isSelected:this.selectedSegment===t})}},this.hoverable&&(this.chart.options.onHover=(t,e)=>{if(this.canvas.style.cursor=e.length>0?"pointer":"default",e.length>0){const t=e[0].index,s=this.getApp()?.events;s&&s.emit("chart:segment-hover",{chart:this,index:t,label:this.chart.data.labels[t],value:this.chart.data.datasets[0].data[t]})}}))}toggleSegmentSelection(t){this.selectedSegment===t?(this.selectedSegment=null,this.resetSegmentStyle(t)):(null!==this.selectedSegment&&this.resetSegmentStyle(this.selectedSegment),this.selectedSegment=t,this.highlightSegment(t))}highlightSegment(t){if(!this.chart)return;const e=this.chart.getDatasetMeta(0).data[t];e&&(e.outerRadius+=10,this.chart.update("none"))}resetSegmentStyle(t){if(!this.chart)return;const e=this.chart.getDatasetMeta(0).data[t];e&&(e.outerRadius-=10,this.chart.update("none"))}highlightSegments(t){Array.isArray(t)||(t=[t]),this.highlightedSegments.clear(),t.forEach(t=>{this.highlightedSegments.add(t),this.highlightSegment(t)})}clearHighlights(){this.highlightedSegments.forEach(t=>{this.resetSegmentStyle(t)}),this.highlightedSegments.clear(),null!==this.selectedSegment&&(this.resetSegmentStyle(this.selectedSegment),this.selectedSegment=null)}selectSegment(t){t>=0&&t<this.chart?.data?.labels?.length&&this.toggleSegmentSelection(t)}getSegmentData(t){if(!this.chart||!this.chart.data)return null;const e=this.chart.data.datasets[0],s=this.chart.data.labels[t],a=e.data[t],i=(a/e.data.reduce((t,e)=>t+e,0)*100).toFixed(1);return{index:t,label:s,value:a,percentage:parseFloat(i),color:e.backgroundColor[t],isSelected:this.selectedSegment===t}}getAllSegments(){return this.chart&&this.chart.data?this.chart.data.labels.map((t,e)=>this.getSegmentData(e)):[]}updateSegmentColor(t,e){if(!this.chart||!this.chart.data.datasets[0])return;this.chart.data.datasets[0].backgroundColor[t]=e,this.chart.update("none");const s=this.getApp()?.events;s&&s.emit("chart:segment-color-changed",{chart:this,index:t,color:e,segment:this.getSegmentData(t)})}addSegment(t,e,s=null){if(!this.chart||!this.chart.data)return;const a=this.chart.data.datasets[0],i=s||this.colors[this.chart.data.labels.length%this.colors.length];this.chart.data.labels.push(t),a.data.push(e),a.backgroundColor.push(i),this.chart.update();const r=this.getApp()?.events;r&&r.emit("chart:segment-added",{chart:this,label:t,value:e,color:i,index:this.chart.data.labels.length-1})}removeSegment(t){if(!this.chart||!this.chart.data||t<0||t>=this.chart.data.labels.length)return;const e=this.chart.data.datasets[0],s=this.chart.data.labels[t],a=e.data[t];this.chart.data.labels.splice(t,1),e.data.splice(t,1),e.backgroundColor.splice(t,1),this.selectedSegment===t?this.selectedSegment=null:this.selectedSegment>t&&this.selectedSegment--,this.chart.update();const i=this.getApp()?.events;i&&i.emit("chart:segment-removed",{chart:this,label:s,value:a,index:t,removedSegment:{label:s,value:a,index:t}})}applyThemeToOptions(t){super.applyThemeToOptions(t);const e="dark"===this.theme;this.borderColor=e?"#404449":"#ffffff"}static async showDialog(e={}){const{title:s="Pie Chart",size:a="lg",...i}=e,r=new PieChart({...i,title:s}),h=new t.default({title:s,body:r,size:a,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Export PNG",action:"export",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}]});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(r)}),h.on("action:export",()=>{r.exportChart("png")}),h.on("action:close",()=>{h.hide()})})}}class MiniChart extends e.View{constructor(t={}){super({className:"mini-chart",...t}),this.chartType=t.chartType||"line",this.data=t.data||[],this.width=t.width||"100%",this.height=t.height||30,this.maintainAspectRatio=t.maintainAspectRatio||!1,this.color=t.color||"rgba(54, 162, 235, 1)",this.fillColor=t.fillColor||"rgba(54, 162, 235, 0.1)",this.strokeWidth=t.strokeWidth||2,this.barGap=t.barGap||2,this.fill=!1!==t.fill,this.smoothing=t.smoothing||.3,this.padding=t.padding||2,this.minValue=t.minValue,this.maxValue=t.maxValue,this.showDots=t.showDots||!1,this.dotRadius=t.dotRadius||2,this.animate=!1!==t.animate,this.animationDuration=t.animationDuration||300,this.showTooltip=!1!==t.showTooltip,this.tooltipFormatter=t.tooltipFormatter||null,this.tooltipTemplate=t.tooltipTemplate||null,this.valueFormat=t.valueFormat||null,this.labelFormat=t.labelFormat||null,this.showCrosshair=!1!==t.showCrosshair,this.crosshairColor=t.crosshairColor||"rgba(0, 0, 0, 0.2)",this.crosshairWidth=t.crosshairWidth||1,this.showXAxis=t.showXAxis||!1,this.xAxisColor=t.xAxisColor||this.color,this.xAxisWidth=t.xAxisWidth||1,this.xAxisDashed=!1!==t.xAxisDashed,this.tooltip=null,this.crosshair=null,this.hoveredIndex=-1,this.dataFormatter=e.dataFormatter,this.labels=t.labels||null}getTemplate(){const t="number"==typeof this.width?`${this.width}px`:this.width,e="number"==typeof this.height?`${this.height}px`:this.height,s=this.maintainAspectRatio?"xMidYMid meet":"none";return`\n <div class="mini-chart-wrapper" style="position: relative; display: block; width: ${t}; height: ${e};">\n <svg\n class="mini-chart-svg"\n width="100%"\n height="100%"\n viewBox="0 0 100 ${this.height}"\n preserveAspectRatio="${s}"\n style="display: block;">\n </svg>\n ${this.showTooltip?'<div class="mini-chart-tooltip" style="display: none;"></div>':""}\n </div>\n `}async onAfterRender(){this.svg=this.element.querySelector(".mini-chart-svg"),this.tooltip=this.element.querySelector(".mini-chart-tooltip"),this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart(),this.showTooltip&&this.svg&&this.setupTooltip(),this.setupResizeObserver()}updateDimensions(){if(!this.svg)return;const t=this.svg.getBoundingClientRect();this.actualWidth=t.width||100,this.actualHeight=t.height||this.height,this.svg.setAttribute("viewBox",`0 0 ${this.actualWidth} ${this.actualHeight}`)}setupResizeObserver(){"undefined"!=typeof ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>{this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart()}),this.svg&&this.resizeObserver.observe(this.svg))}renderChart(){if(!this.svg||!this.data||0===this.data.length)return;this.svg.innerHTML="";const{min:t,max:e}=this.calculateBounds();if(this.showXAxis&&this.renderXAxis(t,e),"line"===this.chartType?this.renderLine(t,e):"bar"===this.chartType&&this.renderBar(t,e),this.showCrosshair){const t=this.getActualHeight();this.crosshair=this.createSVGElement("line",{x1:0,y1:0,x2:0,y2:t,stroke:this.crosshairColor,"stroke-width":this.crosshairWidth,"stroke-dasharray":"3,3",style:"display: none; pointer-events: none;"}),this.svg.appendChild(this.crosshair)}this.showTooltip&&this.tooltip&&this.setupTooltip(),this.animate&&this.applyAnimation()}renderXAxis(t,e){const s=this.getActualWidth(),a=this.getActualHeight();let i;if(t<=0&&e>=0){const s=e-t,r=(a-2*this.padding)/s;i=a-this.padding-(0-t)*r}else i=a-this.padding;const r=this.createSVGElement("line",{x1:this.padding,y1:i,x2:s-this.padding,y2:i,stroke:this.xAxisColor,"stroke-width":this.xAxisWidth,"stroke-dasharray":this.xAxisDashed?"2,2":"none","stroke-opacity":"0.5"});this.svg.appendChild(r)}calculateBounds(){const t=this.data.map(t=>"object"==typeof t?t.value:t);let e=void 0!==this.minValue?this.minValue:Math.min(...t),s=void 0!==this.maxValue?this.maxValue:Math.max(...t);return 0===s-e&&("bar"===this.chartType&&0===e?(e=0,s=1):(e-=1,s+=1)),{min:e,max:s}}getActualWidth(){return this.actualWidth||this.width||100}getActualHeight(){return this.actualHeight||this.height||30}renderLine(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),a=this.calculatePoints(s,t,e);if(this.fill){const t=this.createAreaPath(a),e=this.createSVGElement("path",{d:t,fill:this.fillColor,stroke:"none"});this.svg.appendChild(e)}const i=this.smoothing>0?this.createSmoothPath(a):this.createLinePath(a),r=this.createSVGElement("path",{d:i,fill:"none",stroke:this.color,"stroke-width":this.strokeWidth,"stroke-linecap":"round","stroke-linejoin":"round"});this.svg.appendChild(r),this.showDots&&a.forEach(t=>{const e=this.createSVGElement("circle",{cx:t.x,cy:t.y,r:this.dotRadius,fill:this.color});this.svg.appendChild(e)})}renderBar(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),a=this.calculatePoints(s,t,e),i=this.getActualWidth(),r=this.getActualHeight(),h=(i-2*this.padding-this.barGap*(s.length-1))/s.length;a.forEach((t,e)=>{const s=r-2*this.padding-t.y+this.padding,a=t.x-h/2,i=t.y,o=this.createSVGElement("rect",{x:a,y:i,width:h,height:s,fill:this.color,rx:1,"data-bar-index":e,class:"mini-chart-bar"});this.svg.appendChild(o)})}calculatePoints(t,e,s){const a=s-e,i=this.getActualWidth(),r=this.getActualHeight(),h=(i-2*this.padding)/(t.length-1||1),o=(r-2*this.padding)/a;return t.map((t,s)=>({x:this.padding+s*h,y:r-this.padding-(t-e)*o}))}createLinePath(t){if(0===t.length)return"";let e=`M ${t[0].x},${t[0].y}`;for(let s=1;s<t.length;s++)e+=` L ${t[s].x},${t[s].y}`;return e}createSmoothPath(t){if(t.length<2)return this.createLinePath(t);let e=`M ${t[0].x},${t[0].y}`;for(let s=0;s<t.length-1;s++){const a=t[s],i=t[s+1];e+=` C ${a.x+(i.x-a.x)*this.smoothing},${a.y} ${i.x-(i.x-a.x)*this.smoothing},${i.y} ${i.x},${i.y}`}return e}createAreaPath(t){if(0===t.length)return"";const e=this.smoothing>0?this.createSmoothPath(t):this.createLinePath(t),s=t[t.length-1],a=t[0],i=this.getActualHeight();return`${e} L ${s.x},${i-this.padding} L ${a.x},${i-this.padding} Z`}createSVGElement(t,e={}){const s=document.createElementNS("http://www.w3.org/2000/svg",t);return Object.entries(e).forEach(([t,e])=>{s.setAttribute(t,e)}),s}applyAnimation(){this.svg.querySelectorAll("path").forEach(t=>{const e=t.getTotalLength();t.style.strokeDasharray=e,t.style.strokeDashoffset=e,t.style.animation=`mini-chart-draw ${this.animationDuration}ms ease-out forwards`}),this.svg.querySelectorAll("rect").forEach((t,e)=>{t.style.transformOrigin="bottom",t.style.animation=`mini-chart-bar-grow ${this.animationDuration}ms ease-out ${20*e}ms forwards`,t.style.transform="scaleY(0)"})}setupTooltip(){if(!this.svg||!this.tooltip)return;const t=this.data.map(t=>"object"==typeof t?t.value:t),e=this.calculatePoints(t,...Object.values(this.calculateBounds())),s=this.getActualWidth(),a=this.getActualHeight(),i=s/t.length;e.forEach((t,e)=>{const s=this.createSVGElement("rect",{x:e*i,y:0,width:i,height:a,fill:"transparent",style:"cursor: pointer;"});s.addEventListener("mouseenter",t=>{this.showTooltipAtIndex(e,t)}),s.addEventListener("mousemove",t=>{this.updateTooltipPosition(t)}),s.addEventListener("mouseleave",()=>{this.hideTooltip()}),this.svg.appendChild(s)})}showTooltipAtIndex(t,e){if(!this.tooltip)return;this.hoveredIndex=t;const s="object"==typeof this.data[t]?this.data[t].value:this.data[t],a="object"==typeof this.data[t]?this.data[t].label:null,i=this.labels?this.labels[t]:a;let r;if(this.tooltipTemplate&&"function"==typeof this.tooltipTemplate)r=this.tooltipTemplate({value:s,label:i,index:t,data:this.data[t]});else{let e=s;e=this.valueFormat&&this.dataFormatter?this.dataFormatter.pipe(s,this.valueFormat):this.tooltipFormatter&&"function"==typeof this.tooltipFormatter?this.tooltipFormatter(s,t):"number"==typeof s?s.toLocaleString():s;let a=i;i&&this.labelFormat&&this.dataFormatter&&(a=this.dataFormatter.pipe(i,this.labelFormat)),r=`<strong>${e}</strong>`,a&&(r=`<div class="mini-chart-tooltip-label">${a}</div>${r}`)}if(this.tooltip.innerHTML=r,this.tooltip.style.display="block",this.updateTooltipPosition(e),"bar"===this.chartType&&this.highlightBar(t),this.crosshair&&this.showCrosshair){const e=this.getActualWidth()/this.data.length,s=t*e+e/2;this.crosshair.setAttribute("x1",s),this.crosshair.setAttribute("x2",s),this.crosshair.style.display="block"}}updateTooltipPosition(t){if(!this.tooltip||"none"===this.tooltip.style.display)return;const e=this.svg.getBoundingClientRect(),s=t.clientX-e.left,a=t.clientY-e.top;this.tooltip.style.left=`${s}px`,this.tooltip.style.top=a-10+"px",this.tooltip.style.transform="translate(-50%, -100%)"}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none",this.hoveredIndex=-1),"bar"===this.chartType&&this.unhighlightBars(),this.crosshair&&(this.crosshair.style.display="none")}highlightBar(t){if(!this.svg)return;this.unhighlightBars();const e=this.svg.querySelector(`rect.mini-chart-bar[data-bar-index="${t}"]`);e&&(e.style.opacity="0.7")}unhighlightBars(){this.svg&&this.svg.querySelectorAll("rect.mini-chart-bar").forEach(t=>{t.style.opacity="1"})}setData(t){this.data=t,this.svg&&this.renderChart()}setColor(t){this.color=t,this.svg&&this.renderChart()}setType(t){["line","bar"].includes(t)&&(this.chartType=t,this.svg&&this.renderChart())}resize(t,e){this.width=t,this.height=e,this.updateDimensions(),this.svg&&this.renderChart()}async onBeforeDestroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),await super.onBeforeDestroy()}}class MetricsMiniChart extends MiniChart{constructor(t={}){super(t),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||"24h",this.isLoading=!1,this.lastFetch=null,this.refreshInterval=t.refreshInterval,this.dateStart&&this.dateEnd||this.setQuickRange(this.defaultDateRange),this.slugs&&!Array.isArray(this.slugs)&&(this.slugs=[this.slugs])}async onAfterRender(){await super.onAfterRender(),!this.endpoint||this.data&&0!==this.data.length||await this.fetchData(),this.refreshInterval&&this.endpoint&&this.startAutoRefresh()}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.length>0&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0;try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const a=s.data.data;this.processMetricsData(a),this.lastFetch=/* @__PURE__ */new Date,await this.render(),this.emit("metrics:loaded",{chart:this,data:a,params:e})}catch(t){console.error("Failed to fetch metrics:",t),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1}}}processMetricsData(t){const{data:e,labels:s}=t;if(!e)return;const a=Object.keys(e);if(0===a.length)return;const i=e[a[0]].map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0);this.labels=s||null,this.setData(i)}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}startAutoRefresh(){this.refreshTimer&&clearInterval(this.refreshTimer),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval)}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null)}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=Array.isArray(t)?t:[t],this.fetchData()}refresh(){return this.fetchData()}async onBeforeDestroy(){this.stopAutoRefresh(),await super.onBeforeDestroy()}}class MetricsMiniChartWidget extends e.View{constructor(t={}){super({...t,tagName:"div",className:`metrics-mini-chart-widget ${t.className||""}`.trim()}),this.icon=t.icon||null,this.title=t.title||"",this.subtitle=t.subtitle||"",this.background=t.background||null,this.textColor=t.textColor||null,this.showTrending=!!t.showTrending,this.trendRange=t.trendRange??null,this.trendOffset=t.trendOffset??0,this.prevTrendOffset=t.prevTrendOffset??0,this.total=0,this.lastValue=0,this.prevValue=0,this.trendingPercent=0,this.trendingUp=null,this.hasTrending=!1,this.trendingClass="",this.trendingIcon="",this.trendingLabel="",this.chartOptions={endpoint:t.endpoint,account:t.account,granularity:t.granularity,slugs:t.slugs,category:t.category,dateStart:t.dateStart,dateEnd:t.dateEnd,defaultDateRange:t.defaultDateRange,refreshInterval:t.refreshInterval,chartType:t.chartType||"line",showTooltip:void 0===t.showTooltip||t.showTooltip,showXAxis:t.showXAxis||!1,height:t.height||80,width:t.chartWidth||t.width||"100%",color:t.color,fill:void 0===t.fill||t.fill,fillColor:t.fillColor,smoothing:t.smoothing??.3,strokeWidth:t.strokeWidth,barGap:t.barGap,valueFormat:t.valueFormat,labelFormat:t.labelFormat,tooltipFormatter:t.tooltipFormatter,tooltipTemplate:t.tooltipTemplate,showCrosshair:t.showCrosshair,crosshairColor:t.crosshairColor,crosshairWidth:t.crosshairWidth,xAxisColor:t.xAxisColor,xAxisWidth:t.xAxisWidth,xAxisDashed:t.xAxisDashed,padding:t.padding,minValue:t.minValue,maxValue:t.maxValue,showDots:t.showDots,dotRadius:t.dotRadius,animate:t.animate,animationDuration:t.animationDuration}}async onInit(){this.chart=new MetricsMiniChart({...this.chartOptions,containerId:"chart"}),this.addChild(this.chart),this.header=new e.View({containerId:"chart-header",title:this.title,icon:this.icon,template:`\n <div class="d-flex justify-content-between align-items-start mb-2">\n <div class="me-3">\n <h6 class="card-title mb-1" style="${this.textColor?`color: ${this.textColor}`:""}">${this.title}</h6>\n <div class="card-subtitle" style="${this.textColor?`color: ${this.textColor}`:""}">${this.subtitle}</div>\n {{#hasTrending}}\n <div class="small mt-1 fw-semibold {{trendingClass}}" style="${this.textColor?`color: ${this.textColor}`:""}">\n <i class="{{trendingIcon}} me-1"></i>{{trendingLabel}}\n </div>\n {{/hasTrending}}\n </div>\n ${this.icon?`<i class="${this.icon} fs-4 flex-shrink-0" aria-hidden="true" style="${this.textColor?`color: ${this.textColor}`:""}"></i>`:""}\n </div>`}),this.addChild(this.header),this.chart?.on&&this.chart.on("metrics:loaded",this.onChildMetricsLoaded,this),this.updateFromChartData({render:!1})}onChildMetricsLoaded(){this.updateFromChartData({render:!0})}updateFromChartData({render:t=!0}={}){const e=Array.isArray(this.chart?.data)?this.chart.data:null;if(!e||0===e.length)return this.total=0,this.hasTrending=!1,this.header.title=this.title,void(t&&this.render());const s=e.map(t=>{if("number"==typeof t)return t;if(t&&"number"==typeof t.value)return t.value;const e=parseFloat(t);return Number.isNaN(e)?0:e});this.header.title=this.title,this.header.total=s.reduce((t,e)=>t+e,0);const a=Math.max(0,parseInt(this.trendOffset||0,10)||0),i=Math.max(0,s.length-1-a);this.header.now_value=s[i];let r=!1,h=0,o=0;const n=this.trendRange&&this.trendRange>=2?Math.max(1,Math.floor(this.trendRange/2)):1;if(i>=0){const t=i,e=t-(n-1);let a,l;if(this.prevTrendOffset&&this.prevTrendOffset>0?(a=e-this.prevTrendOffset,l=t-this.prevTrendOffset):(l=e-1,a=l-(n-1)),e>=0&&a>=0){const i=(t,e,s)=>{let a=0;for(let i=e;i<=s;i++)a+=t[i]||0;return a};h=i(s,e,t),o=i(s,a,l),r=!0}}if(!r){const t=i-(this.prevTrendOffset&&this.prevTrendOffset>0?this.prevTrendOffset:1);t>=0&&(h=s[i],o=s[t],r=!0)}if(r){this.header.lastValue=h,this.header.prevValue=o;let t=0;t=0===o?h>0?100:0:(h-o)/Math.abs(o)*100,this.header.trendingPercent=t,this.header.trendingUp=t>=0,this.textColor?this.header.trendingClass="":this.header.trendingClass=this.header.trendingUp?"text-success":"text-danger",this.header.trendingIcon=this.header.trendingUp?"bi bi-arrow-up":"bi bi-arrow-down";const e=t>0?"+":"";this.header.trendingLabel=`${e}${t.toFixed(1)}%`,this.header.hasTrending=!0}else this.header.hasTrending=!1;t&&this.header.render()}get cardStyle(){const t=[];return this.background&&t.push(`background: ${this.background}`),this.textColor&&t.push(`color: ${this.textColor}`),t.push("border: 0"),t.join("; ")}async getTemplate(){return`\n <div class="card h-100 shadow-sm" style="${this.cardStyle}">\n <div class="card-body p-3">\n <div data-container="chart-header"></div>\n <div data-container="chart"></div>\n </div>\n </div>\n `}async onBeforeDestroy(){this.chart?.off&&this.chart.off("metrics:loaded",this.onChildMetricsLoaded,this),await super.onBeforeDestroy()}refresh(){this.chart&&(this.chart.account=this.account,this.chart.refresh())}}exports.BaseChart=BaseChart,exports.MetricsChart=class extends SeriesChart{constructor(t={}){super({...t,chartType:t.chartType||"line",title:t.title||"Metrics",colors:t.colors||["rgba(54, 162, 235, 0.8)","rgba(255, 99, 132, 0.8)","rgba(75, 192, 192, 0.8)","rgba(255, 206, 86, 0.8)","rgba(153, 102, 255, 0.8)","rgba(255, 159, 64, 0.8)","rgba(199, 199, 199, 0.8)","rgba(83, 102, 255, 0.8)"],yAxis:t.yAxis||{label:"Count",beginAtZero:!0},tooltip:t.tooltip||{y:"number"},width:t.width,height:t.height}),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||"24h",this.showGranularity=!1!==t.showGranularity,this.showDateRange=!1!==t.showDateRange,this.granularityOptions=t.granularityOptions||[{value:"minutes",label:"Minutes"},{value:"hours",label:"Hours"},{value:"days",label:"Days"},{value:"weeks",label:"Weeks"},{value:"months",label:"Months"}],this.quickRanges=t.quickRanges||[{value:"1h",label:"1H"},{value:"24h",label:"24H"},{value:"7d",label:"7D"},{value:"30d",label:"30D"}],this.availableMetrics=t.availableMetrics||[{value:"api_calls",label:"API Calls"},{value:"api_errors",label:"API Errors"},{value:"incident_evt",label:"System Events"},{value:"incidents",label:"Incidents"}],this.isLoading=!1,this.lastFetch=null,this.dateStart&&this.dateEnd||this.setQuickRange(this.defaultDateRange)}async onInit(){const t=[];this.showGranularity&&t.push({type:"select",name:"granularity",action:"granularity-changed",size:"sm",options:this.granularityOptions.map(t=>({value:t.value,label:t.label,selected:t.value===this.granularity}))}),this.showDateRange&&t.push({type:"button",action:"show-date-range-dialog",labelHtml:`<i class="bi bi-calendar-range me-1"></i>${this.formatDateRangeDisplay()}`,title:"Select Date Range",variant:"outline-secondary",size:"sm"}),this.headerConfig={titleHtml:this.title||"Metrics",chartTitle:this.chartTitle||"",showExport:!0===this.exportEnabled,showRefresh:this.refreshEnabled,showTheme:!1,controls:t},await super.onInit()}async onActionGranularityChanged(t,e){const s=e.value;s&&s!==this.granularity&&(this.granularity=s,await this.fetchData())}async onActionShowDateRangeDialog(){try{const e=await t.default.showForm({title:"Select Date Range",size:"md",fields:[{name:"dateRange",type:"daterange",label:"Date Range",startName:"dt_start",endName:"dt_end",startDate:this.formatDateTimeLocal(this.dateStart),endDate:this.formatDateTimeLocal(this.dateEnd),required:!0}],formConfig:{options:{submitButton:!1,resetButton:!1}}});if(e&&e.startDate&&e.endDate){this.dateStart=new Date(e.startDate),this.dateEnd=new Date(e.endDate);const t=this.element?.querySelector('[data-action="show-date-range-dialog"]');t&&(t.innerHTML=`<i class="bi bi-calendar-range me-1"></i>${this.formatDateRangeDisplay()}`),await this.fetchData()}}catch(e){console.error("Date range dialog error:",e)}}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0,this.showLoading();try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const a=s.data.data,i=this.processMetricsData(a);await this.setData(i),this.lastFetch=/* @__PURE__ */new Date,this.emit("metrics:data-loaded",{chart:this,data:a,params:e})}catch(t){console.error("Failed to fetch metrics data:",t),this.showError(`Failed to load metrics: ${t.message}`),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1,this.hideLoading()}}}processMetricsData(t){const{data:e,labels:s}=t,a=[];return Object.keys(e).forEach((t,s)=>{const i=e[t].map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0);a.push({label:this.formatMetricLabel(t),data:i,backgroundColor:this.colors[s%this.colors.length].replace("0.8","0.6"),borderColor:this.colors[s%this.colors.length],borderWidth:2,tension:"line"===this.chartType?.4:0,fill:!1,pointRadius:"line"===this.chartType?3:0,pointHoverRadius:5})}),{labels:s,datasets:a}}formatMetricLabel(t){return t.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}formatDateTimeLocal(t){return t?`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}T${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`:""}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=[...t],this.fetchData()}getStats(){return{...super.getStats(),lastFetch:this.lastFetch,granularity:this.granularity,slugs:[...this.slugs],dateRange:{start:this.dateStart,end:this.dateEnd}}}},exports.MetricsMiniChart=MetricsMiniChart,exports.MetricsMiniChartWidget=MetricsMiniChartWidget,exports.MiniChart=MiniChart,exports.PieChart=PieChart,exports.SeriesChart=SeriesChart;
|
|
2
|
-
//# sourceMappingURL=MetricsMiniChartWidget-D5P4K2VW.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("./WebApp-DlfbVd3B.js");class Page extends e.View{constructor(e={}){e.tagName=e.tagName||"main",e.className=e.className||"mojo-page";const t=e.pageName||"";t&&!e.id&&(e.id="page_"+t.toLowerCase().replace(/\s+/g,"_")),super(e),this.pageName=e.pageName||this.constructor.pageName||"",this.route=e.route||this.constructor.route||"",this.title=e.title||this.pageName||"",this.id||!this.constructor.pageName||e.pageName||(this.id="page_"+this.constructor.pageName.toLowerCase().replace(/\s+/g,"_")),this.pageIcon=e.icon||e.pageIcon||this.constructor.pageIcon||"bi bi-file-text",this.displayName=e.displayName||this.constructor.displayName||this.pageName||"",this.pageDescription=e.pageDescription||this.constructor.pageDescription||"",this.params={},this.query={},this.matched=!1,this.isActive=!1,this.pageOptions={title:e.title||this.pageName||"Untitled Page",description:e.description||"",requiresAuth:e.requiresAuth||!1,...e.pageOptions},this.savedState=null,console.log(`Page ${this.pageName} constructed with route: ${this.route}`)}async onParams(e={},t={}){this.params=e,this.query=t}canEnter(){if(this.options.permissions){const e=this.getApp().activeUser;if(!e||!e.hasPermission(this.options.permissions))return!1}return!(this.options.requiresGroup&&!this.getApp().activeGroup)}async onEnter(){this.isActive=!0,await this.onInitView(),this.savedState&&(this.restoreState(this.savedState),this.savedState=null),this.pageOptions&&this.pageOptions.title&&"undefined"!=typeof document&&(document.title=this.pageOptions.title),this.emit("activated",{page:this.getMetadata()}),console.log(`Page ${this.pageName} entered`)}async onExit(){this.savedState=this.captureState(),this.isActive=!1,this.emit("deactivated",{page:this.getMetadata()}),console.log(`Page ${this.pageName} exiting`)}getMetadata(){return{name:this.pageName,displayName:this.displayName||this.pageName,icon:this.pageIcon,description:this.pageDescription,route:this.route,isActive:this.isActive}}async onActionDefault(e){console.log(`Default action '${e}' triggered on page: ${this.pageName}`)}async makeActive(){this.getApp().showPage(this)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.page;this.getApp().showPage(s)}captureState(){return this.element?{scrollTop:this.element.scrollTop,formData:this.captureFormData(),custom:this.captureCustomState()}:null}restoreState(e){e&&this.element&&(this.element.scrollTop=e.scrollTop||0,this.restoreFormData(e.formData),e.custom&&this.restoreCustomState(e.custom))}captureFormData(){const e={};return this.element?(this.element.querySelectorAll("input, select, textarea").forEach(t=>{t.name&&("checkbox"===t.type?e[t.name]=t.checked:"radio"===t.type?t.checked&&(e[t.name]=t.value):e[t.name]=t.value)}),e):e}restoreFormData(e){e&&this.element&&Object.entries(e).forEach(([e,t])=>{const s=this.element.querySelector(`[name="${e}"]`);if(s)if("checkbox"===s.type)s.checked=t;else if("radio"===s.type){const s=this.element.querySelector(`[name="${e}"][value="${t}"]`);s&&(s.checked=!0)}else s.value=t})}captureCustomState(){return{}}restoreCustomState(e){}setMeta(e={}){if("undefined"!=typeof document){if(e.title&&(document.title=e.title,this.pageOptions.title=e.title),e.description){let t=document.querySelector('meta[name="description"]');t||(t=document.createElement("meta"),t.name="description",document.head.appendChild(t)),t.content=e.description,this.pageOptions.description=e.description}Object.entries(e).forEach(([e,t])=>{if("title"!==e&&"description"!==e){let s=document.querySelector(`meta[name="${e}"]`);s||(s=document.createElement("meta"),s.name=e,document.head.appendChild(s)),s.content=t}})}}showError(e){if(super.showError(e),this.element){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.parentNode.removeChild(t)},5e3)}}showSuccess(e){if(super.showSuccess(e),this.element){const t=document.createElement("div");t.className="alert alert-success 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.parentNode.removeChild(t)},3e3)}}async onBeforeRender(){await super.onBeforeRender(),this.setMeta({title:this.pageOptions.title,description:this.pageOptions.description})}async onAfterMount(){await super.onAfterMount(),"undefined"!=typeof document&&this.pageName&&document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}async onBeforeDestroy(){await super.onBeforeDestroy(),"undefined"!=typeof document&&this.pageName&&document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}navigate(e,t={},s={}){return this.app&&this.app.router?this.app.router.navigate(e,s):"undefined"!=typeof window&&window.MOJO?.router?window.MOJO.router.navigate(e,s):void console.error("No router available for navigation")}getRoute(){if(this.route){let e=this.route;return"string"==typeof e&&e.startsWith("/")&&(e=e.substring(1)),e}return this.pageName}syncUrl(e=!0){this.updateBrowserUrl(this.query,!1,!1)}updateBrowserUrl(e=null,t=!1,s=!1){this.getApp(),this.app.router.updateBrowserUrl(this.getRoute(),e,t,s)}static define(e){class DefinedPage extends Page{constructor(t={}){super({...e,...t})}}return DefinedPage.template=e.template,DefinedPage.pageName=e.pageName,DefinedPage.route=e.route,DefinedPage}}exports.Page=Page;
|
|
2
|
-
//# sourceMappingURL=Page-BQ7bila2.js.map
|