web-mojo 2.1.955 → 2.1.963
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 +84 -67
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.cjs.js.map +1 -1
- package/dist/auth.es.js +2 -2
- 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-BunvEH18.js +2 -0
- package/dist/chunks/ChatView-BunvEH18.js.map +1 -0
- package/dist/chunks/{ChatView-DLEStri1.js → ChatView-XgMgUF-Y.js} +99 -60
- package/dist/chunks/ChatView-XgMgUF-Y.js.map +1 -0
- package/dist/chunks/{Collection-YRfGoT73.js → Collection-DaTm-2LH.js} +2 -2
- package/dist/chunks/{Collection-YRfGoT73.js.map → Collection-DaTm-2LH.js.map} +1 -1
- package/dist/chunks/{ContextMenu-By2g3KYY.js → ContextMenu-BuEqfeZS.js} +3 -2
- package/dist/chunks/ContextMenu-BuEqfeZS.js.map +1 -0
- package/dist/chunks/ContextMenu-DcLhcYMp.js +3 -0
- package/dist/chunks/ContextMenu-DcLhcYMp.js.map +1 -0
- package/dist/chunks/{Dialog-C2mRUxga.js → Dialog-5iyCQwEP.js} +3 -3
- package/dist/chunks/{Dialog-C2mRUxga.js.map → Dialog-5iyCQwEP.js.map} +1 -1
- package/dist/chunks/{Dialog-Cl6MN8if.js → Dialog-CV-J2Ozg.js} +2 -2
- package/dist/chunks/{Dialog-Cl6MN8if.js.map → Dialog-CV-J2Ozg.js.map} +1 -1
- package/dist/chunks/{FormView-HWvIdFkB.js → FormView-B2MQEHGz.js} +708 -1
- package/dist/chunks/FormView-B2MQEHGz.js.map +1 -0
- package/dist/chunks/FormView-DSpbf4Si.js +3 -0
- package/dist/chunks/FormView-DSpbf4Si.js.map +1 -0
- package/dist/chunks/{ListView-BMNhd5-B.js → ListView-BrsQ26R6.js} +2 -2
- package/dist/chunks/{ListView-BMNhd5-B.js.map → ListView-BrsQ26R6.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-BkTEO87S.js → MetricsMiniChartWidget-BDINfoNd.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-BkTEO87S.js.map → MetricsMiniChartWidget-BDINfoNd.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-Y70IHFIe.js → MetricsMiniChartWidget-mX32QYXz.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-Y70IHFIe.js.map → MetricsMiniChartWidget-mX32QYXz.js.map} +1 -1
- package/dist/chunks/{PDFViewer-DkbYnnoV.js → PDFViewer-BBzpEsVz.js} +2 -2
- package/dist/chunks/{PDFViewer-DkbYnnoV.js.map → PDFViewer-BBzpEsVz.js.map} +1 -1
- package/dist/chunks/{PDFViewer-C0aMqGJL.js → PDFViewer-CjUb32i6.js} +2 -2
- package/dist/chunks/{PDFViewer-C0aMqGJL.js.map → PDFViewer-CjUb32i6.js.map} +1 -1
- package/dist/chunks/{TopNav-A7NQ4viq.js → TopNav-CP1lWFXW.js} +2 -2
- package/dist/chunks/{TopNav-A7NQ4viq.js.map → TopNav-CP1lWFXW.js.map} +1 -1
- package/dist/chunks/{TopNav-Dch6cZFa.js → TopNav-Thmp-3pk.js} +4 -4
- package/dist/chunks/{TopNav-Dch6cZFa.js.map → TopNav-Thmp-3pk.js.map} +1 -1
- package/dist/chunks/{WebApp-CaOPY_k7.js → WebApp-CwApyeWG.js} +2 -2
- package/dist/chunks/{WebApp-CaOPY_k7.js.map → WebApp-CwApyeWG.js.map} +1 -1
- package/dist/chunks/{WebApp-RHtJ4hFZ.js → WebApp-WREVA4Bx.js} +13 -13
- package/dist/chunks/{WebApp-RHtJ4hFZ.js.map → WebApp-WREVA4Bx.js.map} +1 -1
- package/dist/docit.cjs.js +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 +48 -47
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +3 -3
- package/dist/map.es.js +1 -1
- package/dist/timeline.es.js +2 -2
- package/package.json +1 -1
- package/dist/chunks/ChatView-CTtQHvRP.js +0 -2
- package/dist/chunks/ChatView-CTtQHvRP.js.map +0 -1
- package/dist/chunks/ChatView-DLEStri1.js.map +0 -1
- package/dist/chunks/ContextMenu-By2g3KYY.js.map +0 -1
- package/dist/chunks/ContextMenu-Cl0TRsIa.js +0 -3
- package/dist/chunks/ContextMenu-Cl0TRsIa.js.map +0 -1
- package/dist/chunks/FormView-BSWaXDav.js +0 -3
- package/dist/chunks/FormView-BSWaXDav.js.map +0 -1
- 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-
|
|
7334
|
+
//# sourceMappingURL=FormView-B2MQEHGz.js.map
|