@elle96/ng-json-schema-manager 0.0.14 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/fesm2022/elle96-ng-json-schema-manager.mjs +928 -769
- package/fesm2022/elle96-ng-json-schema-manager.mjs.map +1 -1
- package/index.d.ts +24 -3
- package/package.json +25 -1
|
@@ -471,6 +471,39 @@ class JsonSchemaValidationService {
|
|
|
471
471
|
}
|
|
472
472
|
return validate.errors ?? [];
|
|
473
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Validates the schema itself against its JSON Schema meta-schema.
|
|
476
|
+
* Returns a list of SchemaError describing what is wrong in the schema definition.
|
|
477
|
+
*/
|
|
478
|
+
validateSchema(schema) {
|
|
479
|
+
const draft = this.detectDraft(schema);
|
|
480
|
+
const ajv = this.getAjv(draft);
|
|
481
|
+
try {
|
|
482
|
+
const valid = ajv.validateSchema(schema);
|
|
483
|
+
if (valid)
|
|
484
|
+
return [];
|
|
485
|
+
return (ajv.errors ?? []).map((e) => ({
|
|
486
|
+
path: e.instancePath || '/',
|
|
487
|
+
message: e.message ?? e.keyword,
|
|
488
|
+
keyword: e.keyword,
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
// ajv.validateSchema throws when the schema is so broken it cannot be compiled.
|
|
493
|
+
// The error message contains comma-separated individual violations — split them.
|
|
494
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
495
|
+
// Strip the "schema is invalid: " prefix if present
|
|
496
|
+
const body = raw.replace(/^schema is invalid:\s*/i, '');
|
|
497
|
+
// Each violation looks like "data/some/path must ..." — extract path and message
|
|
498
|
+
return body.split(', ').map((msg) => {
|
|
499
|
+
const trimmed = msg.trim();
|
|
500
|
+
const match = trimmed.match(/^(data[^\s]*)\s+(.+)$/);
|
|
501
|
+
return match
|
|
502
|
+
? { path: match[1].replace(/^data/, '') || '/', message: match[2], keyword: 'schema' }
|
|
503
|
+
: { path: '/', message: trimmed, keyword: 'schema' };
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
474
507
|
detectDraft(schema) {
|
|
475
508
|
const schemaId = schema.$schema ?? '';
|
|
476
509
|
if (schemaId.includes('draft-04')) {
|
|
@@ -557,6 +590,7 @@ class JsonSchemaNodeComponent {
|
|
|
557
590
|
errorsMap = new Map();
|
|
558
591
|
parent;
|
|
559
592
|
controlKey;
|
|
593
|
+
allowAdditionalProperties = false;
|
|
560
594
|
controlReplaced = new EventEmitter();
|
|
561
595
|
selectedOneOf = 0;
|
|
562
596
|
selectedAnyOf = new Set();
|
|
@@ -571,133 +605,115 @@ class JsonSchemaNodeComponent {
|
|
|
571
605
|
this.selectedAnyOf.add(0);
|
|
572
606
|
}
|
|
573
607
|
}
|
|
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
|
+
isSimpleChild(key) {
|
|
614
|
+
const childSchema = this.schemaForKey(key);
|
|
615
|
+
if (childSchema.oneOf?.length || childSchema.anyOf?.length || childSchema.allOf?.length)
|
|
616
|
+
return false;
|
|
617
|
+
const t = this.schemaService.resolveType(childSchema);
|
|
618
|
+
return t !== 'object' && t !== 'array';
|
|
619
|
+
}
|
|
620
|
+
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
|
+
};
|
|
627
|
+
}
|
|
574
628
|
get nodeKind() {
|
|
575
|
-
if (this.schema.oneOf?.length)
|
|
629
|
+
if (this.schema.oneOf?.length)
|
|
576
630
|
return 'oneOf';
|
|
577
|
-
|
|
578
|
-
if (this.schema.anyOf?.length) {
|
|
631
|
+
if (this.schema.anyOf?.length)
|
|
579
632
|
return 'anyOf';
|
|
580
|
-
|
|
581
|
-
if (this.schema.allOf?.length) {
|
|
633
|
+
if (this.schema.allOf?.length)
|
|
582
634
|
return 'allOf';
|
|
583
|
-
}
|
|
584
635
|
const resolved = this.resolvedType;
|
|
585
|
-
if (resolved === 'object')
|
|
636
|
+
if (resolved === 'object')
|
|
586
637
|
return 'object';
|
|
587
|
-
|
|
588
|
-
if (resolved === 'array') {
|
|
638
|
+
if (resolved === 'array')
|
|
589
639
|
return 'array';
|
|
590
|
-
}
|
|
591
640
|
return 'primitive';
|
|
592
641
|
}
|
|
593
642
|
get resolvedType() {
|
|
594
643
|
return this.schemaService.resolveType(this.effectiveSchema) ?? 'string';
|
|
595
644
|
}
|
|
596
645
|
get inputKind() {
|
|
597
|
-
if (this.effectiveSchema.enum)
|
|
646
|
+
if (this.effectiveSchema.enum)
|
|
598
647
|
return 'select';
|
|
599
|
-
|
|
600
|
-
if (this.resolvedType === 'boolean') {
|
|
648
|
+
if (this.resolvedType === 'boolean')
|
|
601
649
|
return 'checkbox';
|
|
602
|
-
|
|
603
|
-
if (this.effectiveSchema.format === 'textarea' || (this.effectiveSchema.maxLength ?? 0) > 200) {
|
|
650
|
+
if (this.effectiveSchema.format === 'textarea' || (this.effectiveSchema.maxLength ?? 0) > 200)
|
|
604
651
|
return 'textarea';
|
|
605
|
-
}
|
|
606
652
|
return 'text';
|
|
607
653
|
}
|
|
608
654
|
get inputType() {
|
|
609
|
-
if (this.effectiveSchema.format === 'email')
|
|
655
|
+
if (this.effectiveSchema.format === 'email')
|
|
610
656
|
return 'email';
|
|
611
|
-
|
|
612
|
-
if (this.effectiveSchema.format === 'date') {
|
|
657
|
+
if (this.effectiveSchema.format === 'date')
|
|
613
658
|
return 'date';
|
|
614
|
-
|
|
615
|
-
if (this.effectiveSchema.format === 'time') {
|
|
659
|
+
if (this.effectiveSchema.format === 'time')
|
|
616
660
|
return 'time';
|
|
617
|
-
|
|
618
|
-
if (this.effectiveSchema.format === 'date-time') {
|
|
661
|
+
if (this.effectiveSchema.format === 'date-time')
|
|
619
662
|
return 'datetime-local';
|
|
620
|
-
|
|
621
|
-
if (this.resolvedType === 'number' || this.resolvedType === 'integer') {
|
|
663
|
+
if (this.resolvedType === 'number' || this.resolvedType === 'integer')
|
|
622
664
|
return 'number';
|
|
623
|
-
|
|
624
|
-
if (this.effectiveSchema.format === 'password') {
|
|
665
|
+
if (this.effectiveSchema.format === 'password')
|
|
625
666
|
return 'password';
|
|
626
|
-
}
|
|
627
667
|
return 'text';
|
|
628
668
|
}
|
|
629
|
-
get controlAsGroup() {
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
get
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
get controlAsArray() {
|
|
636
|
-
return this.control;
|
|
637
|
-
}
|
|
638
|
-
get arrayControls() {
|
|
639
|
-
return this.controlAsArray.controls;
|
|
640
|
-
}
|
|
641
|
-
get objectKeys() {
|
|
642
|
-
return Object.keys(this.controlAsGroup.controls);
|
|
643
|
-
}
|
|
669
|
+
get controlAsGroup() { return this.control; }
|
|
670
|
+
get controlAsFormControl() { return this.control; }
|
|
671
|
+
get controlAsArray() { return this.control; }
|
|
672
|
+
get arrayControls() { return this.controlAsArray.controls; }
|
|
673
|
+
get objectKeys() { return Object.keys(this.controlAsGroup.controls); }
|
|
644
674
|
get canAddProperty() {
|
|
675
|
+
if (!this.allowAdditionalProperties)
|
|
676
|
+
return false;
|
|
645
677
|
return this.effectiveSchema.additionalProperties !== false || !!this.effectiveSchema.patternProperties;
|
|
646
678
|
}
|
|
647
|
-
get errorsForPath() {
|
|
648
|
-
return this.errorsMap.get(this.path) ?? [];
|
|
649
|
-
}
|
|
679
|
+
get errorsForPath() { return this.errorsMap.get(this.path) ?? []; }
|
|
650
680
|
get metaBadges() {
|
|
651
681
|
const badges = [];
|
|
652
|
-
if (this.effectiveSchema.deprecated)
|
|
682
|
+
if (this.effectiveSchema.deprecated)
|
|
653
683
|
badges.push('Deprecated');
|
|
654
|
-
|
|
655
|
-
if (this.effectiveSchema.readOnly) {
|
|
684
|
+
if (this.effectiveSchema.readOnly)
|
|
656
685
|
badges.push('Read-only');
|
|
657
|
-
|
|
658
|
-
if (this.effectiveSchema.writeOnly) {
|
|
686
|
+
if (this.effectiveSchema.writeOnly)
|
|
659
687
|
badges.push('Write-only');
|
|
660
|
-
|
|
661
|
-
if (this.effectiveSchema.nullable) {
|
|
688
|
+
if (this.effectiveSchema.nullable)
|
|
662
689
|
badges.push('Nullable');
|
|
663
|
-
|
|
664
|
-
if (this.effectiveSchema.not) {
|
|
690
|
+
if (this.effectiveSchema.not)
|
|
665
691
|
badges.push('Not constraint');
|
|
666
|
-
}
|
|
667
692
|
return badges;
|
|
668
693
|
}
|
|
669
694
|
get activeVariant() {
|
|
670
|
-
if (this.schema.oneOf?.length)
|
|
695
|
+
if (this.schema.oneOf?.length)
|
|
671
696
|
return this.schema.oneOf[this.selectedOneOf] ?? null;
|
|
672
|
-
}
|
|
673
697
|
if (this.schema.anyOf?.length) {
|
|
674
|
-
const selections = Array.from(this.selectedAnyOf).map((
|
|
698
|
+
const selections = Array.from(this.selectedAnyOf).map((i) => this.schema.anyOf[i]);
|
|
675
699
|
return selections.length ? this.mergeSchemas(selections) : this.schema.anyOf[0] ?? null;
|
|
676
700
|
}
|
|
677
|
-
if (this.schema.allOf?.length)
|
|
701
|
+
if (this.schema.allOf?.length)
|
|
678
702
|
return this.mergeSchemas([this.schema, ...this.schema.allOf]);
|
|
679
|
-
}
|
|
680
703
|
return this.effectiveSchema;
|
|
681
704
|
}
|
|
682
705
|
get mergedAllOf() {
|
|
683
706
|
return this.schema.allOf?.length ? [this.schema, ...this.schema.allOf] : [this.schema];
|
|
684
707
|
}
|
|
685
|
-
isRequired(key) {
|
|
686
|
-
return this.effectiveSchema.required?.includes(key) ?? false;
|
|
687
|
-
}
|
|
708
|
+
isRequired(key) { return this.effectiveSchema.required?.includes(key) ?? false; }
|
|
688
709
|
schemaForKey(key) {
|
|
689
710
|
return (this.effectiveSchema.properties?.[key] ??
|
|
690
711
|
this.schemaService.resolveDynamicSchema(this.effectiveSchema, key) ??
|
|
691
712
|
{});
|
|
692
713
|
}
|
|
693
|
-
isDynamicKey(key) {
|
|
694
|
-
return !this.effectiveSchema.properties?.[key];
|
|
695
|
-
}
|
|
714
|
+
isDynamicKey(key) { return !this.effectiveSchema.properties?.[key]; }
|
|
696
715
|
pathForChild(key) {
|
|
697
|
-
|
|
698
|
-
return `/${key}`;
|
|
699
|
-
}
|
|
700
|
-
return `${this.path}/${key}`;
|
|
716
|
+
return this.path === '' ? `/${key}` : `${this.path}/${key}`;
|
|
701
717
|
}
|
|
702
718
|
addDynamicProperty() {
|
|
703
719
|
this.propertyError = '';
|
|
@@ -726,135 +742,107 @@ class JsonSchemaNodeComponent {
|
|
|
726
742
|
this.controlAsGroup.addControl(key, control);
|
|
727
743
|
this.newPropertyKey = '';
|
|
728
744
|
}
|
|
729
|
-
removeDynamicKey(key) {
|
|
730
|
-
this.controlAsGroup.removeControl(key);
|
|
731
|
-
}
|
|
745
|
+
removeDynamicKey(key) { this.controlAsGroup.removeControl(key); }
|
|
732
746
|
schemaForIndex(index) {
|
|
733
747
|
const prefixItems = this.effectiveSchema.prefixItems ?? [];
|
|
734
|
-
if (prefixItems[index])
|
|
748
|
+
if (prefixItems[index])
|
|
735
749
|
return prefixItems[index];
|
|
736
|
-
|
|
737
|
-
if (this.effectiveSchema.items && this.effectiveSchema.items !== true) {
|
|
750
|
+
if (this.effectiveSchema.items && this.effectiveSchema.items !== true)
|
|
738
751
|
return this.effectiveSchema.items;
|
|
739
|
-
|
|
740
|
-
if (this.effectiveSchema.additionalItems && this.effectiveSchema.additionalItems !== true) {
|
|
752
|
+
if (this.effectiveSchema.additionalItems && this.effectiveSchema.additionalItems !== true)
|
|
741
753
|
return this.effectiveSchema.additionalItems;
|
|
742
|
-
|
|
743
|
-
if (this.effectiveSchema.unevaluatedItems && this.effectiveSchema.unevaluatedItems !== true) {
|
|
754
|
+
if (this.effectiveSchema.unevaluatedItems && this.effectiveSchema.unevaluatedItems !== true)
|
|
744
755
|
return this.effectiveSchema.unevaluatedItems;
|
|
745
|
-
}
|
|
746
756
|
return {};
|
|
747
757
|
}
|
|
748
758
|
get canAddArrayItem() {
|
|
749
|
-
|
|
759
|
+
return !(this.effectiveSchema.items === false ||
|
|
750
760
|
this.effectiveSchema.additionalItems === false ||
|
|
751
|
-
this.effectiveSchema.unevaluatedItems === false)
|
|
752
|
-
return false;
|
|
753
|
-
}
|
|
754
|
-
return true;
|
|
761
|
+
this.effectiveSchema.unevaluatedItems === false);
|
|
755
762
|
}
|
|
756
763
|
addArrayItem() {
|
|
757
|
-
if (!this.canAddArrayItem)
|
|
764
|
+
if (!this.canAddArrayItem)
|
|
758
765
|
return;
|
|
759
|
-
}
|
|
760
766
|
const formArray = this.controlAsArray;
|
|
761
767
|
const index = formArray.length;
|
|
762
|
-
|
|
763
|
-
formArray.push(this.schemaService.buildControl(schemaForItem, undefined, false));
|
|
768
|
+
formArray.push(this.schemaService.buildControl(this.schemaForIndex(index), undefined, false));
|
|
764
769
|
formArray.markAsDirty();
|
|
765
770
|
}
|
|
766
771
|
removeArrayItem(index) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
formArray.markAsDirty();
|
|
772
|
+
this.controlAsArray.removeAt(index);
|
|
773
|
+
this.controlAsArray.markAsDirty();
|
|
770
774
|
}
|
|
771
775
|
onOneOfChange() {
|
|
772
|
-
if (!this.activeVariant)
|
|
776
|
+
if (!this.activeVariant)
|
|
773
777
|
return;
|
|
774
|
-
}
|
|
775
778
|
this.replaceControl(this.schemaService.buildControl(this.activeVariant, this.control.value, this.required));
|
|
776
779
|
}
|
|
777
780
|
toggleAnyOf(index) {
|
|
778
|
-
if (this.selectedAnyOf.has(index))
|
|
781
|
+
if (this.selectedAnyOf.has(index))
|
|
779
782
|
this.selectedAnyOf.delete(index);
|
|
780
|
-
|
|
781
|
-
else {
|
|
783
|
+
else
|
|
782
784
|
this.selectedAnyOf.add(index);
|
|
783
|
-
}
|
|
784
785
|
const variant = this.activeVariant;
|
|
785
|
-
if (variant)
|
|
786
|
+
if (variant)
|
|
786
787
|
this.replaceControl(this.schemaService.buildControl(variant, this.control.value, this.required));
|
|
787
|
-
}
|
|
788
788
|
}
|
|
789
789
|
replaceControl(control) {
|
|
790
790
|
if (this.parent) {
|
|
791
|
-
if (this.parent instanceof FormGroup && typeof this.controlKey === 'string')
|
|
791
|
+
if (this.parent instanceof FormGroup && typeof this.controlKey === 'string')
|
|
792
792
|
this.parent.setControl(this.controlKey, control);
|
|
793
|
-
|
|
794
|
-
if (this.parent instanceof FormArray && typeof this.controlKey === 'number') {
|
|
793
|
+
if (this.parent instanceof FormArray && typeof this.controlKey === 'number')
|
|
795
794
|
this.parent.setControl(this.controlKey, control);
|
|
796
|
-
}
|
|
797
795
|
this.control = control;
|
|
798
796
|
return;
|
|
799
797
|
}
|
|
800
798
|
this.control = control;
|
|
801
799
|
this.controlReplaced.emit(control);
|
|
802
800
|
}
|
|
803
|
-
childControl(key) {
|
|
804
|
-
return this.controlAsGroup.get(key);
|
|
805
|
-
}
|
|
801
|
+
childControl(key) { return this.controlAsGroup.get(key); }
|
|
806
802
|
get effectiveSchema() {
|
|
807
803
|
let schema = this.schema;
|
|
808
|
-
if (schema.allOf?.length)
|
|
804
|
+
if (schema.allOf?.length)
|
|
809
805
|
schema = this.mergeSchemas([schema, ...schema.allOf]);
|
|
810
|
-
}
|
|
811
806
|
schema = this.applyConditional(schema);
|
|
812
807
|
schema = this.applyDependencies(schema);
|
|
813
808
|
return schema;
|
|
814
809
|
}
|
|
815
810
|
applyConditional(schema) {
|
|
816
|
-
if (!schema.if)
|
|
811
|
+
if (!schema.if)
|
|
817
812
|
return schema;
|
|
818
|
-
}
|
|
819
813
|
const errors = this.validation.validate(schema.if, this.control.value);
|
|
820
814
|
const branch = errors.length === 0 ? schema.then : schema.else;
|
|
821
|
-
if (!branch)
|
|
815
|
+
if (!branch)
|
|
822
816
|
return schema;
|
|
823
|
-
}
|
|
824
817
|
return this.mergeSchemas([schema, branch]);
|
|
825
818
|
}
|
|
826
819
|
applyDependencies(schema) {
|
|
827
820
|
const value = this.control.value;
|
|
828
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
821
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
829
822
|
return schema;
|
|
830
|
-
}
|
|
831
823
|
let merged = { ...schema };
|
|
832
824
|
const valueKeys = Object.keys(value);
|
|
833
825
|
if (schema.dependentRequired) {
|
|
834
826
|
for (const key of valueKeys) {
|
|
835
827
|
const required = schema.dependentRequired[key];
|
|
836
|
-
if (required?.length)
|
|
828
|
+
if (required?.length)
|
|
837
829
|
merged = this.mergeSchemas([merged, { required }]);
|
|
838
|
-
}
|
|
839
830
|
}
|
|
840
831
|
}
|
|
841
832
|
if (schema.dependentSchemas) {
|
|
842
833
|
for (const key of valueKeys) {
|
|
843
834
|
const depSchema = schema.dependentSchemas[key];
|
|
844
|
-
if (depSchema)
|
|
835
|
+
if (depSchema)
|
|
845
836
|
merged = this.mergeSchemas([merged, depSchema]);
|
|
846
|
-
}
|
|
847
837
|
}
|
|
848
838
|
}
|
|
849
839
|
if (schema.dependencies) {
|
|
850
840
|
for (const key of valueKeys) {
|
|
851
841
|
const dep = schema.dependencies[key];
|
|
852
|
-
if (Array.isArray(dep))
|
|
842
|
+
if (Array.isArray(dep))
|
|
853
843
|
merged = this.mergeSchemas([merged, { required: dep }]);
|
|
854
|
-
|
|
855
|
-
else if (dep) {
|
|
844
|
+
else if (dep)
|
|
856
845
|
merged = this.mergeSchemas([merged, dep]);
|
|
857
|
-
}
|
|
858
846
|
}
|
|
859
847
|
}
|
|
860
848
|
return merged;
|
|
@@ -877,306 +865,351 @@ class JsonSchemaNodeComponent {
|
|
|
877
865
|
}, {});
|
|
878
866
|
}
|
|
879
867
|
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 });
|
|
880
|
-
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" }, outputs: { controlReplaced: "controlReplaced" }, ngImport: i0, template: `
|
|
881
|
-
<ng-container [ngSwitch]="nodeKind">
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
<
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
class="
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
[
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
[
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
<
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
<
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
[controlKey]="controlKey"
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
[
|
|
1008
|
-
[
|
|
1009
|
-
[
|
|
1010
|
-
[
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
class="
|
|
1028
|
-
(
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
</div>
|
|
1033
|
-
<
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
</div>
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
<
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
[
|
|
1091
|
-
[
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
[
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
class
|
|
1140
|
-
[
|
|
1141
|
-
[
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
868
|
+
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: `
|
|
869
|
+
<ng-container [ngSwitch]="nodeKind">
|
|
870
|
+
|
|
871
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
872
|
+
oneOf — tab bar: ogni opzione è una tab, nessuna icona
|
|
873
|
+
═══════════════════════════════════════════════════════════ -->
|
|
874
|
+
<div *ngSwitchCase="'oneOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
|
|
875
|
+
<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">
|
|
876
|
+
<div>
|
|
877
|
+
<p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
|
|
878
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
|
|
879
|
+
</div>
|
|
880
|
+
<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>
|
|
881
|
+
</div>
|
|
882
|
+
<div class="flex overflow-x-auto border-b border-slate-200 dark:border-slate-700">
|
|
883
|
+
<button
|
|
884
|
+
*ngFor="let option of schema.oneOf; let i = index"
|
|
885
|
+
type="button"
|
|
886
|
+
class="px-4 py-2.5 text-xs font-medium whitespace-nowrap border-b-2 transition-colors"
|
|
887
|
+
[class.border-indigo-500]="selectedOneOf === i"
|
|
888
|
+
[class.text-indigo-600]="selectedOneOf === i"
|
|
889
|
+
[class.dark:text-indigo-400]="selectedOneOf === i"
|
|
890
|
+
[class.font-semibold]="selectedOneOf === i"
|
|
891
|
+
[class.border-transparent]="selectedOneOf !== i"
|
|
892
|
+
[class.text-slate-500]="selectedOneOf !== i"
|
|
893
|
+
[class.dark:text-slate-400]="selectedOneOf !== i"
|
|
894
|
+
(click)="selectedOneOf = i; onOneOfChange()"
|
|
895
|
+
>{{ option.title || ('Option ' + (i + 1)) }}</button>
|
|
896
|
+
</div>
|
|
897
|
+
<div class="p-4" *ngIf="activeVariant">
|
|
898
|
+
<jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
|
|
899
|
+
[path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
|
|
900
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
901
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
|
|
905
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
906
|
+
anyOf — pill toggle, nessuna icona
|
|
907
|
+
═══════════════════════════════════════════════════════════ -->
|
|
908
|
+
<div *ngSwitchCase="'anyOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
|
|
909
|
+
<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">
|
|
910
|
+
<div>
|
|
911
|
+
<p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
|
|
912
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
|
|
913
|
+
</div>
|
|
914
|
+
<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>
|
|
915
|
+
</div>
|
|
916
|
+
<div class="flex flex-wrap gap-2 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
917
|
+
<label
|
|
918
|
+
*ngFor="let option of schema.anyOf; let i = index"
|
|
919
|
+
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"
|
|
920
|
+
[class.border-indigo-400]="selectedAnyOf.has(i)"
|
|
921
|
+
[class.bg-indigo-50]="selectedAnyOf.has(i)"
|
|
922
|
+
[class.dark:bg-indigo-900/30]="selectedAnyOf.has(i)"
|
|
923
|
+
[class.text-indigo-700]="selectedAnyOf.has(i)"
|
|
924
|
+
[class.dark:text-indigo-400]="selectedAnyOf.has(i)"
|
|
925
|
+
[class.border-slate-200]="!selectedAnyOf.has(i)"
|
|
926
|
+
[class.dark:border-slate-600]="!selectedAnyOf.has(i)"
|
|
927
|
+
[class.text-slate-600]="!selectedAnyOf.has(i)"
|
|
928
|
+
[class.dark:text-slate-400]="!selectedAnyOf.has(i)"
|
|
929
|
+
>
|
|
930
|
+
<input type="checkbox" class="h-3.5 w-3.5 accent-indigo-600" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
|
|
931
|
+
{{ option.title || ('Option ' + (i + 1)) }}
|
|
932
|
+
</label>
|
|
933
|
+
</div>
|
|
934
|
+
<div class="p-4" *ngIf="activeVariant">
|
|
935
|
+
<jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
|
|
936
|
+
[path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
|
|
937
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
938
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
939
|
+
</div>
|
|
940
|
+
</div>
|
|
941
|
+
|
|
942
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
943
|
+
allOf — sezioni in colonna con bordo sinistro
|
|
944
|
+
═══════════════════════════════════════════════════════════ -->
|
|
945
|
+
<div *ngSwitchCase="'allOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
|
|
946
|
+
<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">
|
|
947
|
+
<div>
|
|
948
|
+
<p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
|
|
949
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
|
|
950
|
+
</div>
|
|
951
|
+
<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>
|
|
952
|
+
</div>
|
|
953
|
+
<div class="p-4 space-y-4">
|
|
954
|
+
<div *ngFor="let subSchema of mergedAllOf; let i = index" class="border-l-2 border-indigo-200 dark:border-indigo-700 pl-4">
|
|
955
|
+
<jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
|
|
956
|
+
[path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
|
|
957
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
958
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
962
|
+
|
|
963
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
964
|
+
object — fieldset con griglia SOLO per campi primitivi.
|
|
965
|
+
Logica layout:
|
|
966
|
+
- I figli vengono classificati come "semplici" (primitive/enum/bool)
|
|
967
|
+
o "complessi" (object/array/oneOf/anyOf/allOf).
|
|
968
|
+
- I semplici vanno in griglia 2 colonne (sm:grid-cols-2).
|
|
969
|
+
- I complessi vanno sempre in colonna piena (col-span-2).
|
|
970
|
+
═══════════════════════════════════════════════════════════ -->
|
|
971
|
+
<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">
|
|
972
|
+
<legend class="px-2 text-sm font-bold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</legend>
|
|
973
|
+
|
|
974
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mb-3 mt-1">{{ schema.description }}</p>
|
|
975
|
+
|
|
976
|
+
<div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5 mb-3">
|
|
977
|
+
<span *ngFor="let badge of metaBadges"
|
|
978
|
+
class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
|
|
979
|
+
[ngClass]="badgeClass(badge)">{{ badge }}</span>
|
|
980
|
+
</div>
|
|
981
|
+
|
|
982
|
+
<!-- griglia 2 col: i figli semplici occupano 1 col, i complessi 2 col -->
|
|
983
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4" [formGroup]="controlAsGroup">
|
|
984
|
+
<ng-container *ngFor="let key of objectKeys">
|
|
985
|
+
<div class="flex flex-col gap-1"
|
|
986
|
+
[class.sm:col-span-2]="!isSimpleChild(key)">
|
|
987
|
+
<div *ngIf="isDynamicKey(key)"
|
|
988
|
+
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">
|
|
989
|
+
<span class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</span>
|
|
990
|
+
<button type="button"
|
|
991
|
+
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"
|
|
992
|
+
(click)="removeDynamicKey(key)">Remove</button>
|
|
993
|
+
</div>
|
|
994
|
+
<jsm-schema-node
|
|
995
|
+
[schema]="schemaForKey(key)"
|
|
996
|
+
[control]="childControl(key)"
|
|
997
|
+
[parent]="controlAsGroup"
|
|
998
|
+
[controlKey]="key"
|
|
999
|
+
[path]="pathForChild(key)"
|
|
1000
|
+
[errorsMap]="errorsMap"
|
|
1001
|
+
[label]="key"
|
|
1002
|
+
[required]="isRequired(key)"
|
|
1003
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1004
|
+
(controlReplaced)="controlReplaced.emit($event)">
|
|
1005
|
+
</jsm-schema-node>
|
|
1006
|
+
</div>
|
|
1007
|
+
</ng-container>
|
|
1008
|
+
</div>
|
|
1009
|
+
|
|
1010
|
+
<!-- add dynamic property -->
|
|
1011
|
+
<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">
|
|
1012
|
+
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-400 dark:text-slate-500">Add property</p>
|
|
1013
|
+
<div class="flex gap-2">
|
|
1014
|
+
<input
|
|
1015
|
+
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"
|
|
1016
|
+
[(ngModel)]="newPropertyKey" placeholder="propertyName" />
|
|
1017
|
+
<button type="button"
|
|
1018
|
+
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"
|
|
1019
|
+
(click)="addDynamicProperty()">Add</button>
|
|
1020
|
+
</div>
|
|
1021
|
+
<p *ngIf="propertyError" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1022
|
+
<!-- icona errore: unico caso in cui ha senso, segnala un problema -->
|
|
1023
|
+
<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>
|
|
1024
|
+
{{ propertyError }}
|
|
1025
|
+
</p>
|
|
1026
|
+
</div>
|
|
1027
|
+
|
|
1028
|
+
<div *ngIf="errorsForPath.length" class="mt-3 space-y-1">
|
|
1029
|
+
<p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1030
|
+
<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>
|
|
1031
|
+
{{ error }}
|
|
1032
|
+
</p>
|
|
1033
|
+
</div>
|
|
1034
|
+
</fieldset>
|
|
1035
|
+
|
|
1036
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1037
|
+
array — sempre in colonna piena, items in colonna
|
|
1038
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1039
|
+
<div *ngSwitchCase="'array'" class="space-y-3">
|
|
1040
|
+
<div class="flex items-start justify-between gap-2">
|
|
1041
|
+
<div>
|
|
1042
|
+
<p class="text-sm font-semibold text-slate-800">
|
|
1043
|
+
{{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
|
|
1044
|
+
</p>
|
|
1045
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 mt-0.5">{{ schema.description }}</p>
|
|
1046
|
+
</div>
|
|
1047
|
+
<!-- icona + nel bottone: indica l'azione "aggiungi", non decorativa -->
|
|
1048
|
+
<button type="button"
|
|
1049
|
+
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"
|
|
1050
|
+
(click)="addArrayItem()" [disabled]="!canAddArrayItem">
|
|
1051
|
+
<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
|
+
Add item
|
|
1053
|
+
</button>
|
|
1054
|
+
</div>
|
|
1055
|
+
|
|
1056
|
+
<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">
|
|
1057
|
+
Must contain matching item(s)
|
|
1058
|
+
<span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
|
|
1059
|
+
<span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
|
|
1060
|
+
</div>
|
|
1061
|
+
|
|
1062
|
+
<div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
|
|
1063
|
+
<span *ngFor="let badge of metaBadges"
|
|
1064
|
+
class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
|
|
1065
|
+
[ngClass]="badgeClass(badge)">{{ badge }}</span>
|
|
1066
|
+
</div>
|
|
1067
|
+
|
|
1068
|
+
<div class="space-y-2" *ngIf="arrayControls.length > 0; else emptyArray">
|
|
1069
|
+
<div *ngFor="let item of arrayControls; let i = index"
|
|
1070
|
+
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">
|
|
1071
|
+
<div class="flex items-center justify-between mb-3">
|
|
1072
|
+
<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>
|
|
1073
|
+
<button type="button"
|
|
1074
|
+
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"
|
|
1075
|
+
(click)="removeArrayItem(i)">Remove</button>
|
|
1076
|
+
</div>
|
|
1077
|
+
<jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
|
|
1078
|
+
[controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
|
|
1079
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1080
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
|
|
1084
|
+
<ng-template #emptyArray>
|
|
1085
|
+
<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">
|
|
1086
|
+
No items yet — click "Add item" to start.
|
|
1087
|
+
</div>
|
|
1088
|
+
</ng-template>
|
|
1089
|
+
|
|
1090
|
+
<div *ngIf="errorsForPath.length" class="space-y-1">
|
|
1091
|
+
<p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1092
|
+
<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>
|
|
1093
|
+
{{ error }}
|
|
1094
|
+
</p>
|
|
1095
|
+
</div>
|
|
1096
|
+
</div>
|
|
1097
|
+
|
|
1098
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1099
|
+
primitive — sempre in colonna, nessuna icona decorativa.
|
|
1100
|
+
Icona errore solo nei messaggi di validazione.
|
|
1101
|
+
Icona chevron nel select: funzionale (indica dropdown).
|
|
1102
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1103
|
+
<div *ngSwitchDefault class="space-y-1.5">
|
|
1104
|
+
<ng-container *ngIf="inputKind !== 'checkbox'">
|
|
1105
|
+
<label class="block text-[13px] font-semibold text-slate-700 dark:text-slate-300">
|
|
1106
|
+
{{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
|
|
1107
|
+
</label>
|
|
1108
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 -mt-0.5">{{ schema.description }}</p>
|
|
1109
|
+
</ng-container>
|
|
1110
|
+
|
|
1111
|
+
<div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
|
|
1112
|
+
<span *ngFor="let badge of metaBadges"
|
|
1113
|
+
class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
|
|
1114
|
+
[ngClass]="badgeClass(badge)">{{ badge }}</span>
|
|
1115
|
+
</div>
|
|
1116
|
+
|
|
1117
|
+
<ng-container [ngSwitch]="inputKind">
|
|
1118
|
+
|
|
1119
|
+
<input *ngSwitchCase="'text'"
|
|
1120
|
+
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"
|
|
1121
|
+
[class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1122
|
+
[class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1123
|
+
[class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1124
|
+
[class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1125
|
+
[class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1126
|
+
[class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1127
|
+
[class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1128
|
+
[class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1129
|
+
[class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1130
|
+
[class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1131
|
+
[class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1132
|
+
[attr.type]="inputType"
|
|
1133
|
+
[attr.placeholder]="schema.title || label"
|
|
1134
|
+
[formControl]="controlAsFormControl"
|
|
1135
|
+
/>
|
|
1136
|
+
|
|
1137
|
+
<textarea *ngSwitchCase="'textarea'" rows="4"
|
|
1138
|
+
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"
|
|
1139
|
+
[class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1140
|
+
[class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1141
|
+
[class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1142
|
+
[class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1143
|
+
[class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1144
|
+
[class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1145
|
+
[class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1146
|
+
[class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1147
|
+
[class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1148
|
+
[class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1149
|
+
[class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1150
|
+
[attr.placeholder]="schema.title || label"
|
|
1151
|
+
[formControl]="controlAsFormControl"
|
|
1152
|
+
></textarea>
|
|
1153
|
+
|
|
1154
|
+
<!-- select: il chevron SVG è funzionale, indica che è un dropdown -->
|
|
1155
|
+
<div *ngSwitchCase="'select'" class="relative">
|
|
1156
|
+
<select
|
|
1157
|
+
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"
|
|
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
|
+
[formControl]="controlAsFormControl">
|
|
1163
|
+
<option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
|
|
1164
|
+
</select>
|
|
1165
|
+
<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"
|
|
1166
|
+
viewBox="0 0 16 16" fill="none">
|
|
1167
|
+
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1168
|
+
</svg>
|
|
1169
|
+
</div>
|
|
1170
|
+
|
|
1171
|
+
<!-- checkbox come toggle switch -->
|
|
1172
|
+
<label *ngSwitchCase="'checkbox'" class="inline-flex cursor-pointer items-center gap-2.5">
|
|
1173
|
+
<span class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors"
|
|
1174
|
+
[class.bg-indigo-600]="controlAsFormControl.value"
|
|
1175
|
+
[class.bg-slate-300]="!controlAsFormControl.value"
|
|
1176
|
+
[class.dark:bg-slate-600]="!controlAsFormControl.value">
|
|
1177
|
+
<input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
|
|
1178
|
+
<span class="inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform"
|
|
1179
|
+
[class.translate-x-4]="controlAsFormControl.value"
|
|
1180
|
+
[class.translate-x-0.5]="!controlAsFormControl.value"></span>
|
|
1181
|
+
</span>
|
|
1182
|
+
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
|
1183
|
+
{{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
|
|
1184
|
+
</span>
|
|
1185
|
+
</label>
|
|
1186
|
+
|
|
1187
|
+
</ng-container>
|
|
1188
|
+
|
|
1189
|
+
<p *ngIf="inputKind === 'checkbox' && schema.description" class="text-xs text-slate-500 dark:text-slate-400">{{ schema.description }}</p>
|
|
1190
|
+
|
|
1191
|
+
<p *ngIf="effectiveSchema.examples?.length" class="text-[11px] text-slate-400 dark:text-slate-500">
|
|
1192
|
+
e.g. {{ effectiveSchema.examples?.[0] }}
|
|
1193
|
+
</p>
|
|
1194
|
+
|
|
1195
|
+
<p *ngIf="effectiveSchema.contentMediaType" class="text-[11px] text-slate-400 dark:text-slate-500">
|
|
1196
|
+
{{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
|
|
1197
|
+
</p>
|
|
1198
|
+
|
|
1199
|
+
<!-- icona errore: funzionale, segnala un problema di validazione -->
|
|
1200
|
+
<div *ngIf="errorsForPath.length && controlAsFormControl.dirty" class="space-y-1 pt-0.5">
|
|
1201
|
+
<p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1202
|
+
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none">
|
|
1203
|
+
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
|
|
1204
|
+
<path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1205
|
+
</svg>
|
|
1206
|
+
{{ error }}
|
|
1207
|
+
</p>
|
|
1208
|
+
</div>
|
|
1209
|
+
</div>
|
|
1210
|
+
|
|
1211
|
+
</ng-container>
|
|
1212
|
+
`, isInline: true, 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 });
|
|
1180
1213
|
}
|
|
1181
1214
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
|
|
1182
1215
|
type: Component,
|
|
@@ -1184,305 +1217,350 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1184
1217
|
selector: 'jsm-schema-node',
|
|
1185
1218
|
standalone: true,
|
|
1186
1219
|
imports: [CommonModule, FormsModule, ReactiveFormsModule],
|
|
1187
|
-
template: `
|
|
1188
|
-
<ng-container [ngSwitch]="nodeKind">
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
<
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
class="
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
[
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
[
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
<
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
<
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
[controlKey]="controlKey"
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
[
|
|
1315
|
-
[
|
|
1316
|
-
[
|
|
1317
|
-
[
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
class="
|
|
1335
|
-
(
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
</div>
|
|
1340
|
-
<
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
</div>
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
<
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
[
|
|
1398
|
-
[
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
[
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
class
|
|
1447
|
-
[
|
|
1448
|
-
[
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1220
|
+
template: `
|
|
1221
|
+
<ng-container [ngSwitch]="nodeKind">
|
|
1222
|
+
|
|
1223
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1224
|
+
oneOf — tab bar: ogni opzione è una tab, nessuna icona
|
|
1225
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1226
|
+
<div *ngSwitchCase="'oneOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
|
|
1227
|
+
<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">
|
|
1228
|
+
<div>
|
|
1229
|
+
<p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
|
|
1230
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
|
|
1231
|
+
</div>
|
|
1232
|
+
<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>
|
|
1233
|
+
</div>
|
|
1234
|
+
<div class="flex overflow-x-auto border-b border-slate-200 dark:border-slate-700">
|
|
1235
|
+
<button
|
|
1236
|
+
*ngFor="let option of schema.oneOf; let i = index"
|
|
1237
|
+
type="button"
|
|
1238
|
+
class="px-4 py-2.5 text-xs font-medium whitespace-nowrap border-b-2 transition-colors"
|
|
1239
|
+
[class.border-indigo-500]="selectedOneOf === i"
|
|
1240
|
+
[class.text-indigo-600]="selectedOneOf === i"
|
|
1241
|
+
[class.dark:text-indigo-400]="selectedOneOf === i"
|
|
1242
|
+
[class.font-semibold]="selectedOneOf === i"
|
|
1243
|
+
[class.border-transparent]="selectedOneOf !== i"
|
|
1244
|
+
[class.text-slate-500]="selectedOneOf !== i"
|
|
1245
|
+
[class.dark:text-slate-400]="selectedOneOf !== i"
|
|
1246
|
+
(click)="selectedOneOf = i; onOneOfChange()"
|
|
1247
|
+
>{{ option.title || ('Option ' + (i + 1)) }}</button>
|
|
1248
|
+
</div>
|
|
1249
|
+
<div class="p-4" *ngIf="activeVariant">
|
|
1250
|
+
<jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
|
|
1251
|
+
[path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
|
|
1252
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1253
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
1254
|
+
</div>
|
|
1255
|
+
</div>
|
|
1256
|
+
|
|
1257
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1258
|
+
anyOf — pill toggle, nessuna icona
|
|
1259
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1260
|
+
<div *ngSwitchCase="'anyOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
|
|
1261
|
+
<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">
|
|
1262
|
+
<div>
|
|
1263
|
+
<p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
|
|
1264
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
|
|
1265
|
+
</div>
|
|
1266
|
+
<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>
|
|
1267
|
+
</div>
|
|
1268
|
+
<div class="flex flex-wrap gap-2 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
1269
|
+
<label
|
|
1270
|
+
*ngFor="let option of schema.anyOf; let i = index"
|
|
1271
|
+
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"
|
|
1272
|
+
[class.border-indigo-400]="selectedAnyOf.has(i)"
|
|
1273
|
+
[class.bg-indigo-50]="selectedAnyOf.has(i)"
|
|
1274
|
+
[class.dark:bg-indigo-900/30]="selectedAnyOf.has(i)"
|
|
1275
|
+
[class.text-indigo-700]="selectedAnyOf.has(i)"
|
|
1276
|
+
[class.dark:text-indigo-400]="selectedAnyOf.has(i)"
|
|
1277
|
+
[class.border-slate-200]="!selectedAnyOf.has(i)"
|
|
1278
|
+
[class.dark:border-slate-600]="!selectedAnyOf.has(i)"
|
|
1279
|
+
[class.text-slate-600]="!selectedAnyOf.has(i)"
|
|
1280
|
+
[class.dark:text-slate-400]="!selectedAnyOf.has(i)"
|
|
1281
|
+
>
|
|
1282
|
+
<input type="checkbox" class="h-3.5 w-3.5 accent-indigo-600" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
|
|
1283
|
+
{{ option.title || ('Option ' + (i + 1)) }}
|
|
1284
|
+
</label>
|
|
1285
|
+
</div>
|
|
1286
|
+
<div class="p-4" *ngIf="activeVariant">
|
|
1287
|
+
<jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
|
|
1288
|
+
[path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
|
|
1289
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1290
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
1291
|
+
</div>
|
|
1292
|
+
</div>
|
|
1293
|
+
|
|
1294
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1295
|
+
allOf — sezioni in colonna con bordo sinistro
|
|
1296
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1297
|
+
<div *ngSwitchCase="'allOf'" class="rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-sm overflow-hidden">
|
|
1298
|
+
<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">
|
|
1299
|
+
<div>
|
|
1300
|
+
<p class="text-sm font-semibold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</p>
|
|
1301
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{{ schema.description }}</p>
|
|
1302
|
+
</div>
|
|
1303
|
+
<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>
|
|
1304
|
+
</div>
|
|
1305
|
+
<div class="p-4 space-y-4">
|
|
1306
|
+
<div *ngFor="let subSchema of mergedAllOf; let i = index" class="border-l-2 border-indigo-200 dark:border-indigo-700 pl-4">
|
|
1307
|
+
<jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
|
|
1308
|
+
[path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
|
|
1309
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1310
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
1311
|
+
</div>
|
|
1312
|
+
</div>
|
|
1313
|
+
</div>
|
|
1314
|
+
|
|
1315
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1316
|
+
object — fieldset con griglia SOLO per campi primitivi.
|
|
1317
|
+
Logica layout:
|
|
1318
|
+
- I figli vengono classificati come "semplici" (primitive/enum/bool)
|
|
1319
|
+
o "complessi" (object/array/oneOf/anyOf/allOf).
|
|
1320
|
+
- I semplici vanno in griglia 2 colonne (sm:grid-cols-2).
|
|
1321
|
+
- I complessi vanno sempre in colonna piena (col-span-2).
|
|
1322
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1323
|
+
<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">
|
|
1324
|
+
<legend class="px-2 text-sm font-bold text-slate-800 dark:text-slate-200">{{ schema.title || label }}</legend>
|
|
1325
|
+
|
|
1326
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 mb-3 mt-1">{{ schema.description }}</p>
|
|
1327
|
+
|
|
1328
|
+
<div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5 mb-3">
|
|
1329
|
+
<span *ngFor="let badge of metaBadges"
|
|
1330
|
+
class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
|
|
1331
|
+
[ngClass]="badgeClass(badge)">{{ badge }}</span>
|
|
1332
|
+
</div>
|
|
1333
|
+
|
|
1334
|
+
<!-- griglia 2 col: i figli semplici occupano 1 col, i complessi 2 col -->
|
|
1335
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4" [formGroup]="controlAsGroup">
|
|
1336
|
+
<ng-container *ngFor="let key of objectKeys">
|
|
1337
|
+
<div class="flex flex-col gap-1"
|
|
1338
|
+
[class.sm:col-span-2]="!isSimpleChild(key)">
|
|
1339
|
+
<div *ngIf="isDynamicKey(key)"
|
|
1340
|
+
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">
|
|
1341
|
+
<span class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</span>
|
|
1342
|
+
<button type="button"
|
|
1343
|
+
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"
|
|
1344
|
+
(click)="removeDynamicKey(key)">Remove</button>
|
|
1345
|
+
</div>
|
|
1346
|
+
<jsm-schema-node
|
|
1347
|
+
[schema]="schemaForKey(key)"
|
|
1348
|
+
[control]="childControl(key)"
|
|
1349
|
+
[parent]="controlAsGroup"
|
|
1350
|
+
[controlKey]="key"
|
|
1351
|
+
[path]="pathForChild(key)"
|
|
1352
|
+
[errorsMap]="errorsMap"
|
|
1353
|
+
[label]="key"
|
|
1354
|
+
[required]="isRequired(key)"
|
|
1355
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1356
|
+
(controlReplaced)="controlReplaced.emit($event)">
|
|
1357
|
+
</jsm-schema-node>
|
|
1358
|
+
</div>
|
|
1359
|
+
</ng-container>
|
|
1360
|
+
</div>
|
|
1361
|
+
|
|
1362
|
+
<!-- add dynamic property -->
|
|
1363
|
+
<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">
|
|
1364
|
+
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-400 dark:text-slate-500">Add property</p>
|
|
1365
|
+
<div class="flex gap-2">
|
|
1366
|
+
<input
|
|
1367
|
+
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"
|
|
1368
|
+
[(ngModel)]="newPropertyKey" placeholder="propertyName" />
|
|
1369
|
+
<button type="button"
|
|
1370
|
+
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"
|
|
1371
|
+
(click)="addDynamicProperty()">Add</button>
|
|
1372
|
+
</div>
|
|
1373
|
+
<p *ngIf="propertyError" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1374
|
+
<!-- icona errore: unico caso in cui ha senso, segnala un problema -->
|
|
1375
|
+
<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>
|
|
1376
|
+
{{ propertyError }}
|
|
1377
|
+
</p>
|
|
1378
|
+
</div>
|
|
1379
|
+
|
|
1380
|
+
<div *ngIf="errorsForPath.length" class="mt-3 space-y-1">
|
|
1381
|
+
<p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1382
|
+
<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>
|
|
1383
|
+
{{ error }}
|
|
1384
|
+
</p>
|
|
1385
|
+
</div>
|
|
1386
|
+
</fieldset>
|
|
1387
|
+
|
|
1388
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1389
|
+
array — sempre in colonna piena, items in colonna
|
|
1390
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1391
|
+
<div *ngSwitchCase="'array'" class="space-y-3">
|
|
1392
|
+
<div class="flex items-start justify-between gap-2">
|
|
1393
|
+
<div>
|
|
1394
|
+
<p class="text-sm font-semibold text-slate-800">
|
|
1395
|
+
{{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
|
|
1396
|
+
</p>
|
|
1397
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 mt-0.5">{{ schema.description }}</p>
|
|
1398
|
+
</div>
|
|
1399
|
+
<!-- icona + nel bottone: indica l'azione "aggiungi", non decorativa -->
|
|
1400
|
+
<button type="button"
|
|
1401
|
+
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"
|
|
1402
|
+
(click)="addArrayItem()" [disabled]="!canAddArrayItem">
|
|
1403
|
+
<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>
|
|
1404
|
+
Add item
|
|
1405
|
+
</button>
|
|
1406
|
+
</div>
|
|
1407
|
+
|
|
1408
|
+
<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">
|
|
1409
|
+
Must contain matching item(s)
|
|
1410
|
+
<span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
|
|
1411
|
+
<span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
|
|
1412
|
+
</div>
|
|
1413
|
+
|
|
1414
|
+
<div *ngIf="metaBadges.length" class="flex flex-wrap gap-1.5">
|
|
1415
|
+
<span *ngFor="let badge of metaBadges"
|
|
1416
|
+
class="rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide"
|
|
1417
|
+
[ngClass]="badgeClass(badge)">{{ badge }}</span>
|
|
1418
|
+
</div>
|
|
1419
|
+
|
|
1420
|
+
<div class="space-y-2" *ngIf="arrayControls.length > 0; else emptyArray">
|
|
1421
|
+
<div *ngFor="let item of arrayControls; let i = index"
|
|
1422
|
+
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">
|
|
1423
|
+
<div class="flex items-center justify-between mb-3">
|
|
1424
|
+
<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>
|
|
1425
|
+
<button type="button"
|
|
1426
|
+
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"
|
|
1427
|
+
(click)="removeArrayItem(i)">Remove</button>
|
|
1428
|
+
</div>
|
|
1429
|
+
<jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
|
|
1430
|
+
[controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
|
|
1431
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1432
|
+
(controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
|
|
1433
|
+
</div>
|
|
1434
|
+
</div>
|
|
1435
|
+
|
|
1436
|
+
<ng-template #emptyArray>
|
|
1437
|
+
<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">
|
|
1438
|
+
No items yet — click "Add item" to start.
|
|
1439
|
+
</div>
|
|
1440
|
+
</ng-template>
|
|
1441
|
+
|
|
1442
|
+
<div *ngIf="errorsForPath.length" class="space-y-1">
|
|
1443
|
+
<p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1444
|
+
<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>
|
|
1445
|
+
{{ error }}
|
|
1446
|
+
</p>
|
|
1447
|
+
</div>
|
|
1448
|
+
</div>
|
|
1449
|
+
|
|
1450
|
+
<!-- ═══════════════════════════════════════════════════════════
|
|
1451
|
+
primitive — sempre in colonna, nessuna icona decorativa.
|
|
1452
|
+
Icona errore solo nei messaggi di validazione.
|
|
1453
|
+
Icona chevron nel select: funzionale (indica dropdown).
|
|
1454
|
+
═══════════════════════════════════════════════════════════ -->
|
|
1455
|
+
<div *ngSwitchDefault class="space-y-1.5">
|
|
1456
|
+
<ng-container *ngIf="inputKind !== 'checkbox'">
|
|
1457
|
+
<label class="block text-[13px] font-semibold text-slate-700 dark:text-slate-300">
|
|
1458
|
+
{{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
|
|
1459
|
+
</label>
|
|
1460
|
+
<p *ngIf="schema.description" class="text-xs text-slate-500 dark:text-slate-400 -mt-0.5">{{ schema.description }}</p>
|
|
1461
|
+
</ng-container>
|
|
1462
|
+
|
|
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>
|
|
1467
|
+
</div>
|
|
1468
|
+
|
|
1469
|
+
<ng-container [ngSwitch]="inputKind">
|
|
1470
|
+
|
|
1471
|
+
<input *ngSwitchCase="'text'"
|
|
1472
|
+
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"
|
|
1473
|
+
[class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1474
|
+
[class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1475
|
+
[class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1476
|
+
[class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1477
|
+
[class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1478
|
+
[class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1479
|
+
[class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1480
|
+
[class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1481
|
+
[class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1482
|
+
[class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1483
|
+
[class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1484
|
+
[attr.type]="inputType"
|
|
1485
|
+
[attr.placeholder]="schema.title || label"
|
|
1486
|
+
[formControl]="controlAsFormControl"
|
|
1487
|
+
/>
|
|
1488
|
+
|
|
1489
|
+
<textarea *ngSwitchCase="'textarea'" rows="4"
|
|
1490
|
+
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"
|
|
1491
|
+
[class.border-slate-300]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1492
|
+
[class.dark:border-slate-600]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1493
|
+
[class.focus:border-indigo-400]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1494
|
+
[class.focus:ring-indigo-100]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1495
|
+
[class.dark:focus:ring-indigo-900/40]="!(errorsForPath.length && controlAsFormControl.dirty)"
|
|
1496
|
+
[class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1497
|
+
[class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1498
|
+
[class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1499
|
+
[class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1500
|
+
[class.focus:border-red-400]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1501
|
+
[class.focus:ring-red-100]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1502
|
+
[attr.placeholder]="schema.title || label"
|
|
1503
|
+
[formControl]="controlAsFormControl"
|
|
1504
|
+
></textarea>
|
|
1505
|
+
|
|
1506
|
+
<!-- select: il chevron SVG è funzionale, indica che è un dropdown -->
|
|
1507
|
+
<div *ngSwitchCase="'select'" class="relative">
|
|
1508
|
+
<select
|
|
1509
|
+
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"
|
|
1510
|
+
[class.border-red-300]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1511
|
+
[class.dark:border-red-700]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1512
|
+
[class.bg-red-50]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1513
|
+
[class.dark:bg-red-900/20]="errorsForPath.length && controlAsFormControl.dirty"
|
|
1514
|
+
[formControl]="controlAsFormControl">
|
|
1515
|
+
<option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
|
|
1516
|
+
</select>
|
|
1517
|
+
<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"
|
|
1518
|
+
viewBox="0 0 16 16" fill="none">
|
|
1519
|
+
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1520
|
+
</svg>
|
|
1521
|
+
</div>
|
|
1522
|
+
|
|
1523
|
+
<!-- checkbox come toggle switch -->
|
|
1524
|
+
<label *ngSwitchCase="'checkbox'" class="inline-flex cursor-pointer items-center gap-2.5">
|
|
1525
|
+
<span class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors"
|
|
1526
|
+
[class.bg-indigo-600]="controlAsFormControl.value"
|
|
1527
|
+
[class.bg-slate-300]="!controlAsFormControl.value"
|
|
1528
|
+
[class.dark:bg-slate-600]="!controlAsFormControl.value">
|
|
1529
|
+
<input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
|
|
1530
|
+
<span class="inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform"
|
|
1531
|
+
[class.translate-x-4]="controlAsFormControl.value"
|
|
1532
|
+
[class.translate-x-0.5]="!controlAsFormControl.value"></span>
|
|
1533
|
+
</span>
|
|
1534
|
+
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
|
1535
|
+
{{ schema.title || label }}<span *ngIf="required" class="ml-0.5 text-red-500">*</span>
|
|
1536
|
+
</span>
|
|
1537
|
+
</label>
|
|
1538
|
+
|
|
1539
|
+
</ng-container>
|
|
1540
|
+
|
|
1541
|
+
<p *ngIf="inputKind === 'checkbox' && schema.description" class="text-xs text-slate-500 dark:text-slate-400">{{ schema.description }}</p>
|
|
1542
|
+
|
|
1543
|
+
<p *ngIf="effectiveSchema.examples?.length" class="text-[11px] text-slate-400 dark:text-slate-500">
|
|
1544
|
+
e.g. {{ effectiveSchema.examples?.[0] }}
|
|
1545
|
+
</p>
|
|
1546
|
+
|
|
1547
|
+
<p *ngIf="effectiveSchema.contentMediaType" class="text-[11px] text-slate-400 dark:text-slate-500">
|
|
1548
|
+
{{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
|
|
1549
|
+
</p>
|
|
1550
|
+
|
|
1551
|
+
<!-- icona errore: funzionale, segnala un problema di validazione -->
|
|
1552
|
+
<div *ngIf="errorsForPath.length && controlAsFormControl.dirty" class="space-y-1 pt-0.5">
|
|
1553
|
+
<p *ngFor="let error of errorsForPath" class="flex items-center gap-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
1554
|
+
<svg class="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none">
|
|
1555
|
+
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
|
|
1556
|
+
<path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1557
|
+
</svg>
|
|
1558
|
+
{{ error }}
|
|
1559
|
+
</p>
|
|
1560
|
+
</div>
|
|
1561
|
+
</div>
|
|
1562
|
+
|
|
1563
|
+
</ng-container>
|
|
1486
1564
|
`,
|
|
1487
1565
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1488
1566
|
}]
|
|
@@ -1504,6 +1582,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1504
1582
|
type: Input
|
|
1505
1583
|
}], controlKey: [{
|
|
1506
1584
|
type: Input
|
|
1585
|
+
}], allowAdditionalProperties: [{
|
|
1586
|
+
type: Input
|
|
1507
1587
|
}], controlReplaced: [{
|
|
1508
1588
|
type: Output
|
|
1509
1589
|
}] } });
|
|
@@ -1515,13 +1595,16 @@ class JsonSchemaFormComponent {
|
|
|
1515
1595
|
schema;
|
|
1516
1596
|
value;
|
|
1517
1597
|
data;
|
|
1598
|
+
allowAdditionalProperties = false;
|
|
1518
1599
|
formReady = new EventEmitter();
|
|
1519
1600
|
valueChange = new EventEmitter();
|
|
1520
1601
|
schemaReady = new EventEmitter();
|
|
1602
|
+
schemaErrors = new EventEmitter();
|
|
1521
1603
|
form;
|
|
1522
1604
|
rootControl;
|
|
1523
1605
|
resolvedSchema;
|
|
1524
1606
|
errorsMap = new Map();
|
|
1607
|
+
schemaErrorList = [];
|
|
1525
1608
|
loading = false;
|
|
1526
1609
|
valueSub;
|
|
1527
1610
|
constructor(schemaService, resolver, validation) {
|
|
@@ -1541,7 +1624,17 @@ class JsonSchemaFormComponent {
|
|
|
1541
1624
|
this.valueSub?.unsubscribe();
|
|
1542
1625
|
this.loading = true;
|
|
1543
1626
|
this.resolvedSchema = await this.resolver.resolve(this.schema, this.schema?.$id);
|
|
1627
|
+
// Validate the schema itself and emit any structural errors
|
|
1628
|
+
const schemaErrs = this.validation.validateSchema(this.resolvedSchema);
|
|
1629
|
+
this.schemaErrorList = schemaErrs;
|
|
1630
|
+
this.schemaErrors.emit(schemaErrs);
|
|
1544
1631
|
this.schemaReady.emit(this.resolvedSchema);
|
|
1632
|
+
// If the schema has structural errors, stop here — don't try to build the form
|
|
1633
|
+
if (schemaErrs.length > 0) {
|
|
1634
|
+
this.form = undefined;
|
|
1635
|
+
this.loading = false;
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1545
1638
|
const initialData = this.value !== undefined ? this.value : this.data;
|
|
1546
1639
|
this.rootControl = this.schemaService.buildControl(this.resolvedSchema, initialData);
|
|
1547
1640
|
this.form = this.rootControl instanceof FormGroup ? this.rootControl : new FormGroup({ value: this.rootControl });
|
|
@@ -1588,29 +1681,60 @@ class JsonSchemaFormComponent {
|
|
|
1588
1681
|
});
|
|
1589
1682
|
}
|
|
1590
1683
|
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 });
|
|
1591
|
-
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" }, outputs: { formReady: "formReady", valueChange: "valueChange", schemaReady: "schemaReady" }, usesOnChanges: true, ngImport: i0, template: `
|
|
1592
|
-
<form class="w-full
|
|
1593
|
-
<div class="space-y-
|
|
1594
|
-
<h2 class="text-
|
|
1595
|
-
<p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500">
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1684
|
+
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: `
|
|
1685
|
+
<form class="w-full space-y-5">
|
|
1686
|
+
<div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
|
|
1687
|
+
<h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || 'Form' }}</h2>
|
|
1688
|
+
<p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
|
|
1689
|
+
</div>
|
|
1690
|
+
|
|
1691
|
+
<div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" aria-live="polite">
|
|
1692
|
+
<svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none">
|
|
1693
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
1694
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
|
1695
|
+
</svg>
|
|
1696
|
+
Resolving schema…
|
|
1697
|
+
</div>
|
|
1698
|
+
|
|
1699
|
+
<!-- schema structural errors panel -->
|
|
1700
|
+
<div *ngIf="schemaErrorList.length > 0 && !loading"
|
|
1701
|
+
class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
|
|
1702
|
+
role="alert">
|
|
1703
|
+
<div class="flex items-center gap-2">
|
|
1704
|
+
<svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none">
|
|
1705
|
+
<path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
|
1706
|
+
<path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1707
|
+
</svg>
|
|
1708
|
+
<span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
|
|
1709
|
+
Schema errors ({{ schemaErrorList.length }})
|
|
1710
|
+
</span>
|
|
1711
|
+
</div>
|
|
1712
|
+
<p class="text-xs text-amber-700 dark:text-amber-400">
|
|
1713
|
+
The schema has structural issues. Fix them to get a valid form.
|
|
1714
|
+
</p>
|
|
1715
|
+
<div class="space-y-2">
|
|
1716
|
+
<div *ngFor="let err of schemaErrorList"
|
|
1717
|
+
class="rounded-lg border border-amber-200 dark:border-amber-800 bg-white dark:bg-slate-900 px-3 py-2 space-y-0.5">
|
|
1718
|
+
<p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
|
|
1719
|
+
<p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
|
|
1720
|
+
<p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
|
|
1721
|
+
</div>
|
|
1722
|
+
</div>
|
|
1723
|
+
</div>
|
|
1724
|
+
|
|
1725
|
+
<div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
|
|
1726
|
+
<jsm-schema-node
|
|
1727
|
+
[schema]="resolvedSchema"
|
|
1728
|
+
[control]="rootControl"
|
|
1729
|
+
[path]="''"
|
|
1730
|
+
[errorsMap]="errorsMap"
|
|
1731
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1732
|
+
label="Root"
|
|
1733
|
+
(controlReplaced)="onRootReplaced($event)"
|
|
1734
|
+
></jsm-schema-node>
|
|
1735
|
+
</div>
|
|
1736
|
+
</form>
|
|
1737
|
+
`, isInline: true, 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 });
|
|
1614
1738
|
}
|
|
1615
1739
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
|
|
1616
1740
|
type: Component,
|
|
@@ -1618,28 +1742,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1618
1742
|
selector: 'jsm-json-schema-form',
|
|
1619
1743
|
standalone: true,
|
|
1620
1744
|
imports: [CommonModule, ReactiveFormsModule, JsonSchemaNodeComponent],
|
|
1621
|
-
template: `
|
|
1622
|
-
<form class="w-full
|
|
1623
|
-
<div class="space-y-
|
|
1624
|
-
<h2 class="text-
|
|
1625
|
-
<p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500">
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1745
|
+
template: `
|
|
1746
|
+
<form class="w-full space-y-5">
|
|
1747
|
+
<div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
|
|
1748
|
+
<h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || 'Form' }}</h2>
|
|
1749
|
+
<p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
|
|
1750
|
+
</div>
|
|
1751
|
+
|
|
1752
|
+
<div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" aria-live="polite">
|
|
1753
|
+
<svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none">
|
|
1754
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
1755
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
|
1756
|
+
</svg>
|
|
1757
|
+
Resolving schema…
|
|
1758
|
+
</div>
|
|
1759
|
+
|
|
1760
|
+
<!-- schema structural errors panel -->
|
|
1761
|
+
<div *ngIf="schemaErrorList.length > 0 && !loading"
|
|
1762
|
+
class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
|
|
1763
|
+
role="alert">
|
|
1764
|
+
<div class="flex items-center gap-2">
|
|
1765
|
+
<svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none">
|
|
1766
|
+
<path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
|
1767
|
+
<path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1768
|
+
</svg>
|
|
1769
|
+
<span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
|
|
1770
|
+
Schema errors ({{ schemaErrorList.length }})
|
|
1771
|
+
</span>
|
|
1772
|
+
</div>
|
|
1773
|
+
<p class="text-xs text-amber-700 dark:text-amber-400">
|
|
1774
|
+
The schema has structural issues. Fix them to get a valid form.
|
|
1775
|
+
</p>
|
|
1776
|
+
<div class="space-y-2">
|
|
1777
|
+
<div *ngFor="let err of schemaErrorList"
|
|
1778
|
+
class="rounded-lg border border-amber-200 dark:border-amber-800 bg-white dark:bg-slate-900 px-3 py-2 space-y-0.5">
|
|
1779
|
+
<p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
|
|
1780
|
+
<p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
|
|
1781
|
+
<p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
|
|
1782
|
+
</div>
|
|
1783
|
+
</div>
|
|
1784
|
+
</div>
|
|
1785
|
+
|
|
1786
|
+
<div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
|
|
1787
|
+
<jsm-schema-node
|
|
1788
|
+
[schema]="resolvedSchema"
|
|
1789
|
+
[control]="rootControl"
|
|
1790
|
+
[path]="''"
|
|
1791
|
+
[errorsMap]="errorsMap"
|
|
1792
|
+
[allowAdditionalProperties]="allowAdditionalProperties"
|
|
1793
|
+
label="Root"
|
|
1794
|
+
(controlReplaced)="onRootReplaced($event)"
|
|
1795
|
+
></jsm-schema-node>
|
|
1796
|
+
</div>
|
|
1797
|
+
</form>
|
|
1643
1798
|
`,
|
|
1644
1799
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1645
1800
|
}]
|
|
@@ -1650,12 +1805,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1650
1805
|
type: Input
|
|
1651
1806
|
}], data: [{
|
|
1652
1807
|
type: Input
|
|
1808
|
+
}], allowAdditionalProperties: [{
|
|
1809
|
+
type: Input
|
|
1653
1810
|
}], formReady: [{
|
|
1654
1811
|
type: Output
|
|
1655
1812
|
}], valueChange: [{
|
|
1656
1813
|
type: Output
|
|
1657
1814
|
}], schemaReady: [{
|
|
1658
1815
|
type: Output
|
|
1816
|
+
}], schemaErrors: [{
|
|
1817
|
+
type: Output
|
|
1659
1818
|
}] } });
|
|
1660
1819
|
|
|
1661
1820
|
class JsonSchemaFormModule {
|