jedison 1.15.0 → 1.16.1

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 {
@@ -5094,6 +5261,7 @@ class EditorArrayTable extends EditorArray {
5094
5261
  table.thead.appendChild(th);
5095
5262
  }
5096
5263
  this.instance.children.forEach((child, index2) => {
5264
+ var _a, _b;
5097
5265
  const tbodyRow = document.createElement("tr");
5098
5266
  const buttonsTd = this.theme.getTableDefinition({ isButtonColumn: true });
5099
5267
  const { deleteBtn, moveUpBtn, moveDownBtn, dragBtn, btnGroup, addAfterBtn } = this.getButtons(index2);
@@ -5116,6 +5284,7 @@ class EditorArrayTable extends EditorArray {
5116
5284
  }
5117
5285
  const td = this.theme.getTableDefinition();
5118
5286
  child.ui.adaptForTable(child, td);
5287
+ (_b = (_a = child.ui.control.info) == null ? void 0 : _a.container) == null ? void 0 : _b.remove();
5119
5288
  td.appendChild(child.ui.control.container);
5120
5289
  tbodyRow.appendChild(td);
5121
5290
  if (arrayButtonsPosition === "right") {
@@ -5244,6 +5413,7 @@ class EditorArrayTableObject extends EditorArray {
5244
5413
  table.thead.appendChild(th);
5245
5414
  }
5246
5415
  this.instance.children.forEach((child, index2) => {
5416
+ var _a, _b;
5247
5417
  const tbodyRow = document.createElement("tr");
5248
5418
  const buttonsTd = this.theme.getTableDefinition({ isButtonColumn: true });
5249
5419
  const { deleteBtn, moveUpBtn, moveDownBtn, dragBtn, btnGroup, addAfterBtn } = this.getButtons(index2);
@@ -5266,14 +5436,17 @@ class EditorArrayTableObject extends EditorArray {
5266
5436
  }
5267
5437
  if (child.children.length) {
5268
5438
  child.children.forEach((grandchild) => {
5439
+ var _a2, _b2;
5269
5440
  const td = this.theme.getTableDefinition();
5270
5441
  grandchild.ui.adaptForTable(td);
5442
+ (_b2 = (_a2 = grandchild.ui.control.info) == null ? void 0 : _a2.container) == null ? void 0 : _b2.remove();
5271
5443
  td.appendChild(grandchild.ui.control.container);
5272
5444
  tbodyRow.appendChild(td);
5273
5445
  });
5274
5446
  } else {
5275
5447
  const td = this.theme.getTableDefinition();
5276
5448
  child.ui.adaptForTable(td);
5449
+ (_b = (_a = child.ui.control.info) == null ? void 0 : _a.container) == null ? void 0 : _b.remove();
5277
5450
  td.appendChild(child.ui.control.container);
5278
5451
  tbodyRow.appendChild(td);
5279
5452
  }
@@ -5443,6 +5616,9 @@ class EditorArrayNav extends EditorArray {
5443
5616
  const hasNavFormat = regex.test(format2);
5444
5617
  return getSchemaType(schema) === "array" && hasNavFormat;
5445
5618
  }
5619
+ static providesChildHeading() {
5620
+ return true;
5621
+ }
5446
5622
  navigateTo(path) {
5447
5623
  const nextChildPath = this.getNextChildPath(path);
5448
5624
  if (nextChildPath) {
@@ -5597,7 +5773,7 @@ class EditorMultiple extends Editor {
5597
5773
  }
5598
5774
  build() {
5599
5775
  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");
5776
+ this.embedSwitcher = this.instance.jedison.getOption("embedSwitcher");
5601
5777
  this.control = this.theme.getMultipleControl({
5602
5778
  titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
5603
5779
  id: this.getIdFromPath(this.instance.path),
@@ -5609,6 +5785,7 @@ class EditorMultiple extends Editor {
5609
5785
  if (this.embedSwitcher) {
5610
5786
  this.control.header.style.display = "none";
5611
5787
  }
5788
+ this.switcherWrapper = this.theme.getSwitcherOwner();
5612
5789
  this.instance.on("change", (initiator) => {
5613
5790
  if (initiator === "api") return;
5614
5791
  const jedison = this.instance.jedison;
@@ -5663,35 +5840,22 @@ class EditorMultiple extends Editor {
5663
5840
  }
5664
5841
  }
5665
5842
  refreshUI() {
5666
- var _a;
5667
5843
  this.refreshDisabledState();
5668
5844
  this.control.childrenSlot.innerHTML = "";
5669
5845
  this.control.childrenSlot.appendChild(this.instance.activeInstance.ui.control.container);
5670
- if (this.embedSwitcher) {
5671
- const slot = this.instance.activeInstance.ui.control.switcherSlot;
5846
+ const slot = this.instance.activeInstance.ui.control.switcherSlot;
5847
+ this.control.switcherSlot = slot;
5848
+ if (this.embedSwitcher || this.switcherInput === "modal" || this.switcherInput === "select-inline") {
5672
5849
  if (slot) {
5673
- slot.innerHTML = "";
5674
- slot.appendChild(this.control.switcher.container);
5850
+ this.switcherWrapper.innerHTML = "";
5851
+ this.switcherWrapper.appendChild(this.control.switcher.container);
5852
+ this.insertBySwitcherDepth(slot, this.switcherWrapper, this.getNestedSwitcherDepth(this.instance.activeInstance));
5675
5853
  this.control.header.style.display = "none";
5676
5854
  } else {
5677
5855
  this.control.header.style.display = "";
5678
5856
  this.control.header.appendChild(this.control.switcher.container);
5679
5857
  }
5680
5858
  }
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
5859
  if (this.switcherInput === "select") {
5696
5860
  this.control.switcher.input.value = this.instance.index;
5697
5861
  }
@@ -5719,6 +5883,23 @@ class EditorMultiple extends Editor {
5719
5883
  getErrorFeedback(config) {
5720
5884
  return this.theme.getAlert(config);
5721
5885
  }
5886
+ // Counts how many EditorMultiple owners sit between `instance` and the real
5887
+ // leaf control, so stacked switchers in a shared slot can be ordered
5888
+ // outer-first instead of shuffling with every refresh.
5889
+ getNestedSwitcherDepth(instance) {
5890
+ if (!instance || !instance.activeInstance) return 0;
5891
+ const childDepth = this.getNestedSwitcherDepth(instance.activeInstance);
5892
+ return instance.ui.switcherWrapper ? childDepth + 1 : childDepth;
5893
+ }
5894
+ insertBySwitcherDepth(slot, wrapper, depth) {
5895
+ wrapper.dataset.switcherDepth = depth;
5896
+ const sibling = Array.from(slot.children).find((child) => Number(child.dataset.switcherDepth) < depth);
5897
+ if (sibling) {
5898
+ slot.insertBefore(wrapper, sibling);
5899
+ } else {
5900
+ slot.appendChild(wrapper);
5901
+ }
5902
+ }
5722
5903
  }
5723
5904
  class EditorNull extends Editor {
5724
5905
  static resolves(schema) {
@@ -6302,37 +6483,24 @@ class EditorArrayCheckboxes extends Editor {
6302
6483
  const values = this.getEnumSourceValues();
6303
6484
  const schemaItems = this.instance.schema.items || {};
6304
6485
  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
6486
  this.control.checkboxControls.forEach((cc) => {
6310
6487
  if (cc.parentNode) cc.parentNode.removeChild(cc);
6311
6488
  });
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);
6489
+ const rebuilt = this.theme.getCheckboxesControl({
6490
+ title: this.getTitle(),
6491
+ description: this.getDescription(),
6492
+ values,
6493
+ titles,
6494
+ id: this.getIdFromPath(this.instance.path),
6495
+ titleHidden: getSchemaXOption(this.instance.schema, "titleHidden"),
6496
+ inline: getSchemaXOption(this.instance.schema, "format") === "checkboxes-inline",
6497
+ info: this.getInfo()
6498
+ });
6499
+ this.control.checkboxes = rebuilt.checkboxes;
6500
+ this.control.labels = rebuilt.labels;
6501
+ this.control.labelTexts = rebuilt.labelTexts;
6502
+ this.control.checkboxControls = rebuilt.checkboxControls;
6503
+ this.control.checkboxControls.forEach((checkboxControl) => {
6336
6504
  this.control.fieldset.insertBefore(checkboxControl, this.control.description);
6337
6505
  });
6338
6506
  this.addDragHandles();
@@ -6982,6 +7150,7 @@ class Jedison extends EventEmitter {
6982
7150
  editJsonData: false,
6983
7151
  enablePropertiesToggle: false,
6984
7152
  enableCollapseToggle: false,
7153
+ autoFlat: false,
6985
7154
  btnContents: true,
6986
7155
  btnIcons: true,
6987
7156
  arrayDelete: true,
@@ -7307,6 +7476,20 @@ class Jedison extends EventEmitter {
7307
7476
  }
7308
7477
  });
7309
7478
  }
7479
+ if (this.isEditor) {
7480
+ const schemaTypeArray = getSchemaType(config.schema);
7481
+ if (isArray(schemaTypeArray)) {
7482
+ const inferTypeSource = getSchemaXOption(config.schema, "inferType");
7483
+ if (isSet(inferTypeSource)) {
7484
+ const siblingPath = resolveInstancePath(config.path, inferTypeSource);
7485
+ const siblingInstance = this.getInstance(siblingPath);
7486
+ const siblingValue = siblingInstance ? siblingInstance.getValue() : void 0;
7487
+ if (isSet(siblingValue) && schemaTypeArray.includes(siblingValue)) {
7488
+ config.schema = { ...config.schema, type: siblingValue };
7489
+ }
7490
+ }
7491
+ }
7492
+ }
7310
7493
  const schemaOneOf = getSchemaOneOf(config.schema);
7311
7494
  const schemaAnyOf = getSchemaAnyOf(config.schema);
7312
7495
  const schemaIf = getSchemaIf(config.schema);
@@ -7886,6 +8069,25 @@ class Theme {
7886
8069
  html.classList.add("jedi-children-slot");
7887
8070
  return html;
7888
8071
  }
8072
+ /**
8073
+ * Wrapper used by EditorMultiple to embed an inline switcher next to a control's title
8074
+ */
8075
+ getSwitcherSlot() {
8076
+ const html = document.createElement("span");
8077
+ html.classList.add("jedi-switcher-slot");
8078
+ return html;
8079
+ }
8080
+ /**
8081
+ * Per-owner wrapper inside a switcherSlot — lets more than one EditorMultiple
8082
+ * embed a switcher into the same slot (e.g. nested anyOf/oneOf) without one
8083
+ * overwriting the other.
8084
+ */
8085
+ getSwitcherOwner() {
8086
+ const html = document.createElement("span");
8087
+ html.classList.add("jedi-switcher-owner");
8088
+ html.style.marginInlineStart = "0.25rem";
8089
+ return html;
8090
+ }
7889
8091
  /**
7890
8092
  * Wrapper for error messages
7891
8093
  */
@@ -8150,6 +8352,42 @@ class Theme {
8150
8352
  button.appendChild(text);
8151
8353
  return button;
8152
8354
  }
8355
+ /**
8356
+ * A schema-defined action button (x-buttons keyword).
8357
+ *
8358
+ * This method only renders: the label is expected to be already sanitized by
8359
+ * the editor (Editor.purifyContent(), decision 9c) and the attributes are
8360
+ * expected to be already filtered against the allowlist by the editor
8361
+ * (utils.filterAttributes(), decision 6a). The theme does not sanitize or
8362
+ * filter. No theme styling is applied, only the unstyled `jedi-x-button` hook
8363
+ * class (decision 4).
8364
+ * @param {object} config - Button config
8365
+ * @param {string} [config.label] - Pre-sanitized HTML label
8366
+ * @param {object} [config.attributes] - Pre-filtered HTML attributes
8367
+ * @return {HTMLButtonElement}
8368
+ */
8369
+ getXButton(config = {}) {
8370
+ const button = document.createElement("button");
8371
+ const label = document.createElement("span");
8372
+ button.classList.add("jedi-x-button");
8373
+ button.setAttribute("type", "button");
8374
+ label.classList.add("jedi-x-button-label");
8375
+ label.innerHTML = config.label ?? "";
8376
+ button.appendChild(label);
8377
+ const attributes = isObject(config.attributes) ? config.attributes : {};
8378
+ for (const [key, value] of Object.entries(attributes)) {
8379
+ if (key === "class") {
8380
+ String(value).split(" ").forEach((cls) => {
8381
+ if (cls) {
8382
+ button.classList.add(cls);
8383
+ }
8384
+ });
8385
+ } else {
8386
+ button.setAttribute(key, value);
8387
+ }
8388
+ }
8389
+ return button;
8390
+ }
8153
8391
  getAddPropertyButton(config) {
8154
8392
  const html = this.getButton(config);
8155
8393
  html.classList.add("jedi-add-property-btn");
@@ -8345,6 +8583,7 @@ class Theme {
8345
8583
  const messages = this.getMessagesSlot({
8346
8584
  id: messagesId
8347
8585
  });
8586
+ const switcherSlot = this.getSwitcherSlot();
8348
8587
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
8349
8588
  this.infoAsModal(info, config.id, config.info);
8350
8589
  }
@@ -8352,11 +8591,12 @@ class Theme {
8352
8591
  if (isObject(config.info)) {
8353
8592
  container.appendChild(info.container);
8354
8593
  }
8594
+ container.appendChild(switcherSlot);
8355
8595
  container.appendChild(placeholder);
8356
8596
  container.appendChild(description);
8357
8597
  container.appendChild(messages);
8358
8598
  container.appendChild(actions);
8359
- return { container, placeholder, label, info, labelText, description, messages, actions };
8599
+ return { container, placeholder, label, info, labelText, description, messages, actions, switcherSlot };
8360
8600
  }
8361
8601
  /**
8362
8602
  * Object control is a card containing multiple editors.
@@ -8422,7 +8662,7 @@ class Theme {
8422
8662
  propertiesContainer: quickAddPropertyContainer
8423
8663
  });
8424
8664
  const fieldset = this.getFieldset();
8425
- const { legend, infoContainer, legendText, right } = this.getLegend({
8665
+ const { legend, left, infoContainer, legendText, right } = this.getLegend({
8426
8666
  content: config.title,
8427
8667
  id: config.id,
8428
8668
  titleHidden: config.titleHidden,
@@ -8453,10 +8693,9 @@ class Theme {
8453
8693
  body.appendChild(description);
8454
8694
  }
8455
8695
  body.appendChild(messages);
8456
- const switcherSlot = document.createElement("div");
8457
- switcherSlot.classList.add("jedi-switcher-slot");
8696
+ const switcherSlot = this.getSwitcherSlot();
8697
+ left.appendChild(switcherSlot);
8458
8698
  if (config.readOnly === false) {
8459
- right.appendChild(switcherSlot);
8460
8699
  right.appendChild(actions);
8461
8700
  }
8462
8701
  body.appendChild(childrenSlot);
@@ -8511,6 +8750,9 @@ class Theme {
8511
8750
  const ariaLive = this.getPropertiesAriaLive();
8512
8751
  const messages = this.getMessagesSlot();
8513
8752
  const childrenSlot = this.getChildrenSlot();
8753
+ if (config.isAccordion || config.isAccordionProperties) {
8754
+ childrenSlot.id = "accordion-" + config.id;
8755
+ }
8514
8756
  const propertiesActivators = this.getPropertiesActivators();
8515
8757
  const info = this.getInfo(config.info);
8516
8758
  const description = this.getDescription({ content: config.description });
@@ -8537,8 +8779,7 @@ class Theme {
8537
8779
  const collapse = document.createElement("div");
8538
8780
  const collapseToggle = document.createElement("div");
8539
8781
  const infoContainer = document.createElement("div");
8540
- const switcherSlot = document.createElement("div");
8541
- switcherSlot.classList.add("jedi-switcher-slot");
8782
+ const switcherSlot = this.getSwitcherSlot();
8542
8783
  const legend = document.createElement("div");
8543
8784
  legend.classList.add("jedi-editor-legend");
8544
8785
  legend.style.display = "flex";
@@ -8559,6 +8800,7 @@ class Theme {
8559
8800
  this.visuallyHidden(legendText);
8560
8801
  }
8561
8802
  left.appendChild(legendText);
8803
+ left.appendChild(switcherSlot);
8562
8804
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
8563
8805
  this.infoAsModal(info, config.id, config.info);
8564
8806
  }
@@ -8567,8 +8809,6 @@ class Theme {
8567
8809
  }
8568
8810
  const innerWrapper = document.createElement("div");
8569
8811
  innerWrapper.appendChild(legend);
8570
- innerWrapper.appendChild(propertiesContainer);
8571
- innerWrapper.appendChild(quickAddPropertyContainer);
8572
8812
  container.appendChild(innerWrapper);
8573
8813
  if (config.addProperty) {
8574
8814
  quickAddPropertyContainer.appendChild(quickAddPropertyControl.container);
@@ -8579,11 +8819,12 @@ class Theme {
8579
8819
  }
8580
8820
  body.appendChild(messages);
8581
8821
  if (config.readOnly === false) {
8582
- right.appendChild(switcherSlot);
8583
8822
  right.appendChild(actions);
8584
8823
  }
8585
8824
  body.appendChild(childrenSlot);
8586
8825
  innerWrapper.appendChild(body);
8826
+ innerWrapper.appendChild(propertiesContainer);
8827
+ innerWrapper.appendChild(quickAddPropertyContainer);
8587
8828
  if (config.editJsonData) {
8588
8829
  actions.appendChild(jsonData.toggle);
8589
8830
  }
@@ -8676,8 +8917,11 @@ class Theme {
8676
8917
  chevron.style.display = "inline-block";
8677
8918
  chevron.style.transition = "transform 0.1s ease";
8678
8919
  chevron.style.marginRight = "0.5em";
8920
+ const titleSpan = document.createElement("span");
8921
+ titleSpan.style.marginRight = "0.5em";
8922
+ titleSpan.textContent = config.title;
8679
8923
  toggle.appendChild(chevron);
8680
- toggle.appendChild(document.createTextNode(config.title));
8924
+ toggle.appendChild(titleSpan);
8681
8925
  const collapse = document.createElement("div");
8682
8926
  collapse.classList.add("jedi-accordion-collapse");
8683
8927
  collapse.style.display = "none";
@@ -8730,7 +8974,7 @@ class Theme {
8730
8974
  const footer = this.getArrayFooter();
8731
8975
  const fieldset = this.getFieldset();
8732
8976
  const info = this.getInfo(config.info);
8733
- const { legend, legendText, infoContainer, right } = this.getLegend({
8977
+ const { legend, left, legendText, infoContainer, right } = this.getLegend({
8734
8978
  content: config.title,
8735
8979
  id: config.id,
8736
8980
  titleHidden: config.titleHidden,
@@ -8773,10 +9017,9 @@ class Theme {
8773
9017
  body.appendChild(description);
8774
9018
  }
8775
9019
  body.appendChild(messages);
8776
- const switcherSlot = document.createElement("div");
8777
- switcherSlot.classList.add("jedi-switcher-slot");
9020
+ const switcherSlot = this.getSwitcherSlot();
9021
+ left.appendChild(switcherSlot);
8778
9022
  if (config.readOnly === false) {
8779
- right.appendChild(switcherSlot);
8780
9023
  right.appendChild(actions);
8781
9024
  }
8782
9025
  actions.appendChild(btnGroup);
@@ -8969,6 +9212,7 @@ class Theme {
8969
9212
  content: config.description,
8970
9213
  id: descriptionId
8971
9214
  });
9215
+ const switcherSlot = this.getSwitcherSlot();
8972
9216
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
8973
9217
  this.infoAsModal(info, config.id, config.info);
8974
9218
  }
@@ -8976,11 +9220,12 @@ class Theme {
8976
9220
  if (isObject(config.info)) {
8977
9221
  container.appendChild(info.container);
8978
9222
  }
9223
+ container.appendChild(switcherSlot);
8979
9224
  container.appendChild(br);
8980
9225
  container.appendChild(description);
8981
9226
  container.appendChild(messages);
8982
9227
  container.appendChild(actions);
8983
- return { container, label, info, labelText, description, messages, actions };
9228
+ return { container, label, info, labelText, description, messages, actions, switcherSlot };
8984
9229
  }
8985
9230
  /**
8986
9231
  * A Textarea
@@ -9007,6 +9252,7 @@ class Theme {
9007
9252
  const messages = this.getMessagesSlot({
9008
9253
  id: messagesId
9009
9254
  });
9255
+ const switcherSlot = this.getSwitcherSlot();
9010
9256
  input.setAttribute("aria-describedby", describedBy);
9011
9257
  input.setAttribute("id", config.id);
9012
9258
  input.setAttribute("name", config.id);
@@ -9018,11 +9264,12 @@ class Theme {
9018
9264
  if (isObject(config.info)) {
9019
9265
  container.appendChild(info.container);
9020
9266
  }
9267
+ container.appendChild(switcherSlot);
9021
9268
  container.appendChild(input);
9022
9269
  container.appendChild(description);
9023
9270
  container.appendChild(messages);
9024
9271
  container.appendChild(actions);
9025
- return { container, input, label, info, labelText, description, messages, actions };
9272
+ return { container, input, label, info, labelText, description, messages, actions, switcherSlot };
9026
9273
  }
9027
9274
  adaptForTableTextareaControl(control) {
9028
9275
  this.visuallyHidden(control.label);
@@ -9054,6 +9301,7 @@ class Theme {
9054
9301
  const messages = this.getMessagesSlot({
9055
9302
  id: messagesId
9056
9303
  });
9304
+ const switcherSlot = this.getSwitcherSlot();
9057
9305
  input.setAttribute("aria-describedby", describedBy);
9058
9306
  input.setAttribute("type", config.type);
9059
9307
  input.setAttribute("id", config.id);
@@ -9066,11 +9314,12 @@ class Theme {
9066
9314
  if (isObject(config.info)) {
9067
9315
  container.appendChild(info.container);
9068
9316
  }
9317
+ container.appendChild(switcherSlot);
9069
9318
  container.appendChild(input);
9070
9319
  container.appendChild(description);
9071
9320
  container.appendChild(messages);
9072
9321
  container.appendChild(actions);
9073
- return { container, input, label, info, labelText, description, messages, actions };
9322
+ return { container, input, label, info, labelText, description, messages, actions, switcherSlot };
9074
9323
  }
9075
9324
  getInputRangeControl(config) {
9076
9325
  const control = this.getInputControl(config);
@@ -9140,8 +9389,10 @@ class Theme {
9140
9389
  radios.push(radio);
9141
9390
  labels.push(label);
9142
9391
  });
9392
+ const switcherSlot = this.getSwitcherSlot();
9143
9393
  container.appendChild(fieldset);
9144
9394
  fieldset.appendChild(legend);
9395
+ legend.appendChild(switcherSlot);
9145
9396
  if (isObject(config.info)) {
9146
9397
  legendText.after(info.container);
9147
9398
  }
@@ -9164,7 +9415,8 @@ class Theme {
9164
9415
  labelTexts,
9165
9416
  radioControls,
9166
9417
  description,
9167
- messages
9418
+ messages,
9419
+ switcherSlot
9168
9420
  };
9169
9421
  }
9170
9422
  adaptForTableRadiosControl(control) {
@@ -9201,6 +9453,7 @@ class Theme {
9201
9453
  input.setAttribute("id", config.id);
9202
9454
  input.setAttribute("name", config.id);
9203
9455
  input.setAttribute("aria-describedby", describedBy);
9456
+ const switcherSlot = this.getSwitcherSlot();
9204
9457
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
9205
9458
  this.infoAsModal(info, config.id, config.info);
9206
9459
  }
@@ -9211,9 +9464,10 @@ class Theme {
9211
9464
  if (isObject(config.info)) {
9212
9465
  formGroup.appendChild(info.container);
9213
9466
  }
9467
+ formGroup.appendChild(switcherSlot);
9214
9468
  formGroup.appendChild(description);
9215
9469
  formGroup.appendChild(messages);
9216
- return { container, formGroup, input, label, info, labelText, description, messages, actions };
9470
+ return { container, formGroup, input, label, info, labelText, description, messages, actions, switcherSlot };
9217
9471
  }
9218
9472
  adaptForTableCheckboxControl(control, td) {
9219
9473
  this.visuallyHidden(control.label);
@@ -9270,8 +9524,10 @@ class Theme {
9270
9524
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
9271
9525
  this.infoAsModal(info, config.id, config.info);
9272
9526
  }
9527
+ const switcherSlot = this.getSwitcherSlot();
9273
9528
  container.appendChild(fieldset);
9274
9529
  fieldset.appendChild(legend);
9530
+ legend.appendChild(switcherSlot);
9275
9531
  if (isObject(config.info)) {
9276
9532
  legendText.after(info.container);
9277
9533
  }
@@ -9293,7 +9549,8 @@ class Theme {
9293
9549
  labelTexts,
9294
9550
  checkboxControls,
9295
9551
  description,
9296
- messages
9552
+ messages,
9553
+ switcherSlot
9297
9554
  };
9298
9555
  }
9299
9556
  adaptForTableCheckboxesControl(control, td) {
@@ -9336,6 +9593,7 @@ class Theme {
9336
9593
  }
9337
9594
  input.appendChild(option);
9338
9595
  });
9596
+ const switcherSlot = this.getSwitcherSlot();
9339
9597
  if (((_a = config == null ? void 0 : config.info) == null ? void 0 : _a.variant) === "modal") {
9340
9598
  this.infoAsModal(info, config.id, config.info);
9341
9599
  }
@@ -9343,11 +9601,12 @@ class Theme {
9343
9601
  if (isObject(config.info)) {
9344
9602
  container.appendChild(info.container);
9345
9603
  }
9604
+ container.appendChild(switcherSlot);
9346
9605
  container.appendChild(input);
9347
9606
  container.appendChild(description);
9348
9607
  container.appendChild(messages);
9349
9608
  container.appendChild(actions);
9350
- return { container, input, label, info, labelText, description, messages, actions };
9609
+ return { container, input, label, info, labelText, description, messages, actions, switcherSlot };
9351
9610
  }
9352
9611
  adaptForTableSelectControl(control) {
9353
9612
  this.visuallyHidden(control.label);
@@ -9780,8 +10039,11 @@ class ThemeBootstrap3 extends Theme {
9780
10039
  chevron.style.display = "inline-block";
9781
10040
  chevron.style.transition = "transform 0.1s ease";
9782
10041
  chevron.style.marginRight = "0.5em";
10042
+ const titleSpan = document.createElement("span");
10043
+ titleSpan.style.marginRight = "0.5em";
10044
+ titleSpan.textContent = config.title;
9783
10045
  toggle.appendChild(chevron);
9784
- toggle.appendChild(document.createTextNode(config.title));
10046
+ toggle.appendChild(titleSpan);
9785
10047
  const collapse = document.createElement("div");
9786
10048
  collapse.id = collapseId;
9787
10049
  collapse.classList.add("panel-collapse", "collapse");
@@ -10308,8 +10570,11 @@ class ThemeBootstrap4 extends Theme {
10308
10570
  }
10309
10571
  chevron.classList.add("d-inline-block", "mr-2");
10310
10572
  chevron.style.transition = "transform 0.1s ease";
10573
+ const titleSpan = document.createElement("span");
10574
+ titleSpan.classList.add("mr-1");
10575
+ titleSpan.textContent = config.title;
10311
10576
  toggle.appendChild(chevron);
10312
- toggle.appendChild(document.createTextNode(config.title));
10577
+ toggle.appendChild(titleSpan);
10313
10578
  const collapse = document.createElement("div");
10314
10579
  collapse.id = collapseId;
10315
10580
  collapse.classList.add("collapse");
@@ -10859,8 +11124,11 @@ class ThemeBootstrap5 extends Theme {
10859
11124
  }
10860
11125
  chevron.classList.add("d-inline-block", "me-2");
10861
11126
  chevron.style.transition = "transform 0.1s ease";
11127
+ const titleSpan = document.createElement("span");
11128
+ titleSpan.classList.add("me-1");
11129
+ titleSpan.textContent = config.title;
10862
11130
  toggle.appendChild(chevron);
10863
- toggle.appendChild(document.createTextNode(config.title));
11131
+ toggle.appendChild(titleSpan);
10864
11132
  const collapse = document.createElement("div");
10865
11133
  collapse.id = collapseId;
10866
11134
  collapse.classList.add("accordion-collapse", "collapse");