web-mojo 2.1.957 → 2.1.964

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +64 -65
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +2 -2
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +2 -2
  11. package/dist/chunks/ChatView-BunvEH18.js +2 -0
  12. package/dist/chunks/ChatView-BunvEH18.js.map +1 -0
  13. package/dist/chunks/{ChatView-CoCfp717.js → ChatView-XgMgUF-Y.js} +82 -56
  14. package/dist/chunks/ChatView-XgMgUF-Y.js.map +1 -0
  15. package/dist/chunks/{Dialog-C2mRUxga.js → Dialog-5iyCQwEP.js} +3 -3
  16. package/dist/chunks/{Dialog-C2mRUxga.js.map → Dialog-5iyCQwEP.js.map} +1 -1
  17. package/dist/chunks/{Dialog-Cl6MN8if.js → Dialog-CV-J2Ozg.js} +2 -2
  18. package/dist/chunks/{Dialog-Cl6MN8if.js.map → Dialog-CV-J2Ozg.js.map} +1 -1
  19. package/dist/chunks/{FormView-HWvIdFkB.js → FormView-B2MQEHGz.js} +708 -1
  20. package/dist/chunks/FormView-B2MQEHGz.js.map +1 -0
  21. package/dist/chunks/FormView-DSpbf4Si.js +3 -0
  22. package/dist/chunks/FormView-DSpbf4Si.js.map +1 -0
  23. package/dist/chunks/{MetricsMiniChartWidget-BkTEO87S.js → MetricsMiniChartWidget-BDINfoNd.js} +2 -2
  24. package/dist/chunks/{MetricsMiniChartWidget-BkTEO87S.js.map → MetricsMiniChartWidget-BDINfoNd.js.map} +1 -1
  25. package/dist/chunks/{MetricsMiniChartWidget-Y70IHFIe.js → MetricsMiniChartWidget-mX32QYXz.js} +2 -2
  26. package/dist/chunks/{MetricsMiniChartWidget-Y70IHFIe.js.map → MetricsMiniChartWidget-mX32QYXz.js.map} +1 -1
  27. package/dist/chunks/{PDFViewer-DkbYnnoV.js → PDFViewer-BBzpEsVz.js} +2 -2
  28. package/dist/chunks/{PDFViewer-DkbYnnoV.js.map → PDFViewer-BBzpEsVz.js.map} +1 -1
  29. package/dist/chunks/{PDFViewer-C0aMqGJL.js → PDFViewer-CjUb32i6.js} +2 -2
  30. package/dist/chunks/{PDFViewer-C0aMqGJL.js.map → PDFViewer-CjUb32i6.js.map} +1 -1
  31. package/dist/chunks/{TopNav-CiEW7hl-.js → TopNav-CP1lWFXW.js} +2 -2
  32. package/dist/chunks/{TopNav-CiEW7hl-.js.map → TopNav-CP1lWFXW.js.map} +1 -1
  33. package/dist/chunks/{TopNav-Bt3-5HMg.js → TopNav-Thmp-3pk.js} +2 -2
  34. package/dist/chunks/{TopNav-Bt3-5HMg.js.map → TopNav-Thmp-3pk.js.map} +1 -1
  35. package/dist/chunks/{WebApp-BTcA5UQh.js → WebApp-BGbtHcQv.js} +2 -2
  36. package/dist/chunks/{WebApp-BTcA5UQh.js.map → WebApp-BGbtHcQv.js.map} +1 -1
  37. package/dist/chunks/{WebApp-kVssBssy.js → WebApp-_HcsDVBh.js} +13 -13
  38. package/dist/chunks/{WebApp-kVssBssy.js.map → WebApp-_HcsDVBh.js.map} +1 -1
  39. package/dist/docit.cjs.js +1 -1
  40. package/dist/docit.es.js +3 -3
  41. package/dist/index.cjs.js +1 -1
  42. package/dist/index.cjs.js.map +1 -1
  43. package/dist/index.es.js +44 -43
  44. package/dist/lightbox.cjs.js +1 -1
  45. package/dist/lightbox.es.js +3 -3
  46. package/package.json +1 -1
  47. package/dist/chunks/ChatView-CoCfp717.js.map +0 -1
  48. package/dist/chunks/ChatView-uTNbBQfi.js +0 -2
  49. package/dist/chunks/ChatView-uTNbBQfi.js.map +0 -1
  50. package/dist/chunks/FormView-BSWaXDav.js +0 -3
  51. package/dist/chunks/FormView-BSWaXDav.js.map +0 -1
  52. package/dist/chunks/FormView-HWvIdFkB.js.map +0 -1
@@ -620,6 +620,11 @@ class FormBuilder {
620
620
  case "buttongroup":
621
621
  fieldHTML = this.renderButtonGroupField(field);
622
622
  break;
623
+ case "combo":
624
+ case "combobox":
625
+ case "autocomplete":
626
+ fieldHTML = this.renderComboField(field);
627
+ break;
623
628
  default:
624
629
  console.warn(`Unknown field type: ${type}`);
625
630
  fieldHTML = this.renderTextField(field);
@@ -1945,6 +1950,66 @@ class FormBuilder {
1945
1950
  }
1946
1951
  return `btn btn-${variant}`;
1947
1952
  }
1953
+ /**
1954
+ * Render combo input field (editable select/autocomplete)
1955
+ * @param {Object} field - Field configuration
1956
+ * @returns {string} Field HTML
1957
+ */
1958
+ renderComboField(field) {
1959
+ const {
1960
+ name,
1961
+ label,
1962
+ value = "",
1963
+ placeholder = "Select or type...",
1964
+ options = [],
1965
+ required = false,
1966
+ disabled = false,
1967
+ readonly = false,
1968
+ allowCustom = true,
1969
+ showDescription = true,
1970
+ minChars = 0,
1971
+ maxSuggestions = 10,
1972
+ help = field.helpText || field.help || ""
1973
+ } = field;
1974
+ const fieldId = this.getFieldId(name);
1975
+ const error = this.errors[name];
1976
+ const fieldValue = this.getFieldValue(name) ?? value;
1977
+ return `
1978
+ <div class="mojo-form-control">
1979
+ ${label ? `<label for="${fieldId}" class="${this.options.labelClass}">${this.escapeHtml(label)}${required ? '<span class="text-danger">*</span>' : ""}</label>` : ""}
1980
+ <div class="combo-input-placeholder"
1981
+ data-field-name="${name}"
1982
+ data-field-type="combo"
1983
+ data-field-config='${JSON.stringify({
1984
+ name,
1985
+ value: fieldValue,
1986
+ placeholder,
1987
+ options,
1988
+ allowCustom,
1989
+ showDescription,
1990
+ minChars,
1991
+ maxSuggestions,
1992
+ disabled,
1993
+ readonly,
1994
+ required
1995
+ })}'>
1996
+ <input type="text"
1997
+ id="${fieldId}"
1998
+ name="${name}_display"
1999
+ class="${this.options.inputClass}${error ? " is-invalid" : ""}"
2000
+ placeholder="${this.escapeHtml(placeholder)}"
2001
+ value="${this.escapeHtml(fieldValue)}"
2002
+ ${disabled ? "disabled" : ""}
2003
+ ${readonly ? "readonly" : ""}
2004
+ ${required ? "required" : ""}>
2005
+ <input type="hidden" name="${name}" value="${this.escapeHtml(fieldValue)}">
2006
+ <small class="form-text text-muted">This will be enhanced with ComboInput component</small>
2007
+ </div>
2008
+ ${help ? `<div class="${this.options.helpClass}">${this.escapeHtml(help)}</div>` : ""}
2009
+ ${error ? `<div class="${this.options.errorClass}">${this.escapeHtml(error)}</div>` : ""}
2010
+ </div>
2011
+ `;
2012
+ }
1948
2013
  /**
1949
2014
  * Generate select options automatically from numeric range or patterns
1950
2015
  * Supports multiple generation modes:
@@ -4681,6 +4746,610 @@ class DateRangePicker extends View {
4681
4746
  return new DateRangePicker(options);
4682
4747
  }
4683
4748
  }
4749
+ class ComboInput extends View {
4750
+ constructor(options = {}) {
4751
+ const {
4752
+ name,
4753
+ value = "",
4754
+ placeholder = "Select or type...",
4755
+ options: optionsList = [],
4756
+ allowCustom = true,
4757
+ showDescription = true,
4758
+ minChars = 0,
4759
+ // Minimum characters before showing suggestions
4760
+ maxSuggestions = 10,
4761
+ disabled = false,
4762
+ readonly = false,
4763
+ required = false,
4764
+ class: containerClass = "",
4765
+ inputClass = "form-control",
4766
+ onSelect = null,
4767
+ // Callback when option is selected
4768
+ onChange = null,
4769
+ // Callback when value changes
4770
+ ...viewOptions
4771
+ } = options;
4772
+ super({
4773
+ tagName: "div",
4774
+ className: `combo-input ${containerClass}`,
4775
+ ...viewOptions
4776
+ });
4777
+ this.name = name;
4778
+ this.placeholder = placeholder;
4779
+ this.options = this.normalizeOptions(optionsList);
4780
+ this.allowCustom = allowCustom;
4781
+ this.showDescription = showDescription;
4782
+ this.minChars = minChars;
4783
+ this.maxSuggestions = maxSuggestions;
4784
+ this.disabled = disabled;
4785
+ this.readonly = readonly;
4786
+ this.required = required;
4787
+ this.inputClass = inputClass;
4788
+ this.onSelectCallback = onSelect;
4789
+ this.onChangeCallback = onChange;
4790
+ this.currentValue = value;
4791
+ this.inputValue = this.getDisplayValue(value);
4792
+ this.filteredOptions = [];
4793
+ this.highlightedIndex = -1;
4794
+ this.isOpen = false;
4795
+ this.selectedOption = this.findOptionByValue(value);
4796
+ }
4797
+ /**
4798
+ * Normalize options to consistent format
4799
+ */
4800
+ normalizeOptions(options) {
4801
+ if (!Array.isArray(options)) return [];
4802
+ return options.map((opt) => {
4803
+ if (typeof opt === "string") {
4804
+ return { value: opt, label: opt };
4805
+ } else if (typeof opt === "object" && opt.value !== void 0) {
4806
+ return {
4807
+ value: opt.value,
4808
+ label: opt.label || String(opt.value),
4809
+ description: opt.description || opt.label || "",
4810
+ meta: opt.meta || {}
4811
+ };
4812
+ }
4813
+ return null;
4814
+ }).filter((opt) => opt !== null);
4815
+ }
4816
+ /**
4817
+ * Find option by value
4818
+ */
4819
+ findOptionByValue(value) {
4820
+ return this.options.find((opt) => opt.value === value) || null;
4821
+ }
4822
+ /**
4823
+ * Get display value for a given value
4824
+ */
4825
+ getDisplayValue(value) {
4826
+ const option = this.findOptionByValue(value);
4827
+ return option ? option.label : value;
4828
+ }
4829
+ /**
4830
+ * Render the combo input component
4831
+ */
4832
+ async renderTemplate() {
4833
+ return `
4834
+ <div class="combo-input-container position-relative">
4835
+ <div class="input-wrapper position-relative">
4836
+ ${this.renderInput()}
4837
+ ${this.renderDropdownToggle()}
4838
+ </div>
4839
+ ${this.renderHiddenInput()}
4840
+ ${this.renderDropdown()}
4841
+ </div>
4842
+ `;
4843
+ }
4844
+ /**
4845
+ * Render the input field
4846
+ */
4847
+ renderInput() {
4848
+ return `
4849
+ <input type="text"
4850
+ class="${this.inputClass} combo-input-field"
4851
+ placeholder="${this.escapeHtml(this.placeholder)}"
4852
+ value="${this.escapeHtml(this.inputValue)}"
4853
+ ${this.disabled ? "disabled" : ""}
4854
+ ${this.readonly ? "readonly" : ""}
4855
+ ${this.required ? "required" : ""}
4856
+ data-change-action="input-change"
4857
+ data-action="input-keydown"
4858
+ autocomplete="off"
4859
+ role="combobox"
4860
+ aria-expanded="${this.isOpen}"
4861
+ aria-autocomplete="list"
4862
+ aria-controls="combo-dropdown-${this.cid}">
4863
+ `;
4864
+ }
4865
+ /**
4866
+ * Render dropdown toggle button
4867
+ */
4868
+ renderDropdownToggle() {
4869
+ if (this.readonly || this.disabled) return "";
4870
+ return `
4871
+ <button type="button"
4872
+ class="btn btn-sm combo-toggle position-absolute top-50 end-0 translate-middle-y border-0"
4873
+ data-action="toggle-dropdown"
4874
+ tabindex="-1"
4875
+ aria-label="Toggle dropdown"
4876
+ style="padding: 0.25rem 0.5rem;">
4877
+ <i class="bi bi-chevron-down"></i>
4878
+ </button>
4879
+ `;
4880
+ }
4881
+ /**
4882
+ * Render hidden input for form submission
4883
+ */
4884
+ renderHiddenInput() {
4885
+ if (!this.name) return "";
4886
+ return `
4887
+ <input type="hidden"
4888
+ name="${this.name}"
4889
+ value="${this.escapeHtml(this.currentValue)}"
4890
+ class="combo-input-hidden">
4891
+ `;
4892
+ }
4893
+ /**
4894
+ * Render dropdown menu
4895
+ */
4896
+ renderDropdown() {
4897
+ return `
4898
+ <div id="combo-dropdown-${this.cid}"
4899
+ class="combo-dropdown dropdown-menu position-absolute w-100 ${this.isOpen ? "show" : ""}"
4900
+ role="listbox"
4901
+ style="max-height: 300px; overflow-y: auto; z-index: 1050;">
4902
+ ${this.renderDropdownContent()}
4903
+ </div>
4904
+ `;
4905
+ }
4906
+ /**
4907
+ * Render dropdown content based on filtered options
4908
+ */
4909
+ renderDropdownContent() {
4910
+ if (this.filteredOptions.length === 0) {
4911
+ return this.renderNoResults();
4912
+ }
4913
+ return this.filteredOptions.slice(0, this.maxSuggestions).map((option, index) => this.renderOption(option, index)).join("");
4914
+ }
4915
+ /**
4916
+ * Render a single option
4917
+ */
4918
+ renderOption(option, index) {
4919
+ const isHighlighted = index === this.highlightedIndex;
4920
+ const isSelected = option.value === this.currentValue;
4921
+ return `
4922
+ <div class="dropdown-item combo-option ${isHighlighted ? "active" : ""} ${isSelected ? "selected" : ""}"
4923
+ data-action="select-option"
4924
+ data-option-index="${index}"
4925
+ role="option"
4926
+ aria-selected="${isSelected}"
4927
+ style="cursor: pointer;">
4928
+ <div class="d-flex justify-content-between align-items-start">
4929
+ <div class="flex-grow-1">
4930
+ <div class="combo-option-label fw-semibold">${this.highlightMatch(option.label)}</div>
4931
+ ${this.showDescription && option.description ? `
4932
+ <div class="combo-option-description small text-muted">${this.escapeHtml(option.description)}</div>
4933
+ ` : ""}
4934
+ </div>
4935
+ ${isSelected ? '<i class="bi bi-check text-primary ms-2"></i>' : ""}
4936
+ </div>
4937
+ </div>
4938
+ `;
4939
+ }
4940
+ /**
4941
+ * Render no results message
4942
+ */
4943
+ renderNoResults() {
4944
+ if (this.allowCustom && this.inputValue.length >= this.minChars) {
4945
+ return `
4946
+ <div class="dropdown-item-text text-muted small">
4947
+ <i class="bi bi-info-circle me-1"></i>
4948
+ ${this.inputValue ? "No matches found. Press Enter to use custom value." : "Start typing to see suggestions..."}
4949
+ </div>
4950
+ `;
4951
+ }
4952
+ return `
4953
+ <div class="dropdown-item-text text-muted small">
4954
+ <i class="bi bi-search me-1"></i>
4955
+ No matching options found.
4956
+ </div>
4957
+ `;
4958
+ }
4959
+ /**
4960
+ * Highlight matching text in option label
4961
+ */
4962
+ highlightMatch(label) {
4963
+ if (!this.inputValue) return this.escapeHtml(label);
4964
+ const escaped = this.escapeHtml(label);
4965
+ const pattern = new RegExp(`(${this.escapeRegex(this.inputValue)})`, "gi");
4966
+ return escaped.replace(pattern, '<mark class="bg-warning bg-opacity-25">$1</mark>');
4967
+ }
4968
+ /**
4969
+ * Handle component initialization after render
4970
+ */
4971
+ async onAfterRender() {
4972
+ await super.onAfterRender();
4973
+ this.updateFilteredOptions();
4974
+ this.handleOutsideClick = (event) => {
4975
+ if (this.element && !this.element.contains(event.target)) {
4976
+ this.closeDropdown();
4977
+ }
4978
+ };
4979
+ document.addEventListener("click", this.handleOutsideClick);
4980
+ }
4981
+ // ========================================
4982
+ // EventDelegate Action Handlers
4983
+ // ========================================
4984
+ /**
4985
+ * Handle input changes (typing)
4986
+ */
4987
+ async onChangeInputChange(event, element) {
4988
+ this.inputValue = element.value;
4989
+ this.updateFilteredOptions();
4990
+ if (this.inputValue.length >= this.minChars) {
4991
+ this.openDropdown();
4992
+ } else {
4993
+ this.closeDropdown();
4994
+ }
4995
+ this.highlightedIndex = -1;
4996
+ await this.updateDropdownDisplay();
4997
+ }
4998
+ /**
4999
+ * Handle input keydown for navigation
5000
+ */
5001
+ async onActionInputKeydown(event, _element) {
5002
+ switch (event.key) {
5003
+ case "ArrowDown":
5004
+ event.preventDefault();
5005
+ if (!this.isOpen) {
5006
+ this.openDropdown();
5007
+ } else {
5008
+ this.highlightNext();
5009
+ }
5010
+ await this.updateDropdownDisplay();
5011
+ break;
5012
+ case "ArrowUp":
5013
+ event.preventDefault();
5014
+ if (this.isOpen) {
5015
+ this.highlightPrevious();
5016
+ await this.updateDropdownDisplay();
5017
+ }
5018
+ break;
5019
+ case "Enter":
5020
+ event.preventDefault();
5021
+ if (this.isOpen && this.highlightedIndex >= 0) {
5022
+ await this.selectHighlightedOption();
5023
+ } else if (this.allowCustom && this.inputValue) {
5024
+ await this.selectCustomValue(this.inputValue);
5025
+ }
5026
+ break;
5027
+ case "Escape":
5028
+ event.preventDefault();
5029
+ this.closeDropdown();
5030
+ const input = this.element.querySelector(".combo-input-field");
5031
+ if (input) {
5032
+ input.value = this.getDisplayValue(this.currentValue);
5033
+ this.inputValue = input.value;
5034
+ }
5035
+ break;
5036
+ case "Tab":
5037
+ if (this.isOpen) {
5038
+ this.closeDropdown();
5039
+ }
5040
+ break;
5041
+ }
5042
+ }
5043
+ /**
5044
+ * Handle toggle dropdown button click
5045
+ */
5046
+ async onActionToggleDropdown(event, _element) {
5047
+ event.preventDefault();
5048
+ event.stopPropagation();
5049
+ if (this.isOpen) {
5050
+ this.closeDropdown();
5051
+ } else {
5052
+ this.inputValue = "";
5053
+ const input = this.element.querySelector(".combo-input-field");
5054
+ if (input) {
5055
+ input.value = "";
5056
+ input.focus();
5057
+ }
5058
+ this.updateFilteredOptions();
5059
+ this.openDropdown();
5060
+ await this.updateDropdownDisplay();
5061
+ }
5062
+ }
5063
+ /**
5064
+ * Handle option selection
5065
+ */
5066
+ async onActionSelectOption(event, element) {
5067
+ event.preventDefault();
5068
+ event.stopPropagation();
5069
+ const index = parseInt(element.getAttribute("data-option-index"));
5070
+ if (index >= 0 && index < this.filteredOptions.length) {
5071
+ await this.selectOption(this.filteredOptions[index]);
5072
+ }
5073
+ }
5074
+ // ========================================
5075
+ // Dropdown Management
5076
+ // ========================================
5077
+ /**
5078
+ * Open dropdown
5079
+ */
5080
+ openDropdown() {
5081
+ this.isOpen = true;
5082
+ const dropdown = this.element?.querySelector(".combo-dropdown");
5083
+ if (dropdown) {
5084
+ dropdown.classList.add("show");
5085
+ }
5086
+ const input = this.element?.querySelector(".combo-input-field");
5087
+ if (input) {
5088
+ input.setAttribute("aria-expanded", "true");
5089
+ }
5090
+ }
5091
+ /**
5092
+ * Close dropdown
5093
+ */
5094
+ closeDropdown() {
5095
+ this.isOpen = false;
5096
+ this.highlightedIndex = -1;
5097
+ const dropdown = this.element?.querySelector(".combo-dropdown");
5098
+ if (dropdown) {
5099
+ dropdown.classList.remove("show");
5100
+ }
5101
+ const input = this.element?.querySelector(".combo-input-field");
5102
+ if (input) {
5103
+ input.setAttribute("aria-expanded", "false");
5104
+ }
5105
+ }
5106
+ /**
5107
+ * Update filtered options based on input
5108
+ */
5109
+ updateFilteredOptions() {
5110
+ const query = this.inputValue.toLowerCase().trim();
5111
+ if (!query) {
5112
+ this.filteredOptions = [...this.options];
5113
+ return;
5114
+ }
5115
+ this.filteredOptions = this.options.filter((option) => {
5116
+ const labelMatch = option.label.toLowerCase().includes(query);
5117
+ const valueMatch = String(option.value).toLowerCase().includes(query);
5118
+ const descMatch = option.description?.toLowerCase().includes(query);
5119
+ return labelMatch || valueMatch || descMatch;
5120
+ });
5121
+ this.filteredOptions.sort((a, b) => {
5122
+ const aLabelExact = a.label.toLowerCase() === query;
5123
+ const bLabelExact = b.label.toLowerCase() === query;
5124
+ if (aLabelExact && !bLabelExact) return -1;
5125
+ if (!aLabelExact && bLabelExact) return 1;
5126
+ const aLabelStarts = a.label.toLowerCase().startsWith(query);
5127
+ const bLabelStarts = b.label.toLowerCase().startsWith(query);
5128
+ if (aLabelStarts && !bLabelStarts) return -1;
5129
+ if (!aLabelStarts && bLabelStarts) return 1;
5130
+ return 0;
5131
+ });
5132
+ }
5133
+ /**
5134
+ * Update dropdown display
5135
+ */
5136
+ async updateDropdownDisplay() {
5137
+ const dropdown = this.element?.querySelector(".combo-dropdown");
5138
+ if (!dropdown) return;
5139
+ dropdown.innerHTML = this.renderDropdownContent();
5140
+ if (this.highlightedIndex >= 0) {
5141
+ const highlightedElement = dropdown.querySelector(".combo-option.active");
5142
+ if (highlightedElement) {
5143
+ highlightedElement.scrollIntoView({ block: "nearest" });
5144
+ }
5145
+ }
5146
+ }
5147
+ /**
5148
+ * Highlight next option
5149
+ */
5150
+ highlightNext() {
5151
+ if (this.filteredOptions.length === 0) return;
5152
+ this.highlightedIndex = (this.highlightedIndex + 1) % Math.min(this.filteredOptions.length, this.maxSuggestions);
5153
+ }
5154
+ /**
5155
+ * Highlight previous option
5156
+ */
5157
+ highlightPrevious() {
5158
+ if (this.filteredOptions.length === 0) return;
5159
+ this.highlightedIndex = this.highlightedIndex <= 0 ? Math.min(this.filteredOptions.length, this.maxSuggestions) - 1 : this.highlightedIndex - 1;
5160
+ }
5161
+ /**
5162
+ * Select highlighted option
5163
+ */
5164
+ async selectHighlightedOption() {
5165
+ if (this.highlightedIndex >= 0 && this.highlightedIndex < this.filteredOptions.length) {
5166
+ await this.selectOption(this.filteredOptions[this.highlightedIndex]);
5167
+ }
5168
+ }
5169
+ /**
5170
+ * Select an option
5171
+ */
5172
+ async selectOption(option) {
5173
+ this.currentValue = option.value;
5174
+ this.inputValue = option.label;
5175
+ this.selectedOption = option;
5176
+ const input = this.element?.querySelector(".combo-input-field");
5177
+ if (input) {
5178
+ input.value = option.label;
5179
+ }
5180
+ const hiddenInput = this.element?.querySelector(".combo-input-hidden");
5181
+ if (hiddenInput) {
5182
+ hiddenInput.value = option.value;
5183
+ }
5184
+ this.closeDropdown();
5185
+ this.emit("select", { option, value: option.value, meta: option.meta });
5186
+ this.emit("change", { value: option.value, option, meta: option.meta });
5187
+ if (typeof this.onSelectCallback === "function") {
5188
+ this.onSelectCallback(option);
5189
+ }
5190
+ if (typeof this.onChangeCallback === "function") {
5191
+ this.onChangeCallback(option.value);
5192
+ }
5193
+ }
5194
+ /**
5195
+ * Select custom value (not in options)
5196
+ */
5197
+ async selectCustomValue(value) {
5198
+ if (!this.allowCustom) return;
5199
+ this.currentValue = value;
5200
+ this.inputValue = value;
5201
+ this.selectedOption = null;
5202
+ const hiddenInput = this.element?.querySelector(".combo-input-hidden");
5203
+ if (hiddenInput) {
5204
+ hiddenInput.value = value;
5205
+ }
5206
+ this.closeDropdown();
5207
+ this.emit("custom", { value });
5208
+ this.emit("change", { value, custom: true });
5209
+ if (typeof this.onChangeCallback === "function") {
5210
+ this.onChangeCallback(value);
5211
+ }
5212
+ }
5213
+ // ========================================
5214
+ // Public API Methods
5215
+ // ========================================
5216
+ /**
5217
+ * Get current value
5218
+ */
5219
+ getValue() {
5220
+ return this.currentValue;
5221
+ }
5222
+ /**
5223
+ * Set value programmatically
5224
+ */
5225
+ async setValue(value) {
5226
+ this.currentValue = value;
5227
+ this.selectedOption = this.findOptionByValue(value);
5228
+ this.inputValue = this.getDisplayValue(value);
5229
+ const input = this.element?.querySelector(".combo-input-field");
5230
+ if (input) {
5231
+ input.value = this.inputValue;
5232
+ }
5233
+ const hiddenInput = this.element?.querySelector(".combo-input-hidden");
5234
+ if (hiddenInput) {
5235
+ hiddenInput.value = value;
5236
+ }
5237
+ this.updateFilteredOptions();
5238
+ }
5239
+ /**
5240
+ * Get selected option with metadata
5241
+ */
5242
+ getSelectedOption() {
5243
+ return this.selectedOption;
5244
+ }
5245
+ /**
5246
+ * Update options
5247
+ */
5248
+ async setOptions(options) {
5249
+ this.options = this.normalizeOptions(options);
5250
+ this.updateFilteredOptions();
5251
+ if (this.isOpen) {
5252
+ await this.updateDropdownDisplay();
5253
+ }
5254
+ }
5255
+ /**
5256
+ * Enable/disable the component
5257
+ */
5258
+ setEnabled(enabled) {
5259
+ this.disabled = !enabled;
5260
+ const input = this.element?.querySelector(".combo-input-field");
5261
+ if (input) {
5262
+ input.disabled = this.disabled;
5263
+ }
5264
+ const toggle = this.element?.querySelector(".combo-toggle");
5265
+ if (toggle) {
5266
+ toggle.disabled = this.disabled;
5267
+ }
5268
+ }
5269
+ /**
5270
+ * Set readonly state
5271
+ */
5272
+ setReadonly(readonly) {
5273
+ this.readonly = readonly;
5274
+ const input = this.element?.querySelector(".combo-input-field");
5275
+ if (input) {
5276
+ if (readonly) {
5277
+ input.setAttribute("readonly", "");
5278
+ } else {
5279
+ input.removeAttribute("readonly");
5280
+ }
5281
+ }
5282
+ }
5283
+ /**
5284
+ * Focus the input
5285
+ */
5286
+ focus() {
5287
+ const input = this.element?.querySelector(".combo-input-field");
5288
+ if (input) {
5289
+ input.focus();
5290
+ }
5291
+ }
5292
+ /**
5293
+ * Clear the input
5294
+ */
5295
+ async clear() {
5296
+ await this.setValue("");
5297
+ this.inputValue = "";
5298
+ const input = this.element?.querySelector(".combo-input-field");
5299
+ if (input) {
5300
+ input.value = "";
5301
+ }
5302
+ this.emit("clear");
5303
+ }
5304
+ // ========================================
5305
+ // Form Integration
5306
+ // ========================================
5307
+ /**
5308
+ * Get form value (for FormView integration)
5309
+ */
5310
+ getFormValue() {
5311
+ return this.currentValue;
5312
+ }
5313
+ /**
5314
+ * Set form value (for FormView integration)
5315
+ */
5316
+ async setFormValue(value) {
5317
+ await this.setValue(value);
5318
+ }
5319
+ // ========================================
5320
+ // Utility Methods
5321
+ // ========================================
5322
+ /**
5323
+ * Escape HTML to prevent XSS
5324
+ */
5325
+ escapeHtml(str) {
5326
+ if (str == null) return "";
5327
+ const div = document.createElement("div");
5328
+ div.textContent = String(str);
5329
+ return div.innerHTML;
5330
+ }
5331
+ /**
5332
+ * Escape regex special characters
5333
+ */
5334
+ escapeRegex(str) {
5335
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5336
+ }
5337
+ /**
5338
+ * Cleanup on destroy
5339
+ */
5340
+ async onBeforeDestroy() {
5341
+ if (this.handleOutsideClick) {
5342
+ document.removeEventListener("click", this.handleOutsideClick);
5343
+ }
5344
+ await super.onBeforeDestroy();
5345
+ }
5346
+ /**
5347
+ * Static factory method
5348
+ */
5349
+ static create(options = {}) {
5350
+ return new ComboInput(options);
5351
+ }
5352
+ }
4684
5353
  class FormView extends View {
4685
5354
  constructor(options = {}) {
4686
5355
  const {
@@ -4851,6 +5520,7 @@ class FormView extends View {
4851
5520
  this.initializeCollectionMultiSelects();
4852
5521
  this.initializeDatePickers();
4853
5522
  this.initializeDateRangePickers();
5523
+ this.initializeComboInputs();
4854
5524
  const componentContainers = this.element.querySelectorAll("[data-component]");
4855
5525
  componentContainers.forEach((container) => {
4856
5526
  container.getAttribute("data-component");
@@ -5051,6 +5721,43 @@ class FormView extends View {
5051
5721
  }
5052
5722
  });
5053
5723
  }
5724
+ /**
5725
+ * Initialize ComboInput components
5726
+ */
5727
+ initializeComboInputs() {
5728
+ const comboPlaceholders = this.element.querySelectorAll('[data-field-type="combo"]');
5729
+ comboPlaceholders.forEach((placeholder) => {
5730
+ try {
5731
+ const fieldName = placeholder.getAttribute("data-field-name");
5732
+ const configData = placeholder.getAttribute("data-field-config");
5733
+ const config = JSON.parse(configData);
5734
+ const comboInput = new ComboInput({
5735
+ ...config,
5736
+ containerId: null
5737
+ // We'll mount directly
5738
+ });
5739
+ let value = MOJOUtils.getContextData(this.data, fieldName);
5740
+ if (value) {
5741
+ comboInput.setValue(value);
5742
+ }
5743
+ comboInput.render(true, placeholder);
5744
+ this.customComponents.set(fieldName, comboInput);
5745
+ comboInput.on("change", (data) => {
5746
+ this.handleFieldChange(fieldName, data.value);
5747
+ });
5748
+ comboInput.on("select", (data) => {
5749
+ this.emit("field:select", {
5750
+ field: fieldName,
5751
+ value: data.value,
5752
+ option: data.option,
5753
+ meta: data.meta
5754
+ });
5755
+ });
5756
+ } catch (error) {
5757
+ console.error("ComboInput initialization failed:", error);
5758
+ }
5759
+ });
5760
+ }
5054
5761
  /**
5055
5762
  * Handle field changes from custom components
5056
5763
  */
@@ -6624,4 +7331,4 @@ export {
6624
7331
  applyFileDropMixin as a,
6625
7332
  FormView$1 as b
6626
7333
  };
6627
- //# sourceMappingURL=FormView-HWvIdFkB.js.map
7334
+ //# sourceMappingURL=FormView-B2MQEHGz.js.map