jedison 1.11.2 → 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.
@@ -3270,7 +3270,8 @@ const glyphicons = {
3270
3270
  close: "glyphicon glyphicon-remove",
3271
3271
  edit: "glyphicon glyphicon-pencil",
3272
3272
  save: "glyphicon glyphicon-floppy-disk",
3273
- copy: "glyphicon glyphicon-copy"
3273
+ copy: "glyphicon glyphicon-copy",
3274
+ switcher: "glyphicon glyphicon-chevron-down"
3274
3275
  };
3275
3276
  const bootstrapIcons = {
3276
3277
  properties: "bi bi-card-list",
@@ -3285,7 +3286,8 @@ const bootstrapIcons = {
3285
3286
  close: "bi bi-x",
3286
3287
  edit: "bi bi-pencil",
3287
3288
  save: "bi bi-floppy",
3288
- copy: "bi bi-clipboard"
3289
+ copy: "bi bi-clipboard",
3290
+ switcher: "bi bi-chevron-down"
3289
3291
  };
3290
3292
  const fontAwesome3 = {
3291
3293
  properties: "icon-list",
@@ -3300,7 +3302,8 @@ const fontAwesome3 = {
3300
3302
  close: "icon-remove",
3301
3303
  edit: "icon-pencil",
3302
3304
  save: "icon-save",
3303
- copy: "icon-copy"
3305
+ copy: "icon-copy",
3306
+ switcher: "icon-chevron-down"
3304
3307
  };
3305
3308
  const fontAwesome4 = {
3306
3309
  properties: "fa fa-list",
@@ -3315,7 +3318,8 @@ const fontAwesome4 = {
3315
3318
  close: "fa fa-times",
3316
3319
  edit: "fa fa-pencil",
3317
3320
  save: "fa fa-floppy-o",
3318
- copy: "fa fa-clipboard"
3321
+ copy: "fa fa-clipboard",
3322
+ switcher: "fa fa-chevron-down"
3319
3323
  };
3320
3324
  const fontAwesome5 = {
3321
3325
  properties: "fas fa-list",
@@ -3330,7 +3334,8 @@ const fontAwesome5 = {
3330
3334
  close: "fas fa-times",
3331
3335
  edit: "fas fa-pencil-alt",
3332
3336
  save: "fas fa-save",
3333
- copy: "fas fa-clipboard"
3337
+ copy: "fas fa-clipboard",
3338
+ switcher: "fas fa-chevron-down"
3334
3339
  };
3335
3340
  const fontAwesome6 = {
3336
3341
  properties: "fa-solid fa-list",
@@ -3345,7 +3350,8 @@ const fontAwesome6 = {
3345
3350
  close: "fa-solid fa-xmark",
3346
3351
  edit: "fa-solid fa-pencil",
3347
3352
  save: "fa-solid fa-floppy-disk",
3348
- copy: "fa-solid fa-clipboard"
3353
+ copy: "fa-solid fa-clipboard",
3354
+ switcher: "fa-solid fa-chevron-down"
3349
3355
  };
3350
3356
  class EditorBoolean extends Editor {
3351
3357
  sanitize(value) {
@@ -4052,8 +4058,7 @@ class EditorObject extends Editor {
4052
4058
  static resolves(schema) {
4053
4059
  return getSchemaType(schema) === "object";
4054
4060
  }
4055
- build() {
4056
- this.propertyActivators = {};
4061
+ getObjectControlConfig() {
4057
4062
  let addProperty = true;
4058
4063
  const additionalProperties2 = getSchemaAdditionalProperties(this.instance.schema);
4059
4064
  if (isSet(additionalProperties2) && additionalProperties2 === false) {
@@ -4071,7 +4076,7 @@ class EditorObject extends Editor {
4071
4076
  if (isSet(schemaEnablePropertiesToggle)) {
4072
4077
  enablePropertiesToggle = schemaEnablePropertiesToggle;
4073
4078
  }
4074
- this.control = this.theme.getObjectControl({
4079
+ return {
4075
4080
  title: this.getTitle(),
4076
4081
  description: this.getDescription(),
4077
4082
  titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
@@ -4085,8 +4090,13 @@ class EditorObject extends Editor {
4085
4090
  editJsonData: getSchemaXOption(this.instance.schema, "editJsonData") ?? this.instance.jedison.getOption("editJsonData"),
4086
4091
  propertiesToggleContent: getSchemaXOption(this.instance.schema, "propertiesToggleContent") ?? this.instance.jedison.translator.translate("propertiesToggle"),
4087
4092
  collapseToggleContent: getSchemaXOption(this.instance.schema, "collapseToggleContent") ?? this.instance.jedison.translator.translate("collapseToggle"),
4088
- addPropertyContent: getSchemaXOption(this.instance.schema, "addPropertyContent") ?? this.instance.jedison.translator.translate("objectAddProperty")
4089
- });
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());
4090
4100
  this.control.jsonData.input.value = JSON.stringify(this.instance.getValue(), null, 2);
4091
4101
  }
4092
4102
  announcePropertyAdded(propertyName, child) {
@@ -4531,6 +4541,32 @@ class EditorObjectNav extends EditorObject {
4531
4541
  });
4532
4542
  }
4533
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
+ }
4534
4570
  class EditorArray extends Editor {
4535
4571
  static resolves(schema) {
4536
4572
  return getSchemaType(schema) === "array";
@@ -5470,6 +5506,15 @@ class EditorMultiple extends Editor {
5470
5506
  });
5471
5507
  });
5472
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
+ }
5473
5518
  }
5474
5519
  refreshUI() {
5475
5520
  this.refreshDisabledState();
@@ -5486,6 +5531,14 @@ class EditorMultiple extends Editor {
5486
5531
  this.control.header.appendChild(this.control.switcher.container);
5487
5532
  }
5488
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
+ }
5489
5542
  if (this.switcherInput === "select") {
5490
5543
  this.control.switcher.input.value = this.instance.index;
5491
5544
  }
@@ -5495,6 +5548,12 @@ class EditorMultiple extends Editor {
5495
5548
  radio.checked = radioIndex === this.instance.index;
5496
5549
  });
5497
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
+ }
5498
5557
  if (this.disabled || this.instance.isReadOnly()) {
5499
5558
  this.instance.activeInstance.ui.disable();
5500
5559
  } else {
@@ -5995,6 +6054,48 @@ class EditorArrayCheckboxes extends Editor {
5995
6054
  }
5996
6055
  return getSchemaEnum(this.instance.schema.items) || [];
5997
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
+ }
5998
6099
  build() {
5999
6100
  const values = this.getEnumSourceValues();
6000
6101
  const schemaItems = this.instance.schema.items || {};
@@ -6009,6 +6110,7 @@ class EditorArrayCheckboxes extends Editor {
6009
6110
  inline: getSchemaXOption(this.instance.schema, "format") === "checkboxes-inline",
6010
6111
  info: this.getInfo()
6011
6112
  });
6113
+ this.addDragHandles();
6012
6114
  }
6013
6115
  refreshOptions() {
6014
6116
  const values = this.getEnumSourceValues();
@@ -6047,6 +6149,7 @@ class EditorArrayCheckboxes extends Editor {
6047
6149
  this.control.checkboxControls.push(checkboxControl);
6048
6150
  this.control.fieldset.insertBefore(checkboxControl, this.control.description);
6049
6151
  });
6152
+ this.addDragHandles();
6050
6153
  this.addEventListeners();
6051
6154
  this.refreshUI();
6052
6155
  }
@@ -6081,6 +6184,7 @@ class EditorArrayCheckboxes extends Editor {
6081
6184
  this.control.checkboxes.forEach((checkbox) => {
6082
6185
  checkbox.checked = value.includes(checkbox.value);
6083
6186
  });
6187
+ this.refreshSortable();
6084
6188
  }
6085
6189
  setAriaInvalid(invalid) {
6086
6190
  this.control.checkboxes.forEach((checkbox) => {
@@ -6257,6 +6361,69 @@ class EditorStringAce extends EditorString {
6257
6361
  }
6258
6362
  }
6259
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
+ }
6260
6427
  class UiResolver {
6261
6428
  constructor(options) {
6262
6429
  this.customEditors = options.customEditors ?? [];
@@ -6281,6 +6448,7 @@ class UiResolver {
6281
6448
  EditorStringFlatpickr,
6282
6449
  EditorStringIMask,
6283
6450
  EditorStringAce,
6451
+ EditorStringFilepond,
6284
6452
  EditorStringInput,
6285
6453
  EditorNumberIMask,
6286
6454
  EditorNumberRaty,
@@ -6291,6 +6459,7 @@ class UiResolver {
6291
6459
  EditorObjectGrid,
6292
6460
  EditorObjectCategories,
6293
6461
  EditorObjectNav,
6462
+ EditorObjectAccordion,
6294
6463
  EditorObject,
6295
6464
  EditorArrayChoices,
6296
6465
  EditorArrayCheckboxes,
@@ -7570,31 +7739,27 @@ class Theme {
7570
7739
  }
7571
7740
  });
7572
7741
  }
7573
- let collapsed = config.startCollapsed;
7574
- if (collapsed) {
7575
- toggle.setAttribute("aria-expanded", "false");
7576
- } else {
7577
- toggle.setAttribute("aria-expanded", "true");
7578
- }
7579
7742
  toggle.style.transition = "transform 0.1s ease";
7580
- if (collapsed) {
7581
- toggle.style.transform = "rotate(90deg)";
7743
+ if (config.startCollapsed) {
7744
+ toggle.classList.add("collapsed");
7582
7745
  }
7583
- toggle.addEventListener("click", () => {
7584
- if (collapsed) {
7585
- toggle.style.transform = "rotate(0deg)";
7586
- } else {
7587
- toggle.style.transform = "rotate(90deg)";
7588
- }
7589
- collapsed = !collapsed;
7590
- });
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"] });
7591
7756
  return toggle;
7592
7757
  }
7593
7758
  /**
7594
7759
  * Container for properties editing elements like property activators
7595
7760
  */
7596
7761
  getPropertiesSlot(config) {
7597
- const html = document.createElement("dialog");
7762
+ const html = this.getDialog();
7598
7763
  html.classList.add("jedi-properties-slot");
7599
7764
  html.setAttribute("id", config.id);
7600
7765
  html.addEventListener("click", (event) => {
@@ -7605,7 +7770,7 @@ class Theme {
7605
7770
  return html;
7606
7771
  }
7607
7772
  getQuickAddPropertySlot(config) {
7608
- const html = document.createElement("dialog");
7773
+ const html = this.getDialog();
7609
7774
  html.classList.add("jedi-quick-add-property-slot");
7610
7775
  html.setAttribute("id", config.id);
7611
7776
  html.addEventListener("click", (event) => {
@@ -7619,7 +7784,7 @@ class Theme {
7619
7784
  * Container for properties editing elements like property activators
7620
7785
  */
7621
7786
  getJsonData(config) {
7622
- const dialog = document.createElement("dialog");
7787
+ const dialog = this.getDialog();
7623
7788
  dialog.classList.add("jedi-json-data");
7624
7789
  dialog.setAttribute("id", config.id);
7625
7790
  dialog.addEventListener("click", (event) => {
@@ -7881,18 +8046,28 @@ class Theme {
7881
8046
  container.appendChild(info);
7882
8047
  return { container, info };
7883
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
+ }
7884
8060
  /**
7885
8061
  * Dialog or modal that contains extra information about the control
7886
8062
  */
7887
8063
  infoAsModal(info, id, config = {}) {
7888
- const dialog = document.createElement("dialog");
8064
+ const dialog = this.getDialog();
7889
8065
  const title = document.createElement("div");
7890
8066
  const content = document.createElement("div");
7891
8067
  const closeBtn = this.getButton({
7892
8068
  content: "Close",
7893
8069
  icon: "close"
7894
8070
  });
7895
- dialog.classList.add("jedi-modal-dialog");
7896
8071
  dialog.setAttribute("id", id + "-modal");
7897
8072
  title.classList.add("jedi-modal-title");
7898
8073
  if (isString(config.title)) {
@@ -7968,6 +8143,9 @@ class Theme {
7968
8143
  const ariaLive = this.getPropertiesAriaLive();
7969
8144
  const messages = this.getMessagesSlot();
7970
8145
  const childrenSlot = this.getChildrenSlot();
8146
+ if (config.isAccordion || config.isAccordionProperties) {
8147
+ childrenSlot.id = "accordion-" + config.id;
8148
+ }
7971
8149
  const propertiesActivators = this.getPropertiesActivators();
7972
8150
  const info = this.getInfo(config.info);
7973
8151
  const description = this.getDescription({
@@ -8089,6 +8267,57 @@ class Theme {
8089
8267
  switcherSlot
8090
8268
  };
8091
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
+ }
8092
8321
  /**
8093
8322
  * Array control is a card containing multiple editors.
8094
8323
  * Items can bve added, deleted or moved up or down.
@@ -8240,7 +8469,7 @@ class Theme {
8240
8469
  const messages = this.getMessagesSlot();
8241
8470
  const childrenSlot = this.getChildrenSlot();
8242
8471
  const randomId = generateRandomID(5);
8243
- const knownSwitchers = ["select", "radios", "radios-inline"];
8472
+ const knownSwitchers = ["select", "radios", "radios-inline", "modal"];
8244
8473
  const switcherType = knownSwitchers.includes(config.switcher) ? config.switcher : "select";
8245
8474
  let switcher;
8246
8475
  if (switcherType === "select") {
@@ -8268,6 +8497,14 @@ class Theme {
8268
8497
  noSpacing: true
8269
8498
  });
8270
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
+ }
8271
8508
  switcher.container.classList.add("jedi-switcher");
8272
8509
  container.appendChild(header);
8273
8510
  container.appendChild(body);
@@ -8361,7 +8598,8 @@ class Theme {
8361
8598
  const { label, labelText } = this.getLabel({
8362
8599
  for: config.id,
8363
8600
  text: config.title,
8364
- visuallyHidden: config.titleHidden
8601
+ visuallyHidden: config.titleHidden,
8602
+ titleIconClass: config.titleIconClass
8365
8603
  });
8366
8604
  const description = this.getDescription({
8367
8605
  content: config.description,
@@ -8724,6 +8962,62 @@ class Theme {
8724
8962
  getSwitcherRadios(config) {
8725
8963
  return this.getRadiosControl(config);
8726
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
+ }
8727
9021
  /**
8728
9022
  * Another type of error message container used for more complex editors like
8729
9023
  * object, array and multiple editors
@@ -8935,6 +9229,74 @@ class ThemeBootstrap3 extends Theme {
8935
9229
  }
8936
9230
  return collapse;
8937
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
+ }
8938
9300
  getJsonData(config) {
8939
9301
  const jsonData = super.getJsonData(config);
8940
9302
  jsonData.control.classList.add("form-group");
@@ -9136,6 +9498,26 @@ class ThemeBootstrap3 extends Theme {
9136
9498
  super.adaptForTableSelectControl(control, td);
9137
9499
  control.container.classList.remove("form-group");
9138
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
+ }
9139
9521
  adaptForTableMultipleControl(control, td) {
9140
9522
  super.adaptForTableMultipleControl(control, td);
9141
9523
  }
@@ -9302,6 +9684,69 @@ class ThemeBootstrap4 extends Theme {
9302
9684
  }
9303
9685
  return collapse;
9304
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
+ }
9305
9750
  getJsonData(config) {
9306
9751
  const jsonData = super.getJsonData(config);
9307
9752
  jsonData.control.classList.add("form-group");
@@ -9514,6 +9959,26 @@ class ThemeBootstrap4 extends Theme {
9514
9959
  super.adaptForTableSelectControl(control, td);
9515
9960
  control.container.classList.remove("form-group");
9516
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
+ }
9517
9982
  adaptForTableMultipleControl(control, td) {
9518
9983
  super.adaptForTableMultipleControl(control, td);
9519
9984
  control.container.classList.remove("mb-3");
@@ -9688,6 +10153,75 @@ class ThemeBootstrap5 extends Theme {
9688
10153
  }
9689
10154
  return collapse;
9690
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
+ }
9691
10225
  getJsonData(config) {
9692
10226
  const jsonData = super.getJsonData(config);
9693
10227
  jsonData.control.classList.add("mb-3");
@@ -9898,6 +10432,26 @@ class ThemeBootstrap5 extends Theme {
9898
10432
  super.adaptForTableSelectControl(control, td);
9899
10433
  control.container.classList.remove("mb-3");
9900
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
+ }
9901
10455
  adaptForTableMultipleControl(control, td) {
9902
10456
  super.adaptForTableMultipleControl(control, td);
9903
10457
  control.container.classList.remove("mb-3");
@@ -10066,6 +10620,7 @@ const index = {
10066
10620
  EditorObjectGrid,
10067
10621
  EditorObjectCategories,
10068
10622
  EditorObjectNav,
10623
+ EditorObjectAccordion,
10069
10624
  EditorObject,
10070
10625
  EditorArrayChoices,
10071
10626
  EditorArrayNav,