@structured-field/widget-editor 1.1.0 → 1.2.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.
@@ -1,4 +1,4 @@
1
- var script$c = {
1
+ var script$d = {
2
2
  name: 'StringEditor',
3
3
  props: {
4
4
  schema: { type: Object, required: true },
@@ -1583,6 +1583,44 @@ const toReadonly = (value) => isObject(value) ? /* @__PURE__ */ readonly(value)
1583
1583
  function isRef(r) {
1584
1584
  return r ? r["__v_isRef"] === true : false;
1585
1585
  }
1586
+ // @__NO_SIDE_EFFECTS__
1587
+ function ref(value) {
1588
+ return createRef(value, false);
1589
+ }
1590
+ function createRef(rawValue, shallow) {
1591
+ if (/* @__PURE__ */ isRef(rawValue)) {
1592
+ return rawValue;
1593
+ }
1594
+ return new RefImpl(rawValue, shallow);
1595
+ }
1596
+ class RefImpl {
1597
+ constructor(value, isShallow2) {
1598
+ this.dep = new Dep();
1599
+ this["__v_isRef"] = true;
1600
+ this["__v_isShallow"] = false;
1601
+ this._rawValue = isShallow2 ? value : toRaw(value);
1602
+ this._value = isShallow2 ? value : toReactive(value);
1603
+ this["__v_isShallow"] = isShallow2;
1604
+ }
1605
+ get value() {
1606
+ {
1607
+ this.dep.track();
1608
+ }
1609
+ return this._value;
1610
+ }
1611
+ set value(newValue) {
1612
+ const oldValue = this._rawValue;
1613
+ const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue);
1614
+ newValue = useDirectValue ? newValue : toRaw(newValue);
1615
+ if (hasChanged(newValue, oldValue)) {
1616
+ this._rawValue = newValue;
1617
+ this._value = useDirectValue ? newValue : toReactive(newValue);
1618
+ {
1619
+ this.dep.trigger();
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1586
1624
  function unref(ref2) {
1587
1625
  return /* @__PURE__ */ isRef(ref2) ? ref2.value : ref2;
1588
1626
  }
@@ -5974,6 +6012,32 @@ const computed = (getterOrOptions, debugOptions) => {
5974
6012
  return c;
5975
6013
  };
5976
6014
 
6015
+ function h(type, propsOrChildren, children) {
6016
+ try {
6017
+ setBlockTracking(-1);
6018
+ const l = arguments.length;
6019
+ if (l === 2) {
6020
+ if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
6021
+ if (isVNode(propsOrChildren)) {
6022
+ return createVNode(type, null, [propsOrChildren]);
6023
+ }
6024
+ return createVNode(type, propsOrChildren);
6025
+ } else {
6026
+ return createVNode(type, null, propsOrChildren);
6027
+ }
6028
+ } else {
6029
+ if (l > 3) {
6030
+ children = Array.prototype.slice.call(arguments, 2);
6031
+ } else if (l === 3 && isVNode(children)) {
6032
+ children = [children];
6033
+ }
6034
+ return createVNode(type, propsOrChildren, children);
6035
+ }
6036
+ } finally {
6037
+ setBlockTracking(1);
6038
+ }
6039
+ }
6040
+
5977
6041
  const version = "3.5.30";
5978
6042
 
5979
6043
  /**
@@ -7044,10 +7108,10 @@ function render$c(_ctx, _cache, $props, $setup, $data, $options) {
7044
7108
  ], 2 /* CLASS */))
7045
7109
  }
7046
7110
 
7047
- script$c.render = render$c;
7048
- script$c.__file = "src/editors/StringEditor.vue";
7111
+ script$d.render = render$c;
7112
+ script$d.__file = "src/editors/StringEditor.vue";
7049
7113
 
7050
- var script$b = {
7114
+ var script$c = {
7051
7115
  name: 'NumberEditor',
7052
7116
  props: {
7053
7117
  schema: { type: Object, required: true },
@@ -7120,10 +7184,10 @@ function render$b(_ctx, _cache, $props, $setup, $data, $options) {
7120
7184
  ], 2 /* CLASS */))
7121
7185
  }
7122
7186
 
7123
- script$b.render = render$b;
7124
- script$b.__file = "src/editors/NumberEditor.vue";
7187
+ script$c.render = render$b;
7188
+ script$c.__file = "src/editors/NumberEditor.vue";
7125
7189
 
7126
- var script$a = {
7190
+ var script$b = {
7127
7191
  name: 'BooleanEditor',
7128
7192
  props: {
7129
7193
  schema: { type: Object, required: true },
@@ -7179,10 +7243,10 @@ function render$a(_ctx, _cache, $props, $setup, $data, $options) {
7179
7243
  ], 2 /* CLASS */))
7180
7244
  }
7181
7245
 
7182
- script$a.render = render$a;
7183
- script$a.__file = "src/editors/BooleanEditor.vue";
7246
+ script$b.render = render$a;
7247
+ script$b.__file = "src/editors/BooleanEditor.vue";
7184
7248
 
7185
- var script$9 = {
7249
+ var script$a = {
7186
7250
  name: 'SelectEditor',
7187
7251
  props: {
7188
7252
  schema: { type: Object, required: true },
@@ -7251,10 +7315,10 @@ function render$9(_ctx, _cache, $props, $setup, $data, $options) {
7251
7315
  ], 2 /* CLASS */))
7252
7316
  }
7253
7317
 
7254
- script$9.render = render$9;
7255
- script$9.__file = "src/editors/SelectEditor.vue";
7318
+ script$a.render = render$9;
7319
+ script$a.__file = "src/editors/SelectEditor.vue";
7256
7320
 
7257
- var script$8 = {
7321
+ var script$9 = {
7258
7322
  name: 'HiddenEditor',
7259
7323
  props: {
7260
7324
  schema: { type: Object, required: true },
@@ -7288,10 +7352,10 @@ function render$8(_ctx, _cache, $props, $setup, $data, $options) {
7288
7352
  return (openBlock(), createElementBlock("div", _hoisted_1$7))
7289
7353
  }
7290
7354
 
7291
- script$8.render = render$8;
7292
- script$8.__file = "src/editors/HiddenEditor.vue";
7355
+ script$9.render = render$8;
7356
+ script$9.__file = "src/editors/HiddenEditor.vue";
7293
7357
 
7294
- var script$7 = {
7358
+ var script$8 = {
7295
7359
  name: 'SfIcon',
7296
7360
  props: {
7297
7361
  name: { type: String, required: true },
@@ -7383,15 +7447,264 @@ function render$7(_ctx, _cache, $props, $setup, $data, $options) {
7383
7447
  ], 8 /* PROPS */, _hoisted_1$6))
7384
7448
  }
7385
7449
 
7386
- script$7.render = render$7;
7387
- script$7.__file = "src/editors/SfIcon.vue";
7450
+ script$8.render = render$7;
7451
+ script$8.__file = "src/editors/SfIcon.vue";
7452
+
7453
+ // JSON Schema conditional evaluation for form rendering.
7454
+ //
7455
+ // Supports the standard keywords: `if`/`then`/`else`, `allOf` of those,
7456
+ // `dependentSchemas`, and `dependentRequired`. The matcher implements the
7457
+ // subset of JSON Schema validation that is meaningful for form-time
7458
+ // conditionals on object properties:
7459
+ //
7460
+ // - `properties: { field: { const, enum, type, not } }`
7461
+ // - `required: [...]` (treated as "key is present and not null/undefined")
7462
+ // - `not`, `allOf`, `anyOf`, `oneOf` (recursive)
7463
+ //
7464
+ // The functions are pure: they take a schema + value and return an
7465
+ // "effective schema" with `properties`/`required` merged from any matching
7466
+ // branches. The renderer uses that effective schema instead of the raw one.
7467
+
7468
+ function isPresent(value, key) {
7469
+ if (value == null || typeof value !== 'object') return false;
7470
+ if (!(key in value)) return false;
7471
+ const v = value[key];
7472
+ return v !== undefined && v !== null && v !== '';
7473
+ }
7474
+
7475
+ function matchesPropertyConstraint(value, constraint) {
7476
+ if (!constraint || typeof constraint !== 'object') return true;
7477
+ if ('const' in constraint) return value === constraint.const;
7478
+ if (Array.isArray(constraint.enum)) return constraint.enum.includes(value);
7479
+ if (constraint.type) {
7480
+ const t = constraint.type;
7481
+ if (t === 'string' && typeof value !== 'string') return false;
7482
+ if (t === 'number' && typeof value !== 'number') return false;
7483
+ if (t === 'integer' && (typeof value !== 'number' || !Number.isInteger(value))) return false;
7484
+ if (t === 'boolean' && typeof value !== 'boolean') return false;
7485
+ if (t === 'null' && value !== null) return false;
7486
+ if (t === 'array' && !Array.isArray(value)) return false;
7487
+ if (t === 'object' && (value == null || typeof value !== 'object' || Array.isArray(value))) return false;
7488
+ }
7489
+ // Numeric comparators
7490
+ if (typeof value === 'number') {
7491
+ if (typeof constraint.minimum === 'number' && value < constraint.minimum) return false;
7492
+ if (typeof constraint.maximum === 'number' && value > constraint.maximum) return false;
7493
+ if (typeof constraint.exclusiveMinimum === 'number' && value <= constraint.exclusiveMinimum) return false;
7494
+ if (typeof constraint.exclusiveMaximum === 'number' && value >= constraint.exclusiveMaximum) return false;
7495
+ if (typeof constraint.multipleOf === 'number' && constraint.multipleOf > 0) {
7496
+ const q = value / constraint.multipleOf;
7497
+ if (Math.abs(q - Math.round(q)) > 1e-9) return false;
7498
+ }
7499
+ }
7500
+ // String comparators
7501
+ if (typeof value === 'string') {
7502
+ if (typeof constraint.minLength === 'number' && value.length < constraint.minLength) return false;
7503
+ if (typeof constraint.maxLength === 'number' && value.length > constraint.maxLength) return false;
7504
+ if (typeof constraint.pattern === 'string') {
7505
+ try {
7506
+ if (!new RegExp(constraint.pattern).test(value)) return false;
7507
+ } catch (e) {
7508
+ // Invalid pattern — treat as non-match rather than throwing in render path.
7509
+ return false;
7510
+ }
7511
+ }
7512
+ }
7513
+ if (constraint.not) return !matchesSchema(value, constraint.not);
7514
+ return true;
7515
+ }
7388
7516
 
7389
- var script$6 = {
7517
+ // Returns true if `value` (an object) satisfies the form-relevant subset of `schema`.
7518
+ function matchesSchema(value, schema) {
7519
+ if (!schema || typeof schema !== 'object') return true;
7520
+
7521
+ if (Array.isArray(schema.required)) {
7522
+ for (const k of schema.required) {
7523
+ if (!isPresent(value, k)) return false;
7524
+ }
7525
+ }
7526
+
7527
+ if (schema.properties && typeof schema.properties === 'object') {
7528
+ for (const [k, constraint] of Object.entries(schema.properties)) {
7529
+ // Standard JSON Schema: property constraints only apply if the key is present.
7530
+ if (value == null || !(k in value)) continue;
7531
+ if (!matchesPropertyConstraint(value[k], constraint)) return false;
7532
+ }
7533
+ }
7534
+
7535
+ if (schema.not && matchesSchema(value, schema.not)) return false;
7536
+ if (Array.isArray(schema.allOf) && !schema.allOf.every((s) => matchesSchema(value, s))) return false;
7537
+ if (Array.isArray(schema.anyOf) && !schema.anyOf.some((s) => matchesSchema(value, s))) return false;
7538
+ if (Array.isArray(schema.oneOf)) {
7539
+ const matched = schema.oneOf.filter((s) => matchesSchema(value, s)).length;
7540
+ if (matched !== 1) return false;
7541
+ }
7542
+
7543
+ return true;
7544
+ }
7545
+
7546
+ function insertAfter(properties, anchorKey, newEntries) {
7547
+ // Rebuild the property map so newly-added keys appear immediately after
7548
+ // their controlling field instead of being appended to the end.
7549
+ const existingKeys = Object.keys(properties);
7550
+ const anchorIdx = anchorKey ? existingKeys.indexOf(anchorKey) : -1;
7551
+ if (anchorIdx === -1) {
7552
+ const out = { ...properties };
7553
+ for (const [k, v] of newEntries) out[k] = v;
7554
+ return out;
7555
+ }
7556
+ const out = {};
7557
+ for (let i = 0; i < existingKeys.length; i++) {
7558
+ const key = existingKeys[i];
7559
+ out[key] = properties[key];
7560
+ if (i === anchorIdx) {
7561
+ for (const [k, v] of newEntries) {
7562
+ if (!(k in properties)) out[k] = v;
7563
+ }
7564
+ }
7565
+ }
7566
+ // Any new keys that already existed in properties have been kept in place;
7567
+ // overwrite their values with the merged versions.
7568
+ for (const [k, v] of newEntries) {
7569
+ if (k in properties) out[k] = v;
7570
+ }
7571
+ return out;
7572
+ }
7573
+
7574
+ function mergeBranch(target, branch, anchorKey) {
7575
+ if (!branch || typeof branch !== 'object') return target;
7576
+
7577
+ if (branch.properties) {
7578
+ const merged = [];
7579
+ for (const [k, v] of Object.entries(branch.properties)) {
7580
+ const existing = target.properties && target.properties[k];
7581
+ merged.push([k, existing ? { ...existing, ...v } : v]);
7582
+ }
7583
+ target.properties = insertAfter(target.properties || {}, anchorKey, merged);
7584
+ }
7585
+
7586
+ if (Array.isArray(branch.required)) {
7587
+ const set = new Set(target.required || []);
7588
+ for (const k of branch.required) set.add(k);
7589
+ target.required = Array.from(set);
7590
+ }
7591
+
7592
+ // Conditionals can also nest more conditionals — flatten them.
7593
+ if (branch.allOf) {
7594
+ target.allOf = [...(target.allOf || []), ...branch.allOf];
7595
+ }
7596
+ if (branch.if) {
7597
+ target.allOf = [...(target.allOf || []), { if: branch.if, then: branch.then, else: branch.else }];
7598
+ }
7599
+ if (branch.dependentSchemas) {
7600
+ target.dependentSchemas = { ...(target.dependentSchemas || {}), ...branch.dependentSchemas };
7601
+ }
7602
+ if (branch.dependentRequired) {
7603
+ target.dependentRequired = { ...(target.dependentRequired || {}), ...branch.dependentRequired };
7604
+ }
7605
+
7606
+ return target;
7607
+ }
7608
+
7609
+ // Returns an effective schema for an object schema given the current value.
7610
+ // Resolves `if/then/else`, `allOf` of those, `dependentSchemas`, and
7611
+ // `dependentRequired`. Idempotent and safe to call on every render.
7612
+ //
7613
+ // `resolver` (optional) is called on each `then`/`else`/`dependentSchemas`
7614
+ // branch before merging, so `$ref` inside conditional branches is followed.
7615
+ // Pass `form.resolveSchema` from the renderer.
7616
+ function applyConditionals(schema, value, resolver) {
7617
+ const resolve = typeof resolver === 'function' ? resolver : (s) => s;
7618
+ if (!schema || typeof schema !== 'object') return schema;
7619
+ if (schema.type !== 'object' && !schema.properties) return schema;
7620
+
7621
+ // Start with a shallow clone of the parts we may mutate.
7622
+ let effective = {
7623
+ ...schema,
7624
+ properties: { ...(schema.properties || {}) },
7625
+ required: Array.isArray(schema.required) ? [...schema.required] : [],
7626
+ };
7627
+
7628
+ const safeValue = value && typeof value === 'object' ? value : {};
7629
+
7630
+ // Pick the controlling field of an `if` clause so newly-added properties
7631
+ // can be inserted right after it in render order.
7632
+ const anchorOf = (ifClause) => {
7633
+ if (!ifClause || typeof ifClause !== 'object') return null;
7634
+ const props = ifClause.properties && Object.keys(ifClause.properties);
7635
+ if (props && props.length) return props[0];
7636
+ if (Array.isArray(ifClause.required) && ifClause.required.length) return ifClause.required[0];
7637
+ return null;
7638
+ };
7639
+
7640
+ // Collect rules: top-level if/then/else + every entry in allOf that has one.
7641
+ const rules = [];
7642
+ if (effective.if) {
7643
+ rules.push({ if: effective.if, then: effective.then, else: effective.else, anchor: anchorOf(effective.if) });
7644
+ }
7645
+ if (Array.isArray(effective.allOf)) {
7646
+ for (const entry of effective.allOf) {
7647
+ if (entry && typeof entry === 'object' && entry.if) {
7648
+ rules.push({ if: entry.if, then: entry.then, else: entry.else, anchor: anchorOf(entry.if) });
7649
+ } else if (entry && typeof entry === 'object' && (entry.properties || entry.required)) {
7650
+ // Plain allOf branch (e.g. shared base) — always merge.
7651
+ mergeBranch(effective, entry);
7652
+ }
7653
+ }
7654
+ }
7655
+
7656
+ // Iterate to a fixed point so newly-merged rules can themselves trigger further rules.
7657
+ // Capped to avoid pathological loops.
7658
+ for (let i = 0; i < 8; i++) {
7659
+ let changed = false;
7660
+ const before = JSON.stringify({ p: effective.properties, r: effective.required });
7661
+
7662
+ for (const rule of rules) {
7663
+ const matched = matchesSchema(safeValue, rule.if);
7664
+ const branch = matched ? rule.then : rule.else;
7665
+ if (branch) mergeBranch(effective, resolve(branch), rule.anchor);
7666
+ }
7667
+
7668
+ if (effective.dependentSchemas) {
7669
+ for (const [key, branch] of Object.entries(effective.dependentSchemas)) {
7670
+ if (isPresent(safeValue, key)) mergeBranch(effective, resolve(branch), key);
7671
+ }
7672
+ }
7673
+
7674
+ if (effective.dependentRequired) {
7675
+ for (const [key, requiredKeys] of Object.entries(effective.dependentRequired)) {
7676
+ if (isPresent(safeValue, key) && Array.isArray(requiredKeys)) {
7677
+ const set = new Set(effective.required || []);
7678
+ for (const k of requiredKeys) set.add(k);
7679
+ effective.required = Array.from(set);
7680
+ }
7681
+ }
7682
+ }
7683
+
7684
+ const after = JSON.stringify({ p: effective.properties, r: effective.required });
7685
+ if (after !== before) changed = true;
7686
+ if (!changed) break;
7687
+ }
7688
+
7689
+ return effective;
7690
+ }
7691
+
7692
+ // Returns true if the schema declares any form-relevant conditional logic.
7693
+ function hasConditionals(schema) {
7694
+ if (!schema || typeof schema !== 'object') return false;
7695
+ if (schema.if || schema.dependentSchemas || schema.dependentRequired) return true;
7696
+ if (Array.isArray(schema.allOf)) {
7697
+ return schema.allOf.some((e) => e && typeof e === 'object' && (e.if || e.dependentSchemas));
7698
+ }
7699
+ return false;
7700
+ }
7701
+
7702
+ var script$7 = {
7390
7703
  name: 'ObjectEditor',
7391
7704
  beforeCreate() {
7392
7705
  if (!this.$options.components) this.$options.components = {};
7393
7706
  this.$options.components.SchemaEditor = script$1;
7394
- this.$options.components.SfIcon = script$7;
7707
+ this.$options.components.SfIcon = script$8;
7395
7708
  },
7396
7709
  props: {
7397
7710
  schema: { type: Object, required: true },
@@ -7412,10 +7725,14 @@ var script$6 = {
7412
7725
  title() {
7413
7726
  return this.schema.title || this.humanize(this.path[this.path.length - 1]) || '';
7414
7727
  },
7728
+ effectiveSchema() {
7729
+ if (!hasConditionals(this.schema)) return this.schema;
7730
+ return applyConditionals(this.schema, this.modelValue || {}, this.form?.resolveSchema);
7731
+ },
7415
7732
  summary() {
7416
7733
  const val = this.modelValue || {};
7417
7734
  const parts = [];
7418
- for (const key of Object.keys(this.schema.properties || {})) {
7735
+ for (const key of Object.keys(this.effectiveSchema.properties || {})) {
7419
7736
  if (parts.length >= 3) break;
7420
7737
  const v = val[key];
7421
7738
  if (v !== null && v !== undefined && v !== '' && typeof v !== 'object') {
@@ -7438,7 +7755,22 @@ var script$6 = {
7438
7755
  },
7439
7756
  onChildChange(key, value) {
7440
7757
  const newVal = { ...(this.modelValue || {}), [key]: value };
7441
- this.$emit('update:modelValue', newVal);
7758
+ this.$emit('update:modelValue', this.pruneInactive(newVal));
7759
+ },
7760
+ pruneInactive(value) {
7761
+ if (!hasConditionals(this.schema)) return value;
7762
+ const effective = applyConditionals(this.schema, value, this.form?.resolveSchema);
7763
+ const allowed = new Set(Object.keys(effective.properties || {}));
7764
+ let changed = false;
7765
+ const out = {};
7766
+ for (const k of Object.keys(value)) {
7767
+ if (allowed.has(k)) {
7768
+ out[k] = value[k];
7769
+ } else {
7770
+ changed = true;
7771
+ }
7772
+ }
7773
+ return changed ? out : value;
7442
7774
  },
7443
7775
  },
7444
7776
  };
@@ -7464,7 +7796,7 @@ function render$6(_ctx, _cache, $props, $setup, $data, $options) {
7464
7796
  return ($options.isRoot)
7465
7797
  ? (openBlock(), createElementBlock("div", _hoisted_1$5, [
7466
7798
  createBaseVNode("div", _hoisted_2$4, [
7467
- (openBlock(true), createElementBlock(Fragment, null, renderList(($props.schema.properties || {}), (propSchema, key) => {
7799
+ (openBlock(true), createElementBlock(Fragment, null, renderList(($options.effectiveSchema.properties || {}), (propSchema, key) => {
7468
7800
  return (openBlock(), createBlock(_component_SchemaEditor, {
7469
7801
  key: key,
7470
7802
  schema: $props.form.resolveSchema(propSchema),
@@ -7498,7 +7830,7 @@ function render$6(_ctx, _cache, $props, $setup, $data, $options) {
7498
7830
  : createCommentVNode("v-if", true)
7499
7831
  ]),
7500
7832
  withDirectives(createBaseVNode("div", _hoisted_7$2, [
7501
- (openBlock(true), createElementBlock(Fragment, null, renderList(($props.schema.properties || {}), (propSchema, key) => {
7833
+ (openBlock(true), createElementBlock(Fragment, null, renderList(($options.effectiveSchema.properties || {}), (propSchema, key) => {
7502
7834
  return (openBlock(), createBlock(_component_SchemaEditor, {
7503
7835
  key: key,
7504
7836
  schema: $props.form.resolveSchema(propSchema),
@@ -7514,8 +7846,8 @@ function render$6(_ctx, _cache, $props, $setup, $data, $options) {
7514
7846
  ], 2 /* CLASS */))
7515
7847
  }
7516
7848
 
7517
- script$6.render = render$6;
7518
- script$6.__file = "src/editors/ObjectEditor.vue";
7849
+ script$7.render = render$6;
7850
+ script$7.__file = "src/editors/ObjectEditor.vue";
7519
7851
 
7520
7852
  function debounce(fn, delay) {
7521
7853
  let timer;
@@ -7558,12 +7890,12 @@ function getDefaultForSchema(schema) {
7558
7890
 
7559
7891
  let keyCounter = 0;
7560
7892
 
7561
- var script$5 = {
7893
+ var script$6 = {
7562
7894
  name: 'ArrayEditor',
7563
7895
  beforeCreate() {
7564
7896
  if (!this.$options.components) this.$options.components = {};
7565
7897
  this.$options.components.SchemaEditor = script$1;
7566
- this.$options.components.SfIcon = script$7;
7898
+ this.$options.components.SfIcon = script$8;
7567
7899
  },
7568
7900
  props: {
7569
7901
  schema: { type: Object, required: true },
@@ -7747,13 +8079,16 @@ function render$5(_ctx, _cache, $props, $setup, $data, $options) {
7747
8079
  createVNode(_component_SfIcon, { name: "arrow-up" })
7748
8080
  ], 8 /* PROPS */, _hoisted_12))
7749
8081
  : createCommentVNode("v-if", true),
7750
- createBaseVNode("button", {
7751
- type: "button",
7752
- class: "sf-btn sf-btn-sm",
7753
- onClick: $event => ($options.moveItem(index, 1))
7754
- }, [
7755
- createVNode(_component_SfIcon, { name: "arrow-down" })
7756
- ], 8 /* PROPS */, _hoisted_13),
8082
+ (index < $data.items.length - 1)
8083
+ ? (openBlock(), createElementBlock("button", {
8084
+ key: 1,
8085
+ type: "button",
8086
+ class: "sf-btn sf-btn-sm",
8087
+ onClick: $event => ($options.moveItem(index, 1))
8088
+ }, [
8089
+ createVNode(_component_SfIcon, { name: "arrow-down" })
8090
+ ], 8 /* PROPS */, _hoisted_13))
8091
+ : createCommentVNode("v-if", true),
7757
8092
  createBaseVNode("button", {
7758
8093
  type: "button",
7759
8094
  class: "sf-btn sf-btn-sm sf-btn-danger",
@@ -7791,15 +8126,15 @@ function render$5(_ctx, _cache, $props, $setup, $data, $options) {
7791
8126
  ], 2 /* CLASS */))
7792
8127
  }
7793
8128
 
7794
- script$5.render = render$5;
7795
- script$5.__file = "src/editors/ArrayEditor.vue";
8129
+ script$6.render = render$5;
8130
+ script$6.__file = "src/editors/ArrayEditor.vue";
7796
8131
 
7797
- var script$4 = {
8132
+ var script$5 = {
7798
8133
  name: 'NullableEditor',
7799
8134
  beforeCreate() {
7800
8135
  if (!this.$options.components) this.$options.components = {};
7801
8136
  this.$options.components.SchemaEditor = script$1;
7802
- this.$options.components.SfIcon = script$7;
8137
+ this.$options.components.SfIcon = script$8;
7803
8138
  },
7804
8139
  props: {
7805
8140
  schema: { type: Object, required: true },
@@ -7915,10 +8250,10 @@ function render$4(_ctx, _cache, $props, $setup, $data, $options) {
7915
8250
  ], 2 /* CLASS */))
7916
8251
  }
7917
8252
 
7918
- script$4.render = render$4;
7919
- script$4.__file = "src/editors/NullableEditor.vue";
8253
+ script$5.render = render$4;
8254
+ script$5.__file = "src/editors/NullableEditor.vue";
7920
8255
 
7921
- var script$3 = {
8256
+ var script$4 = {
7922
8257
  name: 'UnionEditor',
7923
8258
  beforeCreate() {
7924
8259
  if (!this.$options.components) this.$options.components = {};
@@ -8018,12 +8353,12 @@ function render$3(_ctx, _cache, $props, $setup, $data, $options) {
8018
8353
  ]))
8019
8354
  }
8020
8355
 
8021
- script$3.render = render$3;
8022
- script$3.__file = "src/editors/UnionEditor.vue";
8356
+ script$4.render = render$3;
8357
+ script$4.__file = "src/editors/UnionEditor.vue";
8023
8358
 
8024
- var script$2 = {
8359
+ var script$3 = {
8025
8360
  name: 'RelationEditor',
8026
- components: { SfIcon: script$7 },
8361
+ components: { SfIcon: script$8 },
8027
8362
  props: {
8028
8363
  schema: { type: Object, required: true },
8029
8364
  modelValue: { default: null },
@@ -8291,24 +8626,82 @@ function render$2(_ctx, _cache, $props, $setup, $data, $options) {
8291
8626
  ], 2 /* CLASS */))
8292
8627
  }
8293
8628
 
8294
- script$2.render = render$2;
8295
- script$2.__file = "src/editors/RelationEditor.vue";
8629
+ script$3.render = render$2;
8630
+ script$3.__file = "src/editors/RelationEditor.vue";
8631
+
8632
+ var script$2 = {
8633
+ name: 'WebComponentWrapper',
8634
+ props: {
8635
+ tagName: { type: String, required: true },
8636
+ schema: { type: Object, required: true },
8637
+ modelValue: { default: undefined },
8638
+ path: { type: Array, default: () => [] },
8639
+ form: { type: Object, required: true },
8640
+ },
8641
+ emits: ['update:modelValue'],
8642
+ setup(props, { emit }) {
8643
+ const elRef = ref(null);
8644
+
8645
+ function syncProps() {
8646
+ const el = elRef.value;
8647
+ if (!el) return;
8648
+ el.schema = props.schema;
8649
+ el.modelValue = props.modelValue;
8650
+ el.path = props.path;
8651
+ el.form = props.form;
8652
+ }
8653
+
8654
+ function handleChange(e) {
8655
+ const value = e.detail != null
8656
+ ? (Array.isArray(e.detail) ? e.detail[0] : e.detail)
8657
+ : undefined;
8658
+ emit('update:modelValue', value);
8659
+ }
8660
+
8661
+ onMounted(() => {
8662
+ syncProps();
8663
+ const el = elRef.value;
8664
+ if (el) {
8665
+ el.addEventListener('update:model-value', handleChange);
8666
+ el.addEventListener('change', handleChange);
8667
+ }
8668
+ });
8669
+
8670
+ onBeforeUnmount(() => {
8671
+ const el = elRef.value;
8672
+ if (el) {
8673
+ el.removeEventListener('update:model-value', handleChange);
8674
+ el.removeEventListener('change', handleChange);
8675
+ }
8676
+ });
8677
+
8678
+ watch(() => [props.schema, props.modelValue, props.path, props.form], syncProps, { deep: true });
8679
+
8680
+ return () => h(props.tagName, { ref: elRef });
8681
+ },
8682
+ };
8683
+
8684
+ script$2.__file = "src/editors/WebComponentWrapper.vue";
8296
8685
 
8297
8686
  const MAX_DEPTH = 12;
8298
8687
 
8299
8688
  var script$1 = {
8300
8689
  name: 'SchemaEditor',
8301
8690
  components: {
8302
- StringEditor: script$c,
8303
- NumberEditor: script$b,
8304
- BooleanEditor: script$a,
8305
- SelectEditor: script$9,
8306
- HiddenEditor: script$8,
8307
- ObjectEditor: script$6,
8308
- ArrayEditor: script$5,
8309
- NullableEditor: script$4,
8310
- UnionEditor: script$3,
8311
- RelationEditor: script$2,
8691
+ StringEditor: script$d,
8692
+ NumberEditor: script$c,
8693
+ BooleanEditor: script$b,
8694
+ SelectEditor: script$a,
8695
+ HiddenEditor: script$9,
8696
+ ObjectEditor: script$7,
8697
+ ArrayEditor: script$6,
8698
+ NullableEditor: script$5,
8699
+ UnionEditor: script$4,
8700
+ RelationEditor: script$3,
8701
+ WebComponentWrapper: script$2,
8702
+ },
8703
+ inject: {
8704
+ customEditors: { default: () => () => [] },
8312
8705
  },
8313
8706
  props: {
8314
8707
  schema: { type: Object, required: true },
@@ -8330,11 +8723,20 @@ var script$1 = {
8330
8723
  },
8331
8724
  },
8332
8725
  computed: {
8726
+ isWebComponent() {
8727
+ const c = this.editorComponent;
8728
+ return typeof c === 'string' && c.includes('-');
8729
+ },
8333
8730
  editorComponent() {
8334
8731
  const schema = this.schema;
8335
8732
 
8336
8733
  if (this.path.length > MAX_DEPTH) return 'StringEditor';
8337
8734
 
8735
+ const overrides = this.customEditors();
8736
+ for (const override of overrides) {
8737
+ if (override.match(schema, this.path)) return override.component;
8738
+ }
8739
+
8338
8740
  if (schema.type === 'relation') return 'RelationEditor';
8339
8741
  if (schema.oneOf && schema.discriminator) return 'UnionEditor';
8340
8742
  if ('const' in schema) return 'HiddenEditor';
@@ -8352,14 +8754,28 @@ var script$1 = {
8352
8754
  };
8353
8755
 
8354
8756
  function render$1(_ctx, _cache, $props, $setup, $data, $options) {
8355
- return (openBlock(), createBlock(resolveDynamicComponent($options.editorComponent), {
8356
- ref: "editor",
8357
- schema: $props.schema,
8358
- "model-value": $props.modelValue,
8359
- path: $props.path,
8360
- form: $props.form,
8361
- "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (_ctx.$emit('update:modelValue', $event)))
8362
- }, null, 8 /* PROPS */, ["schema", "model-value", "path", "form"]))
8757
+ const _component_WebComponentWrapper = resolveComponent("WebComponentWrapper");
8758
+
8759
+ return ($options.isWebComponent)
8760
+ ? (openBlock(), createBlock(_component_WebComponentWrapper, {
8761
+ key: 0,
8762
+ ref: "editor",
8763
+ "tag-name": $options.editorComponent,
8764
+ schema: $props.schema,
8765
+ "model-value": $props.modelValue,
8766
+ path: $props.path,
8767
+ form: $props.form,
8768
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (_ctx.$emit('update:modelValue', $event)))
8769
+ }, null, 8 /* PROPS */, ["tag-name", "schema", "model-value", "path", "form"]))
8770
+ : (openBlock(), createBlock(resolveDynamicComponent($options.editorComponent), {
8771
+ key: 1,
8772
+ ref: "editor",
8773
+ schema: $props.schema,
8774
+ "model-value": $props.modelValue,
8775
+ path: $props.path,
8776
+ form: $props.form,
8777
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (_ctx.$emit('update:modelValue', $event)))
8778
+ }, null, 8 /* PROPS */, ["schema", "model-value", "path", "form"]))
8363
8779
  }
8364
8780
 
8365
8781
  script$1.render = render$1;
@@ -8372,9 +8788,15 @@ var script = {
8372
8788
  schema: { type: [Object, String], default: () => ({}) },
8373
8789
  initialData: { default: undefined },
8374
8790
  errors: { type: Object, default: () => ({}) },
8791
+ customEditors: { type: Array, default: () => [] },
8375
8792
  },
8376
8793
  emits: ['change'],
8377
8794
  expose: ['getValue'],
8795
+ provide() {
8796
+ return {
8797
+ customEditors: () => this.customEditors,
8798
+ };
8799
+ },
8378
8800
  data() {
8379
8801
  const parsedSchema = typeof this.schema === 'string' ? JSON.parse(this.schema) : this.schema;
8380
8802
  const defs = parsedSchema.$defs || parsedSchema.definitions || {};
@@ -8392,6 +8814,7 @@ var script = {
8392
8814
  return {
8393
8815
  resolveSchema: (s) => this.resolveSchema(s),
8394
8816
  getSchemaAtPath: (p) => this.getSchemaAtPath(p),
8817
+ getEffectiveSchemaAtPath: (p) => this.getEffectiveSchemaAtPath(p),
8395
8818
  getErrorsForPath: (p) => this.getErrorsForPath(p),
8396
8819
  };
8397
8820
  },
@@ -8474,6 +8897,28 @@ var script = {
8474
8897
  return schema;
8475
8898
  },
8476
8899
 
8900
+ getEffectiveSchemaAtPath(path) {
8901
+ let schema = this.resolveSchema(this.rootSchema);
8902
+ let value = this.currentValue;
8903
+ for (const segment of path) {
8904
+ if (!schema) return null;
8905
+ if (schema.properties || schema.if || schema.allOf || schema.dependentSchemas) {
8906
+ if (hasConditionals(schema)) schema = applyConditionals(schema, value || {}, (s) => this.resolveSchema(s));
8907
+ }
8908
+ if (schema.properties && schema.properties[segment] !== undefined) {
8909
+ schema = this.resolveSchema(schema.properties[segment]);
8910
+ value = value != null ? value[segment] : undefined;
8911
+ } else if (schema.items) {
8912
+ schema = this.resolveSchema(schema.items);
8913
+ value = Array.isArray(value) ? value[segment] : undefined;
8914
+ } else {
8915
+ return null;
8916
+ }
8917
+ }
8918
+ if (schema && hasConditionals(schema)) schema = applyConditionals(schema, value || {});
8919
+ return schema;
8920
+ },
8921
+
8477
8922
  onValueChange(val) {
8478
8923
  this.currentValue = val;
8479
8924
  this.$emit('change', val);
@@ -8513,6 +8958,115 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
8513
8958
  script.render = render;
8514
8959
  script.__file = "src/SchemaForm.vue";
8515
8960
 
8961
+ /**
8962
+ * Base class for creating web component custom editors.
8963
+ *
8964
+ * Handles the property contract with the structured-widget-editor wrapper:
8965
+ * - Receives `schema`, `modelValue`, `path`, and `form` as JS properties.
8966
+ * - Provides `emitChange(value)` to dispatch the value back to the form.
8967
+ * - Provides `getErrors()` to retrieve validation errors for this field.
8968
+ * - Calls `render()` once on `connectedCallback` and `update()` on property changes.
8969
+ *
8970
+ * @example
8971
+ * import { BaseEditorElement } from '@structured-field/widget-editor';
8972
+ *
8973
+ * class MyColorPicker extends BaseEditorElement {
8974
+ * render() {
8975
+ * const input = document.createElement('input');
8976
+ * input.type = 'color';
8977
+ * input.value = this.modelValue || '#000000';
8978
+ * input.addEventListener('input', () => this.emitChange(input.value));
8979
+ * this._input = input;
8980
+ * this.appendChild(input);
8981
+ * }
8982
+ *
8983
+ * update() {
8984
+ * if (this._input) this._input.value = this.modelValue || '#000000';
8985
+ * }
8986
+ * }
8987
+ *
8988
+ * customElements.define('my-color-picker', MyColorPicker);
8989
+ */
8990
+ class BaseEditorElement extends HTMLElement {
8991
+ constructor() {
8992
+ super();
8993
+ this._schema = {};
8994
+ this._modelValue = undefined;
8995
+ this._path = [];
8996
+ this._form = null;
8997
+ this._connected = false;
8998
+ }
8999
+
9000
+ /* ── Property contract ─────────────────────────────────────────────── */
9001
+
9002
+ get schema() { return this._schema; }
9003
+ set schema(v) {
9004
+ this._schema = v;
9005
+ if (this._connected) this.update();
9006
+ }
9007
+
9008
+ get modelValue() { return this._modelValue; }
9009
+ set modelValue(v) {
9010
+ this._modelValue = v;
9011
+ if (this._connected) this.update();
9012
+ }
9013
+
9014
+ get path() { return this._path; }
9015
+ set path(v) {
9016
+ this._path = v;
9017
+ if (this._connected) this.update();
9018
+ }
9019
+
9020
+ get form() { return this._form; }
9021
+ set form(v) {
9022
+ this._form = v;
9023
+ if (this._connected) this.update();
9024
+ }
9025
+
9026
+ /* ── Lifecycle ─────────────────────────────────────────────────────── */
9027
+
9028
+ connectedCallback() {
9029
+ this._connected = true;
9030
+ this.render();
9031
+ }
9032
+
9033
+ disconnectedCallback() {
9034
+ this._connected = false;
9035
+ }
9036
+
9037
+ /* ── Helpers ───────────────────────────────────────────────────────── */
9038
+
9039
+ /**
9040
+ * Dispatch the new value back to the form.
9041
+ * @param {*} value - The new field value.
9042
+ */
9043
+ emitChange(value) {
9044
+ this.dispatchEvent(new CustomEvent('change', { detail: value }));
9045
+ }
9046
+
9047
+ /**
9048
+ * Returns the current validation errors for this field.
9049
+ * @returns {string[]}
9050
+ */
9051
+ getErrors() {
9052
+ return this._form?.getErrorsForPath?.(this._path) ?? [];
9053
+ }
9054
+
9055
+ /* ── Override points ───────────────────────────────────────────────── */
9056
+
9057
+ /**
9058
+ * Called once when the element is connected to the DOM.
9059
+ * Build the initial DOM structure here.
9060
+ */
9061
+ render() {}
9062
+
9063
+ /**
9064
+ * Called whenever a property (schema, modelValue, path, form) changes
9065
+ * after the initial render. Update the DOM here.
9066
+ */
9067
+ update() {}
9068
+ }
9069
+
8516
9070
  const SchemaFormElement = defineCustomElement({
8517
9071
  ...script,
8518
9072
  shadowRoot: false,
@@ -8524,5 +9078,5 @@ function registerCustomElement(tagName = 'schema-form') {
8524
9078
  }
8525
9079
  }
8526
9080
 
8527
- export { script$5 as ArrayEditor, script$a as BooleanEditor, script$8 as HiddenEditor, script$4 as NullableEditor, script$b as NumberEditor, script$6 as ObjectEditor, script$2 as RelationEditor, script$1 as SchemaEditor, script as SchemaForm, SchemaFormElement, script$9 as SelectEditor, script$c as StringEditor, script$3 as UnionEditor, registerCustomElement };
9081
+ export { script$6 as ArrayEditor, BaseEditorElement, script$b as BooleanEditor, script$9 as HiddenEditor, script$5 as NullableEditor, script$c as NumberEditor, script$7 as ObjectEditor, script$3 as RelationEditor, script$1 as SchemaEditor, script as SchemaForm, SchemaFormElement, script$a as SelectEditor, script$d as StringEditor, script$4 as UnionEditor, script$2 as WebComponentWrapper, applyConditionals, hasConditionals, matchesSchema, registerCustomElement };
8528
9082
  //# sourceMappingURL=structured-widget-editor.esm.js.map