@elle96/ng-json-schema-manager 0.1.2 → 0.1.4

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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, ViewEncapsulation, Component, NgModule } from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, ViewEncapsulation, Component, PLATFORM_ID, Inject, NgModule } from '@angular/core';
3
3
  import * as i4 from '@angular/forms';
4
4
  import { FormGroup, FormArray, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
5
5
  import Ajv from 'ajv';
@@ -8,7 +8,7 @@ import Ajv2020 from 'ajv/dist/2020';
8
8
  import addFormats from 'ajv-formats';
9
9
  import AjvDraft04 from 'ajv-draft-04';
10
10
  import * as i3 from '@angular/common';
11
- import { CommonModule } from '@angular/common';
11
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
12
12
 
13
13
  class JsonSchemaFormService {
14
14
  buildControl(schema, value, required = false) {
@@ -274,10 +274,10 @@ class JsonSchemaFormService {
274
274
  return merged;
275
275
  }, {});
276
276
  }
277
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
278
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormService, providedIn: 'root' });
277
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
278
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormService, providedIn: 'root' });
279
279
  }
280
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormService, decorators: [{
280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormService, decorators: [{
281
281
  type: Injectable,
282
282
  args: [{
283
283
  providedIn: 'root',
@@ -449,10 +449,10 @@ class JsonSchemaResolverService {
449
449
  }
450
450
  return current;
451
451
  }
452
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaResolverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
453
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaResolverService, providedIn: 'root' });
452
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaResolverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
453
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaResolverService, providedIn: 'root' });
454
454
  }
455
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaResolverService, decorators: [{
455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaResolverService, decorators: [{
456
456
  type: Injectable,
457
457
  args: [{
458
458
  providedIn: 'root',
@@ -569,10 +569,10 @@ class JsonSchemaValidationService {
569
569
  this.ajvCache.set(draft, ajv);
570
570
  return ajv;
571
571
  }
572
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
573
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaValidationService, providedIn: 'root' });
572
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
573
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaValidationService, providedIn: 'root' });
574
574
  }
575
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaValidationService, decorators: [{
575
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaValidationService, decorators: [{
576
576
  type: Injectable,
577
577
  args: [{
578
578
  providedIn: 'root',
@@ -582,6 +582,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
582
582
  class JsonSchemaNodeComponent {
583
583
  schemaService;
584
584
  validation;
585
+ cdr;
585
586
  schema;
586
587
  control;
587
588
  label = '';
@@ -591,39 +592,53 @@ class JsonSchemaNodeComponent {
591
592
  parent;
592
593
  controlKey;
593
594
  allowAdditionalProperties = false;
595
+ /** When true, errors are shown regardless of touched/dirty state (e.g. triggered by an external save button). */
596
+ showErrors = false;
594
597
  controlReplaced = new EventEmitter();
595
598
  selectedOneOf = 0;
596
599
  selectedAnyOf = new Set();
597
600
  newPropertyKey = '';
598
601
  propertyError = '';
599
- constructor(schemaService, validation) {
602
+ /** Tracks whether this primitive control has been touched (for OnPush CD). */
603
+ isTouched = false;
604
+ valueSub;
605
+ constructor(schemaService, validation, cdr) {
600
606
  this.schemaService = schemaService;
601
607
  this.validation = validation;
608
+ this.cdr = cdr;
602
609
  }
603
610
  ngOnInit() {
604
- if (this.schema.anyOf?.length) {
611
+ if (this.schema.anyOf?.length)
605
612
  this.selectedAnyOf.add(0);
606
- }
613
+ // markForCheck is enough for value changes — Angular will re-render in the next CD cycle
614
+ // without recreating DOM nodes, so the focused input keeps its focus.
615
+ this.valueSub = this.control.valueChanges.subscribe(() => this.cdr.markForCheck());
616
+ }
617
+ ngOnDestroy() {
618
+ this.valueSub?.unsubscribe();
619
+ }
620
+ /** Called on blur of primitive inputs to trigger CD after Angular marks the control as touched. */
621
+ onBlur() {
622
+ this.isTouched = true;
623
+ this.cdr.markForCheck();
607
624
  }
608
- /**
609
- * Determina se un figlio di un object è "semplice" (primitive/enum/bool),
610
- * quindi può stare in griglia 2-col, oppure "complesso" (object/array/combinator)
611
- * che deve occupare la larghezza piena.
612
- */
613
625
  isSimpleChild(key) {
614
- const childSchema = this.schemaForKey(key);
615
- if (childSchema.oneOf?.length || childSchema.anyOf?.length || childSchema.allOf?.length)
626
+ const s = this.schemaForKey(key);
627
+ if (s.oneOf?.length || s.anyOf?.length || s.allOf?.length)
616
628
  return false;
617
- const t = this.schemaService.resolveType(childSchema);
629
+ const t = this.schemaService.resolveType(s);
618
630
  return t !== 'object' && t !== 'array';
619
631
  }
620
632
  badgeClass(badge) {
621
- return {
622
- 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400': badge === 'Deprecated',
623
- 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400': badge === 'Read-only' || badge === 'Nullable',
624
- 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400': badge === 'Write-only',
625
- 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400': badge === 'Not constraint',
626
- };
633
+ if (badge === 'Deprecated')
634
+ return 'jsm-badge--deprecated';
635
+ if (badge === 'Read-only' || badge === 'Nullable')
636
+ return 'jsm-badge--readonly';
637
+ if (badge === 'Write-only')
638
+ return 'jsm-badge--writeonly';
639
+ if (badge === 'Not constraint')
640
+ return 'jsm-badge--not';
641
+ return 'jsm-badge--nullable';
627
642
  }
628
643
  get nodeKind() {
629
644
  if (this.schema.oneOf?.length)
@@ -632,10 +647,10 @@ class JsonSchemaNodeComponent {
632
647
  return 'anyOf';
633
648
  if (this.schema.allOf?.length)
634
649
  return 'allOf';
635
- const resolved = this.resolvedType;
636
- if (resolved === 'object')
650
+ const r = this.resolvedType;
651
+ if (r === 'object')
637
652
  return 'object';
638
- if (resolved === 'array')
653
+ if (r === 'array')
639
654
  return 'array';
640
655
  return 'primitive';
641
656
  }
@@ -671,38 +686,73 @@ class JsonSchemaNodeComponent {
671
686
  get controlAsArray() { return this.control; }
672
687
  get arrayControls() { return this.controlAsArray.controls; }
673
688
  get objectKeys() { return Object.keys(this.controlAsGroup.controls); }
674
- get simpleKeys() {
675
- return this.objectKeys.filter(k => this.isSimpleChild(k));
676
- }
677
- get complexKeys() {
678
- return this.objectKeys.filter(k => !this.isSimpleChild(k));
679
- }
689
+ get simpleKeys() { return this.objectKeys.filter(k => this.isSimpleChild(k)); }
690
+ get complexKeys() { return this.objectKeys.filter(k => !this.isSimpleChild(k)); }
680
691
  get canAddProperty() {
681
692
  if (!this.allowAdditionalProperties)
682
693
  return false;
683
694
  return this.effectiveSchema.additionalProperties !== false || !!this.effectiveSchema.patternProperties;
684
695
  }
685
696
  get errorsForPath() { return this.errorsMap.get(this.path) ?? []; }
697
+ /** Combined errors: Ajv errors from errorsMap + Angular validator errors on the control. */
698
+ get allErrors() {
699
+ const ajvErrors = this.errorsForPath;
700
+ const ctrl = this.control;
701
+ if (!ctrl.errors)
702
+ return ajvErrors;
703
+ const angularErrors = [];
704
+ const errs = ctrl.errors;
705
+ if (errs['required'])
706
+ angularErrors.push('This field is required');
707
+ if (errs['minlength'])
708
+ angularErrors.push(`Minimum length is ${errs['minlength'].requiredLength}`);
709
+ if (errs['maxlength'])
710
+ angularErrors.push(`Maximum length is ${errs['maxlength'].requiredLength}`);
711
+ if (errs['min'])
712
+ angularErrors.push(`Minimum value is ${errs['min'].min}`);
713
+ if (errs['max'])
714
+ angularErrors.push(`Maximum value is ${errs['max'].max}`);
715
+ if (errs['email'])
716
+ angularErrors.push('Invalid email address');
717
+ if (errs['pattern'])
718
+ angularErrors.push('Value does not match the required pattern');
719
+ if (errs['integer'])
720
+ angularErrors.push('Value must be an integer');
721
+ if (errs['exclusiveMinimum'])
722
+ angularErrors.push('Value must be greater than the minimum');
723
+ if (errs['exclusiveMaximum'])
724
+ angularErrors.push('Value must be less than the maximum');
725
+ if (errs['multipleOf'])
726
+ angularErrors.push('Value must be a multiple of the required number');
727
+ if (errs['minItems'])
728
+ angularErrors.push(`Minimum ${errs['minItems'].requiredLength ?? ''} items required`);
729
+ if (errs['maxItems'])
730
+ angularErrors.push(`Maximum ${errs['maxItems'].requiredLength ?? ''} items allowed`);
731
+ if (errs['uniqueItems'])
732
+ angularErrors.push('Items must be unique');
733
+ // Merge: prefer Ajv messages, fall back to Angular messages for errors not covered by Ajv
734
+ return ajvErrors.length > 0 ? ajvErrors : angularErrors;
735
+ }
686
736
  get metaBadges() {
687
- const badges = [];
737
+ const b = [];
688
738
  if (this.effectiveSchema.deprecated)
689
- badges.push('Deprecated');
739
+ b.push('Deprecated');
690
740
  if (this.effectiveSchema.readOnly)
691
- badges.push('Read-only');
741
+ b.push('Read-only');
692
742
  if (this.effectiveSchema.writeOnly)
693
- badges.push('Write-only');
743
+ b.push('Write-only');
694
744
  if (this.effectiveSchema.nullable)
695
- badges.push('Nullable');
745
+ b.push('Nullable');
696
746
  if (this.effectiveSchema.not)
697
- badges.push('Not constraint');
698
- return badges;
747
+ b.push('Not constraint');
748
+ return b;
699
749
  }
700
750
  get activeVariant() {
701
751
  if (this.schema.oneOf?.length)
702
752
  return this.schema.oneOf[this.selectedOneOf] ?? null;
703
753
  if (this.schema.anyOf?.length) {
704
- const selections = Array.from(this.selectedAnyOf).map((i) => this.schema.anyOf[i]);
705
- return selections.length ? this.mergeSchemas(selections) : this.schema.anyOf[0] ?? null;
754
+ const sel = Array.from(this.selectedAnyOf).map(i => this.schema.anyOf[i]);
755
+ return sel.length ? this.mergeSchemas(sel) : this.schema.anyOf[0] ?? null;
706
756
  }
707
757
  if (this.schema.allOf?.length)
708
758
  return this.mergeSchemas([this.schema, ...this.schema.allOf]);
@@ -713,9 +763,7 @@ class JsonSchemaNodeComponent {
713
763
  }
714
764
  isRequired(key) { return this.effectiveSchema.required?.includes(key) ?? false; }
715
765
  schemaForKey(key) {
716
- return (this.effectiveSchema.properties?.[key] ??
717
- this.schemaService.resolveDynamicSchema(this.effectiveSchema, key) ??
718
- {});
766
+ return this.effectiveSchema.properties?.[key] ?? this.schemaService.resolveDynamicSchema(this.effectiveSchema, key) ?? {};
719
767
  }
720
768
  isDynamicKey(key) { return !this.effectiveSchema.properties?.[key]; }
721
769
  pathForChild(key) {
@@ -729,8 +777,7 @@ class JsonSchemaNodeComponent {
729
777
  return;
730
778
  }
731
779
  if (this.effectiveSchema.propertyNames) {
732
- const nameErrors = this.validation.validate(this.effectiveSchema.propertyNames, key);
733
- if (nameErrors.length) {
780
+ if (this.validation.validate(this.effectiveSchema.propertyNames, key).length) {
734
781
  this.propertyError = 'Property name does not match schema constraints.';
735
782
  return;
736
783
  }
@@ -739,20 +786,19 @@ class JsonSchemaNodeComponent {
739
786
  this.propertyError = 'Property already exists.';
740
787
  return;
741
788
  }
742
- const schemaForKey = this.schemaService.resolveDynamicSchema(this.effectiveSchema, key);
743
- if (!schemaForKey) {
789
+ const s = this.schemaService.resolveDynamicSchema(this.effectiveSchema, key);
790
+ if (!s) {
744
791
  this.propertyError = 'This property is not allowed by the schema.';
745
792
  return;
746
793
  }
747
- const control = this.schemaService.buildControl(schemaForKey, undefined, false);
748
- this.controlAsGroup.addControl(key, control);
794
+ this.controlAsGroup.addControl(key, this.schemaService.buildControl(s, undefined, false));
749
795
  this.newPropertyKey = '';
750
796
  }
751
797
  removeDynamicKey(key) { this.controlAsGroup.removeControl(key); }
752
798
  schemaForIndex(index) {
753
- const prefixItems = this.effectiveSchema.prefixItems ?? [];
754
- if (prefixItems[index])
755
- return prefixItems[index];
799
+ const p = this.effectiveSchema.prefixItems ?? [];
800
+ if (p[index])
801
+ return p[index];
756
802
  if (this.effectiveSchema.items && this.effectiveSchema.items !== true)
757
803
  return this.effectiveSchema.items;
758
804
  if (this.effectiveSchema.additionalItems && this.effectiveSchema.additionalItems !== true)
@@ -762,17 +808,14 @@ class JsonSchemaNodeComponent {
762
808
  return {};
763
809
  }
764
810
  get canAddArrayItem() {
765
- return !(this.effectiveSchema.items === false ||
766
- this.effectiveSchema.additionalItems === false ||
767
- this.effectiveSchema.unevaluatedItems === false);
811
+ return !(this.effectiveSchema.items === false || this.effectiveSchema.additionalItems === false || this.effectiveSchema.unevaluatedItems === false);
768
812
  }
769
813
  addArrayItem() {
770
814
  if (!this.canAddArrayItem)
771
815
  return;
772
- const formArray = this.controlAsArray;
773
- const index = formArray.length;
774
- formArray.push(this.schemaService.buildControl(this.schemaForIndex(index), undefined, false));
775
- formArray.markAsDirty();
816
+ const fa = this.controlAsArray;
817
+ fa.push(this.schemaService.buildControl(this.schemaForIndex(fa.length), undefined, false));
818
+ fa.markAsDirty();
776
819
  }
777
820
  removeArrayItem(index) {
778
821
  this.controlAsArray.removeAt(index);
@@ -788,9 +831,9 @@ class JsonSchemaNodeComponent {
788
831
  this.selectedAnyOf.delete(index);
789
832
  else
790
833
  this.selectedAnyOf.add(index);
791
- const variant = this.activeVariant;
792
- if (variant)
793
- this.replaceControl(this.schemaService.buildControl(variant, this.control.value, this.required));
834
+ const v = this.activeVariant;
835
+ if (v)
836
+ this.replaceControl(this.schemaService.buildControl(v, this.control.value, this.required));
794
837
  }
795
838
  replaceControl(control) {
796
839
  if (this.parent) {
@@ -806,12 +849,12 @@ class JsonSchemaNodeComponent {
806
849
  }
807
850
  childControl(key) { return this.controlAsGroup.get(key); }
808
851
  get effectiveSchema() {
809
- let schema = this.schema;
810
- if (schema.allOf?.length)
811
- schema = this.mergeSchemas([schema, ...schema.allOf]);
812
- schema = this.applyConditional(schema);
813
- schema = this.applyDependencies(schema);
814
- return schema;
852
+ let s = this.schema;
853
+ if (s.allOf?.length)
854
+ s = this.mergeSchemas([s, ...s.allOf]);
855
+ s = this.applyConditional(s);
856
+ s = this.applyDependencies(s);
857
+ return s;
815
858
  }
816
859
  applyConditional(schema) {
817
860
  if (!schema.if)
@@ -827,406 +870,284 @@ class JsonSchemaNodeComponent {
827
870
  if (!value || typeof value !== 'object' || Array.isArray(value))
828
871
  return schema;
829
872
  let merged = { ...schema };
830
- const valueKeys = Object.keys(value);
873
+ const keys = Object.keys(value);
831
874
  if (schema.dependentRequired) {
832
- for (const key of valueKeys) {
833
- const required = schema.dependentRequired[key];
834
- if (required?.length)
835
- merged = this.mergeSchemas([merged, { required }]);
875
+ for (const k of keys) {
876
+ const r = schema.dependentRequired[k];
877
+ if (r?.length)
878
+ merged = this.mergeSchemas([merged, { required: r }]);
836
879
  }
837
880
  }
838
881
  if (schema.dependentSchemas) {
839
- for (const key of valueKeys) {
840
- const depSchema = schema.dependentSchemas[key];
841
- if (depSchema)
842
- merged = this.mergeSchemas([merged, depSchema]);
882
+ for (const k of keys) {
883
+ const d = schema.dependentSchemas[k];
884
+ if (d)
885
+ merged = this.mergeSchemas([merged, d]);
843
886
  }
844
887
  }
845
888
  if (schema.dependencies) {
846
- for (const key of valueKeys) {
847
- const dep = schema.dependencies[key];
848
- if (Array.isArray(dep))
849
- merged = this.mergeSchemas([merged, { required: dep }]);
850
- else if (dep)
851
- merged = this.mergeSchemas([merged, dep]);
889
+ for (const k of keys) {
890
+ const d = schema.dependencies[k];
891
+ if (Array.isArray(d))
892
+ merged = this.mergeSchemas([merged, { required: d }]);
893
+ else if (d)
894
+ merged = this.mergeSchemas([merged, d]);
852
895
  }
853
896
  }
854
897
  return merged;
855
898
  }
856
899
  mergeSchemas(schemas) {
857
- return schemas.reduce((acc, schema) => {
858
- const merged = {
859
- ...acc,
860
- ...schema,
861
- properties: { ...(acc.properties ?? {}), ...(schema.properties ?? {}) },
862
- patternProperties: { ...(acc.patternProperties ?? {}), ...(schema.patternProperties ?? {}) },
863
- required: Array.from(new Set([...(acc.required ?? []), ...(schema.required ?? [])])),
864
- };
865
- if (acc.type && schema.type) {
866
- const accTypes = Array.isArray(acc.type) ? acc.type : [acc.type];
867
- const schemaTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
868
- merged.type = Array.from(new Set([...accTypes, ...schemaTypes]));
869
- }
870
- return merged;
871
- }, {});
900
+ return schemas.reduce((acc, s) => ({
901
+ ...acc, ...s,
902
+ properties: { ...(acc.properties ?? {}), ...(s.properties ?? {}) },
903
+ patternProperties: { ...(acc.patternProperties ?? {}), ...(s.patternProperties ?? {}) },
904
+ required: Array.from(new Set([...(acc.required ?? []), ...(s.required ?? [])])),
905
+ ...(acc.type && s.type ? {
906
+ type: Array.from(new Set([
907
+ ...(Array.isArray(acc.type) ? acc.type : [acc.type]),
908
+ ...(Array.isArray(s.type) ? s.type : [s.type]),
909
+ ]))
910
+ } : {}),
911
+ }), {});
872
912
  }
873
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaValidationService }], target: i0.ɵɵFactoryTarget.Component });
874
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: JsonSchemaNodeComponent, isStandalone: true, selector: "jsm-schema-node", inputs: { schema: "schema", control: "control", label: "label", required: "required", path: "path", errorsMap: "errorsMap", parent: "parent", controlKey: "controlKey", allowAdditionalProperties: "allowAdditionalProperties" }, outputs: { controlReplaced: "controlReplaced" }, ngImport: i0, template: `
913
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaNodeComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaValidationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
914
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: JsonSchemaNodeComponent, isStandalone: true, selector: "jsm-schema-node", inputs: { schema: "schema", control: "control", label: "label", required: "required", path: "path", errorsMap: "errorsMap", parent: "parent", controlKey: "controlKey", allowAdditionalProperties: "allowAdditionalProperties", showErrors: "showErrors" }, outputs: { controlReplaced: "controlReplaced" }, ngImport: i0, template: `
875
915
  <ng-container [ngSwitch]="nodeKind">
876
916
 
877
- <!-- ═══════════════════════════════════════════════════════════
878
- oneOf — tab bar: ogni opzione è una tab, nessuna icona
879
- ═══════════════════════════════════════════════════════════ -->
880
- <div *ngSwitchCase="'oneOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
881
- <div class="flex items-start justify-between px-4 py-3 bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-700">
917
+ <!-- ═══ oneOf ═══ -->
918
+ <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
919
+ <div class="jsm-combinator-header">
882
920
  <div>
883
- <p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
884
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
921
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
922
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
885
923
  </div>
886
- <span class="ml-3 shrink-0 rounded-full bg-blue-100 dark:bg-blue-900/40 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-blue-700 dark:text-blue-400">oneOf</span>
924
+ <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
887
925
  </div>
888
- <div class="flex overflow-x-auto border-b border-slate-200 dark:border-slate-700">
889
- <button
890
- *ngFor="let option of schema.oneOf; let i = index"
891
- type="button"
892
- class="px-4 py-2.5 text-xs font-medium whitespace-nowrap border-b-2 transition-colors"
893
- [class.border-indigo-500]="selectedOneOf === i"
894
- [class.text-indigo-600]="selectedOneOf === i"
895
- [class.dark:text-indigo-400]="selectedOneOf === i"
896
- [class.font-semibold]="selectedOneOf === i"
897
- [class.border-transparent]="selectedOneOf !== i"
898
- [class.text-slate-500]="selectedOneOf !== i"
899
- [class.dark:text-slate-400]="selectedOneOf !== i"
900
- (click)="selectedOneOf = i; onOneOfChange()"
901
- >{{ option.title || ('Option ' + (i + 1)) }}</button>
926
+ <div class="jsm-oneof-tabs">
927
+ <button *ngFor="let option of schema.oneOf; let i = index" type="button"
928
+ class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
929
+ (click)="selectedOneOf = i; onOneOfChange()">
930
+ {{ option.title || ('Option ' + (i + 1)) }}
931
+ </button>
902
932
  </div>
903
- <div class="p-4" *ngIf="activeVariant">
933
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
904
934
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
905
935
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
906
- [allowAdditionalProperties]="allowAdditionalProperties"
936
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
907
937
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
908
938
  </div>
909
939
  </div>
910
940
 
911
- <!-- ═══════════════════════════════════════════════════════════
912
- anyOf — pill toggle, nessuna icona
913
- ═══════════════════════════════════════════════════════════ -->
914
- <div *ngSwitchCase="'anyOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
915
- <div class="flex items-start justify-between px-4 py-3 bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-700">
941
+ <!-- ═══ anyOf ═══ -->
942
+ <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
943
+ <div class="jsm-combinator-header">
916
944
  <div>
917
- <p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
918
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
945
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
946
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
919
947
  </div>
920
- <span class="ml-3 shrink-0 rounded-full bg-violet-100 dark:bg-violet-900/40 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-violet-700 dark:text-violet-400">anyOf</span>
948
+ <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
921
949
  </div>
922
- <div class="flex flex-wrap gap-2 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
923
- <label
924
- *ngFor="let option of schema.anyOf; let i = index"
925
- class="inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium cursor-pointer transition-colors select-none"
926
- [class.border-indigo-400]="selectedAnyOf.has(i)"
927
- [class.bg-indigo-50]="selectedAnyOf.has(i)"
928
- [class.dark:bg-indigo-900/30]="selectedAnyOf.has(i)"
929
- [class.text-indigo-700]="selectedAnyOf.has(i)"
930
- [class.dark:text-indigo-400]="selectedAnyOf.has(i)"
931
- [class.border-slate-200]="!selectedAnyOf.has(i)"
932
- [class.dark:border-slate-600]="!selectedAnyOf.has(i)"
933
- [class.text-slate-600]="!selectedAnyOf.has(i)"
934
- [class.dark:text-slate-400]="!selectedAnyOf.has(i)"
935
- >
936
- <input type="checkbox" class="h-3.5 w-3.5 accent-indigo-600" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
950
+ <div class="jsm-anyof-options">
951
+ <label *ngFor="let option of schema.anyOf; let i = index"
952
+ class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
953
+ <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
937
954
  {{ option.title || ('Option ' + (i + 1)) }}
938
955
  </label>
939
956
  </div>
940
- <div class="p-4" *ngIf="activeVariant">
957
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
941
958
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
942
959
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
943
- [allowAdditionalProperties]="allowAdditionalProperties"
960
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
944
961
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
945
962
  </div>
946
963
  </div>
947
964
 
948
- <!-- ═══════════════════════════════════════════════════════════
949
- allOf — sezioni in colonna con bordo sinistro
950
- ═══════════════════════════════════════════════════════════ -->
951
- <div *ngSwitchCase="'allOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
952
- <div class="flex items-start justify-between px-4 py-3 bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-700">
965
+ <!-- ═══ allOf ═══ -->
966
+ <div *ngSwitchCase="'allOf'" class="jsm-combinator">
967
+ <div class="jsm-combinator-header">
953
968
  <div>
954
- <p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
955
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
969
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
970
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
956
971
  </div>
957
- <span class="ml-3 shrink-0 rounded-full bg-teal-100 dark:bg-teal-900/40 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-teal-700 dark:text-teal-400">allOf</span>
972
+ <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
958
973
  </div>
959
- <div class="p-4 space-y-4">
960
- <div *ngFor="let subSchema of mergedAllOf; let i = index" class="border-l-2 border-indigo-200 dark:border-indigo-700 pl-4">
974
+ <div class="jsm-combinator-body">
975
+ <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
961
976
  <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
962
977
  [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
963
- [allowAdditionalProperties]="allowAdditionalProperties"
978
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
964
979
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
965
980
  </div>
966
981
  </div>
967
982
  </div>
968
983
 
969
- <!-- ═══════════════════════════════════════════════════════════
970
- object — fieldset con griglia SOLO per campi primitivi.
971
- Logica layout:
972
- - I figli vengono classificati come "semplici" (primitive/enum/bool)
973
- o "complessi" (object/array/oneOf/anyOf/allOf).
974
- - I semplici vanno in griglia 2 colonne (sm:grid-cols-2).
975
- - I complessi vanno sempre in colonna piena (col-span-2).
976
- ═══════════════════════════════════════════════════════════ -->
977
- <fieldset *ngSwitchCase="'object'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm p-4 transition-shadow focus-within:border-indigo-300 dark:focus-within:border-indigo-600 focus-within:ring-2 focus-within:ring-indigo-100 dark:focus-within:ring-indigo-900/40">
978
- <legend class="px-2 text-sm font-bold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</legend>
979
-
980
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mb-3 mt-1">{{ schema.description }}</p>
984
+ <!-- ═══ object ═══ -->
985
+ <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
986
+ <legend class="jsm-legend">{{ schema.title || label }}</legend>
987
+ <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
981
988
 
982
- <div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5 mb-3">
983
- <span *ngFor="let badge of metaBadges"
984
- class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
985
- [ngClass]="badgeClass(badge)">{{ badge }}</span>
989
+ <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
990
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
986
991
  </div>
987
992
 
988
- <!-- campi semplici (string/number/bool/enum) → griglia 2 colonne -->
989
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-4" [formGroup]="controlAsGroup">
993
+ <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
990
994
  <ng-container *ngFor="let key of simpleKeys">
991
- <div class="flex flex-col gap-1">
992
- <div *ngIf="isDynamicKey(key)"
993
- class="flex items-center justify-between rounded-md border border-dashed border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800/50 px-2 py-1 mb-1">
994
- <span class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</span>
995
- <button type="button"
996
- class="rounded px-1.5 py-0.5 text-xs font-semibold text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
997
- (click)="removeDynamicKey(key)">Remove</button>
995
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
996
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
997
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
998
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
998
999
  </div>
999
- <jsm-schema-node
1000
- [schema]="schemaForKey(key)"
1001
- [control]="childControl(key)"
1002
- [parent]="controlAsGroup"
1003
- [controlKey]="key"
1004
- [path]="pathForChild(key)"
1005
- [errorsMap]="errorsMap"
1006
- [label]="key"
1007
- [required]="isRequired(key)"
1008
- [allowAdditionalProperties]="allowAdditionalProperties"
1009
- (controlReplaced)="controlReplaced.emit($event)">
1010
- </jsm-schema-node>
1000
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1001
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1002
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1003
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1011
1004
  </div>
1012
1005
  </ng-container>
1013
1006
  </div>
1014
1007
 
1015
- <!-- campi complessi (object/array/combinator) → colonna piena -->
1016
- <div class="space-y-4 mt-4" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1008
+ <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1017
1009
  <ng-container *ngFor="let key of complexKeys">
1018
- <div class="flex flex-col gap-1">
1019
- <div *ngIf="isDynamicKey(key)"
1020
- class="flex items-center justify-between rounded-md border border-dashed border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800/50 px-2 py-1 mb-1">
1021
- <span class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</span>
1022
- <button type="button"
1023
- class="rounded px-1.5 py-0.5 text-xs font-semibold text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
1024
- (click)="removeDynamicKey(key)">Remove</button>
1010
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1011
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1012
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1013
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1025
1014
  </div>
1026
- <jsm-schema-node
1027
- [schema]="schemaForKey(key)"
1028
- [control]="childControl(key)"
1029
- [parent]="controlAsGroup"
1030
- [controlKey]="key"
1031
- [path]="pathForChild(key)"
1032
- [errorsMap]="errorsMap"
1033
- [label]="key"
1034
- [required]="isRequired(key)"
1035
- [allowAdditionalProperties]="allowAdditionalProperties"
1036
- (controlReplaced)="controlReplaced.emit($event)">
1037
- </jsm-schema-node>
1015
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1016
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1017
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1018
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1038
1019
  </div>
1039
1020
  </ng-container>
1040
1021
  </div>
1041
1022
 
1042
- <!-- add dynamic property -->
1043
- <div *ngIf="canAddProperty" class="mt-4 rounded-lg border border-dashed border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800/40 p-3 space-y-2">
1044
- <p class="text-[10px] font-bold uppercase tracking-widest text-slate-400 dark:text-slate-500">Add property</p>
1045
- <div class="flex gap-2">
1046
- <input
1047
- class="flex-1 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 py-1.5 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:focus:ring-indigo-900/40"
1048
- [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1049
- <button type="button"
1050
- class="rounded-lg bg-indigo-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-700 active:scale-95"
1051
- (click)="addDynamicProperty()">Add</button>
1023
+ <div *ngIf="canAddProperty" class="jsm-add-prop">
1024
+ <p class="jsm-add-prop-label">Add property</p>
1025
+ <div class="jsm-add-prop-row">
1026
+ <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1027
+ <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">Add</button>
1052
1028
  </div>
1053
- <p *ngIf="propertyError" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1054
- <!-- icona errore: unico caso in cui ha senso, segnala un problema -->
1055
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1029
+ <p *ngIf="propertyError" class="jsm-error-msg">
1030
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1056
1031
  {{ propertyError }}
1057
1032
  </p>
1058
1033
  </div>
1059
1034
 
1060
- <div *ngIf="errorsForPath.length" class="mt-3 space-y-1">
1061
- <p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1062
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1035
+ <div *ngIf="errorsForPath.length" class="jsm-error-list" style="margin-top:.75rem;">
1036
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1037
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1063
1038
  {{ error }}
1064
1039
  </p>
1065
1040
  </div>
1066
1041
  </fieldset>
1067
1042
 
1068
- <!-- ═══════════════════════════════════════════════════════════
1069
- array — sempre in colonna piena, items in colonna
1070
- ═══════════════════════════════════════════════════════════ -->
1071
- <div *ngSwitchCase="'array'" class="space-y-3">
1072
- <div class="flex items-start justify-between gap-2">
1043
+ <!-- ═══ array ═══ -->
1044
+ <div *ngSwitchCase="'array'" class="jsm-array-block">
1045
+ <div class="jsm-array-header">
1073
1046
  <div>
1074
- <p class="text-sm font-semibold text-slate-800">
1075
- {{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
1047
+ <p class="jsm-array-title">
1048
+ {{ schema.title || label }}<span *ngIf="required" style="color:#ef4444;margin-left:.125rem;">*</span>
1076
1049
  </p>
1077
- <p *ngIf="schema.description" class="text-xs text-slate-500 mt-0.5">{{ schema.description }}</p>
1050
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1078
1051
  </div>
1079
- <!-- icona + nel bottone: indica l'azione "aggiungi", non decorativa -->
1080
- <button type="button"
1081
- class="shrink-0 flex items-center gap-1 rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm transition hover:bg-indigo-700 active:scale-95 disabled:opacity-40 disabled:cursor-not-allowed"
1082
- (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1083
- <svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1052
+ <button type="button" class="jsm-btn-primary" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1053
+ <svg style="width:.875rem;height:.875rem;flex-shrink:0;" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1084
1054
  Add item
1085
1055
  </button>
1086
1056
  </div>
1087
1057
 
1088
- <div *ngIf="effectiveSchema.contains" class="rounded-md bg-indigo-50 dark:bg-indigo-900/30 px-3 py-1.5 text-xs text-indigo-700 dark:text-indigo-400">
1058
+ <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1089
1059
  Must contain matching item(s)
1090
1060
  <span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
1091
1061
  <span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
1092
1062
  </div>
1093
1063
 
1094
- <div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
1095
- <span *ngFor="let badge of metaBadges"
1096
- class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
1097
- [ngClass]="badgeClass(badge)">{{ badge }}</span>
1064
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1065
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1098
1066
  </div>
1099
1067
 
1100
- <div class="space-y-2" *ngIf="arrayControls.length > 0; else emptyArray">
1101
- <div *ngFor="let item of arrayControls; let i = index"
1102
- class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-3 shadow-sm transition focus-within:border-indigo-300 dark:focus-within:border-indigo-600 focus-within:ring-2 focus-within:ring-indigo-100 dark:focus-within:ring-indigo-900/40">
1103
- <div class="flex items-center justify-between mb-3">
1104
- <span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-indigo-100 dark:bg-indigo-900/50 text-[11px] font-bold text-indigo-600 dark:text-indigo-400">{{ i + 1 }}</span>
1105
- <button type="button"
1106
- class="rounded-md border border-red-200 dark:border-red-800 px-2 py-0.5 text-xs font-semibold text-red-600 dark:text-red-400 transition hover:bg-red-50 dark:hover:bg-red-900/20"
1107
- (click)="removeArrayItem(i)">Remove</button>
1068
+ <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1069
+ <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item">
1070
+ <div class="jsm-array-item-header">
1071
+ <span class="jsm-array-index">{{ i + 1 }}</span>
1072
+ <button type="button" class="jsm-btn-danger" (click)="removeArrayItem(i)">Remove</button>
1108
1073
  </div>
1109
1074
  <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1110
1075
  [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1111
- [allowAdditionalProperties]="allowAdditionalProperties"
1076
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1112
1077
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1113
1078
  </div>
1114
1079
  </div>
1115
1080
 
1116
1081
  <ng-template #emptyArray>
1117
- <div class="flex flex-col items-center gap-2 rounded-xl border border-dashed border-slate-200 dark:border-slate-700 py-8 text-slate-400 dark:text-slate-500 text-xs">
1118
- No items yet — click "Add item" to start.
1119
- </div>
1082
+ <div class="jsm-empty-array">No items yet — click "Add item" to start.</div>
1120
1083
  </ng-template>
1121
1084
 
1122
- <div *ngIf="errorsForPath.length" class="space-y-1">
1123
- <p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1124
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1085
+ <div *ngIf="errorsForPath.length" class="jsm-error-list">
1086
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1087
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1125
1088
  {{ error }}
1126
1089
  </p>
1127
1090
  </div>
1128
1091
  </div>
1129
1092
 
1130
- <!-- ═══════════════════════════════════════════════════════════
1131
- primitive — sempre in colonna, nessuna icona decorativa.
1132
- Icona errore solo nei messaggi di validazione.
1133
- Icona chevron nel select: funzionale (indica dropdown).
1134
- ═══════════════════════════════════════════════════════════ -->
1135
- <div *ngSwitchDefault class="space-y-1.5">
1093
+ <!-- ═══ primitive ═══ -->
1094
+ <div *ngSwitchDefault class="jsm-field">
1136
1095
  <ng-container *ngIf="inputKind !== 'checkbox'">
1137
- <label class="block text-[13px] font-semibold text-slate-700 dark:text-slate-300">
1138
- {{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
1096
+ <label class="jsm-field-label">
1097
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1139
1098
  </label>
1140
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 -mt-0.5">{{ schema.description }}</p>
1099
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1141
1100
  </ng-container>
1142
1101
 
1143
- <div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
1144
- <span *ngFor="let badge of metaBadges"
1145
- class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
1146
- [ngClass]="badgeClass(badge)">{{ badge }}</span>
1102
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1103
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1147
1104
  </div>
1148
1105
 
1149
- <ng-container [ngSwitch]="inputKind">
1150
-
1151
- <input *ngSwitchCase="'text'"
1152
- class="w-full rounded-lg border bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 py-2 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:ring-2"
1153
- [class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
1154
- [class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
1155
- [class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
1156
- [class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
1157
- [class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
1158
- [class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
1159
- [class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
1160
- [class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
1161
- [class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
1162
- [class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
1163
- [class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
1164
- [attr.type]="inputType"
1165
- [attr.placeholder]="schema.title || label"
1106
+ <ng-container>
1107
+ <input *ngIf="inputKind === 'text'" class="jsm-input"
1108
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1109
+ [attr.type]="inputType" [attr.placeholder]="schema.title || label"
1166
1110
  [formControl]="controlAsFormControl"
1167
- />
1111
+ (blur)="onBlur()" />
1168
1112
 
1169
- <textarea *ngSwitchCase="'textarea'" rows="4"
1170
- class="w-full resize-y rounded-lg border bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 py-2 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:ring-2"
1171
- [class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
1172
- [class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
1173
- [class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
1174
- [class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
1175
- [class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
1176
- [class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
1177
- [class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
1178
- [class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
1179
- [class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
1180
- [class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
1181
- [class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
1113
+ <textarea *ngIf="inputKind === 'textarea'" rows="4" class="jsm-textarea"
1114
+ [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1182
1115
  [attr.placeholder]="schema.title || label"
1183
1116
  [formControl]="controlAsFormControl"
1184
- ></textarea>
1117
+ (blur)="onBlur()"></textarea>
1185
1118
 
1186
- <!-- select: il chevron SVG è funzionale, indica che è un dropdown -->
1187
- <div *ngSwitchCase="'select'" class="relative">
1188
- <select
1189
- class="w-full appearance-none rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 px-3 py-2 pr-8 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:focus:ring-indigo-900/40"
1190
- [class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
1191
- [class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
1192
- [class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
1193
- [class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
1194
- [formControl]="controlAsFormControl">
1119
+ <div *ngIf="inputKind === 'select'" class="jsm-select-wrap">
1120
+ <select class="jsm-select"
1121
+ [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1122
+ [formControl]="controlAsFormControl"
1123
+ (blur)="onBlur()">
1195
1124
  <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1196
1125
  </select>
1197
- <svg class="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 dark:text-slate-500"
1198
- viewBox="0 0 16 16" fill="none">
1126
+ <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none">
1199
1127
  <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1200
1128
  </svg>
1201
1129
  </div>
1202
1130
 
1203
- <!-- checkbox come toggle switch -->
1204
- <label *ngSwitchCase="'checkbox'" class="inline-flex cursor-pointer items-center gap-2.5">
1205
- <span class="jsm-toggle-track" [class.jsm-toggle-on]="controlAsFormControl.value">
1131
+ <label *ngIf="inputKind === 'checkbox'" class="jsm-toggle-label">
1132
+ <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1206
1133
  <input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
1207
1134
  <span class="jsm-toggle-thumb"></span>
1208
1135
  </span>
1209
- <span class="text-sm font-medium text-slate-700 dark:text-slate-300">
1210
- {{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
1136
+ <span class="jsm-toggle-text">
1137
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1211
1138
  </span>
1212
1139
  </label>
1213
-
1214
1140
  </ng-container>
1215
1141
 
1216
- <p *ngIf="inputKind === 'checkbox' && schema.description" class="text-xs text-slate-500 dark:text-slate-400">{{ schema.description }}</p>
1217
-
1218
- <p *ngIf="effectiveSchema.examples?.length" class="text-[11px] text-slate-400 dark:text-slate-500">
1219
- e.g. {{ effectiveSchema.examples?.[0] }}
1220
- </p>
1221
-
1222
- <p *ngIf="effectiveSchema.contentMediaType" class="text-[11px] text-slate-400 dark:text-slate-500">
1142
+ <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1143
+ <p *ngIf="effectiveSchema.examples?.length" class="jsm-example">e.g. {{ effectiveSchema.examples?.[0] }}</p>
1144
+ <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example">
1223
1145
  {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1224
1146
  </p>
1225
1147
 
1226
- <!-- icona errore: funzionale, segnala un problema di validazione -->
1227
- <div *ngIf="errorsForPath.length && controlAsFormControl.dirty" class="space-y-1 pt-0.5">
1228
- <p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1229
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none">
1148
+ <div *ngIf="allErrors.length && (controlAsFormControl.dirty || isTouched || showErrors)" class="jsm-error-list">
1149
+ <p *ngFor="let error of allErrors" class="jsm-error-msg">
1150
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none">
1230
1151
  <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1231
1152
  <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1232
1153
  </svg>
@@ -1236,366 +1157,247 @@ class JsonSchemaNodeComponent {
1236
1157
  </div>
1237
1158
 
1238
1159
  </ng-container>
1239
- `, isInline: true, styles: [".jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;transition:background-color .2s;background-color:#cbd5e1}.jsm-toggle-track.jsm-toggle-on{background-color:#4f46e5}.dark .jsm-toggle-track:not(.jsm-toggle-on){background-color:#475569}.jsm-toggle-thumb{display:inline-block;width:1rem;height:1rem;border-radius:9999px;background:#fff;box-shadow:0 1px 3px #0003;transform:translate(.125rem);transition:transform .2s}.jsm-toggle-track.jsm-toggle-on .jsm-toggle-thumb{transform:translate(1.125rem)}\n"], dependencies: [{ kind: "component", type: JsonSchemaNodeComponent, selector: "jsm-schema-node", inputs: ["schema", "control", "label", "required", "path", "errorsMap", "parent", "controlKey", "allowAdditionalProperties"], outputs: ["controlReplaced"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1160
+ `, isInline: true, styles: [".jsm-field{display:flex;flex-direction:column;gap:.375rem}.jsm-field-label{display:block;font-size:.8125rem;font-weight:600;color:#374151}.jsm-field-hint{font-size:.75rem;color:#6b7280;margin:0;line-height:1.4}.jsm-required{color:#ef4444;margin-left:.125rem}.jsm-example{font-size:.6875rem;color:#9ca3af}.jsm-input,.jsm-textarea,.jsm-select{width:100%;border-radius:.5rem;border:1.5px solid #d1d5db;background:Canvas;padding:.5rem .75rem;font-size:.875rem;color:CanvasText;outline:none;box-shadow:0 1px 2px #0000000a;transition:border-color .15s,box-shadow .15s;font-family:inherit;box-sizing:border-box}.jsm-input:hover,.jsm-textarea:hover,.jsm-select:hover{border-color:#9ca3af}.jsm-input:focus,.jsm-textarea:focus,.jsm-select:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f126}.jsm-input--error,.jsm-textarea--error,.jsm-select--error{border-color:#f87171!important;background:#fff5f5}.jsm-input--error:focus,.jsm-textarea--error:focus,.jsm-select--error:focus{box-shadow:0 0 0 3px #f8717133!important}.jsm-textarea{resize:vertical;min-height:6rem}.jsm-select-wrap{position:relative}.jsm-select{appearance:none;padding-right:2.25rem;cursor:pointer}.jsm-select-chevron{position:absolute;right:.625rem;top:50%;transform:translateY(-50%);width:1rem;height:1rem;color:#9ca3af;pointer-events:none}.jsm-toggle-label{display:inline-flex;align-items:center;gap:.625rem;cursor:pointer;-webkit-user-select:none;user-select:none}.jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;background:#d1d5db;transition:background .2s}.jsm-toggle-track.jsm-on{background:#4f46e5}.jsm-toggle-thumb{display:inline-block;width:1rem;height:1rem;border-radius:9999px;background:#fff;box-shadow:0 1px 3px #0003;transform:translate(.125rem);transition:transform .2s}.jsm-toggle-track.jsm-on .jsm-toggle-thumb{transform:translate(1.125rem)}.jsm-toggle-text{font-size:.875rem;font-weight:500;color:#374151}.jsm-btn-primary{display:inline-flex;align-items:center;gap:.25rem;padding:.375rem .75rem;border-radius:.5rem;font-size:.8125rem;font-weight:600;background:#4f46e5;color:#fff;border:none;cursor:pointer;box-shadow:0 1px 2px #4f46e54d;transition:background .15s,transform .1s,box-shadow .15s;white-space:nowrap;font-family:inherit}.jsm-btn-primary:hover{background:#4338ca;transform:translateY(-1px);box-shadow:0 3px 8px #4f46e54d}.jsm-btn-primary:active{transform:translateY(0)}.jsm-btn-primary:disabled{opacity:.45;cursor:not-allowed;transform:none}.jsm-btn-danger{display:inline-flex;align-items:center;gap:.25rem;padding:.25rem .5rem;border-radius:.375rem;font-size:.75rem;font-weight:600;background:transparent;color:#dc2626;border:1.5px solid #fca5a5;cursor:pointer;transition:background .15s,border-color .15s;font-family:inherit}.jsm-btn-danger:hover{background:#fee2e2;border-color:#f87171}.jsm-error-list{display:flex;flex-direction:column;gap:.25rem;margin-top:.25rem}.jsm-error-msg{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#dc2626;font-weight:500}.jsm-error-icon{width:.875rem;height:.875rem;flex-shrink:0}.jsm-badge-row{display:flex;flex-wrap:wrap;gap:.375rem}.jsm-badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .5rem;font-size:.6875rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase}.jsm-badge--deprecated{background:#fef3c7;color:#92400e}.jsm-badge--readonly{background:#f1f5f9;color:#475569}.jsm-badge--writeonly{background:#dbeafe;color:#1e40af}.jsm-badge--not{background:#fee2e2;color:#991b1b}.jsm-badge--nullable{background:#f3f4f6;color:#6b7280}.jsm-fieldset{border:1.5px solid #e5e7eb;border-radius:.875rem;padding:1.25rem;background:transparent;box-shadow:0 1px 3px #0000000f;transition:box-shadow .2s;box-sizing:border-box}.jsm-fieldset:focus-within{border-color:#c7d2fe;box-shadow:0 0 0 3px #6366f114,0 1px 3px #0000000f}.jsm-legend{display:inline-flex;align-items:center;gap:.375rem;padding:0 .5rem;font-size:.875rem;font-weight:700;color:#1f2937}.jsm-obj-grid{display:grid;grid-template-columns:1fr;gap:1rem}@media (min-width:640px){.jsm-obj-grid{grid-template-columns:repeat(2,1fr)}}.jsm-obj-complex{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}.jsm-dynamic-key-header{display:flex;align-items:center;justify-content:space-between;padding:.25rem .5rem;background:#f9fafb;border-radius:.375rem;border:1px dashed #d1d5db;margin-bottom:.25rem}.jsm-dynamic-key-label{font-size:.75rem;font-weight:600;color:#6b7280;font-family:monospace}.jsm-add-prop{margin-top:1rem;padding:.875rem;background:#f9fafb;border-radius:.625rem;border:1.5px dashed #d1d5db;display:flex;flex-direction:column;gap:.5rem}.jsm-add-prop-label{font-size:.75rem;font-weight:600;color:#6b7280;text-transform:uppercase;letter-spacing:.04em}.jsm-add-prop-row{display:flex;gap:.5rem;align-items:center}.jsm-input--sm{padding:.375rem .625rem;font-size:.8125rem}.jsm-array-block{display:flex;flex-direction:column;gap:.75rem}.jsm-array-header{display:flex;align-items:flex-start;justify-content:space-between;gap:.5rem}.jsm-array-title{font-size:.875rem;font-weight:700;color:#1f2937;display:flex;align-items:center;gap:.375rem}.jsm-array-items{display:flex;flex-direction:column;gap:.75rem}.jsm-array-item{border:1.5px solid #e5e7eb;border-radius:.75rem;padding:.875rem;background:transparent;box-shadow:0 1px 2px #0000000a;transition:border-color .15s,box-shadow .15s}.jsm-array-item:focus-within{border-color:#c7d2fe;box-shadow:0 0 0 3px #6366f114}.jsm-array-item-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem}.jsm-array-index{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:9999px;background:#eef2ff;color:#4f46e5;font-size:.6875rem;font-weight:700}.jsm-empty-array{display:flex;flex-direction:column;align-items:center;gap:.5rem;padding:2rem 1rem;border:1.5px dashed #e5e7eb;border-radius:.75rem;color:#9ca3af;font-size:.8125rem;text-align:center}.jsm-contains-hint{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#6366f1;background:#eef2ff;border-radius:.375rem;padding:.375rem .625rem}.jsm-combinator{border:1.5px solid #e5e7eb;border-radius:.875rem;overflow:hidden;background:transparent;box-shadow:0 1px 3px #0000000f}.jsm-combinator-header{display:flex;align-items:flex-start;justify-content:space-between;padding:.875rem 1rem;background:#00000008;border-bottom:1.5px solid #e5e7eb}.jsm-combinator-title{font-size:.875rem;font-weight:700;color:#1f2937}.jsm-combinator-body{padding:1rem}.jsm-combinator-hint{font-size:.75rem;color:#6b7280;margin:.25rem 0 0}.jsm-oneof-tabs{display:flex;overflow-x:auto;border-bottom:1.5px solid #e5e7eb}.jsm-tab{padding:.5rem 1rem;font-size:.8125rem;font-weight:500;color:#6b7280;border:none;background:transparent;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1.5px;transition:color .15s,border-color .15s;white-space:nowrap;font-family:inherit}.jsm-tab:hover{color:#4f46e5}.jsm-tab--active{color:#4f46e5;border-bottom-color:#4f46e5;font-weight:600}.jsm-anyof-options{display:flex;flex-wrap:wrap;gap:.5rem;padding:.75rem 1rem;border-bottom:1.5px solid #e5e7eb}.jsm-anyof-option{display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border-radius:9999px;border:1.5px solid #e5e7eb;font-size:.8125rem;font-weight:500;color:#6b7280;cursor:pointer;transition:all .15s;background:transparent}.jsm-anyof-option:hover{border-color:#a5b4fc;color:#4f46e5}.jsm-anyof-option--active{border-color:#6366f1;background:#eef2ff;color:#4f46e5}.jsm-allof-section{border-left:3px solid #e0e7ff;padding-left:1rem}.jsm-combinator-badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .5rem;font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;flex-shrink:0;margin-left:.75rem}.jsm-badge--oneof{background:#dbeafe;color:#1e40af}.jsm-badge--anyof{background:#ede9fe;color:#5b21b6}.jsm-badge--allof{background:#ccfbf1;color:#065f46}.dark .jsm-field-label{color:#d1d5db}.dark .jsm-field-hint{color:#9ca3af}.dark .jsm-toggle-track{background:#475569}.dark .jsm-toggle-track.jsm-on{background:#4f46e5}.dark .jsm-toggle-text{color:#d1d5db}.dark .jsm-input,.dark .jsm-textarea,.dark .jsm-select{border-color:#475569;color:#f1f5f9}.dark .jsm-input:focus,.dark .jsm-textarea:focus,.dark .jsm-select:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f133}.dark .jsm-input--error,.dark .jsm-textarea--error,.dark .jsm-select--error{border-color:#f87171!important;background:#f871711a}.dark .jsm-fieldset{border-color:#334155}.dark .jsm-fieldset:focus-within{border-color:#6366f1}.dark .jsm-legend{color:#f1f5f9}.dark .jsm-combinator{border-color:#334155}.dark .jsm-combinator-header{background:#ffffff0a;border-color:#334155}.dark .jsm-combinator-title{color:#f1f5f9}.dark .jsm-oneof-tabs{border-color:#334155}.dark .jsm-tab{color:#94a3b8}.dark .jsm-tab--active{color:#818cf8;border-bottom-color:#818cf8}.dark .jsm-anyof-options{border-color:#334155}.dark .jsm-anyof-option{border-color:#334155;color:#94a3b8;background:#0f172a}.dark .jsm-anyof-option--active{border-color:#6366f1;background:#6366f126;color:#818cf8}.dark .jsm-allof-section{border-left-color:#3730a3}.dark .jsm-array-item{border-color:#334155}.dark .jsm-array-item:focus-within{border-color:#6366f1}.dark .jsm-array-index{background:#6366f133;color:#818cf8}.dark .jsm-empty-array{border-color:#334155;color:#64748b}.dark .jsm-contains-hint{background:#6366f126;color:#818cf8}.dark .jsm-add-prop{background:#1e293b66;border-color:#475569}.dark .jsm-add-prop-label{color:#64748b}.dark .jsm-dynamic-key-header{background:#1e293b80;border-color:#475569}.dark .jsm-dynamic-key-label{color:#94a3b8}.dark .jsm-error-msg{color:#f87171}.dark .jsm-array-title{color:#f1f5f9}.dark .jsm-badge--deprecated{background:#d9770633;color:#fbbf24}.dark .jsm-badge--readonly{background:#1e293b;color:#94a3b8}.dark .jsm-badge--writeonly{background:#2563eb33;color:#60a5fa}.dark .jsm-badge--not{background:#dc262633;color:#f87171}.dark .jsm-badge--nullable{background:#1e293b;color:#94a3b8}.dark .jsm-select-chevron{color:#64748b}\n"], dependencies: [{ kind: "component", type: JsonSchemaNodeComponent, selector: "jsm-schema-node", inputs: ["schema", "control", "label", "required", "path", "errorsMap", "parent", "controlKey", "allowAdditionalProperties", "showErrors"], outputs: ["controlReplaced"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1240
1161
  }
1241
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
1162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
1242
1163
  type: Component,
1243
1164
  args: [{ selector: 'jsm-schema-node', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule], encapsulation: ViewEncapsulation.None, template: `
1244
1165
  <ng-container [ngSwitch]="nodeKind">
1245
1166
 
1246
- <!-- ═══════════════════════════════════════════════════════════
1247
- oneOf — tab bar: ogni opzione è una tab, nessuna icona
1248
- ═══════════════════════════════════════════════════════════ -->
1249
- <div *ngSwitchCase="'oneOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
1250
- <div class="flex items-start justify-between px-4 py-3 bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-700">
1167
+ <!-- ═══ oneOf ═══ -->
1168
+ <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
1169
+ <div class="jsm-combinator-header">
1251
1170
  <div>
1252
- <p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
1253
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
1171
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1172
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1254
1173
  </div>
1255
- <span class="ml-3 shrink-0 rounded-full bg-blue-100 dark:bg-blue-900/40 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-blue-700 dark:text-blue-400">oneOf</span>
1174
+ <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1256
1175
  </div>
1257
- <div class="flex overflow-x-auto border-b border-slate-200 dark:border-slate-700">
1258
- <button
1259
- *ngFor="let option of schema.oneOf; let i = index"
1260
- type="button"
1261
- class="px-4 py-2.5 text-xs font-medium whitespace-nowrap border-b-2 transition-colors"
1262
- [class.border-indigo-500]="selectedOneOf === i"
1263
- [class.text-indigo-600]="selectedOneOf === i"
1264
- [class.dark:text-indigo-400]="selectedOneOf === i"
1265
- [class.font-semibold]="selectedOneOf === i"
1266
- [class.border-transparent]="selectedOneOf !== i"
1267
- [class.text-slate-500]="selectedOneOf !== i"
1268
- [class.dark:text-slate-400]="selectedOneOf !== i"
1269
- (click)="selectedOneOf = i; onOneOfChange()"
1270
- >{{ option.title || ('Option ' + (i + 1)) }}</button>
1176
+ <div class="jsm-oneof-tabs">
1177
+ <button *ngFor="let option of schema.oneOf; let i = index" type="button"
1178
+ class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
1179
+ (click)="selectedOneOf = i; onOneOfChange()">
1180
+ {{ option.title || ('Option ' + (i + 1)) }}
1181
+ </button>
1271
1182
  </div>
1272
- <div class="p-4" *ngIf="activeVariant">
1183
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
1273
1184
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1274
1185
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1275
- [allowAdditionalProperties]="allowAdditionalProperties"
1186
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1276
1187
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1277
1188
  </div>
1278
1189
  </div>
1279
1190
 
1280
- <!-- ═══════════════════════════════════════════════════════════
1281
- anyOf — pill toggle, nessuna icona
1282
- ═══════════════════════════════════════════════════════════ -->
1283
- <div *ngSwitchCase="'anyOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
1284
- <div class="flex items-start justify-between px-4 py-3 bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-700">
1191
+ <!-- ═══ anyOf ═══ -->
1192
+ <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
1193
+ <div class="jsm-combinator-header">
1285
1194
  <div>
1286
- <p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
1287
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
1195
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1196
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1288
1197
  </div>
1289
- <span class="ml-3 shrink-0 rounded-full bg-violet-100 dark:bg-violet-900/40 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-violet-700 dark:text-violet-400">anyOf</span>
1198
+ <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1290
1199
  </div>
1291
- <div class="flex flex-wrap gap-2 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
1292
- <label
1293
- *ngFor="let option of schema.anyOf; let i = index"
1294
- class="inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium cursor-pointer transition-colors select-none"
1295
- [class.border-indigo-400]="selectedAnyOf.has(i)"
1296
- [class.bg-indigo-50]="selectedAnyOf.has(i)"
1297
- [class.dark:bg-indigo-900/30]="selectedAnyOf.has(i)"
1298
- [class.text-indigo-700]="selectedAnyOf.has(i)"
1299
- [class.dark:text-indigo-400]="selectedAnyOf.has(i)"
1300
- [class.border-slate-200]="!selectedAnyOf.has(i)"
1301
- [class.dark:border-slate-600]="!selectedAnyOf.has(i)"
1302
- [class.text-slate-600]="!selectedAnyOf.has(i)"
1303
- [class.dark:text-slate-400]="!selectedAnyOf.has(i)"
1304
- >
1305
- <input type="checkbox" class="h-3.5 w-3.5 accent-indigo-600" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1200
+ <div class="jsm-anyof-options">
1201
+ <label *ngFor="let option of schema.anyOf; let i = index"
1202
+ class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
1203
+ <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1306
1204
  {{ option.title || ('Option ' + (i + 1)) }}
1307
1205
  </label>
1308
1206
  </div>
1309
- <div class="p-4" *ngIf="activeVariant">
1207
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
1310
1208
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1311
1209
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1312
- [allowAdditionalProperties]="allowAdditionalProperties"
1210
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1313
1211
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1314
1212
  </div>
1315
1213
  </div>
1316
1214
 
1317
- <!-- ═══════════════════════════════════════════════════════════
1318
- allOf — sezioni in colonna con bordo sinistro
1319
- ═══════════════════════════════════════════════════════════ -->
1320
- <div *ngSwitchCase="'allOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
1321
- <div class="flex items-start justify-between px-4 py-3 bg-slate-50 dark:bg-slate-800/60 border-b border-slate-200 dark:border-slate-700">
1215
+ <!-- ═══ allOf ═══ -->
1216
+ <div *ngSwitchCase="'allOf'" class="jsm-combinator">
1217
+ <div class="jsm-combinator-header">
1322
1218
  <div>
1323
- <p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
1324
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
1219
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1220
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1325
1221
  </div>
1326
- <span class="ml-3 shrink-0 rounded-full bg-teal-100 dark:bg-teal-900/40 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-teal-700 dark:text-teal-400">allOf</span>
1222
+ <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1327
1223
  </div>
1328
- <div class="p-4 space-y-4">
1329
- <div *ngFor="let subSchema of mergedAllOf; let i = index" class="border-l-2 border-indigo-200 dark:border-indigo-700 pl-4">
1224
+ <div class="jsm-combinator-body">
1225
+ <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
1330
1226
  <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
1331
1227
  [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
1332
- [allowAdditionalProperties]="allowAdditionalProperties"
1228
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1333
1229
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1334
1230
  </div>
1335
1231
  </div>
1336
1232
  </div>
1337
1233
 
1338
- <!-- ═══════════════════════════════════════════════════════════
1339
- object — fieldset con griglia SOLO per campi primitivi.
1340
- Logica layout:
1341
- - I figli vengono classificati come "semplici" (primitive/enum/bool)
1342
- o "complessi" (object/array/oneOf/anyOf/allOf).
1343
- - I semplici vanno in griglia 2 colonne (sm:grid-cols-2).
1344
- - I complessi vanno sempre in colonna piena (col-span-2).
1345
- ═══════════════════════════════════════════════════════════ -->
1346
- <fieldset *ngSwitchCase="'object'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm p-4 transition-shadow focus-within:border-indigo-300 dark:focus-within:border-indigo-600 focus-within:ring-2 focus-within:ring-indigo-100 dark:focus-within:ring-indigo-900/40">
1347
- <legend class="px-2 text-sm font-bold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</legend>
1234
+ <!-- ═══ object ═══ -->
1235
+ <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
1236
+ <legend class="jsm-legend">{{ schema.title || label }}</legend>
1237
+ <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1348
1238
 
1349
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mb-3 mt-1">{{ schema.description }}</p>
1350
-
1351
- <div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5 mb-3">
1352
- <span *ngFor="let badge of metaBadges"
1353
- class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
1354
- [ngClass]="badgeClass(badge)">{{ badge }}</span>
1239
+ <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
1240
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1355
1241
  </div>
1356
1242
 
1357
- <!-- campi semplici (string/number/bool/enum) → griglia 2 colonne -->
1358
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-4" [formGroup]="controlAsGroup">
1243
+ <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1359
1244
  <ng-container *ngFor="let key of simpleKeys">
1360
- <div class="flex flex-col gap-1">
1361
- <div *ngIf="isDynamicKey(key)"
1362
- class="flex items-center justify-between rounded-md border border-dashed border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800/50 px-2 py-1 mb-1">
1363
- <span class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</span>
1364
- <button type="button"
1365
- class="rounded px-1.5 py-0.5 text-xs font-semibold text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
1366
- (click)="removeDynamicKey(key)">Remove</button>
1245
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1246
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1247
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1248
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1367
1249
  </div>
1368
- <jsm-schema-node
1369
- [schema]="schemaForKey(key)"
1370
- [control]="childControl(key)"
1371
- [parent]="controlAsGroup"
1372
- [controlKey]="key"
1373
- [path]="pathForChild(key)"
1374
- [errorsMap]="errorsMap"
1375
- [label]="key"
1376
- [required]="isRequired(key)"
1377
- [allowAdditionalProperties]="allowAdditionalProperties"
1378
- (controlReplaced)="controlReplaced.emit($event)">
1379
- </jsm-schema-node>
1250
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1251
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1252
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1253
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1380
1254
  </div>
1381
1255
  </ng-container>
1382
1256
  </div>
1383
1257
 
1384
- <!-- campi complessi (object/array/combinator) → colonna piena -->
1385
- <div class="space-y-4 mt-4" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1258
+ <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1386
1259
  <ng-container *ngFor="let key of complexKeys">
1387
- <div class="flex flex-col gap-1">
1388
- <div *ngIf="isDynamicKey(key)"
1389
- class="flex items-center justify-between rounded-md border border-dashed border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800/50 px-2 py-1 mb-1">
1390
- <span class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</span>
1391
- <button type="button"
1392
- class="rounded px-1.5 py-0.5 text-xs font-semibold text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
1393
- (click)="removeDynamicKey(key)">Remove</button>
1260
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1261
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1262
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1263
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1394
1264
  </div>
1395
- <jsm-schema-node
1396
- [schema]="schemaForKey(key)"
1397
- [control]="childControl(key)"
1398
- [parent]="controlAsGroup"
1399
- [controlKey]="key"
1400
- [path]="pathForChild(key)"
1401
- [errorsMap]="errorsMap"
1402
- [label]="key"
1403
- [required]="isRequired(key)"
1404
- [allowAdditionalProperties]="allowAdditionalProperties"
1405
- (controlReplaced)="controlReplaced.emit($event)">
1406
- </jsm-schema-node>
1265
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1266
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1267
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1268
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1407
1269
  </div>
1408
1270
  </ng-container>
1409
1271
  </div>
1410
1272
 
1411
- <!-- add dynamic property -->
1412
- <div *ngIf="canAddProperty" class="mt-4 rounded-lg border border-dashed border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800/40 p-3 space-y-2">
1413
- <p class="text-[10px] font-bold uppercase tracking-widest text-slate-400 dark:text-slate-500">Add property</p>
1414
- <div class="flex gap-2">
1415
- <input
1416
- class="flex-1 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 py-1.5 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:focus:ring-indigo-900/40"
1417
- [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1418
- <button type="button"
1419
- class="rounded-lg bg-indigo-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-700 active:scale-95"
1420
- (click)="addDynamicProperty()">Add</button>
1273
+ <div *ngIf="canAddProperty" class="jsm-add-prop">
1274
+ <p class="jsm-add-prop-label">Add property</p>
1275
+ <div class="jsm-add-prop-row">
1276
+ <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1277
+ <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">Add</button>
1421
1278
  </div>
1422
- <p *ngIf="propertyError" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1423
- <!-- icona errore: unico caso in cui ha senso, segnala un problema -->
1424
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1279
+ <p *ngIf="propertyError" class="jsm-error-msg">
1280
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1425
1281
  {{ propertyError }}
1426
1282
  </p>
1427
1283
  </div>
1428
1284
 
1429
- <div *ngIf="errorsForPath.length" class="mt-3 space-y-1">
1430
- <p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1431
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1285
+ <div *ngIf="errorsForPath.length" class="jsm-error-list" style="margin-top:.75rem;">
1286
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1287
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1432
1288
  {{ error }}
1433
1289
  </p>
1434
1290
  </div>
1435
1291
  </fieldset>
1436
1292
 
1437
- <!-- ═══════════════════════════════════════════════════════════
1438
- array — sempre in colonna piena, items in colonna
1439
- ═══════════════════════════════════════════════════════════ -->
1440
- <div *ngSwitchCase="'array'" class="space-y-3">
1441
- <div class="flex items-start justify-between gap-2">
1293
+ <!-- ═══ array ═══ -->
1294
+ <div *ngSwitchCase="'array'" class="jsm-array-block">
1295
+ <div class="jsm-array-header">
1442
1296
  <div>
1443
- <p class="text-sm font-semibold text-slate-800">
1444
- {{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
1297
+ <p class="jsm-array-title">
1298
+ {{ schema.title || label }}<span *ngIf="required" style="color:#ef4444;margin-left:.125rem;">*</span>
1445
1299
  </p>
1446
- <p *ngIf="schema.description" class="text-xs text-slate-500 mt-0.5">{{ schema.description }}</p>
1300
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1447
1301
  </div>
1448
- <!-- icona + nel bottone: indica l'azione "aggiungi", non decorativa -->
1449
- <button type="button"
1450
- class="shrink-0 flex items-center gap-1 rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm transition hover:bg-indigo-700 active:scale-95 disabled:opacity-40 disabled:cursor-not-allowed"
1451
- (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1452
- <svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1302
+ <button type="button" class="jsm-btn-primary" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1303
+ <svg style="width:.875rem;height:.875rem;flex-shrink:0;" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1453
1304
  Add item
1454
1305
  </button>
1455
1306
  </div>
1456
1307
 
1457
- <div *ngIf="effectiveSchema.contains" class="rounded-md bg-indigo-50 dark:bg-indigo-900/30 px-3 py-1.5 text-xs text-indigo-700 dark:text-indigo-400">
1308
+ <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1458
1309
  Must contain matching item(s)
1459
1310
  <span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
1460
1311
  <span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
1461
1312
  </div>
1462
1313
 
1463
- <div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
1464
- <span *ngFor="let badge of metaBadges"
1465
- class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
1466
- [ngClass]="badgeClass(badge)">{{ badge }}</span>
1314
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1315
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1467
1316
  </div>
1468
1317
 
1469
- <div class="space-y-2" *ngIf="arrayControls.length > 0; else emptyArray">
1470
- <div *ngFor="let item of arrayControls; let i = index"
1471
- class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-3 shadow-sm transition focus-within:border-indigo-300 dark:focus-within:border-indigo-600 focus-within:ring-2 focus-within:ring-indigo-100 dark:focus-within:ring-indigo-900/40">
1472
- <div class="flex items-center justify-between mb-3">
1473
- <span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-indigo-100 dark:bg-indigo-900/50 text-[11px] font-bold text-indigo-600 dark:text-indigo-400">{{ i + 1 }}</span>
1474
- <button type="button"
1475
- class="rounded-md border border-red-200 dark:border-red-800 px-2 py-0.5 text-xs font-semibold text-red-600 dark:text-red-400 transition hover:bg-red-50 dark:hover:bg-red-900/20"
1476
- (click)="removeArrayItem(i)">Remove</button>
1318
+ <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1319
+ <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item">
1320
+ <div class="jsm-array-item-header">
1321
+ <span class="jsm-array-index">{{ i + 1 }}</span>
1322
+ <button type="button" class="jsm-btn-danger" (click)="removeArrayItem(i)">Remove</button>
1477
1323
  </div>
1478
1324
  <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1479
1325
  [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1480
- [allowAdditionalProperties]="allowAdditionalProperties"
1326
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1481
1327
  (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1482
1328
  </div>
1483
1329
  </div>
1484
1330
 
1485
1331
  <ng-template #emptyArray>
1486
- <div class="flex flex-col items-center gap-2 rounded-xl border border-dashed border-slate-200 dark:border-slate-700 py-8 text-slate-400 dark:text-slate-500 text-xs">
1487
- No items yet — click "Add item" to start.
1488
- </div>
1332
+ <div class="jsm-empty-array">No items yet — click "Add item" to start.</div>
1489
1333
  </ng-template>
1490
1334
 
1491
- <div *ngIf="errorsForPath.length" class="space-y-1">
1492
- <p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1493
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1335
+ <div *ngIf="errorsForPath.length" class="jsm-error-list">
1336
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1337
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/><path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1494
1338
  {{ error }}
1495
1339
  </p>
1496
1340
  </div>
1497
1341
  </div>
1498
1342
 
1499
- <!-- ═══════════════════════════════════════════════════════════
1500
- primitive — sempre in colonna, nessuna icona decorativa.
1501
- Icona errore solo nei messaggi di validazione.
1502
- Icona chevron nel select: funzionale (indica dropdown).
1503
- ═══════════════════════════════════════════════════════════ -->
1504
- <div *ngSwitchDefault class="space-y-1.5">
1343
+ <!-- ═══ primitive ═══ -->
1344
+ <div *ngSwitchDefault class="jsm-field">
1505
1345
  <ng-container *ngIf="inputKind !== 'checkbox'">
1506
- <label class="block text-[13px] font-semibold text-slate-700 dark:text-slate-300">
1507
- {{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
1346
+ <label class="jsm-field-label">
1347
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1508
1348
  </label>
1509
- <p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 -mt-0.5">{{ schema.description }}</p>
1349
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1510
1350
  </ng-container>
1511
1351
 
1512
- <div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
1513
- <span *ngFor="let badge of metaBadges"
1514
- class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
1515
- [ngClass]="badgeClass(badge)">{{ badge }}</span>
1352
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1353
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1516
1354
  </div>
1517
1355
 
1518
- <ng-container [ngSwitch]="inputKind">
1519
-
1520
- <input *ngSwitchCase="'text'"
1521
- class="w-full rounded-lg border bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 py-2 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:ring-2"
1522
- [class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
1523
- [class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
1524
- [class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
1525
- [class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
1526
- [class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
1527
- [class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
1528
- [class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
1529
- [class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
1530
- [class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
1531
- [class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
1532
- [class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
1533
- [attr.type]="inputType"
1534
- [attr.placeholder]="schema.title || label"
1356
+ <ng-container>
1357
+ <input *ngIf="inputKind === 'text'" class="jsm-input"
1358
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1359
+ [attr.type]="inputType" [attr.placeholder]="schema.title || label"
1535
1360
  [formControl]="controlAsFormControl"
1536
- />
1361
+ (blur)="onBlur()" />
1537
1362
 
1538
- <textarea *ngSwitchCase="'textarea'" rows="4"
1539
- class="w-full resize-y rounded-lg border bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 py-2 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:ring-2"
1540
- [class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
1541
- [class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
1542
- [class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
1543
- [class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
1544
- [class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
1545
- [class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
1546
- [class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
1547
- [class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
1548
- [class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
1549
- [class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
1550
- [class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
1363
+ <textarea *ngIf="inputKind === 'textarea'" rows="4" class="jsm-textarea"
1364
+ [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1551
1365
  [attr.placeholder]="schema.title || label"
1552
1366
  [formControl]="controlAsFormControl"
1553
- ></textarea>
1367
+ (blur)="onBlur()"></textarea>
1554
1368
 
1555
- <!-- select: il chevron SVG è funzionale, indica che è un dropdown -->
1556
- <div *ngSwitchCase="'select'" class="relative">
1557
- <select
1558
- class="w-full appearance-none rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 px-3 py-2 pr-8 text-sm shadow-sm outline-none transition hover:border-slate-400 dark:hover:border-slate-500 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:focus:ring-indigo-900/40"
1559
- [class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
1560
- [class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
1561
- [class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
1562
- [class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
1563
- [formControl]="controlAsFormControl">
1369
+ <div *ngIf="inputKind === 'select'" class="jsm-select-wrap">
1370
+ <select class="jsm-select"
1371
+ [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1372
+ [formControl]="controlAsFormControl"
1373
+ (blur)="onBlur()">
1564
1374
  <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1565
1375
  </select>
1566
- <svg class="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 dark:text-slate-500"
1567
- viewBox="0 0 16 16" fill="none">
1376
+ <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none">
1568
1377
  <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1569
1378
  </svg>
1570
1379
  </div>
1571
1380
 
1572
- <!-- checkbox come toggle switch -->
1573
- <label *ngSwitchCase="'checkbox'" class="inline-flex cursor-pointer items-center gap-2.5">
1574
- <span class="jsm-toggle-track" [class.jsm-toggle-on]="controlAsFormControl.value">
1381
+ <label *ngIf="inputKind === 'checkbox'" class="jsm-toggle-label">
1382
+ <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1575
1383
  <input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
1576
1384
  <span class="jsm-toggle-thumb"></span>
1577
1385
  </span>
1578
- <span class="text-sm font-medium text-slate-700 dark:text-slate-300">
1579
- {{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
1386
+ <span class="jsm-toggle-text">
1387
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1580
1388
  </span>
1581
1389
  </label>
1582
-
1583
1390
  </ng-container>
1584
1391
 
1585
- <p *ngIf="inputKind === 'checkbox' && schema.description" class="text-xs text-slate-500 dark:text-slate-400">{{ schema.description }}</p>
1586
-
1587
- <p *ngIf="effectiveSchema.examples?.length" class="text-[11px] text-slate-400 dark:text-slate-500">
1588
- e.g. {{ effectiveSchema.examples?.[0] }}
1589
- </p>
1590
-
1591
- <p *ngIf="effectiveSchema.contentMediaType" class="text-[11px] text-slate-400 dark:text-slate-500">
1392
+ <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1393
+ <p *ngIf="effectiveSchema.examples?.length" class="jsm-example">e.g. {{ effectiveSchema.examples?.[0] }}</p>
1394
+ <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example">
1592
1395
  {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1593
1396
  </p>
1594
1397
 
1595
- <!-- icona errore: funzionale, segnala un problema di validazione -->
1596
- <div *ngIf="errorsForPath.length && controlAsFormControl.dirty" class="space-y-1 pt-0.5">
1597
- <p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
1598
- <svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none">
1398
+ <div *ngIf="allErrors.length && (controlAsFormControl.dirty || isTouched || showErrors)" class="jsm-error-list">
1399
+ <p *ngFor="let error of allErrors" class="jsm-error-msg">
1400
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none">
1599
1401
  <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1600
1402
  <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1601
1403
  </svg>
@@ -1605,8 +1407,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1605
1407
  </div>
1606
1408
 
1607
1409
  </ng-container>
1608
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;transition:background-color .2s;background-color:#cbd5e1}.jsm-toggle-track.jsm-toggle-on{background-color:#4f46e5}.dark .jsm-toggle-track:not(.jsm-toggle-on){background-color:#475569}.jsm-toggle-thumb{display:inline-block;width:1rem;height:1rem;border-radius:9999px;background:#fff;box-shadow:0 1px 3px #0003;transform:translate(.125rem);transition:transform .2s}.jsm-toggle-track.jsm-toggle-on .jsm-toggle-thumb{transform:translate(1.125rem)}\n"] }]
1609
- }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaValidationService }], propDecorators: { schema: [{
1410
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".jsm-field{display:flex;flex-direction:column;gap:.375rem}.jsm-field-label{display:block;font-size:.8125rem;font-weight:600;color:#374151}.jsm-field-hint{font-size:.75rem;color:#6b7280;margin:0;line-height:1.4}.jsm-required{color:#ef4444;margin-left:.125rem}.jsm-example{font-size:.6875rem;color:#9ca3af}.jsm-input,.jsm-textarea,.jsm-select{width:100%;border-radius:.5rem;border:1.5px solid #d1d5db;background:Canvas;padding:.5rem .75rem;font-size:.875rem;color:CanvasText;outline:none;box-shadow:0 1px 2px #0000000a;transition:border-color .15s,box-shadow .15s;font-family:inherit;box-sizing:border-box}.jsm-input:hover,.jsm-textarea:hover,.jsm-select:hover{border-color:#9ca3af}.jsm-input:focus,.jsm-textarea:focus,.jsm-select:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f126}.jsm-input--error,.jsm-textarea--error,.jsm-select--error{border-color:#f87171!important;background:#fff5f5}.jsm-input--error:focus,.jsm-textarea--error:focus,.jsm-select--error:focus{box-shadow:0 0 0 3px #f8717133!important}.jsm-textarea{resize:vertical;min-height:6rem}.jsm-select-wrap{position:relative}.jsm-select{appearance:none;padding-right:2.25rem;cursor:pointer}.jsm-select-chevron{position:absolute;right:.625rem;top:50%;transform:translateY(-50%);width:1rem;height:1rem;color:#9ca3af;pointer-events:none}.jsm-toggle-label{display:inline-flex;align-items:center;gap:.625rem;cursor:pointer;-webkit-user-select:none;user-select:none}.jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;background:#d1d5db;transition:background .2s}.jsm-toggle-track.jsm-on{background:#4f46e5}.jsm-toggle-thumb{display:inline-block;width:1rem;height:1rem;border-radius:9999px;background:#fff;box-shadow:0 1px 3px #0003;transform:translate(.125rem);transition:transform .2s}.jsm-toggle-track.jsm-on .jsm-toggle-thumb{transform:translate(1.125rem)}.jsm-toggle-text{font-size:.875rem;font-weight:500;color:#374151}.jsm-btn-primary{display:inline-flex;align-items:center;gap:.25rem;padding:.375rem .75rem;border-radius:.5rem;font-size:.8125rem;font-weight:600;background:#4f46e5;color:#fff;border:none;cursor:pointer;box-shadow:0 1px 2px #4f46e54d;transition:background .15s,transform .1s,box-shadow .15s;white-space:nowrap;font-family:inherit}.jsm-btn-primary:hover{background:#4338ca;transform:translateY(-1px);box-shadow:0 3px 8px #4f46e54d}.jsm-btn-primary:active{transform:translateY(0)}.jsm-btn-primary:disabled{opacity:.45;cursor:not-allowed;transform:none}.jsm-btn-danger{display:inline-flex;align-items:center;gap:.25rem;padding:.25rem .5rem;border-radius:.375rem;font-size:.75rem;font-weight:600;background:transparent;color:#dc2626;border:1.5px solid #fca5a5;cursor:pointer;transition:background .15s,border-color .15s;font-family:inherit}.jsm-btn-danger:hover{background:#fee2e2;border-color:#f87171}.jsm-error-list{display:flex;flex-direction:column;gap:.25rem;margin-top:.25rem}.jsm-error-msg{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#dc2626;font-weight:500}.jsm-error-icon{width:.875rem;height:.875rem;flex-shrink:0}.jsm-badge-row{display:flex;flex-wrap:wrap;gap:.375rem}.jsm-badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .5rem;font-size:.6875rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase}.jsm-badge--deprecated{background:#fef3c7;color:#92400e}.jsm-badge--readonly{background:#f1f5f9;color:#475569}.jsm-badge--writeonly{background:#dbeafe;color:#1e40af}.jsm-badge--not{background:#fee2e2;color:#991b1b}.jsm-badge--nullable{background:#f3f4f6;color:#6b7280}.jsm-fieldset{border:1.5px solid #e5e7eb;border-radius:.875rem;padding:1.25rem;background:transparent;box-shadow:0 1px 3px #0000000f;transition:box-shadow .2s;box-sizing:border-box}.jsm-fieldset:focus-within{border-color:#c7d2fe;box-shadow:0 0 0 3px #6366f114,0 1px 3px #0000000f}.jsm-legend{display:inline-flex;align-items:center;gap:.375rem;padding:0 .5rem;font-size:.875rem;font-weight:700;color:#1f2937}.jsm-obj-grid{display:grid;grid-template-columns:1fr;gap:1rem}@media (min-width:640px){.jsm-obj-grid{grid-template-columns:repeat(2,1fr)}}.jsm-obj-complex{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}.jsm-dynamic-key-header{display:flex;align-items:center;justify-content:space-between;padding:.25rem .5rem;background:#f9fafb;border-radius:.375rem;border:1px dashed #d1d5db;margin-bottom:.25rem}.jsm-dynamic-key-label{font-size:.75rem;font-weight:600;color:#6b7280;font-family:monospace}.jsm-add-prop{margin-top:1rem;padding:.875rem;background:#f9fafb;border-radius:.625rem;border:1.5px dashed #d1d5db;display:flex;flex-direction:column;gap:.5rem}.jsm-add-prop-label{font-size:.75rem;font-weight:600;color:#6b7280;text-transform:uppercase;letter-spacing:.04em}.jsm-add-prop-row{display:flex;gap:.5rem;align-items:center}.jsm-input--sm{padding:.375rem .625rem;font-size:.8125rem}.jsm-array-block{display:flex;flex-direction:column;gap:.75rem}.jsm-array-header{display:flex;align-items:flex-start;justify-content:space-between;gap:.5rem}.jsm-array-title{font-size:.875rem;font-weight:700;color:#1f2937;display:flex;align-items:center;gap:.375rem}.jsm-array-items{display:flex;flex-direction:column;gap:.75rem}.jsm-array-item{border:1.5px solid #e5e7eb;border-radius:.75rem;padding:.875rem;background:transparent;box-shadow:0 1px 2px #0000000a;transition:border-color .15s,box-shadow .15s}.jsm-array-item:focus-within{border-color:#c7d2fe;box-shadow:0 0 0 3px #6366f114}.jsm-array-item-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem}.jsm-array-index{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:9999px;background:#eef2ff;color:#4f46e5;font-size:.6875rem;font-weight:700}.jsm-empty-array{display:flex;flex-direction:column;align-items:center;gap:.5rem;padding:2rem 1rem;border:1.5px dashed #e5e7eb;border-radius:.75rem;color:#9ca3af;font-size:.8125rem;text-align:center}.jsm-contains-hint{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#6366f1;background:#eef2ff;border-radius:.375rem;padding:.375rem .625rem}.jsm-combinator{border:1.5px solid #e5e7eb;border-radius:.875rem;overflow:hidden;background:transparent;box-shadow:0 1px 3px #0000000f}.jsm-combinator-header{display:flex;align-items:flex-start;justify-content:space-between;padding:.875rem 1rem;background:#00000008;border-bottom:1.5px solid #e5e7eb}.jsm-combinator-title{font-size:.875rem;font-weight:700;color:#1f2937}.jsm-combinator-body{padding:1rem}.jsm-combinator-hint{font-size:.75rem;color:#6b7280;margin:.25rem 0 0}.jsm-oneof-tabs{display:flex;overflow-x:auto;border-bottom:1.5px solid #e5e7eb}.jsm-tab{padding:.5rem 1rem;font-size:.8125rem;font-weight:500;color:#6b7280;border:none;background:transparent;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1.5px;transition:color .15s,border-color .15s;white-space:nowrap;font-family:inherit}.jsm-tab:hover{color:#4f46e5}.jsm-tab--active{color:#4f46e5;border-bottom-color:#4f46e5;font-weight:600}.jsm-anyof-options{display:flex;flex-wrap:wrap;gap:.5rem;padding:.75rem 1rem;border-bottom:1.5px solid #e5e7eb}.jsm-anyof-option{display:inline-flex;align-items:center;gap:.375rem;padding:.375rem .75rem;border-radius:9999px;border:1.5px solid #e5e7eb;font-size:.8125rem;font-weight:500;color:#6b7280;cursor:pointer;transition:all .15s;background:transparent}.jsm-anyof-option:hover{border-color:#a5b4fc;color:#4f46e5}.jsm-anyof-option--active{border-color:#6366f1;background:#eef2ff;color:#4f46e5}.jsm-allof-section{border-left:3px solid #e0e7ff;padding-left:1rem}.jsm-combinator-badge{display:inline-flex;align-items:center;border-radius:9999px;padding:.125rem .5rem;font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;flex-shrink:0;margin-left:.75rem}.jsm-badge--oneof{background:#dbeafe;color:#1e40af}.jsm-badge--anyof{background:#ede9fe;color:#5b21b6}.jsm-badge--allof{background:#ccfbf1;color:#065f46}.dark .jsm-field-label{color:#d1d5db}.dark .jsm-field-hint{color:#9ca3af}.dark .jsm-toggle-track{background:#475569}.dark .jsm-toggle-track.jsm-on{background:#4f46e5}.dark .jsm-toggle-text{color:#d1d5db}.dark .jsm-input,.dark .jsm-textarea,.dark .jsm-select{border-color:#475569;color:#f1f5f9}.dark .jsm-input:focus,.dark .jsm-textarea:focus,.dark .jsm-select:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f133}.dark .jsm-input--error,.dark .jsm-textarea--error,.dark .jsm-select--error{border-color:#f87171!important;background:#f871711a}.dark .jsm-fieldset{border-color:#334155}.dark .jsm-fieldset:focus-within{border-color:#6366f1}.dark .jsm-legend{color:#f1f5f9}.dark .jsm-combinator{border-color:#334155}.dark .jsm-combinator-header{background:#ffffff0a;border-color:#334155}.dark .jsm-combinator-title{color:#f1f5f9}.dark .jsm-oneof-tabs{border-color:#334155}.dark .jsm-tab{color:#94a3b8}.dark .jsm-tab--active{color:#818cf8;border-bottom-color:#818cf8}.dark .jsm-anyof-options{border-color:#334155}.dark .jsm-anyof-option{border-color:#334155;color:#94a3b8;background:#0f172a}.dark .jsm-anyof-option--active{border-color:#6366f1;background:#6366f126;color:#818cf8}.dark .jsm-allof-section{border-left-color:#3730a3}.dark .jsm-array-item{border-color:#334155}.dark .jsm-array-item:focus-within{border-color:#6366f1}.dark .jsm-array-index{background:#6366f133;color:#818cf8}.dark .jsm-empty-array{border-color:#334155;color:#64748b}.dark .jsm-contains-hint{background:#6366f126;color:#818cf8}.dark .jsm-add-prop{background:#1e293b66;border-color:#475569}.dark .jsm-add-prop-label{color:#64748b}.dark .jsm-dynamic-key-header{background:#1e293b80;border-color:#475569}.dark .jsm-dynamic-key-label{color:#94a3b8}.dark .jsm-error-msg{color:#f87171}.dark .jsm-array-title{color:#f1f5f9}.dark .jsm-badge--deprecated{background:#d9770633;color:#fbbf24}.dark .jsm-badge--readonly{background:#1e293b;color:#94a3b8}.dark .jsm-badge--writeonly{background:#2563eb33;color:#60a5fa}.dark .jsm-badge--not{background:#dc262633;color:#f87171}.dark .jsm-badge--nullable{background:#1e293b;color:#94a3b8}.dark .jsm-select-chevron{color:#64748b}\n"] }]
1411
+ }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }], propDecorators: { schema: [{
1610
1412
  type: Input,
1611
1413
  args: [{ required: true }]
1612
1414
  }], control: [{
@@ -1626,14 +1428,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1626
1428
  type: Input
1627
1429
  }], allowAdditionalProperties: [{
1628
1430
  type: Input
1431
+ }], showErrors: [{
1432
+ type: Input
1629
1433
  }], controlReplaced: [{
1630
1434
  type: Output
1631
1435
  }] } });
1632
1436
 
1437
+ // AUTO-GENERATED — do not edit manually. Run: node scripts/generate-lib-styles.mjs
1438
+ const JSM_CSS = "/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-red-50: oklch(97.1% 0.013 17.38);\n --color-red-300: oklch(80.8% 0.114 19.571);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-red-900: oklch(39.6% 0.141 25.723);\n --color-amber-50: oklch(98.7% 0.022 95.277);\n --color-amber-200: oklch(92.4% 0.12 95.746);\n --color-amber-300: oklch(87.9% 0.169 91.605);\n --color-amber-400: oklch(82.8% 0.189 84.429);\n --color-amber-600: oklch(66.6% 0.179 58.318);\n --color-amber-700: oklch(55.5% 0.163 48.998);\n --color-amber-800: oklch(47.3% 0.137 46.201);\n --color-amber-900: oklch(41.4% 0.112 45.904);\n --color-emerald-50: oklch(97.9% 0.021 166.113);\n --color-emerald-400: oklch(76.5% 0.177 163.223);\n --color-emerald-700: oklch(50.8% 0.118 165.612);\n --color-emerald-900: oklch(37.8% 0.077 168.94);\n --color-indigo-50: oklch(96.2% 0.018 272.314);\n --color-indigo-100: oklch(93% 0.034 272.788);\n --color-indigo-200: oklch(87% 0.065 274.039);\n --color-indigo-300: oklch(78.5% 0.115 274.713);\n --color-indigo-400: oklch(67.3% 0.182 276.935);\n --color-indigo-500: oklch(58.5% 0.233 277.117);\n --color-indigo-600: oklch(51.1% 0.262 276.966);\n --color-indigo-700: oklch(45.7% 0.24 277.023);\n --color-indigo-900: oklch(35.9% 0.144 278.697);\n --color-rose-50: oklch(96.9% 0.015 12.422);\n --color-rose-400: oklch(71.2% 0.194 13.428);\n --color-rose-500: oklch(64.5% 0.246 16.439);\n --color-rose-600: oklch(58.6% 0.253 17.585);\n --color-rose-700: oklch(51.4% 0.222 16.935);\n --color-rose-900: oklch(41% 0.159 10.272);\n --color-slate-50: oklch(98.4% 0.003 247.858);\n --color-slate-100: oklch(96.8% 0.007 247.896);\n --color-slate-200: oklch(92.9% 0.013 255.508);\n --color-slate-300: oklch(86.9% 0.022 252.894);\n --color-slate-400: oklch(70.4% 0.04 256.788);\n --color-slate-500: oklch(55.4% 0.046 257.417);\n --color-slate-600: oklch(44.6% 0.043 257.281);\n --color-slate-700: oklch(37.2% 0.044 257.287);\n --color-slate-800: oklch(27.9% 0.041 260.031);\n --color-slate-900: oklch(20.8% 0.042 265.755);\n --color-slate-950: oklch(12.9% 0.042 264.695);\n --color-white: #fff;\n --spacing: 0.25rem;\n --container-2xl: 42rem;\n --container-3xl: 48rem;\n --container-6xl: 72rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --text-4xl: 2.25rem;\n --text-4xl--line-height: calc(2.5 / 2.25);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --tracking-tight: -0.025em;\n --tracking-wide: 0.025em;\n --radius-lg: 0.5rem;\n --radius-xl: 0.75rem;\n --radius-2xl: 1rem;\n --animate-spin: spin 1s linear infinite;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border-width: 0;\n }\n .relative {\n position: relative;\n }\n .start {\n inset-inline-start: var(--spacing);\n }\n .col-span-2 {\n grid-column: span 2 / span 2;\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .contents {\n display: contents;\n }\n .flex {\n display: flex;\n }\n .grid {\n display: grid;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .inline-flex {\n display: inline-flex;\n }\n .table {\n display: table;\n }\n .h-3 {\n height: calc(var(--spacing) * 3);\n }\n .h-3\\.5 {\n height: calc(var(--spacing) * 3.5);\n }\n .h-4 {\n height: calc(var(--spacing) * 4);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-40 {\n height: calc(var(--spacing) * 40);\n }\n .h-56 {\n height: calc(var(--spacing) * 56);\n }\n .min-h-screen {\n min-height: 100vh;\n }\n .w-3 {\n width: calc(var(--spacing) * 3);\n }\n .w-3\\.5 {\n width: calc(var(--spacing) * 3.5);\n }\n .w-4 {\n width: calc(var(--spacing) * 4);\n }\n .w-9 {\n width: calc(var(--spacing) * 9);\n }\n .w-full {\n width: 100%;\n }\n .max-w-2xl {\n max-width: var(--container-2xl);\n }\n .max-w-3xl {\n max-width: var(--container-3xl);\n }\n .max-w-6xl {\n max-width: var(--container-6xl);\n }\n .flex-shrink {\n flex-shrink: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\n }\n .border-collapse {\n border-collapse: collapse;\n }\n .translate-x-0 {\n --tw-translate-x: calc(var(--spacing) * 0);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .translate-x-0\\.5 {\n --tw-translate-x: calc(var(--spacing) * 0.5);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .translate-x-4 {\n --tw-translate-x: calc(var(--spacing) * 4);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .resize {\n resize: both;\n }\n .grid-cols-1 {\n grid-template-columns: repeat(1, minmax(0, 1fr));\n }\n .flex-col {\n flex-direction: column;\n }\n .items-center {\n align-items: center;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-3 {\n gap: calc(var(--spacing) * 3);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-6 {\n gap: calc(var(--spacing) * 6);\n }\n .space-y-0 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-0\\.5 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0.5) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0.5) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-1 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-2 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-4 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-5 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-6 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-10 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-2xl {\n border-radius: var(--radius-2xl);\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-xl {\n border-radius: var(--radius-xl);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-2 {\n border-style: var(--tw-border-style);\n border-width: 2px;\n }\n .border-amber-200 {\n border-color: var(--color-amber-200);\n }\n .border-amber-300 {\n border-color: var(--color-amber-300);\n }\n .border-indigo-200 {\n border-color: var(--color-indigo-200);\n }\n .border-red-300 {\n border-color: var(--color-red-300);\n }\n .border-red-700 {\n border-color: var(--color-red-700);\n }\n .border-slate-100 {\n border-color: var(--color-slate-100);\n }\n .border-slate-200 {\n border-color: var(--color-slate-200);\n }\n .border-slate-300 {\n border-color: var(--color-slate-300);\n }\n .border-t-slate-900 {\n border-top-color: var(--color-slate-900);\n }\n .bg-amber-50 {\n background-color: var(--color-amber-50);\n }\n .bg-emerald-50 {\n background-color: var(--color-emerald-50);\n }\n .bg-indigo-50 {\n background-color: var(--color-indigo-50);\n }\n .bg-indigo-600 {\n background-color: var(--color-indigo-600);\n }\n .bg-red-50 {\n background-color: var(--color-red-50);\n }\n .bg-red-900 {\n background-color: var(--color-red-900);\n }\n .bg-red-900\\/20 {\n background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-red-900) 20%, transparent);\n }\n }\n .bg-rose-50 {\n background-color: var(--color-rose-50);\n }\n .bg-slate-50 {\n background-color: var(--color-slate-50);\n }\n .bg-slate-300 {\n background-color: var(--color-slate-300);\n }\n .bg-slate-900 {\n background-color: var(--color-slate-900);\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/70 {\n background-color: color-mix(in srgb, #fff 70%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 70%, transparent);\n }\n }\n .bg-white\\/90 {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n .p-3 {\n padding: calc(var(--spacing) * 3);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-5 {\n padding: calc(var(--spacing) * 5);\n }\n .p-6 {\n padding: calc(var(--spacing) * 6);\n }\n .px-2 {\n padding-inline: calc(var(--spacing) * 2);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-2\\.5 {\n padding-block: calc(var(--spacing) * 2.5);\n }\n .py-6 {\n padding-block: calc(var(--spacing) * 6);\n }\n .py-8 {\n padding-block: calc(var(--spacing) * 8);\n }\n .py-10 {\n padding-block: calc(var(--spacing) * 10);\n }\n .font-mono {\n font-family: var(--font-mono);\n }\n .text-3xl {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .text-\\[10px\\] {\n font-size: 10px;\n }\n .text-\\[11px\\] {\n font-size: 11px;\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-\\[0\\.2em\\] {\n --tw-tracking: 0.2em;\n letter-spacing: 0.2em;\n }\n .tracking-tight {\n --tw-tracking: var(--tracking-tight);\n letter-spacing: var(--tracking-tight);\n }\n .tracking-wide {\n --tw-tracking: var(--tracking-wide);\n letter-spacing: var(--tracking-wide);\n }\n .whitespace-pre-wrap {\n white-space: pre-wrap;\n }\n .text-amber-600 {\n color: var(--color-amber-600);\n }\n .text-amber-700 {\n color: var(--color-amber-700);\n }\n .text-amber-800 {\n color: var(--color-amber-800);\n }\n .text-emerald-700 {\n color: var(--color-emerald-700);\n }\n .text-indigo-500 {\n color: var(--color-indigo-500);\n }\n .text-indigo-700 {\n color: var(--color-indigo-700);\n }\n .text-rose-500 {\n color: var(--color-rose-500);\n }\n .text-rose-600 {\n color: var(--color-rose-600);\n }\n .text-rose-700 {\n color: var(--color-rose-700);\n }\n .text-slate-400 {\n color: var(--color-slate-400);\n }\n .text-slate-500 {\n color: var(--color-slate-500);\n }\n .text-slate-600 {\n color: var(--color-slate-600);\n }\n .text-slate-700 {\n color: var(--color-slate-700);\n }\n .text-slate-800 {\n color: var(--color-slate-800);\n }\n .text-slate-900 {\n color: var(--color-slate-900);\n }\n .text-white {\n color: var(--color-white);\n }\n .uppercase {\n text-transform: uppercase;\n }\n .italic {\n font-style: italic;\n }\n .underline {\n text-decoration-line: underline;\n }\n .opacity-25 {\n opacity: 25%;\n }\n .opacity-75 {\n opacity: 75%;\n }\n .shadow {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-sm {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .blur {\n --tw-blur: blur(8px);\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n }\n .filter {\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n }\n .backdrop-blur {\n --tw-backdrop-blur: blur(8px);\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-filter {\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-transform {\n transition-property: transform, translate, scale, rotate;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .focus-within\\:border-indigo-300 {\n &:focus-within {\n border-color: var(--color-indigo-300);\n }\n }\n .focus-within\\:ring-2 {\n &:focus-within {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .hover\\:-translate-y-0\\.5 {\n &:hover {\n @media (hover: hover) {\n --tw-translate-y: calc(var(--spacing) * -0.5);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n }\n }\n .hover\\:border-slate-300 {\n &:hover {\n @media (hover: hover) {\n border-color: var(--color-slate-300);\n }\n }\n }\n .hover\\:border-slate-400 {\n &:hover {\n @media (hover: hover) {\n border-color: var(--color-slate-400);\n }\n }\n }\n .hover\\:bg-indigo-100 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-indigo-100);\n }\n }\n }\n .hover\\:bg-slate-50 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-slate-50);\n }\n }\n }\n .hover\\:bg-slate-800 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-slate-800);\n }\n }\n }\n .hover\\:text-rose-700 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-rose-700);\n }\n }\n }\n .hover\\:shadow {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .hover\\:shadow-lg {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .hover\\:shadow-md {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .focus\\:border-indigo-400 {\n &:focus {\n border-color: var(--color-indigo-400);\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .focus\\:ring-indigo-100 {\n &:focus {\n --tw-ring-color: var(--color-indigo-100);\n }\n }\n .focus\\:ring-indigo-500 {\n &:focus {\n --tw-ring-color: var(--color-indigo-500);\n }\n }\n .focus\\:ring-slate-900 {\n &:focus {\n --tw-ring-color: var(--color-slate-900);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .sm\\:grid-cols-2 {\n @media (width >= 40rem) {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n }\n .md\\:grid-cols-2 {\n @media (width >= 48rem) {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n }\n .md\\:flex-row {\n @media (width >= 48rem) {\n flex-direction: row;\n }\n }\n .md\\:items-center {\n @media (width >= 48rem) {\n align-items: center;\n }\n }\n .md\\:justify-between {\n @media (width >= 48rem) {\n justify-content: space-between;\n }\n }\n .md\\:p-6 {\n @media (width >= 48rem) {\n padding: calc(var(--spacing) * 6);\n }\n }\n .md\\:text-4xl {\n @media (width >= 48rem) {\n font-size: var(--text-4xl);\n line-height: var(--tw-leading, var(--text-4xl--line-height));\n }\n }\n .lg\\:grid-cols-\\[360px\\,1fr\\] {\n @media (width >= 64rem) {\n grid-template-columns: 360px,1fr;\n }\n }\n .dark\\:border-amber-700 {\n &:where(.dark, .dark *) {\n border-color: var(--color-amber-700);\n }\n }\n .dark\\:border-amber-800 {\n &:where(.dark, .dark *) {\n border-color: var(--color-amber-800);\n }\n }\n .dark\\:border-indigo-700 {\n &:where(.dark, .dark *) {\n border-color: var(--color-indigo-700);\n }\n }\n .dark\\:border-slate-600 {\n &:where(.dark, .dark *) {\n border-color: var(--color-slate-600);\n }\n }\n .dark\\:border-slate-700 {\n &:where(.dark, .dark *) {\n border-color: var(--color-slate-700);\n }\n }\n .dark\\:border-t-indigo-400 {\n &:where(.dark, .dark *) {\n border-top-color: var(--color-indigo-400);\n }\n }\n .dark\\:bg-amber-900\\/20 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(41.4% 0.112 45.904) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-amber-900) 20%, transparent);\n }\n }\n }\n .dark\\:bg-emerald-900\\/30 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(37.8% 0.077 168.94) 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-emerald-900) 30%, transparent);\n }\n }\n }\n .dark\\:bg-indigo-600 {\n &:where(.dark, .dark *) {\n background-color: var(--color-indigo-600);\n }\n }\n .dark\\:bg-indigo-900\\/30 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(35.9% 0.144 278.697) 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-indigo-900) 30%, transparent);\n }\n }\n }\n .dark\\:bg-rose-900\\/30 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(41% 0.159 10.272) 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-rose-900) 30%, transparent);\n }\n }\n }\n .dark\\:bg-slate-600 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-600);\n }\n }\n .dark\\:bg-slate-700 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-700);\n }\n }\n .dark\\:bg-slate-800 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-800);\n }\n }\n .dark\\:bg-slate-800\\/50 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(27.9% 0.041 260.031) 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-slate-800) 50%, transparent);\n }\n }\n }\n .dark\\:bg-slate-800\\/60 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(27.9% 0.041 260.031) 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-slate-800) 60%, transparent);\n }\n }\n }\n .dark\\:bg-slate-900 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-900);\n }\n }\n .dark\\:bg-slate-900\\/90 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(20.8% 0.042 265.755) 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-slate-900) 90%, transparent);\n }\n }\n }\n .dark\\:bg-slate-950 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-950);\n }\n }\n .dark\\:text-amber-300 {\n &:where(.dark, .dark *) {\n color: var(--color-amber-300);\n }\n }\n .dark\\:text-amber-400 {\n &:where(.dark, .dark *) {\n color: var(--color-amber-400);\n }\n }\n .dark\\:text-emerald-400 {\n &:where(.dark, .dark *) {\n color: var(--color-emerald-400);\n }\n }\n .dark\\:text-indigo-300 {\n &:where(.dark, .dark *) {\n color: var(--color-indigo-300);\n }\n }\n .dark\\:text-indigo-400 {\n &:where(.dark, .dark *) {\n color: var(--color-indigo-400);\n }\n }\n .dark\\:text-rose-400 {\n &:where(.dark, .dark *) {\n color: var(--color-rose-400);\n }\n }\n .dark\\:text-slate-100 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-100);\n }\n }\n .dark\\:text-slate-200 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-200);\n }\n }\n .dark\\:text-slate-300 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-300);\n }\n }\n .dark\\:text-slate-400 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-400);\n }\n }\n .dark\\:text-slate-500 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-500);\n }\n }\n .dark\\:text-white {\n &:where(.dark, .dark *) {\n color: var(--color-white);\n }\n }\n .dark\\:hover\\:border-slate-500 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n border-color: var(--color-slate-500);\n }\n }\n }\n }\n .dark\\:hover\\:bg-indigo-700 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-indigo-700);\n }\n }\n }\n }\n .dark\\:hover\\:bg-indigo-900\\/50 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, oklch(35.9% 0.144 278.697) 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-indigo-900) 50%, transparent);\n }\n }\n }\n }\n }\n .dark\\:hover\\:bg-slate-700 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-slate-700);\n }\n }\n }\n }\n .dark\\:focus\\:ring-indigo-900\\/40 {\n &:where(.dark, .dark *) {\n &:focus {\n --tw-ring-color: color-mix(in srgb, oklch(35.9% 0.144 278.697) 40%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-ring-color: color-mix(in oklab, var(--color-indigo-900) 40%, transparent);\n }\n }\n }\n }\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-tracking {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-outline-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-drop-shadow {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-drop-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-drop-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-drop-shadow-size {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-font-weight: initial;\n --tw-tracking: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-outline-style: solid;\n --tw-blur: initial;\n --tw-brightness: initial;\n --tw-contrast: initial;\n --tw-grayscale: initial;\n --tw-hue-rotate: initial;\n --tw-invert: initial;\n --tw-opacity: initial;\n --tw-saturate: initial;\n --tw-sepia: initial;\n --tw-drop-shadow: initial;\n --tw-drop-shadow-color: initial;\n --tw-drop-shadow-alpha: 100%;\n --tw-drop-shadow-size: initial;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n }\n }\n}\n";
1439
+
1440
+ const STYLE_ID = 'jsm-tailwind-styles';
1441
+ class JsonSchemaStylesService {
1442
+ platformId;
1443
+ injected = false;
1444
+ constructor(platformId) {
1445
+ this.platformId = platformId;
1446
+ }
1447
+ inject() {
1448
+ if (this.injected || !isPlatformBrowser(this.platformId))
1449
+ return;
1450
+ if (typeof document === 'undefined')
1451
+ return;
1452
+ if (document.getElementById(STYLE_ID)) {
1453
+ this.injected = true;
1454
+ return;
1455
+ }
1456
+ const style = document.createElement('style');
1457
+ style.id = STYLE_ID;
1458
+ style.textContent = JSM_CSS;
1459
+ document.head.appendChild(style);
1460
+ this.injected = true;
1461
+ }
1462
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1463
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, providedIn: 'root' });
1464
+ }
1465
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, decorators: [{
1466
+ type: Injectable,
1467
+ args: [{ providedIn: 'root' }]
1468
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1469
+ type: Inject,
1470
+ args: [PLATFORM_ID]
1471
+ }] }] });
1472
+
1633
1473
  class JsonSchemaFormComponent {
1634
1474
  schemaService;
1635
1475
  resolver;
1636
1476
  validation;
1477
+ cdr;
1637
1478
  schema;
1638
1479
  value;
1639
1480
  data;
@@ -1648,20 +1489,48 @@ class JsonSchemaFormComponent {
1648
1489
  errorsMap = new Map();
1649
1490
  schemaErrorList = [];
1650
1491
  loading = false;
1492
+ /** When true, all field errors are shown regardless of touched/dirty state. */
1493
+ showErrors = false;
1651
1494
  valueSub;
1652
- constructor(schemaService, resolver, validation) {
1495
+ constructor(schemaService, resolver, validation, cdr, stylesService) {
1653
1496
  this.schemaService = schemaService;
1654
1497
  this.resolver = resolver;
1655
1498
  this.validation = validation;
1499
+ this.cdr = cdr;
1500
+ stylesService.inject();
1656
1501
  }
1657
1502
  ngOnChanges(changes) {
1658
- if (changes['schema'] || changes['value'] || changes['data']) {
1503
+ if (changes['schema']) {
1504
+ // Schema changed — full rebuild required
1505
+ void this.buildForm();
1506
+ }
1507
+ else if ((changes['value'] || changes['data']) && this.form) {
1508
+ // Only data changed and form already exists — patch values without rebuilding
1509
+ const newData = this.value !== undefined ? this.value : this.data;
1510
+ this.form.patchValue(newData, { emitEvent: false });
1511
+ }
1512
+ else if (changes['value'] || changes['data']) {
1513
+ // Form not built yet — build it
1659
1514
  void this.buildForm();
1660
1515
  }
1661
1516
  }
1662
1517
  ngOnDestroy() {
1663
1518
  this.valueSub?.unsubscribe();
1664
1519
  }
1520
+ /**
1521
+ * Marks all fields as touched and forces error display across the entire form.
1522
+ * Call this from an external "Save" or "Submit" button to show all validation errors at once.
1523
+ * Returns true if the form is valid, false otherwise.
1524
+ */
1525
+ validate() {
1526
+ if (!this.form)
1527
+ return false;
1528
+ this.form.markAllAsTouched();
1529
+ this.showErrors = true;
1530
+ this.updateErrors();
1531
+ this.cdr.detectChanges();
1532
+ return this.form.valid;
1533
+ }
1665
1534
  async buildForm() {
1666
1535
  this.valueSub?.unsubscribe();
1667
1536
  this.loading = true;
@@ -1689,7 +1558,8 @@ class JsonSchemaFormComponent {
1689
1558
  this.errorsMap = new Map();
1690
1559
  return;
1691
1560
  }
1692
- const errors = this.validation.validate(this.resolvedSchema, this.rootControl.value);
1561
+ const normalized = this.normalizeValue(this.resolvedSchema, this.rootControl.value);
1562
+ const errors = this.validation.validate(this.resolvedSchema, normalized);
1693
1563
  const map = new Map();
1694
1564
  for (const error of errors) {
1695
1565
  const path = error.instancePath ?? '';
@@ -1699,6 +1569,37 @@ class JsonSchemaFormComponent {
1699
1569
  }
1700
1570
  this.errorsMap = map;
1701
1571
  }
1572
+ /**
1573
+ * Recursively coerces string values to numbers/booleans where the schema declares
1574
+ * type "number" or "integer". This is needed because <input type="number"> always
1575
+ * returns a string to Angular's FormControl, but Ajv validates against the JSON type.
1576
+ */
1577
+ normalizeValue(schema, value) {
1578
+ if (value === null || value === undefined)
1579
+ return value;
1580
+ const type = Array.isArray(schema.type)
1581
+ ? schema.type.find((t) => t !== 'null') ?? schema.type[0]
1582
+ : schema.type;
1583
+ if ((type === 'number' || type === 'integer') && typeof value === 'string') {
1584
+ const n = Number(value);
1585
+ return isNaN(n) ? value : n;
1586
+ }
1587
+ if (type === 'object' && schema.properties && typeof value === 'object' && !Array.isArray(value)) {
1588
+ const obj = value;
1589
+ const result = { ...obj };
1590
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
1591
+ if (key in result) {
1592
+ result[key] = this.normalizeValue(propSchema, result[key]);
1593
+ }
1594
+ }
1595
+ return result;
1596
+ }
1597
+ if (type === 'array' && Array.isArray(value)) {
1598
+ const itemSchema = schema.items ?? {};
1599
+ return value.map((item) => this.normalizeValue(itemSchema, item));
1600
+ }
1601
+ return value;
1602
+ }
1702
1603
  onRootReplaced(control) {
1703
1604
  this.rootControl = control;
1704
1605
  if (control instanceof FormGroup) {
@@ -1722,8 +1623,8 @@ class JsonSchemaFormComponent {
1722
1623
  this.valueChange.emit(this.rootControl.value);
1723
1624
  });
1724
1625
  }
1725
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaResolverService }, { token: JsonSchemaValidationService }], target: i0.ɵɵFactoryTarget.Component });
1726
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: JsonSchemaFormComponent, isStandalone: true, selector: "jsm-json-schema-form", inputs: { schema: "schema", value: "value", data: "data", allowAdditionalProperties: "allowAdditionalProperties" }, outputs: { formReady: "formReady", valueChange: "valueChange", schemaReady: "schemaReady", schemaErrors: "schemaErrors" }, usesOnChanges: true, ngImport: i0, template: `
1626
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaResolverService }, { token: JsonSchemaValidationService }, { token: i0.ChangeDetectorRef }, { token: JsonSchemaStylesService }], target: i0.ɵɵFactoryTarget.Component });
1627
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: JsonSchemaFormComponent, isStandalone: true, selector: "jsm-json-schema-form", inputs: { schema: "schema", value: "value", data: "data", allowAdditionalProperties: "allowAdditionalProperties" }, outputs: { formReady: "formReady", valueChange: "valueChange", schemaReady: "schemaReady", schemaErrors: "schemaErrors" }, usesOnChanges: true, ngImport: i0, template: `
1727
1628
  <div class="jsm-root">
1728
1629
  <form class="w-full space-y-5">
1729
1630
  <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
@@ -1772,15 +1673,16 @@ class JsonSchemaFormComponent {
1772
1673
  [path]="''"
1773
1674
  [errorsMap]="errorsMap"
1774
1675
  [allowAdditionalProperties]="allowAdditionalProperties"
1676
+ [showErrors]="showErrors"
1775
1677
  label="Root"
1776
1678
  (controlReplaced)="onRootReplaced($event)"
1777
1679
  ></jsm-schema-node>
1778
1680
  </div>
1779
1681
  </form>
1780
1682
  </div>
1781
- `, isInline: true, styles: [".jsm-root *,.jsm-root *:before,.jsm-root *:after{box-sizing:border-box}.jsm-root input,.jsm-root textarea,.jsm-root select,.jsm-root button{font-family:inherit;font-size:inherit;line-height:inherit}.jsm-root input[type=number]::-webkit-inner-spin-button,.jsm-root input[type=number]::-webkit-outer-spin-button{opacity:1}.jsm-root input:not([type=checkbox]):not([type=radio]):not(.sr-only),.jsm-root textarea,.jsm-root select{border-width:1.5px!important;border-style:solid!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: JsonSchemaNodeComponent, selector: "jsm-schema-node", inputs: ["schema", "control", "label", "required", "path", "errorsMap", "parent", "controlKey", "allowAdditionalProperties"], outputs: ["controlReplaced"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1683
+ `, isInline: true, styles: [".jsm-root *,.jsm-root *:before,.jsm-root *:after{box-sizing:border-box}.jsm-root input,.jsm-root textarea,.jsm-root select,.jsm-root button{font-family:inherit;font-size:inherit;line-height:inherit}.jsm-root input[type=number]::-webkit-inner-spin-button,.jsm-root input[type=number]::-webkit-outer-spin-button{opacity:1}.jsm-root input:not([type=checkbox]):not([type=radio]):not(.sr-only),.jsm-root textarea,.jsm-root select{border-width:1.5px!important;border-style:solid!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: JsonSchemaNodeComponent, selector: "jsm-schema-node", inputs: ["schema", "control", "label", "required", "path", "errorsMap", "parent", "controlKey", "allowAdditionalProperties", "showErrors"], outputs: ["controlReplaced"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1782
1684
  }
1783
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
1685
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
1784
1686
  type: Component,
1785
1687
  args: [{ selector: 'jsm-json-schema-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, JsonSchemaNodeComponent], encapsulation: ViewEncapsulation.None, template: `
1786
1688
  <div class="jsm-root">
@@ -1831,6 +1733,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1831
1733
  [path]="''"
1832
1734
  [errorsMap]="errorsMap"
1833
1735
  [allowAdditionalProperties]="allowAdditionalProperties"
1736
+ [showErrors]="showErrors"
1834
1737
  label="Root"
1835
1738
  (controlReplaced)="onRootReplaced($event)"
1836
1739
  ></jsm-schema-node>
@@ -1838,7 +1741,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1838
1741
  </form>
1839
1742
  </div>
1840
1743
  `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".jsm-root *,.jsm-root *:before,.jsm-root *:after{box-sizing:border-box}.jsm-root input,.jsm-root textarea,.jsm-root select,.jsm-root button{font-family:inherit;font-size:inherit;line-height:inherit}.jsm-root input[type=number]::-webkit-inner-spin-button,.jsm-root input[type=number]::-webkit-outer-spin-button{opacity:1}.jsm-root input:not([type=checkbox]):not([type=radio]):not(.sr-only),.jsm-root textarea,.jsm-root select{border-width:1.5px!important;border-style:solid!important}\n"] }]
1841
- }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaResolverService }, { type: JsonSchemaValidationService }], propDecorators: { schema: [{
1744
+ }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaResolverService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }, { type: JsonSchemaStylesService }], propDecorators: { schema: [{
1842
1745
  type: Input,
1843
1746
  args: [{ required: true }]
1844
1747
  }], value: [{
@@ -1858,11 +1761,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1858
1761
  }] } });
1859
1762
 
1860
1763
  class JsonSchemaFormModule {
1861
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1862
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent], exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1863
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1764
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1765
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent], exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1766
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1864
1767
  }
1865
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, decorators: [{
1768
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, decorators: [{
1866
1769
  type: NgModule,
1867
1770
  args: [{
1868
1771
  imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
@@ -1874,5 +1777,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1874
1777
  * Generated bundle index. Do not edit.
1875
1778
  */
1876
1779
 
1877
- export { JsonSchemaFormComponent, JsonSchemaFormModule, JsonSchemaFormService, JsonSchemaNodeComponent, JsonSchemaResolverService, JsonSchemaValidationService };
1780
+ export { JsonSchemaFormComponent, JsonSchemaFormModule, JsonSchemaFormService, JsonSchemaNodeComponent, JsonSchemaResolverService, JsonSchemaStylesService, JsonSchemaValidationService };
1878
1781
  //# sourceMappingURL=elle96-ng-json-schema-manager.mjs.map