jedison 1.15.0 → 1.16.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.
@@ -116,6 +116,36 @@ function getType(value) {
116
116
  }
117
117
  return type2;
118
118
  }
119
+ const DEFAULT_ATTRIBUTE_ALLOWLIST = [
120
+ "id",
121
+ "class",
122
+ "title",
123
+ "name",
124
+ "value",
125
+ "disabled",
126
+ "always-enabled",
127
+ "always-disabled"
128
+ ];
129
+ const DEFAULT_ATTRIBUTE_ALLOWED_PREFIXES = ["aria-", "data-"];
130
+ function filterAttributes(attributes, options = {}) {
131
+ const {
132
+ allowlist = DEFAULT_ATTRIBUTE_ALLOWLIST,
133
+ allowedPrefixes = DEFAULT_ATTRIBUTE_ALLOWED_PREFIXES
134
+ } = options;
135
+ const result = {};
136
+ if (!isObject(attributes)) {
137
+ return result;
138
+ }
139
+ const allowed = new Set(allowlist.map((name) => name.toLowerCase()));
140
+ for (const [key, value] of Object.entries(attributes)) {
141
+ const normalizedKey = key.trim().toLowerCase();
142
+ const isAllowed = allowed.has(normalizedKey) || allowedPrefixes.some((prefix) => normalizedKey.startsWith(prefix));
143
+ if (isAllowed) {
144
+ result[key] = value;
145
+ }
146
+ }
147
+ return result;
148
+ }
119
149
  const UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
120
150
  function mergeDeep(target, ...sources) {
121
151
  if (!sources.length) return target;
@@ -148,14 +178,14 @@ function combineDeep(target, ...sources) {
148
178
  Object.keys(source).forEach((key) => {
149
179
  if (UNSAFE_KEYS.has(key)) return;
150
180
  if (isObject(source[key])) {
151
- if (!target[key]) {
181
+ if (!isObject(target[key])) {
152
182
  Object.assign(target, {
153
183
  [key]: {}
154
184
  });
155
185
  }
156
186
  combineDeep(target[key], source[key]);
157
187
  } else if (Array.isArray(source[key])) {
158
- if (!target[key]) {
188
+ if (!Array.isArray(target[key])) {
159
189
  target[key] = [];
160
190
  }
161
191
  target[key].push(...source[key]);
@@ -430,6 +460,10 @@ function getSchemaXOption(schema, option) {
430
460
  if (schema["x-options"] && isSet(schema["x-options"][option])) {
431
461
  return schema["x-options"][option];
432
462
  }
463
+ if (option === "switcherInput" && schema["x-embedSwitcher"] === true) {
464
+ console.warn(`Jedison: schema option "x-embedSwitcher" is deprecated. Use "x-switcherInput: 'select-inline'" instead.`);
465
+ return "select-inline";
466
+ }
433
467
  for (const alias of getAliasesFor(option)) {
434
468
  const xAlias = "x-" + alias;
435
469
  if (isSet(schema[xAlias])) {
@@ -1778,8 +1812,28 @@ class EventEmitter {
1778
1812
  }
1779
1813
  callbacks.push(callback);
1780
1814
  }
1781
- off(name) {
1782
- this.listeners.delete(name);
1815
+ /**
1816
+ * Removes event listeners for a named event.
1817
+ * @public
1818
+ * @param {string} name - The name of the event
1819
+ * @param {function} [callback] - When given, only this specific callback is
1820
+ * removed; when omitted, all listeners for the event are removed.
1821
+ */
1822
+ off(name, callback) {
1823
+ if (typeof callback !== "function") {
1824
+ this.listeners.delete(name);
1825
+ return;
1826
+ }
1827
+ const callbacks = this.listeners.get(name);
1828
+ if (!callbacks) {
1829
+ return;
1830
+ }
1831
+ const remaining = callbacks.filter((cb) => cb !== callback);
1832
+ if (remaining.length === 0) {
1833
+ this.listeners.delete(name);
1834
+ } else {
1835
+ this.listeners.set(name, remaining);
1836
+ }
1783
1837
  }
1784
1838
  /**
1785
1839
  * Triggers the callback function of a named event listener
@@ -2145,6 +2199,7 @@ class Editor {
2145
2199
  this.addEventListeners();
2146
2200
  this.setVisibility();
2147
2201
  this.setContainerAttributes();
2202
+ this.appendSchemaButtons();
2148
2203
  this.refreshUI();
2149
2204
  const alwaysShowErrors = this.instance.jedison.getOption("showErrors") === "always" || getSchemaXOption(this.instance.schema, "showErrors") === "always";
2150
2205
  if (alwaysShowErrors) {
@@ -2158,6 +2213,14 @@ class Editor {
2158
2213
  }
2159
2214
  static resolves(schema) {
2160
2215
  }
2216
+ /**
2217
+ * Whether this editor already renders a heading for each of its children
2218
+ * (e.g. an accordion toggle or a nav tab label), so a child editor can
2219
+ * skip drawing its own duplicate heading/panel when embedded here.
2220
+ */
2221
+ static providesChildHeading() {
2222
+ return false;
2223
+ }
2161
2224
  /**
2162
2225
  * Initializes the editor
2163
2226
  */
@@ -2200,6 +2263,62 @@ class Editor {
2200
2263
  }
2201
2264
  }
2202
2265
  }
2266
+ /**
2267
+ * Renders schema-defined action buttons (the `x-buttons` keyword) into the
2268
+ * editor container. Works for every editor because it lives on the base
2269
+ * class, next to setAttributes()/setContainerAttributes(). Both `x-buttons`
2270
+ * and `x-options.buttons` spellings resolve through getSchemaXOption().
2271
+ *
2272
+ * Security decisions from issue #62:
2273
+ * - The label is HTML sanitized through the existing DOMPurify pipeline
2274
+ * (purifyContent(), decision 9c / F6) before being handed to the theme.
2275
+ * - The `attributes` bag is filtered against an allowlist (filterAttributes(),
2276
+ * decision 6a / F1); `always-enabled` / `always-disabled` are intentionally
2277
+ * allowed (decision 6b, trusted-schema stance).
2278
+ * - Each button emits a `jedison:<name>` event on the root Jedison instance
2279
+ * through the internal EventEmitter (decision 1c / 2), carrying a live
2280
+ * payload `{ jedison, editor, path }` (decision 8). Consumers subscribe with
2281
+ * `jedison.on('jedison:<name>', ({ jedison, editor, path }) => ...)`. The
2282
+ * listener map is private to the instance, so the payload is not exposed to
2283
+ * unrelated scripts on the page (F3 contained).
2284
+ * - Click listeners are registered through storedEventListeners so destroy()
2285
+ * cleans them up.
2286
+ */
2287
+ appendSchemaButtons() {
2288
+ const buttons = getSchemaXOption(this.instance.schema, "buttons");
2289
+ if (!isArray(buttons)) {
2290
+ return;
2291
+ }
2292
+ const domPurifyOptions = this.instance.jedison.getOption("domPurifyOptions");
2293
+ buttons.forEach((config) => {
2294
+ if (!isObject(config)) {
2295
+ return;
2296
+ }
2297
+ const rawLabel = isString(config.label) ? config.label : "";
2298
+ const label = this.purifyEnabled ? this.purifyContent(rawLabel, domPurifyOptions) : rawLabel;
2299
+ const attributes = filterAttributes(config.attributes);
2300
+ const button = this.theme.getXButton({ label, attributes });
2301
+ this.control.container.appendChild(button);
2302
+ const eventName = isObject(config.event) && isString(config.event.name) ? config.event.name : null;
2303
+ if (!eventName) {
2304
+ return;
2305
+ }
2306
+ const handler = () => {
2307
+ const jedison = this.instance.jedison;
2308
+ jedison.emit("jedison:" + eventName, {
2309
+ jedison,
2310
+ editor: this,
2311
+ path: this.instance.path
2312
+ });
2313
+ };
2314
+ button.addEventListener("click", handler);
2315
+ this.storedEventListeners.push({
2316
+ element: button,
2317
+ eventType: "click",
2318
+ handler
2319
+ });
2320
+ });
2321
+ }
2203
2322
  /**
2204
2323
  * Builds the editor control and appends it to the editor container
2205
2324
  */
@@ -2508,6 +2627,7 @@ class EditorIfThenElse extends Editor {
2508
2627
  this.refreshDisabledState();
2509
2628
  this.control.childrenSlot.innerHTML = "";
2510
2629
  this.control.childrenSlot.appendChild(this.instance.activeInstance.ui.control.container);
2630
+ this.control.switcherSlot = this.instance.activeInstance.ui.control.switcherSlot;
2511
2631
  if (this.disabled || this.instance.isReadOnly()) {
2512
2632
  this.instance.activeInstance.ui.disable();
2513
2633
  } else {
@@ -4131,7 +4251,7 @@ class EditorObject extends Editor {
4131
4251
  return {
4132
4252
  title: this.getTitle(),
4133
4253
  description: this.getDescription(),
4134
- titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
4254
+ titleHidden: getSchemaXOption(this.instance.schema, "titleHidden") ?? this.isEmbeddedInParentChrome(),
4135
4255
  id: this.getIdFromPath(this.instance.path),
4136
4256
  enablePropertiesToggle,
4137
4257
  addProperty,
@@ -4147,9 +4267,20 @@ class EditorObject extends Editor {
4147
4267
  titleIconClass: getSchemaXOption(this.instance.schema, "titleIconClass")
4148
4268
  };
4149
4269
  }
4270
+ isEmbeddedInParentChrome() {
4271
+ var _a;
4272
+ const autoFlat = getSchemaXOption(this.instance.schema, "autoFlat") ?? this.instance.jedison.getOption("autoFlat");
4273
+ if (!autoFlat) return false;
4274
+ const parentInstance = this.instance.parent;
4275
+ if (!parentInstance) return false;
4276
+ const ParentEditorClass = this.instance.jedison.uiResolver.getClass(parentInstance.schema);
4277
+ return !!((_a = ParentEditorClass == null ? void 0 : ParentEditorClass.providesChildHeading) == null ? void 0 : _a.call(ParentEditorClass));
4278
+ }
4150
4279
  build() {
4151
4280
  this.propertyActivators = {};
4152
- const card = getSchemaXOption(this.instance.schema, "card") ?? this.instance.jedison.getOption("card");
4281
+ const explicitCard = getSchemaXOption(this.instance.schema, "card");
4282
+ const globalCard = this.instance.jedison.getOption("card");
4283
+ const card = explicitCard ?? (this.isEmbeddedInParentChrome() ? false : globalCard);
4153
4284
  const config = this.getObjectControlConfig();
4154
4285
  this.control = card === false ? this.theme.getObjectControlFlat(config) : this.theme.getObjectControl(config);
4155
4286
  this.control.jsonData.input.value = JSON.stringify(this.instance.getValue(), null, 2);
@@ -4400,6 +4531,7 @@ class EditorObjectCategories extends EditorObject {
4400
4531
  init() {
4401
4532
  super.init();
4402
4533
  this.activeCategoryName = null;
4534
+ this.userSelectedCategory = false;
4403
4535
  }
4404
4536
  navigateTo(path) {
4405
4537
  const nextChildPath = this.getNextChildPath(path);
@@ -4419,6 +4551,7 @@ class EditorObjectCategories extends EditorObject {
4419
4551
  categoryName = defaultLabel;
4420
4552
  }
4421
4553
  this.activeCategoryName = categoryName;
4554
+ this.userSelectedCategory = true;
4422
4555
  this.refreshUI();
4423
4556
  }
4424
4557
  }
@@ -4469,9 +4602,6 @@ class EditorObjectCategories extends EditorObject {
4469
4602
  }
4470
4603
  categoriesMap.get(categoryName).children.push(child);
4471
4604
  });
4472
- if (!categoriesMap.has(this.activeCategoryName)) {
4473
- this.activeCategoryName = categoriesMap.keys().next().value;
4474
- }
4475
4605
  const categoryOrder = getSchemaXOption(this.instance.schema, "categoryOrder");
4476
4606
  const allNames = Array.from(categoriesMap.keys());
4477
4607
  let orderedCategoryNames = allNames;
@@ -4480,6 +4610,9 @@ class EditorObjectCategories extends EditorObject {
4480
4610
  const unspecified = allNames.filter((name) => !categoryOrder.includes(name));
4481
4611
  orderedCategoryNames = [...specifiedFirst, ...unspecified];
4482
4612
  }
4613
+ if (!this.userSelectedCategory || !categoriesMap.has(this.activeCategoryName)) {
4614
+ this.activeCategoryName = orderedCategoryNames[0];
4615
+ }
4483
4616
  orderedCategoryNames.forEach((categoryName) => {
4484
4617
  const category = categoriesMap.get(categoryName);
4485
4618
  const active = categoryName === this.activeCategoryName;
@@ -4494,6 +4627,7 @@ class EditorObjectCategories extends EditorObject {
4494
4627
  });
4495
4628
  tab.list.addEventListener("click", () => {
4496
4629
  this.activeCategoryName = categoryName;
4630
+ this.userSelectedCategory = true;
4497
4631
  });
4498
4632
  const pane = document.createElement("div");
4499
4633
  this.theme.setTabPaneAttributes(pane, active, id);
@@ -4517,6 +4651,9 @@ class EditorObjectNav extends EditorObject {
4517
4651
  const hasNavFormat = regex.test(format2);
4518
4652
  return getSchemaType(schema) === "object" && hasNavFormat;
4519
4653
  }
4654
+ static providesChildHeading() {
4655
+ return true;
4656
+ }
4520
4657
  init() {
4521
4658
  super.init();
4522
4659
  this.activeTabIndex = 0;
@@ -4603,11 +4740,35 @@ class EditorObjectAccordion extends EditorObject {
4603
4740
  static resolves(schema) {
4604
4741
  return getSchemaType(schema) === "object" && getSchemaXOption(schema, "format") === "accordion";
4605
4742
  }
4743
+ static providesChildHeading() {
4744
+ return true;
4745
+ }
4606
4746
  getObjectControlConfig() {
4607
4747
  return { ...super.getObjectControlConfig(), isAccordionProperties: true };
4608
4748
  }
4749
+ refreshAccordionWarnings() {
4750
+ if (!this.accordionToggles) return;
4751
+ const navWarning = getSchemaXOption(this.instance.schema, "navWarning") ?? true;
4752
+ const navWarningMessage = getSchemaXOption(this.instance.schema, "navWarningMessage");
4753
+ this.instance.children.forEach((child) => {
4754
+ if (!child.isActive) return;
4755
+ const toggle = this.accordionToggles[child.getKey()];
4756
+ if (!toggle) return;
4757
+ const existing = toggle.querySelector(".jedi-legend-warning");
4758
+ if (existing) existing.parentNode.removeChild(existing);
4759
+ if (navWarning && child.hasNestedValidationErrors()) {
4760
+ const warning = document.createElement("span");
4761
+ warning.classList.add("jedi-legend-warning");
4762
+ warning.textContent = "⚠";
4763
+ if (navWarningMessage) warning.setAttribute("title", navWarningMessage);
4764
+ this.theme.styleLegendWarning(warning);
4765
+ toggle.appendChild(warning);
4766
+ }
4767
+ });
4768
+ }
4609
4769
  refreshEditors() {
4610
4770
  this.control.childrenSlot.replaceChildren();
4771
+ this.accordionToggles = {};
4611
4772
  const accordionId = this.control.childrenSlot.id;
4612
4773
  this.instance.children.forEach((child) => {
4613
4774
  if (!child.isActive) return;
@@ -4617,12 +4778,18 @@ class EditorObjectAccordion extends EditorObject {
4617
4778
  const accordionItem = this.theme.getAccordionItem({ title, id, accordionId });
4618
4779
  accordionItem.body.appendChild(child.ui.control.container);
4619
4780
  this.control.childrenSlot.appendChild(accordionItem.container);
4781
+ this.accordionToggles[child.getKey()] = accordionItem.toggle;
4620
4782
  if (this.disabled || this.instance.isReadOnly()) {
4621
4783
  child.ui.disable();
4622
4784
  } else {
4623
4785
  child.ui.enable();
4624
4786
  }
4625
4787
  });
4788
+ this.refreshAccordionWarnings();
4789
+ }
4790
+ showValidationErrors(errors, force = false) {
4791
+ super.showValidationErrors(errors, force);
4792
+ this.refreshAccordionWarnings();
4626
4793
  }
4627
4794
  }
4628
4795
  class EditorObjectHorizontal extends EditorObject {
@@ -5443,6 +5610,9 @@ class EditorArrayNav extends EditorArray {
5443
5610
  const hasNavFormat = regex.test(format2);
5444
5611
  return getSchemaType(schema) === "array" && hasNavFormat;
5445
5612
  }
5613
+ static providesChildHeading() {
5614
+ return true;
5615
+ }
5446
5616
  navigateTo(path) {
5447
5617
  const nextChildPath = this.getNextChildPath(path);
5448
5618
  if (nextChildPath) {
@@ -5597,7 +5767,7 @@ class EditorMultiple extends Editor {
5597
5767
  }
5598
5768
  build() {
5599
5769
  this.switcherInput = getSchemaXOption(this.instance.schema, "switcherInput") ?? this.instance.jedison.getOption("switcherInput");
5600
- this.embedSwitcher = getSchemaXOption(this.instance.schema, "embedSwitcher") ?? this.instance.jedison.getOption("embedSwitcher");
5770
+ this.embedSwitcher = this.instance.jedison.getOption("embedSwitcher");
5601
5771
  this.control = this.theme.getMultipleControl({
5602
5772
  titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
5603
5773
  id: this.getIdFromPath(this.instance.path),
@@ -5609,6 +5779,7 @@ class EditorMultiple extends Editor {
5609
5779
  if (this.embedSwitcher) {
5610
5780
  this.control.header.style.display = "none";
5611
5781
  }
5782
+ this.switcherWrapper = this.theme.getSwitcherOwner();
5612
5783
  this.instance.on("change", (initiator) => {
5613
5784
  if (initiator === "api") return;
5614
5785
  const jedison = this.instance.jedison;
@@ -5663,35 +5834,22 @@ class EditorMultiple extends Editor {
5663
5834
  }
5664
5835
  }
5665
5836
  refreshUI() {
5666
- var _a;
5667
5837
  this.refreshDisabledState();
5668
5838
  this.control.childrenSlot.innerHTML = "";
5669
5839
  this.control.childrenSlot.appendChild(this.instance.activeInstance.ui.control.container);
5670
- if (this.embedSwitcher) {
5671
- const slot = this.instance.activeInstance.ui.control.switcherSlot;
5840
+ const slot = this.instance.activeInstance.ui.control.switcherSlot;
5841
+ this.control.switcherSlot = slot;
5842
+ if (this.embedSwitcher || this.switcherInput === "modal" || this.switcherInput === "select-inline") {
5672
5843
  if (slot) {
5673
- slot.innerHTML = "";
5674
- slot.appendChild(this.control.switcher.container);
5844
+ this.switcherWrapper.innerHTML = "";
5845
+ this.switcherWrapper.appendChild(this.control.switcher.container);
5846
+ this.insertBySwitcherDepth(slot, this.switcherWrapper, this.getNestedSwitcherDepth(this.instance.activeInstance));
5675
5847
  this.control.header.style.display = "none";
5676
5848
  } else {
5677
5849
  this.control.header.style.display = "";
5678
5850
  this.control.header.appendChild(this.control.switcher.container);
5679
5851
  }
5680
5852
  }
5681
- if (this.switcherInput === "modal" || this.switcherInput === "select-inline") {
5682
- const childControl = this.instance.activeInstance.ui.control;
5683
- const infoContainer = childControl.infoContainer;
5684
- const titleEl = childControl.legendText || childControl.label;
5685
- if (infoContainer) {
5686
- infoContainer.after(this.control.switcher.container);
5687
- this.control.header.style.display = "none";
5688
- } else if (titleEl) {
5689
- const infoEl = (_a = childControl.info) == null ? void 0 : _a.container;
5690
- const anchor = infoEl && infoEl.parentNode ? infoEl : titleEl;
5691
- anchor.after(this.control.switcher.container);
5692
- this.control.header.style.display = "none";
5693
- }
5694
- }
5695
5853
  if (this.switcherInput === "select") {
5696
5854
  this.control.switcher.input.value = this.instance.index;
5697
5855
  }
@@ -5719,6 +5877,23 @@ class EditorMultiple extends Editor {
5719
5877
  getErrorFeedback(config) {
5720
5878
  return this.theme.getAlert(config);
5721
5879
  }
5880
+ // Counts how many EditorMultiple owners sit between `instance` and the real
5881
+ // leaf control, so stacked switchers in a shared slot can be ordered
5882
+ // outer-first instead of shuffling with every refresh.
5883
+ getNestedSwitcherDepth(instance) {
5884
+ if (!instance || !instance.activeInstance) return 0;
5885
+ const childDepth = this.getNestedSwitcherDepth(instance.activeInstance);
5886
+ return instance.ui.switcherWrapper ? childDepth + 1 : childDepth;
5887
+ }
5888
+ insertBySwitcherDepth(slot, wrapper, depth) {
5889
+ wrapper.dataset.switcherDepth = depth;
5890
+ const sibling = Array.from(slot.children).find((child) => Number(child.dataset.switcherDepth) < depth);
5891
+ if (sibling) {
5892
+ slot.insertBefore(wrapper, sibling);
5893
+ } else {
5894
+ slot.appendChild(wrapper);
5895
+ }
5896
+ }
5722
5897
  }
5723
5898
  class EditorNull extends Editor {
5724
5899
  static resolves(schema) {
@@ -6302,37 +6477,24 @@ class EditorArrayCheckboxes extends Editor {
6302
6477
  const values = this.getEnumSourceValues();
6303
6478
  const schemaItems = this.instance.schema.items || {};
6304
6479
  const titles = getSchemaXOption(schemaItems, "enumTitles") || values;
6305
- const id = this.getIdFromPath(this.instance.path);
6306
- const messagesId = id + "-messages";
6307
- const descriptionId = id + "-description";
6308
- const describedBy = messagesId + " " + descriptionId;
6309
6480
  this.control.checkboxControls.forEach((cc) => {
6310
6481
  if (cc.parentNode) cc.parentNode.removeChild(cc);
6311
6482
  });
6312
- this.control.checkboxes = [];
6313
- this.control.labels = [];
6314
- this.control.checkboxControls = [];
6315
- this.control.labelTexts = [];
6316
- values.forEach((value, index2) => {
6317
- const checkboxId = id + "-" + index2;
6318
- const checkboxControl = document.createElement("div");
6319
- const checkbox = document.createElement("input");
6320
- const label = document.createElement("label");
6321
- const labelText = document.createElement("span");
6322
- checkbox.setAttribute("type", "checkbox");
6323
- checkbox.setAttribute("id", checkboxId);
6324
- checkbox.setAttribute("name", id);
6325
- checkbox.setAttribute("value", value);
6326
- checkbox.setAttribute("aria-describedby", describedBy);
6327
- label.setAttribute("for", checkboxId);
6328
- labelText.textContent = titles && titles[index2] !== void 0 ? titles[index2] : value;
6329
- checkboxControl.appendChild(checkbox);
6330
- checkboxControl.appendChild(label);
6331
- label.appendChild(labelText);
6332
- this.control.checkboxes.push(checkbox);
6333
- this.control.labels.push(label);
6334
- this.control.labelTexts.push(labelText);
6335
- this.control.checkboxControls.push(checkboxControl);
6483
+ const rebuilt = this.theme.getCheckboxesControl({
6484
+ title: this.getTitle(),
6485
+ description: this.getDescription(),
6486
+ values,
6487
+ titles,
6488
+ id: this.getIdFromPath(this.instance.path),
6489
+ titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
6490
+ inline: getSchemaXOption(this.instance.schema, "format") === "checkboxes-inline",
6491
+ info: this.getInfo()
6492
+ });
6493
+ this.control.checkboxes = rebuilt.checkboxes;
6494
+ this.control.labels = rebuilt.labels;
6495
+ this.control.labelTexts = rebuilt.labelTexts;
6496
+ this.control.checkboxControls = rebuilt.checkboxControls;
6497
+ this.control.checkboxControls.forEach((checkboxControl) => {
6336
6498
  this.control.fieldset.insertBefore(checkboxControl, this.control.description);
6337
6499
  });
6338
6500
  this.addDragHandles();
@@ -6982,6 +7144,7 @@ class Jedison extends EventEmitter {
6982
7144
  editJsonData: false,
6983
7145
  enablePropertiesToggle: false,
6984
7146
  enableCollapseToggle: false,
7147
+ autoFlat: false,
6985
7148
  btnContents: true,
6986
7149
  btnIcons: true,
6987
7150
  arrayDelete: true,
@@ -7307,6 +7470,20 @@ class Jedison extends EventEmitter {
7307
7470
  }
7308
7471
  });
7309
7472
  }
7473
+ if (this.isEditor) {
7474
+ const schemaTypeArray = getSchemaType(config.schema);
7475
+ if (isArray(schemaTypeArray)) {
7476
+ const inferTypeSource = getSchemaXOption(config.schema, "inferType");
7477
+ if (isSet(inferTypeSource)) {
7478
+ const siblingPath = resolveInstancePath(config.path, inferTypeSource);
7479
+ const siblingInstance = this.getInstance(siblingPath);
7480
+ const siblingValue = siblingInstance ? siblingInstance.getValue() : void 0;
7481
+ if (isSet(siblingValue) && schemaTypeArray.includes(siblingValue)) {
7482
+ config.schema = { ...config.schema, type: siblingValue };
7483
+ }
7484
+ }
7485
+ }
7486
+ }
7310
7487
  const schemaOneOf = getSchemaOneOf(config.schema);
7311
7488
  const schemaAnyOf = getSchemaAnyOf(config.schema);
7312
7489
  const schemaIf = getSchemaIf(config.schema);
@@ -7886,6 +8063,25 @@ class Theme {
7886
8063
  html.classList.add("jedi-children-slot");
7887
8064
  return html;
7888
8065
  }
8066
+ /**
8067
+ * Wrapper used by EditorMultiple to embed an inline switcher next to a control's title
8068
+ */
8069
+ getSwitcherSlot() {
8070
+ const html = document.createElement("span");
8071
+ html.classList.add("jedi-switcher-slot");
8072
+ return html;
8073
+ }
8074
+ /**
8075
+ * Per-owner wrapper inside a switcherSlot — lets more than one EditorMultiple
8076
+ * embed a switcher into the same slot (e.g. nested anyOf/oneOf) without one
8077
+ * overwriting the other.
8078
+ */
8079
+ getSwitcherOwner() {
8080
+ const html = document.createElement("span");
8081
+ html.classList.add("jedi-switcher-owner");
8082
+ html.style.marginInlineStart = "0.25rem";
8083
+ return html;
8084
+ }
7889
8085
  /**
7890
8086
  * Wrapper for error messages
7891
8087
  */
@@ -8150,6 +8346,42 @@ class Theme {
8150
8346
  button.appendChild(text);
8151
8347
  return button;
8152
8348
  }
8349
+ /**
8350
+ * A schema-defined action button (x-buttons keyword).
8351
+ *
8352
+ * This method only renders: the label is expected to be already sanitized by
8353
+ * the editor (Editor.purifyContent(), decision 9c) and the attributes are
8354
+ * expected to be already filtered against the allowlist by the editor
8355
+ * (utils.filterAttributes(), decision 6a). The theme does not sanitize or
8356
+ * filter. No theme styling is applied, only the unstyled `jedi-x-button` hook
8357
+ * class (decision 4).
8358
+ * @param {object} config - Button config
8359
+ * @param {string} [config.label] - Pre-sanitized HTML label
8360
+ * @param {object} [config.attributes] - Pre-filtered HTML attributes
8361
+ * @return {HTMLButtonElement}
8362
+ */
8363
+ getXButton(config = {}) {
8364
+ const button = document.createElement("button");
8365
+ const label = document.createElement("span");
8366
+ button.classList.add("jedi-x-button");
8367
+ button.setAttribute("type", "button");
8368
+ label.classList.add("jedi-x-button-label");
8369
+ label.innerHTML = config.label ?? "";
8370
+ button.appendChild(label);
8371
+ const attributes = isObject(config.attributes) ? config.attributes : {};
8372
+ for (const [key, value] of Object.entries(attributes)) {
8373
+ if (key === "class") {
8374
+ String(value).split(" ").forEach((cls) => {
8375
+ if (cls) {
8376
+ button.classList.add(cls);
8377
+ }
8378
+ });
8379
+ } else {
8380
+ button.setAttribute(key, value);
8381
+ }
8382
+ }
8383
+ return button;
8384
+ }
8153
8385
  getAddPropertyButton(config) {
8154
8386
  const html = this.getButton(config);
8155
8387
  html.classList.add("jedi-add-property-btn");
@@ -8345,6 +8577,7 @@ class Theme {
8345
8577
  const messages = this.getMessagesSlot({
8346
8578
  id: messagesId
8347
8579
  });
8580
+ const switcherSlot = this.getSwitcherSlot();
8348
8581
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
8349
8582
  this.infoAsModal(info, config.id, config.info);
8350
8583
  }
@@ -8352,11 +8585,12 @@ class Theme {
8352
8585
  if (isObject(config.info)) {
8353
8586
  container.appendChild(info.container);
8354
8587
  }
8588
+ container.appendChild(switcherSlot);
8355
8589
  container.appendChild(placeholder);
8356
8590
  container.appendChild(description);
8357
8591
  container.appendChild(messages);
8358
8592
  container.appendChild(actions);
8359
- return { container, placeholder, label, info, labelText, description, messages, actions };
8593
+ return { container, placeholder, label, info, labelText, description, messages, actions, switcherSlot };
8360
8594
  }
8361
8595
  /**
8362
8596
  * Object control is a card containing multiple editors.
@@ -8422,7 +8656,7 @@ class Theme {
8422
8656
  propertiesContainer: quickAddPropertyContainer
8423
8657
  });
8424
8658
  const fieldset = this.getFieldset();
8425
- const { legend, infoContainer, legendText, right } = this.getLegend({
8659
+ const { legend, left, infoContainer, legendText, right } = this.getLegend({
8426
8660
  content: config.title,
8427
8661
  id: config.id,
8428
8662
  titleHidden: config.titleHidden,
@@ -8453,10 +8687,9 @@ class Theme {
8453
8687
  body.appendChild(description);
8454
8688
  }
8455
8689
  body.appendChild(messages);
8456
- const switcherSlot = document.createElement("div");
8457
- switcherSlot.classList.add("jedi-switcher-slot");
8690
+ const switcherSlot = this.getSwitcherSlot();
8691
+ left.appendChild(switcherSlot);
8458
8692
  if (config.readOnly === false) {
8459
- right.appendChild(switcherSlot);
8460
8693
  right.appendChild(actions);
8461
8694
  }
8462
8695
  body.appendChild(childrenSlot);
@@ -8511,6 +8744,9 @@ class Theme {
8511
8744
  const ariaLive = this.getPropertiesAriaLive();
8512
8745
  const messages = this.getMessagesSlot();
8513
8746
  const childrenSlot = this.getChildrenSlot();
8747
+ if (config.isAccordion || config.isAccordionProperties) {
8748
+ childrenSlot.id = "accordion-" + config.id;
8749
+ }
8514
8750
  const propertiesActivators = this.getPropertiesActivators();
8515
8751
  const info = this.getInfo(config.info);
8516
8752
  const description = this.getDescription({ content: config.description });
@@ -8537,8 +8773,7 @@ class Theme {
8537
8773
  const collapse = document.createElement("div");
8538
8774
  const collapseToggle = document.createElement("div");
8539
8775
  const infoContainer = document.createElement("div");
8540
- const switcherSlot = document.createElement("div");
8541
- switcherSlot.classList.add("jedi-switcher-slot");
8776
+ const switcherSlot = this.getSwitcherSlot();
8542
8777
  const legend = document.createElement("div");
8543
8778
  legend.classList.add("jedi-editor-legend");
8544
8779
  legend.style.display = "flex";
@@ -8559,6 +8794,7 @@ class Theme {
8559
8794
  this.visuallyHidden(legendText);
8560
8795
  }
8561
8796
  left.appendChild(legendText);
8797
+ left.appendChild(switcherSlot);
8562
8798
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
8563
8799
  this.infoAsModal(info, config.id, config.info);
8564
8800
  }
@@ -8567,8 +8803,6 @@ class Theme {
8567
8803
  }
8568
8804
  const innerWrapper = document.createElement("div");
8569
8805
  innerWrapper.appendChild(legend);
8570
- innerWrapper.appendChild(propertiesContainer);
8571
- innerWrapper.appendChild(quickAddPropertyContainer);
8572
8806
  container.appendChild(innerWrapper);
8573
8807
  if (config.addProperty) {
8574
8808
  quickAddPropertyContainer.appendChild(quickAddPropertyControl.container);
@@ -8579,11 +8813,12 @@ class Theme {
8579
8813
  }
8580
8814
  body.appendChild(messages);
8581
8815
  if (config.readOnly === false) {
8582
- right.appendChild(switcherSlot);
8583
8816
  right.appendChild(actions);
8584
8817
  }
8585
8818
  body.appendChild(childrenSlot);
8586
8819
  innerWrapper.appendChild(body);
8820
+ innerWrapper.appendChild(propertiesContainer);
8821
+ innerWrapper.appendChild(quickAddPropertyContainer);
8587
8822
  if (config.editJsonData) {
8588
8823
  actions.appendChild(jsonData.toggle);
8589
8824
  }
@@ -8676,8 +8911,11 @@ class Theme {
8676
8911
  chevron.style.display = "inline-block";
8677
8912
  chevron.style.transition = "transform 0.1s ease";
8678
8913
  chevron.style.marginRight = "0.5em";
8914
+ const titleSpan = document.createElement("span");
8915
+ titleSpan.style.marginRight = "0.5em";
8916
+ titleSpan.textContent = config.title;
8679
8917
  toggle.appendChild(chevron);
8680
- toggle.appendChild(document.createTextNode(config.title));
8918
+ toggle.appendChild(titleSpan);
8681
8919
  const collapse = document.createElement("div");
8682
8920
  collapse.classList.add("jedi-accordion-collapse");
8683
8921
  collapse.style.display = "none";
@@ -8730,7 +8968,7 @@ class Theme {
8730
8968
  const footer = this.getArrayFooter();
8731
8969
  const fieldset = this.getFieldset();
8732
8970
  const info = this.getInfo(config.info);
8733
- const { legend, legendText, infoContainer, right } = this.getLegend({
8971
+ const { legend, left, legendText, infoContainer, right } = this.getLegend({
8734
8972
  content: config.title,
8735
8973
  id: config.id,
8736
8974
  titleHidden: config.titleHidden,
@@ -8773,10 +9011,9 @@ class Theme {
8773
9011
  body.appendChild(description);
8774
9012
  }
8775
9013
  body.appendChild(messages);
8776
- const switcherSlot = document.createElement("div");
8777
- switcherSlot.classList.add("jedi-switcher-slot");
9014
+ const switcherSlot = this.getSwitcherSlot();
9015
+ left.appendChild(switcherSlot);
8778
9016
  if (config.readOnly === false) {
8779
- right.appendChild(switcherSlot);
8780
9017
  right.appendChild(actions);
8781
9018
  }
8782
9019
  actions.appendChild(btnGroup);
@@ -8969,6 +9206,7 @@ class Theme {
8969
9206
  content: config.description,
8970
9207
  id: descriptionId
8971
9208
  });
9209
+ const switcherSlot = this.getSwitcherSlot();
8972
9210
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
8973
9211
  this.infoAsModal(info, config.id, config.info);
8974
9212
  }
@@ -8976,11 +9214,12 @@ class Theme {
8976
9214
  if (isObject(config.info)) {
8977
9215
  container.appendChild(info.container);
8978
9216
  }
9217
+ container.appendChild(switcherSlot);
8979
9218
  container.appendChild(br);
8980
9219
  container.appendChild(description);
8981
9220
  container.appendChild(messages);
8982
9221
  container.appendChild(actions);
8983
- return { container, label, info, labelText, description, messages, actions };
9222
+ return { container, label, info, labelText, description, messages, actions, switcherSlot };
8984
9223
  }
8985
9224
  /**
8986
9225
  * A Textarea
@@ -9007,6 +9246,7 @@ class Theme {
9007
9246
  const messages = this.getMessagesSlot({
9008
9247
  id: messagesId
9009
9248
  });
9249
+ const switcherSlot = this.getSwitcherSlot();
9010
9250
  input.setAttribute("aria-describedby", describedBy);
9011
9251
  input.setAttribute("id", config.id);
9012
9252
  input.setAttribute("name", config.id);
@@ -9018,11 +9258,12 @@ class Theme {
9018
9258
  if (isObject(config.info)) {
9019
9259
  container.appendChild(info.container);
9020
9260
  }
9261
+ container.appendChild(switcherSlot);
9021
9262
  container.appendChild(input);
9022
9263
  container.appendChild(description);
9023
9264
  container.appendChild(messages);
9024
9265
  container.appendChild(actions);
9025
- return { container, input, label, info, labelText, description, messages, actions };
9266
+ return { container, input, label, info, labelText, description, messages, actions, switcherSlot };
9026
9267
  }
9027
9268
  adaptForTableTextareaControl(control) {
9028
9269
  this.visuallyHidden(control.label);
@@ -9054,6 +9295,7 @@ class Theme {
9054
9295
  const messages = this.getMessagesSlot({
9055
9296
  id: messagesId
9056
9297
  });
9298
+ const switcherSlot = this.getSwitcherSlot();
9057
9299
  input.setAttribute("aria-describedby", describedBy);
9058
9300
  input.setAttribute("type", config.type);
9059
9301
  input.setAttribute("id", config.id);
@@ -9066,11 +9308,12 @@ class Theme {
9066
9308
  if (isObject(config.info)) {
9067
9309
  container.appendChild(info.container);
9068
9310
  }
9311
+ container.appendChild(switcherSlot);
9069
9312
  container.appendChild(input);
9070
9313
  container.appendChild(description);
9071
9314
  container.appendChild(messages);
9072
9315
  container.appendChild(actions);
9073
- return { container, input, label, info, labelText, description, messages, actions };
9316
+ return { container, input, label, info, labelText, description, messages, actions, switcherSlot };
9074
9317
  }
9075
9318
  getInputRangeControl(config) {
9076
9319
  const control = this.getInputControl(config);
@@ -9140,8 +9383,10 @@ class Theme {
9140
9383
  radios.push(radio);
9141
9384
  labels.push(label);
9142
9385
  });
9386
+ const switcherSlot = this.getSwitcherSlot();
9143
9387
  container.appendChild(fieldset);
9144
9388
  fieldset.appendChild(legend);
9389
+ legend.appendChild(switcherSlot);
9145
9390
  if (isObject(config.info)) {
9146
9391
  legendText.after(info.container);
9147
9392
  }
@@ -9164,7 +9409,8 @@ class Theme {
9164
9409
  labelTexts,
9165
9410
  radioControls,
9166
9411
  description,
9167
- messages
9412
+ messages,
9413
+ switcherSlot
9168
9414
  };
9169
9415
  }
9170
9416
  adaptForTableRadiosControl(control) {
@@ -9201,6 +9447,7 @@ class Theme {
9201
9447
  input.setAttribute("id", config.id);
9202
9448
  input.setAttribute("name", config.id);
9203
9449
  input.setAttribute("aria-describedby", describedBy);
9450
+ const switcherSlot = this.getSwitcherSlot();
9204
9451
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
9205
9452
  this.infoAsModal(info, config.id, config.info);
9206
9453
  }
@@ -9211,9 +9458,10 @@ class Theme {
9211
9458
  if (isObject(config.info)) {
9212
9459
  formGroup.appendChild(info.container);
9213
9460
  }
9461
+ formGroup.appendChild(switcherSlot);
9214
9462
  formGroup.appendChild(description);
9215
9463
  formGroup.appendChild(messages);
9216
- return { container, formGroup, input, label, info, labelText, description, messages, actions };
9464
+ return { container, formGroup, input, label, info, labelText, description, messages, actions, switcherSlot };
9217
9465
  }
9218
9466
  adaptForTableCheckboxControl(control, td) {
9219
9467
  this.visuallyHidden(control.label);
@@ -9270,8 +9518,10 @@ class Theme {
9270
9518
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
9271
9519
  this.infoAsModal(info, config.id, config.info);
9272
9520
  }
9521
+ const switcherSlot = this.getSwitcherSlot();
9273
9522
  container.appendChild(fieldset);
9274
9523
  fieldset.appendChild(legend);
9524
+ legend.appendChild(switcherSlot);
9275
9525
  if (isObject(config.info)) {
9276
9526
  legendText.after(info.container);
9277
9527
  }
@@ -9293,7 +9543,8 @@ class Theme {
9293
9543
  labelTexts,
9294
9544
  checkboxControls,
9295
9545
  description,
9296
- messages
9546
+ messages,
9547
+ switcherSlot
9297
9548
  };
9298
9549
  }
9299
9550
  adaptForTableCheckboxesControl(control, td) {
@@ -9336,6 +9587,7 @@ class Theme {
9336
9587
  }
9337
9588
  input.appendChild(option);
9338
9589
  });
9590
+ const switcherSlot = this.getSwitcherSlot();
9339
9591
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
9340
9592
  this.infoAsModal(info, config.id, config.info);
9341
9593
  }
@@ -9343,11 +9595,12 @@ class Theme {
9343
9595
  if (isObject(config.info)) {
9344
9596
  container.appendChild(info.container);
9345
9597
  }
9598
+ container.appendChild(switcherSlot);
9346
9599
  container.appendChild(input);
9347
9600
  container.appendChild(description);
9348
9601
  container.appendChild(messages);
9349
9602
  container.appendChild(actions);
9350
- return { container, input, label, info, labelText, description, messages, actions };
9603
+ return { container, input, label, info, labelText, description, messages, actions, switcherSlot };
9351
9604
  }
9352
9605
  adaptForTableSelectControl(control) {
9353
9606
  this.visuallyHidden(control.label);
@@ -9780,8 +10033,11 @@ class ThemeBootstrap3 extends Theme {
9780
10033
  chevron.style.display = "inline-block";
9781
10034
  chevron.style.transition = "transform 0.1s ease";
9782
10035
  chevron.style.marginRight = "0.5em";
10036
+ const titleSpan = document.createElement("span");
10037
+ titleSpan.style.marginRight = "0.5em";
10038
+ titleSpan.textContent = config.title;
9783
10039
  toggle.appendChild(chevron);
9784
- toggle.appendChild(document.createTextNode(config.title));
10040
+ toggle.appendChild(titleSpan);
9785
10041
  const collapse = document.createElement("div");
9786
10042
  collapse.id = collapseId;
9787
10043
  collapse.classList.add("panel-collapse", "collapse");
@@ -10308,8 +10564,11 @@ class ThemeBootstrap4 extends Theme {
10308
10564
  }
10309
10565
  chevron.classList.add("d-inline-block", "mr-2");
10310
10566
  chevron.style.transition = "transform 0.1s ease";
10567
+ const titleSpan = document.createElement("span");
10568
+ titleSpan.classList.add("mr-1");
10569
+ titleSpan.textContent = config.title;
10311
10570
  toggle.appendChild(chevron);
10312
- toggle.appendChild(document.createTextNode(config.title));
10571
+ toggle.appendChild(titleSpan);
10313
10572
  const collapse = document.createElement("div");
10314
10573
  collapse.id = collapseId;
10315
10574
  collapse.classList.add("collapse");
@@ -10859,8 +11118,11 @@ class ThemeBootstrap5 extends Theme {
10859
11118
  }
10860
11119
  chevron.classList.add("d-inline-block", "me-2");
10861
11120
  chevron.style.transition = "transform 0.1s ease";
11121
+ const titleSpan = document.createElement("span");
11122
+ titleSpan.classList.add("me-1");
11123
+ titleSpan.textContent = config.title;
10862
11124
  toggle.appendChild(chevron);
10863
- toggle.appendChild(document.createTextNode(config.title));
11125
+ toggle.appendChild(titleSpan);
10864
11126
  const collapse = document.createElement("div");
10865
11127
  collapse.id = collapseId;
10866
11128
  collapse.classList.add("accordion-collapse", "collapse");