@elle96/ng-json-schema-manager 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, ViewEncapsulation, Component, PLATFORM_ID, Inject, NgModule } from '@angular/core';
3
- import * as i4 from '@angular/forms';
4
- import { FormGroup, FormArray, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
2
+ import { Injectable, forwardRef, Input, Directive, EventEmitter, Output, Optional, ChangeDetectionStrategy, ViewEncapsulation, Component, PLATFORM_ID, Inject, NgModule } from '@angular/core';
3
+ import * as i5 from '@angular/forms';
4
+ import { FormGroup, FormArray, FormControl, Validators, NG_VALUE_ACCESSOR, FormsModule, ReactiveFormsModule } from '@angular/forms';
5
5
  import Ajv from 'ajv';
6
6
  import Ajv2019 from 'ajv/dist/2019';
7
7
  import Ajv2020 from 'ajv/dist/2020';
8
8
  import addFormats from 'ajv-formats';
9
9
  import AjvDraft04 from 'ajv-draft-04';
10
- import * as i3 from '@angular/common';
10
+ import * as i4 from '@angular/common';
11
11
  import { CommonModule, isPlatformBrowser } from '@angular/common';
12
12
 
13
13
  class JsonSchemaFormService {
@@ -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,20 +569,159 @@ 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',
579
579
  }]
580
580
  }] });
581
581
 
582
+ /**
583
+ * Options shared by every node of a form. Provided by `jsm-json-schema-form`, so each form instance has its own.
584
+ */
585
+ class JsonSchemaFormOptions {
586
+ decimalSeparator = '.';
587
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormOptions, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
588
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormOptions });
589
+ }
590
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormOptions, decorators: [{
591
+ type: Injectable
592
+ }] });
593
+ const COMPLETE_NUMBER = /^-?(\d+[.,]?\d*|[.,]\d+)$/;
594
+ /**
595
+ * Keeps digits, a leading minus and a single decimal separator (written with `separator`);
596
+ * `,` and `.` are both accepted as input.
597
+ */
598
+ function sanitizeNumericText(text, separator) {
599
+ let result = '';
600
+ let hasDecimal = false;
601
+ for (const char of text) {
602
+ if (char >= '0' && char <= '9') {
603
+ result += char;
604
+ }
605
+ else if (char === '-' && result === '') {
606
+ result += char;
607
+ }
608
+ else if ((char === ',' || char === '.') && !hasDecimal) {
609
+ hasDecimal = true;
610
+ result += separator;
611
+ }
612
+ }
613
+ return result;
614
+ }
615
+ /** Text → model: empty → `null`, a complete number → number, anything else stays a string (validation reports it). */
616
+ function parseNumericText(text) {
617
+ const trimmed = text.trim();
618
+ if (trimmed === '') {
619
+ return null;
620
+ }
621
+ if (COMPLETE_NUMBER.test(trimmed)) {
622
+ return Number(trimmed.replace(',', '.'));
623
+ }
624
+ return trimmed;
625
+ }
626
+ /** Model → text shown in the input. */
627
+ function formatNumeric(value, separator) {
628
+ if (value === null || value === undefined) {
629
+ return '';
630
+ }
631
+ return typeof value === 'number' ? String(value).replace('.', separator) : String(value);
632
+ }
633
+ /** Shows decimals with the configured separator inside messages ("must be >= 0.1" → "must be >= 0,1"). */
634
+ function localizeNumbers(message, separator) {
635
+ return separator === ',' ? message.replace(/(\d)\.(\d)/g, '$1,$2') : message;
636
+ }
637
+ const toParsed = (value) => (typeof value === 'string' ? parseNumericText(value) : (value ?? null));
638
+ /**
639
+ * `ControlValueAccessor` for `number`/`integer` fields. The input is a text field (`inputmode="decimal"`), so the
640
+ * decimal comma is accepted, and the model always holds a JSON number once the text is a complete number.
641
+ * It never writes to the control while rendering or when the text is unchanged.
642
+ */
643
+ class NumericInputDirective {
644
+ _el;
645
+ _renderer;
646
+ jsmDecimalSeparator = '.';
647
+ _model = null;
648
+ _onChange = () => undefined;
649
+ _onTouched = () => undefined;
650
+ constructor(_el, _renderer) {
651
+ this._el = _el;
652
+ this._renderer = _renderer;
653
+ }
654
+ _show(text) {
655
+ this._renderer.setProperty(this._el.nativeElement, 'value', text);
656
+ }
657
+ ngOnChanges(changes) {
658
+ if (changes['jsmDecimalSeparator'] && !changes['jsmDecimalSeparator'].firstChange && typeof this._model === 'number') {
659
+ this._show(formatNumeric(this._model, this.jsmDecimalSeparator));
660
+ }
661
+ }
662
+ writeValue(value) {
663
+ this._model = value ?? null;
664
+ this._show(formatNumeric(value, this.jsmDecimalSeparator));
665
+ }
666
+ registerOnChange(fn) {
667
+ this._onChange = fn;
668
+ }
669
+ registerOnTouched(fn) {
670
+ this._onTouched = fn;
671
+ }
672
+ setDisabledState(isDisabled) {
673
+ this._renderer.setProperty(this._el.nativeElement, 'disabled', isDisabled);
674
+ }
675
+ onInput() {
676
+ const input = this._el.nativeElement;
677
+ const raw = input.value;
678
+ const cleaned = sanitizeNumericText(raw, this.jsmDecimalSeparator);
679
+ if (cleaned !== raw) {
680
+ const caret = Math.max(0, (input.selectionStart ?? raw.length) - (raw.length - cleaned.length));
681
+ input.value = cleaned;
682
+ input.setSelectionRange(caret, caret);
683
+ }
684
+ this._model = parseNumericText(cleaned);
685
+ this._onChange(this._model);
686
+ }
687
+ onBlur() {
688
+ const input = this._el.nativeElement;
689
+ let text = input.value.trim();
690
+ if (text.endsWith(',') || text.endsWith('.')) {
691
+ text = text.slice(0, -1);
692
+ }
693
+ if (text === '-') {
694
+ text = '';
695
+ }
696
+ const parsed = parseNumericText(text);
697
+ this._show(typeof parsed === 'number' ? formatNumeric(parsed, this.jsmDecimalSeparator) : text);
698
+ // Update the model only when the value really changed ("13" and 13 are the same value).
699
+ if (parsed !== toParsed(this._model)) {
700
+ this._model = parsed;
701
+ this._onChange(parsed);
702
+ }
703
+ this._onTouched();
704
+ }
705
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NumericInputDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
706
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.18", type: NumericInputDirective, isStandalone: true, selector: "input[jsmNumeric]", inputs: { jsmDecimalSeparator: "jsmDecimalSeparator" }, host: { listeners: { "input": "onInput()", "blur": "onBlur()" } }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumericInputDirective), multi: true }], usesOnChanges: true, ngImport: i0 });
707
+ }
708
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NumericInputDirective, decorators: [{
709
+ type: Directive,
710
+ args: [{
711
+ selector: 'input[jsmNumeric]',
712
+ standalone: true,
713
+ host: { '(input)': 'onInput()', '(blur)': 'onBlur()' },
714
+ providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumericInputDirective), multi: true }],
715
+ }]
716
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { jsmDecimalSeparator: [{
717
+ type: Input
718
+ }] } });
719
+
582
720
  class JsonSchemaNodeComponent {
583
721
  schemaService;
584
722
  validation;
585
723
  cdr;
724
+ options;
586
725
  schema;
587
726
  control;
588
727
  label = '';
@@ -592,26 +731,37 @@ class JsonSchemaNodeComponent {
592
731
  parent;
593
732
  controlKey;
594
733
  allowAdditionalProperties = false;
734
+ /** When true, errors are shown regardless of touched/dirty state (e.g. triggered by an external save button). */
735
+ showErrors = false;
595
736
  controlReplaced = new EventEmitter();
596
737
  selectedOneOf = 0;
597
738
  selectedAnyOf = new Set();
598
739
  newPropertyKey = '';
599
740
  propertyError = '';
741
+ /** Tracks whether this primitive control has been touched (for OnPush CD). */
742
+ isTouched = false;
600
743
  valueSub;
601
- constructor(schemaService, validation, cdr) {
744
+ constructor(schemaService, validation, cdr, options) {
602
745
  this.schemaService = schemaService;
603
746
  this.validation = validation;
604
747
  this.cdr = cdr;
748
+ this.options = options;
605
749
  }
606
750
  ngOnInit() {
607
751
  if (this.schema.anyOf?.length)
608
752
  this.selectedAnyOf.add(0);
609
- // Subscribe to value changes to trigger CD for OnPush (needed for toggle visual state)
753
+ // markForCheck is enough for value changes — Angular will re-render in the next CD cycle
754
+ // without recreating DOM nodes, so the focused input keeps its focus.
610
755
  this.valueSub = this.control.valueChanges.subscribe(() => this.cdr.markForCheck());
611
756
  }
612
757
  ngOnDestroy() {
613
758
  this.valueSub?.unsubscribe();
614
759
  }
760
+ /** Called on blur of primitive inputs to trigger CD after Angular marks the control as touched. */
761
+ onBlur() {
762
+ this.isTouched = true;
763
+ this.cdr.markForCheck();
764
+ }
615
765
  isSimpleChild(key) {
616
766
  const s = this.schemaForKey(key);
617
767
  if (s.oneOf?.length || s.anyOf?.length || s.allOf?.length)
@@ -647,11 +797,20 @@ class JsonSchemaNodeComponent {
647
797
  get resolvedType() {
648
798
  return this.schemaService.resolveType(this.effectiveSchema) ?? 'string';
649
799
  }
800
+ get decimalSeparator() {
801
+ return this.options?.decimalSeparator ?? '.';
802
+ }
803
+ /** `multipleOf` of this node's own schema, exposed as step metadata. */
804
+ get numericStep() {
805
+ return this.effectiveSchema.multipleOf ? String(this.effectiveSchema.multipleOf) : 'any';
806
+ }
650
807
  get inputKind() {
651
808
  if (this.effectiveSchema.enum)
652
809
  return 'select';
653
810
  if (this.resolvedType === 'boolean')
654
811
  return 'checkbox';
812
+ if (this.resolvedType === 'number' || this.resolvedType === 'integer')
813
+ return 'number';
655
814
  if (this.effectiveSchema.format === 'textarea' || (this.effectiveSchema.maxLength ?? 0) > 200)
656
815
  return 'textarea';
657
816
  return 'text';
@@ -684,6 +843,46 @@ class JsonSchemaNodeComponent {
684
843
  return this.effectiveSchema.additionalProperties !== false || !!this.effectiveSchema.patternProperties;
685
844
  }
686
845
  get errorsForPath() { return this.errorsMap.get(this.path) ?? []; }
846
+ /** Combined errors: Ajv errors from errorsMap + Angular validator errors on the control. */
847
+ get allErrors() {
848
+ const ajvErrors = this.errorsForPath;
849
+ const ctrl = this.control;
850
+ if (!ctrl.errors)
851
+ return ajvErrors;
852
+ const angularErrors = [];
853
+ const errs = ctrl.errors;
854
+ if (errs['required'])
855
+ angularErrors.push('This field is required');
856
+ if (errs['minlength'])
857
+ angularErrors.push(`Minimum length is ${errs['minlength'].requiredLength}`);
858
+ if (errs['maxlength'])
859
+ angularErrors.push(`Maximum length is ${errs['maxlength'].requiredLength}`);
860
+ if (errs['min'])
861
+ angularErrors.push(`Minimum value is ${errs['min'].min}`);
862
+ if (errs['max'])
863
+ angularErrors.push(`Maximum value is ${errs['max'].max}`);
864
+ if (errs['email'])
865
+ angularErrors.push('Invalid email address');
866
+ if (errs['pattern'])
867
+ angularErrors.push('Value does not match the required pattern');
868
+ if (errs['integer'])
869
+ angularErrors.push('Value must be an integer');
870
+ if (errs['exclusiveMinimum'])
871
+ angularErrors.push('Value must be greater than the minimum');
872
+ if (errs['exclusiveMaximum'])
873
+ angularErrors.push('Value must be less than the maximum');
874
+ if (errs['multipleOf'])
875
+ angularErrors.push('Value must be a multiple of the required number');
876
+ if (errs['minItems'])
877
+ angularErrors.push(`Minimum ${errs['minItems'].requiredLength ?? ''} items required`);
878
+ if (errs['maxItems'])
879
+ angularErrors.push(`Maximum ${errs['maxItems'].requiredLength ?? ''} items allowed`);
880
+ if (errs['uniqueItems'])
881
+ angularErrors.push('Items must be unique');
882
+ // Merge: prefer Ajv messages, fall back to Angular messages for errors not covered by Ajv
883
+ const messages = ajvErrors.length > 0 ? ajvErrors : angularErrors;
884
+ return messages.map((message) => localizeNumbers(message, this.decimalSeparator));
885
+ }
687
886
  get metaBadges() {
688
887
  const b = [];
689
888
  if (this.effectiveSchema.deprecated)
@@ -861,499 +1060,521 @@ class JsonSchemaNodeComponent {
861
1060
  } : {}),
862
1061
  }), {});
863
1062
  }
864
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaValidationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
865
- 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: `
866
- <ng-container [ngSwitch]="nodeKind">
867
-
868
- <!-- ═══ oneOf ═══ -->
869
- <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
870
- <div class="jsm-combinator-header">
871
- <div>
872
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
873
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
874
- </div>
875
- <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
876
- </div>
877
- <div class="jsm-oneof-tabs">
878
- <button *ngFor="let option of schema.oneOf; let i = index" type="button"
879
- class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
880
- (click)="selectedOneOf = i; onOneOfChange()">
881
- {{ option.title || ('Option ' + (i + 1)) }}
882
- </button>
883
- </div>
884
- <div class="jsm-combinator-body" *ngIf="activeVariant">
885
- <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
886
- [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
887
- [allowAdditionalProperties]="allowAdditionalProperties"
888
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
889
- </div>
890
- </div>
891
-
892
- <!-- ═══ anyOf ═══ -->
893
- <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
894
- <div class="jsm-combinator-header">
895
- <div>
896
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
897
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
898
- </div>
899
- <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
900
- </div>
901
- <div class="jsm-anyof-options">
902
- <label *ngFor="let option of schema.anyOf; let i = index"
903
- class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
904
- <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
905
- {{ option.title || ('Option ' + (i + 1)) }}
906
- </label>
907
- </div>
908
- <div class="jsm-combinator-body" *ngIf="activeVariant">
909
- <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
910
- [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
911
- [allowAdditionalProperties]="allowAdditionalProperties"
912
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
913
- </div>
914
- </div>
915
-
916
- <!-- ═══ allOf ═══ -->
917
- <div *ngSwitchCase="'allOf'" class="jsm-combinator">
918
- <div class="jsm-combinator-header">
919
- <div>
920
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
921
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
922
- </div>
923
- <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
924
- </div>
925
- <div class="jsm-combinator-body">
926
- <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
927
- <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
928
- [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
929
- [allowAdditionalProperties]="allowAdditionalProperties"
930
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
931
- </div>
932
- </div>
933
- </div>
934
-
935
- <!-- ═══ object ═══ -->
936
- <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
937
- <legend class="jsm-legend">{{ schema.title || label }}</legend>
938
- <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
939
-
940
- <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
941
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
942
- </div>
943
-
944
- <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
945
- <ng-container *ngFor="let key of simpleKeys">
946
- <div style="display:flex;flex-direction:column;gap:.25rem;">
947
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
948
- <span class="jsm-dynamic-key-label">{{ key }}</span>
949
- <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
950
- </div>
951
- <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
952
- [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
953
- [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties"
954
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
955
- </div>
956
- </ng-container>
957
- </div>
958
-
959
- <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
960
- <ng-container *ngFor="let key of complexKeys">
961
- <div style="display:flex;flex-direction:column;gap:.25rem;">
962
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
963
- <span class="jsm-dynamic-key-label">{{ key }}</span>
964
- <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
965
- </div>
966
- <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
967
- [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
968
- [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties"
969
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
970
- </div>
971
- </ng-container>
972
- </div>
973
-
974
- <div *ngIf="canAddProperty" class="jsm-add-prop">
975
- <p class="jsm-add-prop-label">Add property</p>
976
- <div class="jsm-add-prop-row">
977
- <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" placeholder="propertyName" />
978
- <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">Add</button>
979
- </div>
980
- <p *ngIf="propertyError" class="jsm-error-msg">
981
- <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>
982
- {{ propertyError }}
983
- </p>
984
- </div>
985
-
986
- <div *ngIf="errorsForPath.length" class="jsm-error-list" style="margin-top:.75rem;">
987
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
988
- <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>
989
- {{ error }}
990
- </p>
991
- </div>
992
- </fieldset>
993
-
994
- <!-- ═══ array ═══ -->
995
- <div *ngSwitchCase="'array'" class="jsm-array-block">
996
- <div class="jsm-array-header">
997
- <div>
998
- <p class="jsm-array-title">
999
- {{ schema.title || label }}<span *ngIf="required" style="color:#ef4444;margin-left:.125rem;">*</span>
1000
- </p>
1001
- <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1002
- </div>
1003
- <button type="button" class="jsm-btn-primary" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1004
- <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>
1005
- Add item
1006
- </button>
1007
- </div>
1008
-
1009
- <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1010
- Must contain matching item(s)
1011
- <span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
1012
- <span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
1013
- </div>
1014
-
1015
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1016
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1017
- </div>
1018
-
1019
- <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1020
- <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item">
1021
- <div class="jsm-array-item-header">
1022
- <span class="jsm-array-index">{{ i + 1 }}</span>
1023
- <button type="button" class="jsm-btn-danger" (click)="removeArrayItem(i)">Remove</button>
1024
- </div>
1025
- <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1026
- [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1027
- [allowAdditionalProperties]="allowAdditionalProperties"
1028
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1029
- </div>
1030
- </div>
1031
-
1032
- <ng-template #emptyArray>
1033
- <div class="jsm-empty-array">No items yet — click "Add item" to start.</div>
1034
- </ng-template>
1035
-
1036
- <div *ngIf="errorsForPath.length" class="jsm-error-list">
1037
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1038
- <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>
1039
- {{ error }}
1040
- </p>
1041
- </div>
1042
- </div>
1043
-
1044
- <!-- ═══ primitive ═══ -->
1045
- <div *ngSwitchDefault class="jsm-field">
1046
- <ng-container *ngIf="inputKind !== 'checkbox'">
1047
- <label class="jsm-field-label">
1048
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1049
- </label>
1050
- <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1051
- </ng-container>
1052
-
1053
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1054
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1055
- </div>
1056
-
1057
- <ng-container [ngSwitch]="inputKind">
1058
- <input *ngSwitchCase="'text'" class="jsm-input"
1059
- [class.jsm-input--error]="errorsForPath.length && controlAsFormControl.dirty"
1060
- [attr.type]="inputType" [attr.placeholder]="schema.title || label"
1061
- [formControl]="controlAsFormControl" />
1062
-
1063
- <textarea *ngSwitchCase="'textarea'" rows="4" class="jsm-textarea"
1064
- [class.jsm-textarea--error]="errorsForPath.length && controlAsFormControl.dirty"
1065
- [attr.placeholder]="schema.title || label"
1066
- [formControl]="controlAsFormControl"></textarea>
1067
-
1068
- <div *ngSwitchCase="'select'" class="jsm-select-wrap">
1069
- <select class="jsm-select"
1070
- [class.jsm-select--error]="errorsForPath.length && controlAsFormControl.dirty"
1071
- [formControl]="controlAsFormControl">
1072
- <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1073
- </select>
1074
- <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none">
1075
- <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1076
- </svg>
1077
- </div>
1078
-
1079
- <label *ngSwitchCase="'checkbox'" class="jsm-toggle-label">
1080
- <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1081
- <input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
1082
- <span class="jsm-toggle-thumb"></span>
1083
- </span>
1084
- <span class="jsm-toggle-text">
1085
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1086
- </span>
1087
- </label>
1088
- </ng-container>
1089
-
1090
- <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1091
- <p *ngIf="effectiveSchema.examples?.length" class="jsm-example">e.g. {{ effectiveSchema.examples?.[0] }}</p>
1092
- <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example">
1093
- {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1094
- </p>
1095
-
1096
- <div *ngIf="errorsForPath.length && controlAsFormControl.dirty" class="jsm-error-list">
1097
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1098
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none">
1099
- <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1100
- <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1101
- </svg>
1102
- {{ error }}
1103
- </p>
1104
- </div>
1105
- </div>
1106
-
1107
- </ng-container>
1108
- `, 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"], 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 });
1063
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaNodeComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaValidationService }, { token: i0.ChangeDetectorRef }, { token: JsonSchemaFormOptions, optional: true }], target: i0.ɵɵFactoryTarget.Component });
1064
+ 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: `
1065
+ <ng-container [ngSwitch]="nodeKind">
1066
+
1067
+ <!-- ═══ oneOf ═══ -->
1068
+ <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
1069
+ <div class="jsm-combinator-header">
1070
+ <div>
1071
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1072
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1073
+ </div>
1074
+ <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1075
+ </div>
1076
+ <div class="jsm-oneof-tabs">
1077
+ <button *ngFor="let option of schema.oneOf; let i = index" type="button"
1078
+ class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
1079
+ (click)="selectedOneOf = i; onOneOfChange()">
1080
+ {{ option.title || ('Option ' + (i + 1)) }}
1081
+ </button>
1082
+ </div>
1083
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
1084
+ <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1085
+ [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1086
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1087
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1088
+ </div>
1089
+ </div>
1090
+
1091
+ <!-- ═══ anyOf ═══ -->
1092
+ <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
1093
+ <div class="jsm-combinator-header">
1094
+ <div>
1095
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1096
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1097
+ </div>
1098
+ <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1099
+ </div>
1100
+ <div class="jsm-anyof-options">
1101
+ <label *ngFor="let option of schema.anyOf; let i = index"
1102
+ class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
1103
+ <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1104
+ {{ option.title || ('Option ' + (i + 1)) }}
1105
+ </label>
1106
+ </div>
1107
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
1108
+ <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1109
+ [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1110
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1111
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1112
+ </div>
1113
+ </div>
1114
+
1115
+ <!-- ═══ allOf ═══ -->
1116
+ <div *ngSwitchCase="'allOf'" class="jsm-combinator">
1117
+ <div class="jsm-combinator-header">
1118
+ <div>
1119
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1120
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1121
+ </div>
1122
+ <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1123
+ </div>
1124
+ <div class="jsm-combinator-body">
1125
+ <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
1126
+ <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
1127
+ [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
1128
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1129
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1130
+ </div>
1131
+ </div>
1132
+ </div>
1133
+
1134
+ <!-- ═══ object ═══ -->
1135
+ <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
1136
+ <legend class="jsm-legend">{{ schema.title || label }}</legend>
1137
+ <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1138
+
1139
+ <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
1140
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1141
+ </div>
1142
+
1143
+ <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1144
+ <ng-container *ngFor="let key of simpleKeys">
1145
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1146
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1147
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1148
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1149
+ </div>
1150
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1151
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1152
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1153
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1154
+ </div>
1155
+ </ng-container>
1156
+ </div>
1157
+
1158
+ <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1159
+ <ng-container *ngFor="let key of complexKeys">
1160
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1161
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1162
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1163
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1164
+ </div>
1165
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1166
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1167
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1168
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1169
+ </div>
1170
+ </ng-container>
1171
+ </div>
1172
+
1173
+ <div *ngIf="canAddProperty" class="jsm-add-prop">
1174
+ <p class="jsm-add-prop-label">Add property</p>
1175
+ <div class="jsm-add-prop-row">
1176
+ <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1177
+ <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">Add</button>
1178
+ </div>
1179
+ <p *ngIf="propertyError" class="jsm-error-msg">
1180
+ <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>
1181
+ {{ propertyError }}
1182
+ </p>
1183
+ </div>
1184
+
1185
+ <div *ngIf="errorsForPath.length" class="jsm-error-list" style="margin-top:.75rem;">
1186
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1187
+ <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>
1188
+ {{ error }}
1189
+ </p>
1190
+ </div>
1191
+ </fieldset>
1192
+
1193
+ <!-- ═══ array ═══ -->
1194
+ <div *ngSwitchCase="'array'" class="jsm-array-block">
1195
+ <div class="jsm-array-header">
1196
+ <div>
1197
+ <p class="jsm-array-title">
1198
+ {{ schema.title || label }}<span *ngIf="required" style="color:#ef4444;margin-left:.125rem;">*</span>
1199
+ </p>
1200
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1201
+ </div>
1202
+ <button type="button" class="jsm-btn-primary" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1203
+ <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>
1204
+ Add item
1205
+ </button>
1206
+ </div>
1207
+
1208
+ <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1209
+ Must contain matching item(s)
1210
+ <span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
1211
+ <span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
1212
+ </div>
1213
+
1214
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1215
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1216
+ </div>
1217
+
1218
+ <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1219
+ <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item">
1220
+ <div class="jsm-array-item-header">
1221
+ <span class="jsm-array-index">{{ i + 1 }}</span>
1222
+ <button type="button" class="jsm-btn-danger" (click)="removeArrayItem(i)">Remove</button>
1223
+ </div>
1224
+ <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1225
+ [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1226
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1227
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1228
+ </div>
1229
+ </div>
1230
+
1231
+ <ng-template #emptyArray>
1232
+ <div class="jsm-empty-array">No items yet — click "Add item" to start.</div>
1233
+ </ng-template>
1234
+
1235
+ <div *ngIf="errorsForPath.length" class="jsm-error-list">
1236
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1237
+ <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>
1238
+ {{ error }}
1239
+ </p>
1240
+ </div>
1241
+ </div>
1242
+
1243
+ <!-- ═══ primitive ═══ -->
1244
+ <div *ngSwitchDefault class="jsm-field">
1245
+ <ng-container *ngIf="inputKind !== 'checkbox'">
1246
+ <label class="jsm-field-label">
1247
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1248
+ </label>
1249
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1250
+ </ng-container>
1251
+
1252
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1253
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1254
+ </div>
1255
+
1256
+ <ng-container>
1257
+ <input *ngIf="inputKind === 'number'" jsmNumeric type="text" inputmode="decimal" class="jsm-input"
1258
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1259
+ [jsmDecimalSeparator]="decimalSeparator" [attr.data-step]="numericStep"
1260
+ [attr.placeholder]="schema.title || label"
1261
+ [formControl]="controlAsFormControl"
1262
+ (blur)="onBlur()" />
1263
+
1264
+ <input *ngIf="inputKind === 'text'" class="jsm-input"
1265
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1266
+ [attr.type]="inputType" [attr.placeholder]="schema.title || label"
1267
+ [formControl]="controlAsFormControl"
1268
+ (blur)="onBlur()" />
1269
+
1270
+ <textarea *ngIf="inputKind === 'textarea'" rows="4" class="jsm-textarea"
1271
+ [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1272
+ [attr.placeholder]="schema.title || label"
1273
+ [formControl]="controlAsFormControl"
1274
+ (blur)="onBlur()"></textarea>
1275
+
1276
+ <div *ngIf="inputKind === 'select'" class="jsm-select-wrap">
1277
+ <select class="jsm-select"
1278
+ [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1279
+ [formControl]="controlAsFormControl"
1280
+ (blur)="onBlur()">
1281
+ <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1282
+ </select>
1283
+ <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none">
1284
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1285
+ </svg>
1286
+ </div>
1287
+
1288
+ <label *ngIf="inputKind === 'checkbox'" class="jsm-toggle-label">
1289
+ <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1290
+ <input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
1291
+ <span class="jsm-toggle-thumb"></span>
1292
+ </span>
1293
+ <span class="jsm-toggle-text">
1294
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1295
+ </span>
1296
+ </label>
1297
+ </ng-container>
1298
+
1299
+ <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1300
+ <p *ngIf="effectiveSchema.examples?.length" class="jsm-example">e.g. {{ effectiveSchema.examples?.[0] }}</p>
1301
+ <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example">
1302
+ {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1303
+ </p>
1304
+
1305
+ <div *ngIf="allErrors.length && (controlAsFormControl.dirty || isTouched || showErrors)" class="jsm-error-list">
1306
+ <p *ngFor="let error of allErrors" class="jsm-error-msg">
1307
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none">
1308
+ <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1309
+ <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1310
+ </svg>
1311
+ {{ error }}
1312
+ </p>
1313
+ </div>
1314
+ </div>
1315
+
1316
+ </ng-container>
1317
+ `, 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: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i4.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i4.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i5.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i5.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i5.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: i5.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i5.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i5.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NumericInputDirective, selector: "input[jsmNumeric]", inputs: ["jsmDecimalSeparator"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1109
1318
  }
1110
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
1319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
1111
1320
  type: Component,
1112
- args: [{ selector: 'jsm-schema-node', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule], encapsulation: ViewEncapsulation.None, template: `
1113
- <ng-container [ngSwitch]="nodeKind">
1114
-
1115
- <!-- ═══ oneOf ═══ -->
1116
- <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
1117
- <div class="jsm-combinator-header">
1118
- <div>
1119
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1120
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1121
- </div>
1122
- <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1123
- </div>
1124
- <div class="jsm-oneof-tabs">
1125
- <button *ngFor="let option of schema.oneOf; let i = index" type="button"
1126
- class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
1127
- (click)="selectedOneOf = i; onOneOfChange()">
1128
- {{ option.title || ('Option ' + (i + 1)) }}
1129
- </button>
1130
- </div>
1131
- <div class="jsm-combinator-body" *ngIf="activeVariant">
1132
- <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1133
- [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1134
- [allowAdditionalProperties]="allowAdditionalProperties"
1135
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1136
- </div>
1137
- </div>
1138
-
1139
- <!-- ═══ anyOf ═══ -->
1140
- <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
1141
- <div class="jsm-combinator-header">
1142
- <div>
1143
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1144
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1145
- </div>
1146
- <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1147
- </div>
1148
- <div class="jsm-anyof-options">
1149
- <label *ngFor="let option of schema.anyOf; let i = index"
1150
- class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
1151
- <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1152
- {{ option.title || ('Option ' + (i + 1)) }}
1153
- </label>
1154
- </div>
1155
- <div class="jsm-combinator-body" *ngIf="activeVariant">
1156
- <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1157
- [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1158
- [allowAdditionalProperties]="allowAdditionalProperties"
1159
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1160
- </div>
1161
- </div>
1162
-
1163
- <!-- ═══ allOf ═══ -->
1164
- <div *ngSwitchCase="'allOf'" class="jsm-combinator">
1165
- <div class="jsm-combinator-header">
1166
- <div>
1167
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1168
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1169
- </div>
1170
- <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1171
- </div>
1172
- <div class="jsm-combinator-body">
1173
- <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
1174
- <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
1175
- [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
1176
- [allowAdditionalProperties]="allowAdditionalProperties"
1177
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1178
- </div>
1179
- </div>
1180
- </div>
1181
-
1182
- <!-- ═══ object ═══ -->
1183
- <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
1184
- <legend class="jsm-legend">{{ schema.title || label }}</legend>
1185
- <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1186
-
1187
- <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
1188
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1189
- </div>
1190
-
1191
- <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1192
- <ng-container *ngFor="let key of simpleKeys">
1193
- <div style="display:flex;flex-direction:column;gap:.25rem;">
1194
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1195
- <span class="jsm-dynamic-key-label">{{ key }}</span>
1196
- <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1197
- </div>
1198
- <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1199
- [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1200
- [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties"
1201
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1202
- </div>
1203
- </ng-container>
1204
- </div>
1205
-
1206
- <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1207
- <ng-container *ngFor="let key of complexKeys">
1208
- <div style="display:flex;flex-direction:column;gap:.25rem;">
1209
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1210
- <span class="jsm-dynamic-key-label">{{ key }}</span>
1211
- <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1212
- </div>
1213
- <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1214
- [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1215
- [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties"
1216
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1217
- </div>
1218
- </ng-container>
1219
- </div>
1220
-
1221
- <div *ngIf="canAddProperty" class="jsm-add-prop">
1222
- <p class="jsm-add-prop-label">Add property</p>
1223
- <div class="jsm-add-prop-row">
1224
- <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1225
- <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">Add</button>
1226
- </div>
1227
- <p *ngIf="propertyError" class="jsm-error-msg">
1228
- <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>
1229
- {{ propertyError }}
1230
- </p>
1231
- </div>
1232
-
1233
- <div *ngIf="errorsForPath.length" class="jsm-error-list" style="margin-top:.75rem;">
1234
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1235
- <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>
1236
- {{ error }}
1237
- </p>
1238
- </div>
1239
- </fieldset>
1240
-
1241
- <!-- ═══ array ═══ -->
1242
- <div *ngSwitchCase="'array'" class="jsm-array-block">
1243
- <div class="jsm-array-header">
1244
- <div>
1245
- <p class="jsm-array-title">
1246
- {{ schema.title || label }}<span *ngIf="required" style="color:#ef4444;margin-left:.125rem;">*</span>
1247
- </p>
1248
- <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1249
- </div>
1250
- <button type="button" class="jsm-btn-primary" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1251
- <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>
1252
- Add item
1253
- </button>
1254
- </div>
1255
-
1256
- <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1257
- Must contain matching item(s)
1258
- <span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
1259
- <span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
1260
- </div>
1261
-
1262
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1263
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1264
- </div>
1265
-
1266
- <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1267
- <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item">
1268
- <div class="jsm-array-item-header">
1269
- <span class="jsm-array-index">{{ i + 1 }}</span>
1270
- <button type="button" class="jsm-btn-danger" (click)="removeArrayItem(i)">Remove</button>
1271
- </div>
1272
- <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1273
- [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1274
- [allowAdditionalProperties]="allowAdditionalProperties"
1275
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1276
- </div>
1277
- </div>
1278
-
1279
- <ng-template #emptyArray>
1280
- <div class="jsm-empty-array">No items yet — click "Add item" to start.</div>
1281
- </ng-template>
1282
-
1283
- <div *ngIf="errorsForPath.length" class="jsm-error-list">
1284
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1285
- <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>
1286
- {{ error }}
1287
- </p>
1288
- </div>
1289
- </div>
1290
-
1291
- <!-- ═══ primitive ═══ -->
1292
- <div *ngSwitchDefault class="jsm-field">
1293
- <ng-container *ngIf="inputKind !== 'checkbox'">
1294
- <label class="jsm-field-label">
1295
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1296
- </label>
1297
- <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1298
- </ng-container>
1299
-
1300
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1301
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1302
- </div>
1303
-
1304
- <ng-container [ngSwitch]="inputKind">
1305
- <input *ngSwitchCase="'text'" class="jsm-input"
1306
- [class.jsm-input--error]="errorsForPath.length && controlAsFormControl.dirty"
1307
- [attr.type]="inputType" [attr.placeholder]="schema.title || label"
1308
- [formControl]="controlAsFormControl" />
1309
-
1310
- <textarea *ngSwitchCase="'textarea'" rows="4" class="jsm-textarea"
1311
- [class.jsm-textarea--error]="errorsForPath.length && controlAsFormControl.dirty"
1312
- [attr.placeholder]="schema.title || label"
1313
- [formControl]="controlAsFormControl"></textarea>
1314
-
1315
- <div *ngSwitchCase="'select'" class="jsm-select-wrap">
1316
- <select class="jsm-select"
1317
- [class.jsm-select--error]="errorsForPath.length && controlAsFormControl.dirty"
1318
- [formControl]="controlAsFormControl">
1319
- <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1320
- </select>
1321
- <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none">
1322
- <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1323
- </svg>
1324
- </div>
1325
-
1326
- <label *ngSwitchCase="'checkbox'" class="jsm-toggle-label">
1327
- <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1328
- <input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
1329
- <span class="jsm-toggle-thumb"></span>
1330
- </span>
1331
- <span class="jsm-toggle-text">
1332
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1333
- </span>
1334
- </label>
1335
- </ng-container>
1336
-
1337
- <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1338
- <p *ngIf="effectiveSchema.examples?.length" class="jsm-example">e.g. {{ effectiveSchema.examples?.[0] }}</p>
1339
- <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example">
1340
- {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1341
- </p>
1342
-
1343
- <div *ngIf="errorsForPath.length && controlAsFormControl.dirty" class="jsm-error-list">
1344
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1345
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none">
1346
- <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1347
- <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1348
- </svg>
1349
- {{ error }}
1350
- </p>
1351
- </div>
1352
- </div>
1353
-
1354
- </ng-container>
1321
+ args: [{ selector: 'jsm-schema-node', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, NumericInputDirective], encapsulation: ViewEncapsulation.None, template: `
1322
+ <ng-container [ngSwitch]="nodeKind">
1323
+
1324
+ <!-- ═══ oneOf ═══ -->
1325
+ <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
1326
+ <div class="jsm-combinator-header">
1327
+ <div>
1328
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1329
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1330
+ </div>
1331
+ <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1332
+ </div>
1333
+ <div class="jsm-oneof-tabs">
1334
+ <button *ngFor="let option of schema.oneOf; let i = index" type="button"
1335
+ class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
1336
+ (click)="selectedOneOf = i; onOneOfChange()">
1337
+ {{ option.title || ('Option ' + (i + 1)) }}
1338
+ </button>
1339
+ </div>
1340
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
1341
+ <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1342
+ [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1343
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1344
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1345
+ </div>
1346
+ </div>
1347
+
1348
+ <!-- ═══ anyOf ═══ -->
1349
+ <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
1350
+ <div class="jsm-combinator-header">
1351
+ <div>
1352
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1353
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1354
+ </div>
1355
+ <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1356
+ </div>
1357
+ <div class="jsm-anyof-options">
1358
+ <label *ngFor="let option of schema.anyOf; let i = index"
1359
+ class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
1360
+ <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1361
+ {{ option.title || ('Option ' + (i + 1)) }}
1362
+ </label>
1363
+ </div>
1364
+ <div class="jsm-combinator-body" *ngIf="activeVariant">
1365
+ <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1366
+ [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1367
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1368
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1369
+ </div>
1370
+ </div>
1371
+
1372
+ <!-- ═══ allOf ═══ -->
1373
+ <div *ngSwitchCase="'allOf'" class="jsm-combinator">
1374
+ <div class="jsm-combinator-header">
1375
+ <div>
1376
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1377
+ <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1378
+ </div>
1379
+ <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1380
+ </div>
1381
+ <div class="jsm-combinator-body">
1382
+ <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
1383
+ <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
1384
+ [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || ('Section ' + (i + 1))" [required]="required"
1385
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1386
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1387
+ </div>
1388
+ </div>
1389
+ </div>
1390
+
1391
+ <!-- ═══ object ═══ -->
1392
+ <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
1393
+ <legend class="jsm-legend">{{ schema.title || label }}</legend>
1394
+ <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1395
+
1396
+ <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
1397
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1398
+ </div>
1399
+
1400
+ <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1401
+ <ng-container *ngFor="let key of simpleKeys">
1402
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1403
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1404
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1405
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1406
+ </div>
1407
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1408
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1409
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1410
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1411
+ </div>
1412
+ </ng-container>
1413
+ </div>
1414
+
1415
+ <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1416
+ <ng-container *ngFor="let key of complexKeys">
1417
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1418
+ <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1419
+ <span class="jsm-dynamic-key-label">{{ key }}</span>
1420
+ <button type="button" class="jsm-btn-danger" (click)="removeDynamicKey(key)">Remove</button>
1421
+ </div>
1422
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1423
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1424
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1425
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1426
+ </div>
1427
+ </ng-container>
1428
+ </div>
1429
+
1430
+ <div *ngIf="canAddProperty" class="jsm-add-prop">
1431
+ <p class="jsm-add-prop-label">Add property</p>
1432
+ <div class="jsm-add-prop-row">
1433
+ <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" placeholder="propertyName" />
1434
+ <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">Add</button>
1435
+ </div>
1436
+ <p *ngIf="propertyError" class="jsm-error-msg">
1437
+ <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>
1438
+ {{ propertyError }}
1439
+ </p>
1440
+ </div>
1441
+
1442
+ <div *ngIf="errorsForPath.length" class="jsm-error-list" style="margin-top:.75rem;">
1443
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1444
+ <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>
1445
+ {{ error }}
1446
+ </p>
1447
+ </div>
1448
+ </fieldset>
1449
+
1450
+ <!-- ═══ array ═══ -->
1451
+ <div *ngSwitchCase="'array'" class="jsm-array-block">
1452
+ <div class="jsm-array-header">
1453
+ <div>
1454
+ <p class="jsm-array-title">
1455
+ {{ schema.title || label }}<span *ngIf="required" style="color:#ef4444;margin-left:.125rem;">*</span>
1456
+ </p>
1457
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1458
+ </div>
1459
+ <button type="button" class="jsm-btn-primary" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1460
+ <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>
1461
+ Add item
1462
+ </button>
1463
+ </div>
1464
+
1465
+ <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1466
+ Must contain matching item(s)
1467
+ <span *ngIf="effectiveSchema.minContains"> · min {{ effectiveSchema.minContains }}</span>
1468
+ <span *ngIf="effectiveSchema.maxContains"> · max {{ effectiveSchema.maxContains }}</span>
1469
+ </div>
1470
+
1471
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1472
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1473
+ </div>
1474
+
1475
+ <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1476
+ <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item">
1477
+ <div class="jsm-array-item-header">
1478
+ <span class="jsm-array-index">{{ i + 1 }}</span>
1479
+ <button type="button" class="jsm-btn-danger" (click)="removeArrayItem(i)">Remove</button>
1480
+ </div>
1481
+ <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1482
+ [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1483
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1484
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1485
+ </div>
1486
+ </div>
1487
+
1488
+ <ng-template #emptyArray>
1489
+ <div class="jsm-empty-array">No items yet — click "Add item" to start.</div>
1490
+ </ng-template>
1491
+
1492
+ <div *ngIf="errorsForPath.length" class="jsm-error-list">
1493
+ <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1494
+ <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>
1495
+ {{ error }}
1496
+ </p>
1497
+ </div>
1498
+ </div>
1499
+
1500
+ <!-- ═══ primitive ═══ -->
1501
+ <div *ngSwitchDefault class="jsm-field">
1502
+ <ng-container *ngIf="inputKind !== 'checkbox'">
1503
+ <label class="jsm-field-label">
1504
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1505
+ </label>
1506
+ <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1507
+ </ng-container>
1508
+
1509
+ <div *ngIf="metaBadges.length" class="jsm-badge-row">
1510
+ <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1511
+ </div>
1512
+
1513
+ <ng-container>
1514
+ <input *ngIf="inputKind === 'number'" jsmNumeric type="text" inputmode="decimal" class="jsm-input"
1515
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1516
+ [jsmDecimalSeparator]="decimalSeparator" [attr.data-step]="numericStep"
1517
+ [attr.placeholder]="schema.title || label"
1518
+ [formControl]="controlAsFormControl"
1519
+ (blur)="onBlur()" />
1520
+
1521
+ <input *ngIf="inputKind === 'text'" class="jsm-input"
1522
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1523
+ [attr.type]="inputType" [attr.placeholder]="schema.title || label"
1524
+ [formControl]="controlAsFormControl"
1525
+ (blur)="onBlur()" />
1526
+
1527
+ <textarea *ngIf="inputKind === 'textarea'" rows="4" class="jsm-textarea"
1528
+ [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1529
+ [attr.placeholder]="schema.title || label"
1530
+ [formControl]="controlAsFormControl"
1531
+ (blur)="onBlur()"></textarea>
1532
+
1533
+ <div *ngIf="inputKind === 'select'" class="jsm-select-wrap">
1534
+ <select class="jsm-select"
1535
+ [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1536
+ [formControl]="controlAsFormControl"
1537
+ (blur)="onBlur()">
1538
+ <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1539
+ </select>
1540
+ <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none">
1541
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1542
+ </svg>
1543
+ </div>
1544
+
1545
+ <label *ngIf="inputKind === 'checkbox'" class="jsm-toggle-label">
1546
+ <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1547
+ <input type="checkbox" class="sr-only" [formControl]="controlAsFormControl" />
1548
+ <span class="jsm-toggle-thumb"></span>
1549
+ </span>
1550
+ <span class="jsm-toggle-text">
1551
+ {{ schema.title || label }}<span *ngIf="required" class="jsm-required">*</span>
1552
+ </span>
1553
+ </label>
1554
+ </ng-container>
1555
+
1556
+ <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1557
+ <p *ngIf="effectiveSchema.examples?.length" class="jsm-example">e.g. {{ effectiveSchema.examples?.[0] }}</p>
1558
+ <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example">
1559
+ {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1560
+ </p>
1561
+
1562
+ <div *ngIf="allErrors.length && (controlAsFormControl.dirty || isTouched || showErrors)" class="jsm-error-list">
1563
+ <p *ngFor="let error of allErrors" class="jsm-error-msg">
1564
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none">
1565
+ <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1566
+ <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1567
+ </svg>
1568
+ {{ error }}
1569
+ </p>
1570
+ </div>
1571
+ </div>
1572
+
1573
+ </ng-container>
1355
1574
  `, 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"] }]
1356
- }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }], propDecorators: { schema: [{
1575
+ }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }, { type: JsonSchemaFormOptions, decorators: [{
1576
+ type: Optional
1577
+ }] }], propDecorators: { schema: [{
1357
1578
  type: Input,
1358
1579
  args: [{ required: true }]
1359
1580
  }], control: [{
@@ -1373,10 +1594,145 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1373
1594
  type: Input
1374
1595
  }], allowAdditionalProperties: [{
1375
1596
  type: Input
1597
+ }], showErrors: [{
1598
+ type: Input
1376
1599
  }], controlReplaced: [{
1377
1600
  type: Output
1378
1601
  }] } });
1379
1602
 
1603
+ const OMIT = Symbol('omit');
1604
+ /** Complete decimal numbers only: "13", "-0.5", "0,5", "3," (comma or dot as separator). */
1605
+ const NUMERIC_STRING = /^[+-]?(\d+([.,]\d*)?|[.,]\d+)$/;
1606
+ const isSchema = (schema) => !!schema && typeof schema === 'object';
1607
+ const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
1608
+ const primaryType = (schema) => {
1609
+ const type = schema.type;
1610
+ if (Array.isArray(type)) {
1611
+ return type.find((item) => item !== 'null') ?? type[0];
1612
+ }
1613
+ return type;
1614
+ };
1615
+ const isNumericType = (type) => type === 'number' || type === 'integer';
1616
+ /** Converts a numeric string to a number; empty values are omitted; anything else is left as typed. */
1617
+ function normalizeNumber(value) {
1618
+ if (value === null || value === undefined) {
1619
+ return OMIT;
1620
+ }
1621
+ if (typeof value !== 'string') {
1622
+ return value;
1623
+ }
1624
+ const text = value.trim();
1625
+ if (text === '') {
1626
+ return OMIT;
1627
+ }
1628
+ if (NUMERIC_STRING.test(text)) {
1629
+ return Number(text.replace(',', '.'));
1630
+ }
1631
+ return value;
1632
+ }
1633
+ /** Picks the oneOf/anyOf variant that declares the most of the object's keys. */
1634
+ function pickVariant(variants, value) {
1635
+ let best;
1636
+ let bestScore = 0;
1637
+ for (const variant of variants) {
1638
+ const declared = Object.keys(variant.properties ?? {});
1639
+ const score = declared.filter((key) => key in value).length;
1640
+ if (score > bestScore) {
1641
+ best = variant;
1642
+ bestScore = score;
1643
+ }
1644
+ }
1645
+ return best;
1646
+ }
1647
+ function normalizeObject(schema, value) {
1648
+ const result = { ...value };
1649
+ const patterns = Object.entries(schema.patternProperties ?? {}).map(([pattern, sub]) => [new RegExp(pattern), sub]);
1650
+ for (const key of Object.keys(result)) {
1651
+ let sub = schema.properties?.[key];
1652
+ if (sub === undefined) {
1653
+ sub = patterns.find(([regex]) => regex.test(key))?.[1];
1654
+ }
1655
+ if (sub === undefined && isSchema(schema.additionalProperties)) {
1656
+ sub = schema.additionalProperties;
1657
+ }
1658
+ if (sub === undefined) {
1659
+ continue;
1660
+ }
1661
+ const next = normalizeNode(sub, result[key]);
1662
+ if (next === OMIT) {
1663
+ delete result[key];
1664
+ }
1665
+ else {
1666
+ result[key] = next;
1667
+ }
1668
+ }
1669
+ return result;
1670
+ }
1671
+ function normalizeArray(schema, value) {
1672
+ const prefixItems = schema.prefixItems ?? [];
1673
+ return value.map((item, index) => {
1674
+ const sub = (prefixItems[index] ?? schema.items);
1675
+ const next = normalizeNode(sub, item);
1676
+ // Array positions cannot be dropped without shifting the others.
1677
+ return next === OMIT ? null : next;
1678
+ });
1679
+ }
1680
+ function normalizeNode(schema, value) {
1681
+ if (!isSchema(schema)) {
1682
+ return value;
1683
+ }
1684
+ const type = primaryType(schema);
1685
+ if (isNumericType(type)) {
1686
+ return normalizeNumber(value);
1687
+ }
1688
+ let result = value;
1689
+ for (const sub of schema.allOf ?? []) {
1690
+ if (result === OMIT) {
1691
+ return OMIT;
1692
+ }
1693
+ result = normalizeNode(sub, result);
1694
+ }
1695
+ if (result === OMIT) {
1696
+ return OMIT;
1697
+ }
1698
+ const variants = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])];
1699
+ if (variants.length > 0 && type === undefined) {
1700
+ if (isRecord(result)) {
1701
+ const variant = pickVariant(variants, result);
1702
+ if (variant) {
1703
+ result = normalizeNode(variant, result);
1704
+ }
1705
+ }
1706
+ else if (!Array.isArray(result)) {
1707
+ // Primitive: convert only when the variants are numeric and none accepts a string.
1708
+ const types = variants.map((variant) => primaryType(variant));
1709
+ if (types.some(isNumericType) && !types.includes('string')) {
1710
+ result = normalizeNumber(result);
1711
+ }
1712
+ }
1713
+ }
1714
+ if (isRecord(result)) {
1715
+ return normalizeObject(schema, result);
1716
+ }
1717
+ if (Array.isArray(result)) {
1718
+ return normalizeArray(schema, result);
1719
+ }
1720
+ return result;
1721
+ }
1722
+ /**
1723
+ * Coerces values to the types declared by the schema so that the emitted value respects it:
1724
+ * - `number`/`integer` numeric strings become JSON numbers (comma or dot as separator);
1725
+ * - empty numeric values (`""`, whitespace, `null`) leave the key absent, never `""` or `0`;
1726
+ * - integers with decimals are converted to their number but never truncated (validation reports them);
1727
+ * - non-numeric strings are left unchanged (validation reports them).
1728
+ * Descends into properties, patternProperties, additionalProperties, items, prefixItems and combinators.
1729
+ * The same function feeds both the Ajv validation and the emitted value.
1730
+ */
1731
+ function normalizeValue(schema, value) {
1732
+ const normalized = normalizeNode(schema, value);
1733
+ return normalized === OMIT ? null : normalized;
1734
+ }
1735
+
1380
1736
  // AUTO-GENERATED — do not edit manually. Run: node scripts/generate-lib-styles.mjs
1381
1737
  const JSM_CSS = "/*! tailwindcss v4.1.18 | 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-100: oklch(93% 0.034 272.788);\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 .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-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-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-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 .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-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-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-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-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-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-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-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";
1382
1738
 
@@ -1402,10 +1758,10 @@ class JsonSchemaStylesService {
1402
1758
  document.head.appendChild(style);
1403
1759
  this.injected = true;
1404
1760
  }
1405
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaStylesService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1406
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaStylesService, providedIn: 'root' });
1761
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1762
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, providedIn: 'root' });
1407
1763
  }
1408
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaStylesService, decorators: [{
1764
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, decorators: [{
1409
1765
  type: Injectable,
1410
1766
  args: [{ providedIn: 'root' }]
1411
1767
  }], ctorParameters: () => [{ type: undefined, decorators: [{
@@ -1417,10 +1773,17 @@ class JsonSchemaFormComponent {
1417
1773
  schemaService;
1418
1774
  resolver;
1419
1775
  validation;
1776
+ cdr;
1777
+ options;
1420
1778
  schema;
1421
1779
  value;
1422
1780
  data;
1423
1781
  allowAdditionalProperties = false;
1782
+ /**
1783
+ * Decimal separator of number/integer fields. `,` shows and accepts `0,5` (both `,` and `.` are accepted as input
1784
+ * either way); the emitted value is always a JSON number.
1785
+ */
1786
+ decimalSeparator = '.';
1424
1787
  formReady = new EventEmitter();
1425
1788
  valueChange = new EventEmitter();
1426
1789
  schemaReady = new EventEmitter();
@@ -1431,21 +1794,52 @@ class JsonSchemaFormComponent {
1431
1794
  errorsMap = new Map();
1432
1795
  schemaErrorList = [];
1433
1796
  loading = false;
1797
+ /** When true, all field errors are shown regardless of touched/dirty state. */
1798
+ showErrors = false;
1434
1799
  valueSub;
1435
- constructor(schemaService, resolver, validation, stylesService) {
1800
+ constructor(schemaService, resolver, validation, cdr, options, stylesService) {
1436
1801
  this.schemaService = schemaService;
1437
1802
  this.resolver = resolver;
1438
1803
  this.validation = validation;
1804
+ this.cdr = cdr;
1805
+ this.options = options;
1439
1806
  stylesService.inject();
1440
1807
  }
1441
1808
  ngOnChanges(changes) {
1442
- if (changes['schema'] || changes['value'] || changes['data']) {
1809
+ if (changes['decimalSeparator']) {
1810
+ this.options.decimalSeparator = this.decimalSeparator;
1811
+ }
1812
+ if (changes['schema']) {
1813
+ // Schema changed — full rebuild required
1814
+ void this.buildForm();
1815
+ }
1816
+ else if ((changes['value'] || changes['data']) && this.form) {
1817
+ // Only data changed and form already exists — patch values without rebuilding
1818
+ const newData = this.value !== undefined ? this.value : this.data;
1819
+ this.form.patchValue(newData, { emitEvent: false });
1820
+ }
1821
+ else if (changes['value'] || changes['data']) {
1822
+ // Form not built yet — build it
1443
1823
  void this.buildForm();
1444
1824
  }
1445
1825
  }
1446
1826
  ngOnDestroy() {
1447
1827
  this.valueSub?.unsubscribe();
1448
1828
  }
1829
+ /**
1830
+ * Marks all fields as touched and forces error display across the entire form.
1831
+ * Call this from an external "Save" or "Submit" button to show all validation errors at once.
1832
+ * Returns true if the form is valid, false otherwise.
1833
+ */
1834
+ validate() {
1835
+ if (!this.form)
1836
+ return false;
1837
+ this.form.markAllAsTouched();
1838
+ this.showErrors = true;
1839
+ this.updateErrors();
1840
+ this.cdr.detectChanges();
1841
+ return this.form.valid;
1842
+ }
1449
1843
  async buildForm() {
1450
1844
  this.valueSub?.unsubscribe();
1451
1845
  this.loading = true;
@@ -1473,12 +1867,13 @@ class JsonSchemaFormComponent {
1473
1867
  this.errorsMap = new Map();
1474
1868
  return;
1475
1869
  }
1476
- const errors = this.validation.validate(this.resolvedSchema, this.rootControl.value);
1870
+ const normalized = normalizeValue(this.resolvedSchema, this.rootControl.value);
1871
+ const errors = this.validation.validate(this.resolvedSchema, normalized);
1477
1872
  const map = new Map();
1478
1873
  for (const error of errors) {
1479
1874
  const path = error.instancePath ?? '';
1480
1875
  const messages = map.get(path) ?? [];
1481
- messages.push(error.message ?? error.keyword);
1876
+ messages.push(localizeNumbers(error.message ?? error.keyword, this.decimalSeparator));
1482
1877
  map.set(path, messages);
1483
1878
  }
1484
1879
  this.errorsMap = map;
@@ -1503,126 +1898,128 @@ class JsonSchemaFormComponent {
1503
1898
  this.updateErrors();
1504
1899
  this.valueSub = this.form.valueChanges.subscribe(() => {
1505
1900
  this.updateErrors();
1506
- this.valueChange.emit(this.rootControl.value);
1901
+ this.valueChange.emit(this.resolvedSchema ? normalizeValue(this.resolvedSchema, this.rootControl.value) : this.rootControl.value);
1507
1902
  });
1508
1903
  }
1509
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaResolverService }, { token: JsonSchemaValidationService }, { token: JsonSchemaStylesService }], target: i0.ɵɵFactoryTarget.Component });
1510
- 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: `
1511
- <div class="jsm-root">
1512
- <form class="w-full space-y-5">
1513
- <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
1514
- <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || 'Form' }}</h2>
1515
- <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
1516
- </div>
1517
-
1518
- <div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" aria-live="polite">
1519
- <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none">
1520
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
1521
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
1522
- </svg>
1523
- Resolving schema&hellip;
1524
- </div>
1525
-
1526
- <!-- schema structural errors panel -->
1527
- <div *ngIf="schemaErrorList.length > 0 && !loading"
1528
- class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
1529
- role="alert">
1530
- <div class="flex items-center gap-2">
1531
- <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none">
1532
- <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
1533
- <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1534
- </svg>
1535
- <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
1536
- Schema errors ({{ schemaErrorList.length }})
1537
- </span>
1538
- </div>
1539
- <p class="text-xs text-amber-700 dark:text-amber-400">
1540
- The schema has structural issues. Fix them to get a valid form.
1541
- </p>
1542
- <div class="space-y-2">
1543
- <div *ngFor="let err of schemaErrorList"
1544
- 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">
1545
- <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
1546
- <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
1547
- <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
1548
- </div>
1549
- </div>
1550
- </div>
1551
-
1552
- <div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
1553
- <jsm-schema-node
1554
- [schema]="resolvedSchema"
1555
- [control]="rootControl"
1556
- [path]="''"
1557
- [errorsMap]="errorsMap"
1558
- [allowAdditionalProperties]="allowAdditionalProperties"
1559
- label="Root"
1560
- (controlReplaced)="onRootReplaced($event)"
1561
- ></jsm-schema-node>
1562
- </div>
1563
- </form>
1564
- </div>
1565
- `, 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 });
1904
+ 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: JsonSchemaFormOptions }, { token: JsonSchemaStylesService }], target: i0.ɵɵFactoryTarget.Component });
1905
+ 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", decimalSeparator: "decimalSeparator" }, outputs: { formReady: "formReady", valueChange: "valueChange", schemaReady: "schemaReady", schemaErrors: "schemaErrors" }, providers: [JsonSchemaFormOptions], usesOnChanges: true, ngImport: i0, template: `
1906
+ <div class="jsm-root">
1907
+ <form class="w-full space-y-5">
1908
+ <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
1909
+ <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || 'Form' }}</h2>
1910
+ <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
1911
+ </div>
1912
+
1913
+ <div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" aria-live="polite">
1914
+ <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none">
1915
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
1916
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
1917
+ </svg>
1918
+ Resolving schema&hellip;
1919
+ </div>
1920
+
1921
+ <!-- schema structural errors panel -->
1922
+ <div *ngIf="schemaErrorList.length > 0 && !loading"
1923
+ class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
1924
+ role="alert">
1925
+ <div class="flex items-center gap-2">
1926
+ <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none">
1927
+ <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
1928
+ <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1929
+ </svg>
1930
+ <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
1931
+ Schema errors ({{ schemaErrorList.length }})
1932
+ </span>
1933
+ </div>
1934
+ <p class="text-xs text-amber-700 dark:text-amber-400">
1935
+ The schema has structural issues. Fix them to get a valid form.
1936
+ </p>
1937
+ <div class="space-y-2">
1938
+ <div *ngFor="let err of schemaErrorList"
1939
+ 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">
1940
+ <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
1941
+ <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
1942
+ <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
1943
+ </div>
1944
+ </div>
1945
+ </div>
1946
+
1947
+ <div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
1948
+ <jsm-schema-node
1949
+ [schema]="resolvedSchema"
1950
+ [control]="rootControl"
1951
+ [path]="''"
1952
+ [errorsMap]="errorsMap"
1953
+ [allowAdditionalProperties]="allowAdditionalProperties"
1954
+ [showErrors]="showErrors"
1955
+ label="Root"
1956
+ (controlReplaced)="onRootReplaced($event)"
1957
+ ></jsm-schema-node>
1958
+ </div>
1959
+ </form>
1960
+ </div>
1961
+ `, 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: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.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 });
1566
1962
  }
1567
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
1963
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
1568
1964
  type: Component,
1569
- args: [{ selector: 'jsm-json-schema-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, JsonSchemaNodeComponent], encapsulation: ViewEncapsulation.None, template: `
1570
- <div class="jsm-root">
1571
- <form class="w-full space-y-5">
1572
- <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
1573
- <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || 'Form' }}</h2>
1574
- <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
1575
- </div>
1576
-
1577
- <div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" aria-live="polite">
1578
- <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none">
1579
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
1580
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
1581
- </svg>
1582
- Resolving schema&hellip;
1583
- </div>
1584
-
1585
- <!-- schema structural errors panel -->
1586
- <div *ngIf="schemaErrorList.length > 0 && !loading"
1587
- class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
1588
- role="alert">
1589
- <div class="flex items-center gap-2">
1590
- <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none">
1591
- <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
1592
- <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1593
- </svg>
1594
- <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
1595
- Schema errors ({{ schemaErrorList.length }})
1596
- </span>
1597
- </div>
1598
- <p class="text-xs text-amber-700 dark:text-amber-400">
1599
- The schema has structural issues. Fix them to get a valid form.
1600
- </p>
1601
- <div class="space-y-2">
1602
- <div *ngFor="let err of schemaErrorList"
1603
- 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">
1604
- <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
1605
- <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
1606
- <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
1607
- </div>
1608
- </div>
1609
- </div>
1610
-
1611
- <div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
1612
- <jsm-schema-node
1613
- [schema]="resolvedSchema"
1614
- [control]="rootControl"
1615
- [path]="''"
1616
- [errorsMap]="errorsMap"
1617
- [allowAdditionalProperties]="allowAdditionalProperties"
1618
- label="Root"
1619
- (controlReplaced)="onRootReplaced($event)"
1620
- ></jsm-schema-node>
1621
- </div>
1622
- </form>
1623
- </div>
1965
+ args: [{ selector: 'jsm-json-schema-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, JsonSchemaNodeComponent], providers: [JsonSchemaFormOptions], encapsulation: ViewEncapsulation.None, template: `
1966
+ <div class="jsm-root">
1967
+ <form class="w-full space-y-5">
1968
+ <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
1969
+ <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || 'Form' }}</h2>
1970
+ <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
1971
+ </div>
1972
+
1973
+ <div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" aria-live="polite">
1974
+ <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none">
1975
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
1976
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
1977
+ </svg>
1978
+ Resolving schema&hellip;
1979
+ </div>
1980
+
1981
+ <!-- schema structural errors panel -->
1982
+ <div *ngIf="schemaErrorList.length > 0 && !loading"
1983
+ class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
1984
+ role="alert">
1985
+ <div class="flex items-center gap-2">
1986
+ <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none">
1987
+ <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
1988
+ <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1989
+ </svg>
1990
+ <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
1991
+ Schema errors ({{ schemaErrorList.length }})
1992
+ </span>
1993
+ </div>
1994
+ <p class="text-xs text-amber-700 dark:text-amber-400">
1995
+ The schema has structural issues. Fix them to get a valid form.
1996
+ </p>
1997
+ <div class="space-y-2">
1998
+ <div *ngFor="let err of schemaErrorList"
1999
+ 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">
2000
+ <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
2001
+ <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
2002
+ <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
2003
+ </div>
2004
+ </div>
2005
+ </div>
2006
+
2007
+ <div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
2008
+ <jsm-schema-node
2009
+ [schema]="resolvedSchema"
2010
+ [control]="rootControl"
2011
+ [path]="''"
2012
+ [errorsMap]="errorsMap"
2013
+ [allowAdditionalProperties]="allowAdditionalProperties"
2014
+ [showErrors]="showErrors"
2015
+ label="Root"
2016
+ (controlReplaced)="onRootReplaced($event)"
2017
+ ></jsm-schema-node>
2018
+ </div>
2019
+ </form>
2020
+ </div>
1624
2021
  `, 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"] }]
1625
- }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaResolverService }, { type: JsonSchemaValidationService }, { type: JsonSchemaStylesService }], propDecorators: { schema: [{
2022
+ }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaResolverService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }, { type: JsonSchemaFormOptions }, { type: JsonSchemaStylesService }], propDecorators: { schema: [{
1626
2023
  type: Input,
1627
2024
  args: [{ required: true }]
1628
2025
  }], value: [{
@@ -1631,6 +2028,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1631
2028
  type: Input
1632
2029
  }], allowAdditionalProperties: [{
1633
2030
  type: Input
2031
+ }], decimalSeparator: [{
2032
+ type: Input
1634
2033
  }], formReady: [{
1635
2034
  type: Output
1636
2035
  }], valueChange: [{
@@ -1642,11 +2041,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1642
2041
  }] } });
1643
2042
 
1644
2043
  class JsonSchemaFormModule {
1645
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1646
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent], exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1647
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
2044
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2045
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent], exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
2046
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1648
2047
  }
1649
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, decorators: [{
2048
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, decorators: [{
1650
2049
  type: NgModule,
1651
2050
  args: [{
1652
2051
  imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
@@ -1658,5 +2057,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1658
2057
  * Generated bundle index. Do not edit.
1659
2058
  */
1660
2059
 
1661
- export { JsonSchemaFormComponent, JsonSchemaFormModule, JsonSchemaFormService, JsonSchemaNodeComponent, JsonSchemaResolverService, JsonSchemaStylesService, JsonSchemaValidationService };
2060
+ export { JsonSchemaFormComponent, JsonSchemaFormModule, JsonSchemaFormOptions, JsonSchemaFormService, JsonSchemaNodeComponent, JsonSchemaResolverService, JsonSchemaStylesService, JsonSchemaValidationService, NumericInputDirective, formatNumeric, localizeNumbers, normalizeValue, parseNumericText, sanitizeNumericText };
1662
2061
  //# sourceMappingURL=elle96-ng-json-schema-manager.mjs.map