jedison 1.11.1 → 1.12.0

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.
@@ -3053,7 +3053,9 @@ class InstanceObject extends Instance {
3053
3053
  this.value = value;
3054
3054
  this.jedison.emit("instance-change", this, initiator);
3055
3055
  this.emit("change", initiator);
3056
- this.emit("notifyParent", initiator);
3056
+ if (!this.refreshingInstances) {
3057
+ this.emit("notifyParent", initiator);
3058
+ }
3057
3059
  }
3058
3060
  /**
3059
3061
  * Sorts the children of the current instance based on their `propertyOrder` value in ascending order.
@@ -3083,8 +3085,11 @@ class InstanceObject extends Instance {
3083
3085
  });
3084
3086
  }
3085
3087
  refreshInstances(initiator) {
3088
+ const wasRefreshing = this.refreshingInstances;
3089
+ this.refreshingInstances = true;
3086
3090
  const value = this.getValue();
3087
3091
  if (!isObject(value)) {
3092
+ this.refreshingInstances = wasRefreshing;
3088
3093
  return;
3089
3094
  }
3090
3095
  const childMap = /* @__PURE__ */ new Map();
@@ -3119,6 +3124,7 @@ class InstanceObject extends Instance {
3119
3124
  }
3120
3125
  this.sortChildrenByPropertyOrder();
3121
3126
  this.value = value;
3127
+ this.refreshingInstances = wasRefreshing;
3122
3128
  }
3123
3129
  }
3124
3130
  class InstanceArray extends Instance {
@@ -3264,7 +3270,8 @@ const glyphicons = {
3264
3270
  close: "glyphicon glyphicon-remove",
3265
3271
  edit: "glyphicon glyphicon-pencil",
3266
3272
  save: "glyphicon glyphicon-floppy-disk",
3267
- copy: "glyphicon glyphicon-copy"
3273
+ copy: "glyphicon glyphicon-copy",
3274
+ switcher: "glyphicon glyphicon-chevron-down"
3268
3275
  };
3269
3276
  const bootstrapIcons = {
3270
3277
  properties: "bi bi-card-list",
@@ -3279,7 +3286,8 @@ const bootstrapIcons = {
3279
3286
  close: "bi bi-x",
3280
3287
  edit: "bi bi-pencil",
3281
3288
  save: "bi bi-floppy",
3282
- copy: "bi bi-clipboard"
3289
+ copy: "bi bi-clipboard",
3290
+ switcher: "bi bi-chevron-down"
3283
3291
  };
3284
3292
  const fontAwesome3 = {
3285
3293
  properties: "icon-list",
@@ -3294,7 +3302,8 @@ const fontAwesome3 = {
3294
3302
  close: "icon-remove",
3295
3303
  edit: "icon-pencil",
3296
3304
  save: "icon-save",
3297
- copy: "icon-copy"
3305
+ copy: "icon-copy",
3306
+ switcher: "icon-chevron-down"
3298
3307
  };
3299
3308
  const fontAwesome4 = {
3300
3309
  properties: "fa fa-list",
@@ -3309,7 +3318,8 @@ const fontAwesome4 = {
3309
3318
  close: "fa fa-times",
3310
3319
  edit: "fa fa-pencil",
3311
3320
  save: "fa fa-floppy-o",
3312
- copy: "fa fa-clipboard"
3321
+ copy: "fa fa-clipboard",
3322
+ switcher: "fa fa-chevron-down"
3313
3323
  };
3314
3324
  const fontAwesome5 = {
3315
3325
  properties: "fas fa-list",
@@ -3324,7 +3334,8 @@ const fontAwesome5 = {
3324
3334
  close: "fas fa-times",
3325
3335
  edit: "fas fa-pencil-alt",
3326
3336
  save: "fas fa-save",
3327
- copy: "fas fa-clipboard"
3337
+ copy: "fas fa-clipboard",
3338
+ switcher: "fas fa-chevron-down"
3328
3339
  };
3329
3340
  const fontAwesome6 = {
3330
3341
  properties: "fa-solid fa-list",
@@ -3339,7 +3350,8 @@ const fontAwesome6 = {
3339
3350
  close: "fa-solid fa-xmark",
3340
3351
  edit: "fa-solid fa-pencil",
3341
3352
  save: "fa-solid fa-floppy-disk",
3342
- copy: "fa-solid fa-clipboard"
3353
+ copy: "fa-solid fa-clipboard",
3354
+ switcher: "fa-solid fa-chevron-down"
3343
3355
  };
3344
3356
  class EditorBoolean extends Editor {
3345
3357
  sanitize(value) {
@@ -4046,8 +4058,7 @@ class EditorObject extends Editor {
4046
4058
  static resolves(schema) {
4047
4059
  return getSchemaType(schema) === "object";
4048
4060
  }
4049
- build() {
4050
- this.propertyActivators = {};
4061
+ getObjectControlConfig() {
4051
4062
  let addProperty = true;
4052
4063
  const additionalProperties2 = getSchemaAdditionalProperties(this.instance.schema);
4053
4064
  if (isSet(additionalProperties2) && additionalProperties2 === false) {
@@ -4065,7 +4076,7 @@ class EditorObject extends Editor {
4065
4076
  if (isSet(schemaEnablePropertiesToggle)) {
4066
4077
  enablePropertiesToggle = schemaEnablePropertiesToggle;
4067
4078
  }
4068
- this.control = this.theme.getObjectControl({
4079
+ return {
4069
4080
  title: this.getTitle(),
4070
4081
  description: this.getDescription(),
4071
4082
  titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
@@ -4079,8 +4090,13 @@ class EditorObject extends Editor {
4079
4090
  editJsonData: getSchemaXOption(this.instance.schema, "editJsonData") ?? this.instance.jedison.getOption("editJsonData"),
4080
4091
  propertiesToggleContent: getSchemaXOption(this.instance.schema, "propertiesToggleContent") ?? this.instance.jedison.translator.translate("propertiesToggle"),
4081
4092
  collapseToggleContent: getSchemaXOption(this.instance.schema, "collapseToggleContent") ?? this.instance.jedison.translator.translate("collapseToggle"),
4082
- addPropertyContent: getSchemaXOption(this.instance.schema, "addPropertyContent") ?? this.instance.jedison.translator.translate("objectAddProperty")
4083
- });
4093
+ addPropertyContent: getSchemaXOption(this.instance.schema, "addPropertyContent") ?? this.instance.jedison.translator.translate("objectAddProperty"),
4094
+ isAccordion: false
4095
+ };
4096
+ }
4097
+ build() {
4098
+ this.propertyActivators = {};
4099
+ this.control = this.theme.getObjectControl(this.getObjectControlConfig());
4084
4100
  this.control.jsonData.input.value = JSON.stringify(this.instance.getValue(), null, 2);
4085
4101
  }
4086
4102
  announcePropertyAdded(propertyName, child) {
@@ -4525,6 +4541,32 @@ class EditorObjectNav extends EditorObject {
4525
4541
  });
4526
4542
  }
4527
4543
  }
4544
+ class EditorObjectAccordion extends EditorObject {
4545
+ static resolves(schema) {
4546
+ return getSchemaType(schema) === "object" && getSchemaXOption(schema, "format") === "accordion";
4547
+ }
4548
+ getObjectControlConfig() {
4549
+ return { ...super.getObjectControlConfig(), isAccordionProperties: true };
4550
+ }
4551
+ refreshEditors() {
4552
+ this.control.childrenSlot.replaceChildren();
4553
+ const accordionId = this.control.childrenSlot.id;
4554
+ this.instance.children.forEach((child) => {
4555
+ if (!child.isActive) return;
4556
+ const schemaTitle = getSchemaTitle(child.schema);
4557
+ const title = isSet(schemaTitle) ? schemaTitle : child.getKey();
4558
+ const id = this.getIdFromPath(child.path);
4559
+ const accordionItem = this.theme.getAccordionItem({ title, id, accordionId });
4560
+ accordionItem.body.appendChild(child.ui.control.container);
4561
+ this.control.childrenSlot.appendChild(accordionItem.container);
4562
+ if (this.disabled || this.instance.isReadOnly()) {
4563
+ child.ui.disable();
4564
+ } else {
4565
+ child.ui.enable();
4566
+ }
4567
+ });
4568
+ }
4569
+ }
4528
4570
  class EditorArray extends Editor {
4529
4571
  static resolves(schema) {
4530
4572
  return getSchemaType(schema) === "array";
@@ -5430,7 +5472,8 @@ class EditorMultiple extends Editor {
5430
5472
  if (this.embedSwitcher) {
5431
5473
  this.control.header.style.display = "none";
5432
5474
  }
5433
- this.instance.on("change", () => {
5475
+ this.instance.on("change", (initiator) => {
5476
+ if (initiator === "api") return;
5434
5477
  const jedison = this.instance.jedison;
5435
5478
  const errors = jedison.getErrors(["error", "warning"]);
5436
5479
  const prefix = this.instance.path + "/";
@@ -5463,6 +5506,15 @@ class EditorMultiple extends Editor {
5463
5506
  });
5464
5507
  });
5465
5508
  }
5509
+ if (this.switcherInput === "modal") {
5510
+ this.control.switcher.optionButtons.forEach((btn) => {
5511
+ btn.addEventListener("click", () => {
5512
+ const index2 = Number(btn.dataset.switcherValue);
5513
+ this.control.switcher.dialog.close();
5514
+ this.instance.switchInstance(index2, void 0, "user");
5515
+ });
5516
+ });
5517
+ }
5466
5518
  }
5467
5519
  refreshUI() {
5468
5520
  this.refreshDisabledState();
@@ -5479,6 +5531,14 @@ class EditorMultiple extends Editor {
5479
5531
  this.control.header.appendChild(this.control.switcher.container);
5480
5532
  }
5481
5533
  }
5534
+ if (this.switcherInput === "modal") {
5535
+ const childControl = this.instance.activeInstance.ui.control;
5536
+ const titleEl = childControl.legendText || childControl.label;
5537
+ if (titleEl) {
5538
+ titleEl.after(this.control.switcher.container);
5539
+ this.control.header.style.display = "none";
5540
+ }
5541
+ }
5482
5542
  if (this.switcherInput === "select") {
5483
5543
  this.control.switcher.input.value = this.instance.index;
5484
5544
  }
@@ -5488,6 +5548,12 @@ class EditorMultiple extends Editor {
5488
5548
  radio.checked = radioIndex === this.instance.index;
5489
5549
  });
5490
5550
  }
5551
+ if (this.switcherInput === "modal") {
5552
+ this.control.switcher.triggerText.textContent = this.instance.switcherOptionsLabels[this.instance.index];
5553
+ this.control.switcher.optionButtons.forEach((btn, index2) => {
5554
+ this.theme.setSwitcherOptionActive(btn, index2 === this.instance.index);
5555
+ });
5556
+ }
5491
5557
  if (this.disabled || this.instance.isReadOnly()) {
5492
5558
  this.instance.activeInstance.ui.disable();
5493
5559
  } else {
@@ -5988,6 +6054,48 @@ class EditorArrayCheckboxes extends Editor {
5988
6054
  }
5989
6055
  return getSchemaEnum(this.instance.schema.items) || [];
5990
6056
  }
6057
+ isSortable() {
6058
+ return window.Sortable && isSet(getSchemaXOption(this.instance.schema, "sortable"));
6059
+ }
6060
+ addDragHandles() {
6061
+ if (!this.isSortable()) return;
6062
+ this.control.checkboxControls.forEach((checkboxControl, index2) => {
6063
+ if (checkboxControl.classList.contains("jedi-checkbox-control")) return;
6064
+ const wrapper = document.createElement("div");
6065
+ wrapper.classList.add("jedi-checkbox-control");
6066
+ wrapper.style.display = "flex";
6067
+ wrapper.style.alignItems = "baseline";
6068
+ const dragBtn = this.theme.getDragItemBtn({
6069
+ content: this.instance.jedison.translator.translate("arrayDrag")
6070
+ });
6071
+ checkboxControl.parentNode.insertBefore(wrapper, checkboxControl);
6072
+ wrapper.appendChild(dragBtn);
6073
+ wrapper.appendChild(checkboxControl);
6074
+ this.control.checkboxControls[index2] = wrapper;
6075
+ });
6076
+ }
6077
+ refreshSortable() {
6078
+ if (this.isSortable()) {
6079
+ if (this.sortable) {
6080
+ this.sortable.destroy();
6081
+ }
6082
+ this.sortable = window.Sortable.create(this.control.fieldset, {
6083
+ animation: 150,
6084
+ handle: ".jedi-array-drag",
6085
+ draggable: ".jedi-checkbox-control",
6086
+ disabled: this.disabled || this.readOnly,
6087
+ onEnd: () => {
6088
+ const sorted = Array.from(this.control.fieldset.querySelectorAll(".jedi-checkbox-control"));
6089
+ this.control.checkboxControls = sorted;
6090
+ this.control.checkboxes = sorted.map((cc) => cc.querySelector('input[type="checkbox"]'));
6091
+ this.control.labels = sorted.map((cc) => cc.querySelector("label"));
6092
+ this.control.labelTexts = sorted.map((cc) => cc.querySelector("label span"));
6093
+ const newValue = this.control.checkboxes.filter((cb) => cb.checked).map((cb) => cb.value);
6094
+ this.instance.setValue(newValue, true, "user");
6095
+ }
6096
+ });
6097
+ }
6098
+ }
5991
6099
  build() {
5992
6100
  const values = this.getEnumSourceValues();
5993
6101
  const schemaItems = this.instance.schema.items || {};
@@ -6002,6 +6110,7 @@ class EditorArrayCheckboxes extends Editor {
6002
6110
  inline: getSchemaXOption(this.instance.schema, "format") === "checkboxes-inline",
6003
6111
  info: this.getInfo()
6004
6112
  });
6113
+ this.addDragHandles();
6005
6114
  }
6006
6115
  refreshOptions() {
6007
6116
  const values = this.getEnumSourceValues();
@@ -6040,6 +6149,7 @@ class EditorArrayCheckboxes extends Editor {
6040
6149
  this.control.checkboxControls.push(checkboxControl);
6041
6150
  this.control.fieldset.insertBefore(checkboxControl, this.control.description);
6042
6151
  });
6152
+ this.addDragHandles();
6043
6153
  this.addEventListeners();
6044
6154
  this.refreshUI();
6045
6155
  }
@@ -6074,6 +6184,7 @@ class EditorArrayCheckboxes extends Editor {
6074
6184
  this.control.checkboxes.forEach((checkbox) => {
6075
6185
  checkbox.checked = value.includes(checkbox.value);
6076
6186
  });
6187
+ this.refreshSortable();
6077
6188
  }
6078
6189
  setAriaInvalid(invalid) {
6079
6190
  this.control.checkboxes.forEach((checkbox) => {
@@ -6250,6 +6361,69 @@ class EditorStringAce extends EditorString {
6250
6361
  }
6251
6362
  }
6252
6363
  }
6364
+ class EditorStringFilepond extends EditorString {
6365
+ static resolves(schema) {
6366
+ const format2 = getSchemaXOption(schema, "format");
6367
+ return isSet(format2) && format2 === "filepond" && window.FilePond && getSchemaType(schema) === "string";
6368
+ }
6369
+ build() {
6370
+ this.control = this.theme.getInputControl({
6371
+ title: this.getTitle(),
6372
+ description: this.getDescription(),
6373
+ type: "file",
6374
+ id: this.getIdFromPath(this.instance.path),
6375
+ titleIconClass: getSchemaXOption(this.instance.schema, "titleIconClass"),
6376
+ titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
6377
+ info: this.getInfo()
6378
+ });
6379
+ try {
6380
+ const schemaFilepond = getSchemaXOption(this.instance.schema, "filepond") ?? {};
6381
+ const settingsKey = schemaFilepond["x-settings"];
6382
+ const settings = settingsKey && this.instance.jedison.getOption("settings")[settingsKey] ? this.instance.jedison.getOption("settings")[settingsKey] : {};
6383
+ const filepondOptions = { ...schemaFilepond, ...settings };
6384
+ this.hasServer = !!filepondOptions.server;
6385
+ this.filepond = window.FilePond.create(this.control.input, filepondOptions);
6386
+ this.filepond.on("processfile", (error, file) => {
6387
+ if (error) return;
6388
+ const serverIds = this.filepond.getFiles().filter((f) => f.serverId).map((f) => f.serverId);
6389
+ this.instance.setValue(serverIds.join(", "), true, "user");
6390
+ });
6391
+ this.filepond.on("addfile", (error) => {
6392
+ if (error || this.hasServer) return;
6393
+ const names = this.filepond.getFiles().map((f) => f.filename).join(", ");
6394
+ this.instance.setValue(names, true, "user");
6395
+ });
6396
+ this.filepond.on("processfilerevert", () => {
6397
+ const serverIds = this.filepond.getFiles().filter((f) => f.serverId).map((f) => f.serverId);
6398
+ this.instance.setValue(serverIds.join(", "), true, "user");
6399
+ });
6400
+ this.filepond.on("removefile", () => {
6401
+ const remaining = this.filepond.getFiles();
6402
+ if (this.hasServer) {
6403
+ const serverIds = remaining.filter((f) => f.serverId).map((f) => f.serverId);
6404
+ this.instance.setValue(serverIds.join(", "), true, "user");
6405
+ } else {
6406
+ this.instance.setValue(remaining.map((f) => f.filename).join(", "), true, "user");
6407
+ }
6408
+ });
6409
+ } catch (e) {
6410
+ console.error("FilePond is not available or not loaded correctly.", e);
6411
+ }
6412
+ }
6413
+ addEventListeners() {
6414
+ }
6415
+ refreshUI() {
6416
+ if (!this.filepond) return;
6417
+ this.refreshTemplates();
6418
+ this.filepond.setOptions({ disabled: this.disabled || this.readOnly });
6419
+ }
6420
+ destroy() {
6421
+ if (this.filepond) {
6422
+ this.filepond.destroy();
6423
+ }
6424
+ super.destroy();
6425
+ }
6426
+ }
6253
6427
  class UiResolver {
6254
6428
  constructor(options) {
6255
6429
  this.customEditors = options.customEditors ?? [];
@@ -6274,6 +6448,7 @@ class UiResolver {
6274
6448
  EditorStringFlatpickr,
6275
6449
  EditorStringIMask,
6276
6450
  EditorStringAce,
6451
+ EditorStringFilepond,
6277
6452
  EditorStringInput,
6278
6453
  EditorNumberIMask,
6279
6454
  EditorNumberRaty,
@@ -6284,6 +6459,7 @@ class UiResolver {
6284
6459
  EditorObjectGrid,
6285
6460
  EditorObjectCategories,
6286
6461
  EditorObjectNav,
6462
+ EditorObjectAccordion,
6287
6463
  EditorObject,
6288
6464
  EditorArrayChoices,
6289
6465
  EditorArrayCheckboxes,
@@ -7563,31 +7739,27 @@ class Theme {
7563
7739
  }
7564
7740
  });
7565
7741
  }
7566
- let collapsed = config.startCollapsed;
7567
- if (collapsed) {
7568
- toggle.setAttribute("aria-expanded", "false");
7569
- } else {
7570
- toggle.setAttribute("aria-expanded", "true");
7571
- }
7572
7742
  toggle.style.transition = "transform 0.1s ease";
7573
- if (collapsed) {
7574
- toggle.style.transform = "rotate(90deg)";
7743
+ if (config.startCollapsed) {
7744
+ toggle.classList.add("collapsed");
7575
7745
  }
7576
- toggle.addEventListener("click", () => {
7577
- if (collapsed) {
7578
- toggle.style.transform = "rotate(0deg)";
7579
- } else {
7580
- toggle.style.transform = "rotate(90deg)";
7581
- }
7582
- collapsed = !collapsed;
7583
- });
7746
+ const syncState = () => {
7747
+ const collapsed = toggle.classList.contains("collapsed");
7748
+ toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
7749
+ toggle.style.transform = collapsed ? "rotate(90deg)" : "rotate(0deg)";
7750
+ };
7751
+ syncState();
7752
+ if (this.useToggleEvents) {
7753
+ toggle.addEventListener("click", () => toggle.classList.toggle("collapsed"));
7754
+ }
7755
+ new MutationObserver(syncState).observe(toggle, { attributes: true, attributeFilter: ["class"] });
7584
7756
  return toggle;
7585
7757
  }
7586
7758
  /**
7587
7759
  * Container for properties editing elements like property activators
7588
7760
  */
7589
7761
  getPropertiesSlot(config) {
7590
- const html = document.createElement("dialog");
7762
+ const html = this.getDialog();
7591
7763
  html.classList.add("jedi-properties-slot");
7592
7764
  html.setAttribute("id", config.id);
7593
7765
  html.addEventListener("click", (event) => {
@@ -7598,7 +7770,7 @@ class Theme {
7598
7770
  return html;
7599
7771
  }
7600
7772
  getQuickAddPropertySlot(config) {
7601
- const html = document.createElement("dialog");
7773
+ const html = this.getDialog();
7602
7774
  html.classList.add("jedi-quick-add-property-slot");
7603
7775
  html.setAttribute("id", config.id);
7604
7776
  html.addEventListener("click", (event) => {
@@ -7612,7 +7784,7 @@ class Theme {
7612
7784
  * Container for properties editing elements like property activators
7613
7785
  */
7614
7786
  getJsonData(config) {
7615
- const dialog = document.createElement("dialog");
7787
+ const dialog = this.getDialog();
7616
7788
  dialog.classList.add("jedi-json-data");
7617
7789
  dialog.setAttribute("id", config.id);
7618
7790
  dialog.addEventListener("click", (event) => {
@@ -7874,18 +8046,28 @@ class Theme {
7874
8046
  container.appendChild(info);
7875
8047
  return { container, info };
7876
8048
  }
8049
+ /**
8050
+ * Creates a base native <dialog> element with shared styling
8051
+ */
8052
+ getDialog() {
8053
+ const dialog = document.createElement("dialog");
8054
+ dialog.classList.add("jedi-modal-dialog");
8055
+ dialog.style.border = "1px solid #6c757d";
8056
+ dialog.style.borderRadius = "4px";
8057
+ dialog.style.minWidth = "200px";
8058
+ return dialog;
8059
+ }
7877
8060
  /**
7878
8061
  * Dialog or modal that contains extra information about the control
7879
8062
  */
7880
8063
  infoAsModal(info, id, config = {}) {
7881
- const dialog = document.createElement("dialog");
8064
+ const dialog = this.getDialog();
7882
8065
  const title = document.createElement("div");
7883
8066
  const content = document.createElement("div");
7884
8067
  const closeBtn = this.getButton({
7885
8068
  content: "Close",
7886
8069
  icon: "close"
7887
8070
  });
7888
- dialog.classList.add("jedi-modal-dialog");
7889
8071
  dialog.setAttribute("id", id + "-modal");
7890
8072
  title.classList.add("jedi-modal-title");
7891
8073
  if (isString(config.title)) {
@@ -7961,6 +8143,9 @@ class Theme {
7961
8143
  const ariaLive = this.getPropertiesAriaLive();
7962
8144
  const messages = this.getMessagesSlot();
7963
8145
  const childrenSlot = this.getChildrenSlot();
8146
+ if (config.isAccordion || config.isAccordionProperties) {
8147
+ childrenSlot.id = "accordion-" + config.id;
8148
+ }
7964
8149
  const propertiesActivators = this.getPropertiesActivators();
7965
8150
  const info = this.getInfo(config.info);
7966
8151
  const description = this.getDescription({
@@ -8082,6 +8267,57 @@ class Theme {
8082
8267
  switcherSlot
8083
8268
  };
8084
8269
  }
8270
+ /**
8271
+ * Returns an accordion item wrapping a child editor.
8272
+ * Used by EditorObjectAccordionProperties to wrap each property.
8273
+ */
8274
+ getAccordionItem(config) {
8275
+ const container = document.createElement("div");
8276
+ container.classList.add("jedi-accordion-item");
8277
+ const header = document.createElement("div");
8278
+ header.classList.add("jedi-accordion-header");
8279
+ const toggle = document.createElement("button");
8280
+ toggle.type = "button";
8281
+ toggle.classList.add("jedi-accordion-toggle", "collapsed");
8282
+ const chevron = document.createElement("i");
8283
+ chevron.classList.add("jedi-accordion-chevron");
8284
+ if (this.icons && this.icons["collapse"]) {
8285
+ this.addIconClass(chevron, this.icons["collapse"]);
8286
+ } else {
8287
+ chevron.textContent = "▾";
8288
+ }
8289
+ chevron.style.display = "inline-block";
8290
+ chevron.style.transition = "transform 0.1s ease";
8291
+ chevron.style.marginRight = "0.5em";
8292
+ toggle.appendChild(chevron);
8293
+ toggle.appendChild(document.createTextNode(config.title));
8294
+ const collapse = document.createElement("div");
8295
+ collapse.classList.add("jedi-accordion-collapse");
8296
+ collapse.style.display = "none";
8297
+ const body = document.createElement("div");
8298
+ body.classList.add("jedi-accordion-body");
8299
+ const syncState = () => {
8300
+ const collapsed = toggle.classList.contains("collapsed");
8301
+ chevron.style.transform = collapsed ? "rotate(0deg)" : "rotate(-180deg)";
8302
+ };
8303
+ syncState();
8304
+ if (this.useToggleEvents) {
8305
+ toggle.addEventListener("click", () => {
8306
+ if (collapse.style.display === "none") {
8307
+ collapse.style.display = "block";
8308
+ } else {
8309
+ collapse.style.display = "none";
8310
+ }
8311
+ toggle.classList.toggle("collapsed");
8312
+ });
8313
+ }
8314
+ new MutationObserver(syncState).observe(toggle, { attributes: true, attributeFilter: ["class"] });
8315
+ header.appendChild(toggle);
8316
+ collapse.appendChild(body);
8317
+ container.appendChild(header);
8318
+ container.appendChild(collapse);
8319
+ return { container, header, toggle, collapse, body };
8320
+ }
8085
8321
  /**
8086
8322
  * Array control is a card containing multiple editors.
8087
8323
  * Items can bve added, deleted or moved up or down.
@@ -8233,7 +8469,7 @@ class Theme {
8233
8469
  const messages = this.getMessagesSlot();
8234
8470
  const childrenSlot = this.getChildrenSlot();
8235
8471
  const randomId = generateRandomID(5);
8236
- const knownSwitchers = ["select", "radios", "radios-inline"];
8472
+ const knownSwitchers = ["select", "radios", "radios-inline", "modal"];
8237
8473
  const switcherType = knownSwitchers.includes(config.switcher) ? config.switcher : "select";
8238
8474
  let switcher;
8239
8475
  if (switcherType === "select") {
@@ -8261,6 +8497,14 @@ class Theme {
8261
8497
  noSpacing: true
8262
8498
  });
8263
8499
  }
8500
+ if (switcherType === "modal") {
8501
+ switcher = this.getSwitcherModal({
8502
+ values: config.switcherOptionValues,
8503
+ titles: config.switcherOptionsLabels,
8504
+ id: config.id + "-switcher-" + randomId,
8505
+ readOnly: config.readOnly
8506
+ });
8507
+ }
8264
8508
  switcher.container.classList.add("jedi-switcher");
8265
8509
  container.appendChild(header);
8266
8510
  container.appendChild(body);
@@ -8354,7 +8598,8 @@ class Theme {
8354
8598
  const { label, labelText } = this.getLabel({
8355
8599
  for: config.id,
8356
8600
  text: config.title,
8357
- visuallyHidden: config.titleHidden
8601
+ visuallyHidden: config.titleHidden,
8602
+ titleIconClass: config.titleIconClass
8358
8603
  });
8359
8604
  const description = this.getDescription({
8360
8605
  content: config.description,
@@ -8717,6 +8962,62 @@ class Theme {
8717
8962
  getSwitcherRadios(config) {
8718
8963
  return this.getRadiosControl(config);
8719
8964
  }
8965
+ /**
8966
+ * Compact badge-button trigger that opens a modal to switch between multiple editors options
8967
+ */
8968
+ getSwitcherModal(config) {
8969
+ const container = document.createElement("span");
8970
+ const trigger = document.createElement("span");
8971
+ const dialog = this.getDialog();
8972
+ const dialogBody = document.createElement("div");
8973
+ const optionButtons = [];
8974
+ const triggerText = document.createElement("span");
8975
+ const triggerIcon = document.createElement("i");
8976
+ container.classList.add("jedi-switcher-modal");
8977
+ trigger.classList.add("jedi-switcher-modal-trigger");
8978
+ trigger.setAttribute("role", "button");
8979
+ trigger.setAttribute("tabindex", "0");
8980
+ trigger.setAttribute("aria-haspopup", "dialog");
8981
+ trigger.setAttribute("aria-label", "Switch type");
8982
+ trigger.appendChild(triggerText);
8983
+ if (this.icons && this.icons.switcher) {
8984
+ this.addIconClass(triggerIcon, this.icons.switcher);
8985
+ trigger.appendChild(document.createTextNode(" "));
8986
+ trigger.appendChild(triggerIcon);
8987
+ }
8988
+ dialogBody.classList.add("jedi-modal-content");
8989
+ config.values.forEach((value, index2) => {
8990
+ const btn = document.createElement("button");
8991
+ btn.setAttribute("type", "button");
8992
+ btn.setAttribute("aria-label", `Select: ${config.titles[index2]}`);
8993
+ btn.textContent = config.titles[index2];
8994
+ btn.dataset.switcherValue = value;
8995
+ btn.classList.add("jedi-switcher-option-btn");
8996
+ optionButtons.push(btn);
8997
+ dialogBody.appendChild(btn);
8998
+ });
8999
+ trigger.addEventListener("click", () => {
9000
+ dialog.showModal();
9001
+ });
9002
+ trigger.addEventListener("keydown", (event) => {
9003
+ if (event.key === "Enter" || event.key === " ") {
9004
+ event.preventDefault();
9005
+ dialog.showModal();
9006
+ }
9007
+ });
9008
+ dialog.addEventListener("click", (event) => {
9009
+ if (event.target === dialog) {
9010
+ dialog.close();
9011
+ }
9012
+ });
9013
+ container.appendChild(trigger);
9014
+ container.appendChild(dialog);
9015
+ dialog.appendChild(dialogBody);
9016
+ return { container, trigger, triggerText, dialog, dialogBody, optionButtons };
9017
+ }
9018
+ setSwitcherOptionActive(btn, active) {
9019
+ btn.classList.toggle("jedi-switcher-option-active", active);
9020
+ }
8720
9021
  /**
8721
9022
  * Another type of error message container used for more complex editors like
8722
9023
  * object, array and multiple editors
@@ -8928,6 +9229,74 @@ class ThemeBootstrap3 extends Theme {
8928
9229
  }
8929
9230
  return collapse;
8930
9231
  }
9232
+ getObjectControl(config) {
9233
+ const control = super.getObjectControl(config);
9234
+ if (config.isAccordion) {
9235
+ const { childrenSlot } = control;
9236
+ childrenSlot.classList.add("panel-group");
9237
+ const accordionId = childrenSlot.id;
9238
+ const originalAppendChild = childrenSlot.appendChild.bind(childrenSlot);
9239
+ childrenSlot.appendChild = (child) => {
9240
+ const collapse = child.querySelector(".collapse");
9241
+ if (collapse) {
9242
+ collapse.classList.remove("in");
9243
+ collapse.classList.add("panel-collapse");
9244
+ }
9245
+ const collapseToggle = child.querySelector(".jedi-collapse-toggle");
9246
+ if (collapseToggle) {
9247
+ collapseToggle.setAttribute("data-parent", "#" + accordionId);
9248
+ }
9249
+ return originalAppendChild(child);
9250
+ };
9251
+ }
9252
+ if (config.isAccordionProperties) {
9253
+ control.childrenSlot.classList.add("panel-group");
9254
+ }
9255
+ return control;
9256
+ }
9257
+ getAccordionItem(config) {
9258
+ const collapseId = config.id + "-acc-collapse";
9259
+ const container = document.createElement("div");
9260
+ container.classList.add("panel", "panel-default");
9261
+ const header = document.createElement("div");
9262
+ header.classList.add("panel-heading", "collapsed");
9263
+ header.setAttribute("data-toggle", "collapse");
9264
+ header.setAttribute("data-parent", "#" + config.accordionId);
9265
+ header.setAttribute("href", "#" + collapseId);
9266
+ header.style.cursor = "pointer";
9267
+ const title = document.createElement("h4");
9268
+ title.classList.add("panel-title");
9269
+ const toggle = document.createElement("a");
9270
+ const chevron = document.createElement("i");
9271
+ chevron.classList.add("jedi-accordion-chevron");
9272
+ if (this.icons && this.icons["collapse"]) {
9273
+ this.addIconClass(chevron, this.icons["collapse"]);
9274
+ } else {
9275
+ chevron.textContent = "▾";
9276
+ }
9277
+ chevron.style.display = "inline-block";
9278
+ chevron.style.transition = "transform 0.1s ease";
9279
+ chevron.style.marginRight = "0.5em";
9280
+ toggle.appendChild(chevron);
9281
+ toggle.appendChild(document.createTextNode(config.title));
9282
+ const collapse = document.createElement("div");
9283
+ collapse.id = collapseId;
9284
+ collapse.classList.add("panel-collapse", "collapse");
9285
+ const syncState = () => {
9286
+ const collapsed = header.classList.contains("collapsed");
9287
+ chevron.style.transform = collapsed ? "rotate(-90deg)" : "rotate(0deg)";
9288
+ };
9289
+ syncState();
9290
+ new MutationObserver(syncState).observe(header, { attributes: true, attributeFilter: ["class"] });
9291
+ const body = document.createElement("div");
9292
+ body.classList.add("panel-body", "p-0");
9293
+ title.appendChild(toggle);
9294
+ header.appendChild(title);
9295
+ collapse.appendChild(body);
9296
+ container.appendChild(header);
9297
+ container.appendChild(collapse);
9298
+ return { container, header, toggle, collapse, body };
9299
+ }
8931
9300
  getJsonData(config) {
8932
9301
  const jsonData = super.getJsonData(config);
8933
9302
  jsonData.control.classList.add("form-group");
@@ -9129,6 +9498,26 @@ class ThemeBootstrap3 extends Theme {
9129
9498
  super.adaptForTableSelectControl(control, td);
9130
9499
  control.container.classList.remove("form-group");
9131
9500
  }
9501
+ getSwitcherSelect(config) {
9502
+ const control = super.getSwitcherSelect(config);
9503
+ control.input.classList.add("input-sm");
9504
+ return control;
9505
+ }
9506
+ getSwitcherModal(config) {
9507
+ const control = super.getSwitcherModal(config);
9508
+ control.container.style.marginLeft = "4px";
9509
+ control.trigger.classList.add("label", "label-default");
9510
+ control.dialogBody.classList.add("btn-group-vertical");
9511
+ control.optionButtons.forEach((btn) => {
9512
+ btn.classList.add("btn", "btn-default");
9513
+ });
9514
+ return control;
9515
+ }
9516
+ setSwitcherOptionActive(btn, active) {
9517
+ super.setSwitcherOptionActive(btn, active);
9518
+ btn.classList.toggle("btn-primary", active);
9519
+ btn.classList.toggle("btn-default", !active);
9520
+ }
9132
9521
  adaptForTableMultipleControl(control, td) {
9133
9522
  super.adaptForTableMultipleControl(control, td);
9134
9523
  }
@@ -9295,6 +9684,69 @@ class ThemeBootstrap4 extends Theme {
9295
9684
  }
9296
9685
  return collapse;
9297
9686
  }
9687
+ getObjectControl(config) {
9688
+ const control = super.getObjectControl(config);
9689
+ if (config.isAccordion) {
9690
+ const { childrenSlot } = control;
9691
+ const accordionId = childrenSlot.id;
9692
+ const originalAppendChild = childrenSlot.appendChild.bind(childrenSlot);
9693
+ childrenSlot.appendChild = (child) => {
9694
+ const collapse = child.querySelector(".collapse");
9695
+ if (collapse) {
9696
+ collapse.classList.remove("show");
9697
+ collapse.setAttribute("data-parent", "#" + accordionId);
9698
+ }
9699
+ return originalAppendChild(child);
9700
+ };
9701
+ }
9702
+ if (config.isAccordionProperties) {
9703
+ control.childrenSlot.classList.add("accordion", "pb-3");
9704
+ }
9705
+ return control;
9706
+ }
9707
+ getAccordionItem(config) {
9708
+ const collapseId = config.id + "-acc-collapse";
9709
+ const container = document.createElement("div");
9710
+ container.classList.add("card", "mb-0");
9711
+ const header = document.createElement("div");
9712
+ header.classList.add("card-header", "p-0");
9713
+ const toggle = document.createElement("button");
9714
+ toggle.type = "button";
9715
+ toggle.classList.add("btn", "btn-link", "w-100", "text-left");
9716
+ toggle.setAttribute("data-toggle", "collapse");
9717
+ toggle.setAttribute("data-target", "#" + collapseId);
9718
+ toggle.setAttribute("data-parent", "#" + config.accordionId);
9719
+ toggle.classList.add("collapsed");
9720
+ const chevron = document.createElement("i");
9721
+ chevron.classList.add("jedi-accordion-chevron");
9722
+ if (this.icons && this.icons["collapse"]) {
9723
+ this.addIconClass(chevron, this.icons["collapse"]);
9724
+ } else {
9725
+ chevron.textContent = "▾";
9726
+ }
9727
+ chevron.style.display = "inline-block";
9728
+ chevron.style.transition = "transform 0.1s ease";
9729
+ chevron.style.marginRight = "0.5em";
9730
+ toggle.appendChild(chevron);
9731
+ toggle.appendChild(document.createTextNode(config.title));
9732
+ const collapse = document.createElement("div");
9733
+ collapse.id = collapseId;
9734
+ collapse.classList.add("collapse");
9735
+ collapse.setAttribute("data-parent", "#" + config.accordionId);
9736
+ const syncState = () => {
9737
+ const collapsed = toggle.classList.contains("collapsed");
9738
+ chevron.style.transform = collapsed ? "rotate(-90deg)" : "rotate(0deg)";
9739
+ };
9740
+ syncState();
9741
+ new MutationObserver(syncState).observe(toggle, { attributes: true, attributeFilter: ["class"] });
9742
+ const body = document.createElement("div");
9743
+ body.classList.add("card-body", "pb-0");
9744
+ header.appendChild(toggle);
9745
+ collapse.appendChild(body);
9746
+ container.appendChild(header);
9747
+ container.appendChild(collapse);
9748
+ return { container, header, toggle, collapse, body };
9749
+ }
9298
9750
  getJsonData(config) {
9299
9751
  const jsonData = super.getJsonData(config);
9300
9752
  jsonData.control.classList.add("form-group");
@@ -9507,6 +9959,26 @@ class ThemeBootstrap4 extends Theme {
9507
9959
  super.adaptForTableSelectControl(control, td);
9508
9960
  control.container.classList.remove("form-group");
9509
9961
  }
9962
+ getSwitcherSelect(config) {
9963
+ const control = super.getSwitcherSelect(config);
9964
+ control.input.classList.add("form-control-sm");
9965
+ return control;
9966
+ }
9967
+ getSwitcherModal(config) {
9968
+ const control = super.getSwitcherModal(config);
9969
+ control.container.classList.add("ml-1");
9970
+ control.trigger.classList.add("badge", "badge-secondary");
9971
+ control.dialogBody.classList.add("btn-group", "btn-group-vertical", "w-100");
9972
+ control.optionButtons.forEach((btn) => {
9973
+ btn.classList.add("btn", "btn-secondary");
9974
+ });
9975
+ return control;
9976
+ }
9977
+ setSwitcherOptionActive(btn, active) {
9978
+ super.setSwitcherOptionActive(btn, active);
9979
+ btn.classList.toggle("btn-primary", active);
9980
+ btn.classList.toggle("btn-secondary", !active);
9981
+ }
9510
9982
  adaptForTableMultipleControl(control, td) {
9511
9983
  super.adaptForTableMultipleControl(control, td);
9512
9984
  control.container.classList.remove("mb-3");
@@ -9681,6 +10153,75 @@ class ThemeBootstrap5 extends Theme {
9681
10153
  }
9682
10154
  return collapse;
9683
10155
  }
10156
+ getObjectControl(config) {
10157
+ const control = super.getObjectControl(config);
10158
+ if (config.isAccordion) {
10159
+ const { childrenSlot } = control;
10160
+ const accordionId = childrenSlot.id;
10161
+ const originalAppendChild = childrenSlot.appendChild.bind(childrenSlot);
10162
+ childrenSlot.appendChild = (child) => {
10163
+ const collapse = child.querySelector(".collapse");
10164
+ if (collapse) {
10165
+ collapse.classList.remove("show");
10166
+ collapse.setAttribute("data-bs-parent", "#" + accordionId);
10167
+ }
10168
+ return originalAppendChild(child);
10169
+ };
10170
+ }
10171
+ if (config.isAccordionProperties) {
10172
+ control.childrenSlot.classList.add("accordion", "pb-3");
10173
+ }
10174
+ return control;
10175
+ }
10176
+ getAccordionItem(config) {
10177
+ const collapseId = config.id + "-acc-collapse";
10178
+ const container = document.createElement("div");
10179
+ container.classList.add("accordion-item");
10180
+ const header = document.createElement("h2");
10181
+ header.classList.add("accordion-header");
10182
+ const toggle = document.createElement("button");
10183
+ toggle.type = "button";
10184
+ toggle.classList.add("accordion-button", "collapsed");
10185
+ toggle.setAttribute("data-bs-toggle", "collapse");
10186
+ toggle.setAttribute("data-bs-target", "#" + collapseId);
10187
+ toggle.setAttribute("data-bs-parent", "#" + config.accordionId);
10188
+ toggle.classList.add("jedi-accordion-button");
10189
+ if (!document.getElementById("jedi-accordion-button-style")) {
10190
+ const style = document.createElement("style");
10191
+ style.id = "jedi-accordion-button-style";
10192
+ style.textContent = ".jedi-accordion-button::after { display: none !important; }";
10193
+ document.head.appendChild(style);
10194
+ }
10195
+ const chevron = document.createElement("i");
10196
+ chevron.classList.add("jedi-accordion-chevron");
10197
+ if (this.icons && this.icons["collapse"]) {
10198
+ this.addIconClass(chevron, this.icons["collapse"]);
10199
+ } else {
10200
+ chevron.textContent = "▾";
10201
+ }
10202
+ chevron.style.display = "inline-block";
10203
+ chevron.style.transition = "transform 0.1s ease";
10204
+ chevron.style.marginRight = "0.5em";
10205
+ toggle.appendChild(chevron);
10206
+ toggle.appendChild(document.createTextNode(config.title));
10207
+ const collapse = document.createElement("div");
10208
+ collapse.id = collapseId;
10209
+ collapse.classList.add("accordion-collapse", "collapse");
10210
+ collapse.setAttribute("data-bs-parent", "#" + config.accordionId);
10211
+ const syncState = () => {
10212
+ const collapsed = toggle.classList.contains("collapsed");
10213
+ chevron.style.transform = collapsed ? "rotate(-90deg)" : "rotate(0deg)";
10214
+ };
10215
+ syncState();
10216
+ new MutationObserver(syncState).observe(toggle, { attributes: true, attributeFilter: ["class"] });
10217
+ const body = document.createElement("div");
10218
+ body.classList.add("accordion-body", "pb-3");
10219
+ header.appendChild(toggle);
10220
+ collapse.appendChild(body);
10221
+ container.appendChild(header);
10222
+ container.appendChild(collapse);
10223
+ return { container, header, toggle, collapse, body };
10224
+ }
9684
10225
  getJsonData(config) {
9685
10226
  const jsonData = super.getJsonData(config);
9686
10227
  jsonData.control.classList.add("mb-3");
@@ -9891,6 +10432,26 @@ class ThemeBootstrap5 extends Theme {
9891
10432
  super.adaptForTableSelectControl(control, td);
9892
10433
  control.container.classList.remove("mb-3");
9893
10434
  }
10435
+ getSwitcherSelect(config) {
10436
+ const control = super.getSwitcherSelect(config);
10437
+ control.input.classList.add("form-select-sm");
10438
+ return control;
10439
+ }
10440
+ getSwitcherModal(config) {
10441
+ const control = super.getSwitcherModal(config);
10442
+ control.container.classList.add("ms-1");
10443
+ control.trigger.classList.add("badge", "bg-secondary");
10444
+ control.dialogBody.classList.add("btn-group", "btn-group-vertical", "w-100");
10445
+ control.optionButtons.forEach((btn) => {
10446
+ btn.classList.add("btn", "btn-secondary");
10447
+ });
10448
+ return control;
10449
+ }
10450
+ setSwitcherOptionActive(btn, active) {
10451
+ super.setSwitcherOptionActive(btn, active);
10452
+ btn.classList.toggle("btn-primary", active);
10453
+ btn.classList.toggle("btn-secondary", !active);
10454
+ }
9894
10455
  adaptForTableMultipleControl(control, td) {
9895
10456
  super.adaptForTableMultipleControl(control, td);
9896
10457
  control.container.classList.remove("mb-3");
@@ -10059,6 +10620,7 @@ const index = {
10059
10620
  EditorObjectGrid,
10060
10621
  EditorObjectCategories,
10061
10622
  EditorObjectNav,
10623
+ EditorObjectAccordion,
10062
10624
  EditorObject,
10063
10625
  EditorArrayChoices,
10064
10626
  EditorArrayNav,