@elle96/ng-json-schema-manager 0.0.5

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.
@@ -0,0 +1,1679 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
3
+ import * as i4 from '@angular/forms';
4
+ import { FormGroup, FormArray, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
5
+ import Ajv from 'ajv';
6
+ import Ajv2019 from 'ajv/dist/2019';
7
+ import Ajv2020 from 'ajv/dist/2020';
8
+ import addFormats from 'ajv-formats';
9
+ import AjvDraft04 from 'ajv-draft-04';
10
+ import * as i3 from '@angular/common';
11
+ import { CommonModule } from '@angular/common';
12
+
13
+ class JsonSchemaFormService {
14
+ buildControl(schema, value, required = false) {
15
+ const effectiveSchema = this.applyCombinators(schema);
16
+ const resolvedType = this.resolveType(effectiveSchema);
17
+ if (resolvedType === 'object') {
18
+ const group = {};
19
+ const properties = effectiveSchema.properties ?? {};
20
+ const requiredFields = new Set(effectiveSchema.required ?? []);
21
+ const valueObject = this.isRecord(value) ? value : {};
22
+ for (const [key, propertySchema] of Object.entries(properties)) {
23
+ const childValue = valueObject[key];
24
+ group[key] = this.buildControl(propertySchema, childValue, requiredFields.has(key));
25
+ }
26
+ // Include dynamic keys from value (patternProperties/additionalProperties).
27
+ for (const key of Object.keys(valueObject)) {
28
+ if (group[key]) {
29
+ continue;
30
+ }
31
+ const dynamicSchema = this.resolveDynamicSchema(effectiveSchema, key);
32
+ group[key] = this.buildControl(dynamicSchema ?? {}, valueObject[key], false);
33
+ }
34
+ const control = new FormGroup(group, {
35
+ validators: this.buildValidators(effectiveSchema, required),
36
+ });
37
+ if (effectiveSchema.readOnly) {
38
+ control.disable({ emitEvent: false });
39
+ }
40
+ return control;
41
+ }
42
+ if (resolvedType === 'array') {
43
+ const controls = [];
44
+ const initialItems = Array.isArray(value) ? value : [];
45
+ const minItems = effectiveSchema.minItems ?? 0;
46
+ const prefixItems = effectiveSchema.prefixItems ?? [];
47
+ const tupleLength = Math.max(prefixItems.length, minItems, initialItems.length);
48
+ for (let i = 0; i < tupleLength; i += 1) {
49
+ const itemSchema = (prefixItems[i] ?? effectiveSchema.items);
50
+ if (itemSchema === false) {
51
+ continue;
52
+ }
53
+ const itemValue = initialItems[i];
54
+ controls.push(this.buildControl(itemSchema ?? {}, itemValue, false));
55
+ }
56
+ const control = new FormArray(controls, {
57
+ validators: this.buildValidators(effectiveSchema, required),
58
+ });
59
+ if (effectiveSchema.readOnly) {
60
+ control.disable({ emitEvent: false });
61
+ }
62
+ return control;
63
+ }
64
+ const initialValue = this.resolveInitialValue(effectiveSchema, value);
65
+ const control = new FormControl(initialValue, {
66
+ validators: this.buildValidators(effectiveSchema, required, resolvedType),
67
+ nonNullable: false,
68
+ });
69
+ if (effectiveSchema.readOnly || effectiveSchema.const !== undefined) {
70
+ control.disable({ emitEvent: false });
71
+ }
72
+ return control;
73
+ }
74
+ resolveType(schema) {
75
+ if (schema.enum) {
76
+ return 'enum';
77
+ }
78
+ if (schema.oneOf?.length) {
79
+ return this.resolveType(schema.oneOf[0]);
80
+ }
81
+ if (schema.anyOf?.length) {
82
+ return this.resolveType(schema.anyOf[0]);
83
+ }
84
+ if (schema.allOf?.length) {
85
+ return this.resolveType(schema.allOf[0]);
86
+ }
87
+ if (schema.type) {
88
+ if (Array.isArray(schema.type)) {
89
+ const preferred = schema.type.find((item) => item !== 'null');
90
+ return preferred ?? schema.type[0];
91
+ }
92
+ return schema.type;
93
+ }
94
+ if (schema.properties || schema.patternProperties || schema.additionalProperties) {
95
+ return 'object';
96
+ }
97
+ if (schema.items || schema.prefixItems) {
98
+ return 'array';
99
+ }
100
+ return 'string';
101
+ }
102
+ applyCombinators(schema) {
103
+ let effective = schema;
104
+ if (schema.allOf?.length) {
105
+ effective = this.mergeSchemas([schema, ...schema.allOf]);
106
+ }
107
+ if (schema.oneOf?.length) {
108
+ effective = schema.oneOf[0];
109
+ }
110
+ if (schema.anyOf?.length) {
111
+ effective = schema.anyOf[0];
112
+ }
113
+ return effective;
114
+ }
115
+ resolveDynamicSchema(schema, key) {
116
+ const patternSchemas = schema.patternProperties ?? {};
117
+ for (const [pattern, patternSchema] of Object.entries(patternSchemas)) {
118
+ try {
119
+ const regex = new RegExp(pattern);
120
+ if (regex.test(key)) {
121
+ return patternSchema;
122
+ }
123
+ }
124
+ catch {
125
+ // ignore invalid regex
126
+ }
127
+ }
128
+ if (schema.additionalProperties === true || schema.additionalProperties === undefined) {
129
+ return {};
130
+ }
131
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
132
+ return schema.additionalProperties;
133
+ }
134
+ if (schema.unevaluatedProperties && typeof schema.unevaluatedProperties === 'object') {
135
+ return schema.unevaluatedProperties;
136
+ }
137
+ if (schema.unevaluatedProperties === true) {
138
+ return {};
139
+ }
140
+ return undefined;
141
+ }
142
+ resolveInitialValue(schema, value) {
143
+ if (value !== undefined) {
144
+ return value;
145
+ }
146
+ if (schema.const !== undefined) {
147
+ return schema.const;
148
+ }
149
+ if (schema.default !== undefined) {
150
+ return schema.default;
151
+ }
152
+ if (schema.enum && schema.enum.length > 0) {
153
+ return schema.enum[0];
154
+ }
155
+ if (schema.type === 'boolean') {
156
+ return false;
157
+ }
158
+ if (schema.type === 'array') {
159
+ return [];
160
+ }
161
+ if (schema.type === 'object') {
162
+ return {};
163
+ }
164
+ return null;
165
+ }
166
+ buildValidators(schema, required, resolvedType) {
167
+ const validators = [];
168
+ if (required) {
169
+ validators.push(Validators.required);
170
+ }
171
+ if (schema.minLength !== undefined) {
172
+ validators.push(Validators.minLength(schema.minLength));
173
+ }
174
+ if (schema.maxLength !== undefined) {
175
+ validators.push(Validators.maxLength(schema.maxLength));
176
+ }
177
+ if (schema.minimum !== undefined) {
178
+ validators.push(Validators.min(schema.minimum));
179
+ }
180
+ if (schema.maximum !== undefined) {
181
+ validators.push(Validators.max(schema.maximum));
182
+ }
183
+ if (schema.exclusiveMinimum !== undefined) {
184
+ validators.push((control) => control.value === null || control.value === undefined || control.value === ''
185
+ ? null
186
+ : Number(control.value) > schema.exclusiveMinimum
187
+ ? null
188
+ : { exclusiveMinimum: true });
189
+ }
190
+ if (schema.exclusiveMaximum !== undefined) {
191
+ validators.push((control) => control.value === null || control.value === undefined || control.value === ''
192
+ ? null
193
+ : Number(control.value) < schema.exclusiveMaximum
194
+ ? null
195
+ : { exclusiveMaximum: true });
196
+ }
197
+ if (schema.multipleOf !== undefined) {
198
+ validators.push((control) => {
199
+ if (control.value === null || control.value === undefined || control.value === '') {
200
+ return null;
201
+ }
202
+ const numericValue = Number(control.value);
203
+ return numericValue % schema.multipleOf === 0 ? null : { multipleOf: true };
204
+ });
205
+ }
206
+ if (schema.pattern) {
207
+ validators.push(Validators.pattern(schema.pattern));
208
+ }
209
+ if (schema.format === 'email') {
210
+ validators.push(Validators.email);
211
+ }
212
+ if (schema.minItems !== undefined) {
213
+ validators.push((control) => Array.isArray(control.value) && control.value.length < schema.minItems
214
+ ? { minItems: true }
215
+ : null);
216
+ }
217
+ if (schema.maxItems !== undefined) {
218
+ validators.push((control) => Array.isArray(control.value) && control.value.length > schema.maxItems
219
+ ? { maxItems: true }
220
+ : null);
221
+ }
222
+ if (schema.uniqueItems) {
223
+ validators.push((control) => {
224
+ if (!Array.isArray(control.value)) {
225
+ return null;
226
+ }
227
+ const unique = new Set(control.value.map((item) => JSON.stringify(item)));
228
+ return unique.size === control.value.length ? null : { uniqueItems: true };
229
+ });
230
+ }
231
+ if (schema.minProperties !== undefined) {
232
+ validators.push((control) => {
233
+ if (!control.value || typeof control.value !== 'object') {
234
+ return null;
235
+ }
236
+ return Object.keys(control.value).length < schema.minProperties
237
+ ? { minProperties: true }
238
+ : null;
239
+ });
240
+ }
241
+ if (schema.maxProperties !== undefined) {
242
+ validators.push((control) => {
243
+ if (!control.value || typeof control.value !== 'object') {
244
+ return null;
245
+ }
246
+ return Object.keys(control.value).length > schema.maxProperties
247
+ ? { maxProperties: true }
248
+ : null;
249
+ });
250
+ }
251
+ if (resolvedType === 'integer') {
252
+ validators.push((control) => {
253
+ if (control.value === null || control.value === undefined || control.value === '') {
254
+ return null;
255
+ }
256
+ const numericValue = Number(control.value);
257
+ return Number.isInteger(numericValue) ? null : { integer: true };
258
+ });
259
+ }
260
+ return validators;
261
+ }
262
+ isRecord(value) {
263
+ return !!value && typeof value === 'object' && !Array.isArray(value);
264
+ }
265
+ mergeSchemas(schemas) {
266
+ return schemas.reduce((acc, schema) => {
267
+ const merged = {
268
+ ...acc,
269
+ ...schema,
270
+ properties: { ...(acc.properties ?? {}), ...(schema.properties ?? {}) },
271
+ patternProperties: { ...(acc.patternProperties ?? {}), ...(schema.patternProperties ?? {}) },
272
+ required: Array.from(new Set([...(acc.required ?? []), ...(schema.required ?? [])])),
273
+ };
274
+ return merged;
275
+ }, {});
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' });
279
+ }
280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormService, decorators: [{
281
+ type: Injectable,
282
+ args: [{
283
+ providedIn: 'root',
284
+ }]
285
+ }] });
286
+
287
+ class JsonSchemaResolverService {
288
+ async resolve(schema, baseUri) {
289
+ const rootBase = baseUri ?? schema.$id ?? '';
290
+ const localCache = new Map();
291
+ const externalCache = new Map();
292
+ return this.resolveRefs(schema, schema, rootBase, localCache, externalCache);
293
+ }
294
+ async resolveRefs(current, root, baseUri, localCache, externalCache) {
295
+ if (!current || typeof current !== 'object') {
296
+ return current;
297
+ }
298
+ if (current.$ref) {
299
+ const ref = current.$ref;
300
+ if (ref.startsWith('#/')) {
301
+ if (localCache.has(ref)) {
302
+ return localCache.get(ref);
303
+ }
304
+ const resolved = this.resolvePointer(root, ref);
305
+ if (resolved) {
306
+ localCache.set(ref, resolved);
307
+ return this.resolveRefs(resolved, root, baseUri, localCache, externalCache);
308
+ }
309
+ }
310
+ else {
311
+ const resolved = await this.resolveExternalRef(ref, baseUri, externalCache, localCache);
312
+ if (resolved) {
313
+ return resolved;
314
+ }
315
+ }
316
+ }
317
+ const next = { ...current };
318
+ if (next.properties) {
319
+ const entries = await Promise.all(Object.entries(next.properties).map(async ([key, value]) => [
320
+ key,
321
+ await this.resolveRefs(value, root, baseUri, localCache, externalCache),
322
+ ]));
323
+ next.properties = Object.fromEntries(entries);
324
+ }
325
+ if (next.patternProperties) {
326
+ const entries = await Promise.all(Object.entries(next.patternProperties).map(async ([key, value]) => [
327
+ key,
328
+ await this.resolveRefs(value, root, baseUri, localCache, externalCache),
329
+ ]));
330
+ next.patternProperties = Object.fromEntries(entries);
331
+ }
332
+ if (next.items && typeof next.items === 'object') {
333
+ next.items = await this.resolveRefs(next.items, root, baseUri, localCache, externalCache);
334
+ }
335
+ if (next.prefixItems) {
336
+ next.prefixItems = await Promise.all(next.prefixItems.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
337
+ }
338
+ if (next.additionalProperties && typeof next.additionalProperties === 'object') {
339
+ next.additionalProperties = await this.resolveRefs(next.additionalProperties, root, baseUri, localCache, externalCache);
340
+ }
341
+ if (next.additionalItems && typeof next.additionalItems === 'object') {
342
+ next.additionalItems = await this.resolveRefs(next.additionalItems, root, baseUri, localCache, externalCache);
343
+ }
344
+ if (next.unevaluatedProperties && typeof next.unevaluatedProperties === 'object') {
345
+ next.unevaluatedProperties = await this.resolveRefs(next.unevaluatedProperties, root, baseUri, localCache, externalCache);
346
+ }
347
+ if (next.unevaluatedItems && typeof next.unevaluatedItems === 'object') {
348
+ next.unevaluatedItems = await this.resolveRefs(next.unevaluatedItems, root, baseUri, localCache, externalCache);
349
+ }
350
+ if (next.contains) {
351
+ next.contains = await this.resolveRefs(next.contains, root, baseUri, localCache, externalCache);
352
+ }
353
+ if (next.oneOf) {
354
+ next.oneOf = await Promise.all(next.oneOf.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
355
+ }
356
+ if (next.anyOf) {
357
+ next.anyOf = await Promise.all(next.anyOf.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
358
+ }
359
+ if (next.allOf) {
360
+ next.allOf = await Promise.all(next.allOf.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
361
+ }
362
+ if (next.not) {
363
+ next.not = await this.resolveRefs(next.not, root, baseUri, localCache, externalCache);
364
+ }
365
+ if (next.if) {
366
+ next.if = await this.resolveRefs(next.if, root, baseUri, localCache, externalCache);
367
+ }
368
+ if (next.then) {
369
+ next.then = await this.resolveRefs(next.then, root, baseUri, localCache, externalCache);
370
+ }
371
+ if (next.else) {
372
+ next.else = await this.resolveRefs(next.else, root, baseUri, localCache, externalCache);
373
+ }
374
+ if (next.dependencies) {
375
+ const entries = await Promise.all(Object.entries(next.dependencies).map(async ([key, value]) => {
376
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
377
+ return [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)];
378
+ }
379
+ return [key, value];
380
+ }));
381
+ next.dependencies = Object.fromEntries(entries);
382
+ }
383
+ if (next.dependentSchemas) {
384
+ const entries = await Promise.all(Object.entries(next.dependentSchemas).map(async ([key, value]) => [
385
+ key,
386
+ await this.resolveRefs(value, root, baseUri, localCache, externalCache),
387
+ ]));
388
+ next.dependentSchemas = Object.fromEntries(entries);
389
+ }
390
+ if (next.$defs) {
391
+ const entries = await Promise.all(Object.entries(next.$defs).map(async ([key, value]) => [
392
+ key,
393
+ await this.resolveRefs(value, root, baseUri, localCache, externalCache),
394
+ ]));
395
+ next.$defs = Object.fromEntries(entries);
396
+ }
397
+ if (next.definitions) {
398
+ const entries = await Promise.all(Object.entries(next.definitions).map(async ([key, value]) => [
399
+ key,
400
+ await this.resolveRefs(value, root, baseUri, localCache, externalCache),
401
+ ]));
402
+ next.definitions = Object.fromEntries(entries);
403
+ }
404
+ return next;
405
+ }
406
+ async resolveExternalRef(ref, baseUri, externalCache, localCache) {
407
+ try {
408
+ const url = new URL(ref, baseUri || window.location.href).toString();
409
+ if (externalCache.has(url)) {
410
+ return externalCache.get(url);
411
+ }
412
+ const { schema, fragment } = await this.fetchSchema(url);
413
+ if (!schema) {
414
+ return null;
415
+ }
416
+ const target = fragment ? this.resolvePointer(schema, fragment) : schema;
417
+ if (!target) {
418
+ return null;
419
+ }
420
+ const resolved = await this.resolveRefs(target, schema, schema.$id ?? url, localCache, externalCache);
421
+ externalCache.set(url, resolved);
422
+ return resolved;
423
+ }
424
+ catch {
425
+ return null;
426
+ }
427
+ }
428
+ async fetchSchema(url) {
429
+ const [base, hash] = url.split('#');
430
+ const fragment = hash ? `#/${hash.replace(/^\//, '')}` : null;
431
+ const response = await fetch(base, { headers: { Accept: 'application/json' } });
432
+ if (!response.ok) {
433
+ return { schema: null, fragment: null };
434
+ }
435
+ const schema = (await response.json());
436
+ return { schema, fragment };
437
+ }
438
+ resolvePointer(root, ref) {
439
+ const pointer = ref.replace(/^#\//, '');
440
+ const parts = pointer.split('/').map((part) => part.replace(/~1/g, '/').replace(/~0/g, '~'));
441
+ let current = root;
442
+ for (const part of parts) {
443
+ if (current && typeof current === 'object' && part in current) {
444
+ current = current[part];
445
+ }
446
+ else {
447
+ return null;
448
+ }
449
+ }
450
+ return current;
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' });
454
+ }
455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaResolverService, decorators: [{
456
+ type: Injectable,
457
+ args: [{
458
+ providedIn: 'root',
459
+ }]
460
+ }] });
461
+
462
+ class JsonSchemaValidationService {
463
+ ajvCache = new Map();
464
+ validate(schema, value) {
465
+ const draft = this.detectDraft(schema);
466
+ const ajv = this.getAjv(draft);
467
+ const validate = ajv.compile(schema);
468
+ const valid = validate(value);
469
+ if (valid) {
470
+ return [];
471
+ }
472
+ return validate.errors ?? [];
473
+ }
474
+ detectDraft(schema) {
475
+ const schemaId = schema.$schema ?? '';
476
+ if (schemaId.includes('draft-04')) {
477
+ return 'draft4';
478
+ }
479
+ if (schemaId.includes('draft-06')) {
480
+ return 'draft6';
481
+ }
482
+ if (schemaId.includes('draft-07')) {
483
+ return 'draft7';
484
+ }
485
+ if (schemaId.includes('2019-09')) {
486
+ return 'draft2019';
487
+ }
488
+ if (schemaId.includes('2020-12')) {
489
+ return 'draft2020';
490
+ }
491
+ if (schema.$defs) {
492
+ return 'draft2020';
493
+ }
494
+ if (schema.definitions) {
495
+ return 'draft7';
496
+ }
497
+ return 'draft2020';
498
+ }
499
+ getAjv(draft) {
500
+ if (this.ajvCache.has(draft)) {
501
+ return this.ajvCache.get(draft);
502
+ }
503
+ let ajv;
504
+ switch (draft) {
505
+ case 'draft4':
506
+ ajv = new AjvDraft04({
507
+ strict: false,
508
+ allErrors: true,
509
+ allowUnionTypes: true,
510
+ });
511
+ break;
512
+ case 'draft2019':
513
+ ajv = new Ajv2019({
514
+ strict: false,
515
+ allErrors: true,
516
+ allowUnionTypes: true,
517
+ });
518
+ break;
519
+ case 'draft6':
520
+ case 'draft7':
521
+ ajv = new Ajv({
522
+ strict: false,
523
+ allErrors: true,
524
+ allowUnionTypes: true,
525
+ });
526
+ break;
527
+ case 'draft2020':
528
+ default:
529
+ ajv = new Ajv2020({
530
+ strict: false,
531
+ allErrors: true,
532
+ allowUnionTypes: true,
533
+ });
534
+ }
535
+ addFormats(ajv);
536
+ this.ajvCache.set(draft, ajv);
537
+ return ajv;
538
+ }
539
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
540
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaValidationService, providedIn: 'root' });
541
+ }
542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaValidationService, decorators: [{
543
+ type: Injectable,
544
+ args: [{
545
+ providedIn: 'root',
546
+ }]
547
+ }] });
548
+
549
+ class JsonSchemaNodeComponent {
550
+ schemaService;
551
+ validation;
552
+ schema;
553
+ control;
554
+ label = '';
555
+ required = false;
556
+ path = '';
557
+ errorsMap = new Map();
558
+ parent;
559
+ controlKey;
560
+ controlReplaced = new EventEmitter();
561
+ selectedOneOf = 0;
562
+ selectedAnyOf = new Set();
563
+ newPropertyKey = '';
564
+ propertyError = '';
565
+ constructor(schemaService, validation) {
566
+ this.schemaService = schemaService;
567
+ this.validation = validation;
568
+ }
569
+ ngOnInit() {
570
+ if (this.schema.anyOf?.length) {
571
+ this.selectedAnyOf.add(0);
572
+ }
573
+ }
574
+ get nodeKind() {
575
+ if (this.schema.oneOf?.length) {
576
+ return 'oneOf';
577
+ }
578
+ if (this.schema.anyOf?.length) {
579
+ return 'anyOf';
580
+ }
581
+ if (this.schema.allOf?.length) {
582
+ return 'allOf';
583
+ }
584
+ const resolved = this.resolvedType;
585
+ if (resolved === 'object') {
586
+ return 'object';
587
+ }
588
+ if (resolved === 'array') {
589
+ return 'array';
590
+ }
591
+ return 'primitive';
592
+ }
593
+ get resolvedType() {
594
+ return this.schemaService.resolveType(this.effectiveSchema) ?? 'string';
595
+ }
596
+ get inputKind() {
597
+ if (this.effectiveSchema.enum) {
598
+ return 'select';
599
+ }
600
+ if (this.resolvedType === 'boolean') {
601
+ return 'checkbox';
602
+ }
603
+ if (this.effectiveSchema.format === 'textarea' || (this.effectiveSchema.maxLength ?? 0) > 200) {
604
+ return 'textarea';
605
+ }
606
+ return 'text';
607
+ }
608
+ get inputType() {
609
+ if (this.effectiveSchema.format === 'email') {
610
+ return 'email';
611
+ }
612
+ if (this.effectiveSchema.format === 'date') {
613
+ return 'date';
614
+ }
615
+ if (this.effectiveSchema.format === 'time') {
616
+ return 'time';
617
+ }
618
+ if (this.effectiveSchema.format === 'date-time') {
619
+ return 'datetime-local';
620
+ }
621
+ if (this.resolvedType === 'number' || this.resolvedType === 'integer') {
622
+ return 'number';
623
+ }
624
+ if (this.effectiveSchema.format === 'password') {
625
+ return 'password';
626
+ }
627
+ return 'text';
628
+ }
629
+ get controlAsGroup() {
630
+ return this.control;
631
+ }
632
+ get controlAsFormControl() {
633
+ return this.control;
634
+ }
635
+ get controlAsArray() {
636
+ return this.control;
637
+ }
638
+ get arrayControls() {
639
+ return this.controlAsArray.controls;
640
+ }
641
+ get objectKeys() {
642
+ return Object.keys(this.controlAsGroup.controls);
643
+ }
644
+ get canAddProperty() {
645
+ return this.effectiveSchema.additionalProperties !== false || !!this.effectiveSchema.patternProperties;
646
+ }
647
+ get errorsForPath() {
648
+ return this.errorsMap.get(this.path) ?? [];
649
+ }
650
+ get metaBadges() {
651
+ const badges = [];
652
+ if (this.effectiveSchema.deprecated) {
653
+ badges.push('Deprecated');
654
+ }
655
+ if (this.effectiveSchema.readOnly) {
656
+ badges.push('Read-only');
657
+ }
658
+ if (this.effectiveSchema.writeOnly) {
659
+ badges.push('Write-only');
660
+ }
661
+ if (this.effectiveSchema.nullable) {
662
+ badges.push('Nullable');
663
+ }
664
+ if (this.effectiveSchema.not) {
665
+ badges.push('Not constraint');
666
+ }
667
+ return badges;
668
+ }
669
+ get activeVariant() {
670
+ if (this.schema.oneOf?.length) {
671
+ return this.schema.oneOf[this.selectedOneOf] ?? null;
672
+ }
673
+ if (this.schema.anyOf?.length) {
674
+ const selections = Array.from(this.selectedAnyOf).map((index) => this.schema.anyOf[index]);
675
+ return selections.length ? this.mergeSchemas(selections) : this.schema.anyOf[0] ?? null;
676
+ }
677
+ if (this.schema.allOf?.length) {
678
+ return this.mergeSchemas([this.schema, ...this.schema.allOf]);
679
+ }
680
+ return this.effectiveSchema;
681
+ }
682
+ get mergedAllOf() {
683
+ return this.schema.allOf?.length ? [this.schema, ...this.schema.allOf] : [this.schema];
684
+ }
685
+ isRequired(key) {
686
+ return this.effectiveSchema.required?.includes(key) ?? false;
687
+ }
688
+ schemaForKey(key) {
689
+ return (this.effectiveSchema.properties?.[key] ??
690
+ this.schemaService.resolveDynamicSchema(this.effectiveSchema, key) ??
691
+ {});
692
+ }
693
+ isDynamicKey(key) {
694
+ return !this.effectiveSchema.properties?.[key];
695
+ }
696
+ pathForChild(key) {
697
+ if (this.path === '') {
698
+ return `/${key}`;
699
+ }
700
+ return `${this.path}/${key}`;
701
+ }
702
+ addDynamicProperty() {
703
+ this.propertyError = '';
704
+ const key = this.newPropertyKey.trim();
705
+ if (!key) {
706
+ this.propertyError = 'Property name is required.';
707
+ return;
708
+ }
709
+ if (this.effectiveSchema.propertyNames) {
710
+ const nameErrors = this.validation.validate(this.effectiveSchema.propertyNames, key);
711
+ if (nameErrors.length) {
712
+ this.propertyError = 'Property name does not match schema constraints.';
713
+ return;
714
+ }
715
+ }
716
+ if (this.controlAsGroup.contains(key)) {
717
+ this.propertyError = 'Property already exists.';
718
+ return;
719
+ }
720
+ const schemaForKey = this.schemaService.resolveDynamicSchema(this.effectiveSchema, key);
721
+ if (!schemaForKey) {
722
+ this.propertyError = 'This property is not allowed by the schema.';
723
+ return;
724
+ }
725
+ const control = this.schemaService.buildControl(schemaForKey, undefined, false);
726
+ this.controlAsGroup.addControl(key, control);
727
+ this.newPropertyKey = '';
728
+ }
729
+ removeDynamicKey(key) {
730
+ this.controlAsGroup.removeControl(key);
731
+ }
732
+ schemaForIndex(index) {
733
+ const prefixItems = this.effectiveSchema.prefixItems ?? [];
734
+ if (prefixItems[index]) {
735
+ return prefixItems[index];
736
+ }
737
+ if (this.effectiveSchema.items && this.effectiveSchema.items !== true) {
738
+ return this.effectiveSchema.items;
739
+ }
740
+ if (this.effectiveSchema.additionalItems && this.effectiveSchema.additionalItems !== true) {
741
+ return this.effectiveSchema.additionalItems;
742
+ }
743
+ if (this.effectiveSchema.unevaluatedItems && this.effectiveSchema.unevaluatedItems !== true) {
744
+ return this.effectiveSchema.unevaluatedItems;
745
+ }
746
+ return {};
747
+ }
748
+ get canAddArrayItem() {
749
+ if (this.effectiveSchema.items === false ||
750
+ this.effectiveSchema.additionalItems === false ||
751
+ this.effectiveSchema.unevaluatedItems === false) {
752
+ return false;
753
+ }
754
+ return true;
755
+ }
756
+ addArrayItem() {
757
+ if (!this.canAddArrayItem) {
758
+ return;
759
+ }
760
+ const formArray = this.controlAsArray;
761
+ const index = formArray.length;
762
+ const schemaForItem = this.schemaForIndex(index);
763
+ formArray.push(this.schemaService.buildControl(schemaForItem, undefined, false));
764
+ formArray.markAsDirty();
765
+ }
766
+ removeArrayItem(index) {
767
+ const formArray = this.controlAsArray;
768
+ formArray.removeAt(index);
769
+ formArray.markAsDirty();
770
+ }
771
+ onOneOfChange() {
772
+ if (!this.activeVariant) {
773
+ return;
774
+ }
775
+ this.replaceControl(this.schemaService.buildControl(this.activeVariant, this.control.value, this.required));
776
+ }
777
+ toggleAnyOf(index) {
778
+ if (this.selectedAnyOf.has(index)) {
779
+ this.selectedAnyOf.delete(index);
780
+ }
781
+ else {
782
+ this.selectedAnyOf.add(index);
783
+ }
784
+ const variant = this.activeVariant;
785
+ if (variant) {
786
+ this.replaceControl(this.schemaService.buildControl(variant, this.control.value, this.required));
787
+ }
788
+ }
789
+ replaceControl(control) {
790
+ if (this.parent) {
791
+ if (this.parent instanceof FormGroup && typeof this.controlKey === 'string') {
792
+ this.parent.setControl(this.controlKey, control);
793
+ }
794
+ if (this.parent instanceof FormArray && typeof this.controlKey === 'number') {
795
+ this.parent.setControl(this.controlKey, control);
796
+ }
797
+ this.control = control;
798
+ return;
799
+ }
800
+ this.control = control;
801
+ this.controlReplaced.emit(control);
802
+ }
803
+ childControl(key) {
804
+ return this.controlAsGroup.get(key);
805
+ }
806
+ get effectiveSchema() {
807
+ let schema = this.schema;
808
+ if (schema.allOf?.length) {
809
+ schema = this.mergeSchemas([schema, ...schema.allOf]);
810
+ }
811
+ schema = this.applyConditional(schema);
812
+ schema = this.applyDependencies(schema);
813
+ return schema;
814
+ }
815
+ applyConditional(schema) {
816
+ if (!schema.if) {
817
+ return schema;
818
+ }
819
+ const errors = this.validation.validate(schema.if, this.control.value);
820
+ const branch = errors.length === 0 ? schema.then : schema.else;
821
+ if (!branch) {
822
+ return schema;
823
+ }
824
+ return this.mergeSchemas([schema, branch]);
825
+ }
826
+ applyDependencies(schema) {
827
+ const value = this.control.value;
828
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
829
+ return schema;
830
+ }
831
+ let merged = { ...schema };
832
+ const valueKeys = Object.keys(value);
833
+ if (schema.dependentRequired) {
834
+ for (const key of valueKeys) {
835
+ const required = schema.dependentRequired[key];
836
+ if (required?.length) {
837
+ merged = this.mergeSchemas([merged, { required }]);
838
+ }
839
+ }
840
+ }
841
+ if (schema.dependentSchemas) {
842
+ for (const key of valueKeys) {
843
+ const depSchema = schema.dependentSchemas[key];
844
+ if (depSchema) {
845
+ merged = this.mergeSchemas([merged, depSchema]);
846
+ }
847
+ }
848
+ }
849
+ if (schema.dependencies) {
850
+ for (const key of valueKeys) {
851
+ const dep = schema.dependencies[key];
852
+ if (Array.isArray(dep)) {
853
+ merged = this.mergeSchemas([merged, { required: dep }]);
854
+ }
855
+ else if (dep) {
856
+ merged = this.mergeSchemas([merged, dep]);
857
+ }
858
+ }
859
+ }
860
+ return merged;
861
+ }
862
+ mergeSchemas(schemas) {
863
+ return schemas.reduce((acc, schema) => {
864
+ const merged = {
865
+ ...acc,
866
+ ...schema,
867
+ properties: { ...(acc.properties ?? {}), ...(schema.properties ?? {}) },
868
+ patternProperties: { ...(acc.patternProperties ?? {}), ...(schema.patternProperties ?? {}) },
869
+ required: Array.from(new Set([...(acc.required ?? []), ...(schema.required ?? [])])),
870
+ };
871
+ if (acc.type && schema.type) {
872
+ const accTypes = Array.isArray(acc.type) ? acc.type : [acc.type];
873
+ const schemaTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
874
+ merged.type = Array.from(new Set([...accTypes, ...schemaTypes]));
875
+ }
876
+ return merged;
877
+ }, {});
878
+ }
879
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaValidationService }], target: i0.ɵɵFactoryTarget.Component });
880
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: JsonSchemaNodeComponent, isStandalone: true, selector: "jsm-schema-node", inputs: { schema: "schema", control: "control", label: "label", required: "required", path: "path", errorsMap: "errorsMap", parent: "parent", controlKey: "controlKey" }, outputs: { controlReplaced: "controlReplaced" }, ngImport: i0, template: `
881
+ <ng-container [ngSwitch]="nodeKind">
882
+ <div *ngSwitchCase="'oneOf'" class="space-y-3">
883
+ <div class="flex items-center justify-between">
884
+ <div>
885
+ <div class="text-sm font-semibold text-slate-800">{{ schema.title || label }}</div>
886
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
887
+ </div>
888
+ <span class="text-xs text-slate-400">oneOf</span>
889
+ </div>
890
+
891
+ <select
892
+ class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-slate-900"
893
+ [(ngModel)]="selectedOneOf"
894
+ (ngModelChange)="onOneOfChange()"
895
+ >
896
+ <option *ngFor="let option of schema.oneOf; let i = index" [ngValue]="i">
897
+ {{ option.title || ('Option ' + (i + 1)) }}
898
+ </option>
899
+ </select>
900
+
901
+ <jsm-schema-node
902
+ *ngIf="activeVariant"
903
+ [schema]="activeVariant"
904
+ [control]="control"
905
+ [parent]="parent"
906
+ [controlKey]="controlKey"
907
+ [path]="path"
908
+ [errorsMap]="errorsMap"
909
+ [label]="label"
910
+ [required]="required"
911
+ (controlReplaced)="controlReplaced.emit($event)"
912
+ ></jsm-schema-node>
913
+ </div>
914
+
915
+ <div *ngSwitchCase="'anyOf'" class="space-y-3">
916
+ <div class="flex items-center justify-between">
917
+ <div>
918
+ <div class="text-sm font-semibold text-slate-800">{{ schema.title || label }}</div>
919
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
920
+ </div>
921
+ <span class="text-xs text-slate-400">anyOf</span>
922
+ </div>
923
+
924
+ <div class="flex flex-col gap-2">
925
+ <label
926
+ *ngFor="let option of schema.anyOf; let i = index"
927
+ class="inline-flex items-center gap-2 text-sm text-slate-700"
928
+ >
929
+ <input
930
+ type="checkbox"
931
+ class="h-4 w-4 rounded border-slate-300 text-slate-900"
932
+ [checked]="selectedAnyOf.has(i)"
933
+ (change)="toggleAnyOf(i)"
934
+ />
935
+ {{ option.title || ('Option ' + (i + 1)) }}
936
+ </label>
937
+ </div>
938
+
939
+ <jsm-schema-node
940
+ *ngIf="activeVariant"
941
+ [schema]="activeVariant"
942
+ [control]="control"
943
+ [parent]="parent"
944
+ [controlKey]="controlKey"
945
+ [path]="path"
946
+ [errorsMap]="errorsMap"
947
+ [label]="label"
948
+ [required]="required"
949
+ (controlReplaced)="controlReplaced.emit($event)"
950
+ ></jsm-schema-node>
951
+ </div>
952
+
953
+ <div *ngSwitchCase="'allOf'" class="space-y-4">
954
+ <div class="flex items-center justify-between">
955
+ <div>
956
+ <div class="text-sm font-semibold text-slate-800">{{ schema.title || label }}</div>
957
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
958
+ </div>
959
+ <span class="text-xs text-slate-400">allOf</span>
960
+ </div>
961
+
962
+ <div *ngFor="let subSchema of mergedAllOf; let i = index" class="rounded-xl border border-slate-200 p-3">
963
+ <jsm-schema-node
964
+ [schema]="subSchema"
965
+ [control]="control"
966
+ [parent]="parent"
967
+ [controlKey]="controlKey"
968
+ [path]="path"
969
+ [errorsMap]="errorsMap"
970
+ [label]="subSchema.title || ('Section ' + (i + 1))"
971
+ [required]="required"
972
+ (controlReplaced)="controlReplaced.emit($event)"
973
+ ></jsm-schema-node>
974
+ </div>
975
+ </div>
976
+
977
+ <fieldset *ngSwitchCase="'object'" class="group border border-slate-200 rounded-2xl p-4 bg-white/70 shadow-sm transition hover:-translate-y-0.5 hover:shadow-md">
978
+ <legend class="px-2 text-sm font-semibold text-slate-700">{{ schema.title || label }}</legend>
979
+ <p *ngIf="schema.description" class="text-xs text-slate-500 mb-4">{{ schema.description }}</p>
980
+ <div *ngIf="metaBadges.length" class="flex flex-wrap gap-2 mb-4">
981
+ <span
982
+ *ngFor="let badge of metaBadges"
983
+ class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600"
984
+ >
985
+ {{ badge }}
986
+ </span>
987
+ </div>
988
+
989
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4" [formGroup]="controlAsGroup">
990
+ <ng-container *ngFor="let key of objectKeys">
991
+ <div class="flex flex-col gap-2">
992
+ <div class="flex items-center justify-between" *ngIf="isDynamicKey(key)">
993
+ <span class="text-xs font-semibold text-slate-500">{{ key }}</span>
994
+ <button
995
+ type="button"
996
+ class="text-xs font-semibold text-rose-600 transition hover:text-rose-700"
997
+ (click)="removeDynamicKey(key)"
998
+ >
999
+ Remove
1000
+ </button>
1001
+ </div>
1002
+ <jsm-schema-node
1003
+ [schema]="schemaForKey(key)"
1004
+ [control]="childControl(key)"
1005
+ [parent]="controlAsGroup"
1006
+ [controlKey]="key"
1007
+ [path]="pathForChild(key)"
1008
+ [errorsMap]="errorsMap"
1009
+ [label]="key"
1010
+ [required]="isRequired(key)"
1011
+ (controlReplaced)="controlReplaced.emit($event)"
1012
+ ></jsm-schema-node>
1013
+ </div>
1014
+ </ng-container>
1015
+ </div>
1016
+
1017
+ <div class="mt-4 space-y-2" *ngIf="canAddProperty">
1018
+ <div class="text-xs font-semibold text-slate-500">Add property</div>
1019
+ <div class="flex flex-col md:flex-row gap-2">
1020
+ <input
1021
+ class="flex-1 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1022
+ [(ngModel)]="newPropertyKey"
1023
+ placeholder="propertyName"
1024
+ />
1025
+ <button
1026
+ type="button"
1027
+ class="px-3 py-2 rounded-lg text-sm font-medium bg-slate-900 text-white shadow-sm transition hover:-translate-y-0.5 hover:bg-slate-800 hover:shadow-md"
1028
+ (click)="addDynamicProperty()"
1029
+ >
1030
+ Add
1031
+ </button>
1032
+ </div>
1033
+ <div *ngIf="propertyError" class="text-xs text-rose-600">{{ propertyError }}</div>
1034
+ </div>
1035
+
1036
+ <div *ngIf="errorsForPath.length" class="mt-3 text-xs text-rose-600 space-y-1">
1037
+ <div *ngFor="let error of errorsForPath">{{ error }}</div>
1038
+ </div>
1039
+ </fieldset>
1040
+
1041
+ <div *ngSwitchCase="'array'" class="space-y-3">
1042
+ <div class="flex items-center justify-between">
1043
+ <div>
1044
+ <div class="text-sm font-semibold text-slate-700">{{ schema.title || label }}</div>
1045
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1046
+ </div>
1047
+ <button
1048
+ type="button"
1049
+ class="px-3 py-1.5 rounded-full text-sm font-medium bg-slate-900 text-white shadow-sm transition hover:-translate-y-0.5 hover:bg-slate-800 hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed"
1050
+ (click)="addArrayItem()"
1051
+ [disabled]="!canAddArrayItem"
1052
+ >
1053
+ Add
1054
+ </button>
1055
+ </div>
1056
+
1057
+ <div class="text-xs text-slate-500" *ngIf="effectiveSchema.contains">
1058
+ Must contain item(s) that match the contains schema.
1059
+ <span *ngIf="effectiveSchema.minContains">Min: {{ effectiveSchema.minContains }}</span>
1060
+ <span *ngIf="effectiveSchema.maxContains">Max: {{ effectiveSchema.maxContains }}</span>
1061
+ </div>
1062
+
1063
+ <div *ngIf="metaBadges.length" class="flex flex-wrap gap-2">
1064
+ <span
1065
+ *ngFor="let badge of metaBadges"
1066
+ class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600"
1067
+ >
1068
+ {{ badge }}
1069
+ </span>
1070
+ </div>
1071
+
1072
+ <div class="space-y-3" *ngIf="arrayControls.length > 0; else emptyArray">
1073
+ <div
1074
+ *ngFor="let item of arrayControls; let i = index"
1075
+ class="rounded-2xl border border-slate-200 p-3 bg-white shadow-sm transition hover:-translate-y-0.5 hover:shadow-md"
1076
+ >
1077
+ <div class="flex items-center justify-between mb-2">
1078
+ <span class="text-xs font-semibold text-slate-500">Item {{ i + 1 }}</span>
1079
+ <button
1080
+ type="button"
1081
+ class="text-xs font-semibold text-rose-600 transition hover:text-rose-700"
1082
+ (click)="removeArrayItem(i)"
1083
+ >
1084
+ Remove
1085
+ </button>
1086
+ </div>
1087
+ <jsm-schema-node
1088
+ [schema]="schemaForIndex(i)"
1089
+ [control]="item"
1090
+ [parent]="controlAsArray"
1091
+ [controlKey]="i"
1092
+ [path]="pathForChild(i)"
1093
+ [errorsMap]="errorsMap"
1094
+ [label]="label + ' item'"
1095
+ (controlReplaced)="controlReplaced.emit($event)"
1096
+ ></jsm-schema-node>
1097
+ </div>
1098
+ </div>
1099
+
1100
+ <ng-template #emptyArray>
1101
+ <div class="text-xs text-slate-400 italic">No items yet.</div>
1102
+ </ng-template>
1103
+
1104
+ <div *ngIf="errorsForPath.length" class="text-xs text-rose-600 space-y-1">
1105
+ <div *ngFor="let error of errorsForPath">{{ error }}</div>
1106
+ </div>
1107
+ </div>
1108
+
1109
+ <div *ngSwitchDefault class="space-y-2">
1110
+ <ng-container *ngIf="inputKind !== 'checkbox'">
1111
+ <label class="text-sm font-medium text-slate-700">
1112
+ {{ schema.title || label }}
1113
+ <span *ngIf="required" class="text-rose-500">*</span>
1114
+ </label>
1115
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1116
+ </ng-container>
1117
+
1118
+ <div *ngIf="metaBadges.length" class="flex flex-wrap gap-2">
1119
+ <span
1120
+ *ngFor="let badge of metaBadges"
1121
+ class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600"
1122
+ >
1123
+ {{ badge }}
1124
+ </span>
1125
+ </div>
1126
+
1127
+ <ng-container [ngSwitch]="inputKind">
1128
+ <input
1129
+ *ngSwitchCase="'text'"
1130
+ class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1131
+ [attr.type]="inputType"
1132
+ [attr.placeholder]="schema.title || label"
1133
+ [formControl]="controlAsFormControl"
1134
+ />
1135
+
1136
+ <textarea
1137
+ *ngSwitchCase="'textarea'"
1138
+ rows="4"
1139
+ class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1140
+ [attr.placeholder]="schema.title || label"
1141
+ [formControl]="controlAsFormControl"
1142
+ ></textarea>
1143
+
1144
+ <select
1145
+ *ngSwitchCase="'select'"
1146
+ class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1147
+ [formControl]="controlAsFormControl"
1148
+ >
1149
+ <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1150
+ </select>
1151
+
1152
+ <label *ngSwitchCase="'checkbox'" class="inline-flex items-center gap-2 text-sm text-slate-700">
1153
+ <input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-slate-900 transition" [formControl]="controlAsFormControl" />
1154
+ <span>
1155
+ {{ schema.title || label }}
1156
+ <span *ngIf="required" class="text-rose-500">*</span>
1157
+ </span>
1158
+ </label>
1159
+ </ng-container>
1160
+
1161
+ <p *ngIf="inputKind === 'checkbox' && schema.description" class="text-xs text-slate-500">
1162
+ {{ schema.description }}
1163
+ </p>
1164
+
1165
+ <div *ngIf="effectiveSchema.examples?.length" class="text-xs text-slate-400">
1166
+ Example: {{ effectiveSchema.examples?.[0] }}
1167
+ </div>
1168
+
1169
+ <div *ngIf="effectiveSchema.contentMediaType" class="text-xs text-slate-400">
1170
+ Content type: {{ effectiveSchema.contentMediaType }}
1171
+ <span *ngIf="effectiveSchema.contentEncoding">({{ effectiveSchema.contentEncoding }})</span>
1172
+ </div>
1173
+
1174
+ <div *ngIf="errorsForPath.length" class="text-xs text-rose-600 space-y-1">
1175
+ <div *ngFor="let error of errorsForPath">{{ error }}</div>
1176
+ </div>
1177
+ </div>
1178
+ </ng-container>
1179
+ `, isInline: true, dependencies: [{ kind: "component", type: JsonSchemaNodeComponent, selector: "jsm-schema-node", inputs: ["schema", "control", "label", "required", "path", "errorsMap", "parent", "controlKey"], outputs: ["controlReplaced"] }, { 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: "directive", type: i3.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i3.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i3.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1180
+ }
1181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
1182
+ type: Component,
1183
+ args: [{
1184
+ selector: 'jsm-schema-node',
1185
+ standalone: true,
1186
+ imports: [CommonModule, FormsModule, ReactiveFormsModule],
1187
+ template: `
1188
+ <ng-container [ngSwitch]="nodeKind">
1189
+ <div *ngSwitchCase="'oneOf'" class="space-y-3">
1190
+ <div class="flex items-center justify-between">
1191
+ <div>
1192
+ <div class="text-sm font-semibold text-slate-800">{{ schema.title || label }}</div>
1193
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1194
+ </div>
1195
+ <span class="text-xs text-slate-400">oneOf</span>
1196
+ </div>
1197
+
1198
+ <select
1199
+ class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-slate-900"
1200
+ [(ngModel)]="selectedOneOf"
1201
+ (ngModelChange)="onOneOfChange()"
1202
+ >
1203
+ <option *ngFor="let option of schema.oneOf; let i = index" [ngValue]="i">
1204
+ {{ option.title || ('Option ' + (i + 1)) }}
1205
+ </option>
1206
+ </select>
1207
+
1208
+ <jsm-schema-node
1209
+ *ngIf="activeVariant"
1210
+ [schema]="activeVariant"
1211
+ [control]="control"
1212
+ [parent]="parent"
1213
+ [controlKey]="controlKey"
1214
+ [path]="path"
1215
+ [errorsMap]="errorsMap"
1216
+ [label]="label"
1217
+ [required]="required"
1218
+ (controlReplaced)="controlReplaced.emit($event)"
1219
+ ></jsm-schema-node>
1220
+ </div>
1221
+
1222
+ <div *ngSwitchCase="'anyOf'" class="space-y-3">
1223
+ <div class="flex items-center justify-between">
1224
+ <div>
1225
+ <div class="text-sm font-semibold text-slate-800">{{ schema.title || label }}</div>
1226
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1227
+ </div>
1228
+ <span class="text-xs text-slate-400">anyOf</span>
1229
+ </div>
1230
+
1231
+ <div class="flex flex-col gap-2">
1232
+ <label
1233
+ *ngFor="let option of schema.anyOf; let i = index"
1234
+ class="inline-flex items-center gap-2 text-sm text-slate-700"
1235
+ >
1236
+ <input
1237
+ type="checkbox"
1238
+ class="h-4 w-4 rounded border-slate-300 text-slate-900"
1239
+ [checked]="selectedAnyOf.has(i)"
1240
+ (change)="toggleAnyOf(i)"
1241
+ />
1242
+ {{ option.title || ('Option ' + (i + 1)) }}
1243
+ </label>
1244
+ </div>
1245
+
1246
+ <jsm-schema-node
1247
+ *ngIf="activeVariant"
1248
+ [schema]="activeVariant"
1249
+ [control]="control"
1250
+ [parent]="parent"
1251
+ [controlKey]="controlKey"
1252
+ [path]="path"
1253
+ [errorsMap]="errorsMap"
1254
+ [label]="label"
1255
+ [required]="required"
1256
+ (controlReplaced)="controlReplaced.emit($event)"
1257
+ ></jsm-schema-node>
1258
+ </div>
1259
+
1260
+ <div *ngSwitchCase="'allOf'" class="space-y-4">
1261
+ <div class="flex items-center justify-between">
1262
+ <div>
1263
+ <div class="text-sm font-semibold text-slate-800">{{ schema.title || label }}</div>
1264
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1265
+ </div>
1266
+ <span class="text-xs text-slate-400">allOf</span>
1267
+ </div>
1268
+
1269
+ <div *ngFor="let subSchema of mergedAllOf; let i = index" class="rounded-xl border border-slate-200 p-3">
1270
+ <jsm-schema-node
1271
+ [schema]="subSchema"
1272
+ [control]="control"
1273
+ [parent]="parent"
1274
+ [controlKey]="controlKey"
1275
+ [path]="path"
1276
+ [errorsMap]="errorsMap"
1277
+ [label]="subSchema.title || ('Section ' + (i + 1))"
1278
+ [required]="required"
1279
+ (controlReplaced)="controlReplaced.emit($event)"
1280
+ ></jsm-schema-node>
1281
+ </div>
1282
+ </div>
1283
+
1284
+ <fieldset *ngSwitchCase="'object'" class="group border border-slate-200 rounded-2xl p-4 bg-white/70 shadow-sm transition hover:-translate-y-0.5 hover:shadow-md">
1285
+ <legend class="px-2 text-sm font-semibold text-slate-700">{{ schema.title || label }}</legend>
1286
+ <p *ngIf="schema.description" class="text-xs text-slate-500 mb-4">{{ schema.description }}</p>
1287
+ <div *ngIf="metaBadges.length" class="flex flex-wrap gap-2 mb-4">
1288
+ <span
1289
+ *ngFor="let badge of metaBadges"
1290
+ class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600"
1291
+ >
1292
+ {{ badge }}
1293
+ </span>
1294
+ </div>
1295
+
1296
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4" [formGroup]="controlAsGroup">
1297
+ <ng-container *ngFor="let key of objectKeys">
1298
+ <div class="flex flex-col gap-2">
1299
+ <div class="flex items-center justify-between" *ngIf="isDynamicKey(key)">
1300
+ <span class="text-xs font-semibold text-slate-500">{{ key }}</span>
1301
+ <button
1302
+ type="button"
1303
+ class="text-xs font-semibold text-rose-600 transition hover:text-rose-700"
1304
+ (click)="removeDynamicKey(key)"
1305
+ >
1306
+ Remove
1307
+ </button>
1308
+ </div>
1309
+ <jsm-schema-node
1310
+ [schema]="schemaForKey(key)"
1311
+ [control]="childControl(key)"
1312
+ [parent]="controlAsGroup"
1313
+ [controlKey]="key"
1314
+ [path]="pathForChild(key)"
1315
+ [errorsMap]="errorsMap"
1316
+ [label]="key"
1317
+ [required]="isRequired(key)"
1318
+ (controlReplaced)="controlReplaced.emit($event)"
1319
+ ></jsm-schema-node>
1320
+ </div>
1321
+ </ng-container>
1322
+ </div>
1323
+
1324
+ <div class="mt-4 space-y-2" *ngIf="canAddProperty">
1325
+ <div class="text-xs font-semibold text-slate-500">Add property</div>
1326
+ <div class="flex flex-col md:flex-row gap-2">
1327
+ <input
1328
+ class="flex-1 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1329
+ [(ngModel)]="newPropertyKey"
1330
+ placeholder="propertyName"
1331
+ />
1332
+ <button
1333
+ type="button"
1334
+ class="px-3 py-2 rounded-lg text-sm font-medium bg-slate-900 text-white shadow-sm transition hover:-translate-y-0.5 hover:bg-slate-800 hover:shadow-md"
1335
+ (click)="addDynamicProperty()"
1336
+ >
1337
+ Add
1338
+ </button>
1339
+ </div>
1340
+ <div *ngIf="propertyError" class="text-xs text-rose-600">{{ propertyError }}</div>
1341
+ </div>
1342
+
1343
+ <div *ngIf="errorsForPath.length" class="mt-3 text-xs text-rose-600 space-y-1">
1344
+ <div *ngFor="let error of errorsForPath">{{ error }}</div>
1345
+ </div>
1346
+ </fieldset>
1347
+
1348
+ <div *ngSwitchCase="'array'" class="space-y-3">
1349
+ <div class="flex items-center justify-between">
1350
+ <div>
1351
+ <div class="text-sm font-semibold text-slate-700">{{ schema.title || label }}</div>
1352
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1353
+ </div>
1354
+ <button
1355
+ type="button"
1356
+ class="px-3 py-1.5 rounded-full text-sm font-medium bg-slate-900 text-white shadow-sm transition hover:-translate-y-0.5 hover:bg-slate-800 hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed"
1357
+ (click)="addArrayItem()"
1358
+ [disabled]="!canAddArrayItem"
1359
+ >
1360
+ Add
1361
+ </button>
1362
+ </div>
1363
+
1364
+ <div class="text-xs text-slate-500" *ngIf="effectiveSchema.contains">
1365
+ Must contain item(s) that match the contains schema.
1366
+ <span *ngIf="effectiveSchema.minContains">Min: {{ effectiveSchema.minContains }}</span>
1367
+ <span *ngIf="effectiveSchema.maxContains">Max: {{ effectiveSchema.maxContains }}</span>
1368
+ </div>
1369
+
1370
+ <div *ngIf="metaBadges.length" class="flex flex-wrap gap-2">
1371
+ <span
1372
+ *ngFor="let badge of metaBadges"
1373
+ class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600"
1374
+ >
1375
+ {{ badge }}
1376
+ </span>
1377
+ </div>
1378
+
1379
+ <div class="space-y-3" *ngIf="arrayControls.length > 0; else emptyArray">
1380
+ <div
1381
+ *ngFor="let item of arrayControls; let i = index"
1382
+ class="rounded-2xl border border-slate-200 p-3 bg-white shadow-sm transition hover:-translate-y-0.5 hover:shadow-md"
1383
+ >
1384
+ <div class="flex items-center justify-between mb-2">
1385
+ <span class="text-xs font-semibold text-slate-500">Item {{ i + 1 }}</span>
1386
+ <button
1387
+ type="button"
1388
+ class="text-xs font-semibold text-rose-600 transition hover:text-rose-700"
1389
+ (click)="removeArrayItem(i)"
1390
+ >
1391
+ Remove
1392
+ </button>
1393
+ </div>
1394
+ <jsm-schema-node
1395
+ [schema]="schemaForIndex(i)"
1396
+ [control]="item"
1397
+ [parent]="controlAsArray"
1398
+ [controlKey]="i"
1399
+ [path]="pathForChild(i)"
1400
+ [errorsMap]="errorsMap"
1401
+ [label]="label + ' item'"
1402
+ (controlReplaced)="controlReplaced.emit($event)"
1403
+ ></jsm-schema-node>
1404
+ </div>
1405
+ </div>
1406
+
1407
+ <ng-template #emptyArray>
1408
+ <div class="text-xs text-slate-400 italic">No items yet.</div>
1409
+ </ng-template>
1410
+
1411
+ <div *ngIf="errorsForPath.length" class="text-xs text-rose-600 space-y-1">
1412
+ <div *ngFor="let error of errorsForPath">{{ error }}</div>
1413
+ </div>
1414
+ </div>
1415
+
1416
+ <div *ngSwitchDefault class="space-y-2">
1417
+ <ng-container *ngIf="inputKind !== 'checkbox'">
1418
+ <label class="text-sm font-medium text-slate-700">
1419
+ {{ schema.title || label }}
1420
+ <span *ngIf="required" class="text-rose-500">*</span>
1421
+ </label>
1422
+ <p *ngIf="schema.description" class="text-xs text-slate-500">{{ schema.description }}</p>
1423
+ </ng-container>
1424
+
1425
+ <div *ngIf="metaBadges.length" class="flex flex-wrap gap-2">
1426
+ <span
1427
+ *ngFor="let badge of metaBadges"
1428
+ class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600"
1429
+ >
1430
+ {{ badge }}
1431
+ </span>
1432
+ </div>
1433
+
1434
+ <ng-container [ngSwitch]="inputKind">
1435
+ <input
1436
+ *ngSwitchCase="'text'"
1437
+ class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1438
+ [attr.type]="inputType"
1439
+ [attr.placeholder]="schema.title || label"
1440
+ [formControl]="controlAsFormControl"
1441
+ />
1442
+
1443
+ <textarea
1444
+ *ngSwitchCase="'textarea'"
1445
+ rows="4"
1446
+ class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1447
+ [attr.placeholder]="schema.title || label"
1448
+ [formControl]="controlAsFormControl"
1449
+ ></textarea>
1450
+
1451
+ <select
1452
+ *ngSwitchCase="'select'"
1453
+ class="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition focus:outline-none focus:ring-2 focus:ring-slate-900 hover:border-slate-400"
1454
+ [formControl]="controlAsFormControl"
1455
+ >
1456
+ <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1457
+ </select>
1458
+
1459
+ <label *ngSwitchCase="'checkbox'" class="inline-flex items-center gap-2 text-sm text-slate-700">
1460
+ <input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-slate-900 transition" [formControl]="controlAsFormControl" />
1461
+ <span>
1462
+ {{ schema.title || label }}
1463
+ <span *ngIf="required" class="text-rose-500">*</span>
1464
+ </span>
1465
+ </label>
1466
+ </ng-container>
1467
+
1468
+ <p *ngIf="inputKind === 'checkbox' && schema.description" class="text-xs text-slate-500">
1469
+ {{ schema.description }}
1470
+ </p>
1471
+
1472
+ <div *ngIf="effectiveSchema.examples?.length" class="text-xs text-slate-400">
1473
+ Example: {{ effectiveSchema.examples?.[0] }}
1474
+ </div>
1475
+
1476
+ <div *ngIf="effectiveSchema.contentMediaType" class="text-xs text-slate-400">
1477
+ Content type: {{ effectiveSchema.contentMediaType }}
1478
+ <span *ngIf="effectiveSchema.contentEncoding">({{ effectiveSchema.contentEncoding }})</span>
1479
+ </div>
1480
+
1481
+ <div *ngIf="errorsForPath.length" class="text-xs text-rose-600 space-y-1">
1482
+ <div *ngFor="let error of errorsForPath">{{ error }}</div>
1483
+ </div>
1484
+ </div>
1485
+ </ng-container>
1486
+ `,
1487
+ changeDetection: ChangeDetectionStrategy.OnPush,
1488
+ }]
1489
+ }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaValidationService }], propDecorators: { schema: [{
1490
+ type: Input,
1491
+ args: [{ required: true }]
1492
+ }], control: [{
1493
+ type: Input,
1494
+ args: [{ required: true }]
1495
+ }], label: [{
1496
+ type: Input
1497
+ }], required: [{
1498
+ type: Input
1499
+ }], path: [{
1500
+ type: Input
1501
+ }], errorsMap: [{
1502
+ type: Input
1503
+ }], parent: [{
1504
+ type: Input
1505
+ }], controlKey: [{
1506
+ type: Input
1507
+ }], controlReplaced: [{
1508
+ type: Output
1509
+ }] } });
1510
+
1511
+ class JsonSchemaFormComponent {
1512
+ schemaService;
1513
+ resolver;
1514
+ validation;
1515
+ schema;
1516
+ value;
1517
+ data;
1518
+ formReady = new EventEmitter();
1519
+ valueChange = new EventEmitter();
1520
+ schemaReady = new EventEmitter();
1521
+ form;
1522
+ rootControl;
1523
+ resolvedSchema;
1524
+ errorsMap = new Map();
1525
+ loading = false;
1526
+ valueSub;
1527
+ constructor(schemaService, resolver, validation) {
1528
+ this.schemaService = schemaService;
1529
+ this.resolver = resolver;
1530
+ this.validation = validation;
1531
+ }
1532
+ ngOnChanges(changes) {
1533
+ if (changes['schema'] || changes['value'] || changes['data']) {
1534
+ void this.buildForm();
1535
+ }
1536
+ }
1537
+ ngOnDestroy() {
1538
+ this.valueSub?.unsubscribe();
1539
+ }
1540
+ async buildForm() {
1541
+ this.valueSub?.unsubscribe();
1542
+ this.loading = true;
1543
+ this.resolvedSchema = await this.resolver.resolve(this.schema, this.schema?.$id);
1544
+ this.schemaReady.emit(this.resolvedSchema);
1545
+ const initialData = this.value !== undefined ? this.value : this.data;
1546
+ this.rootControl = this.schemaService.buildControl(this.resolvedSchema, initialData);
1547
+ this.form = this.rootControl instanceof FormGroup ? this.rootControl : new FormGroup({ value: this.rootControl });
1548
+ this.formReady.emit(this.form);
1549
+ this.loading = false;
1550
+ this.attachValueSub();
1551
+ }
1552
+ updateErrors() {
1553
+ if (!this.resolvedSchema) {
1554
+ this.errorsMap = new Map();
1555
+ return;
1556
+ }
1557
+ const errors = this.validation.validate(this.resolvedSchema, this.rootControl.value);
1558
+ const map = new Map();
1559
+ for (const error of errors) {
1560
+ const path = error.instancePath ?? '';
1561
+ const messages = map.get(path) ?? [];
1562
+ messages.push(error.message ?? error.keyword);
1563
+ map.set(path, messages);
1564
+ }
1565
+ this.errorsMap = map;
1566
+ }
1567
+ onRootReplaced(control) {
1568
+ this.rootControl = control;
1569
+ if (control instanceof FormGroup) {
1570
+ this.form = control;
1571
+ }
1572
+ else {
1573
+ if (!(this.form instanceof FormGroup) || !this.form.contains('value')) {
1574
+ this.form = new FormGroup({ value: control });
1575
+ }
1576
+ else {
1577
+ this.form.setControl('value', control);
1578
+ }
1579
+ }
1580
+ this.attachValueSub();
1581
+ }
1582
+ attachValueSub() {
1583
+ this.valueSub?.unsubscribe();
1584
+ this.updateErrors();
1585
+ this.valueSub = this.form.valueChanges.subscribe(() => {
1586
+ this.updateErrors();
1587
+ this.valueChange.emit(this.rootControl.value);
1588
+ });
1589
+ }
1590
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, deps: [{ token: JsonSchemaFormService }, { token: JsonSchemaResolverService }, { token: JsonSchemaValidationService }], target: i0.ɵɵFactoryTarget.Component });
1591
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: JsonSchemaFormComponent, isStandalone: true, selector: "jsm-json-schema-form", inputs: { schema: "schema", value: "value", data: "data" }, outputs: { formReady: "formReady", valueChange: "valueChange", schemaReady: "schemaReady" }, usesOnChanges: true, ngImport: i0, template: `
1592
+ <form class="w-full max-w-4xl mx-auto p-4 md:p-6 space-y-6">
1593
+ <div class="space-y-1">
1594
+ <h2 class="text-xl font-semibold text-slate-900">{{ resolvedSchema?.title || 'Generated Form' }}</h2>
1595
+ <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500">
1596
+ {{ resolvedSchema?.description }}
1597
+ </p>
1598
+ </div>
1599
+
1600
+ <div *ngIf="loading" class="text-sm text-slate-500">Resolving schema...</div>
1601
+
1602
+ <div *ngIf="form && resolvedSchema" class="space-y-6" [formGroup]="form">
1603
+ <jsm-schema-node
1604
+ [schema]="resolvedSchema"
1605
+ [control]="rootControl"
1606
+ [path]="''"
1607
+ [errorsMap]="errorsMap"
1608
+ label="Root"
1609
+ (controlReplaced)="onRootReplaced($event)"
1610
+ ></jsm-schema-node>
1611
+ </div>
1612
+ </form>
1613
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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"], outputs: ["controlReplaced"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1614
+ }
1615
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
1616
+ type: Component,
1617
+ args: [{
1618
+ selector: 'jsm-json-schema-form',
1619
+ standalone: true,
1620
+ imports: [CommonModule, ReactiveFormsModule, JsonSchemaNodeComponent],
1621
+ template: `
1622
+ <form class="w-full max-w-4xl mx-auto p-4 md:p-6 space-y-6">
1623
+ <div class="space-y-1">
1624
+ <h2 class="text-xl font-semibold text-slate-900">{{ resolvedSchema?.title || 'Generated Form' }}</h2>
1625
+ <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500">
1626
+ {{ resolvedSchema?.description }}
1627
+ </p>
1628
+ </div>
1629
+
1630
+ <div *ngIf="loading" class="text-sm text-slate-500">Resolving schema...</div>
1631
+
1632
+ <div *ngIf="form && resolvedSchema" class="space-y-6" [formGroup]="form">
1633
+ <jsm-schema-node
1634
+ [schema]="resolvedSchema"
1635
+ [control]="rootControl"
1636
+ [path]="''"
1637
+ [errorsMap]="errorsMap"
1638
+ label="Root"
1639
+ (controlReplaced)="onRootReplaced($event)"
1640
+ ></jsm-schema-node>
1641
+ </div>
1642
+ </form>
1643
+ `,
1644
+ changeDetection: ChangeDetectionStrategy.OnPush,
1645
+ }]
1646
+ }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaResolverService }, { type: JsonSchemaValidationService }], propDecorators: { schema: [{
1647
+ type: Input,
1648
+ args: [{ required: true }]
1649
+ }], value: [{
1650
+ type: Input
1651
+ }], data: [{
1652
+ type: Input
1653
+ }], formReady: [{
1654
+ type: Output
1655
+ }], valueChange: [{
1656
+ type: Output
1657
+ }], schemaReady: [{
1658
+ type: Output
1659
+ }] } });
1660
+
1661
+ class JsonSchemaFormModule {
1662
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1663
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent], exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1664
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
1665
+ }
1666
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: JsonSchemaFormModule, decorators: [{
1667
+ type: NgModule,
1668
+ args: [{
1669
+ imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
1670
+ exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
1671
+ }]
1672
+ }] });
1673
+
1674
+ /**
1675
+ * Generated bundle index. Do not edit.
1676
+ */
1677
+
1678
+ export { JsonSchemaFormComponent, JsonSchemaFormModule, JsonSchemaFormService, JsonSchemaNodeComponent, JsonSchemaResolverService, JsonSchemaValidationService };
1679
+ //# sourceMappingURL=elle96-ng-json-schema-manager.mjs.map