web-mojo 2.2.7 → 2.2.9

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 (56) 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 +7509 -7428
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.es.js +1 -1
  7. package/dist/charts.cjs.js +1 -1
  8. package/dist/charts.es.js +3 -3
  9. package/dist/chunks/ChatView-BxIeRwBQ.js +2 -0
  10. package/dist/chunks/ChatView-BxIeRwBQ.js.map +1 -0
  11. package/dist/chunks/{ChatView-pFVev6ZD.js → ChatView-DfhhZKoN.js} +282 -96
  12. package/dist/chunks/ChatView-DfhhZKoN.js.map +1 -0
  13. package/dist/chunks/{Dialog-LFifi2fg.js → Dialog-Cu_Dx46k.js} +3 -3
  14. package/dist/chunks/{Dialog-LFifi2fg.js.map → Dialog-Cu_Dx46k.js.map} +1 -1
  15. package/dist/chunks/{Dialog-DPGOKfct.js → Dialog-DX5h2QA9.js} +2 -2
  16. package/dist/chunks/{Dialog-DPGOKfct.js.map → Dialog-DX5h2QA9.js.map} +1 -1
  17. package/dist/chunks/{FormView-Xb_l8AD5.js → FormView-B_90L1RY.js} +683 -27
  18. package/dist/chunks/FormView-B_90L1RY.js.map +1 -0
  19. package/dist/chunks/FormView-Bwofbd8S.js +3 -0
  20. package/dist/chunks/FormView-Bwofbd8S.js.map +1 -0
  21. package/dist/chunks/{MetricsMiniChartWidget-CzqYq7d2.js → MetricsMiniChartWidget-Cg5Hyx28.js} +113 -54
  22. package/dist/chunks/MetricsMiniChartWidget-Cg5Hyx28.js.map +1 -0
  23. package/dist/chunks/MetricsMiniChartWidget-DNIU3bL-.js +2 -0
  24. package/dist/chunks/MetricsMiniChartWidget-DNIU3bL-.js.map +1 -0
  25. package/dist/chunks/{PDFViewer-BCn-y_36.js → PDFViewer--jlqnuVw.js} +2 -2
  26. package/dist/chunks/{PDFViewer-BCn-y_36.js.map → PDFViewer--jlqnuVw.js.map} +1 -1
  27. package/dist/chunks/{PDFViewer-BeWHRAeX.js → PDFViewer-DSmi78S6.js} +2 -2
  28. package/dist/chunks/{PDFViewer-BeWHRAeX.js.map → PDFViewer-DSmi78S6.js.map} +1 -1
  29. package/dist/chunks/{TokenManager-D2pm6lM8.js → TokenManager-CEOPgnsw.js} +2 -2
  30. package/dist/chunks/{TokenManager-D2pm6lM8.js.map → TokenManager-CEOPgnsw.js.map} +1 -1
  31. package/dist/chunks/{TokenManager-DZwzcAzD.js → TokenManager-DiQfilqw.js} +2 -2
  32. package/dist/chunks/{TokenManager-DZwzcAzD.js.map → TokenManager-DiQfilqw.js.map} +1 -1
  33. package/dist/chunks/{version-CmZ8J2xW.js → version-BlWcQoar.js} +2 -2
  34. package/dist/chunks/{version-CmZ8J2xW.js.map → version-BlWcQoar.js.map} +1 -1
  35. package/dist/chunks/{version-aJXsYzYB.js → version-CHVtoVR0.js} +4 -4
  36. package/dist/chunks/{version-aJXsYzYB.js.map → version-CHVtoVR0.js.map} +1 -1
  37. package/dist/core.css +183 -0
  38. package/dist/css/web-mojo.css +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 +96 -92
  44. package/dist/index.es.js.map +1 -1
  45. package/dist/lightbox.cjs.js +1 -1
  46. package/dist/lightbox.es.js +4 -4
  47. package/package.json +1 -1
  48. package/dist/chunks/ChatView-B2iog5Hu.js +0 -2
  49. package/dist/chunks/ChatView-B2iog5Hu.js.map +0 -1
  50. package/dist/chunks/ChatView-pFVev6ZD.js.map +0 -1
  51. package/dist/chunks/FormView-Clc_mN0d.js +0 -3
  52. package/dist/chunks/FormView-Clc_mN0d.js.map +0 -1
  53. package/dist/chunks/FormView-Xb_l8AD5.js.map +0 -1
  54. package/dist/chunks/MetricsMiniChartWidget-C8RQnqrd.js +0 -2
  55. package/dist/chunks/MetricsMiniChartWidget-C8RQnqrd.js.map +0 -1
  56. package/dist/chunks/MetricsMiniChartWidget-CzqYq7d2.js.map +0 -1
@@ -652,7 +652,6 @@ class FormBuilder {
652
652
  }
653
653
  }
654
654
  if (!fieldHTML) {
655
- console.log("buildFieldHTML - Processing field type:", type, "for field:", field.name);
656
655
  switch (type) {
657
656
  case "text":
658
657
  fieldHTML = this.renderTextField(field);
@@ -687,6 +686,9 @@ class FormBuilder {
687
686
  case "select":
688
687
  fieldHTML = this.renderSelectField(field);
689
688
  break;
689
+ case "multiselect":
690
+ fieldHTML = this.renderMultiSelectField(field);
691
+ break;
690
692
  case "checkbox":
691
693
  fieldHTML = this.renderCheckboxField(field);
692
694
  break;
@@ -1189,6 +1191,59 @@ class FormBuilder {
1189
1191
  };
1190
1192
  return Mustache.render(this.templates.select, context);
1191
1193
  }
1194
+ /**
1195
+ * Render multiselect dropdown field
1196
+ * @param {Object} field - Field configuration
1197
+ * @returns {string} Field HTML
1198
+ */
1199
+ renderMultiSelectField(field) {
1200
+ const {
1201
+ name,
1202
+ label,
1203
+ options = [],
1204
+ value = [],
1205
+ required = false,
1206
+ disabled = false,
1207
+ maxHeight = 300,
1208
+ help = field.helpText || field.help || ""
1209
+ } = field;
1210
+ const placeholder = field.placeholder || field.placeHolder || "Select...";
1211
+ this.getFieldId(name);
1212
+ const error = this.errors[name];
1213
+ const fieldValue = field.value ?? this.getFieldValue(name) ?? value;
1214
+ return `
1215
+ <div class="mojo-form-control">
1216
+ ${label ? `<label class="${this.options.labelClass}">${this.escapeHtml(label)}${required ? '<span class="text-danger">*</span>' : ""}</label>` : ""}
1217
+ <div class="multiselect-placeholder"
1218
+ data-field-name="${name}"
1219
+ data-field-type="multiselect"
1220
+ data-field-config='${JSON.stringify({
1221
+ name,
1222
+ value: fieldValue,
1223
+ placeholder,
1224
+ maxHeight,
1225
+ disabled,
1226
+ required
1227
+ })}'>
1228
+ <input type="hidden" name="${name}" value="${this.escapeHtml(JSON.stringify(fieldValue))}">
1229
+ <select class="form-select${error ? " is-invalid" : ""}"
1230
+ multiple
1231
+ ${disabled ? "disabled" : ""}
1232
+ ${required ? "required" : ""}>
1233
+ ${options.map((opt) => {
1234
+ const optValue = typeof opt === "string" ? opt : opt.value;
1235
+ const optLabel = typeof opt === "string" ? opt : opt.label || opt.value;
1236
+ const selected = Array.isArray(fieldValue) && fieldValue.includes(optValue) ? "selected" : "";
1237
+ return `<option value="${this.escapeHtml(optValue)}" ${selected}>${this.escapeHtml(optLabel)}</option>`;
1238
+ }).join("")}
1239
+ </select>
1240
+ <small class="form-text text-muted">This will be enhanced with MultiSelectDropdown component</small>
1241
+ </div>
1242
+ ${help ? `<div class="${this.options.helpClass}">${this.escapeHtml(help)}</div>` : ""}
1243
+ ${error ? `<div class="${this.options.errorClass}">${this.escapeHtml(error)}</div>` : ""}
1244
+ </div>
1245
+ `;
1246
+ }
1192
1247
  /**
1193
1248
  * Render checkbox field
1194
1249
  * @param {Object} field - Field configuration
@@ -2113,50 +2168,38 @@ class FormBuilder {
2113
2168
  name,
2114
2169
  label,
2115
2170
  value = "",
2116
- placeholder = "Select or type...",
2117
- options = [],
2118
2171
  required = false,
2119
2172
  disabled = false,
2120
- readonly = false,
2121
- allowCustom = true,
2122
- showDescription = true,
2123
- minChars = 0,
2124
- maxSuggestions = 10,
2173
+ maxHeight = 300,
2125
2174
  help = field.helpText || field.help || ""
2126
2175
  } = field;
2127
- const fieldId = this.getFieldId(name);
2176
+ const placeholder = field.placeholder || field.placeHolder || "Type or select...";
2177
+ const allowCustom = field.allowCustom !== false;
2178
+ this.getFieldId(name);
2128
2179
  const error = this.errors[name];
2129
- const fieldValue = this.getFieldValue(name) ?? value;
2180
+ const fieldValue = field.value ?? this.getFieldValue(name) ?? value;
2130
2181
  return `
2131
2182
  <div class="mojo-form-control">
2132
- ${label ? `<label for="${fieldId}" class="${this.options.labelClass}">${this.escapeHtml(label)}${required ? '<span class="text-danger">*</span>' : ""}</label>` : ""}
2133
- <div class="combo-input-placeholder"
2183
+ ${label ? `<label class="${this.options.labelClass}">${this.escapeHtml(label)}${required ? '<span class="text-danger">*</span>' : ""}</label>` : ""}
2184
+ <div class="combobox-placeholder"
2134
2185
  data-field-name="${name}"
2135
- data-field-type="combo"
2186
+ data-field-type="combobox"
2136
2187
  data-field-config='${JSON.stringify({
2137
2188
  name,
2138
2189
  value: fieldValue,
2139
2190
  placeholder,
2140
- options,
2191
+ maxHeight,
2141
2192
  allowCustom,
2142
- showDescription,
2143
- minChars,
2144
- maxSuggestions,
2145
2193
  disabled,
2146
- readonly,
2147
2194
  required
2148
2195
  })}'>
2149
- <input type="text"
2150
- id="${fieldId}"
2151
- name="${name}_display"
2152
- class="${this.options.inputClass}${error ? " is-invalid" : ""}"
2153
- placeholder="${this.escapeHtml(placeholder)}"
2196
+ <input type="text"
2197
+ class="form-control${error ? " is-invalid" : ""}"
2154
2198
  value="${this.escapeHtml(fieldValue)}"
2199
+ placeholder="${this.escapeHtml(placeholder)}"
2155
2200
  ${disabled ? "disabled" : ""}
2156
- ${readonly ? "readonly" : ""}
2157
2201
  ${required ? "required" : ""}>
2158
- <input type="hidden" name="${name}" value="${this.escapeHtml(fieldValue)}">
2159
- <small class="form-text text-muted">This will be enhanced with ComboInput component</small>
2202
+ <small class="form-text text-muted">This will be enhanced with ComboBox component</small>
2160
2203
  </div>
2161
2204
  ${help ? `<div class="${this.options.helpClass}">${this.escapeHtml(help)}</div>` : ""}
2162
2205
  ${error ? `<div class="${this.options.errorClass}">${this.escapeHtml(error)}</div>` : ""}
@@ -4035,6 +4078,312 @@ class CollectionMultiSelectView extends View {
4035
4078
  this.setValue(value);
4036
4079
  }
4037
4080
  }
4081
+ class MultiSelectItemsView extends View {
4082
+ constructor(options = {}) {
4083
+ super({
4084
+ tagName: "div",
4085
+ className: "multiselect-items",
4086
+ template: `
4087
+ {{#items.length}}
4088
+ <div class="multiselect-list" style="max-height: {{maxHeight}}px; overflow-y: auto;">
4089
+ {{#items}}
4090
+ <div class="multiselect-item form-check px-3 py-2"
4091
+ data-action="toggle"
4092
+ data-value="{{value}}"
4093
+ data-index="{{index}}">
4094
+ <input type="checkbox"
4095
+ class="form-check-input"
4096
+ id="{{id}}"
4097
+ {{#selected}}checked{{/selected}}
4098
+ {{#disabled}}disabled{{/disabled}}>
4099
+ <label class="form-check-label w-100" for="{{id}}">
4100
+ {{label}}
4101
+ </label>
4102
+ </div>
4103
+ {{/items}}
4104
+ </div>
4105
+ <div class="multiselect-footer border-top p-2">
4106
+ <button type="button" class="btn btn-sm btn-primary w-100" data-action="close-dropdown">
4107
+ Done
4108
+ </button>
4109
+ </div>
4110
+ {{/items.length}}
4111
+
4112
+ {{^items.length}}
4113
+ <div class="multiselect-empty text-muted text-center py-3">
4114
+ <small>No options available</small>
4115
+ </div>
4116
+ {{/items.length}}
4117
+ `,
4118
+ ...options
4119
+ });
4120
+ this.items = options.items || [];
4121
+ this.maxHeight = options.maxHeight || 300;
4122
+ }
4123
+ /**
4124
+ * Handle item toggle
4125
+ */
4126
+ handleActionToggle(event, element) {
4127
+ const value = element.getAttribute("data-value");
4128
+ const index = parseInt(element.getAttribute("data-index"), 10);
4129
+ const item = this.items[index];
4130
+ if (!item || item.disabled) return;
4131
+ item.selected = !item.selected;
4132
+ const checkbox = element.querySelector('input[type="checkbox"]');
4133
+ if (checkbox) {
4134
+ checkbox.checked = item.selected;
4135
+ }
4136
+ this.emit("toggle", { value, index, selected: item.selected });
4137
+ }
4138
+ /**
4139
+ * Handle close dropdown button
4140
+ */
4141
+ handleActionCloseDropdown(event, element) {
4142
+ this.emit("close-dropdown");
4143
+ }
4144
+ /**
4145
+ * Get currently selected values
4146
+ */
4147
+ getValue() {
4148
+ return this.items.filter((item) => item.selected).map((item) => item.value);
4149
+ }
4150
+ /**
4151
+ * Set selected values
4152
+ */
4153
+ setValue(values) {
4154
+ const valueSet = new Set(Array.isArray(values) ? values : [values]);
4155
+ this.items.forEach((item) => {
4156
+ item.selected = valueSet.has(item.value);
4157
+ });
4158
+ this.render(false);
4159
+ }
4160
+ /**
4161
+ * Update items and re-render
4162
+ */
4163
+ updateItems(items) {
4164
+ this.items = items;
4165
+ this.render(false);
4166
+ }
4167
+ }
4168
+ class MultiSelectDropdown extends View {
4169
+ constructor(options = {}) {
4170
+ super({
4171
+ tagName: "div",
4172
+ className: "multiselect-dropdown",
4173
+ template: `
4174
+ <div class="mojo-form-control">
4175
+ {{#label}}
4176
+ <label class="form-label">
4177
+ {{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
4178
+ </label>
4179
+ {{/label}}
4180
+
4181
+ <div class="dropdown w-100">
4182
+ <button class="btn btn-outline-secondary dropdown-toggle w-100 text-start d-flex justify-content-between align-items-center"
4183
+ type="button"
4184
+ data-bs-toggle="dropdown"
4185
+ aria-expanded="false"
4186
+ {{#disabled}}disabled{{/disabled}}>
4187
+ <span class="multiselect-button-text">{{buttonText}}</span>
4188
+ <i class="bi bi-chevron-down"></i>
4189
+ </button>
4190
+ <div class="dropdown-menu w-100" data-bs-auto-close="outside" data-container="items"></div>
4191
+ </div>
4192
+
4193
+ {{#help}}
4194
+ <div class="form-text">{{help}}</div>
4195
+ {{/help}}
4196
+ {{#error}}
4197
+ <div class="invalid-feedback d-block">{{error}}</div>
4198
+ {{/error}}
4199
+ </div>
4200
+ `,
4201
+ ...options
4202
+ });
4203
+ this.name = options.name || "multiselect";
4204
+ this.label = options.label || "";
4205
+ this.help = options.help || "";
4206
+ this.error = options.error || "";
4207
+ this.required = options.required || false;
4208
+ this.disabled = options.disabled || false;
4209
+ this.placeholder = options.placeholder || options.placeHolder || "Select...";
4210
+ this.maxHeight = options.maxHeight || 300;
4211
+ this.showSelectedLabels = options.showSelectedLabels !== false;
4212
+ this.maxLabelsToShow = options.maxLabelsToShow || 3;
4213
+ this.options = options.options || [];
4214
+ this.selectedValues = Array.isArray(options.value) ? options.value : [];
4215
+ this.buttonText = this.computeButtonText();
4216
+ this.listView = null;
4217
+ }
4218
+ /**
4219
+ * Compute button text based on current selection
4220
+ */
4221
+ computeButtonText() {
4222
+ const count = this.selectedValues.length;
4223
+ if (count === 0) {
4224
+ return this.placeholder || "Select...";
4225
+ } else if (this.showSelectedLabels && count <= this.maxLabelsToShow) {
4226
+ const labels = this.selectedValues.map((value) => {
4227
+ const selectedOption = this.options.find((opt) => {
4228
+ const optValue = typeof opt === "string" ? opt : opt.value;
4229
+ return optValue === value;
4230
+ });
4231
+ return typeof selectedOption === "string" ? selectedOption : selectedOption?.label || selectedOption?.value || value;
4232
+ });
4233
+ return labels.join(", ");
4234
+ } else {
4235
+ return `${count} selected`;
4236
+ }
4237
+ }
4238
+ /**
4239
+ * Initialize child view after render
4240
+ */
4241
+ async onAfterRender() {
4242
+ await super.onAfterRender();
4243
+ this.createListView();
4244
+ }
4245
+ /**
4246
+ * Create and mount the items list view
4247
+ */
4248
+ createListView() {
4249
+ const container = this.element?.querySelector('[data-container="items"]');
4250
+ if (!container) return;
4251
+ const items = this.options.map((option, index) => {
4252
+ const value = typeof option === "string" ? option : option.value;
4253
+ const label = typeof option === "string" ? option : option.label || option.text || option.value;
4254
+ const disabled = typeof option === "object" ? option.disabled : false;
4255
+ return {
4256
+ id: `${this.name}_${index}`,
4257
+ value,
4258
+ label,
4259
+ index,
4260
+ selected: this.selectedValues.includes(value),
4261
+ disabled
4262
+ };
4263
+ });
4264
+ this.listView = new MultiSelectItemsView({
4265
+ items,
4266
+ maxHeight: this.maxHeight
4267
+ });
4268
+ this.listView.on("toggle", (data) => {
4269
+ this.handleToggle(data);
4270
+ });
4271
+ this.listView.on("close-dropdown", () => {
4272
+ this.closeDropdown();
4273
+ });
4274
+ this.listView.render(true, container);
4275
+ }
4276
+ /**
4277
+ * Close the dropdown programmatically
4278
+ */
4279
+ closeDropdown() {
4280
+ const dropdownButton = this.element?.querySelector(".dropdown-toggle");
4281
+ if (dropdownButton && window.bootstrap?.Dropdown) {
4282
+ const dropdownInstance = window.bootstrap.Dropdown.getInstance(dropdownButton);
4283
+ if (dropdownInstance) {
4284
+ dropdownInstance.hide();
4285
+ }
4286
+ }
4287
+ }
4288
+ /**
4289
+ * Handle item toggle
4290
+ */
4291
+ handleToggle(data) {
4292
+ const { value, selected } = data;
4293
+ if (selected) {
4294
+ if (!this.selectedValues.includes(value)) {
4295
+ this.selectedValues.push(value);
4296
+ }
4297
+ } else {
4298
+ this.selectedValues = this.selectedValues.filter((v) => v !== value);
4299
+ }
4300
+ this.updateButtonText();
4301
+ this.emit("change", {
4302
+ value: this.selectedValues,
4303
+ name: this.name
4304
+ });
4305
+ }
4306
+ /**
4307
+ * Update button text based on selection
4308
+ */
4309
+ updateButtonText() {
4310
+ const button = this.element?.querySelector(".multiselect-button-text");
4311
+ if (!button) return;
4312
+ const count = this.selectedValues.length;
4313
+ this.buttonText = this.computeButtonText();
4314
+ button.textContent = this.buttonText;
4315
+ if (count === 0) {
4316
+ button.classList.add("text-muted");
4317
+ } else {
4318
+ button.classList.remove("text-muted");
4319
+ }
4320
+ }
4321
+ /**
4322
+ * Get current selected values
4323
+ */
4324
+ getValue() {
4325
+ return this.selectedValues;
4326
+ }
4327
+ /**
4328
+ * Set selected values
4329
+ */
4330
+ setValue(values) {
4331
+ this.selectedValues = Array.isArray(values) ? values : values ? [values] : [];
4332
+ if (this.listView) {
4333
+ this.listView.setValue(this.selectedValues);
4334
+ }
4335
+ this.updateButtonText();
4336
+ }
4337
+ /**
4338
+ * Update options list
4339
+ */
4340
+ setOptions(options) {
4341
+ this.options = options;
4342
+ if (this.listView) {
4343
+ const items = this.options.map((option, index) => {
4344
+ const value = typeof option === "string" ? option : option.value;
4345
+ const label = typeof option === "string" ? option : option.label || option.text || option.value;
4346
+ const disabled = typeof option === "object" ? option.disabled : false;
4347
+ return {
4348
+ id: `${this.name}_${index}`,
4349
+ value,
4350
+ label,
4351
+ index,
4352
+ selected: this.selectedValues.includes(value),
4353
+ disabled
4354
+ };
4355
+ });
4356
+ this.listView.updateItems(items);
4357
+ }
4358
+ }
4359
+ /**
4360
+ * Clear all selections
4361
+ */
4362
+ clear() {
4363
+ this.setValue([]);
4364
+ }
4365
+ /**
4366
+ * Get form value (for form integration)
4367
+ */
4368
+ getFormValue() {
4369
+ return this.getValue();
4370
+ }
4371
+ /**
4372
+ * Set form value (for form integration)
4373
+ */
4374
+ setFormValue(value) {
4375
+ this.setValue(value);
4376
+ }
4377
+ /**
4378
+ * Cleanup child view on destroy
4379
+ */
4380
+ async onBeforeDestroy() {
4381
+ await super.onBeforeDestroy();
4382
+ if (this.listView) {
4383
+ this.listView.destroy();
4384
+ }
4385
+ }
4386
+ }
4038
4387
  class DatePicker extends View {
4039
4388
  constructor(options = {}) {
4040
4389
  const {
@@ -5773,6 +6122,240 @@ class ComboInput extends View {
5773
6122
  return new ComboInput(options);
5774
6123
  }
5775
6124
  }
6125
+ class ComboBox extends View {
6126
+ constructor(options = {}) {
6127
+ super(options);
6128
+ this.name = options.name || "combo";
6129
+ this.placeholder = options.placeholder || options.placeHolder || "Type or select...";
6130
+ this.value = options.value || "";
6131
+ this.options = options.options || [];
6132
+ this.allowCustom = options.allowCustom !== false;
6133
+ this.disabled = options.disabled || false;
6134
+ this.required = options.required || false;
6135
+ this.maxHeight = options.maxHeight || 300;
6136
+ this.filteredOptions = [...this.options];
6137
+ this.highlightedIndex = -1;
6138
+ this.isOpen = false;
6139
+ this.template = `
6140
+ <div class="combobox-container">
6141
+ <div class="input-group">
6142
+ <input type="text"
6143
+ class="form-control combobox-input"
6144
+ placeholder="{{placeholder}}"
6145
+ value="{{value}}"
6146
+ {{#disabled}}disabled{{/disabled}}
6147
+ {{#required}}required{{/required}}
6148
+ data-action="combobox-input"
6149
+ autocomplete="off">
6150
+ <button class="btn btn-outline-secondary combobox-toggle"
6151
+ type="button"
6152
+ data-action="combobox-toggle"
6153
+ {{#disabled}}disabled{{/disabled}}>
6154
+ <i class="bi bi-chevron-down"></i>
6155
+ </button>
6156
+ </div>
6157
+ <div class="dropdown-menu combobox-dropdown"
6158
+ style="max-height: {{maxHeight}}px; overflow-y: auto; width: 100%;">
6159
+ <div data-region="dropdown-items"></div>
6160
+ {{^allowCustom}}
6161
+ <div class="combobox-no-match dropdown-item text-muted" style="display: none;">
6162
+ No matches found
6163
+ </div>
6164
+ {{/allowCustom}}
6165
+ </div>
6166
+ </div>
6167
+ `;
6168
+ this.itemTemplate = `
6169
+ {{#items}}
6170
+ <button type="button"
6171
+ class="dropdown-item combobox-item {{#highlighted}}active{{/highlighted}}"
6172
+ data-action="select-item"
6173
+ data-value="{{value}}"
6174
+ data-index="{{index}}">
6175
+ {{label}}
6176
+ </button>
6177
+ {{/items}}
6178
+ `;
6179
+ }
6180
+ async onInit() {
6181
+ await super.onInit();
6182
+ }
6183
+ async onAfterRender() {
6184
+ await super.onAfterRender();
6185
+ this.input = this.element.querySelector(".combobox-input");
6186
+ this.dropdown = this.element.querySelector(".combobox-dropdown");
6187
+ this.dropdownItems = this.element.querySelector('[data-region="dropdown-items"]');
6188
+ this.noMatchDiv = this.element.querySelector(".combobox-no-match");
6189
+ if (this.value && this.input) {
6190
+ const option = this.options.find((opt) => opt.value === this.value);
6191
+ if (option) {
6192
+ this.input.value = option.label || option.value;
6193
+ } else if (this.allowCustom) {
6194
+ this.input.value = this.value;
6195
+ }
6196
+ }
6197
+ this.renderItems();
6198
+ this.setupEventListeners();
6199
+ }
6200
+ setupEventListeners() {
6201
+ this.input.addEventListener("focus", () => this.openDropdown());
6202
+ this.input.addEventListener("input", (e) => this.handleInput(e));
6203
+ this.input.addEventListener("keydown", (e) => this.handleKeydown(e));
6204
+ document.addEventListener("click", (e) => {
6205
+ if (!this.element.contains(e.target)) {
6206
+ this.closeDropdown();
6207
+ }
6208
+ });
6209
+ }
6210
+ handleInput(event) {
6211
+ const searchText = event.target.value.toLowerCase();
6212
+ this.filteredOptions = this.options.filter((opt) => {
6213
+ const label = opt.label || opt.value;
6214
+ return label.toLowerCase().includes(searchText);
6215
+ });
6216
+ this.highlightedIndex = -1;
6217
+ this.renderItems();
6218
+ this.openDropdown();
6219
+ if (!this.allowCustom && this.noMatchDiv) {
6220
+ this.noMatchDiv.style.display = this.filteredOptions.length === 0 ? "block" : "none";
6221
+ }
6222
+ this.value = event.target.value;
6223
+ this.emit("change", { value: this.value });
6224
+ }
6225
+ handleKeydown(event) {
6226
+ if (!this.isOpen && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
6227
+ this.openDropdown();
6228
+ event.preventDefault();
6229
+ return;
6230
+ }
6231
+ if (!this.isOpen) return;
6232
+ switch (event.key) {
6233
+ case "ArrowDown":
6234
+ event.preventDefault();
6235
+ this.highlightedIndex = Math.min(this.highlightedIndex + 1, this.filteredOptions.length - 1);
6236
+ this.renderItems();
6237
+ this.scrollToHighlighted();
6238
+ break;
6239
+ case "ArrowUp":
6240
+ event.preventDefault();
6241
+ this.highlightedIndex = Math.max(this.highlightedIndex - 1, -1);
6242
+ this.renderItems();
6243
+ this.scrollToHighlighted();
6244
+ break;
6245
+ case "Enter":
6246
+ event.preventDefault();
6247
+ if (this.highlightedIndex >= 0) {
6248
+ this.selectItem(this.filteredOptions[this.highlightedIndex]);
6249
+ }
6250
+ break;
6251
+ case "Escape":
6252
+ event.preventDefault();
6253
+ this.closeDropdown();
6254
+ break;
6255
+ case "Tab":
6256
+ this.closeDropdown();
6257
+ break;
6258
+ }
6259
+ }
6260
+ scrollToHighlighted() {
6261
+ if (this.highlightedIndex < 0) return;
6262
+ const items = this.dropdownItems.querySelectorAll(".combobox-item");
6263
+ const highlightedItem = items[this.highlightedIndex];
6264
+ if (highlightedItem) {
6265
+ highlightedItem.scrollIntoView({ block: "nearest" });
6266
+ }
6267
+ }
6268
+ openDropdown() {
6269
+ if (this.disabled || this.isOpen) return;
6270
+ this.isOpen = true;
6271
+ this.dropdown.classList.add("show");
6272
+ if (this.input.value === "") {
6273
+ this.filteredOptions = [...this.options];
6274
+ this.renderItems();
6275
+ }
6276
+ }
6277
+ closeDropdown() {
6278
+ if (!this.isOpen) return;
6279
+ this.isOpen = false;
6280
+ this.dropdown.classList.remove("show");
6281
+ this.highlightedIndex = -1;
6282
+ if (!this.allowCustom) {
6283
+ const validOption = this.options.find(
6284
+ (opt) => opt.value === this.input.value || opt.label === this.input.value
6285
+ );
6286
+ if (!validOption && this.input.value !== "") {
6287
+ this.input.value = this.value;
6288
+ }
6289
+ }
6290
+ }
6291
+ selectItem(option) {
6292
+ const value = option.value;
6293
+ const label = option.label || option.value;
6294
+ this.input.value = label;
6295
+ this.value = value;
6296
+ this.closeDropdown();
6297
+ this.filteredOptions = [...this.options];
6298
+ this.highlightedIndex = -1;
6299
+ this.emit("change", { value: this.value, label });
6300
+ }
6301
+ renderItems() {
6302
+ const items = this.filteredOptions.map((opt, index) => ({
6303
+ value: opt.value,
6304
+ label: opt.label || opt.value,
6305
+ index,
6306
+ highlighted: index === this.highlightedIndex
6307
+ }));
6308
+ const html = Mustache.render(this.itemTemplate, { items });
6309
+ this.dropdownItems.innerHTML = html;
6310
+ }
6311
+ // Action handlers
6312
+ async onActionComboboxInput(event, element) {
6313
+ }
6314
+ async onActionComboboxToggle(event, element) {
6315
+ if (this.isOpen) {
6316
+ this.closeDropdown();
6317
+ } else {
6318
+ this.input.focus();
6319
+ this.openDropdown();
6320
+ }
6321
+ }
6322
+ async onActionSelectItem(event, element) {
6323
+ const value = element.getAttribute("data-value");
6324
+ const option = this.options.find((opt) => opt.value === value);
6325
+ if (option) {
6326
+ this.selectItem(option);
6327
+ }
6328
+ }
6329
+ // Form integration methods
6330
+ getValue() {
6331
+ return this.value;
6332
+ }
6333
+ setValue(value) {
6334
+ this.value = value;
6335
+ if (!this.input) {
6336
+ return;
6337
+ }
6338
+ const option = this.options.find((opt) => opt.value === value);
6339
+ if (option) {
6340
+ this.input.value = option.label || option.value;
6341
+ } else if (this.allowCustom) {
6342
+ this.input.value = value;
6343
+ }
6344
+ }
6345
+ setFormValue(value) {
6346
+ this.setValue(value);
6347
+ }
6348
+ getTemplateData() {
6349
+ return {
6350
+ placeholder: this.placeholder,
6351
+ value: this.input ? this.input.value : this.value,
6352
+ disabled: this.disabled,
6353
+ required: this.required,
6354
+ maxHeight: this.maxHeight,
6355
+ allowCustom: this.allowCustom
6356
+ };
6357
+ }
6358
+ }
5776
6359
  class FormView extends View {
5777
6360
  constructor(options = {}) {
5778
6361
  const {
@@ -5929,6 +6512,8 @@ class FormView extends View {
5929
6512
  this.initializeImageFields();
5930
6513
  this.initializeCustomComponents();
5931
6514
  this.initializeTagInputs();
6515
+ this.initializeMultiSelectDropdowns();
6516
+ this.initializeComboBoxes();
5932
6517
  this.initializeCollectionSelects();
5933
6518
  this.initializeCollectionMultiSelects();
5934
6519
  this.initializeDatePickers();
@@ -6057,6 +6642,77 @@ class FormView extends View {
6057
6642
  }
6058
6643
  });
6059
6644
  }
6645
+ /**
6646
+ * Initialize MultiSelectDropdown components
6647
+ */
6648
+ initializeMultiSelectDropdowns() {
6649
+ const multiselectPlaceholders = this.element.querySelectorAll('[data-field-type="multiselect"]');
6650
+ multiselectPlaceholders.forEach((placeholder) => {
6651
+ try {
6652
+ const fieldName = placeholder.getAttribute("data-field-name");
6653
+ const configData = placeholder.getAttribute("data-field-config");
6654
+ const config = JSON.parse(configData);
6655
+ const fieldConfig = this.getFormFieldConfig(fieldName);
6656
+ if (!fieldConfig) {
6657
+ return;
6658
+ }
6659
+ const multiselect = new MultiSelectDropdown({
6660
+ ...config,
6661
+ options: fieldConfig.options || [],
6662
+ placeholder: fieldConfig.placeholder || config.placeholder || "Select...",
6663
+ label: fieldConfig.label,
6664
+ containerId: null
6665
+ // We'll mount directly
6666
+ });
6667
+ let value = config.value ?? MOJOUtils.getContextData(this.data, fieldName);
6668
+ if (value) {
6669
+ multiselect.setFormValue(value);
6670
+ }
6671
+ multiselect.render(true, placeholder);
6672
+ this.customComponents.set(fieldName, multiselect);
6673
+ multiselect.on("change", (data) => {
6674
+ this.handleFieldChange(fieldName, data.value);
6675
+ });
6676
+ } catch (error) {
6677
+ console.error("MultiSelectDropdown initialization failed:", error);
6678
+ }
6679
+ });
6680
+ }
6681
+ /**
6682
+ * Initialize ComboBox components (autocomplete dropdowns)
6683
+ */
6684
+ initializeComboBoxes() {
6685
+ const comboboxPlaceholders = this.element.querySelectorAll('[data-field-type="combobox"]');
6686
+ comboboxPlaceholders.forEach((placeholder) => {
6687
+ try {
6688
+ const fieldName = placeholder.getAttribute("data-field-name");
6689
+ const configData = placeholder.getAttribute("data-field-config");
6690
+ const config = JSON.parse(configData);
6691
+ const fieldConfig = this.getFormFieldConfig(fieldName);
6692
+ if (!fieldConfig) {
6693
+ return;
6694
+ }
6695
+ const combobox = new ComboBox({
6696
+ ...config,
6697
+ options: fieldConfig.options || [],
6698
+ placeholder: fieldConfig.placeholder || config.placeholder || "Type or select...",
6699
+ containerId: null
6700
+ // We'll mount directly
6701
+ });
6702
+ let value = config.value ?? MOJOUtils.getContextData(this.data, fieldName);
6703
+ if (value) {
6704
+ combobox.setFormValue(value);
6705
+ }
6706
+ combobox.render(true, placeholder);
6707
+ this.customComponents.set(fieldName, combobox);
6708
+ combobox.on("change", (data) => {
6709
+ this.handleFieldChange(fieldName, data.value);
6710
+ });
6711
+ } catch (error) {
6712
+ console.error("ComboBox initialization failed:", error);
6713
+ }
6714
+ });
6715
+ }
6060
6716
  /**
6061
6717
  * Initialize CollectionSelect components
6062
6718
  */
@@ -7893,4 +8549,4 @@ export {
7893
8549
  applyFileDropMixin as a,
7894
8550
  FormView$1 as b
7895
8551
  };
7896
- //# sourceMappingURL=FormView-Xb_l8AD5.js.map
8552
+ //# sourceMappingURL=FormView-B_90L1RY.js.map